feat: initialize Kurdistan SDK - independent fork of Polkadot SDK

This commit is contained in:
2025-12-13 15:44:15 +03:00
commit e4778b4576
6838 changed files with 1847450 additions and 0 deletions
+892
View File
@@ -0,0 +1,892 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use codec::{Decode, Encode, Joiner};
use frame_support::{
dispatch::{DispatchClass, GetDispatchInfo},
traits::Currency,
weights::Weight,
};
use frame_system::{self, AccountInfo, DispatchEventInfo, EventRecord, Phase};
use pezkuwi_sdk::*;
use sp_core::{storage::well_known_keys, traits::Externalities};
use sp_runtime::{
traits::Hash as HashT, transaction_validity::InvalidTransaction, ApplyExtrinsicResult,
};
use kitchensink_runtime::{
constants::{currency::*, time::SLOT_DURATION},
Balances, CheckedExtrinsic, Header, Runtime, RuntimeCall, RuntimeEvent, System,
TransactionPayment, Treasury, UncheckedExtrinsic,
};
use node_primitives::{Balance, Hash};
use node_testing::keyring::*;
use pretty_assertions::assert_eq;
use wat;
pub 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 fn bloaty_code_unwrap() -> &'static [u8] {
kitchensink_runtime::WASM_BINARY_BLOATY.expect(
"Development wasm binary is not available. \
Testing is only supported with the flag disabled.",
)
}
/// Default transfer fee. This will use the same logic that is implemented in transaction-payment
/// module.
///
/// Note that reads the multiplier from storage directly, hence to get the fee of `extrinsic`
/// at block `n`, it must be called prior to executing block `n` to do the calculation with the
/// correct multiplier.
fn transfer_fee(extrinsic: &UncheckedExtrinsic) -> Balance {
let mut info = default_transfer_call().get_dispatch_info();
info.extension_weight = extrinsic.0.extension_weight();
TransactionPayment::compute_fee(extrinsic.encode().len() as u32, &info, 0)
}
/// Default transfer fee, same as `transfer_fee`, but with a weight refund factored in.
fn transfer_fee_with_refund(extrinsic: &UncheckedExtrinsic, weight_refund: Weight) -> Balance {
let mut info = default_transfer_call().get_dispatch_info();
info.extension_weight = extrinsic.0.extension_weight();
let post_info = (Some(info.total_weight().saturating_sub(weight_refund)), info.pays_fee).into();
TransactionPayment::compute_actual_fee(extrinsic.encode().len() as u32, &info, &post_info, 0)
}
fn xt() -> UncheckedExtrinsic {
sign(CheckedExtrinsic {
format: sp_runtime::generic::ExtrinsicFormat::Signed(alice(), tx_ext(0, 0)),
function: RuntimeCall::Balances(default_transfer_call()),
})
}
fn set_heap_pages<E: Externalities>(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<u8>, Hash) {
let time = 42 * 1000;
construct_block(
&mut new_test_ext(compact_code_unwrap()),
1,
GENESIS_HASH.into(),
vec![
CheckedExtrinsic {
format: sp_runtime::generic::ExtrinsicFormat::Bare,
function: RuntimeCall::Timestamp(pallet_timestamp::Call::set { now: time }),
},
CheckedExtrinsic {
format: sp_runtime::generic::ExtrinsicFormat::Signed(alice(), tx_ext(0, 0)),
function: RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death {
dest: bob().into(),
value: 69 * DOLLARS,
}),
},
],
(time / SLOT_DURATION).into(),
)
}
/// 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<u8>, Hash), (Vec<u8>, Hash)) {
let mut t = new_test_ext(compact_code_unwrap());
let time1 = 42 * 1000;
let block1 = construct_block(
&mut t,
1,
GENESIS_HASH.into(),
vec![
CheckedExtrinsic {
format: sp_runtime::generic::ExtrinsicFormat::Bare,
function: RuntimeCall::Timestamp(pallet_timestamp::Call::set { now: time1 }),
},
CheckedExtrinsic {
format: sp_runtime::generic::ExtrinsicFormat::Signed(alice(), tx_ext(0, 0)),
function: RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death {
dest: bob().into(),
value: 69 * DOLLARS,
}),
},
],
(time1 / SLOT_DURATION).into(),
);
let time2 = 52 * 1000;
let block2 = construct_block(
&mut t,
2,
block1.1,
vec![
CheckedExtrinsic {
format: sp_runtime::generic::ExtrinsicFormat::Bare,
function: RuntimeCall::Timestamp(pallet_timestamp::Call::set { now: time2 }),
},
CheckedExtrinsic {
format: sp_runtime::generic::ExtrinsicFormat::Signed(bob(), tx_ext(0, 0)),
function: RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death {
dest: alice().into(),
value: 5 * DOLLARS,
}),
},
CheckedExtrinsic {
format: sp_runtime::generic::ExtrinsicFormat::Signed(alice(), tx_ext(1, 0)),
function: RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death {
dest: bob().into(),
value: 15 * DOLLARS,
}),
},
],
(time2 / SLOT_DURATION).into(),
);
// session change => consensus authorities change => authorities change digest item appears
let digest = Header::decode(&mut &block2.0[..]).unwrap().digest;
assert_eq!(digest.logs().len(), 2 /* Just babe and BEEFY slots */);
(block1, block2)
}
fn block_with_size(time: u64, nonce: u32, size: usize) -> (Vec<u8>, Hash) {
construct_block(
&mut new_test_ext(compact_code_unwrap()),
1,
GENESIS_HASH.into(),
vec![
CheckedExtrinsic {
format: sp_runtime::generic::ExtrinsicFormat::Bare,
function: RuntimeCall::Timestamp(pallet_timestamp::Call::set { now: time * 1000 }),
},
CheckedExtrinsic {
format: sp_runtime::generic::ExtrinsicFormat::Signed(alice(), tx_ext(nonce, 0)),
function: RuntimeCall::System(frame_system::Call::remark { remark: vec![0; size] }),
},
],
(time * 1000 / SLOT_DURATION).into(),
)
}
#[test]
fn panic_execution_with_foreign_code_gives_error() {
let mut t = new_test_ext(bloaty_code_unwrap());
t.insert(
<frame_system::Account<Runtime>>::hashed_key_for(alice()),
AccountInfo::<<Runtime as frame_system::Config>::Nonce, _> {
providers: 1,
data: (69u128, 0u128, 0u128, 1u128 << 127),
..Default::default()
}
.encode(),
);
t.insert(<pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec(), 69_u128.encode());
t.insert(<frame_system::BlockHash<Runtime>>::hashed_key_for(0), vec![0u8; 32]);
let r = executor_call(&mut t, "Core_initialize_block", &vec![].and(&from_block_number(1u32))).0;
assert!(r.is_ok());
let v = executor_call(&mut t, "BlockBuilder_apply_extrinsic", &vec![].and(&xt()))
.0
.unwrap();
let r = ApplyExtrinsicResult::decode(&mut &v[..]).unwrap();
assert_eq!(r, Err(InvalidTransaction::Payment.into()));
}
#[test]
fn bad_extrinsic_with_native_equivalent_code_gives_error() {
let mut t = new_test_ext(compact_code_unwrap());
t.insert(
<frame_system::Account<Runtime>>::hashed_key_for(alice()),
AccountInfo::<<Runtime as frame_system::Config>::Nonce, _> {
providers: 1,
data: (69u128, 0u128, 0u128, 1u128 << 127),
..Default::default()
}
.encode(),
);
t.insert(<pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec(), 69u128.encode());
t.insert(<frame_system::BlockHash<Runtime>>::hashed_key_for(0), vec![0u8; 32]);
let r = executor_call(&mut t, "Core_initialize_block", &vec![].and(&from_block_number(1u32))).0;
assert!(r.is_ok());
let v = executor_call(&mut t, "BlockBuilder_apply_extrinsic", &vec![].and(&xt()))
.0
.unwrap();
let r = ApplyExtrinsicResult::decode(&mut &v[..]).unwrap();
assert_eq!(r, Err(InvalidTransaction::Payment.into()));
}
#[test]
fn successful_execution_with_native_equivalent_code_gives_ok() {
let mut t = new_test_ext(compact_code_unwrap());
t.insert(
<frame_system::Account<Runtime>>::hashed_key_for(alice()),
AccountInfo::<<Runtime as frame_system::Config>::Nonce, _> {
providers: 1,
data: (111 * DOLLARS, 0u128, 0u128, 1u128 << 127),
..Default::default()
}
.encode(),
);
t.insert(
<frame_system::Account<Runtime>>::hashed_key_for(bob()),
AccountInfo::<
<Runtime as frame_system::Config>::Nonce,
<Runtime as frame_system::Config>::AccountData,
>::default()
.encode(),
);
t.insert(
<pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec(),
(111 * DOLLARS).encode(),
);
t.insert(<frame_system::BlockHash<Runtime>>::hashed_key_for(0), vec![0u8; 32]);
let r = executor_call(&mut t, "Core_initialize_block", &vec![].and(&from_block_number(1u32))).0;
assert!(r.is_ok());
let weight_refund = Weight::zero();
let fees_after_refund = t.execute_with(|| transfer_fee_with_refund(&xt(), weight_refund));
let r = executor_call(&mut t, "BlockBuilder_apply_extrinsic", &vec![].and(&xt())).0;
assert!(r.is_ok());
t.execute_with(|| {
assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - fees_after_refund);
assert_eq!(Balances::total_balance(&bob()), 69 * DOLLARS);
});
}
#[test]
fn successful_execution_with_foreign_code_gives_ok() {
let mut t = new_test_ext(bloaty_code_unwrap());
t.insert(
<frame_system::Account<Runtime>>::hashed_key_for(alice()),
AccountInfo::<<Runtime as frame_system::Config>::Nonce, _> {
providers: 1,
data: (111 * DOLLARS, 0u128, 0u128, 1u128 << 127),
..Default::default()
}
.encode(),
);
t.insert(
<frame_system::Account<Runtime>>::hashed_key_for(bob()),
AccountInfo::<
<Runtime as frame_system::Config>::Nonce,
<Runtime as frame_system::Config>::AccountData,
>::default()
.encode(),
);
t.insert(
<pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec(),
(111 * DOLLARS).encode(),
);
t.insert(<frame_system::BlockHash<Runtime>>::hashed_key_for(0), vec![0u8; 32]);
let r = executor_call(&mut t, "Core_initialize_block", &vec![].and(&from_block_number(1u32))).0;
assert!(r.is_ok());
let weight_refund = Weight::zero();
let fees_after_refund = t.execute_with(|| transfer_fee_with_refund(&xt(), weight_refund));
let r = executor_call(&mut t, "BlockBuilder_apply_extrinsic", &vec![].and(&xt())).0;
assert!(r.is_ok());
t.execute_with(|| {
assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - fees_after_refund);
assert_eq!(Balances::total_balance(&bob()), 69 * DOLLARS);
});
}
#[test]
fn full_native_block_import_works() {
let mut t = new_test_ext(compact_code_unwrap());
let (block1, block2) = blocks();
let mut alice_last_known_balance: Balance = Default::default();
let mut fees = t.execute_with(|| transfer_fee(&xt()));
let extension_weight = xt().0.extension_weight();
let weight_refund = Weight::zero();
let fees_after_refund = t.execute_with(|| transfer_fee_with_refund(&xt(), weight_refund));
let transfer_weight = default_transfer_call().get_dispatch_info().call_weight.saturating_add(
<Runtime as frame_system::Config>::BlockWeights::get()
.get(DispatchClass::Normal)
.base_extrinsic,
);
let timestamp_weight = pallet_timestamp::Call::set::<Runtime> { now: Default::default() }
.get_dispatch_info()
.call_weight
.saturating_add(
<Runtime as frame_system::Config>::BlockWeights::get()
.get(DispatchClass::Mandatory)
.base_extrinsic,
);
executor_call(&mut t, "Core_execute_block", &block1.0).0.unwrap();
t.execute_with(|| {
assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - fees_after_refund);
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: RuntimeEvent::System(frame_system::Event::ExtrinsicSuccess {
dispatch_info: DispatchEventInfo {
weight: timestamp_weight,
class: DispatchClass::Mandatory,
pays_fee: Default::default(),
},
}),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(1),
event: RuntimeEvent::Balances(pallet_balances::Event::Withdraw {
who: alice().into(),
amount: fees,
}),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(1),
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
from: alice().into(),
to: bob().into(),
amount: 69 * DOLLARS,
}),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(1),
event: RuntimeEvent::Balances(pallet_balances::Event::Deposit {
who: pallet_treasury::Pallet::<Runtime>::account_id(),
amount: fees_after_refund,
}),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(1),
event: RuntimeEvent::TransactionPayment(
pallet_transaction_payment::Event::TransactionFeePaid {
who: alice().into(),
actual_fee: fees_after_refund,
tip: 0,
},
),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(1),
event: RuntimeEvent::System(frame_system::Event::ExtrinsicSuccess {
dispatch_info: DispatchEventInfo {
weight: transfer_weight
.saturating_add(extension_weight.saturating_sub(weight_refund)),
..Default::default()
},
}),
topics: vec![],
},
];
let filtered_events: Vec<_> = System::events()
.into_iter()
.filter(|ev| {
!matches!(
ev.event,
RuntimeEvent::VoterList(
pallet_bags_list::Event::<Runtime, _>::ScoreUpdated { .. }
)
)
})
.collect();
assert_eq!(filtered_events, events);
});
fees = t.execute_with(|| transfer_fee(&xt()));
let pot = t.execute_with(|| Treasury::pot());
let extension_weight = xt().0.extension_weight();
let weight_refund = Weight::zero();
let fees_after_refund = t.execute_with(|| transfer_fee_with_refund(&xt(), weight_refund));
executor_call(&mut t, "Core_execute_block", &block2.0).0.unwrap();
t.execute_with(|| {
assert_eq!(
Balances::total_balance(&alice()),
alice_last_known_balance - 10 * DOLLARS - fees_after_refund,
);
assert_eq!(Balances::total_balance(&bob()), 179 * DOLLARS - fees_after_refund);
let events = vec![
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Treasury(pallet_treasury::Event::UpdatedInactive {
reactivated: 0,
deactivated: pot,
}),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(0),
event: RuntimeEvent::System(frame_system::Event::ExtrinsicSuccess {
dispatch_info: DispatchEventInfo {
weight: timestamp_weight,
class: DispatchClass::Mandatory,
pays_fee: Default::default(),
},
}),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(1),
event: RuntimeEvent::Balances(pallet_balances::Event::Withdraw {
who: bob().into(),
amount: fees,
}),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(1),
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
from: bob().into(),
to: alice().into(),
amount: 5 * DOLLARS,
}),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(1),
event: RuntimeEvent::Balances(pallet_balances::Event::Deposit {
who: pallet_treasury::Pallet::<Runtime>::account_id(),
amount: fees_after_refund,
}),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(1),
event: RuntimeEvent::TransactionPayment(
pallet_transaction_payment::Event::TransactionFeePaid {
who: bob().into(),
actual_fee: fees_after_refund,
tip: 0,
},
),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(1),
event: RuntimeEvent::System(frame_system::Event::ExtrinsicSuccess {
dispatch_info: DispatchEventInfo {
weight: transfer_weight
.saturating_add(extension_weight.saturating_sub(weight_refund)),
..Default::default()
},
}),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(2),
event: RuntimeEvent::Balances(pallet_balances::Event::Withdraw {
who: alice().into(),
amount: fees,
}),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(2),
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
from: alice().into(),
to: bob().into(),
amount: 15 * DOLLARS,
}),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(2),
event: RuntimeEvent::Balances(pallet_balances::Event::Deposit {
who: pallet_treasury::Pallet::<Runtime>::account_id(),
amount: fees_after_refund,
}),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(2),
event: RuntimeEvent::TransactionPayment(
pallet_transaction_payment::Event::TransactionFeePaid {
who: alice().into(),
actual_fee: fees_after_refund,
tip: 0,
},
),
topics: vec![],
},
EventRecord {
phase: Phase::ApplyExtrinsic(2),
event: RuntimeEvent::System(frame_system::Event::ExtrinsicSuccess {
dispatch_info: DispatchEventInfo {
weight: transfer_weight
.saturating_add(extension_weight.saturating_sub(weight_refund)),
..Default::default()
},
}),
topics: vec![],
},
];
let all_events = System::events();
// Ensure that all expected events (`events`) are present in the full event log
// (`all_events`). We use this instead of strict equality since some events (like
// VoterList::ScoreUpdated) may be emitted non-deterministically depending on runtime
// internals or auto-rebagging logic.
for expected_event in &events {
assert!(
all_events.contains(expected_event),
"Expected event {:?} not found in actual events",
expected_event
);
}
});
}
#[test]
fn full_wasm_block_import_works() {
let mut t = new_test_ext(compact_code_unwrap());
let (block1, block2) = blocks();
let mut alice_last_known_balance: Balance = Default::default();
let weight_refund = Weight::zero();
let fees_after_refund = t.execute_with(|| transfer_fee_with_refund(&xt(), weight_refund));
executor_call(&mut t, "Core_execute_block", &block1.0).0.unwrap();
t.execute_with(|| {
assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - fees_after_refund);
assert_eq!(Balances::total_balance(&bob()), 169 * DOLLARS);
alice_last_known_balance = Balances::total_balance(&alice());
});
let weight_refund = Weight::zero();
let fees_after_refund = t.execute_with(|| transfer_fee_with_refund(&xt(), weight_refund));
executor_call(&mut t, "Core_execute_block", &block2.0).0.unwrap();
t.execute_with(|| {
assert_eq!(
Balances::total_balance(&alice()),
alice_last_known_balance - 10 * DOLLARS - fees_after_refund,
);
assert_eq!(Balances::total_balance(&bob()), 179 * DOLLARS - 1 * fees_after_refund);
});
}
const CODE_TRANSFER: &str = r#"
(module
;; seal_call(
;; callee_ptr: u32,
;; callee_len: u32,
;; gas: u64,
;; value_ptr: u32,
;; value_len: u32,
;; input_data_ptr: u32,
;; input_data_len: u32,
;; output_ptr: u32,
;; output_len_ptr: u32
;; ) -> u32
(import "seal0" "seal_call" (func $seal_call (param i32 i32 i64 i32 i32 i32 i32 i32 i32) (result i32)))
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
(import "env" "memory" (memory 1 1))
(func (export "deploy")
)
(func (export "call")
(block $fail
;; Load input data to contract memory
(call $seal_input
(i32.const 0)
(i32.const 52)
)
;; fail if the input size is not != 4
(br_if $fail
(i32.ne
(i32.const 4)
(i32.load (i32.const 52))
)
)
(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 $seal_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
(i32.const 4294967295) ;; u32 max value is the sentinel value: do not copy output
(i32.const 0) ;; Length is ignored in this case
)
)
(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"
)
;; Length of the input buffer
(data (i32.const 52) "\04")
)
"#;
#[test]
fn deploying_wasm_contract_should_work() {
let transfer_code = wat::parse_str(CODE_TRANSFER).unwrap();
let transfer_ch = <Runtime as frame_system::Config>::Hashing::hash(&transfer_code);
let addr =
pallet_contracts::Pallet::<Runtime>::contract_address(&charlie(), &transfer_ch, &[], &[]);
let time = 42 * 1000;
let b = construct_block(
&mut new_test_ext(compact_code_unwrap()),
1,
GENESIS_HASH.into(),
vec![
CheckedExtrinsic {
format: sp_runtime::generic::ExtrinsicFormat::Bare,
function: RuntimeCall::Timestamp(pallet_timestamp::Call::set { now: time }),
},
CheckedExtrinsic {
format: sp_runtime::generic::ExtrinsicFormat::Signed(charlie(), tx_ext(0, 0)),
function: RuntimeCall::Contracts(pallet_contracts::Call::instantiate_with_code::<
Runtime,
> {
value: 0,
gas_limit: Weight::from_parts(500_000_000, 0),
storage_deposit_limit: None,
code: transfer_code,
data: Vec::new(),
salt: Vec::new(),
}),
},
CheckedExtrinsic {
format: sp_runtime::generic::ExtrinsicFormat::Signed(charlie(), tx_ext(1, 0)),
function: RuntimeCall::Contracts(pallet_contracts::Call::call::<Runtime> {
dest: sp_runtime::MultiAddress::Id(addr.clone()),
value: 10,
gas_limit: Weight::from_parts(500_000_000, 0),
storage_deposit_limit: None,
data: vec![0x00, 0x01, 0x02, 0x03],
}),
},
],
(time / SLOT_DURATION).into(),
);
let mut t = new_test_ext(compact_code_unwrap());
executor_call(&mut t, "Core_execute_block", &b.0).0.unwrap();
t.execute_with(|| {
// Verify that the contract does exist by querying some of its storage items
// It does not matter that the storage item itself does not exist.
assert!(&pallet_contracts::Pallet::<Runtime>::get_storage(addr, vec![]).is_ok());
});
}
#[test]
fn wasm_big_block_import_fails() {
let mut t = new_test_ext(compact_code_unwrap());
set_heap_pages(&mut t.ext(), 4);
let result = executor_call(&mut t, "Core_execute_block", &block_with_size(42, 0, 120_000).0).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_unwrap());
executor_call(&mut t, "Core_execute_block", &block_with_size(42, 0, 120_000).0)
.0
.unwrap();
}
#[test]
fn native_big_block_import_fails_on_fallback() {
let mut t = new_test_ext(compact_code_unwrap());
// We set the heap pages to 8 because we know that should give an OOM in WASM with the given
// block.
set_heap_pages(&mut t.ext(), 8);
assert!(executor_call(&mut t, "Core_execute_block", &block_with_size(42, 0, 120_000).0)
.0
.is_err());
}
#[test]
fn panic_execution_gives_error() {
let mut t = new_test_ext(bloaty_code_unwrap());
t.insert(
<frame_system::Account<Runtime>>::hashed_key_for(alice()),
AccountInfo::<<Runtime as frame_system::Config>::Nonce, _> {
data: (0 * DOLLARS, 0u128, 0u128, 0u128),
..Default::default()
}
.encode(),
);
t.insert(<pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec(), 0_u128.encode());
t.insert(<frame_system::BlockHash<Runtime>>::hashed_key_for(0), vec![0u8; 32]);
let r = executor_call(&mut t, "Core_initialize_block", &vec![].and(&from_block_number(1u32))).0;
assert!(r.is_ok());
let r = executor_call(&mut t, "BlockBuilder_apply_extrinsic", &vec![].and(&xt()))
.0
.unwrap();
let r = ApplyExtrinsicResult::decode(&mut &r[..]).unwrap();
assert_eq!(r, Err(InvalidTransaction::Payment.into()));
}
#[test]
fn successful_execution_gives_ok() {
let mut t = new_test_ext(compact_code_unwrap());
t.insert(
<frame_system::Account<Runtime>>::hashed_key_for(alice()),
AccountInfo::<<Runtime as frame_system::Config>::Nonce, _> {
providers: 1,
data: (111 * DOLLARS, 0u128, 0u128, 1u128 << 127),
..Default::default()
}
.encode(),
);
t.insert(
<frame_system::Account<Runtime>>::hashed_key_for(bob()),
AccountInfo::<
<Runtime as frame_system::Config>::Nonce,
<Runtime as frame_system::Config>::AccountData,
>::default()
.encode(),
);
t.insert(
<pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec(),
(111 * DOLLARS).encode(),
);
t.insert(<frame_system::BlockHash<Runtime>>::hashed_key_for(0), vec![0u8; 32]);
let r = executor_call(&mut t, "Core_initialize_block", &vec![].and(&from_block_number(1u32))).0;
assert!(r.is_ok());
t.execute_with(|| {
assert_eq!(Balances::total_balance(&alice()), 111 * DOLLARS);
});
let weight_refund = Weight::zero();
let fees_after_refund = t.execute_with(|| transfer_fee_with_refund(&xt(), weight_refund));
let r = executor_call(&mut t, "BlockBuilder_apply_extrinsic", &vec![].and(&xt()))
.0
.unwrap();
ApplyExtrinsicResult::decode(&mut &r[..])
.unwrap()
.expect("Extrinsic could not be applied")
.expect("Extrinsic failed");
t.execute_with(|| {
assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - fees_after_refund);
assert_eq!(Balances::total_balance(&bob()), 69 * DOLLARS);
});
}
#[test]
fn should_import_block_with_test_client() {
use node_testing::client::{
sp_consensus::BlockOrigin, ClientBlockImportExt, TestClientBuilder, TestClientBuilderExt,
};
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();
futures::executor::block_on(client.import(BlockOrigin::Own, block)).unwrap();
}
#[test]
fn default_config_as_json_works() {
let mut t = new_test_ext(compact_code_unwrap());
let r = executor_call(
&mut t,
"GenesisBuilder_get_preset",
&None::<&sp_genesis_builder::PresetId>.encode(),
)
.0
.unwrap();
let r = Option::<Vec<u8>>::decode(&mut &r[..])
.unwrap()
.expect("default config is there");
let json = String::from_utf8(r.into()).expect("returned value is json. qed.");
let expected = include_str!("res/default_genesis_config.json").to_string();
assert_eq!(
serde_json::from_str::<serde_json::Value>(&expected).unwrap(),
serde_json::from_str::<serde_json::Value>(&json).unwrap()
);
}
@@ -0,0 +1,47 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
// Unix only since it uses signals from [`common::run_node_for_a_while`].
#![cfg(unix)]
use assert_cmd::cargo::cargo_bin;
use std::process::Command;
use tempfile::tempdir;
use substrate_cli_test_utils as common;
/// `benchmark block` works for the dev runtime using the wasm executor.
#[tokio::test]
async fn benchmark_block_works() {
let base_dir = tempdir().expect("could not create a temp dir");
common::run_node_for_a_while(base_dir.path(), &["--dev", "--no-hardware-benchmarks"]).await;
// Invoke `benchmark block` with all options to make sure that they are valid.
let status = Command::new(cargo_bin("substrate-node"))
.args(["benchmark", "block", "--dev"])
.arg("-d")
.arg(base_dir.path())
.args(["--from", "1", "--to", "1"])
.args(["--repeat", "1"])
.args(["--wasm-execution=compiled"])
.status()
.unwrap();
assert!(status.success())
}
@@ -0,0 +1,47 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use assert_cmd::cargo::cargo_bin;
use std::process::Command;
use tempfile::tempdir;
/// Tests that the `benchmark extrinsic` command works for
/// remark and transfer_keep_alive within the substrate dev runtime.
#[test]
fn benchmark_extrinsic_works() {
benchmark_extrinsic("system", "remark");
benchmark_extrinsic("balances", "transfer_keep_alive");
}
/// Checks that the `benchmark extrinsic` command works for the given pallet and extrinsic.
fn benchmark_extrinsic(pallet: &str, extrinsic: &str) {
let base_dir = tempdir().expect("could not create a temp dir");
let status = Command::new(cargo_bin("substrate-node"))
.args(&["benchmark", "extrinsic", "--dev"])
.arg("-d")
.arg(base_dir.path())
.args(&["--pallet", pallet, "--extrinsic", extrinsic])
// Run with low repeats for faster execution.
.args(["--warmup=10", "--repeat=10", "--max-ext-per-block=10"])
.args(["--wasm-execution=compiled"])
.status()
.unwrap();
assert!(status.success());
}
@@ -0,0 +1,73 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use assert_cmd::cargo::cargo_bin;
use std::process::Command;
/// Tests that the `benchmark machine` command works for the substrate dev runtime.
#[test]
fn benchmark_machine_works() {
let status = Command::new(cargo_bin("substrate-node"))
.args(["benchmark", "machine", "--dev"])
.args([
"--verify-duration",
"0.1",
"--disk-duration",
"0.1",
"--memory-duration",
"0.1",
"--hash-duration",
"0.1",
])
// Make it succeed.
.args(["--allow-fail"])
.status()
.unwrap();
assert!(status.success());
}
/// Test that the hardware does not meet the requirements.
///
/// This is most likely to succeed since it uses a test profile.
#[test]
#[cfg(debug_assertions)]
fn benchmark_machine_fails_with_slow_hardware() {
let output = Command::new(cargo_bin("substrate-node"))
.args(["benchmark", "machine", "--dev"])
.args([
"--verify-duration",
"1.0",
"--disk-duration",
"2",
"--hash-duration",
"1.0",
"--memory-duration",
"1.0",
"--tolerance",
"0",
])
.output()
.unwrap();
// Command should have failed.
assert!(!output.status.success());
// An `UnmetRequirement` error should have been printed.
let log = String::from_utf8_lossy(&output.stderr).to_string();
assert!(log.contains("UnmetRequirement"));
}
@@ -0,0 +1,47 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use assert_cmd::cargo::cargo_bin;
use std::process::Command;
use tempfile::tempdir;
/// Tests that the `benchmark overhead` command works for the substrate dev runtime.
#[test]
fn benchmark_overhead_works() {
let tmp_dir = tempdir().expect("could not create a temp dir");
let base_path = tmp_dir.path();
// Only put 10 extrinsics into the block otherwise it takes forever to build it
// especially for a non-release build.
let status = Command::new(cargo_bin("substrate-node"))
.args(&["benchmark", "overhead", "--dev", "-d"])
.arg(base_path)
.arg("--weight-path")
.arg(base_path)
.args(["--warmup", "10", "--repeat", "10"])
.args(["--add", "100", "--mul", "1.2", "--metric", "p75"])
.args(["--max-ext-per-block", "10"])
.args(["--wasm-execution=compiled"])
.status()
.unwrap();
assert!(status.success());
// Weight files have been created.
assert!(base_path.join("block_weights.rs").exists());
assert!(base_path.join("extrinsic_weights.rs").exists());
}
@@ -0,0 +1,86 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
#![cfg(feature = "runtime-benchmarks")]
use assert_cmd::cargo::cargo_bin;
use std::process::Command;
/// `benchmark pallet` works for the different combinations of `steps` and `repeat`.
#[test]
fn benchmark_pallet_works() {
// Some invalid combinations:
benchmark_pallet(0, 10, false);
benchmark_pallet(1, 10, false);
// ... and some valid:
benchmark_pallet(2, 1, true);
benchmark_pallet(50, 20, true);
benchmark_pallet(20, 50, true);
}
#[test]
fn benchmark_pallet_args_work() {
benchmark_pallet_args(&["--list", "--pallet=pallet_balances"], true);
benchmark_pallet_args(&["--list", "--pallet=pallet_balances"], true);
benchmark_pallet_args(
&["--list", "--pallet=pallet_balances", "--genesis-builder=spec-genesis"],
true,
);
benchmark_pallet_args(
&["--list", "--pallet=pallet_balances", "--chain=dev", "--genesis-builder=spec-genesis"],
true,
);
benchmark_pallet_args(
&["--list", "--pallet=pallet_balances", "--chain=dev", "--genesis-builder=spec-runtime"],
true,
);
// Error because no runtime is provided:
benchmark_pallet_args(
&["--list", "--pallet=pallet_balances", "--chain=dev", "--genesis-builder=runtime"],
false,
);
}
fn benchmark_pallet(steps: u32, repeat: u32, should_work: bool) {
let status = Command::new(cargo_bin("substrate-node"))
.args(["benchmark", "pallet", "--dev"])
// Use the `addition` benchmark since is the fastest.
.args(["--pallet", "frame-benchmarking", "--extrinsic", "addition"])
.args(["--steps", &format!("{}", steps), "--repeat", &format!("{}", repeat)])
.args([
"--wasm-execution=compiled",
"--no-storage-info",
"--no-median-slopes",
"--no-min-squares",
"--heap-pages=4096",
])
.status()
.unwrap();
assert_eq!(status.success(), should_work);
}
fn benchmark_pallet_args(args: &[&str], should_work: bool) {
let status = Command::new(cargo_bin("substrate-node"))
.args(["benchmark", "pallet"])
.args(args)
.status()
.unwrap();
assert_eq!(status.success(), should_work);
}
@@ -0,0 +1,56 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
#![cfg(feature = "runtime-benchmarks")]
use assert_cmd::cargo::cargo_bin;
use std::{
path::Path,
process::{Command, ExitStatus},
};
use tempfile::tempdir;
/// Tests that the `benchmark storage` command works for the dev runtime.
#[test]
fn benchmark_storage_works() {
let tmp_dir = tempdir().expect("could not create a temp dir");
let base_path = tmp_dir.path();
// Benchmarking the storage works and creates the correct weight file.
assert!(benchmark_storage("rocksdb", base_path).success());
assert!(base_path.join("rocksdb_weights.rs").exists());
assert!(benchmark_storage("paritydb", base_path).success());
assert!(base_path.join("paritydb_weights.rs").exists());
}
fn benchmark_storage(db: &str, base_path: &Path) -> ExitStatus {
Command::new(cargo_bin("substrate-node"))
.args(&["benchmark", "storage", "--dev"])
.arg("--db")
.arg(db)
.arg("--weight-path")
.arg(base_path)
.args(["--state-version", "1"])
.args(["--batch-size", "1"])
.args(["--warmups", "0"])
.args(["--add", "100", "--mul", "1.2", "--metric", "p75"])
.arg("--include-child-trees")
.status()
.unwrap()
}
@@ -0,0 +1,39 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use assert_cmd::cargo::cargo_bin;
use std::process::Command;
use tempfile::tempdir;
#[test]
fn build_spec_works() {
let base_path = tempdir().expect("could not create a temp dir");
let output = Command::new(cargo_bin("substrate-node"))
.args(&["build-spec", "--dev", "-d"])
.arg(base_path.path())
.output()
.unwrap();
assert!(output.status.success());
// Make sure that the `dev` chain folder exists, but the `db` doesn't
assert!(base_path.path().join("chains/dev/").exists());
assert!(!base_path.path().join("chains/dev/db").exists());
let _value: serde_json::Value = serde_json::from_slice(output.stdout.as_slice()).unwrap();
}
@@ -0,0 +1,40 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
#![cfg(unix)]
use assert_cmd::cargo::cargo_bin;
use std::process::Command;
use tempfile::tempdir;
use substrate_cli_test_utils as common;
#[tokio::test]
async fn check_block_works() {
let base_path = tempdir().expect("could not create a temp dir");
common::run_node_for_a_while(base_path.path(), &["--dev", "--no-hardware-benchmarks"]).await;
let status = Command::new(cargo_bin("substrate-node"))
.args(&["check-block", "--dev", "-d"])
.arg(base_path.path())
.arg("1")
.status()
.unwrap();
assert!(status.success());
}
+197
View File
@@ -0,0 +1,197 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use codec::{Decode, Encode};
use frame_support::Hashable;
use frame_system::offchain::AppCrypto;
use pezkuwi_sdk::*;
use sc_executor::error::Result;
use sp_consensus_babe::{
digests::{PreDigest, SecondaryPlainPreDigest},
Slot, BABE_ENGINE_ID,
};
use sp_core::{
crypto::KeyTypeId,
sr25519::Signature,
traits::{CallContext, CodeExecutor, RuntimeCode},
};
use sp_runtime::{
traits::{BlakeTwo256, Header as HeaderT},
ApplyExtrinsicResult, Digest, DigestItem, MultiSignature, MultiSigner,
};
use sp_state_machine::TestExternalities as CoreTestExternalities;
use kitchensink_runtime::{
constants::currency::*, Block, BuildStorage, CheckedExtrinsic, Header, Runtime,
UncheckedExtrinsic,
};
use node_primitives::{BlockNumber, Hash};
use node_testing::keyring::*;
use sp_externalities::Externalities;
use staging_node_cli::service::RuntimeExecutor;
pub const TEST_KEY_TYPE_ID: KeyTypeId = KeyTypeId(*b"test");
pub mod sr25519 {
mod app_sr25519 {
use super::super::TEST_KEY_TYPE_ID;
use pezkuwi_sdk::sp_application_crypto::{app_crypto, sr25519};
app_crypto!(sr25519, TEST_KEY_TYPE_ID);
}
pub type AuthorityId = app_sr25519::Public;
}
pub struct TestAuthorityId;
impl AppCrypto<MultiSigner, MultiSignature> for TestAuthorityId {
type RuntimeAppPublic = sr25519::AuthorityId;
type GenericSignature = Signature;
type GenericPublic = sp_core::sr25519::Public;
}
/// 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.
pub fn compact_code_unwrap() -> &'static [u8] {
kitchensink_runtime::WASM_BINARY.expect(
"Development wasm binary is not available. Testing is only supported with the flag \
disabled.",
)
}
pub const GENESIS_HASH: [u8; 32] = [69u8; 32];
pub const SPEC_VERSION: u32 = kitchensink_runtime::VERSION.spec_version;
pub const TRANSACTION_VERSION: u32 = kitchensink_runtime::VERSION.transaction_version;
pub type TestExternalities<H> = CoreTestExternalities<H>;
pub fn sign(xt: CheckedExtrinsic) -> UncheckedExtrinsic {
node_testing::keyring::sign(xt, SPEC_VERSION, TRANSACTION_VERSION, GENESIS_HASH, None)
}
pub fn default_transfer_call() -> pallet_balances::Call<Runtime> {
pallet_balances::Call::<Runtime>::transfer_allow_death {
dest: bob().into(),
value: 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() -> RuntimeExecutor {
RuntimeExecutor::builder().build()
}
pub fn executor_call(
t: &mut TestExternalities<BlakeTwo256>,
method: &str,
data: &[u8],
) -> (Result<Vec<u8>>, bool) {
let mut t = t.ext();
let code = t.storage(sp_core::storage::well_known_keys::CODE).unwrap();
let heap_pages = t.storage(sp_core::storage::well_known_keys::HEAP_PAGES);
let runtime_code = RuntimeCode {
code_fetcher: &sp_core::traits::WrappedRuntimeCode(code.as_slice().into()),
hash: sp_crypto_hashing::blake2_256(&code).to_vec(),
heap_pages: heap_pages.and_then(|hp| Decode::decode(&mut &hp[..]).ok()),
};
sp_tracing::try_init_simple();
executor().call(&mut t, &runtime_code, method, data, CallContext::Onchain)
}
pub fn new_test_ext(code: &[u8]) -> TestExternalities<BlakeTwo256> {
sp_tracing::try_init_simple();
let ext = TestExternalities::new_with_code(
code,
node_testing::genesis::config().build_storage().unwrap(),
);
ext
}
/// Construct a fake block.
///
/// `extrinsics` must be a list of valid extrinsics, i.e. none of the extrinsics for example
/// can report `ExhaustResources`. Otherwise, this function panics.
pub fn construct_block(
env: &mut TestExternalities<BlakeTwo256>,
number: BlockNumber,
parent_hash: Hash,
extrinsics: Vec<CheckedExtrinsic>,
babe_slot: Slot,
) -> (Vec<u8>, Hash) {
use sp_trie::{LayoutV1 as Layout, TrieConfiguration};
// sign extrinsics.
let extrinsics = extrinsics.into_iter().map(sign).collect::<Vec<_>>();
// calculate the header fields that we can.
let extrinsics_root =
Layout::<BlakeTwo256>::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: Digest {
logs: vec![DigestItem::PreRuntime(
BABE_ENGINE_ID,
PreDigest::SecondaryPlain(SecondaryPlainPreDigest {
slot: babe_slot,
authority_index: 42,
})
.encode(),
)],
},
};
// execute the block to get the real header.
executor_call(env, "Core_initialize_block", &header.encode()).0.unwrap();
for extrinsic in extrinsics.iter() {
// Try to apply the `extrinsic`. It should be valid, in the sense that it passes
// all pre-inclusion checks.
let r = executor_call(env, "BlockBuilder_apply_extrinsic", &extrinsic.encode())
.0
.expect("application of an extrinsic failed");
match ApplyExtrinsicResult::decode(&mut &r[..])
.expect("apply result deserialization failed")
{
Ok(_) => {},
Err(e) => panic!("Applying extrinsic failed: {:?}", e),
}
}
let header = Header::decode(
&mut &executor_call(env, "BlockBuilder_finalize_block", &[0u8; 0]).0.unwrap()[..],
)
.unwrap();
let hash = header.blake2_256();
(Block { header, extrinsics }.encode(), hash.into())
}
@@ -0,0 +1,206 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
#![cfg(unix)]
use assert_cmd::cargo::cargo_bin;
use regex::Regex;
use std::{fs, path::PathBuf, process::Command};
use tempfile::{tempdir, TempDir};
use substrate_cli_test_utils as common;
fn contains_error(logged_output: &str) -> bool {
logged_output.contains("Error")
}
/// Helper struct to execute the export/import/revert tests.
/// The fields are paths to a temporary directory
struct ExportImportRevertExecutor<'a> {
base_path: &'a TempDir,
exported_blocks_file: &'a PathBuf,
db_path: &'a PathBuf,
num_exported_blocks: Option<u64>,
}
/// Format options for export / import commands.
enum FormatOpt {
Json,
Binary,
}
/// Command corresponding to the different commands we would like to run.
enum SubCommand {
ExportBlocks,
ImportBlocks,
}
impl ToString for SubCommand {
fn to_string(&self) -> String {
match self {
SubCommand::ExportBlocks => String::from("export-blocks"),
SubCommand::ImportBlocks => String::from("import-blocks"),
}
}
}
impl<'a> ExportImportRevertExecutor<'a> {
fn new(
base_path: &'a TempDir,
exported_blocks_file: &'a PathBuf,
db_path: &'a PathBuf,
) -> Self {
Self { base_path, exported_blocks_file, db_path, num_exported_blocks: None }
}
/// Helper method to run a command. Returns a string corresponding to what has been logged.
fn run_block_command(
&self,
sub_command: SubCommand,
format_opt: FormatOpt,
expected_to_fail: bool,
) -> String {
let sub_command_str = sub_command.to_string();
// Adding "--binary" if need be.
let arguments: Vec<&str> = match format_opt {
FormatOpt::Binary => {
vec![&sub_command_str, "--dev", "--binary", "-d"]
},
FormatOpt::Json => vec![&sub_command_str, "--dev", "-d"],
};
let tmp: TempDir;
// Setting base_path to be a temporary folder if we are importing blocks.
// This allows us to make sure we are importing from scratch.
let base_path = match sub_command {
SubCommand::ExportBlocks => &self.base_path.path(),
SubCommand::ImportBlocks => {
tmp = tempdir().unwrap();
tmp.path()
},
};
// Running the command and capturing the output.
let output = Command::new(cargo_bin("substrate-node"))
.args(&arguments)
.arg(&base_path)
.arg(&self.exported_blocks_file)
.output()
.unwrap();
let logged_output = String::from_utf8_lossy(&output.stderr).to_string();
if expected_to_fail {
// Checking that we did indeed find an error.
assert!(contains_error(&logged_output), "expected to error but did not error!");
assert!(!output.status.success());
} else {
// Making sure no error were logged.
assert!(
!contains_error(&logged_output),
"expected not to error but error'd: \n{logged_output}"
);
assert!(output.status.success());
}
logged_output
}
/// Runs the `export-blocks` command.
fn run_export(&mut self, fmt_opt: FormatOpt) {
let log = self.run_block_command(SubCommand::ExportBlocks, fmt_opt, false);
// Using regex to find out how many block we exported.
let re = Regex::new(r"Exporting blocks from #\d* to #(?P<exported_blocks>\d*)").unwrap();
let caps = re.captures(&log).unwrap();
// Saving the number of blocks we've exported for further use.
self.num_exported_blocks = Some(caps["exported_blocks"].parse::<u64>().unwrap());
let metadata = fs::metadata(&self.exported_blocks_file).unwrap();
assert!(metadata.len() > 0, "file exported_blocks should not be empty");
let _ = fs::remove_dir_all(&self.db_path);
}
/// Runs the `import-blocks` command, asserting that an error was found or
/// not depending on `expected_to_fail`.
fn run_import(&mut self, fmt_opt: FormatOpt, expected_to_fail: bool) {
let log = self.run_block_command(SubCommand::ImportBlocks, fmt_opt, expected_to_fail);
if !expected_to_fail {
// Using regex to find out how much block we imported,
// and what's the best current block.
let re =
Regex::new(r"Imported (?P<imported>\d*) blocks. Best: #(?P<best>\d*)").unwrap();
let caps = re.captures(&log).expect("capture should have succeeded");
let imported = caps["imported"].parse::<u64>().unwrap();
let best = caps["best"].parse::<u64>().unwrap();
assert_eq!(imported, best, "numbers of blocks imported and best number differs");
assert_eq!(
best,
self.num_exported_blocks.expect("number of exported blocks cannot be None; qed"),
"best block number and number of expected blocks should not differ"
);
}
self.num_exported_blocks = None;
}
/// Runs the `revert` command.
fn run_revert(&self) {
let output = Command::new(cargo_bin("substrate-node"))
.args(&["revert", "--dev", "-d"])
.arg(&self.base_path.path())
.output()
.unwrap();
let logged_output = String::from_utf8_lossy(&output.stderr).to_string();
// Reverting should not log any error.
assert!(!contains_error(&logged_output));
// Command should never fail.
assert!(output.status.success());
}
/// Helper function that runs the whole export / import / revert flow and checks for errors.
fn run(&mut self, export_fmt: FormatOpt, import_fmt: FormatOpt, expected_to_fail: bool) {
self.run_export(export_fmt);
self.run_import(import_fmt, expected_to_fail);
self.run_revert();
}
}
#[tokio::test]
async fn export_import_revert() {
let base_path = tempdir().expect("could not create a temp dir");
let exported_blocks_file = base_path.path().join("exported_blocks");
let db_path = base_path.path().join("db");
common::run_node_for_a_while(base_path.path(), &["--dev", "--no-hardware-benchmarks"]).await;
let mut executor = ExportImportRevertExecutor::new(&base_path, &exported_blocks_file, &db_path);
// Binary and binary should work.
executor.run(FormatOpt::Binary, FormatOpt::Binary, false);
// Binary and JSON should fail.
executor.run(FormatOpt::Binary, FormatOpt::Json, true);
// JSON and JSON should work.
executor.run(FormatOpt::Json, FormatOpt::Json, false);
// JSON and binary should fail.
executor.run(FormatOpt::Json, FormatOpt::Binary, true);
}
+193
View File
@@ -0,0 +1,193 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use codec::{Encode, Joiner};
use frame_support::{
dispatch::GetDispatchInfo,
traits::Currency,
weights::{constants::ExtrinsicBaseWeight, IdentityFee, WeightToFee},
};
use kitchensink_runtime::{
constants::{currency::*, time::SLOT_DURATION},
Balances, CheckedExtrinsic, Multiplier, Runtime, RuntimeCall, TransactionByteFee,
TransactionPayment,
};
use node_primitives::Balance;
use node_testing::keyring::*;
use pezkuwi_sdk::*;
use sp_runtime::{traits::One, Perbill};
pub mod common;
use self::common::{sign, *};
#[test]
fn fee_multiplier_increases_and_decreases_on_big_weight() {
let mut t = new_test_ext(compact_code_unwrap());
// initial fee multiplier must be one.
let mut prev_multiplier = Multiplier::one();
t.execute_with(|| {
assert_eq!(TransactionPayment::next_fee_multiplier(), prev_multiplier);
});
let mut tt = new_test_ext(compact_code_unwrap());
let time1 = 42 * 1000;
// big one in terms of weight.
let block1 = construct_block(
&mut tt,
1,
GENESIS_HASH.into(),
vec![
CheckedExtrinsic {
format: sp_runtime::generic::ExtrinsicFormat::Bare,
function: RuntimeCall::Timestamp(pallet_timestamp::Call::set { now: time1 }),
},
CheckedExtrinsic {
format: sp_runtime::generic::ExtrinsicFormat::Signed(charlie(), tx_ext(0, 0)),
function: RuntimeCall::Sudo(pallet_sudo::Call::sudo {
call: Box::new(RuntimeCall::RootTesting(
pallet_root_testing::Call::fill_block { ratio: Perbill::from_percent(60) },
)),
}),
},
],
(time1 / SLOT_DURATION).into(),
);
let time2 = 52 * 1000;
// small one in terms of weight.
let block2 = construct_block(
&mut tt,
2,
block1.1,
vec![
CheckedExtrinsic {
format: sp_runtime::generic::ExtrinsicFormat::Bare,
function: RuntimeCall::Timestamp(pallet_timestamp::Call::set { now: time2 }),
},
CheckedExtrinsic {
format: sp_runtime::generic::ExtrinsicFormat::Signed(charlie(), tx_ext(1, 0)),
function: RuntimeCall::System(frame_system::Call::remark { remark: vec![0; 1] }),
},
],
(time2 / SLOT_DURATION).into(),
);
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).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).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);
});
}
fn new_account_info(free_dollars: u128) -> Vec<u8> {
frame_system::AccountInfo {
nonce: 0u32,
consumers: 0,
providers: 1,
sufficients: 0,
data: (free_dollars * DOLLARS, 0 * DOLLARS, 0 * DOLLARS, 1u128 << 127),
}
.encode()
}
#[test]
fn transaction_fee_is_correct() {
// 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 pezkuwi runtime.
// (this based on assigning 0.1 CENT to the cheapest tx with `weight = 100`)
let mut t = new_test_ext(compact_code_unwrap());
t.insert(<frame_system::Account<Runtime>>::hashed_key_for(alice()), new_account_info(100));
t.insert(<frame_system::Account<Runtime>>::hashed_key_for(bob()), new_account_info(10));
t.insert(
<pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec(),
(110 * DOLLARS).encode(),
);
t.insert(<frame_system::BlockHash<Runtime>>::hashed_key_for(0), vec![0u8; 32]);
let tip = 1_000_000;
let xt = sign(CheckedExtrinsic {
format: sp_runtime::generic::ExtrinsicFormat::Signed(alice(), tx_ext(0, tip)),
function: RuntimeCall::Balances(default_transfer_call()),
});
let r = executor_call(&mut t, "Core_initialize_block", &vec![].and(&from_block_number(1u32))).0;
assert!(r.is_ok());
let r = executor_call(&mut t, "BlockBuilder_apply_extrinsic", &vec![].and(&xt.clone())).0;
assert!(r.is_ok());
t.execute_with(|| {
assert_eq!(Balances::total_balance(&bob()), (10 + 69) * DOLLARS);
// Components deducted from alice's balances:
// - Base fee
// - Weight fee
// - Length fee
// - Tip
// - Creation-fee of bob's account.
let mut balance_alice = (100 - 69) * DOLLARS;
let base_weight = ExtrinsicBaseWeight::get();
let base_fee = IdentityFee::<Balance>::weight_to_fee(&base_weight);
let length_fee = TransactionByteFee::get() * (xt.clone().encode().len() as Balance);
balance_alice -= length_fee;
let mut info = default_transfer_call().get_dispatch_info();
info.extension_weight = xt.0.extension_weight();
let weight = info.total_weight();
let weight_fee = IdentityFee::<Balance>::weight_to_fee(&weight);
// we know that weight to fee multiplier is effect-less in block 1.
// current weight of transfer = 200_000_000
// Linear weight to fee is 1:1 right now (1 weight = 1 unit of balance)
assert_eq!(weight_fee, weight.ref_time() as Balance);
balance_alice -= base_fee;
balance_alice -= weight_fee;
balance_alice -= tip;
assert_eq!(Balances::total_balance(&alice()), balance_alice);
});
}
@@ -0,0 +1,40 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
#![cfg(unix)]
use assert_cmd::cargo::cargo_bin;
use std::process::Command;
use tempfile::tempdir;
use substrate_cli_test_utils as common;
#[tokio::test]
async fn inspect_works() {
let base_path = tempdir().expect("could not create a temp dir");
common::run_node_for_a_while(base_path.path(), &["--dev", "--no-hardware-benchmarks"]).await;
let status = Command::new(cargo_bin("substrate-node"))
.args(&["inspect", "--dev", "-d"])
.arg(base_path.path())
.args(&["block", "1"])
.status()
.unwrap();
assert!(status.success());
}
@@ -0,0 +1,43 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use assert_cmd::cargo::cargo_bin;
use std::process::Command;
use tempfile::tempdir;
use substrate_cli_test_utils as common;
#[tokio::test]
#[cfg(unix)]
async fn purge_chain_works() {
let base_path = tempdir().expect("could not create a temp dir");
common::run_node_for_a_while(base_path.path(), &["--dev", "--no-hardware-benchmarks"]).await;
let status = Command::new(cargo_bin("substrate-node"))
.args(&["purge-chain", "--dev", "-d"])
.arg(base_path.path())
.arg("-y")
.status()
.unwrap();
assert!(status.success());
// Make sure that the `dev` chain folder exists, but the `db` is deleted.
assert!(base_path.path().join("chains/dev/").exists());
assert!(!base_path.path().join("chains/dev/db/full").exists());
}
@@ -0,0 +1,38 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use tempfile::tempdir;
use substrate_cli_test_utils as common;
#[tokio::test]
#[cfg(unix)]
async fn remember_state_pruning_works() {
let base_path = tempdir().expect("could not create a temp dir");
// First run with `--state-pruning=archive`.
common::run_node_for_a_while(
base_path.path(),
&["--dev", "--state-pruning=archive", "--no-hardware-benchmarks"],
)
.await;
// Then run again without specifying the state pruning.
// This should load state pruning settings from the db.
common::run_node_for_a_while(base_path.path(), &["--dev", "--no-hardware-benchmarks"]).await;
}
@@ -0,0 +1,129 @@
{
"system": {},
"babe": {
"authorities": [],
"epochConfig": {
"allowed_slots": "PrimaryAndSecondaryVRFSlots",
"c": [
1,
4
]
}
},
"indices": {
"indices": []
},
"balances": {
"balances": [],
"devAccounts": null
},
"broker": {},
"transactionPayment": {
"multiplier": "1000000000000000000"
},
"staking": {
"validatorCount": 0,
"minimumValidatorCount": 0,
"invulnerables": [],
"forceEra": "NotForcing",
"slashRewardFraction": 0,
"canceledPayout": 0,
"stakers": [],
"minNominatorBond": 0,
"minValidatorBond": 0,
"maxValidatorCount": null,
"maxNominatorCount": null
},
"session": {
"keys": [],
"nonAuthorityKeys": []
},
"revive": {
},
"democracy": {},
"council": {
"members": []
},
"technicalCommittee": {
"members": []
},
"elections": {
"members": []
},
"technicalMembership": {
"members": []
},
"grandpa": {
"authorities": []
},
"beefy": {
"authorities": [],
"genesisBlock": 1
},
"treasury": {},
"sudo": {
"key": null
},
"imOnline": {
"keys": []
},
"authorityDiscovery": {
"keys": []
},
"society": {
"pot": 0
},
"vesting": {
"vesting": []
},
"glutton": {
"compute": "0",
"storage": "0",
"blockLength": "0",
"trashDataCount": 0
},
"assets": {
"assets": [],
"metadata": [],
"accounts": [],
"nextAssetId": null,
"reserves": []
},
"poolAssets": {
"assets": [],
"metadata": [],
"accounts": [],
"nextAssetId": null,
"reserves": []
},
"transactionStorage": {
"byteFee": 10,
"entryFee": 1000,
"storagePeriod": 100800
},
"allianceMotion": {
"members": []
},
"alliance": {
"fellows": [],
"allies": []
},
"mixnet": {
"mixnodes": []
},
"nominationPools": {
"minJoinBond": 0,
"minCreateBond": 0,
"maxPools": 16,
"maxMembersPerPool": 32,
"maxMembers": 512,
"globalMaxCommission": null
},
"txPause": {
"paused": []
},
"safeMode": {
"enteredUntil": null
}
}
@@ -0,0 +1,89 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
#![cfg(unix)]
use assert_cmd::cargo::cargo_bin;
use nix::sys::signal::Signal::{self, SIGINT, SIGTERM};
use std::{
process::{self, Command},
time::Duration,
};
use tempfile::tempdir;
use substrate_cli_test_utils as common;
#[tokio::test]
async fn running_the_node_works_and_can_be_interrupted() {
common::run_with_timeout(Duration::from_secs(60 * 10), async move {
async fn run_command_and_kill(signal: Signal) {
let base_path = tempdir().expect("could not create a temp dir");
let mut cmd = common::KillChildOnDrop(
Command::new(cargo_bin("substrate-node"))
.stdout(process::Stdio::piped())
.stderr(process::Stdio::piped())
.args(&["--dev", "-d"])
.arg(base_path.path())
.arg("--db=paritydb")
.arg("--no-hardware-benchmarks")
.spawn()
.unwrap(),
);
let stderr = cmd.stderr.take().unwrap();
let ws_url = common::extract_info_from_output(stderr).0.ws_url;
common::wait_n_finalized_blocks(3, &ws_url).await;
cmd.assert_still_running();
cmd.stop_with_signal(signal);
// Check if the database was closed gracefully. If it was not,
// there may exist a ref cycle that prevents the Client from being dropped properly.
//
// parity-db only writes the stats file on clean shutdown.
let stats_file = base_path.path().join("chains/dev/paritydb/full/stats.txt");
assert!(std::path::Path::exists(&stats_file));
}
run_command_and_kill(SIGINT).await;
run_command_and_kill(SIGTERM).await;
})
.await;
}
#[tokio::test]
async fn running_two_nodes_with_the_same_ws_port_should_work() {
common::run_with_timeout(Duration::from_secs(60 * 10), async move {
let mut first_node = common::KillChildOnDrop(common::start_node());
let mut second_node = common::KillChildOnDrop(common::start_node());
let stderr = first_node.stderr.take().unwrap();
let ws_url = common::extract_info_from_output(stderr).0.ws_url;
common::wait_n_finalized_blocks(3, &ws_url).await;
first_node.assert_still_running();
second_node.assert_still_running();
first_node.stop();
second_node.stop();
})
.await;
}
@@ -0,0 +1,264 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use codec::Decode;
use frame_system::offchain::{SendSignedTransaction, Signer, SubmitTransaction};
use kitchensink_runtime::{Executive, ExistentialDeposit, Indices, Runtime, UncheckedExtrinsic};
use pezkuwi_sdk::*;
use sp_application_crypto::AppCrypto;
use sp_core::offchain::{testing::TestTransactionPoolExt, TransactionPoolExt};
use sp_keyring::sr25519::Keyring::Alice;
use sp_keystore::{testing::MemoryKeystore, Keystore, KeystoreExt};
use sp_runtime::generic;
pub mod common;
use self::common::*;
#[test]
fn should_submit_unsigned_transaction() {
let mut t = new_test_ext(compact_code_unwrap());
let (pool, state) = TestTransactionPoolExt::new();
t.register_extension(TransactionPoolExt::new(pool));
t.execute_with(|| {
let signature =
pallet_im_online::sr25519::AuthoritySignature::try_from(vec![0; 64]).unwrap();
let heartbeat_data = pallet_im_online::Heartbeat {
block_number: 1,
session_index: 1,
authority_index: 0,
validators_len: 0,
};
let call = pallet_im_online::Call::heartbeat { heartbeat: heartbeat_data, signature };
let xt = generic::UncheckedExtrinsic::new_bare(call.into()).into();
SubmitTransaction::<Runtime, pallet_im_online::Call<Runtime>>::submit_transaction(xt)
.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_unwrap());
let (pool, state) = TestTransactionPoolExt::new();
t.register_extension(TransactionPoolExt::new(pool));
let keystore = MemoryKeystore::new();
keystore
.sr25519_generate_new(sr25519::AuthorityId::ID, Some(&format!("{}/hunter1", PHRASE)))
.unwrap();
keystore
.sr25519_generate_new(sr25519::AuthorityId::ID, Some(&format!("{}/hunter2", PHRASE)))
.unwrap();
keystore
.sr25519_generate_new(sr25519::AuthorityId::ID, Some(&format!("{}/hunter3", PHRASE)))
.unwrap();
t.register_extension(KeystoreExt::new(keystore));
t.execute_with(|| {
let results =
Signer::<Runtime, TestAuthorityId>::all_accounts().send_signed_transaction(|_| {
pallet_balances::Call::transfer_allow_death {
dest: Alice.to_account_id().into(),
value: Default::default(),
}
});
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_unwrap());
let (pool, state) = TestTransactionPoolExt::new();
t.register_extension(TransactionPoolExt::new(pool));
let keystore = MemoryKeystore::new();
keystore
.sr25519_generate_new(sr25519::AuthorityId::ID, Some(&format!("{}/hunter1", PHRASE)))
.unwrap();
keystore
.sr25519_generate_new(sr25519::AuthorityId::ID, Some(&format!("{}/hunter2", PHRASE)))
.unwrap();
t.register_extension(KeystoreExt::new(keystore));
t.execute_with(|| {
let result =
Signer::<Runtime, TestAuthorityId>::any_account().send_signed_transaction(|_| {
pallet_balances::Call::transfer_allow_death {
dest: Alice.to_account_id().into(),
value: Default::default(),
}
});
assert!(result.is_some());
assert_eq!(state.read().transactions.len(), 1);
// submit another one from the same account. The nonce should be incremented.
let result =
Signer::<Runtime, TestAuthorityId>::any_account().send_signed_transaction(|_| {
pallet_balances::Call::transfer_allow_death {
dest: Alice.to_account_id().into(),
value: Default::default(),
}
});
assert!(result.is_some());
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<Runtime> {
let extra = tx.0.preamble.to_signed().unwrap().2;
extra.6
}
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 should_submit_signed_twice_from_all_accounts() {
let mut t = new_test_ext(compact_code_unwrap());
let (pool, state) = TestTransactionPoolExt::new();
t.register_extension(TransactionPoolExt::new(pool));
let keystore = MemoryKeystore::new();
keystore
.sr25519_generate_new(sr25519::AuthorityId::ID, Some(&format!("{}/hunter1", PHRASE)))
.unwrap();
keystore
.sr25519_generate_new(sr25519::AuthorityId::ID, Some(&format!("{}/hunter2", PHRASE)))
.unwrap();
t.register_extension(KeystoreExt::new(keystore));
t.execute_with(|| {
let results = Signer::<Runtime, TestAuthorityId>::all_accounts()
.send_signed_transaction(|_| {
pallet_balances::Call::transfer_allow_death { dest: Alice.to_account_id().into(), value: Default::default() }
});
let len = results.len();
assert_eq!(len, 2);
assert_eq!(results.into_iter().filter_map(|x| x.1.ok()).count(), len);
assert_eq!(state.read().transactions.len(), 2);
// submit another one from the same account. The nonce should be incremented.
let results = Signer::<Runtime, TestAuthorityId>::all_accounts()
.send_signed_transaction(|_| {
pallet_balances::Call::transfer_allow_death { dest: Alice.to_account_id().into(), value: Default::default() }
});
let len = results.len();
assert_eq!(len, 2);
assert_eq!(results.into_iter().filter_map(|x| x.1.ok()).count(), len);
assert_eq!(state.read().transactions.len(), 4);
// now check that the transaction nonces are not equal
let s = state.read();
fn nonce(tx: UncheckedExtrinsic) -> frame_system::CheckNonce<Runtime> {
let extra = tx.0.preamble.to_signed().unwrap().2;
extra.6
}
let nonce1 = nonce(UncheckedExtrinsic::decode(&mut &*s.transactions[0]).unwrap());
let nonce2 = nonce(UncheckedExtrinsic::decode(&mut &*s.transactions[1]).unwrap());
let nonce3 = nonce(UncheckedExtrinsic::decode(&mut &*s.transactions[2]).unwrap());
let nonce4 = nonce(UncheckedExtrinsic::decode(&mut &*s.transactions[3]).unwrap());
assert!(
nonce1 != nonce3,
"Transactions should have different nonces. Got: 1st tx nonce: {:?}, 2nd nonce: {:?}", nonce1, nonce3
);
assert!(
nonce2 != nonce4,
"Transactions should have different nonces. Got: 1st tx nonce: {:?}, 2nd tx nonce: {:?}", nonce2, nonce4
);
});
}
#[test]
fn submitted_transaction_should_be_valid() {
use codec::Encode;
use sp_runtime::{
traits::StaticLookup,
transaction_validity::{TransactionSource, TransactionTag},
};
let mut t = new_test_ext(compact_code_unwrap());
let (pool, state) = TestTransactionPoolExt::new();
t.register_extension(TransactionPoolExt::new(pool));
let keystore = MemoryKeystore::new();
keystore
.sr25519_generate_new(sr25519::AuthorityId::ID, Some(&format!("{}/hunter1", PHRASE)))
.unwrap();
t.register_extension(KeystoreExt::new(keystore));
t.execute_with(|| {
let results =
Signer::<Runtime, TestAuthorityId>::all_accounts().send_signed_transaction(|_| {
pallet_balances::Call::transfer_allow_death {
dest: Alice.to_account_id().into(),
value: Default::default(),
}
});
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_unwrap());
t.execute_with(|| {
let source = TransactionSource::External;
let extrinsic = UncheckedExtrinsic::decode(&mut &*tx0).unwrap();
// add balance to the account
let author = extrinsic.0.preamble.clone().to_signed().clone().unwrap().0;
let address = Indices::lookup(author).unwrap();
let data = pallet_balances::AccountData {
free: ExistentialDeposit::get() * 10,
..Default::default()
};
let account = frame_system::AccountInfo { providers: 1, data, ..Default::default() };
<frame_system::Account<Runtime>>::insert(&address, account);
// check validity
let res = Executive::validate_transaction(
source,
extrinsic,
frame_system::BlockHash::<Runtime>::get(0),
)
.unwrap();
// We ignore res.priority since this number can change based on updates to weights and such.
assert_eq!(res.requires, Vec::<TransactionTag>::new());
assert_eq!(res.provides, vec![(address, 0).encode()]);
assert_eq!(res.longevity, 2047);
assert_eq!(res.propagate, true);
});
}
+92
View File
@@ -0,0 +1,92 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use assert_cmd::cargo::cargo_bin;
use std::{process, time::Duration};
use crate::common::KillChildOnDrop;
use substrate_cli_test_utils as common;
pub mod websocket_server;
#[tokio::test]
async fn telemetry_works() {
common::run_with_timeout(Duration::from_secs(60 * 10), async move {
let config = websocket_server::Config {
capacity: 1,
max_frame_size: 1048 * 1024,
send_buffer_len: 32,
bind_address: "127.0.0.1:0".parse().unwrap(),
};
let mut server = websocket_server::WsServer::new(config).await.unwrap();
let addr = server.local_addr().unwrap();
let server_task = tokio::spawn(async move {
loop {
use websocket_server::Event;
match server.next_event().await {
// New connection on the listener.
Event::ConnectionOpen { address } => {
println!("New connection from {:?}", address);
server.accept();
},
// Received a message from a connection.
Event::BinaryFrame { message, .. } => {
let json: serde_json::Value = serde_json::from_slice(&message).unwrap();
let object =
json.as_object().unwrap().get("payload").unwrap().as_object().unwrap();
if matches!(object.get("best"), Some(serde_json::Value::String(_))) {
break;
}
},
Event::TextFrame { .. } => {
panic!("Got a TextFrame over the socket, this is a bug")
},
// Connection has been closed.
Event::ConnectionError { .. } => {},
}
}
});
let mut substrate = process::Command::new(cargo_bin("substrate-node"));
let mut substrate = KillChildOnDrop(
substrate
.args(&["--dev", "--tmp", "--telemetry-url"])
.arg(format!("ws://{} 10", addr))
.arg("--no-hardware-benchmarks")
.stdout(process::Stdio::piped())
.stderr(process::Stdio::piped())
.stdin(process::Stdio::null())
.spawn()
.unwrap(),
);
server_task.await.expect("server task panicked");
substrate.assert_still_running();
// Stop the process
substrate.stop();
})
.await;
}
@@ -0,0 +1,62 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
#![cfg(unix)]
use assert_cmd::cargo::cargo_bin;
use std::{
process::{Command, Stdio},
time::Duration,
};
use substrate_cli_test_utils as common;
#[allow(dead_code)]
// Apparently `#[ignore]` doesn't actually work to disable this one.
//#[tokio::test]
async fn temp_base_path_works() {
common::run_with_timeout(Duration::from_secs(60 * 10), async move {
let mut cmd = Command::new(cargo_bin("substrate-node"));
let mut child = common::KillChildOnDrop(
cmd.args(&["--dev", "--tmp", "--no-hardware-benchmarks"])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap(),
);
let mut stderr = child.stderr.take().unwrap();
let node_info = common::extract_info_from_output(&mut stderr).0;
// Let it produce some blocks.
common::wait_n_finalized_blocks(3, &node_info.ws_url).await;
// Ensure the db path exists while the node is running
assert!(node_info.db_path.exists());
child.assert_still_running();
// Stop the process
child.stop();
if node_info.db_path.exists() {
panic!("Database path `{}` wasn't deleted!", node_info.db_path.display());
}
})
.await;
}
+51
View File
@@ -0,0 +1,51 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use assert_cmd::cargo::cargo_bin;
use regex::Regex;
use std::process::Command;
fn expected_regex() -> Regex {
Regex::new(r"^substrate-node (.+)-([a-f\d]+)$").unwrap()
}
#[test]
fn version_is_full() {
let expected = expected_regex();
let output = Command::new(cargo_bin("substrate-node")).args(&["--version"]).output().unwrap();
assert!(output.status.success(), "command returned with non-success exit code");
let output = dbg!(String::from_utf8_lossy(&output.stdout).trim().to_owned());
let captures = expected.captures(output.as_str()).expect("could not parse version in output");
assert_eq!(&captures[1], env!("CARGO_PKG_VERSION"));
}
#[test]
fn test_regex_matches_properly() {
let expected = expected_regex();
let captures = expected.captures("substrate-node 2.0.0-da487d19d").unwrap();
assert_eq!(&captures[1], "2.0.0");
assert_eq!(&captures[2], "da487d19d");
let captures = expected.captures("substrate-node 2.0.0-alpha.5-da487d19d").unwrap();
assert_eq!(&captures[1], "2.0.0-alpha.5");
assert_eq!(&captures[2], "da487d19d");
}
@@ -0,0 +1,278 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use core::pin::Pin;
use futures::prelude::*;
use soketto::handshake::{server::Response, Server};
use std::{io, net::SocketAddr};
use tokio::net::{TcpListener, TcpStream};
use tokio_util::compat::{Compat, TokioAsyncReadCompatExt};
/// Configuration for a [`WsServer`].
pub struct Config {
/// IP address to try to bind to.
pub bind_address: SocketAddr,
/// Maximum size, in bytes, of a frame sent by the remote.
///
/// Since the messages are entirely buffered before being returned, a maximum value is
/// necessary in order to prevent malicious clients from sending huge frames that would
/// occupy a lot of memory.
pub max_frame_size: usize,
/// Number of pending messages to buffer up for sending before the socket is considered
/// unresponsive.
pub send_buffer_len: usize,
/// Pre-allocated capacity for the list of connections.
pub capacity: usize,
}
/// Identifier for a connection with regard to a [`WsServer`].
///
/// After a connection has been closed, its [`ConnectionId`] might be reused.
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct ConnectionId(u64);
/// A WebSocket message.
pub enum Message {
Text(String),
Binary(Vec<u8>),
}
/// WebSockets listening socket and list of open connections.
pub struct WsServer {
/// Value passed through [`Config::max_frame_size`].
max_frame_size: usize,
/// Endpoint for incoming TCP sockets.
listener: TcpListener,
/// Pending incoming connection to accept. Accepted by calling [`WsServer::accept`].
pending_incoming: Option<TcpStream>,
/// List of TCP connections that are currently negotiating the WebSocket handshake.
///
/// The output can be an error if the handshake fails.
negotiating: stream::FuturesUnordered<
Pin<
Box<
dyn Future<
Output = Result<
Server<'static, Compat<TcpStream>>,
Box<dyn std::error::Error>,
>,
> + Send,
>,
>,
>,
/// List of streams of incoming messages for all connections.
incoming_messages: stream::SelectAll<
Pin<Box<dyn Stream<Item = Result<Message, Box<dyn std::error::Error>>> + Send>>,
>,
/// Tasks dedicated to closing sockets that have been rejected.
rejected_sockets: stream::FuturesUnordered<Pin<Box<dyn Future<Output = ()> + Send>>>,
}
impl WsServer {
/// Try opening a TCP listening socket.
///
/// Returns an error if the listening socket fails to open.
pub async fn new(config: Config) -> Result<Self, io::Error> {
let listener = TcpListener::bind(config.bind_address).await?;
Ok(WsServer {
max_frame_size: config.max_frame_size,
listener,
pending_incoming: None,
negotiating: stream::FuturesUnordered::new(),
incoming_messages: stream::SelectAll::new(),
rejected_sockets: stream::FuturesUnordered::new(),
})
}
/// Address of the local TCP listening socket, as provided by the operating system.
pub fn local_addr(&self) -> Result<SocketAddr, io::Error> {
self.listener.local_addr()
}
/// Accepts the pending connection.
///
/// Either [`WsServer::accept`] or [`WsServer::reject`] must be called after a
/// [`Event::ConnectionOpen`] event is returned.
///
/// # Panic
///
/// Panics if no connection is pending.
pub fn accept(&mut self) {
let pending_incoming = self.pending_incoming.take().expect("no pending socket");
self.negotiating.push(Box::pin(async move {
let mut server = Server::new(pending_incoming.compat());
let websocket_key = match server.receive_request().await {
Ok(req) => req.key(),
Err(err) => return Err(Box::new(err) as Box<_>),
};
match server
.send_response(&{ Response::Accept { key: websocket_key, protocol: None } })
.await
{
Ok(()) => {},
Err(err) => return Err(Box::new(err) as Box<_>),
};
Ok(server)
}));
}
/// Reject the pending connection.
///
/// Either [`WsServer::accept`] or [`WsServer::reject`] must be called after a
/// [`Event::ConnectionOpen`] event is returned.
///
/// # Panic
///
/// Panics if no connection is pending.
pub fn reject(&mut self) {
let _ = self.pending_incoming.take().expect("no pending socket");
}
/// Returns the next event happening on the server.
pub async fn next_event(&mut self) -> Event {
loop {
futures::select! {
// Only try to fetch a new incoming connection if none is pending.
socket = {
let listener = &self.listener;
let has_pending = self.pending_incoming.is_some();
async move {
if !has_pending {
listener.accept().await
} else {
loop { futures::pending!() }
}
}
}.fuse() => {
let (socket, address) = match socket {
Ok(s) => s,
Err(_) => continue,
};
debug_assert!(self.pending_incoming.is_none());
self.pending_incoming = Some(socket);
return Event::ConnectionOpen { address };
},
result = self.negotiating.select_next_some() => {
let server = match result {
Ok(s) => s,
Err(error) => return Event::ConnectionError {
error,
},
};
let (mut _sender, receiver) = {
let mut builder = server.into_builder();
builder.set_max_frame_size(self.max_frame_size);
builder.set_max_message_size(self.max_frame_size);
builder.finish()
};
// Spawn a task dedicated to receiving messages from the socket.
self.incoming_messages.push({
// Turn `receiver` into a stream of received packets.
let socket_packets = stream::unfold((receiver, Vec::new()), move |(mut receiver, mut buf)| async {
buf.clear();
let ret = match receiver.receive_data(&mut buf).await {
Ok(soketto::Data::Text(len)) => String::from_utf8(buf[..len].to_vec())
.map(Message::Text)
.map_err(|err| Box::new(err) as Box<_>),
Ok(soketto::Data::Binary(len)) => Ok(Message::Binary(buf[..len].to_vec())),
Err(err) => Err(Box::new(err) as Box<_>),
};
Some((ret, (receiver, buf)))
});
Box::pin(socket_packets.map(move |msg| (msg)))
});
},
result = self.incoming_messages.select_next_some() => {
let message = match result {
Ok(m) => m,
Err(error) => return Event::ConnectionError {
error,
},
};
match message {
Message::Text(message) => {
return Event::TextFrame {
message,
}
}
Message::Binary(message) => {
return Event::BinaryFrame {
message,
}
}
}
},
_ = self.rejected_sockets.select_next_some() => {
}
}
}
}
}
/// Event that has happened on a [`WsServer`].
#[derive(Debug)]
pub enum Event {
/// A new TCP connection has arrived on the listening socket.
///
/// The connection *must* be accepted or rejected using [`WsServer::accept`] or
/// [`WsServer::reject`].
/// No other [`Event::ConnectionOpen`] event will be generated until the current pending
/// connection has been either accepted or rejected.
ConnectionOpen {
/// Address of the remote, as provided by the operating system.
address: SocketAddr,
},
/// An error has happened on a connection. The connection is now closed and its
/// [`ConnectionId`] is now invalid.
ConnectionError { error: Box<dyn std::error::Error> },
/// A text frame has been received on a connection.
TextFrame {
/// Message sent by the remote. Its content is entirely decided by the client, and
/// nothing must be assumed about the validity of this message.
message: String,
},
/// A text frame has been received on a connection.
BinaryFrame {
/// Message sent by the remote. Its content is entirely decided by the client, and
/// nothing must be assumed about the validity of this message.
message: Vec<u8>,
},
}