Proof of Existence Pallet
We have gone a long way since we built our very first Balances Pallet.
The structure of our Runtime and Pallets have evolved quite a bit since.
- Generic Types
- Config Trait
- Nested Dispatch
- and more...
This will be the last pallet we build for this tutorial, but we will build it knowing all of the tips and tricks we have learned so far.
The goal here is for you to ensure that all of the intricacies of Pallet development is well understood and that you are able to navigate all of the Rust code.
What is Proof of Existence?
The Proof of Existence Pallet uses the blockchain to provide a secure and immutable ledger that can be used to verify the existence of a particular document, file, or piece of data at a specific point in time.
Because the blockchain acts as an immutable ledger whose history cannot be changed, when some data is placed on the blockchain, it can be referenced at a future time to show that some data already existed in the past.
For example, imagine you discovered a cure to cancer, but before you reveal it, you want to make sure that you can prove when you had made the discovery. To do this, you could put some sort of data on the blockchain which represents the cure. At a later date, when you get your research published and reviewed, you would be able to use the blockchain as verifiable evidence of when you first made the discovery.
Normally, you would not put the raw contents of your claim on the blockchain but a hash of the data, which is both smaller and obfuscates the data in your claim before you are ready to reveal it.
However, for the purposes of this tutorial, we won't introduce hash functions yet.
Pallet Structure
The BTreeMap
is again the best tool to use for storing data in this Pallet. However, you will notice that the construction of the storage is a bit different than before. Rather than having a map from accounts to some data, we will actually map the content we want to claim to the user who owns it.
This construction of content -> account
allows an account to be the owner of multiple different claims, but having each claim only be owned by one user.
Create Your Pallet
Let's start to create this pallet:
-
Create a new file for your Proof of Existence Pallet.
touch src/proof_of_existence.rs
-
Copy the contents from the template into your new file.
-
Complete the
TODO
s to add a storage to your new pallet and allow it to be initialized. -
In your
main.rs
file, import theproof_of_existence
module.
Make sure that everything compiles after you complete these steps.
Compiler warnings about "never read/used" are okay.
mod balances; /* TODO: Import the `proof_of_existence` module. */ mod support; mod system; use crate::support::Dispatch; // These are the concrete types we will use in our simple state machine. // Modules are configured for these types directly, and they satisfy all of our // trait requirements. mod types { pub type AccountId = String; pub type Balance = u128; pub type BlockNumber = u32; pub type Nonce = u32; pub type Extrinsic = crate::support::Extrinsic<AccountId, crate::RuntimeCall>; pub type Header = crate::support::Header<BlockNumber>; pub type Block = crate::support::Block<Header, Extrinsic>; } // These are all the calls which are exposed to the world. // Note that it is just an accumulation of the calls exposed by each module. pub enum RuntimeCall { Balances(balances::Call<Runtime>), } // This is our main Runtime. // It accumulates all of the different pallets we want to use. #[derive(Debug)] pub struct Runtime { system: system::Pallet<Self>, balances: balances::Pallet<Self>, } impl system::Config for Runtime { type AccountId = types::AccountId; type BlockNumber = types::BlockNumber; type Nonce = types::Nonce; } impl balances::Config for Runtime { type Balance = types::Balance; } impl Runtime { // Create a new instance of the main Runtime, by creating a new instance of each pallet. fn new() -> Self { Self { system: system::Pallet::new(), balances: balances::Pallet::new() } } // Execute a block of extrinsics. Increments the block number. fn execute_block(&mut self, block: types::Block) -> support::DispatchResult { self.system.inc_block_number(); if block.header.block_number != self.system.block_number() { return Err("block number does not match what is expected"); } // An extrinsic error is not enough to trigger the block to be invalid. We capture the // result, and emit an error message if one is emitted. for (i, support::Extrinsic { caller, call }) in block.extrinsics.into_iter().enumerate() { self.system.inc_nonce(&caller); let _res = self.dispatch(caller, call).map_err(|e| { eprintln!( "Extrinsic Error\n\tBlock Number: {}\n\tExtrinsic Number: {}\n\tError: {}", block.header.block_number, i, e ) }); } Ok(()) } } impl crate::support::Dispatch for Runtime { type Caller = <Runtime as system::Config>::AccountId; type Call = RuntimeCall; // Dispatch a call on behalf of a caller. Increments the caller's nonce. // // Dispatch allows us to identify which underlying module call we want to execute. // Note that we extract the `caller` from the extrinsic, and use that information // to determine who we are executing the call on behalf of. fn dispatch( &mut self, caller: Self::Caller, runtime_call: Self::Call, ) -> support::DispatchResult { // This match statement will allow us to correctly route `RuntimeCall`s // to the appropriate pallet level function. match runtime_call { RuntimeCall::Balances(call) => { self.balances.dispatch(caller, call)?; }, } Ok(()) } } fn main() { // Create a new instance of the Runtime. // It will instantiate with it all the modules it uses. let mut runtime = Runtime::new(); let alice = "alice".to_string(); let bob = "bob".to_string(); let charlie = "charlie".to_string(); // Initialize the system with some initial balance. runtime.balances.set_balance(&alice, 100); // Here are the extrinsics in our block. // You can add or remove these based on the modules and calls you have set up. let block_1 = types::Block { header: support::Header { block_number: 1 }, extrinsics: vec![ support::Extrinsic { caller: alice.clone(), call: RuntimeCall::Balances(balances::Call::Transfer { to: bob, amount: 30 }), }, support::Extrinsic { caller: alice, call: RuntimeCall::Balances(balances::Call::Transfer { to: charlie, amount: 20 }), }, ], }; // Execute the extrinsics which make up our block. // If there are any errors, our system panics, since we should not execute invalid blocks. runtime.execute_block(block_1).expect("invalid block"); // Simply print the debug format of our runtime state. println!("{:#?}", runtime); }
#![allow(unused)] fn main() { use core::fmt::Debug; use std::collections::BTreeMap; pub trait Config: crate::system::Config { /// The type which represents the content that can be claimed using this pallet. /// Could be the content directly as bytes, or better yet the hash of that content. /// We leave that decision to the runtime developer. type Content: Debug + Ord; } /// This is the Proof of Existence Module. /// It is a simple module that allows accounts to claim existence of some data. #[derive(Debug)] pub struct Pallet<T: Config> { /// A simple storage map from content to the owner of that content. /// Accounts can make multiple different claims, but each claim can only have one owner. /* TODO: Add a field `claims` which is a `BTreeMap` fom `T::Content` to `T::AccountId`. */ } impl<T: Config> Pallet<T> { /// Create a new instance of the Proof of Existence Module. pub fn new() -> Self { /* TODO: Return a new instance of the `Pallet` struct. */ } } }
mod balances; mod proof_of_existence; mod support; mod system; use crate::support::Dispatch; // These are the concrete types we will use in our simple state machine. // Modules are configured for these types directly, and they satisfy all of our // trait requirements. mod types { pub type AccountId = String; pub type Balance = u128; pub type BlockNumber = u32; pub type Nonce = u32; pub type Extrinsic = crate::support::Extrinsic<AccountId, crate::RuntimeCall>; pub type Header = crate::support::Header<BlockNumber>; pub type Block = crate::support::Block<Header, Extrinsic>; } // These are all the calls which are exposed to the world. // Note that it is just an accumulation of the calls exposed by each module. pub enum RuntimeCall { Balances(balances::Call<Runtime>), } // This is our main Runtime. // It accumulates all of the different pallets we want to use. #[derive(Debug)] pub struct Runtime { system: system::Pallet<Self>, balances: balances::Pallet<Self>, } impl system::Config for Runtime { type AccountId = types::AccountId; type BlockNumber = types::BlockNumber; type Nonce = types::Nonce; } impl balances::Config for Runtime { type Balance = types::Balance; } impl Runtime { // Create a new instance of the main Runtime, by creating a new instance of each pallet. fn new() -> Self { Self { system: system::Pallet::new(), balances: balances::Pallet::new() } } // Execute a block of extrinsics. Increments the block number. fn execute_block(&mut self, block: types::Block) -> support::DispatchResult { self.system.inc_block_number(); if block.header.block_number != self.system.block_number() { return Err("block number does not match what is expected"); } // An extrinsic error is not enough to trigger the block to be invalid. We capture the // result, and emit an error message if one is emitted. for (i, support::Extrinsic { caller, call }) in block.extrinsics.into_iter().enumerate() { self.system.inc_nonce(&caller); let _res = self.dispatch(caller, call).map_err(|e| { eprintln!( "Extrinsic Error\n\tBlock Number: {}\n\tExtrinsic Number: {}\n\tError: {}", block.header.block_number, i, e ) }); } Ok(()) } } impl crate::support::Dispatch for Runtime { type Caller = <Runtime as system::Config>::AccountId; type Call = RuntimeCall; // Dispatch a call on behalf of a caller. Increments the caller's nonce. // // Dispatch allows us to identify which underlying module call we want to execute. // Note that we extract the `caller` from the extrinsic, and use that information // to determine who we are executing the call on behalf of. fn dispatch( &mut self, caller: Self::Caller, runtime_call: Self::Call, ) -> support::DispatchResult { // This match statement will allow us to correctly route `RuntimeCall`s // to the appropriate pallet level function. match runtime_call { RuntimeCall::Balances(call) => { self.balances.dispatch(caller, call)?; }, } Ok(()) } } fn main() { // Create a new instance of the Runtime. // It will instantiate with it all the modules it uses. let mut runtime = Runtime::new(); let alice = "alice".to_string(); let bob = "bob".to_string(); let charlie = "charlie".to_string(); // Initialize the system with some initial balance. runtime.balances.set_balance(&alice, 100); // Here are the extrinsics in our block. // You can add or remove these based on the modules and calls you have set up. let block_1 = types::Block { header: support::Header { block_number: 1 }, extrinsics: vec![ support::Extrinsic { caller: alice.clone(), call: RuntimeCall::Balances(balances::Call::Transfer { to: bob, amount: 30 }), }, support::Extrinsic { caller: alice, call: RuntimeCall::Balances(balances::Call::Transfer { to: charlie, amount: 20 }), }, ], }; // Execute the extrinsics which make up our block. // If there are any errors, our system panics, since we should not execute invalid blocks. runtime.execute_block(block_1).expect("invalid block"); // Simply print the debug format of our runtime state. println!("{:#?}", runtime); }
#![allow(unused)] fn main() { use core::fmt::Debug; use std::collections::BTreeMap; pub trait Config: crate::system::Config { /// The type which represents the content that can be claimed using this pallet. /// Could be the content directly as bytes, or better yet the hash of that content. /// We leave that decision to the runtime developer. type Content: Debug + Ord; } /// This is the Proof of Existence Module. /// It is a simple module that allows accounts to claim existence of some data. #[derive(Debug)] pub struct Pallet<T: Config> { /// A simple storage map from content to the owner of that content. /// Accounts can make multiple different claims, but each claim can only have one owner. claims: BTreeMap<T::Content, T::AccountId>, } impl<T: Config> Pallet<T> { /// Create a new instance of the Proof of Existence Module. pub fn new() -> Self { Self { claims: BTreeMap::new() } } } }
diff --git a/src/main.rs b/src/main.rs
index 8a874e18..e15cf4f3 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,4 +1,5 @@
mod balances;
+/* TODO: Import the `proof_of_existence` module. */
mod support;
mod system;
diff --git a/src/proof_of_existence.rs b/src/proof_of_existence.rs
new file mode 100644
index 00000000..87ea242e
--- /dev/null
+++ b/src/proof_of_existence.rs
@@ -0,0 +1,25 @@
+use core::fmt::Debug;
+use std::collections::BTreeMap;
+
+pub trait Config: crate::system::Config {
+ /// The type which represents the content that can be claimed using this pallet.
+ /// Could be the content directly as bytes, or better yet the hash of that content.
+ /// We leave that decision to the runtime developer.
+ type Content: Debug + Ord;
+}
+
+/// This is the Proof of Existence Module.
+/// It is a simple module that allows accounts to claim existence of some data.
+#[derive(Debug)]
+pub struct Pallet<T: Config> {
+ /// A simple storage map from content to the owner of that content.
+ /// Accounts can make multiple different claims, but each claim can only have one owner.
+ /* TODO: Add a field `claims` which is a `BTreeMap` fom `T::Content` to `T::AccountId`. */
+}
+
+impl<T: Config> Pallet<T> {
+ /// Create a new instance of the Proof of Existence Module.
+ pub fn new() -> Self {
+ /* TODO: Return a new instance of the `Pallet` struct. */
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index e15cf4f3..6dd23260 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,5 +1,5 @@
mod balances;
-/* TODO: Import the `proof_of_existence` module. */
+mod proof_of_existence;
mod support;
mod system;
diff --git a/src/proof_of_existence.rs b/src/proof_of_existence.rs
index 87ea242e..5e7de2a7 100644
--- a/src/proof_of_existence.rs
+++ b/src/proof_of_existence.rs
@@ -14,12 +14,12 @@ pub trait Config: crate::system::Config {
pub struct Pallet<T: Config> {
/// A simple storage map from content to the owner of that content.
/// Accounts can make multiple different claims, but each claim can only have one owner.
- /* TODO: Add a field `claims` which is a `BTreeMap` fom `T::Content` to `T::AccountId`. */
+ claims: BTreeMap<T::Content, T::AccountId>,
}
impl<T: Config> Pallet<T> {
/// Create a new instance of the Proof of Existence Module.
pub fn new() -> Self {
- /* TODO: Return a new instance of the `Pallet` struct. */
+ Self { claims: BTreeMap::new() }
}
}