diff --git a/Cargo.lock b/Cargo.lock index f0f096a6598e6..4d4e733000925 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3095,6 +3095,7 @@ dependencies = [ "pallet-balances 2.0.0", "pallet-contracts 2.0.0", "pallet-grandpa 2.0.0", + "pallet-im-online 2.0.0", "pallet-indices 2.0.0", "pallet-session 2.0.0", "pallet-timestamp 2.0.0", @@ -3102,6 +3103,7 @@ dependencies = [ "pallet-treasury 2.0.0", "parity-scale-codec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "sc-executor 2.0.0", + "sp-application-crypto 2.0.0", "sp-core 2.0.0", "sp-io 2.0.0", "sp-runtime 2.0.0", diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index 8580bfd895102..1643c10af257f 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -15,7 +15,7 @@ use sp_runtime::{ impl_opaque_keys, MultiSignature }; use sp_runtime::traits::{ - NumberFor, BlakeTwo256, Block as BlockT, StaticLookup, Verify, ConvertInto, IdentifyAccount + BlakeTwo256, Block as BlockT, StaticLookup, Verify, ConvertInto, IdentifyAccount }; use sp_api::impl_runtime_apis; use sp_consensus_aura::sr25519::AuthorityId as AuraId; @@ -339,8 +339,8 @@ impl_runtime_apis! { } impl sp_offchain::OffchainWorkerApi for Runtime { - fn offchain_worker(number: NumberFor) { - Executive::offchain_worker(number) + fn offchain_worker(header: &::Header) { + Executive::offchain_worker(header) } } diff --git a/bin/node/executor/Cargo.toml b/bin/node/executor/Cargo.toml index 24f593d1ce8bb..0c594a95f9f53 100644 --- a/bin/node/executor/Cargo.toml +++ b/bin/node/executor/Cargo.toml @@ -6,32 +6,34 @@ description = "Substrate node implementation in Rust." edition = "2018" [dependencies] -trie-root = "0.15.2" codec = { package = "parity-scale-codec", version = "1.0.0" } -sp-io = { version = "2.0.0", path = "../../../primitives/io" } -sp-state-machine = { version = "2.0.0", path = "../../../primitives/state-machine" } +node-primitives = { version = "2.0.0", path = "../primitives" } +node-runtime = { version = "2.0.0", path = "../runtime" } sc-executor = { version = "2.0.0", path = "../../../client/executor" } sp-core = { version = "2.0.0", path = "../../../primitives/core" } +sp-io = { version = "2.0.0", path = "../../../primitives/io" } +sp-state-machine = { version = "2.0.0", path = "../../../primitives/state-machine" } sp-trie = { version = "2.0.0", path = "../../../primitives/trie" } -node-primitives = { version = "2.0.0", path = "../primitives" } -node-runtime = { version = "2.0.0", path = "../runtime" } +trie-root = "0.15.2" [dev-dependencies] -node-testing = { version = "2.0.0", path = "../testing" } -substrate-test-client = { version = "2.0.0", path = "../../../test-utils/client" } -sp-runtime = { version = "2.0.0", path = "../../../primitives/runtime" } +criterion = "0.3.0" frame-support = { version = "2.0.0", path = "../../../frame/support" } -pallet-balances = { version = "2.0.0", path = "../../../frame/balances" } -pallet-transaction-payment = { version = "2.0.0", path = "../../../frame/transaction-payment" } -pallet-session = { version = "2.0.0", path = "../../../frame/session" } frame-system = { version = "2.0.0", path = "../../../frame/system" } -pallet-timestamp = { version = "2.0.0", path = "../../../frame/timestamp" } -pallet-treasury = { version = "2.0.0", path = "../../../frame/treasury" } +node-testing = { version = "2.0.0", path = "../testing" } +pallet-balances = { version = "2.0.0", path = "../../../frame/balances" } pallet-contracts = { version = "2.0.0", path = "../../../frame/contracts" } pallet-grandpa = { version = "2.0.0", path = "../../../frame/grandpa" } +pallet-im-online = { version = "2.0.0", path = "../../../frame/im-online" } pallet-indices = { version = "2.0.0", path = "../../../frame/indices" } +pallet-session = { version = "2.0.0", path = "../../../frame/session" } +pallet-timestamp = { version = "2.0.0", path = "../../../frame/timestamp" } +pallet-transaction-payment = { version = "2.0.0", path = "../../../frame/transaction-payment" } +pallet-treasury = { version = "2.0.0", path = "../../../frame/treasury" } +sp-application-crypto = { version = "2.0.0", path = "../../../primitives/application-crypto" } +sp-runtime = { version = "2.0.0", path = "../../../primitives/runtime" } +substrate-test-client = { version = "2.0.0", path = "../../../test-utils/client" } wabt = "0.9.2" -criterion = "0.3.0" [features] wasmtime = [ diff --git a/bin/node/executor/src/lib.rs b/bin/node/executor/src/lib.rs index e8c8072fec3c2..deb947fdd13cf 100644 --- a/bin/node/executor/src/lib.rs +++ b/bin/node/executor/src/lib.rs @@ -28,1230 +28,3 @@ native_executor_instance!( node_runtime::native_version ); -#[cfg(test)] -mod tests { - use sc_executor::error::Result; - use super::Executor; - use codec::{Encode, Decode, Joiner}; - use frame_support::{ - Hashable, StorageValue, StorageMap, - traits::Currency, - weights::{GetDispatchInfo, DispatchInfo, DispatchClass}, - }; - use sp_state_machine::TestExternalities as CoreTestExternalities; - use sp_core::{ - Blake2Hasher, NeverNativeValue, NativeOrEncoded, map, - traits::{CodeExecutor, Externalities}, storage::{well_known_keys, Storage}, - }; - use sp_runtime::{ - Fixed64, traits::{Header as HeaderT, Hash as HashT, Convert}, ApplyExtrinsicResult, - transaction_validity::InvalidTransaction, - }; - use pallet_contracts::ContractAddressFor; - use sc_executor::{NativeExecutor, WasmExecutionMethod}; - use frame_system::{EventRecord, Phase}; - use node_runtime::{ - Header, Block, UncheckedExtrinsic, CheckedExtrinsic, Call, Runtime, Balances, BuildStorage, - System, TransactionPayment, Event, TransferFee, TransactionBaseFee, TransactionByteFee, - WeightFeeCoefficient, constants::currency::*, - }; - use node_runtime::impls::LinearWeightToFee; - use node_primitives::{Balance, Hash, BlockNumber}; - use node_testing::keyring::*; - use wabt; - - /// The wasm runtime code. - /// - /// `compact` since it is after post-processing with wasm-gc which performs tree-shaking thus - /// making the binary slimmer. There is a convention to use compact version of the runtime - /// as canonical. This is why `native_executor_instance` also uses the compact version of the - /// runtime. - const COMPACT_CODE: &[u8] = node_runtime::WASM_BINARY; - - /// The wasm runtime binary which hasn't undergone the compacting process. - /// - /// The idea here is to pass it as the current runtime code to the executor so the executor will - /// have to execute provided wasm code instead of the native equivalent. This trick is used to - /// test code paths that differ between native and wasm versions. - const BLOATY_CODE: &[u8] = node_runtime::WASM_BINARY_BLOATY; - - const GENESIS_HASH: [u8; 32] = [69u8; 32]; - - const VERSION: u32 = node_runtime::VERSION.spec_version; - - type TestExternalities = CoreTestExternalities; - - fn sign(xt: CheckedExtrinsic) -> UncheckedExtrinsic { - node_testing::keyring::sign(xt, VERSION, GENESIS_HASH) - } - - /// Default transfer fee - fn transfer_fee(extrinsic: &E, fee_multiplier: Fixed64) -> Balance { - let length_fee = TransactionByteFee::get() * (extrinsic.encode().len() as Balance); - - let weight = default_transfer_call().get_dispatch_info().weight; - let weight_fee = ::WeightToFee::convert(weight); - - let base_fee = TransactionBaseFee::get(); - - base_fee + fee_multiplier.saturated_multiply_accumulate(length_fee + weight_fee) + TransferFee::get() - } - - fn default_transfer_call() -> pallet_balances::Call { - pallet_balances::Call::transfer::(bob().into(), 69 * DOLLARS) - } - - fn xt() -> UncheckedExtrinsic { - sign(CheckedExtrinsic { - signed: Some((alice(), signed_extra(0, 0))), - function: Call::Balances(default_transfer_call()), - }) - } - - fn from_block_number(n: u32) -> Header { - Header::new(n, Default::default(), Default::default(), [69; 32].into(), Default::default()) - } - - fn executor() -> NativeExecutor { - NativeExecutor::new(WasmExecutionMethod::Interpreted, None) - } - - fn set_heap_pages(ext: &mut E, heap_pages: u64) { - ext.place_storage(well_known_keys::HEAP_PAGES.to_vec(), Some(heap_pages.encode())); - } - - fn executor_call< - R:Decode + Encode + PartialEq, - NC: FnOnce() -> std::result::Result + std::panic::UnwindSafe - >( - t: &mut TestExternalities, - method: &str, - data: &[u8], - use_native: bool, - native_call: Option, - ) -> (Result>, bool) { - let mut t = t.ext(); - executor().call::<_, R, NC>( - &mut t, - method, - data, - use_native, - native_call, - ) - } - - #[test] - fn panic_execution_with_foreign_code_gives_error() { - let mut t = TestExternalities::::new_with_code(BLOATY_CODE, Storage { - top: map![ - >::hashed_key_for(alice()) => { - 69_u128.encode() - }, - >::hashed_key().to_vec() => { - 69_u128.encode() - }, - >::hashed_key().to_vec() => { - 0_u128.encode() - }, - >::hashed_key_for(0) => { - vec![0u8; 32] - } - ], - children: map![], - }); - - let r = executor_call:: _>( - &mut t, - "Core_initialize_block", - &vec![].and(&from_block_number(1u32)), - true, - None, - ).0; - assert!(r.is_ok()); - let v = executor_call:: _>( - &mut t, - "BlockBuilder_apply_extrinsic", - &vec![].and(&xt()), - true, - None, - ).0.unwrap(); - let r = ApplyExtrinsicResult::decode(&mut &v.as_encoded()[..]).unwrap(); - assert_eq!(r, Err(InvalidTransaction::Payment.into())); - } - - #[test] - fn bad_extrinsic_with_native_equivalent_code_gives_error() { - let mut t = TestExternalities::::new_with_code(COMPACT_CODE, Storage { - top: map![ - >::hashed_key_for(alice()) => { - 69_u128.encode() - }, - >::hashed_key().to_vec() => { - 69_u128.encode() - }, - >::hashed_key().to_vec() => { - 0_u128.encode() - }, - >::hashed_key_for(0) => { - vec![0u8; 32] - } - ], - children: map![], - }); - - let r = executor_call:: _>( - &mut t, - "Core_initialize_block", - &vec![].and(&from_block_number(1u32)), - true, - None, - ).0; - assert!(r.is_ok()); - let v = executor_call:: _>( - &mut t, - "BlockBuilder_apply_extrinsic", - &vec![].and(&xt()), - true, - None, - ).0.unwrap(); - let r = ApplyExtrinsicResult::decode(&mut &v.as_encoded()[..]).unwrap(); - assert_eq!(r, Err(InvalidTransaction::Payment.into())); - } - - #[test] - fn successful_execution_with_native_equivalent_code_gives_ok() { - let mut t = TestExternalities::::new_with_code(COMPACT_CODE, Storage { - top: map![ - >::hashed_key_for(alice()) => { - (111 * DOLLARS).encode() - }, - >::hashed_key().to_vec() => { - (111 * DOLLARS).encode() - }, - >::hashed_key().to_vec() => vec![0u8; 16], - >::hashed_key_for(0) => vec![0u8; 32] - ], - children: map![], - }); - - let r = executor_call:: _>( - &mut t, - "Core_initialize_block", - &vec![].and(&from_block_number(1u32)), - true, - None, - ).0; - assert!(r.is_ok()); - - let fm = t.execute_with(TransactionPayment::next_fee_multiplier); - - let r = executor_call:: _>( - &mut t, - "BlockBuilder_apply_extrinsic", - &vec![].and(&xt()), - true, - None, - ).0; - assert!(r.is_ok()); - - t.execute_with(|| { - assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - transfer_fee(&xt(), fm)); - assert_eq!(Balances::total_balance(&bob()), 69 * DOLLARS); - }); - } - - #[test] - fn successful_execution_with_foreign_code_gives_ok() { - let mut t = TestExternalities::::new_with_code(BLOATY_CODE, Storage { - top: map![ - >::hashed_key_for(alice()) => { - (111 * DOLLARS).encode() - }, - >::hashed_key().to_vec() => { - (111 * DOLLARS).encode() - }, - >::hashed_key().to_vec() => vec![0u8; 16], - >::hashed_key_for(0) => vec![0u8; 32] - ], - children: map![], - }); - - let r = executor_call:: _>( - &mut t, - "Core_initialize_block", - &vec![].and(&from_block_number(1u32)), - true, - None, - ).0; - assert!(r.is_ok()); - - let fm = t.execute_with(TransactionPayment::next_fee_multiplier); - - let r = executor_call:: _>( - &mut t, - "BlockBuilder_apply_extrinsic", - &vec![].and(&xt()), - true, - None, - ).0; - assert!(r.is_ok()); - - t.execute_with(|| { - assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - transfer_fee(&xt(), fm)); - assert_eq!(Balances::total_balance(&bob()), 69 * DOLLARS); - }); - } - - fn new_test_ext(code: &[u8], support_changes_trie: bool) -> TestExternalities { - let mut ext = TestExternalities::new_with_code( - code, - node_testing::genesis::config(support_changes_trie, Some(code)).build_storage().unwrap(), - ); - ext.changes_trie_storage().insert(0, GENESIS_HASH.into(), Default::default()); - ext - } - - fn construct_block( - env: &mut TestExternalities, - number: BlockNumber, - parent_hash: Hash, - extrinsics: Vec, - ) -> (Vec, Hash) { - use sp_trie::{TrieConfiguration, trie_types::Layout}; - - // sign extrinsics. - let extrinsics = extrinsics.into_iter().map(sign).collect::>(); - - // calculate the header fields that we can. - let extrinsics_root = Layout::::ordered_trie_root( - extrinsics.iter().map(Encode::encode) - ).to_fixed_bytes() - .into(); - - let header = Header { - parent_hash, - number, - extrinsics_root, - state_root: Default::default(), - digest: Default::default(), - }; - - // execute the block to get the real header. - executor_call:: _>( - env, - "Core_initialize_block", - &header.encode(), - true, - None, - ).0.unwrap(); - - for i in extrinsics.iter() { - executor_call:: _>( - env, - "BlockBuilder_apply_extrinsic", - &i.encode(), - true, - None, - ).0.unwrap(); - } - - let header = match executor_call:: _>( - env, - "BlockBuilder_finalize_block", - &[0u8;0], - true, - None, - ).0.unwrap() { - NativeOrEncoded::Native(_) => unreachable!(), - NativeOrEncoded::Encoded(h) => Header::decode(&mut &h[..]).unwrap(), - }; - - let hash = header.blake2_256(); - (Block { header, extrinsics }.encode(), hash.into()) - } - - fn changes_trie_block() -> (Vec, Hash) { - construct_block( - &mut new_test_ext(COMPACT_CODE, true), - 1, - GENESIS_HASH.into(), - vec![ - CheckedExtrinsic { - signed: None, - function: Call::Timestamp(pallet_timestamp::Call::set(42 * 1000)), - }, - CheckedExtrinsic { - signed: Some((alice(), signed_extra(0, 0))), - function: Call::Balances(pallet_balances::Call::transfer(bob().into(), 69 * DOLLARS)), - }, - ] - ) - } - - // block 1 and 2 must be created together to ensure transactions are only signed once (since they - // are not guaranteed to be deterministic) and to ensure that the correct state is propagated - // from block1's execution to block2 to derive the correct storage_root. - fn blocks() -> ((Vec, Hash), (Vec, Hash)) { - let mut t = new_test_ext(COMPACT_CODE, false); - let block1 = construct_block( - &mut t, - 1, - GENESIS_HASH.into(), - vec![ - CheckedExtrinsic { - signed: None, - function: Call::Timestamp(pallet_timestamp::Call::set(42 * 1000)), - }, - CheckedExtrinsic { - signed: Some((alice(), signed_extra(0, 0))), - function: Call::Balances(pallet_balances::Call::transfer(bob().into(), 69 * DOLLARS)), - }, - ] - ); - let block2 = construct_block( - &mut t, - 2, - block1.1.clone(), - vec![ - CheckedExtrinsic { - signed: None, - function: Call::Timestamp(pallet_timestamp::Call::set(52 * 1000)), - }, - CheckedExtrinsic { - signed: Some((bob(), signed_extra(0, 0))), - function: Call::Balances(pallet_balances::Call::transfer(alice().into(), 5 * DOLLARS)), - }, - CheckedExtrinsic { - signed: Some((alice(), signed_extra(1, 0))), - function: Call::Balances(pallet_balances::Call::transfer(bob().into(), 15 * DOLLARS)), - } - ] - ); - - // session change => consensus authorities change => authorities change digest item appears - let digest = Header::decode(&mut &block2.0[..]).unwrap().digest; - assert_eq!(digest.logs().len(), 0); - - (block1, block2) - } - - fn block_with_size(time: u64, nonce: u32, size: usize) -> (Vec, Hash) { - construct_block( - &mut new_test_ext(COMPACT_CODE, false), - 1, - GENESIS_HASH.into(), - vec![ - CheckedExtrinsic { - signed: None, - function: Call::Timestamp(pallet_timestamp::Call::set(time * 1000)), - }, - CheckedExtrinsic { - signed: Some((alice(), signed_extra(nonce, 0))), - function: Call::System(frame_system::Call::remark(vec![0; size])), - } - ] - ) - } - - #[test] - fn full_native_block_import_works() { - let mut t = new_test_ext(COMPACT_CODE, false); - - let (block1, block2) = blocks(); - - let mut alice_last_known_balance: Balance = Default::default(); - let mut fm = t.execute_with(TransactionPayment::next_fee_multiplier); - - executor_call:: _>( - &mut t, - "Core_execute_block", - &block1.0, - true, - None, - ).0.unwrap(); - - t.execute_with(|| { - assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - transfer_fee(&xt(), fm)); - assert_eq!(Balances::total_balance(&bob()), 169 * DOLLARS); - alice_last_known_balance = Balances::total_balance(&alice()); - let events = vec![ - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: Event::system(frame_system::Event::ExtrinsicSuccess( - DispatchInfo { weight: 10000, class: DispatchClass::Operational, pays_fee: true } - )), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(1), - event: Event::pallet_treasury(pallet_treasury::RawEvent::Deposit(1984800000000)), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(1), - event: Event::pallet_balances(pallet_balances::RawEvent::Transfer( - alice().into(), - bob().into(), - 69 * DOLLARS, - 1 * CENTS, - )), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(1), - event: Event::system(frame_system::Event::ExtrinsicSuccess( - DispatchInfo { weight: 1000000, class: DispatchClass::Normal, pays_fee: true } - )), - topics: vec![], - }, - ]; - assert_eq!(System::events(), events); - }); - - fm = t.execute_with(TransactionPayment::next_fee_multiplier); - - executor_call:: _>( - &mut t, - "Core_execute_block", - &block2.0, - true, - None, - ).0.unwrap(); - - t.execute_with(|| { - assert_eq!( - Balances::total_balance(&alice()), - alice_last_known_balance - 10 * DOLLARS - transfer_fee(&xt(), fm), - ); - assert_eq!( - Balances::total_balance(&bob()), - 179 * DOLLARS - transfer_fee(&xt(), fm), - ); - let events = vec![ - EventRecord { - phase: Phase::ApplyExtrinsic(0), - event: Event::system(frame_system::Event::ExtrinsicSuccess( - DispatchInfo { weight: 10000, class: DispatchClass::Operational, pays_fee: true } - )), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(1), - event: Event::pallet_treasury(pallet_treasury::RawEvent::Deposit(1984788199392)), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(1), - event: Event::pallet_balances( - pallet_balances::RawEvent::Transfer( - bob().into(), - alice().into(), - 5 * DOLLARS, - 1 * CENTS, - ) - ), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(1), - event: Event::system(frame_system::Event::ExtrinsicSuccess( - DispatchInfo { weight: 1000000, class: DispatchClass::Normal, pays_fee: true } - )), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(2), - event: Event::pallet_treasury(pallet_treasury::RawEvent::Deposit(1984788199392)), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(2), - event: Event::pallet_balances( - pallet_balances::RawEvent::Transfer( - alice().into(), - bob().into(), - 15 * DOLLARS, - 1 * CENTS, - ) - ), - topics: vec![], - }, - EventRecord { - phase: Phase::ApplyExtrinsic(2), - event: Event::system(frame_system::Event::ExtrinsicSuccess( - DispatchInfo { weight: 1000000, class: DispatchClass::Normal, pays_fee: true } - )), - topics: vec![], - }, - ]; - assert_eq!(System::events(), events); - }); - } - - #[test] - fn full_wasm_block_import_works() { - let mut t = new_test_ext(COMPACT_CODE, false); - - let (block1, block2) = blocks(); - - let mut alice_last_known_balance: Balance = Default::default(); - let mut fm = t.execute_with(TransactionPayment::next_fee_multiplier); - - executor_call:: _>( - &mut t, - "Core_execute_block", - &block1.0, - false, - None, - ).0.unwrap(); - - t.execute_with(|| { - assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - transfer_fee(&xt(), fm)); - assert_eq!(Balances::total_balance(&bob()), 169 * DOLLARS); - alice_last_known_balance = Balances::total_balance(&alice()); - }); - - fm = t.execute_with(TransactionPayment::next_fee_multiplier); - - executor_call:: _>( - &mut t, - "Core_execute_block", - &block2.0, - false, - None, - ).0.unwrap(); - - t.execute_with(|| { - assert_eq!( - Balances::total_balance(&alice()), - alice_last_known_balance - 10 * DOLLARS - transfer_fee(&xt(), fm), - ); - assert_eq!( - Balances::total_balance(&bob()), - 179 * DOLLARS - 1 * transfer_fee(&xt(), fm), - ); - }); - } - - const CODE_TRANSFER: &str = r#" -(module - ;; ext_call( - ;; callee_ptr: u32, - ;; callee_len: u32, - ;; gas: u64, - ;; value_ptr: u32, - ;; value_len: u32, - ;; input_data_ptr: u32, - ;; input_data_len: u32 - ;; ) -> u32 - (import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result i32))) - (import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) - (import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) - (import "env" "memory" (memory 1 1)) - (func (export "deploy") - ) - (func (export "call") - (block $fail - ;; load and check the input data (which is stored in the scratch buffer). - ;; fail if the input size is not != 4 - (br_if $fail - (i32.ne - (i32.const 4) - (call $ext_scratch_size) - ) - ) - - (call $ext_scratch_read - (i32.const 0) - (i32.const 0) - (i32.const 4) - ) - - - (br_if $fail - (i32.ne - (i32.load8_u (i32.const 0)) - (i32.const 0) - ) - ) - (br_if $fail - (i32.ne - (i32.load8_u (i32.const 1)) - (i32.const 1) - ) - ) - (br_if $fail - (i32.ne - (i32.load8_u (i32.const 2)) - (i32.const 2) - ) - ) - (br_if $fail - (i32.ne - (i32.load8_u (i32.const 3)) - (i32.const 3) - ) - ) - - (drop - (call $ext_call - (i32.const 4) ;; Pointer to "callee" address. - (i32.const 32) ;; Length of "callee" address. - (i64.const 0) ;; How much gas to devote for the execution. 0 = all. - (i32.const 36) ;; Pointer to the buffer with value to transfer - (i32.const 16) ;; Length of the buffer with value to transfer. - (i32.const 0) ;; Pointer to input data buffer address - (i32.const 0) ;; Length of input data buffer - ) - ) - - (return) - ) - unreachable - ) - ;; Destination AccountId to transfer the funds. - ;; Represented by H256 (32 bytes long) in little endian. - (data (i32.const 4) - "\09\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00" - "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00" - "\00\00\00\00" - ) - ;; Amount of value to transfer. - ;; Represented by u128 (16 bytes long) in little endian. - (data (i32.const 36) - "\06\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00" - "\00\00" - ) -) -"#; - - #[test] - fn deploying_wasm_contract_should_work() { - let transfer_code = wabt::wat2wasm(CODE_TRANSFER).unwrap(); - let transfer_ch = ::Hashing::hash(&transfer_code); - - let addr = ::DetermineContractAddress::contract_address_for( - &transfer_ch, - &[], - &charlie(), - ); - - let b = construct_block( - &mut new_test_ext(COMPACT_CODE, false), - 1, - GENESIS_HASH.into(), - vec![ - CheckedExtrinsic { - signed: None, - function: Call::Timestamp(pallet_timestamp::Call::set(42 * 1000)), - }, - CheckedExtrinsic { - signed: Some((charlie(), signed_extra(0, 0))), - function: Call::Contracts( - pallet_contracts::Call::put_code::(10_000, transfer_code) - ), - }, - CheckedExtrinsic { - signed: Some((charlie(), signed_extra(1, 0))), - function: Call::Contracts( - pallet_contracts::Call::instantiate::(1 * DOLLARS, 10_000, transfer_ch, Vec::new()) - ), - }, - CheckedExtrinsic { - signed: Some((charlie(), signed_extra(2, 0))), - function: Call::Contracts( - pallet_contracts::Call::call::( - pallet_indices::address::Address::Id(addr.clone()), - 10, - 10_000, - vec![0x00, 0x01, 0x02, 0x03] - ) - ), - }, - ] - ); - - let mut t = new_test_ext(COMPACT_CODE, false); - - executor_call:: _>( - &mut t, - "Core_execute_block", - &b.0, - false, - None, - ).0.unwrap(); - - t.execute_with(|| { - // Verify that the contract constructor worked well and code of TRANSFER contract is actually deployed. - assert_eq!( - &pallet_contracts::ContractInfoOf::::get(addr) - .and_then(|c| c.get_alive()) - .unwrap() - .code_hash, - &transfer_ch - ); - }); - } - - #[test] - fn wasm_big_block_import_fails() { - let mut t = new_test_ext(COMPACT_CODE, false); - - set_heap_pages(&mut t.ext(), 4); - - let result = executor_call:: _>( - &mut t, - "Core_execute_block", - &block_with_size(42, 0, 120_000).0, - false, - None, - ).0; - assert!(result.is_err()); // Err(Wasmi(Trap(Trap { kind: Host(AllocatorOutOfSpace) }))) - } - - #[test] - fn native_big_block_import_succeeds() { - let mut t = new_test_ext(COMPACT_CODE, false); - - executor_call:: _>( - &mut t, - "Core_execute_block", - &block_with_size(42, 0, 120_000).0, - true, - None, - ).0.unwrap(); - } - - #[test] - fn native_big_block_import_fails_on_fallback() { - let mut t = new_test_ext(COMPACT_CODE, false); - - assert!( - executor_call:: _>( - &mut t, - "Core_execute_block", - &block_with_size(42, 0, 120_000).0, - false, - None, - ).0.is_err() - ); - } - - #[test] - fn panic_execution_gives_error() { - let mut t = TestExternalities::::new_with_code(BLOATY_CODE, Storage { - top: map![ - >::hashed_key_for(alice()) => { - 0_u128.encode() - }, - >::hashed_key().to_vec() => { - 0_u128.encode() - }, - >::hashed_key().to_vec() => vec![0u8; 16], - >::hashed_key_for(0) => vec![0u8; 32] - ], - children: map![], - }); - - let r = executor_call:: _>( - &mut t, - "Core_initialize_block", - &vec![].and(&from_block_number(1u32)), - false, - None, - ).0; - assert!(r.is_ok()); - let r = executor_call:: _>( - &mut t, - "BlockBuilder_apply_extrinsic", - &vec![].and(&xt()), - false, - None, - ).0.unwrap().into_encoded(); - let r = ApplyExtrinsicResult::decode(&mut &r[..]).unwrap(); - assert_eq!(r, Err(InvalidTransaction::Payment.into())); - } - - #[test] - fn successful_execution_gives_ok() { - let mut t = TestExternalities::::new_with_code(COMPACT_CODE, Storage { - top: map![ - >::hashed_key_for(alice()) => { - (111 * DOLLARS).encode() - }, - >::hashed_key().to_vec() => { - (111 * DOLLARS).encode() - }, - >::hashed_key().to_vec() => vec![0u8; 16], - >::hashed_key_for(0) => vec![0u8; 32] - ], - children: map![], - }); - - let r = executor_call:: _>( - &mut t, - "Core_initialize_block", - &vec![].and(&from_block_number(1u32)), - false, - None, - ).0; - assert!(r.is_ok()); - let fm = t.execute_with(TransactionPayment::next_fee_multiplier); - let r = executor_call:: _>( - &mut t, - "BlockBuilder_apply_extrinsic", - &vec![].and(&xt()), - false, - None, - ).0.unwrap().into_encoded(); - ApplyExtrinsicResult::decode(&mut &r[..]) - .unwrap() - .expect("Extrinsic could be applied") - .expect("Extrinsic did not fail"); - - t.execute_with(|| { - assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - 1 * transfer_fee(&xt(), fm)); - assert_eq!(Balances::total_balance(&bob()), 69 * DOLLARS); - }); - } - - #[test] - fn full_native_block_import_works_with_changes_trie() { - let block1 = changes_trie_block(); - let block_data = block1.0; - let block = Block::decode(&mut &block_data[..]).unwrap(); - - let mut t = new_test_ext(COMPACT_CODE, true); - executor_call:: _>( - &mut t, - "Core_execute_block", - &block.encode(), - true, - None, - ).0.unwrap(); - - assert!(t.ext().storage_changes_root(&GENESIS_HASH.encode()).unwrap().is_some()); - } - - #[test] - fn full_wasm_block_import_works_with_changes_trie() { - let block1 = changes_trie_block(); - - let mut t = new_test_ext(COMPACT_CODE, true); - executor_call:: _>( - &mut t, - "Core_execute_block", - &block1.0, - false, - None, - ).0.unwrap(); - - assert!(t.ext().storage_changes_root(&GENESIS_HASH.encode()).unwrap().is_some()); - } - - #[test] - fn should_import_block_with_test_client() { - use node_testing::client::{ - ClientExt, TestClientBuilderExt, TestClientBuilder, sp_consensus::BlockOrigin - }; - - let client = TestClientBuilder::new().build(); - let block1 = changes_trie_block(); - let block_data = block1.0; - let block = node_primitives::Block::decode(&mut &block_data[..]).unwrap(); - - client.import(BlockOrigin::Own, block).unwrap(); - } - - - #[test] - fn fee_multiplier_increases_and_decreases_on_big_weight() { - let mut t = new_test_ext(COMPACT_CODE, false); - - // initial fee multiplier must be zero - let mut prev_multiplier = Fixed64::from_parts(0); - - t.execute_with(|| { - assert_eq!(TransactionPayment::next_fee_multiplier(), prev_multiplier); - }); - - let mut tt = new_test_ext(COMPACT_CODE, false); - - // big one in terms of weight. - let block1 = construct_block( - &mut tt, - 1, - GENESIS_HASH.into(), - vec![ - CheckedExtrinsic { - signed: None, - function: Call::Timestamp(pallet_timestamp::Call::set(42 * 1000)), - }, - CheckedExtrinsic { - signed: Some((charlie(), signed_extra(0, 0))), - function: Call::System(frame_system::Call::fill_block()), - } - ] - ); - - // small one in terms of weight. - let block2 = construct_block( - &mut tt, - 2, - block1.1.clone(), - vec![ - CheckedExtrinsic { - signed: None, - function: Call::Timestamp(pallet_timestamp::Call::set(52 * 1000)), - }, - CheckedExtrinsic { - signed: Some((charlie(), signed_extra(1, 0))), - function: Call::System(frame_system::Call::remark(vec![0; 1])), - } - ] - ); - - println!("++ Block 1 size: {} / Block 2 size {}", block1.0.encode().len(), block2.0.encode().len()); - - // execute a big block. - executor_call:: _>( - &mut t, - "Core_execute_block", - &block1.0, - true, - None, - ).0.unwrap(); - - // weight multiplier is increased for next block. - t.execute_with(|| { - let fm = TransactionPayment::next_fee_multiplier(); - println!("After a big block: {:?} -> {:?}", prev_multiplier, fm); - assert!(fm > prev_multiplier); - prev_multiplier = fm; - }); - - // execute a big block. - executor_call:: _>( - &mut t, - "Core_execute_block", - &block2.0, - true, - None, - ).0.unwrap(); - - // weight multiplier is increased for next block. - t.execute_with(|| { - let fm = TransactionPayment::next_fee_multiplier(); - println!("After a small block: {:?} -> {:?}", prev_multiplier, fm); - assert!(fm < prev_multiplier); - }); - } - - #[test] - fn transaction_fee_is_correct_ultimate() { - // This uses the exact values of substrate-node. - // - // weight of transfer call as of now: 1_000_000 - // if weight of the cheapest weight would be 10^7, this would be 10^9, which is: - // - 1 MILLICENTS in substrate node. - // - 1 milli-dot based on current polkadot runtime. - // (this baed on assigning 0.1 CENT to the cheapest tx with `weight = 100`) - let mut t = TestExternalities::::new_with_code(COMPACT_CODE, Storage { - top: map![ - >::hashed_key_for(alice()) => { - (100 * DOLLARS).encode() - }, - >::hashed_key_for(bob()) => { - (10 * DOLLARS).encode() - }, - >::hashed_key().to_vec() => { - (110 * DOLLARS).encode() - }, - >::hashed_key().to_vec() => vec![0u8; 16], - >::hashed_key_for(0) => vec![0u8; 32] - ], - children: map![], - }); - - let tip = 1_000_000; - let xt = sign(CheckedExtrinsic { - signed: Some((alice(), signed_extra(0, tip))), - function: Call::Balances(default_transfer_call()), - }); - - let r = executor_call:: _>( - &mut t, - "Core_initialize_block", - &vec![].and(&from_block_number(1u32)), - true, - None, - ).0; - - assert!(r.is_ok()); - let r = executor_call:: _>( - &mut t, - "BlockBuilder_apply_extrinsic", - &vec![].and(&xt.clone()), - true, - None, - ).0; - assert!(r.is_ok()); - - t.execute_with(|| { - assert_eq!(Balances::total_balance(&bob()), (10 + 69) * DOLLARS); - // Components deducted from alice's balances: - // - Weight fee - // - Length fee - // - Tip - // - Creation-fee of bob's account. - let mut balance_alice = (100 - 69) * DOLLARS; - - let length_fee = TransactionBaseFee::get() + - TransactionByteFee::get() * - (xt.clone().encode().len() as Balance); - balance_alice -= length_fee; - - let weight = default_transfer_call().get_dispatch_info().weight; - let weight_fee = LinearWeightToFee::::convert(weight); - - // we know that weight to fee multiplier is effect-less in block 1. - assert_eq!(weight_fee as Balance, MILLICENTS); - balance_alice -= weight_fee; - - balance_alice -= tip; - balance_alice -= TransferFee::get(); - - assert_eq!(Balances::total_balance(&alice()), balance_alice); - }); - } - - #[test] - #[should_panic] - #[cfg(feature = "stress-test")] - fn block_weight_capacity_report() { - // Just report how many transfer calls you could fit into a block. The number should at least - // be a few hundred (250 at the time of writing but can change over time). Runs until panic. - use node_primitives::Index; - - // execution ext. - let mut t = new_test_ext(COMPACT_CODE, false); - // setup ext. - let mut tt = new_test_ext(COMPACT_CODE, false); - - let factor = 50; - let mut time = 10; - let mut nonce: Index = 0; - let mut block_number = 1; - let mut previous_hash: Hash = GENESIS_HASH.into(); - - loop { - let num_transfers = block_number * factor; - let mut xts = (0..num_transfers).map(|i| CheckedExtrinsic { - signed: Some((charlie(), signed_extra(nonce + i as Index, 0))), - function: Call::Balances(pallet_balances::Call::transfer(bob().into(), 0)), - }).collect::>(); - - xts.insert(0, CheckedExtrinsic { - signed: None, - function: Call::Timestamp(pallet_timestamp::Call::set(time * 1000)), - }); - - // NOTE: this is super slow. Can probably be improved. - let block = construct_block( - &mut tt, - block_number, - previous_hash, - xts - ); - - let len = block.0.len(); - print!( - "++ Executing block with {} transfers. Block size = {} bytes / {} kb / {} mb", - num_transfers, - len, - len / 1024, - len / 1024 / 1024, - ); - - let r = executor_call:: _>( - &mut t, - "Core_execute_block", - &block.0, - true, - None, - ).0; - - println!(" || Result = {:?}", r); - assert!(r.is_ok()); - - previous_hash = block.1; - nonce += num_transfers; - time += 10; - block_number += 1; - } - } - - #[test] - #[should_panic] - #[cfg(feature = "stress-test")] - fn block_length_capacity_report() { - // Just report how big a block can get. Executes until panic. Should be ignored unless if - // manually inspected. The number should at least be a few megabytes (5 at the time of - // writing but can change over time). - use node_primitives::Index; - - // execution ext. - let mut t = new_test_ext(COMPACT_CODE, false); - // setup ext. - let mut tt = new_test_ext(COMPACT_CODE, false); - - let factor = 256 * 1024; - let mut time = 10; - let mut nonce: Index = 0; - let mut block_number = 1; - let mut previous_hash: Hash = GENESIS_HASH.into(); - - loop { - // NOTE: this is super slow. Can probably be improved. - let block = construct_block( - &mut tt, - block_number, - previous_hash, - vec![ - CheckedExtrinsic { - signed: None, - function: Call::Timestamp(pallet_timestamp::Call::set(time * 1000)), - }, - CheckedExtrinsic { - signed: Some((charlie(), signed_extra(nonce, 0))), - function: Call::System(frame_system::Call::remark(vec![0u8; (block_number * factor) as usize])), - }, - ] - ); - - let len = block.0.len(); - print!( - "++ Executing block with big remark. Block size = {} bytes / {} kb / {} mb", - len, - len / 1024, - len / 1024 / 1024, - ); - - let r = executor_call:: _>( - &mut t, - "Core_execute_block", - &block.0, - true, - None, - ).0; - - println!(" || Result = {:?}", r); - assert!(r.is_ok()); - - previous_hash = block.1; - nonce += 1; - time += 10; - block_number += 1; - } - } -} diff --git a/bin/node/executor/tests/basic.rs b/bin/node/executor/tests/basic.rs new file mode 100644 index 0000000000000..504b6853e1df2 --- /dev/null +++ b/bin/node/executor/tests/basic.rs @@ -0,0 +1,836 @@ +// Copyright 2018-2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +use codec::{Encode, Decode, Joiner}; +use frame_support::{ + StorageValue, StorageMap, + traits::Currency, + weights::{GetDispatchInfo, DispatchInfo, DispatchClass}, +}; +use sp_core::{ + Blake2Hasher, NeverNativeValue, map, + traits::Externalities, + storage::{well_known_keys, Storage}, +}; +use sp_runtime::{ + ApplyExtrinsicResult, Fixed64, + traits::{Hash as HashT, Convert}, + transaction_validity::InvalidTransaction, +}; +use pallet_contracts::ContractAddressFor; +use frame_system::{self, EventRecord, Phase}; + +use node_runtime::{ + Header, Block, UncheckedExtrinsic, CheckedExtrinsic, Call, Runtime, Balances, + System, TransactionPayment, Event, + TransferFee, TransactionBaseFee, TransactionByteFee, + constants::currency::*, +}; +use node_primitives::{Balance, Hash}; +use wabt; +use node_testing::keyring::*; + +mod common; +use self::common::{*, sign}; + +/// The wasm runtime binary which hasn't undergone the compacting process. +/// +/// The idea here is to pass it as the current runtime code to the executor so the executor will +/// have to execute provided wasm code instead of the native equivalent. This trick is used to +/// test code paths that differ between native and wasm versions. +pub const BLOATY_CODE: &[u8] = node_runtime::WASM_BINARY_BLOATY; + +/// Default transfer fee +fn transfer_fee(extrinsic: &E, fee_multiplier: Fixed64) -> Balance { + let length_fee = TransactionByteFee::get() * (extrinsic.encode().len() as Balance); + + let weight = default_transfer_call().get_dispatch_info().weight; + let weight_fee = + ::WeightToFee::convert(weight); + + let base_fee = TransactionBaseFee::get(); + base_fee + fee_multiplier.saturated_multiply_accumulate(length_fee + weight_fee) + TransferFee::get() +} + +fn xt() -> UncheckedExtrinsic { + sign(CheckedExtrinsic { + signed: Some((alice(), signed_extra(0, 0))), + function: Call::Balances(default_transfer_call()), + }) +} + +fn set_heap_pages(ext: &mut E, heap_pages: u64) { + ext.place_storage(well_known_keys::HEAP_PAGES.to_vec(), Some(heap_pages.encode())); +} + +fn changes_trie_block() -> (Vec, Hash) { + construct_block( + &mut new_test_ext(COMPACT_CODE, true), + 1, + GENESIS_HASH.into(), + vec![ + CheckedExtrinsic { + signed: None, + function: Call::Timestamp(pallet_timestamp::Call::set(42 * 1000)), + }, + CheckedExtrinsic { + signed: Some((alice(), signed_extra(0, 0))), + function: Call::Balances(pallet_balances::Call::transfer(bob().into(), 69 * DOLLARS)), + }, + ] + ) +} + + +/// block 1 and 2 must be created together to ensure transactions are only signed once (since they +/// are not guaranteed to be deterministic) and to ensure that the correct state is propagated +/// from block1's execution to block2 to derive the correct storage_root. +fn blocks() -> ((Vec, Hash), (Vec, Hash)) { + let mut t = new_test_ext(COMPACT_CODE, false); + let block1 = construct_block( + &mut t, + 1, + GENESIS_HASH.into(), + vec![ + CheckedExtrinsic { + signed: None, + function: Call::Timestamp(pallet_timestamp::Call::set(42 * 1000)), + }, + CheckedExtrinsic { + signed: Some((alice(), signed_extra(0, 0))), + function: Call::Balances(pallet_balances::Call::transfer(bob().into(), 69 * DOLLARS)), + }, + ] + ); + let block2 = construct_block( + &mut t, + 2, + block1.1.clone(), + vec![ + CheckedExtrinsic { + signed: None, + function: Call::Timestamp(pallet_timestamp::Call::set(52 * 1000)), + }, + CheckedExtrinsic { + signed: Some((bob(), signed_extra(0, 0))), + function: Call::Balances(pallet_balances::Call::transfer(alice().into(), 5 * DOLLARS)), + }, + CheckedExtrinsic { + signed: Some((alice(), signed_extra(1, 0))), + function: Call::Balances(pallet_balances::Call::transfer(bob().into(), 15 * DOLLARS)), + } + ] + ); + + // session change => consensus authorities change => authorities change digest item appears + let digest = Header::decode(&mut &block2.0[..]).unwrap().digest; + assert_eq!(digest.logs().len(), 0); + + (block1, block2) +} + +fn block_with_size(time: u64, nonce: u32, size: usize) -> (Vec, Hash) { + construct_block( + &mut new_test_ext(COMPACT_CODE, false), + 1, + GENESIS_HASH.into(), + vec![ + CheckedExtrinsic { + signed: None, + function: Call::Timestamp(pallet_timestamp::Call::set(time * 1000)), + }, + CheckedExtrinsic { + signed: Some((alice(), signed_extra(nonce, 0))), + function: Call::System(frame_system::Call::remark(vec![0; size])), + } + ] + ) +} + +#[test] +fn panic_execution_with_foreign_code_gives_error() { + let mut t = TestExternalities::::new_with_code(BLOATY_CODE, Storage { + top: map![ + >::hashed_key_for(alice()) => { + 69_u128.encode() + }, + >::hashed_key().to_vec() => { + 69_u128.encode() + }, + >::hashed_key().to_vec() => { + 0_u128.encode() + }, + >::hashed_key_for(0) => { + vec![0u8; 32] + } + ], + children: map![], + }); + + let r = executor_call:: _>( + &mut t, + "Core_initialize_block", + &vec![].and(&from_block_number(1u32)), + true, + None, + ).0; + assert!(r.is_ok()); + let v = executor_call:: _>( + &mut t, + "BlockBuilder_apply_extrinsic", + &vec![].and(&xt()), + true, + None, + ).0.unwrap(); + let r = ApplyExtrinsicResult::decode(&mut &v.as_encoded()[..]).unwrap(); + assert_eq!(r, Err(InvalidTransaction::Payment.into())); +} + +#[test] +fn bad_extrinsic_with_native_equivalent_code_gives_error() { + let mut t = TestExternalities::::new_with_code(COMPACT_CODE, Storage { + top: map![ + >::hashed_key_for(alice()) => { + 69_u128.encode() + }, + >::hashed_key().to_vec() => { + 69_u128.encode() + }, + >::hashed_key().to_vec() => { + 0_u128.encode() + }, + >::hashed_key_for(0) => { + vec![0u8; 32] + } + ], + children: map![], + }); + + let r = executor_call:: _>( + &mut t, + "Core_initialize_block", + &vec![].and(&from_block_number(1u32)), + true, + None, + ).0; + assert!(r.is_ok()); + let v = executor_call:: _>( + &mut t, + "BlockBuilder_apply_extrinsic", + &vec![].and(&xt()), + true, + None, + ).0.unwrap(); + let r = ApplyExtrinsicResult::decode(&mut &v.as_encoded()[..]).unwrap(); + assert_eq!(r, Err(InvalidTransaction::Payment.into())); +} + +#[test] +fn successful_execution_with_native_equivalent_code_gives_ok() { + let mut t = TestExternalities::::new_with_code(COMPACT_CODE, Storage { + top: map![ + >::hashed_key_for(alice()) => { + (111 * DOLLARS).encode() + }, + >::hashed_key().to_vec() => { + (111 * DOLLARS).encode() + }, + >::hashed_key().to_vec() => vec![0u8; 16], + >::hashed_key_for(0) => vec![0u8; 32] + ], + children: map![], + }); + + let r = executor_call:: _>( + &mut t, + "Core_initialize_block", + &vec![].and(&from_block_number(1u32)), + true, + None, + ).0; + assert!(r.is_ok()); + + let fm = t.execute_with(TransactionPayment::next_fee_multiplier); + + let r = executor_call:: _>( + &mut t, + "BlockBuilder_apply_extrinsic", + &vec![].and(&xt()), + true, + None, + ).0; + assert!(r.is_ok()); + + t.execute_with(|| { + assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - transfer_fee(&xt(), fm)); + assert_eq!(Balances::total_balance(&bob()), 69 * DOLLARS); + }); +} + +#[test] +fn successful_execution_with_foreign_code_gives_ok() { + let mut t = TestExternalities::::new_with_code(BLOATY_CODE, Storage { + top: map![ + >::hashed_key_for(alice()) => { + (111 * DOLLARS).encode() + }, + >::hashed_key().to_vec() => { + (111 * DOLLARS).encode() + }, + >::hashed_key().to_vec() => vec![0u8; 16], + >::hashed_key_for(0) => vec![0u8; 32] + ], + children: map![], + }); + + let r = executor_call:: _>( + &mut t, + "Core_initialize_block", + &vec![].and(&from_block_number(1u32)), + true, + None, + ).0; + assert!(r.is_ok()); + + let fm = t.execute_with(TransactionPayment::next_fee_multiplier); + + let r = executor_call:: _>( + &mut t, + "BlockBuilder_apply_extrinsic", + &vec![].and(&xt()), + true, + None, + ).0; + assert!(r.is_ok()); + + t.execute_with(|| { + assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - transfer_fee(&xt(), fm)); + assert_eq!(Balances::total_balance(&bob()), 69 * DOLLARS); + }); +} + +#[test] +fn full_native_block_import_works() { + let mut t = new_test_ext(COMPACT_CODE, false); + + let (block1, block2) = blocks(); + + let mut alice_last_known_balance: Balance = Default::default(); + let mut fm = t.execute_with(TransactionPayment::next_fee_multiplier); + + executor_call:: _>( + &mut t, + "Core_execute_block", + &block1.0, + true, + None, + ).0.unwrap(); + + t.execute_with(|| { + assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - transfer_fee(&xt(), fm)); + assert_eq!(Balances::total_balance(&bob()), 169 * DOLLARS); + alice_last_known_balance = Balances::total_balance(&alice()); + let events = vec![ + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: Event::system(frame_system::Event::ExtrinsicSuccess( + DispatchInfo { weight: 10000, class: DispatchClass::Operational, pays_fee: true } + )), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(1), + event: Event::pallet_treasury(pallet_treasury::RawEvent::Deposit(1984800000000)), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(1), + event: Event::pallet_balances(pallet_balances::RawEvent::Transfer( + alice().into(), + bob().into(), + 69 * DOLLARS, + 1 * CENTS, + )), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(1), + event: Event::system(frame_system::Event::ExtrinsicSuccess( + DispatchInfo { weight: 1000000, class: DispatchClass::Normal, pays_fee: true } + )), + topics: vec![], + }, + ]; + assert_eq!(System::events(), events); + }); + + fm = t.execute_with(TransactionPayment::next_fee_multiplier); + + executor_call:: _>( + &mut t, + "Core_execute_block", + &block2.0, + true, + None, + ).0.unwrap(); + + t.execute_with(|| { + assert_eq!( + Balances::total_balance(&alice()), + alice_last_known_balance - 10 * DOLLARS - transfer_fee(&xt(), fm), + ); + assert_eq!( + Balances::total_balance(&bob()), + 179 * DOLLARS - transfer_fee(&xt(), fm), + ); + let events = vec![ + EventRecord { + phase: Phase::ApplyExtrinsic(0), + event: Event::system(frame_system::Event::ExtrinsicSuccess( + DispatchInfo { weight: 10000, class: DispatchClass::Operational, pays_fee: true } + )), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(1), + event: Event::pallet_treasury(pallet_treasury::RawEvent::Deposit(1984788199392)), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(1), + event: Event::pallet_balances( + pallet_balances::RawEvent::Transfer( + bob().into(), + alice().into(), + 5 * DOLLARS, + 1 * CENTS, + ) + ), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(1), + event: Event::system(frame_system::Event::ExtrinsicSuccess( + DispatchInfo { weight: 1000000, class: DispatchClass::Normal, pays_fee: true } + )), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(2), + event: Event::pallet_treasury(pallet_treasury::RawEvent::Deposit(1984788199392)), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(2), + event: Event::pallet_balances( + pallet_balances::RawEvent::Transfer( + alice().into(), + bob().into(), + 15 * DOLLARS, + 1 * CENTS, + ) + ), + topics: vec![], + }, + EventRecord { + phase: Phase::ApplyExtrinsic(2), + event: Event::system(frame_system::Event::ExtrinsicSuccess( + DispatchInfo { weight: 1000000, class: DispatchClass::Normal, pays_fee: true } + )), + topics: vec![], + }, + ]; + assert_eq!(System::events(), events); + }); +} + +#[test] +fn full_wasm_block_import_works() { + let mut t = new_test_ext(COMPACT_CODE, false); + + let (block1, block2) = blocks(); + + let mut alice_last_known_balance: Balance = Default::default(); + let mut fm = t.execute_with(TransactionPayment::next_fee_multiplier); + + executor_call:: _>( + &mut t, + "Core_execute_block", + &block1.0, + false, + None, + ).0.unwrap(); + + t.execute_with(|| { + assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - transfer_fee(&xt(), fm)); + assert_eq!(Balances::total_balance(&bob()), 169 * DOLLARS); + alice_last_known_balance = Balances::total_balance(&alice()); + }); + + fm = t.execute_with(TransactionPayment::next_fee_multiplier); + + executor_call:: _>( + &mut t, + "Core_execute_block", + &block2.0, + false, + None, + ).0.unwrap(); + + t.execute_with(|| { + assert_eq!( + Balances::total_balance(&alice()), + alice_last_known_balance - 10 * DOLLARS - transfer_fee(&xt(), fm), + ); + assert_eq!( + Balances::total_balance(&bob()), + 179 * DOLLARS - 1 * transfer_fee(&xt(), fm), + ); + }); +} + +const CODE_TRANSFER: &str = r#" +(module +;; ext_call( +;; callee_ptr: u32, +;; callee_len: u32, +;; gas: u64, +;; value_ptr: u32, +;; value_len: u32, +;; input_data_ptr: u32, +;; input_data_len: u32 +;; ) -> u32 +(import "env" "ext_call" (func $ext_call (param i32 i32 i64 i32 i32 i32 i32) (result i32))) +(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32))) +(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32))) +(import "env" "memory" (memory 1 1)) +(func (export "deploy") +) +(func (export "call") + (block $fail + ;; load and check the input data (which is stored in the scratch buffer). + ;; fail if the input size is not != 4 + (br_if $fail + (i32.ne + (i32.const 4) + (call $ext_scratch_size) + ) + ) + + (call $ext_scratch_read + (i32.const 0) + (i32.const 0) + (i32.const 4) + ) + + + (br_if $fail + (i32.ne + (i32.load8_u (i32.const 0)) + (i32.const 0) + ) + ) + (br_if $fail + (i32.ne + (i32.load8_u (i32.const 1)) + (i32.const 1) + ) + ) + (br_if $fail + (i32.ne + (i32.load8_u (i32.const 2)) + (i32.const 2) + ) + ) + (br_if $fail + (i32.ne + (i32.load8_u (i32.const 3)) + (i32.const 3) + ) + ) + + (drop + (call $ext_call + (i32.const 4) ;; Pointer to "callee" address. + (i32.const 32) ;; Length of "callee" address. + (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i32.const 36) ;; Pointer to the buffer with value to transfer + (i32.const 16) ;; Length of the buffer with value to transfer. + (i32.const 0) ;; Pointer to input data buffer address + (i32.const 0) ;; Length of input data buffer + ) + ) + + (return) + ) + unreachable +) +;; Destination AccountId to transfer the funds. +;; Represented by H256 (32 bytes long) in little endian. +(data (i32.const 4) + "\09\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00" + "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00" + "\00\00\00\00" +) +;; Amount of value to transfer. +;; Represented by u128 (16 bytes long) in little endian. +(data (i32.const 36) + "\06\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00" + "\00\00" +) +) +"#; + +#[test] +fn deploying_wasm_contract_should_work() { + let transfer_code = wabt::wat2wasm(CODE_TRANSFER).unwrap(); + let transfer_ch = ::Hashing::hash(&transfer_code); + + let addr = ::DetermineContractAddress::contract_address_for( + &transfer_ch, + &[], + &charlie(), + ); + + let b = construct_block( + &mut new_test_ext(COMPACT_CODE, false), + 1, + GENESIS_HASH.into(), + vec![ + CheckedExtrinsic { + signed: None, + function: Call::Timestamp(pallet_timestamp::Call::set(42 * 1000)), + }, + CheckedExtrinsic { + signed: Some((charlie(), signed_extra(0, 0))), + function: Call::Contracts( + pallet_contracts::Call::put_code::(10_000, transfer_code) + ), + }, + CheckedExtrinsic { + signed: Some((charlie(), signed_extra(1, 0))), + function: Call::Contracts( + pallet_contracts::Call::instantiate::(1 * DOLLARS, 10_000, transfer_ch, Vec::new()) + ), + }, + CheckedExtrinsic { + signed: Some((charlie(), signed_extra(2, 0))), + function: Call::Contracts( + pallet_contracts::Call::call::( + pallet_indices::address::Address::Id(addr.clone()), + 10, + 10_000, + vec![0x00, 0x01, 0x02, 0x03] + ) + ), + }, + ] + ); + + let mut t = new_test_ext(COMPACT_CODE, false); + + executor_call:: _>( + &mut t, + "Core_execute_block", + &b.0, + false, + None, + ).0.unwrap(); + + t.execute_with(|| { + // Verify that the contract constructor worked well and code of TRANSFER contract is actually deployed. + assert_eq!( + &pallet_contracts::ContractInfoOf::::get(addr) + .and_then(|c| c.get_alive()) + .unwrap() + .code_hash, + &transfer_ch + ); + }); +} + +#[test] +fn wasm_big_block_import_fails() { + let mut t = new_test_ext(COMPACT_CODE, false); + + set_heap_pages(&mut t.ext(), 4); + + let result = executor_call:: _>( + &mut t, + "Core_execute_block", + &block_with_size(42, 0, 120_000).0, + false, + None, + ).0; + assert!(result.is_err()); // Err(Wasmi(Trap(Trap { kind: Host(AllocatorOutOfSpace) }))) +} + +#[test] +fn native_big_block_import_succeeds() { + let mut t = new_test_ext(COMPACT_CODE, false); + + executor_call:: _>( + &mut t, + "Core_execute_block", + &block_with_size(42, 0, 120_000).0, + true, + None, + ).0.unwrap(); +} + +#[test] +fn native_big_block_import_fails_on_fallback() { + let mut t = new_test_ext(COMPACT_CODE, false); + + assert!( + executor_call:: _>( + &mut t, + "Core_execute_block", + &block_with_size(42, 0, 120_000).0, + false, + None, + ).0.is_err() + ); +} + +#[test] +fn panic_execution_gives_error() { + let mut t = TestExternalities::::new_with_code(BLOATY_CODE, Storage { + top: map![ + >::hashed_key_for(alice()) => { + 0_u128.encode() + }, + >::hashed_key().to_vec() => { + 0_u128.encode() + }, + >::hashed_key().to_vec() => vec![0u8; 16], + >::hashed_key_for(0) => vec![0u8; 32] + ], + children: map![], + }); + + let r = executor_call:: _>( + &mut t, + "Core_initialize_block", + &vec![].and(&from_block_number(1u32)), + false, + None, + ).0; + assert!(r.is_ok()); + let r = executor_call:: _>( + &mut t, + "BlockBuilder_apply_extrinsic", + &vec![].and(&xt()), + false, + None, + ).0.unwrap().into_encoded(); + let r = ApplyExtrinsicResult::decode(&mut &r[..]).unwrap(); + assert_eq!(r, Err(InvalidTransaction::Payment.into())); +} + +#[test] +fn successful_execution_gives_ok() { + let mut t = TestExternalities::::new_with_code(COMPACT_CODE, Storage { + top: map![ + >::hashed_key_for(alice()) => { + (111 * DOLLARS).encode() + }, + >::hashed_key().to_vec() => { + (111 * DOLLARS).encode() + }, + >::hashed_key().to_vec() => vec![0u8; 16], + >::hashed_key_for(0) => vec![0u8; 32] + ], + children: map![], + }); + + let r = executor_call:: _>( + &mut t, + "Core_initialize_block", + &vec![].and(&from_block_number(1u32)), + false, + None, + ).0; + assert!(r.is_ok()); + let fm = t.execute_with(TransactionPayment::next_fee_multiplier); + let r = executor_call:: _>( + &mut t, + "BlockBuilder_apply_extrinsic", + &vec![].and(&xt()), + false, + None, + ).0.unwrap().into_encoded(); + ApplyExtrinsicResult::decode(&mut &r[..]) + .unwrap() + .expect("Extrinsic could be applied") + .expect("Extrinsic did not fail"); + + t.execute_with(|| { + assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - 1 * transfer_fee(&xt(), fm)); + assert_eq!(Balances::total_balance(&bob()), 69 * DOLLARS); + }); +} + +#[test] +fn full_native_block_import_works_with_changes_trie() { + let block1 = changes_trie_block(); + let block_data = block1.0; + let block = Block::decode(&mut &block_data[..]).unwrap(); + + let mut t = new_test_ext(COMPACT_CODE, true); + executor_call:: _>( + &mut t, + "Core_execute_block", + &block.encode(), + true, + None, + ).0.unwrap(); + + assert!(t.ext().storage_changes_root(&GENESIS_HASH).unwrap().is_some()); +} + +#[test] +fn full_wasm_block_import_works_with_changes_trie() { + let block1 = changes_trie_block(); + + let mut t = new_test_ext(COMPACT_CODE, true); + executor_call:: _>( + &mut t, + "Core_execute_block", + &block1.0, + false, + None, + ).0.unwrap(); + + assert!(t.ext().storage_changes_root(&GENESIS_HASH).unwrap().is_some()); +} + +#[test] +fn should_import_block_with_test_client() { + use node_testing::client::{ + ClientExt, TestClientBuilderExt, TestClientBuilder, + sp_consensus::BlockOrigin, + }; + + let client = TestClientBuilder::new().build(); + let block1 = changes_trie_block(); + let block_data = block1.0; + let block = node_primitives::Block::decode(&mut &block_data[..]).unwrap(); + + client.import(BlockOrigin::Own, block).unwrap(); +} + + diff --git a/bin/node/executor/tests/common.rs b/bin/node/executor/tests/common.rs new file mode 100644 index 0000000000000..d580e7c545047 --- /dev/null +++ b/bin/node/executor/tests/common.rs @@ -0,0 +1,154 @@ +// Copyright 2018-2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +use codec::{Encode, Decode}; +use frame_support::{ + Hashable, +}; +use sp_state_machine::TestExternalities as CoreTestExternalities; +use sp_core::{ + Blake2Hasher, NeverNativeValue, NativeOrEncoded, + traits::CodeExecutor, +}; +use sp_runtime::traits::{Header as HeaderT}; +use sc_executor::{NativeExecutor, WasmExecutionMethod}; +use sc_executor::error::Result; + +use node_executor::Executor; +use node_runtime::{ + Header, Block, UncheckedExtrinsic, CheckedExtrinsic, Runtime, BuildStorage, + constants::currency::*, +}; +use node_primitives::{Hash, BlockNumber}; +use node_testing::keyring::*; + +/// The wasm runtime code. +/// +/// `compact` since it is after post-processing with wasm-gc which performs tree-shaking thus +/// making the binary slimmer. There is a convention to use compact version of the runtime +/// as canonical. This is why `native_executor_instance` also uses the compact version of the +/// runtime. +pub const COMPACT_CODE: &[u8] = node_runtime::WASM_BINARY; + +pub const GENESIS_HASH: [u8; 32] = [69u8; 32]; + +pub const VERSION: u32 = node_runtime::VERSION.spec_version; + +pub type TestExternalities = CoreTestExternalities; + +pub fn sign(xt: CheckedExtrinsic) -> UncheckedExtrinsic { + node_testing::keyring::sign(xt, VERSION, GENESIS_HASH) +} + +pub fn default_transfer_call() -> pallet_balances::Call { + pallet_balances::Call::transfer::(bob().into(), 69 * DOLLARS) +} + +pub fn from_block_number(n: u32) -> Header { + Header::new(n, Default::default(), Default::default(), [69; 32].into(), Default::default()) +} + +pub fn executor() -> NativeExecutor { + NativeExecutor::new(WasmExecutionMethod::Interpreted, None) +} + +pub fn executor_call< + R:Decode + Encode + PartialEq, + NC: FnOnce() -> std::result::Result + std::panic::UnwindSafe +>( + t: &mut TestExternalities, + method: &str, + data: &[u8], + use_native: bool, + native_call: Option, +) -> (Result>, bool) { + let mut t = t.ext(); + executor().call::<_, R, NC>( + &mut t, + method, + data, + use_native, + native_call, + ) +} + +pub fn new_test_ext(code: &[u8], support_changes_trie: bool) -> TestExternalities { + let mut ext = TestExternalities::new_with_code( + code, + node_testing::genesis::config(support_changes_trie, Some(code)).build_storage().unwrap(), + ); + ext.changes_trie_storage().insert(0, GENESIS_HASH.into(), Default::default()); + ext +} + +pub fn construct_block( + env: &mut TestExternalities, + number: BlockNumber, + parent_hash: Hash, + extrinsics: Vec, +) -> (Vec, Hash) { + use sp_trie::{TrieConfiguration, trie_types::Layout}; + + // sign extrinsics. + let extrinsics = extrinsics.into_iter().map(sign).collect::>(); + + // calculate the header fields that we can. + let extrinsics_root = Layout::::ordered_trie_root( + extrinsics.iter().map(Encode::encode) + ).to_fixed_bytes() + .into(); + + let header = Header { + parent_hash, + number, + extrinsics_root, + state_root: Default::default(), + digest: Default::default(), + }; + + // execute the block to get the real header. + executor_call:: _>( + env, + "Core_initialize_block", + &header.encode(), + true, + None, + ).0.unwrap(); + + for i in extrinsics.iter() { + executor_call:: _>( + env, + "BlockBuilder_apply_extrinsic", + &i.encode(), + true, + None, + ).0.unwrap(); + } + + let header = match executor_call:: _>( + env, + "BlockBuilder_finalize_block", + &[0u8;0], + true, + None, + ).0.unwrap() { + NativeOrEncoded::Native(_) => unreachable!(), + NativeOrEncoded::Encoded(h) => Header::decode(&mut &h[..]).unwrap(), + }; + + let hash = header.blake2_256(); + (Block { header, extrinsics }.encode(), hash.into()) +} diff --git a/bin/node/executor/tests/fees.rs b/bin/node/executor/tests/fees.rs new file mode 100644 index 0000000000000..1210812c0565a --- /dev/null +++ b/bin/node/executor/tests/fees.rs @@ -0,0 +1,332 @@ +// Copyright 2018-2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +use codec::{Encode, Joiner}; +use frame_support::{ + StorageValue, StorageMap, + traits::Currency, + weights::GetDispatchInfo, +}; +use sp_core::{ + Blake2Hasher, NeverNativeValue, map, + storage::Storage, +}; +use sp_runtime::{ + Fixed64, + traits::Convert, +}; +use node_runtime::{ + CheckedExtrinsic, Call, Runtime, Balances, + TransactionPayment, TransferFee, TransactionBaseFee, TransactionByteFee, + WeightFeeCoefficient, constants::currency::*, +}; +use node_runtime::impls::LinearWeightToFee; +use node_primitives::Balance; +use node_testing::keyring::*; + +mod common; +use self::common::{*, sign}; + +#[test] +fn fee_multiplier_increases_and_decreases_on_big_weight() { + let mut t = new_test_ext(COMPACT_CODE, false); + + // initial fee multiplier must be zero + let mut prev_multiplier = Fixed64::from_parts(0); + + t.execute_with(|| { + assert_eq!(TransactionPayment::next_fee_multiplier(), prev_multiplier); + }); + + let mut tt = new_test_ext(COMPACT_CODE, false); + + // big one in terms of weight. + let block1 = construct_block( + &mut tt, + 1, + GENESIS_HASH.into(), + vec![ + CheckedExtrinsic { + signed: None, + function: Call::Timestamp(pallet_timestamp::Call::set(42 * 1000)), + }, + CheckedExtrinsic { + signed: Some((charlie(), signed_extra(0, 0))), + function: Call::System(frame_system::Call::fill_block()), + } + ] + ); + + // small one in terms of weight. + let block2 = construct_block( + &mut tt, + 2, + block1.1.clone(), + vec![ + CheckedExtrinsic { + signed: None, + function: Call::Timestamp(pallet_timestamp::Call::set(52 * 1000)), + }, + CheckedExtrinsic { + signed: Some((charlie(), signed_extra(1, 0))), + function: Call::System(frame_system::Call::remark(vec![0; 1])), + } + ] + ); + + println!("++ Block 1 size: {} / Block 2 size {}", block1.0.encode().len(), block2.0.encode().len()); + + // execute a big block. + executor_call:: _>( + &mut t, + "Core_execute_block", + &block1.0, + true, + None, + ).0.unwrap(); + + // weight multiplier is increased for next block. + t.execute_with(|| { + let fm = TransactionPayment::next_fee_multiplier(); + println!("After a big block: {:?} -> {:?}", prev_multiplier, fm); + assert!(fm > prev_multiplier); + prev_multiplier = fm; + }); + + // execute a big block. + executor_call:: _>( + &mut t, + "Core_execute_block", + &block2.0, + true, + None, + ).0.unwrap(); + + // weight multiplier is increased for next block. + t.execute_with(|| { + let fm = TransactionPayment::next_fee_multiplier(); + println!("After a small block: {:?} -> {:?}", prev_multiplier, fm); + assert!(fm < prev_multiplier); + }); +} + +#[test] +fn transaction_fee_is_correct_ultimate() { + // This uses the exact values of substrate-node. + // + // weight of transfer call as of now: 1_000_000 + // if weight of the cheapest weight would be 10^7, this would be 10^9, which is: + // - 1 MILLICENTS in substrate node. + // - 1 milli-dot based on current polkadot runtime. + // (this baed on assigning 0.1 CENT to the cheapest tx with `weight = 100`) + let mut t = TestExternalities::::new_with_code(COMPACT_CODE, Storage { + top: map![ + >::hashed_key_for(alice()) => { + (100 * DOLLARS).encode() + }, + >::hashed_key_for(bob()) => { + (10 * DOLLARS).encode() + }, + >::hashed_key().to_vec() => { + (110 * DOLLARS).encode() + }, + >::hashed_key().to_vec() => vec![0u8; 16], + >::hashed_key_for(0) => vec![0u8; 32] + ], + children: map![], + }); + + let tip = 1_000_000; + let xt = sign(CheckedExtrinsic { + signed: Some((alice(), signed_extra(0, tip))), + function: Call::Balances(default_transfer_call()), + }); + + let r = executor_call:: _>( + &mut t, + "Core_initialize_block", + &vec![].and(&from_block_number(1u32)), + true, + None, + ).0; + + assert!(r.is_ok()); + let r = executor_call:: _>( + &mut t, + "BlockBuilder_apply_extrinsic", + &vec![].and(&xt.clone()), + true, + None, + ).0; + assert!(r.is_ok()); + + t.execute_with(|| { + assert_eq!(Balances::total_balance(&bob()), (10 + 69) * DOLLARS); + // Components deducted from alice's balances: + // - Weight fee + // - Length fee + // - Tip + // - Creation-fee of bob's account. + let mut balance_alice = (100 - 69) * DOLLARS; + + let length_fee = TransactionBaseFee::get() + + TransactionByteFee::get() * + (xt.clone().encode().len() as Balance); + balance_alice -= length_fee; + + let weight = default_transfer_call().get_dispatch_info().weight; + let weight_fee = LinearWeightToFee::::convert(weight); + + // we know that weight to fee multiplier is effect-less in block 1. + assert_eq!(weight_fee as Balance, MILLICENTS); + balance_alice -= weight_fee; + + balance_alice -= tip; + balance_alice -= TransferFee::get(); + + assert_eq!(Balances::total_balance(&alice()), balance_alice); + }); +} + +#[test] +#[should_panic] +#[cfg(feature = "stress-test")] +fn block_weight_capacity_report() { + // Just report how many transfer calls you could fit into a block. The number should at least + // be a few hundred (250 at the time of writing but can change over time). Runs until panic. + use node_primitives::Index; + + // execution ext. + let mut t = new_test_ext(COMPACT_CODE, false); + // setup ext. + let mut tt = new_test_ext(COMPACT_CODE, false); + + let factor = 50; + let mut time = 10; + let mut nonce: Index = 0; + let mut block_number = 1; + let mut previous_hash: Hash = GENESIS_HASH.into(); + + loop { + let num_transfers = block_number * factor; + let mut xts = (0..num_transfers).map(|i| CheckedExtrinsic { + signed: Some((charlie(), signed_extra(nonce + i as Index, 0))), + function: Call::Balances(pallet_balances::Call::transfer(bob().into(), 0)), + }).collect::>(); + + xts.insert(0, CheckedExtrinsic { + signed: None, + function: Call::Timestamp(pallet_timestamp::Call::set(time * 1000)), + }); + + // NOTE: this is super slow. Can probably be improved. + let block = construct_block( + &mut tt, + block_number, + previous_hash, + xts + ); + + let len = block.0.len(); + print!( + "++ Executing block with {} transfers. Block size = {} bytes / {} kb / {} mb", + num_transfers, + len, + len / 1024, + len / 1024 / 1024, + ); + + let r = executor_call:: _>( + &mut t, + "Core_execute_block", + &block.0, + true, + None, + ).0; + + println!(" || Result = {:?}", r); + assert!(r.is_ok()); + + previous_hash = block.1; + nonce += num_transfers; + time += 10; + block_number += 1; + } +} + +#[test] +#[should_panic] +#[cfg(feature = "stress-test")] +fn block_length_capacity_report() { + // Just report how big a block can get. Executes until panic. Should be ignored unless if + // manually inspected. The number should at least be a few megabytes (5 at the time of + // writing but can change over time). + use node_primitives::Index; + + // execution ext. + let mut t = new_test_ext(COMPACT_CODE, false); + // setup ext. + let mut tt = new_test_ext(COMPACT_CODE, false); + + let factor = 256 * 1024; + let mut time = 10; + let mut nonce: Index = 0; + let mut block_number = 1; + let mut previous_hash: Hash = GENESIS_HASH.into(); + + loop { + // NOTE: this is super slow. Can probably be improved. + let block = construct_block( + &mut tt, + block_number, + previous_hash, + vec![ + CheckedExtrinsic { + signed: None, + function: Call::Timestamp(pallet_timestamp::Call::set(time * 1000)), + }, + CheckedExtrinsic { + signed: Some((charlie(), signed_extra(nonce, 0))), + function: Call::System(frame_system::Call::remark(vec![0u8; (block_number * factor) as usize])), + }, + ] + ); + + let len = block.0.len(); + print!( + "++ Executing block with big remark. Block size = {} bytes / {} kb / {} mb", + len, + len / 1024, + len / 1024 / 1024, + ); + + let r = executor_call:: _>( + &mut t, + "Core_execute_block", + &block.0, + true, + None, + ).0; + + println!(" || Result = {:?}", r); + assert!(r.is_ok()); + + previous_hash = block.1; + nonce += 1; + time += 10; + block_number += 1; + } +} diff --git a/bin/node/executor/tests/submit_transaction.rs b/bin/node/executor/tests/submit_transaction.rs new file mode 100644 index 0000000000000..43702dbb3565c --- /dev/null +++ b/bin/node/executor/tests/submit_transaction.rs @@ -0,0 +1,184 @@ +// Copyright 2018-2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +use node_runtime::{ + Call, Executive, Indices, Runtime, SubmitTransaction, UncheckedExtrinsic, +}; +use sp_application_crypto::AppKey; +use sp_core::testing::KeyStore; +use sp_core::traits::KeystoreExt; +use sp_core::offchain::{ + TransactionPoolExt, + testing::TestTransactionPoolExt, +}; +use frame_system::offchain::{SubmitSignedTransaction, SubmitUnsignedTransaction}; +use pallet_im_online::sr25519::AuthorityPair as Key; +use codec::Decode; + +mod common; +use self::common::*; + +#[test] +fn should_submit_unsigned_transaction() { + let mut t = new_test_ext(COMPACT_CODE, false); + let (pool, state) = TestTransactionPoolExt::new(); + t.register_extension(TransactionPoolExt::new(pool)); + + t.execute_with(|| { + let signature = Default::default(); + let heartbeat_data = pallet_im_online::Heartbeat { + block_number: 1, + network_state: Default::default(), + session_index: 1, + authority_index: 0, + }; + + let call = pallet_im_online::Call::heartbeat(heartbeat_data, signature); + > + ::submit_unsigned(call) + .unwrap(); + + assert_eq!(state.read().transactions.len(), 1) + }); +} + +const PHRASE: &str = "news slush supreme milk chapter athlete soap sausage put clutch what kitten"; + +#[test] +fn should_submit_signed_transaction() { + let mut t = new_test_ext(COMPACT_CODE, false); + let (pool, state) = TestTransactionPoolExt::new(); + t.register_extension(TransactionPoolExt::new(pool)); + + let keystore = KeyStore::new(); + keystore.write().sr25519_generate_new(Key::ID, Some(&format!("{}/hunter1", PHRASE))).unwrap(); + keystore.write().sr25519_generate_new(Key::ID, Some(&format!("{}/hunter2", PHRASE))).unwrap(); + keystore.write().sr25519_generate_new(Key::ID, Some(&format!("{}/hunter3", PHRASE))).unwrap(); + t.register_extension(KeystoreExt(keystore)); + + t.execute_with(|| { + let keys = > + ::find_all_local_keys(); + assert_eq!(keys.len(), 3, "Missing keys: {:?}", keys); + + let can_sign = > + ::can_sign(); + assert!(can_sign, "Since there are keys, `can_sign` should return true"); + + let call = pallet_balances::Call::transfer(Default::default(), Default::default()); + let results = + >::submit_signed(call); + + let len = results.len(); + assert_eq!(len, 3); + assert_eq!(results.into_iter().filter_map(|x| x.1.ok()).count(), len); + assert_eq!(state.read().transactions.len(), len); + }); +} + +#[test] +fn should_submit_signed_twice_from_the_same_account() { + let mut t = new_test_ext(COMPACT_CODE, false); + let (pool, state) = TestTransactionPoolExt::new(); + t.register_extension(TransactionPoolExt::new(pool)); + + let keystore = KeyStore::new(); + keystore.write().sr25519_generate_new(Key::ID, Some(&format!("{}/hunter1", PHRASE))).unwrap(); + t.register_extension(KeystoreExt(keystore)); + + t.execute_with(|| { + let call = pallet_balances::Call::transfer(Default::default(), Default::default()); + let results = + >::submit_signed(call); + + let len = results.len(); + assert_eq!(len, 1); + assert_eq!(results.into_iter().filter_map(|x| x.1.ok()).count(), len); + assert_eq!(state.read().transactions.len(), 1); + + // submit another one from the same account. The nonce should be incremented. + let call = pallet_balances::Call::transfer(Default::default(), Default::default()); + let results = + >::submit_signed(call); + + let len = results.len(); + assert_eq!(len, 1); + assert_eq!(results.into_iter().filter_map(|x| x.1.ok()).count(), len); + assert_eq!(state.read().transactions.len(), 2); + + // now check that the transaction nonces are not equal + let s = state.read(); + fn nonce(tx: UncheckedExtrinsic) -> frame_system::CheckNonce { + let extra = tx.signature.unwrap().2; + extra.3 + } + let nonce1 = nonce(UncheckedExtrinsic::decode(&mut &*s.transactions[0]).unwrap()); + let nonce2 = nonce(UncheckedExtrinsic::decode(&mut &*s.transactions[1]).unwrap()); + assert!( + nonce1 != nonce2, + "Transactions should have different nonces. Got: {:?}", nonce1 + ); + }); +} + +#[test] +fn submitted_transaction_should_be_valid() { + use codec::Encode; + use frame_support::storage::StorageMap; + use sp_runtime::transaction_validity::ValidTransaction; + use sp_runtime::traits::StaticLookup; + + let mut t = new_test_ext(COMPACT_CODE, false); + let (pool, state) = TestTransactionPoolExt::new(); + t.register_extension(TransactionPoolExt::new(pool)); + + let keystore = KeyStore::new(); + keystore.write().sr25519_generate_new(Key::ID, Some(&format!("{}/hunter1", PHRASE))).unwrap(); + t.register_extension(KeystoreExt(keystore)); + + t.execute_with(|| { + let call = pallet_balances::Call::transfer(Default::default(), Default::default()); + let results = + >::submit_signed(call); + let len = results.len(); + assert_eq!(len, 1); + assert_eq!(results.into_iter().filter_map(|x| x.1.ok()).count(), len); + }); + + // check that transaction is valid, but reset environment storage, + // since CreateTransaction increments the nonce + let tx0 = state.read().transactions[0].clone(); + let mut t = new_test_ext(COMPACT_CODE, false); + t.execute_with(|| { + let extrinsic = UncheckedExtrinsic::decode(&mut &*tx0).unwrap(); + // add balance to the account + let author = extrinsic.signature.clone().unwrap().0; + let address = Indices::lookup(author).unwrap(); + >::insert(&address, 5_000_000_000_000); + + // check validity + let res = Executive::validate_transaction(extrinsic); + + assert_eq!(res.unwrap(), ValidTransaction { + priority: 2_411_002_000_000, + requires: vec![], + provides: vec![(address, 0).encode()], + longevity: 127, + propagate: true, + }); + }); +} + diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 22d0e4cca31f3..c503559ae8fd0 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -22,7 +22,7 @@ use sp_std::prelude::*; use frame_support::{ - construct_runtime, parameter_types, + construct_runtime, parameter_types, debug, weights::Weight, traits::{SplitTwoWays, Currency, Randomness}, }; @@ -33,7 +33,7 @@ use sp_runtime::{Permill, Perbill, ApplyExtrinsicResult, impl_opaque_keys, gener use sp_runtime::curve::PiecewiseLinear; use sp_runtime::transaction_validity::TransactionValidity; use sp_runtime::traits::{ - self, BlakeTwo256, Block as BlockT, NumberFor, StaticLookup, SaturatedConversion, + self, BlakeTwo256, Block as BlockT, StaticLookup, SaturatedConversion, OpaqueKeys, }; use sp_version::RuntimeVersion; @@ -78,8 +78,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to equal spec_version. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 199, - impl_version: 199, + spec_version: 200, + impl_version: 200, apis: RUNTIME_API_VERSIONS, }; @@ -436,7 +436,8 @@ impl pallet_sudo::Trait for Runtime { type Proposal = Call; } -type SubmitTransaction = TransactionSubmitter; +/// A runtime transaction submitter. +pub type SubmitTransaction = TransactionSubmitter; parameter_types! { pub const SessionDuration: BlockNumber = EPOCH_DURATION_IN_SLOTS as _; @@ -505,7 +506,11 @@ impl frame_system::offchain::CreateTransaction for .checked_next_power_of_two() .map(|c| c / 2) .unwrap_or(2) as u64; - let current_block = System::block_number().saturated_into::(); + let current_block = System::block_number() + .saturated_into::() + // The `System::block_number` is initialized with `n+1`, + // so the actual block number is `n`. + .saturating_sub(1); let tip = 0; let extra: SignedExtra = ( frame_system::CheckVersion::::new(), @@ -516,7 +521,9 @@ impl frame_system::offchain::CreateTransaction for pallet_transaction_payment::ChargeTransactionPayment::::from(tip), Default::default(), ); - let raw_payload = SignedPayload::new(call, extra).ok()?; + let raw_payload = SignedPayload::new(call, extra).map_err(|e| { + debug::warn!("Unable to create signed payload: {:?}", e); + }).ok()?; let signature = TSigner::sign(public, &raw_payload)?; let address = Indices::unlookup(account); let (call, extra, _) = raw_payload.deconstruct(); @@ -637,8 +644,8 @@ impl_runtime_apis! { } impl sp_offchain::OffchainWorkerApi for Runtime { - fn offchain_worker(number: NumberFor) { - Executive::offchain_worker(number) + fn offchain_worker(header: &::Header) { + Executive::offchain_worker(header) } } @@ -738,22 +745,29 @@ impl_runtime_apis! { #[cfg(test)] mod tests { use super::*; - use frame_system::offchain::SubmitSignedTransaction; - - fn is_submit_signed_transaction(_arg: T) where - T: SubmitSignedTransaction< - Runtime, - Call, - Extrinsic=UncheckedExtrinsic, - CreateTransaction=Runtime, - Signer=ImOnlineId, - >, - {} + use frame_system::offchain::{SignAndSubmitTransaction, SubmitSignedTransaction}; #[test] - fn validate_bounds() { - let x = SubmitTransaction::default(); - is_submit_signed_transaction(x); + fn validate_transaction_submitter_bounds() { + fn is_submit_signed_transaction() where + T: SubmitSignedTransaction< + Runtime, + Call, + >, + {} + + fn is_sign_and_submit_transaction() where + T: SignAndSubmitTransaction< + Runtime, + Call, + Extrinsic=UncheckedExtrinsic, + CreateTransaction=Runtime, + Signer=ImOnlineId, + >, + {} + + is_submit_signed_transaction::(); + is_sign_and_submit_transaction::(); } #[test] diff --git a/bin/node/testing/src/keyring.rs b/bin/node/testing/src/keyring.rs index 5b6f7710e0871..6b0d06875d692 100644 --- a/bin/node/testing/src/keyring.rs +++ b/bin/node/testing/src/keyring.rs @@ -23,7 +23,9 @@ use sp_runtime::generic::Era; use codec::Encode; /// Alice's account id. -pub fn alice() -> AccountId { AccountKeyring::Alice.into() } +pub fn alice() -> AccountId { + AccountKeyring::Alice.into() +} /// Bob's account id. pub fn bob() -> AccountId { diff --git a/client/offchain/src/lib.rs b/client/offchain/src/lib.rs index 2beb8fe6c9332..e4bf1bb75c7a2 100644 --- a/client/offchain/src/lib.rs +++ b/client/offchain/src/lib.rs @@ -42,7 +42,7 @@ use futures::future::Future; use log::{debug, warn}; use sc_network::NetworkStateInfo; use sp_core::{offchain::{self, OffchainStorage}, ExecutionContext}; -use sp_runtime::{generic::BlockId, traits::{self, ProvideRuntimeApi}}; +use sp_runtime::{generic::BlockId, traits::{self, ProvideRuntimeApi, Header}}; mod api; @@ -92,33 +92,47 @@ impl OffchainWorkers< #[must_use] pub fn on_block_imported( &self, - number: &::Number, + header: &Block::Header, network_state: Arc, is_validator: bool, ) -> impl Future { let runtime = self.client.runtime_api(); - let at = BlockId::number(*number); - let has_api = runtime.has_api::>(&at); - debug!("Checking offchain workers at {:?}: {:?}", at, has_api); - - if has_api.unwrap_or(false) { + let at = BlockId::number(*header.number()); + let has_api_v1 = runtime.has_api_with::, _>( + &at, |v| v == 1 + ); + let has_api_v2 = runtime.has_api::>(&at); + let version = match (has_api_v1, has_api_v2) { + (_, Ok(true)) => 2, + (Ok(true), _) => 1, + _ => 0, + }; + + debug!("Checking offchain workers at {:?}: version:{}", at, version); + if version > 0 { let (api, runner) = api::AsyncApi::new( self.db.clone(), network_state.clone(), is_validator, ); debug!("Spawning offchain workers at {:?}", at); - let number = *number; + let header = header.clone(); let client = self.client.clone(); self.spawn_worker(move || { let runtime = client.runtime_api(); let api = Box::new(api); debug!("Running offchain workers at {:?}", at); - let run = runtime.offchain_worker_with_context( - &at, - ExecutionContext::OffchainCall(Some((api, offchain::Capabilities::all()))), - number, - ); + let context = ExecutionContext::OffchainCall(Some( + (api, offchain::Capabilities::all()) + )); + let run = if version == 2 { + runtime.offchain_worker_with_context(&at, context, &header) + } else { + #[allow(deprecated)] + runtime.offchain_worker_before_version_2_with_context( + &at, context, *header.number() + ) + }; if let Err(e) = run { log::error!("Error running offchain workers at {:?}: {:?}", at, e); } @@ -150,6 +164,7 @@ mod tests { use substrate_test_runtime_client::runtime::Block; use sc_transaction_pool::{BasicPool, FullChainApi}; use sp_transaction_pool::{TransactionPool, InPoolTransaction}; + use sp_runtime::{generic::Header, traits::Header as _}; struct MockNetworkStateInfo(); @@ -169,7 +184,7 @@ mod tests { fn submit_at( &self, at: &BlockId, - extrinsic: ::Extrinsic, + extrinsic: ::Extrinsic, ) -> Result<(), ()> { futures::executor::block_on(self.0.submit_one(&at, extrinsic)) .map(|_| ()) @@ -187,10 +202,17 @@ mod tests { .register_transaction_pool(Arc::downgrade(&pool.clone()) as _); let db = sc_client_db::offchain::LocalStorage::new_test(); let network_state = Arc::new(MockNetworkStateInfo()); + let header = Header::new( + 0u64, + Default::default(), + Default::default(), + Default::default(), + Default::default(), + ); // when let offchain = OffchainWorkers::new(client, db); - futures::executor::block_on(offchain.on_block_imported(&0u64, network_state, false)); + futures::executor::block_on(offchain.on_block_imported(&header, network_state, false)); // then assert_eq!(pool.0.status().ready, 1); diff --git a/client/rpc/src/state/tests.rs b/client/rpc/src/state/tests.rs index b1b7e059e3cbe..6101724a832ff 100644 --- a/client/rpc/src/state/tests.rs +++ b/client/rpc/src/state/tests.rs @@ -400,7 +400,7 @@ fn should_return_runtime_version() { \"specVersion\":1,\"implVersion\":1,\"apis\":[[\"0xdf6acb689907609b\",2],\ [\"0x37e397fc7c91f5e4\",1],[\"0xd2bc9897eed08f15\",1],[\"0x40fe3ad401f8959a\",4],\ [\"0xc6e9a76309f39b09\",1],[\"0xdd718d5cc53262d4\",1],[\"0xcbca25e39f142387\",1],\ - [\"0xf78b278be53f454c\",1],[\"0xab3c0572291feb8b\",1],[\"0xbc9d89904f5b923f\",1]]}"; + [\"0xf78b278be53f454c\",2],[\"0xab3c0572291feb8b\",1],[\"0xbc9d89904f5b923f\",1]]}"; let runtime_version = api.runtime_version(None.into()).wait().unwrap(); let serialized = serde_json::to_string(&runtime_version).unwrap(); diff --git a/client/service/src/builder.rs b/client/service/src/builder.rs index 8a4d760acc712..1608a51febbfd 100644 --- a/client/service/src/builder.rs +++ b/client/service/src/builder.rs @@ -43,7 +43,7 @@ use sc_rpc; use sp_api::ConstructRuntimeApi; use sp_runtime::generic::BlockId; use sp_runtime::traits::{ - Block as BlockT, ProvideRuntimeApi, NumberFor, Header, SaturatedConversion, + Block as BlockT, ProvideRuntimeApi, NumberFor, SaturatedConversion, }; use sc_executor::{NativeExecutor, NativeExecutionDispatch}; use std::{ @@ -867,7 +867,6 @@ ServiceBuilder< let events = client.import_notification_stream() .map(|v| Ok::<_, ()>(v)).compat() .for_each(move |notification| { - let number = *notification.header.number(); let txpool = txpool.upgrade(); if let Some(txpool) = txpool.as_ref() { @@ -880,8 +879,11 @@ ServiceBuilder< let offchain = offchain.as_ref().and_then(|o| o.upgrade()); if let Some(offchain) = offchain { - let future = offchain.on_block_imported(&number, network_state_info.clone(), is_validator) - .map(|()| Ok(())); + let future = offchain.on_block_imported( + ¬ification.header, + network_state_info.clone(), + is_validator + ).map(|()| Ok(())); let _ = to_spawn_tx_.unbounded_send(Box::new(Compat::new(future))); } diff --git a/frame/authorship/src/lib.rs b/frame/authorship/src/lib.rs index 1c25b81790aa1..216f5c729a9c4 100644 --- a/frame/authorship/src/lib.rs +++ b/frame/authorship/src/lib.rs @@ -571,14 +571,22 @@ mod tests { inner: vec![seal_header(create_header(0, Default::default(), Default::default()), 999)], }; + let initialize_block = |number, hash: H256| System::initialize( + &number, + &hash, + &Default::default(), + &Default::default(), + Default::default() + ); + for number in 1..8 { - System::initialize(&number, &canon_chain.best_hash(), &Default::default(), &Default::default()); + initialize_block(number, canon_chain.best_hash()); let header = seal_header(System::finalize(), author_a); canon_chain.push(header); } // initialize so system context is set up correctly. - System::initialize(&8, &canon_chain.best_hash(), &Default::default(), &Default::default()); + initialize_block(8, canon_chain.best_hash()); // 2 of the same uncle at once { @@ -663,7 +671,13 @@ mod tests { ); header.digest_mut().pop(); // pop the seal off. - System::initialize(&1, &Default::default(), &Default::default(), header.digest()); + System::initialize( + &1, + &Default::default(), + &Default::default(), + header.digest(), + Default::default(), + ); assert_eq!(Authorship::author(), author); }); diff --git a/frame/babe/src/tests.rs b/frame/babe/src/tests.rs index 4e47014a8a54e..dbd61238166b0 100644 --- a/frame/babe/src/tests.rs +++ b/frame/babe/src/tests.rs @@ -81,7 +81,13 @@ fn first_block_epoch_zero_start() { ); assert_eq!(Babe::genesis_slot(), 0); - System::initialize(&1, &Default::default(), &Default::default(), &pre_digest); + System::initialize( + &1, + &Default::default(), + &Default::default(), + &pre_digest, + Default::default(), + ); // see implementation of the function for details why: we issue an // epoch-change digest but don't do it via the normal session mechanism. diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index ca158980099c9..f6e712577bece 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -860,6 +860,16 @@ fn storage_size() { }); } +fn initialize_block(number: u64) { + System::initialize( + &number, + &[0u8; 32].into(), + &[0u8; 32].into(), + &Default::default(), + Default::default(), + ); +} + #[test] fn deduct_blocks() { let (wasm, code_hash) = compile_module::(CODE_SET_RENT).unwrap(); @@ -880,7 +890,7 @@ fn deduct_blocks() { assert_eq!(bob_contract.rent_allowance, 1_000); // Advance 4 blocks - System::initialize(&5, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); + initialize_block(5); // Trigger rent through call assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null())); @@ -895,7 +905,7 @@ fn deduct_blocks() { assert_eq!(Balances::free_balance(BOB), 30_000 - rent); // Advance 7 blocks more - System::initialize(&12, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); + initialize_block(12); // Trigger rent through call assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null())); @@ -970,7 +980,7 @@ fn claim_surcharge(blocks: u64, trigger_call: impl Fn() -> bool, removes: bool) )); // Advance blocks - System::initialize(&blocks, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); + initialize_block(blocks); // Trigger rent through call assert!(trigger_call()); @@ -1010,7 +1020,7 @@ fn removals(trigger_call: impl Fn() -> bool) { assert_eq!(Balances::free_balance(&BOB), 100); // Advance blocks - System::initialize(&10, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); + initialize_block(10); // Trigger rent through call assert!(trigger_call()); @@ -1018,7 +1028,7 @@ fn removals(trigger_call: impl Fn() -> bool) { assert_eq!(Balances::free_balance(&BOB), subsistence_threshold); // Advance blocks - System::initialize(&20, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); + initialize_block(20); // Trigger rent must have no effect assert!(trigger_call()); @@ -1044,7 +1054,7 @@ fn removals(trigger_call: impl Fn() -> bool) { assert_eq!(Balances::free_balance(&BOB), 1_000); // Advance blocks - System::initialize(&10, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); + initialize_block(10); // Trigger rent through call assert!(trigger_call()); @@ -1053,7 +1063,7 @@ fn removals(trigger_call: impl Fn() -> bool) { assert_eq!(Balances::free_balance(&BOB), 900); // Advance blocks - System::initialize(&20, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); + initialize_block(20); // Trigger rent must have no effect assert!(trigger_call()); @@ -1084,7 +1094,7 @@ fn removals(trigger_call: impl Fn() -> bool) { assert_eq!(Balances::free_balance(&BOB), Balances::minimum_balance()); // Advance blocks - System::initialize(&10, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); + initialize_block(10); // Trigger rent through call assert!(trigger_call()); @@ -1092,7 +1102,7 @@ fn removals(trigger_call: impl Fn() -> bool) { assert_eq!(Balances::free_balance(&BOB), Balances::minimum_balance()); // Advance blocks - System::initialize(&20, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); + initialize_block(20); // Trigger rent must have no effect assert!(trigger_call()); @@ -1121,7 +1131,7 @@ fn call_removed_contract() { assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null())); // Advance blocks - System::initialize(&10, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); + initialize_block(10); // Calling contract should remove contract and fail. assert_err!( @@ -1208,7 +1218,7 @@ fn default_rent_allowance_on_instantiate() { assert_eq!(bob_contract.rent_allowance, >::max_value()); // Advance blocks - System::initialize(&5, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); + initialize_block(5); // Trigger rent through call assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null())); @@ -1354,7 +1364,7 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage: } // Advance 4 blocks, to the 5th. - System::initialize(&5, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); + initialize_block(5); // Call `BOB`, which makes it pay rent. Since the rent allowance is set to 0 // we expect that it will get removed leaving tombstone. @@ -1383,7 +1393,7 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage: if !test_restore_to_with_dirty_storage { // Advance 1 block, to the 6th. - System::initialize(&6, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); + initialize_block(6); } // Perform a call to `DJANGO`. This should either perform restoration successfully or diff --git a/frame/executive/Cargo.toml b/frame/executive/Cargo.toml index b58d4a6790730..b2b30edd1cc58 100644 --- a/frame/executive/Cargo.toml +++ b/frame/executive/Cargo.toml @@ -5,13 +5,13 @@ authors = ["Parity Technologies "] edition = "2018" [dependencies] -serde = { version = "1.0.101", optional = true } codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] } -sp-std = { version = "2.0.0", default-features = false, path = "../../primitives/std" } -sp-io ={ path = "../../primitives/io", default-features = false } -sp-runtime = { version = "2.0.0", default-features = false, path = "../../primitives/runtime" } frame-support = { version = "2.0.0", default-features = false, path = "../support" } frame-system = { version = "2.0.0", default-features = false, path = "../system" } +serde = { version = "1.0.101", optional = true } +sp-io ={ path = "../../primitives/io", default-features = false } +sp-runtime = { version = "2.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "2.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] hex-literal = "0.2.1" @@ -23,11 +23,11 @@ pallet-transaction-payment = { version = "2.0.0", path = "../transaction-payment [features] default = ["std"] std = [ - "sp-std/std", + "codec/std", "frame-support/std", + "frame-system/std", "serde", - "codec/std", - "sp-runtime/std", "sp-io/std", - "frame-system/std", + "sp-runtime/std", + "sp-std/std", ] diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index 1ac67d05e2c0d..005178a33c57e 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -82,7 +82,7 @@ use sp_runtime::{ generic::Digest, ApplyExtrinsicResult, traits::{ self, Header, Zero, One, Checkable, Applyable, CheckEqual, OnFinalize, OnInitialize, - NumberFor, Block as BlockT, OffchainWorker, Dispatchable, + NumberFor, Block as BlockT, OffchainWorker, Dispatchable, Saturating, }, transaction_validity::TransactionValidity, }; @@ -154,9 +154,23 @@ where { /// Start the execution of a particular block. pub fn initialize_block(header: &System::Header) { - let mut digests = >::default(); - header.digest().logs().iter().for_each(|d| if d.as_pre_runtime().is_some() { digests.push(d.clone()) }); - Self::initialize_block_impl(header.number(), header.parent_hash(), header.extrinsics_root(), &digests); + let digests = Self::extract_pre_digest(&header); + Self::initialize_block_impl( + header.number(), + header.parent_hash(), + header.extrinsics_root(), + &digests + ); + } + + fn extract_pre_digest(header: &System::Header) -> DigestOf { + let mut digest = >::default(); + header.digest().logs() + .iter() + .for_each(|d| if d.as_pre_runtime().is_some() { + digest.push(d.clone()) + }); + digest } fn initialize_block_impl( @@ -165,7 +179,13 @@ where extrinsics_root: &System::Hash, digest: &Digest, ) { - >::initialize(block_number, parent_hash, extrinsics_root, digest); + >::initialize( + block_number, + parent_hash, + extrinsics_root, + digest, + frame_system::InitKind::Full, + ); >::on_initialize(*block_number); >::register_extra_weight_unchecked( >::on_initialize(*block_number) @@ -310,8 +330,24 @@ where } /// Start an offchain worker and generate extrinsics. - pub fn offchain_worker(n: System::BlockNumber) { - >::offchain_worker(n) + pub fn offchain_worker(header: &System::Header) { + // We need to keep events available for offchain workers, + // hence we initialize the block manually. + // OffchainWorker RuntimeApi should skip initialization. + let digests = Self::extract_pre_digest(header); + + >::initialize( + header.number(), + header.parent_hash(), + header.extrinsics_root(), + &digests, + frame_system::InitKind::Inspection, + ); + >::offchain_worker( + // to maintain backward compatibility we call module offchain workers + // with parent block number. + header.number().saturating_sub(1.into()) + ) } } diff --git a/frame/finality-tracker/src/lib.rs b/frame/finality-tracker/src/lib.rs index 1489fcd0ddfb7..c9c9fbb0b5743 100644 --- a/frame/finality-tracker/src/lib.rs +++ b/frame/finality-tracker/src/lib.rs @@ -291,7 +291,13 @@ mod tests { TestExternalities::new(t).execute_with(|| { let mut parent_hash = System::parent_hash(); for i in 2..106 { - System::initialize(&i, &parent_hash, &Default::default(), &Default::default()); + System::initialize( + &i, + &parent_hash, + &Default::default(), + &Default::default(), + Default::default() + ); FinalityTracker::on_finalize(i); let hdr = System::finalize(); parent_hash = hdr.hash(); @@ -310,7 +316,13 @@ mod tests { TestExternalities::new(t).execute_with(|| { let mut parent_hash = System::parent_hash(); for i in 2..106 { - System::initialize(&i, &parent_hash, &Default::default(), &Default::default()); + System::initialize( + &i, + &parent_hash, + &Default::default(), + &Default::default(), + Default::default(), + ); assert_ok!(FinalityTracker::dispatch( Call::final_hint(i-1), Origin::NONE, diff --git a/frame/grandpa/src/tests.rs b/frame/grandpa/src/tests.rs index c6728c70c8fb4..ff3841b8d4593 100644 --- a/frame/grandpa/src/tests.rs +++ b/frame/grandpa/src/tests.rs @@ -18,17 +18,27 @@ #![cfg(test)] -use sp_runtime::{testing::Digest, traits::{Header, OnFinalize}}; +use sp_runtime::{testing::{H256, Digest}, traits::{Header, OnFinalize}}; use crate::mock::*; use frame_system::{EventRecord, Phase}; use codec::{Decode, Encode}; use fg_primitives::ScheduledChange; use super::*; +fn initialize_block(number: u64, parent_hash: H256) { + System::initialize( + &number, + &parent_hash, + &Default::default(), + &Default::default(), + Default::default(), + ); +} + #[test] fn authorities_change_logged() { new_test_ext(vec![(1, 1), (2, 1), (3, 1)]).execute_with(|| { - System::initialize(&1, &Default::default(), &Default::default(), &Default::default()); + initialize_block(1, Default::default()); Grandpa::schedule_change(to_authorities(vec![(4, 1), (5, 1), (6, 1)]), 0, None).unwrap(); System::note_finished_extrinsics(); @@ -56,7 +66,7 @@ fn authorities_change_logged() { #[test] fn authorities_change_logged_after_delay() { new_test_ext(vec![(1, 1), (2, 1), (3, 1)]).execute_with(|| { - System::initialize(&1, &Default::default(), &Default::default(), &Default::default()); + initialize_block(1, Default::default()); Grandpa::schedule_change(to_authorities(vec![(4, 1), (5, 1), (6, 1)]), 1, None).unwrap(); Grandpa::on_finalize(1); let header = System::finalize(); @@ -71,7 +81,7 @@ fn authorities_change_logged_after_delay() { // no change at this height. assert_eq!(System::events(), vec![]); - System::initialize(&2, &header.hash(), &Default::default(), &Default::default()); + initialize_block(2, header.hash()); System::note_finished_extrinsics(); Grandpa::on_finalize(2); @@ -89,7 +99,7 @@ fn authorities_change_logged_after_delay() { #[test] fn cannot_schedule_change_when_one_pending() { new_test_ext(vec![(1, 1), (2, 1), (3, 1)]).execute_with(|| { - System::initialize(&1, &Default::default(), &Default::default(), &Default::default()); + initialize_block(1, Default::default()); Grandpa::schedule_change(to_authorities(vec![(4, 1), (5, 1), (6, 1)]), 1, None).unwrap(); assert!(>::exists()); assert!(Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None).is_err()); @@ -97,14 +107,14 @@ fn cannot_schedule_change_when_one_pending() { Grandpa::on_finalize(1); let header = System::finalize(); - System::initialize(&2, &header.hash(), &Default::default(), &Default::default()); + initialize_block(2, header.hash()); assert!(>::exists()); assert!(Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None).is_err()); Grandpa::on_finalize(2); let header = System::finalize(); - System::initialize(&3, &header.hash(), &Default::default(), &Default::default()); + initialize_block(3, header.hash()); assert!(!>::exists()); assert!(Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None).is_ok()); @@ -132,7 +142,7 @@ fn new_decodes_from_old() { #[test] fn dispatch_forced_change() { new_test_ext(vec![(1, 1), (2, 1), (3, 1)]).execute_with(|| { - System::initialize(&1, &Default::default(), &Default::default(), &Default::default()); + initialize_block(1, Default::default()); Grandpa::schedule_change( to_authorities(vec![(4, 1), (5, 1), (6, 1)]), 5, @@ -146,7 +156,7 @@ fn dispatch_forced_change() { let mut header = System::finalize(); for i in 2..7 { - System::initialize(&i, &header.hash(), &Default::default(), &Default::default()); + initialize_block(i, header.hash()); assert!(>::get().unwrap().forced.is_some()); assert_eq!(Grandpa::next_forced(), Some(11)); assert!(Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None).is_err()); @@ -159,7 +169,7 @@ fn dispatch_forced_change() { // change has been applied at the end of block 6. // add a normal change. { - System::initialize(&7, &header.hash(), &Default::default(), &Default::default()); + initialize_block(7, header.hash()); assert!(!>::exists()); assert_eq!(Grandpa::grandpa_authorities(), to_authorities(vec![(4, 1), (5, 1), (6, 1)])); assert!(Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None).is_ok()); @@ -169,7 +179,7 @@ fn dispatch_forced_change() { // run the normal change. { - System::initialize(&8, &header.hash(), &Default::default(), &Default::default()); + initialize_block(8, header.hash()); assert!(>::exists()); assert_eq!(Grandpa::grandpa_authorities(), to_authorities(vec![(4, 1), (5, 1), (6, 1)])); assert!(Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None).is_err()); @@ -180,7 +190,7 @@ fn dispatch_forced_change() { // normal change applied. but we can't apply a new forced change for some // time. for i in 9..11 { - System::initialize(&i, &header.hash(), &Default::default(), &Default::default()); + initialize_block(i, header.hash()); assert!(!>::exists()); assert_eq!(Grandpa::grandpa_authorities(), to_authorities(vec![(5, 1)])); assert_eq!(Grandpa::next_forced(), Some(11)); @@ -190,7 +200,7 @@ fn dispatch_forced_change() { } { - System::initialize(&11, &header.hash(), &Default::default(), &Default::default()); + initialize_block(11, header.hash()); assert!(!>::exists()); assert!(Grandpa::schedule_change(to_authorities(vec![(5, 1), (6, 1), (7, 1)]), 5, Some(0)).is_ok()); assert_eq!(Grandpa::next_forced(), Some(21)); @@ -205,7 +215,7 @@ fn dispatch_forced_change() { fn schedule_pause_only_when_live() { new_test_ext(vec![(1, 1), (2, 1), (3, 1)]).execute_with(|| { // we schedule a pause at block 1 with delay of 1 - System::initialize(&1, &Default::default(), &Default::default(), &Default::default()); + initialize_block(1, Default::default()); Grandpa::schedule_pause(1).unwrap(); // we've switched to the pending pause state @@ -220,7 +230,7 @@ fn schedule_pause_only_when_live() { Grandpa::on_finalize(1); let _ = System::finalize(); - System::initialize(&2, &Default::default(), &Default::default(), &Default::default()); + initialize_block(2, Default::default()); // signaling a pause now should fail assert!(Grandpa::schedule_pause(1).is_err()); @@ -239,7 +249,7 @@ fn schedule_pause_only_when_live() { #[test] fn schedule_resume_only_when_paused() { new_test_ext(vec![(1, 1), (2, 1), (3, 1)]).execute_with(|| { - System::initialize(&1, &Default::default(), &Default::default(), &Default::default()); + initialize_block(1, Default::default()); // the set is currently live, resuming it is an error assert!(Grandpa::schedule_resume(1).is_err()); @@ -260,16 +270,16 @@ fn schedule_resume_only_when_paused() { ); // we schedule the set to go back live in 2 blocks - System::initialize(&2, &Default::default(), &Default::default(), &Default::default()); + initialize_block(2, Default::default()); Grandpa::schedule_resume(2).unwrap(); Grandpa::on_finalize(2); let _ = System::finalize(); - System::initialize(&3, &Default::default(), &Default::default(), &Default::default()); + initialize_block(3, Default::default()); Grandpa::on_finalize(3); let _ = System::finalize(); - System::initialize(&4, &Default::default(), &Default::default(), &Default::default()); + initialize_block(4, Default::default()); Grandpa::on_finalize(4); let _ = System::finalize(); diff --git a/frame/im-online/src/lib.rs b/frame/im-online/src/lib.rs index 64cd9fae6f420..62d0ec6ac422b 100644 --- a/frame/im-online/src/lib.rs +++ b/frame/im-online/src/lib.rs @@ -171,10 +171,14 @@ pub type AuthIndex = u32; pub struct Heartbeat where BlockNumber: PartialEq + Eq + Decode + Encode, { - block_number: BlockNumber, - network_state: OpaqueNetworkState, - session_index: SessionIndex, - authority_index: AuthIndex, + /// Block number at the time heartbeat is created.. + pub block_number: BlockNumber, + /// A state of local network (peer id and external addresses) + pub network_state: OpaqueNetworkState, + /// Index of the current session. + pub session_index: SessionIndex, + /// An index of the authority on the list of validators. + pub authority_index: AuthIndex, } pub trait Trait: frame_system::Trait + pallet_session::historical::Trait { diff --git a/frame/randomness-collective-flip/src/lib.rs b/frame/randomness-collective-flip/src/lib.rs index 69c1680659387..c7f7bb0db78b8 100644 --- a/frame/randomness-collective-flip/src/lib.rs +++ b/frame/randomness-collective-flip/src/lib.rs @@ -210,7 +210,13 @@ mod tests { let mut parent_hash = System::parent_hash(); for i in 1 .. (blocks + 1) { - System::initialize(&i, &parent_hash, &Default::default(), &Default::default()); + System::initialize( + &i, + &parent_hash, + &Default::default(), + &Default::default(), + frame_system::InitKind::Full, + ); CollectiveFlip::on_initialize(i); let header = System::finalize(); diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index 3187ea2727432..4541e973c11b4 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -531,6 +531,27 @@ pub fn ensure_none(o: OuterOrigin) -> Result<(), BadOrig } } +/// A type of block initialization to perform. +pub enum InitKind { + /// Leave inspectable storage entries in state. + /// + /// i.e. `Events` are not being reset. + /// Should only be used for off-chain calls, + /// regular block execution should clear those. + Inspection, + + /// Reset also inspectable storage entries. + /// + /// This should be used for regular block execution. + Full, +} + +impl Default for InitKind { + fn default() -> Self { + InitKind::Full + } +} + impl Module { /// Deposits an event into this block's event record. pub fn deposit_event(event: impl Into) { @@ -633,6 +654,7 @@ impl Module { parent_hash: &T::Hash, txs_root: &T::Hash, digest: &DigestOf, + kind: InitKind, ) { // populate environment storage::unhashed::put(well_known_keys::EXTRINSIC_INDEX, &0u32); @@ -641,9 +663,12 @@ impl Module { >::put(parent_hash); >::insert(*number - One::one(), parent_hash); >::put(txs_root); - >::kill(); - EventCount::kill(); - >::remove_all(); + + if let InitKind::Full = kind { + >::kill(); + EventCount::kill(); + >::remove_all(); + } } /// Remove temporary "environment" entries in storage. @@ -1213,7 +1238,13 @@ mod tests { #[test] fn deposit_event_should_work() { new_test_ext().execute_with(|| { - System::initialize(&1, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); + System::initialize( + &1, + &[0u8; 32].into(), + &[0u8; 32].into(), + &Default::default(), + InitKind::Full, + ); System::note_finished_extrinsics(); System::deposit_event(1u16); System::finalize(); @@ -1228,7 +1259,13 @@ mod tests { ] ); - System::initialize(&2, &[0u8; 32].into(), &[0u8; 32].into(), &Default::default()); + System::initialize( + &2, + &[0u8; 32].into(), + &[0u8; 32].into(), + &Default::default(), + InitKind::Full, + ); System::deposit_event(42u16); System::note_applied_extrinsic(&Ok(()), 0, Default::default()); System::note_applied_extrinsic(&Err(DispatchError::BadOrigin), 0, Default::default()); @@ -1257,6 +1294,7 @@ mod tests { &[0u8; 32].into(), &[0u8; 32].into(), &Default::default(), + InitKind::Full, ); System::note_finished_extrinsics(); @@ -1322,6 +1360,7 @@ mod tests { &[n as u8 - 1; 32].into(), &[0u8; 32].into(), &Default::default(), + InitKind::Full, ); System::finalize(); diff --git a/frame/system/src/offchain.rs b/frame/system/src/offchain.rs index 14b57d8903a7a..f5fda34585d4d 100644 --- a/frame/system/src/offchain.rs +++ b/frame/system/src/offchain.rs @@ -14,17 +14,55 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -//! Module helpers for offchain calls. +//! Module helpers for off-chain calls. use codec::Encode; -use sp_runtime::app_crypto::{self, RuntimeAppPublic}; +use sp_std::convert::TryInto; +use sp_std::prelude::Vec; +use sp_runtime::app_crypto::{RuntimeAppPublic, AppPublic, AppSignature}; use sp_runtime::traits::{Extrinsic as ExtrinsicT, IdentifyAccount}; +use frame_support::debug; + +/// Creates runtime-specific signed transaction. +/// +/// This trait should be implemented by your `Runtime` to be able +/// to submit `SignedTransaction`s` to the pool from off-chain code. +pub trait CreateTransaction { + /// A `Public` key representing a particular `AccountId`. + type Public: IdentifyAccount + Clone; + /// A `Signature` generated by the `Signer`. + type Signature; + + /// Attempt to create signed extrinsic data that encodes call from given account. + /// + /// Runtime implementation is free to construct the payload to sign and the signature + /// in any way it wants. + /// Returns `None` if signed extrinsic could not be created (either because signing failed + /// or because of any other runtime-specific reason). + fn create_transaction>( + call: Extrinsic::Call, + public: Self::Public, + account: T::AccountId, + nonce: T::Index, + ) -> Option<(Extrinsic::Call, Extrinsic::SignaturePayload)>; +} /// A trait responsible for signing a payload using given account. +/// +/// This trait is usually going to represent a local public key +/// that has ability to sign arbitrary `Payloads`. +/// +/// NOTE: Most likely you don't need to implement this trait manually. +/// It has a blanket implementation for all `RuntimeAppPublic` types, +/// so it's enough to pass an application-specific crypto type. +/// +/// To easily create `SignedTransaction`s have a look at the +/// [`TransactionSubmitter`] type. pub trait Signer { /// Sign any encodable payload with given account and produce a signature. /// - /// Returns `Some` if signing succeeded and `None` in case the `account` couldn't be used. + /// Returns `Some` if signing succeeded and `None` in case the `account` couldn't + /// be used (for instance we couldn't convert it to required application specific crypto). fn sign(public: Public, payload: &Payload) -> Option; } @@ -33,22 +71,22 @@ pub trait Signer { /// This implementation additionaly supports conversion to/from multi-signature/multi-signer /// wrappers. /// If the wrapped crypto doesn't match `AppPublic`s crypto `None` is returned. -impl Signer for AppPublic where - AppPublic: RuntimeAppPublic - + app_crypto::AppPublic - + From<::Generic>, - ::Signature: app_crypto::AppSignature, +impl Signer for TAnyAppPublic where + TAnyAppPublic: RuntimeAppPublic + + AppPublic + + From<::Generic>, + ::Signature: AppSignature, Signature: From< - <::Signature as app_crypto::AppSignature>::Generic + <::Signature as AppSignature>::Generic >, - Public: sp_std::convert::TryInto<::Generic> + Public: TryInto<::Generic> { fn sign(public: Public, raw_payload: &Payload) -> Option { raw_payload.using_encoded(|payload| { let public = public.try_into().ok()?; - AppPublic::from(public).sign(&payload) + TAnyAppPublic::from(public).sign(&payload) .map( - <::Signature as app_crypto::AppSignature> + <::Signature as AppSignature> ::Generic::from ) .map(Signature::from) @@ -56,36 +94,20 @@ impl Signer for AppPublic where } } -/// Creates a runtime-specific signed transaction. -pub trait CreateTransaction { - /// A `Public` key representing a particular `AccountId`. - type Public: IdentifyAccount + Clone; - /// A `Signature` generated by the `Signer`. - type Signature; - - /// Attempt to create signed extrinsic data that encodes call from given account. - /// - /// Runtime implementation is free to construct the payload to sign and the signature - /// in any way it wants. - /// Returns `None` if signed extrinsic could not be created (either because signing failed - /// or because of any other runtime-specific reason). - fn create_transaction>( - call: Extrinsic::Call, - public: Self::Public, - account: T::AccountId, - nonce: T::Index, - ) -> Option<(Extrinsic::Call, Extrinsic::SignaturePayload)>; -} - -type PublicOf = < - >::CreateTransaction as CreateTransaction< - T, - >::Extrinsic, - > +/// Retrieves a public key type for given `SignAndSubmitTransaction`. +pub type PublicOf = < + >::CreateTransaction + as + CreateTransaction>::Extrinsic> >::Public; -/// A trait to sign and submit transactions in offchain calls. -pub trait SubmitSignedTransaction { +/// A trait to sign and submit transactions in off-chain calls. +/// +/// NOTE: Most likely you should not implement this trait yourself. +/// There is an implementation for +/// [`TransactionSubmitter`] type, which +/// you should use. +pub trait SignAndSubmitTransaction { /// Unchecked extrinsic type. type Extrinsic: ExtrinsicT + codec::Encode; @@ -107,15 +129,30 @@ pub trait SubmitSignedTransaction { let call = call.into(); let id = public.clone().into_account(); let expected = >::account_nonce(&id); + debug::native::debug!( + target: "offchain", + "Creating signed transaction from account: {:?} (nonce: {:?})", + id, + expected, + ); let (call, signature_data) = Self::CreateTransaction - ::create_transaction::(call, public, id, expected) + ::create_transaction::(call, public, id.clone(), expected) .ok_or(())?; + // increment the nonce. This is fine, since the code should always + // be running in off-chain context, so we NEVER persists data. + >::inc_account_nonce(&id); + let xt = Self::Extrinsic::new(call, Some(signature_data)).ok_or(())?; sp_io::offchain::submit_transaction(xt.encode()) } } /// A trait to submit unsigned transactions in off-chain calls. +/// +/// NOTE: Most likely you should not implement this trait yourself. +/// There is an implementation for +/// [`TransactionSubmitter`] type, which +/// you should use. pub trait SubmitUnsignedTransaction { /// Unchecked extrinsic type. type Extrinsic: ExtrinsicT + codec::Encode; @@ -130,9 +167,114 @@ pub trait SubmitUnsignedTransaction { } } +/// A utility trait to easily create signed transactions +/// from accounts in node's local keystore. +/// +/// NOTE: Most likely you should not implement this trait yourself. +/// There is an implementation for +/// [`TransactionSubmitter`] type, which +/// you should use. +pub trait SubmitSignedTransaction { + /// A `SignAndSubmitTransaction` implementation. + type SignAndSubmit: SignAndSubmitTransaction; + + /// Find local keys that match given list of accounts. + /// + /// Technically it finds an intersection between given list of `AccountId`s + /// and accounts that are represented by public keys in local keystore. + /// If `None` is passed it returns all accounts in the keystore. + /// + /// Returns both public keys and `AccountId`s of accounts that are available. + /// Such accounts can later be used to sign a payload or send signed transactions. + fn find_local_keys(accounts: Option>) -> Vec<( + T::AccountId, + PublicOf, + )>; + + /// Find all available local keys. + /// + /// This is equivalent of calling `find_local_keys(None)`. + fn find_all_local_keys() -> Vec<(T::AccountId, PublicOf)> { + Self::find_local_keys(None as Option>) + } + + /// Check if there are keys for any of given accounts that could be used to send a transaction. + /// + /// This check can be used as an early-exit condition to avoid doing too + /// much work, before we actually realise that there are no accounts that you + /// we could use for signing. + fn can_sign_with(accounts: Option>) -> bool { + !Self::find_local_keys(accounts).is_empty() + } + + /// Check if there are any keys that could be used for signing. + /// + /// This is equivalent of calling `can_sign_with(None)`. + fn can_sign() -> bool { + Self::can_sign_with(None as Option>) + } + + /// Create and submit signed transactions from supported accounts. + /// + /// This method should intersect given list of accounts with the ones + /// supported locally and submit signed transaction containing given `Call` + /// with every of them. + /// + /// Returns a vector of results and account ids that were supported. + #[must_use] + fn submit_signed_from( + call: impl Into + Clone, + accounts: impl IntoIterator, + ) -> Vec<(T::AccountId, Result<(), ()>)> { + let keys = Self::find_local_keys(Some(accounts)); + keys.into_iter().map(|(account, pub_key)| { + let call = call.clone().into(); + ( + account, + Self::SignAndSubmit::sign_and_submit(call, pub_key) + ) + }).collect() + } + + /// Create and submit signed transactions from all local accounts. + /// + /// This method submits a signed transaction from all local accounts + /// for given application crypto. + /// + /// Returns a vector of results and account ids that were supported. + #[must_use] + fn submit_signed( + call: impl Into + Clone, + ) -> Vec<(T::AccountId, Result<(), ()>)> { + let keys = Self::find_all_local_keys(); + keys.into_iter().map(|(account, pub_key)| { + let call = call.clone().into(); + ( + account, + Self::SignAndSubmit::sign_and_submit(call, pub_key) + ) + }).collect() + } +} + /// A default type used to submit transactions to the pool. -pub struct TransactionSubmitter { - _signer: sp_std::marker::PhantomData<(S, C, E)>, +/// +/// This is passed into each runtime as an opaque associated type that can have either of: +/// - [`SignAndSubmitTransaction`] +/// - [`SubmitUnsignedTransaction`] +/// - [`SubmitSignedTransaction`] +/// and used accordingly. +/// +/// This struct should be constructed by providing the following generic parameters: +/// * `Signer` - Usually the application specific key type (see `app_crypto`). +/// * `CreateTransaction` - A type that is able to produce signed transactions, +/// usually it's going to be the entire `Runtime` object. +/// * `Extrinsic` - A runtime-specific type for in-block extrinsics. +/// +/// If you only need the ability to submit unsigned transactions, +/// you may substitute both `Signer` and `CreateTransaction` with any type. +pub struct TransactionSubmitter { + _signer: sp_std::marker::PhantomData<(Signer, CreateTransaction, Extrinsic)>, } impl Default for TransactionSubmitter { @@ -144,7 +286,7 @@ impl Default for TransactionSubmitter { } /// A blanket implementation to simplify creation of transaction signer & submitter in the runtime. -impl SubmitSignedTransaction for TransactionSubmitter where +impl SignAndSubmitTransaction for TransactionSubmitter where T: crate::Trait, C: CreateTransaction, S: Signer<>::Public, >::Signature>, @@ -155,10 +297,68 @@ impl SubmitSignedTransaction for TransactionSubmitter type Signer = S; } -/// A blanket impl to use the same submitter for usigned transactions as well. +/// A blanket implementation to use the same submitter for unsigned transactions as well. impl SubmitUnsignedTransaction for TransactionSubmitter where T: crate::Trait, E: ExtrinsicT + codec::Encode, { type Extrinsic = E; } + +/// A blanket implementation to support local keystore of application-crypto types. +impl SubmitSignedTransaction for TransactionSubmitter where + T: crate::Trait, + C: CreateTransaction, + E: ExtrinsicT + codec::Encode, + S: Signer<>::Public, >::Signature>, + // Make sure we can unwrap the app crypto key. + S: RuntimeAppPublic + AppPublic + Into<::Generic>, + // Make sure we can convert from wrapped crypto to public key (e.g. `MultiSigner`) + S::Generic: Into>, + // For simplicity we require the same trait to implement `SignAndSubmitTransaction` too. + Self: SignAndSubmitTransaction, +{ + type SignAndSubmit = Self; + + fn find_local_keys(accounts: Option>) -> Vec<( + T::AccountId, + PublicOf, + )> { + // Convert app-specific keys into generic ones. + let local_accounts_and_keys = S::all() + .into_iter() + .map(|app_key| { + // unwrap app-crypto + let generic_pub_key: ::Generic = app_key.into(); + // convert to expected public key type (might be MultiSigner) + let signer_pub_key: PublicOf = generic_pub_key.into(); + // lookup accountid for that pubkey + let account = signer_pub_key.clone().into_account(); + (account, signer_pub_key) + }).collect::>(); + + if let Some(accounts) = accounts { + let mut local_accounts_and_keys = local_accounts_and_keys; + // sort by accountId to allow bin-search. + local_accounts_and_keys.sort_by(|a, b| a.0.cmp(&b.0)); + + // get all the matching accounts + accounts.into_iter().filter_map(|acc| { + let idx = local_accounts_and_keys.binary_search_by(|a| a.0.cmp(&acc)).ok()?; + local_accounts_and_keys.get(idx).cloned() + }).collect() + } else { + // just return all account ids and keys + local_accounts_and_keys + } + } + + fn can_sign_with(accounts: Option>) -> bool { + // early exit if we care about any account. + if accounts.is_none() { + !S::all().is_empty() + } else { + !Self::find_local_keys(accounts).is_empty() + } + } +} diff --git a/primitives/core/src/offchain/mod.rs b/primitives/core/src/offchain/mod.rs index 59a8e74ad23ed..e2e00c36e0769 100644 --- a/primitives/core/src/offchain/mod.rs +++ b/primitives/core/src/offchain/mod.rs @@ -174,6 +174,7 @@ impl TryFrom for HttpRequestStatus { /// A blob to hold information about the local node's network state /// without committing to its format. #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, PassByCodec)] +#[cfg_attr(feature = "std", derive(Default))] pub struct OpaqueNetworkState { /// PeerId of the local node. pub peer_id: OpaquePeerId, diff --git a/primitives/offchain/src/lib.rs b/primitives/offchain/src/lib.rs index d975e99a788bc..1dd20db0dd846 100644 --- a/primitives/offchain/src/lib.rs +++ b/primitives/offchain/src/lib.rs @@ -26,9 +26,15 @@ pub const STORAGE_PREFIX: &[u8] = b"storage"; sp_api::decl_runtime_apis! { /// The offchain worker api. + #[api_version(2)] pub trait OffchainWorkerApi { /// Starts the off-chain task for given block number. #[skip_initialize_block] + #[changed_in(2)] fn offchain_worker(number: NumberFor); + + /// Starts the off-chain task for given block header. + #[skip_initialize_block] + fn offchain_worker(header: &Block::Header); } } diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs index d00f4c4380793..8babfa47ffbb7 100644 --- a/primitives/runtime/src/traits.rs +++ b/primitives/runtime/src/traits.rs @@ -23,7 +23,7 @@ use sp_io; use std::fmt::Display; #[cfg(feature = "std")] use serde::{Serialize, Deserialize, de::DeserializeOwned}; -use sp_core::{self, Hasher, Blake2Hasher, TypeId}; +use sp_core::{self, Hasher, Blake2Hasher, TypeId, RuntimeDebug}; use crate::codec::{Codec, Encode, Decode}; use crate::transaction_validity::{ ValidTransaction, TransactionValidity, TransactionValidityError, UnknownTransaction, @@ -157,7 +157,7 @@ pub trait EnsureOrigin { } /// An error that indicates that a lookup failed. -#[derive(Encode, Decode)] +#[derive(Encode, Decode, RuntimeDebug)] pub struct LookupError; impl From for &'static str { @@ -391,7 +391,7 @@ pub trait Hash: 'static + MaybeSerializeDeserialize + Debug + Clone + Eq + Parti } /// Blake2-256 Hash implementation. -#[derive(PartialEq, Eq, Clone, sp_core::RuntimeDebug)] +#[derive(PartialEq, Eq, Clone, RuntimeDebug)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct BlakeTwo256; diff --git a/primitives/transaction-pool/src/pool.rs b/primitives/transaction-pool/src/pool.rs index 6e68574dde75e..e67a9890755d7 100644 --- a/primitives/transaction-pool/src/pool.rs +++ b/primitives/transaction-pool/src/pool.rs @@ -163,6 +163,8 @@ pub trait TransactionPool: Send + Sync { /// Error type. type Error: From + crate::error::IntoPoolError; + // Networking + /// Returns a future that imports a bunch of unverified transactions to the pool. fn submit_at( &self, @@ -183,6 +185,8 @@ pub trait TransactionPool: Send + Sync { Self::Error >> + Send + Unpin>; + // RPC + /// Returns a future that import a single transaction and starts to watch their progress in the pool. fn submit_and_watch( &self, @@ -190,23 +194,35 @@ pub trait TransactionPool: Send + Sync { xt: TransactionFor, ) -> Box>, Self::Error>> + Send + Unpin>; + + // Block production / Networking + + /// Get an iterator for ready transactions ordered by priority + fn ready(&self) -> Box>>; + + + // Block production + /// Remove transactions identified by given hashes (and dependent transactions) from the pool. fn remove_invalid(&self, hashes: &[TxHash]) -> Vec>; + // logging + /// Returns pool status. fn status(&self) -> PoolStatus; - /// Get an iterator for ready transactions ordered by priority - fn ready(&self) -> Box>>; + // logging / RPC / networking /// Return an event stream of transactions imported to the pool. fn import_notification_stream(&self) -> ImportNotificationStream; - /// Returns transaction hash - fn hash_of(&self, xt: &TransactionFor) -> TxHash; + // networking /// Notify the pool about transactions broadcast. fn on_broadcasted(&self, propagations: HashMap, Vec>); + + /// Returns transaction hash + fn hash_of(&self, xt: &TransactionFor) -> TxHash; } /// An abstraction for transaction pool. diff --git a/test-utils/runtime/src/lib.rs b/test-utils/runtime/src/lib.rs index cf1d76f390834..3fa2fc62461c6 100644 --- a/test-utils/runtime/src/lib.rs +++ b/test-utils/runtime/src/lib.rs @@ -623,8 +623,8 @@ cfg_if! { } impl sp_offchain::OffchainWorkerApi for Runtime { - fn offchain_worker(block: u64) { - let ex = Extrinsic::IncludeData(block.encode()); + fn offchain_worker(header: &::Header) { + let ex = Extrinsic::IncludeData(header.number.encode()); sp_io::offchain::submit_transaction(ex.encode()).unwrap(); } } @@ -839,8 +839,8 @@ cfg_if! { } impl sp_offchain::OffchainWorkerApi for Runtime { - fn offchain_worker(block: u64) { - let ex = Extrinsic::IncludeData(block.encode()); + fn offchain_worker(header: &::Header) { + let ex = Extrinsic::IncludeData(header.number.encode()); sp_io::offchain::submit_transaction(ex.encode()).unwrap() } }