Browse Source

runtime: blog-module: add benchmarks

conectado 4 years ago
parent
commit
a81531e672

+ 2 - 0
Cargo.lock

@@ -2351,6 +2351,7 @@ dependencies = [
  "pallet-authorship",
  "pallet-babe",
  "pallet-balances",
+ "pallet-blog",
  "pallet-common",
  "pallet-constitution",
  "pallet-content-directory",
@@ -3738,6 +3739,7 @@ dependencies = [
 name = "pallet-blog"
 version = "3.0.0"
 dependencies = [
+ "frame-benchmarking",
  "frame-support",
  "frame-system",
  "pallet-balances",

+ 4 - 0
runtime-modules/blog/Cargo.toml

@@ -12,6 +12,9 @@ frame-system = { package = 'frame-system', default-features = false, git = 'http
 codec = { package = 'parity-scale-codec', version = '1.0.0', default-features = false, features = ['derive'] }
 sp-arithmetic = { package = 'sp-arithmetic', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 
+#Benchmark dependencies
+frame-benchmarking = { package = 'frame-benchmarking', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca', optional = true}
+
 [dev-dependencies]
 sp-io = { package = 'sp-io', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
 sp-core = { package = 'sp-core', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
@@ -20,6 +23,7 @@ balances = { package = 'pallet-balances', default-features = false, git = 'https
 
 [features]
 default = ['std']
+runtime-benchmarks = ['frame-benchmarking']
 std = [
 	'codec/std',
 	'sp-std/std',

+ 312 - 0
runtime-modules/blog/src/benchmarking.rs

@@ -0,0 +1,312 @@
+#![cfg(feature = "runtime-benchmarks")]
+use super::*;
+use frame_benchmarking::{benchmarks_instance, Zero};
+use frame_system::Module as System;
+use frame_system::{EventRecord, RawOrigin};
+use sp_std::convert::TryInto;
+use Module as Blog;
+
+const MAX_BYTES: u32 = 16384;
+
+fn assert_last_event<T: Trait<I>, I: Instance>(generic_event: <T as Trait<I>>::Event) {
+    let events = System::<T>::events();
+    let system_event: <T as frame_system::Trait>::Event = generic_event.into();
+
+    assert!(!events.is_empty(), "There are no events in event queue");
+
+    // compare to the last event record
+    let EventRecord { event, .. } = &events[events.len() - 1];
+    assert_eq!(event, &system_event);
+}
+
+fn get_blog_owner<T: Trait<I>, I: Instance>() -> T::AccountId {
+    T::AccountId::default()
+}
+
+fn generate_post<T: Trait<I>, I: Instance>() -> (T::AccountId, T::PostId) {
+    let caller_id = get_blog_owner::<T, I>();
+    assert_eq!(Blog::<T, I>::post_count(), Zero::zero());
+
+    Blog::<T, I>::create_post(
+        RawOrigin::Signed(caller_id.clone()).into(),
+        vec![0u8],
+        vec![0u8],
+    )
+    .unwrap();
+
+    let post_id = T::PostId::zero();
+
+    assert_eq!(Blog::<T, I>::post_count(), One::one());
+
+    assert!(Blog::<T, I>::post_by_id(post_id) == Post::<T, I>::new(vec![0u8], vec![0u8]));
+
+    (caller_id, post_id)
+}
+
+fn generate_reply<T: Trait<I>, I: Instance>(
+    creator_id: T::AccountId,
+    post_id: T::PostId,
+) -> T::ReplyId {
+    let creator_origin = RawOrigin::Signed(creator_id);
+    let participant_id = Blog::<T, I>::get_participant(creator_origin.clone().into()).unwrap();
+    Blog::<T, I>::create_reply(creator_origin.clone().into(), post_id, None, vec![0u8]).unwrap();
+
+    assert!(
+        Blog::<T, I>::reply_by_id(post_id, T::ReplyId::zero())
+            == Reply::<T, I>::new(vec![0u8], participant_id, ParentId::Post(post_id))
+    );
+
+    T::ReplyId::zero()
+}
+
+benchmarks_instance! {
+    _ {}
+    create_post {
+        let t in 0 .. MAX_BYTES;
+        let b in 0 .. MAX_BYTES;
+        let caller_id = get_blog_owner::<T, I>();
+        assert_eq!(Blog::<T, I>::post_count(), Zero::zero());
+
+    }:_(RawOrigin::Signed(caller_id), vec![0u8; t.try_into().unwrap()], vec![0u8; b.try_into().unwrap()])
+    verify {
+        assert_eq!(Blog::<T, I>::post_count(), One::one());
+
+        assert!(
+            Blog::<T, I>::post_by_id(T::PostId::zero()) ==
+            Post::<T, I>::new(vec![0u8; t.try_into().unwrap()], vec![0u8; b.try_into().unwrap()])
+        );
+
+        assert_last_event::<T, I>(RawEvent::PostCreated(T::PostId::zero()).into());
+    }
+
+    lock_post {
+        let (creator_id, post_id) = generate_post::<T, I>();
+    }: _(RawOrigin::Signed(creator_id), post_id)
+    verify {
+        assert!(Blog::<T, I>::post_by_id(post_id).is_locked());
+        assert_last_event::<T, I>(RawEvent::PostLocked(post_id).into());
+    }
+
+    unlock_post {
+        let (creator_id, post_id) = generate_post::<T, I>();
+        Blog::<T, I>::lock_post(RawOrigin::Signed(creator_id.clone()).into(), post_id).unwrap();
+        assert!(Blog::<T, I>::post_by_id(post_id).is_locked());
+    }: _(RawOrigin::Signed(creator_id), post_id)
+    verify {
+        assert!(!Blog::<T, I>::post_by_id(post_id).is_locked());
+        assert_last_event::<T, I>(RawEvent::PostUnlocked(post_id).into());
+    }
+
+    edit_post {
+        let t in 0 .. MAX_BYTES;
+        let b in 0 .. MAX_BYTES;
+
+        let (creator_id, post_id) = generate_post::<T, I>();
+    }: _(
+        RawOrigin::Signed(creator_id),
+        post_id,
+        Some(vec![1u8; t.try_into().unwrap()]),
+        Some(vec![1u8; b.try_into().unwrap()])
+    )
+    verify {
+        assert!(
+            Blog::<T, I>::post_by_id(post_id) ==
+            Post::<T, I>::new(vec![1u8; t.try_into().unwrap()], vec![1u8; b.try_into().unwrap()])
+        );
+        assert_last_event::<T, I>(RawEvent::PostEdited(post_id).into());
+
+    }
+
+    create_reply_to_post {
+        let t in 0 .. MAX_BYTES;
+
+        let (creator_id, post_id) = generate_post::<T, I>();
+        let creator_origin = RawOrigin::Signed(creator_id);
+    }: create_reply(
+        creator_origin.clone(),
+        post_id,
+        None,
+        vec![0u8; t.try_into().unwrap()]
+    )
+    verify {
+        let mut expected_post = Post::<T, I>::new(vec![0u8], vec![0u8]);
+        let participant_id = Blog::<T, I>::get_participant(creator_origin.into()).unwrap();
+        expected_post.increment_replies_counter();
+        assert!(Blog::<T, I>::post_by_id(post_id) == expected_post);
+        assert!(
+            Blog::<T, I>::reply_by_id(post_id, T::ReplyId::zero()) ==
+            Reply::<T, I>::new(
+                vec![0u8; t.try_into().unwrap()],
+                participant_id,
+                ParentId::Post(post_id)
+            )
+        );
+
+        assert_last_event::<T, I>(RawEvent::ReplyCreated(participant_id, post_id, Zero::zero()).into());
+    }
+
+    create_reply_to_reply {
+        let t in 0 .. MAX_BYTES;
+
+        let (creator_id, post_id) = generate_post::<T, I>();
+        let reply_id = generate_reply::<T, I>(creator_id.clone(), post_id.clone());
+        let creator_origin = RawOrigin::Signed(creator_id);
+        let mut expected_post = Post::<T, I>::new(vec![0u8], vec![0u8]);
+        expected_post.increment_replies_counter();
+        assert!(Blog::<T, I>::post_by_id(post_id) == expected_post);
+    }: create_reply(
+        creator_origin.clone(),
+        post_id,
+        Some(reply_id),
+        vec![0u8; t.try_into().unwrap()]
+    )
+    verify {
+        let participant_id = Blog::<T, I>::get_participant(
+            creator_origin.clone().into()
+        ).unwrap();
+        expected_post.increment_replies_counter();
+        assert!(Blog::<T, I>::post_by_id(post_id) == expected_post);
+        assert!(
+            Blog::<T, I>::reply_by_id(post_id, T::ReplyId::one()) ==
+            Reply::<T, I>::new(
+                vec![0u8; t.try_into().unwrap()],
+                participant_id,
+                ParentId::Reply(reply_id)
+            )
+        );
+
+        assert_last_event::<T, I>(
+            RawEvent::DirectReplyCreated(participant_id, post_id, reply_id, One::one()).into()
+        );
+    }
+
+    edit_reply {
+        let t in 0 .. MAX_BYTES;
+
+        let (creator_id, post_id) = generate_post::<T, I>();
+        let reply_id = generate_reply::<T, I>(creator_id.clone(), post_id.clone());
+        let creator_origin = RawOrigin::Signed(creator_id);
+    }: _(
+        creator_origin.clone(),
+        post_id,
+        reply_id,
+        vec![1u8; t.try_into().unwrap()]
+    )
+    verify {
+        let participant_id = Blog::<T, I>::get_participant(
+            creator_origin.clone().into()
+        ).unwrap();
+        assert_eq!(
+            Blog::<T, I>::reply_by_id(post_id, reply_id).text_hash,
+            T::Hashing::hash(&vec![1u8; t.try_into().unwrap()])
+        );
+        assert!(
+            Blog::<T, I>::reply_by_id(post_id, reply_id) ==
+            Reply::<T, I>::new(
+                vec![1u8; t.try_into().unwrap()],
+                participant_id,
+                ParentId::Post(post_id)
+            )
+        );
+
+        assert_last_event::<T, I>(RawEvent::ReplyEdited(post_id, reply_id).into());
+    }
+
+    react_to_post {
+        let (creator_id, post_id) = generate_post::<T, I>();
+        let creator_origin = RawOrigin::Signed(creator_id);
+    }: react(
+        creator_origin.clone(),
+        0,
+        post_id,
+        None
+        )
+    verify {
+        let owner = Blog::<T, I>::get_participant(
+            creator_origin.clone().into()
+        ).unwrap();
+        assert_last_event::<T, I>(RawEvent::PostReactionsUpdated(owner, post_id, 0).into());
+    }
+
+    react_to_reply {
+        let (creator_id, post_id) = generate_post::<T, I>();
+        let reply_id = generate_reply::<T, I>(creator_id.clone(), post_id.clone());
+        let creator_origin = RawOrigin::Signed(creator_id);
+    }: react(
+        creator_origin.clone(),
+        0,
+        post_id,
+        Some(reply_id)
+    )
+    verify {
+        let owner = Blog::<T, I>::get_participant(
+            creator_origin.clone().into()
+        ).unwrap();
+        assert_last_event::<T, I>(
+            RawEvent::ReplyReactionsUpdated(owner, post_id, reply_id, 0).into()
+        );
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::mock::{ExtBuilder, Runtime};
+    use frame_support::assert_ok;
+
+    #[test]
+    fn test_create_post() {
+        ExtBuilder::default().build().execute_with(|| {
+            assert_ok!(test_benchmark_create_post::<Runtime>());
+        })
+    }
+
+    #[test]
+    fn test_lock_post() {
+        ExtBuilder::default().build().execute_with(|| {
+            assert_ok!(test_benchmark_lock_post::<Runtime>());
+        })
+    }
+
+    #[test]
+    fn test_unlock_post() {
+        ExtBuilder::default().build().execute_with(|| {
+            assert_ok!(test_benchmark_unlock_post::<Runtime>());
+        })
+    }
+
+    #[test]
+    fn test_edit_post() {
+        ExtBuilder::default().build().execute_with(|| {
+            assert_ok!(test_benchmark_edit_post::<Runtime>());
+        })
+    }
+
+    #[test]
+    fn test_create_reply_to_post() {
+        ExtBuilder::default().build().execute_with(|| {
+            assert_ok!(test_benchmark_create_reply_to_post::<Runtime>());
+        })
+    }
+
+    #[test]
+    fn test_create_reply_to_reply() {
+        ExtBuilder::default().build().execute_with(|| {
+            assert_ok!(test_benchmark_create_reply_to_reply::<Runtime>());
+        })
+    }
+
+    #[test]
+    fn test_edit_reply() {
+        ExtBuilder::default().build().execute_with(|| {
+            assert_ok!(test_benchmark_edit_reply::<Runtime>());
+        })
+    }
+
+    #[test]
+    fn test_react_to_post() {
+        ExtBuilder::default().build().execute_with(|| {
+            assert_ok!(test_benchmark_react_to_post::<Runtime>());
+        })
+    }
+}

+ 49 - 24
runtime-modules/blog/src/lib.rs

@@ -47,9 +47,10 @@ use frame_support::{
     decl_event, decl_module, decl_storage, ensure, traits::Get, Parameter, StorageDoubleMap,
 };
 use sp_arithmetic::traits::{BaseArithmetic, One};
-use sp_runtime::traits::{MaybeSerialize, MaybeSerializeDeserialize, Member};
+use sp_runtime::traits::{Hash, MaybeSerialize, MaybeSerializeDeserialize, Member};
 use sp_std::prelude::*;
 
+mod benchmarking;
 mod errors;
 mod mock;
 mod tests;
@@ -114,24 +115,35 @@ pub trait Trait<I: Instance = DefaultInstance>: frame_system::Trait {
 }
 
 /// Type, representing blog related post structure
-#[derive(Encode, Decode, Clone, PartialEq)]
 #[cfg_attr(feature = "std", derive(Debug))]
+#[derive(Encode, Decode, Clone)]
 pub struct Post<T: Trait<I>, I: Instance> {
     /// Locking status
     locked: bool,
-    title: Vec<u8>,
-    body: Vec<u8>,
+    title_hash: T::Hash,
+    body_hash: T::Hash,
     /// Overall replies counter, associated with post
     replies_count: T::ReplyId,
 }
 
+// Note: we derive it by hand because the derive wasn't working
+impl<T: Trait<I>, I: Instance> PartialEq for Post<T, I> {
+    fn eq(&self, other: &Post<T, I>) -> bool {
+        self.locked == other.locked
+            && self.title_hash == other.title_hash
+            && self.body_hash == other.body_hash
+            && self.replies_count == other.replies_count
+    }
+}
+
 /// Default Post
+// Note: We implement it by hand because it couldn't automatically derive it
 impl<T: Trait<I>, I: Instance> Default for Post<T, I> {
     fn default() -> Self {
         Post {
             locked: Default::default(),
-            title: Default::default(),
-            body: Default::default(),
+            title_hash: Default::default(),
+            body_hash: Default::default(),
             replies_count: Default::default(),
         }
     }
@@ -143,8 +155,8 @@ impl<T: Trait<I>, I: Instance> Post<T, I> {
         Self {
             // Post default locking status
             locked: false,
-            title,
-            body,
+            title_hash: T::Hashing::hash(&title),
+            body_hash: T::Hashing::hash(&body),
             // Set replies count of newly created post to zero
             replies_count: T::ReplyId::default(),
         }
@@ -178,10 +190,10 @@ impl<T: Trait<I>, I: Instance> Post<T, I> {
     /// Update post title and body, if Option::Some(_)
     fn update(&mut self, new_title: Option<Vec<u8>>, new_body: Option<Vec<u8>>) {
         if let Some(new_title) = new_title {
-            self.title = new_title
+            self.title_hash = T::Hashing::hash(&new_title)
         }
         if let Some(new_body) = new_body {
-            self.body = new_body
+            self.body_hash = T::Hashing::hash(&new_body)
         }
     }
 }
@@ -189,35 +201,44 @@ impl<T: Trait<I>, I: Instance> Post<T, I> {
 /// Enum variant, representing either reply or post id
 #[derive(Encode, Decode, Clone, PartialEq)]
 #[cfg_attr(feature = "std", derive(Debug))]
-pub enum ParentId<T: Trait<I>, I: Instance> {
-    Reply(T::ReplyId),
-    Post(T::PostId),
+pub enum ParentId<ReplyId, PostId: Default> {
+    Reply(ReplyId),
+    Post(PostId),
 }
 
 /// Default parent representation
-impl<T: Trait<I>, I: Instance> Default for ParentId<T, I> {
+impl<ReplyId, PostId: Default> Default for ParentId<ReplyId, PostId> {
     fn default() -> Self {
-        ParentId::Post(T::PostId::default())
+        ParentId::Post(PostId::default())
     }
 }
 
 /// Type, representing either root post reply or direct reply to reply
-#[derive(Encode, Decode, Clone, PartialEq)]
+#[derive(Encode, Decode, Clone)]
 #[cfg_attr(feature = "std", derive(Debug))]
 pub struct Reply<T: Trait<I>, I: Instance> {
-    /// Reply text content
-    text: Vec<u8>,
+    /// Reply text hash
+    text_hash: T::Hash,
     /// Participant id, associated with a reply owner
     owner: T::ParticipantId,
     /// Reply`s parent id
-    parent_id: ParentId<T, I>,
+    parent_id: ParentId<T::ReplyId, T::PostId>,
+}
+
+/// Reply comparator
+impl<T: Trait<I>, I: Instance> PartialEq for Reply<T, I> {
+    fn eq(&self, other: &Reply<T, I>) -> bool {
+        self.text_hash == other.text_hash
+            && self.owner == other.owner
+            && self.parent_id == other.parent_id
+    }
 }
 
 /// Default Reply
 impl<T: Trait<I>, I: Instance> Default for Reply<T, I> {
     fn default() -> Self {
         Reply {
-            text: Default::default(),
+            text_hash: Default::default(),
             owner: Default::default(),
             parent_id: Default::default(),
         }
@@ -226,9 +247,13 @@ impl<T: Trait<I>, I: Instance> Default for Reply<T, I> {
 
 impl<T: Trait<I>, I: Instance> Reply<T, I> {
     /// Create new reply with given text and owner id
-    fn new(text: Vec<u8>, owner: T::ParticipantId, parent_id: ParentId<T, I>) -> Self {
+    fn new(
+        text: Vec<u8>,
+        owner: T::ParticipantId,
+        parent_id: ParentId<T::ReplyId, T::PostId>,
+    ) -> Self {
         Self {
-            text,
+            text_hash: T::Hashing::hash(&text),
             owner,
             parent_id,
         }
@@ -241,7 +266,7 @@ impl<T: Trait<I>, I: Instance> Reply<T, I> {
 
     /// Update reply`s text
     fn update(&mut self, new_text: Vec<u8>) {
-        self.text = new_text
+        self.text_hash = T::Hashing::hash(&new_text)
     }
 }
 
@@ -506,7 +531,7 @@ decl_module! {
 
 impl<T: Trait<I>, I: Instance> Module<T, I> {
     // Get participant id from origin
-    fn get_participant(origin: T::Origin) -> Result<T::ParticipantId, &'static str> {
+    fn get_participant(origin: T::Origin) -> Result<T::ParticipantId, DispatchError> {
         Ok(T::ParticipantEnsureOrigin::ensure_origin(origin)?)
     }
 

+ 10 - 11
runtime-modules/blog/src/mock.rs

@@ -11,7 +11,7 @@ use sp_runtime::{
     DispatchResult, Perbill,
 };
 
-pub(crate) const FIRST_OWNER_ORIGIN: u64 = 1;
+pub(crate) const FIRST_OWNER_ORIGIN: u64 = 0;
 pub(crate) const SECOND_OWNER_ORIGIN: u64 = 2;
 
 impl_outer_origin! {
@@ -158,9 +158,13 @@ pub fn get_post(locked: bool) -> Post<Runtime, DefaultInstance> {
     post
 }
 
+fn generate_post() -> (Vec<u8>, Vec<u8>) {
+    (generate_text(10), generate_text(100))
+}
+
 pub fn create_post(origin_id: u64) -> DispatchResult {
-    let post = get_post(false);
-    TestBlogModule::create_post(Origin::signed(origin_id), post.title, post.body)
+    let (title, body) = generate_post();
+    TestBlogModule::create_post(Origin::signed(origin_id), title, body)
 }
 
 pub fn lock_post(origin_id: u64, post_id: <Runtime as Trait>::PostId) -> DispatchResult {
@@ -172,13 +176,8 @@ pub fn unlock_post(origin_id: u64, post_id: <Runtime as Trait>::PostId) -> Dispa
 }
 
 pub fn edit_post(origin_id: u64, post_id: <Runtime as Trait>::PostId) -> DispatchResult {
-    let post = get_post(false);
-    TestBlogModule::edit_post(
-        Origin::signed(origin_id),
-        post_id,
-        Some(post.title),
-        Some(post.body),
-    )
+    let (title, body) = generate_post();
+    TestBlogModule::edit_post(Origin::signed(origin_id), post_id, Some(title), Some(body))
 }
 
 // Replies
@@ -198,7 +197,7 @@ pub fn get_reply_text() -> Vec<u8> {
 
 pub fn get_reply(
     owner: <Runtime as frame_system::Trait>::AccountId,
-    parent_id: ParentId<Runtime, DefaultInstance>,
+    parent_id: ParentId<<Runtime as Trait>::ReplyId, <Runtime as Trait>::PostId>,
 ) -> Reply<Runtime, DefaultInstance> {
     let reply_text = get_reply_text();
     Reply::new(reply_text, owner, parent_id)

+ 1 - 1
runtime-modules/blog/src/tests.rs

@@ -40,7 +40,7 @@ fn assert_failure(
 fn ensure_replies_equality(
     reply: Option<Reply<Runtime, DefaultInstance>>,
     reply_owner_id: <Runtime as frame_system::Trait>::AccountId,
-    parent: ParentId<Runtime, DefaultInstance>,
+    parent: ParentId<<Runtime as Trait>::ReplyId, <Runtime as Trait>::PostId>,
 ) {
     // Ensure  stored reply is equal to expected one
     assert!(matches!(

+ 3 - 0
runtime/Cargo.toml

@@ -78,6 +78,7 @@ proposals-codex = { package = 'pallet-proposals-codex', default-features = false
 content-directory = { package = 'pallet-content-directory', default-features = false, path = '../runtime-modules/content-directory' }
 pallet_constitution = { package = 'pallet-constitution', default-features = false, path = '../runtime-modules/constitution' }
 staking-handler = { package = 'pallet-staking-handler', default-features = false, path = '../runtime-modules/staking-handler'}
+blog = { package = 'pallet-blog', default-features = false, path = '../runtime-modules/blog'}
 
 [dev-dependencies]
 sp-io = { package = 'sp-io', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'a200cdb93c6af5763b9c7bf313fa708764ac88ca'}
@@ -151,6 +152,7 @@ std = [
     'content-directory/std',
     'pallet_constitution/std',
     'staking-handler/std',
+    'blog/std',
 ]
 runtime-benchmarks = [
     "frame-system/runtime-benchmarks",
@@ -173,6 +175,7 @@ runtime-benchmarks = [
     "membership/runtime-benchmarks",
     "council/runtime-benchmarks",
     "referendum/runtime-benchmarks",
+    "blog/runtime-benchmarks",
     "hex-literal",
 ]