Using Execute Block
We have now successfully implemented the execute_block
and dispatch
logic needed to build and execute real Blocks
.
Let's bring that logic into our main
function.
Creating a Block
You can create a new Block
by filling out all the fields of the struct and assigning it to a variable.
For example:
#![allow(unused)] fn main() { let block_1 = types::Block { header: support::Header { block_number: 1 }, extrinsics: vec![ support::Extrinsic { caller: &"alice", call: RuntimeCall::BalancesTransfer { to: &"bob", amount: 69 }, }, ], }; }
It is important that you set the block number correctly since we verify this in our execute_block
function. The first block in our state machine will have the number 1
.
Also remember that you can add multiple extrinsics in a single block by extending the vector.
Executing a Block
Once you have constructed your Block
, you can pass it to the execute_block
function implemented on your runtime
.
#![allow(unused)] fn main() { runtime.execute_block(block_1).expect("invalid block"); }
Note how we panic with the message "invalid block"
if the execute_block
function returns an error. This should only happen when something is seriously wrong with your block, for example the block number is incorrect for what we expect.
This panic will NOT be triggered if there is an error in an extrinsic, as we "swallow" those errors in the execute_block
function. This is the behavior we want.
Update Your Main Function
Go ahead and use the Block
type and execute_block
function to update the logic of your main
function.
Follow the TODO
s provided in the template to complete this step
By the end of this step, your code should compile, test, and run successfully, all without compiler warnings!
mod balances; 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 { BalancesTransfer { to: types::AccountId, amount: types::Balance }, } // 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::BalancesTransfer { to, amount } => { self.balances.transfer(caller, to, amount)?; }, } 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); // start emulating a block runtime.system.inc_block_number(); assert_eq!(runtime.system.block_number(), 1); // first transaction runtime.system.inc_nonce(&alice); let _res = runtime .balances .transfer(alice.clone(), bob, 30) .map_err(|e| eprintln!("{}", e)); // second transaction runtime.system.inc_nonce(&alice); let _res = runtime.balances.transfer(alice, charlie, 20).map_err(|e| eprintln!("{}", e)); /* TODO: Replace the logic above with a new `Block`. - Set the block number to 1 in the `Header`. - Move your existing transactions into extrinsic format, using the `Extrinsic` and `RuntimeCall`. */ /* TODO: Use your `runtime` to call the `execute_block` function with your new block. If the `execute_block` function returns an error, you should panic! We `expect` that all the blocks being executed must be valid. */ // Simply print the debug format of our runtime state. println!("{:#?}", runtime); }
mod balances; 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 { BalancesTransfer { to: types::AccountId, amount: types::Balance }, } // 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::BalancesTransfer { to, amount } => { self.balances.transfer(caller, to, amount)?; }, } 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::BalancesTransfer { to: bob, amount: 30 }, }, support::Extrinsic { caller: alice, call: RuntimeCall::BalancesTransfer { 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); }
diff --git a/src/main.rs b/src/main.rs
index d3cbb031..c1203d12 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -93,11 +93,14 @@ impl crate::support::Dispatch for Runtime {
}
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);
// start emulating a block
@@ -115,5 +118,20 @@ fn main() {
runtime.system.inc_nonce(&alice);
let _res = runtime.balances.transfer(alice, charlie, 20).map_err(|e| eprintln!("{}", e));
+ /*
+ TODO: Replace the logic above with a new `Block`.
+ - Set the block number to 1 in the `Header`.
+ - Move your existing transactions into extrinsic format, using the
+ `Extrinsic` and `RuntimeCall`.
+ */
+
+ /*
+ TODO:
+ Use your `runtime` to call the `execute_block` function with your new block.
+ If the `execute_block` function returns an error, you should panic!
+ We `expect` that all the blocks being executed must be valid.
+ */
+
+ // Simply print the debug format of our runtime state.
println!("{:#?}", runtime);
}
diff --git a/src/main.rs b/src/main.rs
index c1203d12..170ad380 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -103,34 +103,25 @@ fn main() {
// Initialize the system with some initial balance.
runtime.balances.set_balance(&alice, 100);
- // start emulating a block
- runtime.system.inc_block_number();
- assert_eq!(runtime.system.block_number(), 1);
-
- // first transaction
- runtime.system.inc_nonce(&alice);
- let _res = runtime
- .balances
- .transfer(alice.clone(), bob, 30)
- .map_err(|e| eprintln!("{}", e));
-
- // second transaction
- runtime.system.inc_nonce(&alice);
- let _res = runtime.balances.transfer(alice, charlie, 20).map_err(|e| eprintln!("{}", e));
-
- /*
- TODO: Replace the logic above with a new `Block`.
- - Set the block number to 1 in the `Header`.
- - Move your existing transactions into extrinsic format, using the
- `Extrinsic` and `RuntimeCall`.
- */
+ // 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::BalancesTransfer { to: bob, amount: 30 },
+ },
+ support::Extrinsic {
+ caller: alice,
+ call: RuntimeCall::BalancesTransfer { to: charlie, amount: 20 },
+ },
+ ],
+ };
- /*
- TODO:
- Use your `runtime` to call the `execute_block` function with your new block.
- If the `execute_block` function returns an error, you should panic!
- We `expect` that all the blocks being executed must be valid.
- */
+ // 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);