mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 20:31:13 +00:00
Extensible transactions (and tips) (#3102)
* Make extrinsics extensible. Also Remove old extrinsic types. * Rest of mockup. Add tips. * Fix some build issues * Runtiem builds :) * Substrate builds. * Fix a doc test * Compact encoding * Extract out the era logic into an extension * Weight Check signed extension. (#3115) * Weight signed extension. * Revert a bit + test for check era. * Update Cargo.toml * Update node/cli/src/factory_impl.rs * Update node/executor/src/lib.rs * Update node/executor/src/lib.rs * Don't use len for weight - use data. * Operational Transaction; second attempt (#3138) * working poc added. * some fixes. * Update doc. * Fix all tests + final logic. * more refactoring. * nits. * System block limit in bytes. * Silent the storage macro warnings. * More logic more tests. * Fix import. * Refactor names. * Fix build. * Update srml/balances/src/lib.rs * Final refactor. * Bump transaction version * Fix weight mult test. * Fix more tests and improve doc. * Bump. * Make some tests work again. * Fix subkey. * Remove todos + bump. * Ignore expensive test. * Bump.
This commit is contained in:
Generated
+4
@@ -2256,9 +2256,11 @@ dependencies = [
|
||||
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sr-io 2.0.0",
|
||||
"sr-primitives 2.0.0",
|
||||
"srml-balances 2.0.0",
|
||||
"srml-contracts 2.0.0",
|
||||
"srml-finality-tracker 2.0.0",
|
||||
"srml-indices 2.0.0",
|
||||
"srml-system 2.0.0",
|
||||
"srml-timestamp 2.0.0",
|
||||
"structopt 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"substrate-basic-authorship 2.0.0",
|
||||
@@ -4135,6 +4137,8 @@ dependencies = [
|
||||
"rustc-hex 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"schnorrkel 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sr-primitives 2.0.0",
|
||||
"srml-balances 2.0.0",
|
||||
"srml-system 2.0.0",
|
||||
"substrate-bip39 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"substrate-primitives 2.0.0",
|
||||
"tiny-bip39 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
||||
@@ -17,50 +17,83 @@
|
||||
//! Generic implementation of an extrinsic that has passed the verification
|
||||
//! stage.
|
||||
|
||||
use crate::traits::{self, Member, SimpleArithmetic, MaybeDisplay};
|
||||
use crate::weights::{Weighable, Weight};
|
||||
use rstd::result::Result;
|
||||
use crate::traits::{
|
||||
self, Member, MaybeDisplay, SignedExtension, DispatchError, Dispatchable, DispatchResult,
|
||||
ValidateUnsigned
|
||||
};
|
||||
use crate::weights::{GetDispatchInfo, DispatchInfo};
|
||||
use crate::transaction_validity::TransactionValidity;
|
||||
|
||||
/// Definition of something that the external world might want to say; its
|
||||
/// existence implies that it has been checked and is good, particularly with
|
||||
/// regards to the signature.
|
||||
#[derive(PartialEq, Eq, Clone)]
|
||||
#[cfg_attr(feature = "std", derive(Debug))]
|
||||
pub struct CheckedExtrinsic<AccountId, Index, Call> {
|
||||
pub struct CheckedExtrinsic<AccountId, Call, Extra> {
|
||||
/// Who this purports to be from and the number of extrinsics have come before
|
||||
/// from the same signer, if anyone (note this is not a signature).
|
||||
pub signed: Option<(AccountId, Index)>,
|
||||
pub signed: Option<(AccountId, Extra)>,
|
||||
|
||||
/// The function that should be called.
|
||||
pub function: Call,
|
||||
}
|
||||
|
||||
impl<AccountId, Index, Call> traits::Applyable for CheckedExtrinsic<AccountId, Index, Call>
|
||||
impl<AccountId, Call, Extra, Origin> traits::Applyable
|
||||
for
|
||||
CheckedExtrinsic<AccountId, Call, Extra>
|
||||
where
|
||||
AccountId: Member + MaybeDisplay,
|
||||
Index: Member + MaybeDisplay + SimpleArithmetic,
|
||||
Call: Member,
|
||||
Call: Member + Dispatchable<Origin=Origin>,
|
||||
Extra: SignedExtension<AccountId=AccountId>,
|
||||
Origin: From<Option<AccountId>>,
|
||||
{
|
||||
type Index = Index;
|
||||
type AccountId = AccountId;
|
||||
type Call = Call;
|
||||
|
||||
fn index(&self) -> Option<&Self::Index> {
|
||||
self.signed.as_ref().map(|x| &x.1)
|
||||
}
|
||||
type Call = Call;
|
||||
|
||||
fn sender(&self) -> Option<&Self::AccountId> {
|
||||
self.signed.as_ref().map(|x| &x.0)
|
||||
}
|
||||
|
||||
fn deconstruct(self) -> (Self::Call, Option<Self::AccountId>) {
|
||||
(self.function, self.signed.map(|x| x.0))
|
||||
fn validate<U: ValidateUnsigned<Call=Self::Call>>(&self,
|
||||
info: DispatchInfo,
|
||||
len: usize,
|
||||
) -> TransactionValidity {
|
||||
if let Some((ref id, ref extra)) = self.signed {
|
||||
Extra::validate(extra, id, info, len).into()
|
||||
} else {
|
||||
match Extra::validate_unsigned(info, len) {
|
||||
Ok(extra) => match U::validate_unsigned(&self.function) {
|
||||
TransactionValidity::Valid(v) =>
|
||||
TransactionValidity::Valid(v.combine_with(extra)),
|
||||
x => x,
|
||||
},
|
||||
x => x.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch(self,
|
||||
info: DispatchInfo,
|
||||
len: usize,
|
||||
) -> Result<DispatchResult, DispatchError> {
|
||||
let maybe_who = if let Some((id, extra)) = self.signed {
|
||||
Extra::pre_dispatch(extra, &id, info, len)?;
|
||||
Some(id)
|
||||
} else {
|
||||
Extra::pre_dispatch_unsigned(info, len)?;
|
||||
None
|
||||
};
|
||||
Ok(self.function.dispatch(Origin::from(maybe_who)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<AccountId, Index, Call> Weighable for CheckedExtrinsic<AccountId, Index, Call>
|
||||
impl<AccountId, Call, Extra> GetDispatchInfo for CheckedExtrinsic<AccountId, Call, Extra>
|
||||
where
|
||||
Call: Weighable,
|
||||
Call: GetDispatchInfo,
|
||||
{
|
||||
fn weight(&self, len: usize) -> Weight {
|
||||
self.function.weight(len)
|
||||
fn get_dispatch_info(&self) -> DispatchInfo {
|
||||
self.function.get_dispatch_info()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,6 @@
|
||||
// end::description[]
|
||||
|
||||
mod unchecked_extrinsic;
|
||||
mod unchecked_mortal_extrinsic;
|
||||
mod unchecked_mortal_compact_extrinsic;
|
||||
mod era;
|
||||
mod checked_extrinsic;
|
||||
mod header;
|
||||
@@ -30,8 +28,6 @@ mod digest;
|
||||
mod tests;
|
||||
|
||||
pub use self::unchecked_extrinsic::UncheckedExtrinsic;
|
||||
pub use self::unchecked_mortal_extrinsic::UncheckedMortalExtrinsic;
|
||||
pub use self::unchecked_mortal_compact_extrinsic::UncheckedMortalCompactExtrinsic;
|
||||
pub use self::era::{Era, Phase};
|
||||
pub use self::checked_extrinsic::CheckedExtrinsic;
|
||||
pub use self::header::Header;
|
||||
|
||||
@@ -20,48 +20,40 @@
|
||||
use std::fmt;
|
||||
|
||||
use rstd::prelude::*;
|
||||
use crate::codec::{Decode, Encode, Codec, Input, HasCompact};
|
||||
use crate::traits::{self, Member, SimpleArithmetic, MaybeDisplay, Lookup, Extrinsic};
|
||||
use runtime_io::blake2_256;
|
||||
use crate::codec::{Decode, Encode, Input};
|
||||
use crate::traits::{self, Member, MaybeDisplay, SignedExtension, Checkable, Extrinsic};
|
||||
use super::CheckedExtrinsic;
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Encode, Decode)]
|
||||
pub struct SignatureContent<Address, Index, Signature>
|
||||
where
|
||||
Address: Codec,
|
||||
Index: HasCompact + Codec,
|
||||
Signature: Codec,
|
||||
{
|
||||
signed: Address,
|
||||
signature: Signature,
|
||||
index: Index,
|
||||
}
|
||||
const TRANSACTION_VERSION: u8 = 2;
|
||||
|
||||
/// A extrinsic right from the external world. This is unchecked and so
|
||||
/// can contain a signature.
|
||||
#[derive(PartialEq, Eq, Clone)]
|
||||
pub struct UncheckedExtrinsic<Address, Index, Call, Signature>
|
||||
pub struct UncheckedExtrinsic<Address, Call, Signature, Extra>
|
||||
where
|
||||
Address: Codec,
|
||||
Index: HasCompact + Codec,
|
||||
Signature: Codec,
|
||||
Extra: SignedExtension
|
||||
{
|
||||
/// The signature, address and number of extrinsics have come before from
|
||||
/// the same signer, if this is a signed extrinsic.
|
||||
pub signature: Option<SignatureContent<Address, Index, Signature>>,
|
||||
/// The signature, address, number of extrinsics have come before from
|
||||
/// the same signer and an era describing the longevity of this transaction,
|
||||
/// if this is a signed extrinsic.
|
||||
pub signature: Option<(Address, Signature, Extra)>,
|
||||
/// The function that should be called.
|
||||
pub function: Call,
|
||||
}
|
||||
|
||||
impl<Address, Index, Signature, Call> UncheckedExtrinsic<Address, Index, Call, Signature>
|
||||
where
|
||||
Address: Codec,
|
||||
Index: HasCompact + Codec,
|
||||
Signature: Codec,
|
||||
impl<Address, Call, Signature, Extra: SignedExtension>
|
||||
UncheckedExtrinsic<Address, Call, Signature, Extra>
|
||||
{
|
||||
/// New instance of a signed extrinsic aka "transaction".
|
||||
pub fn new_signed(index: Index, function: Call, signed: Address, signature: Signature) -> Self {
|
||||
pub fn new_signed(
|
||||
function: Call,
|
||||
signed: Address,
|
||||
signature: Signature,
|
||||
extra: Extra
|
||||
) -> Self {
|
||||
UncheckedExtrinsic {
|
||||
signature: Some(SignatureContent{signed, signature, index}),
|
||||
signature: Some((signed, signature, extra)),
|
||||
function,
|
||||
}
|
||||
}
|
||||
@@ -75,29 +67,52 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<Address, Index, Signature, Call, AccountId, Context> traits::Checkable<Context>
|
||||
for UncheckedExtrinsic<Address, Index, Call, Signature>
|
||||
where
|
||||
Address: Member + MaybeDisplay + Codec,
|
||||
Index: Member + MaybeDisplay + SimpleArithmetic + Codec,
|
||||
Call: Encode + Member,
|
||||
Signature: Member + traits::Verify<Signer=AccountId> + Codec,
|
||||
AccountId: Member + MaybeDisplay,
|
||||
Context: Lookup<Source=Address, Target=AccountId>,
|
||||
impl<Address, Call, Signature, Extra: SignedExtension> Extrinsic
|
||||
for UncheckedExtrinsic<Address, Call, Signature, Extra>
|
||||
{
|
||||
type Checked = CheckedExtrinsic<AccountId, Index, Call>;
|
||||
type Call = Call;
|
||||
|
||||
fn check(self, context: &Context) -> Result<Self::Checked, &'static str> {
|
||||
fn is_signed(&self) -> Option<bool> {
|
||||
Some(self.signature.is_some())
|
||||
}
|
||||
|
||||
fn new_unsigned(function: Call) -> Option<Self> {
|
||||
Some(UncheckedExtrinsic::new_unsigned(function))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Address, AccountId, Call, Signature, Extra, Lookup>
|
||||
Checkable<Lookup>
|
||||
for
|
||||
UncheckedExtrinsic<Address, Call, Signature, Extra>
|
||||
where
|
||||
Address: Member + MaybeDisplay,
|
||||
Call: Encode + Member,
|
||||
Signature: Member + traits::Verify<Signer=AccountId>,
|
||||
Extra: SignedExtension<AccountId=AccountId>,
|
||||
AccountId: Member + MaybeDisplay,
|
||||
Lookup: traits::Lookup<Source=Address, Target=AccountId>
|
||||
{
|
||||
type Checked = CheckedExtrinsic<AccountId, Call, Extra>;
|
||||
|
||||
fn check(self, lookup: &Lookup) -> Result<Self::Checked, &'static str> {
|
||||
Ok(match self.signature {
|
||||
Some(SignatureContent{signed, signature, index}) => {
|
||||
let payload = (index, self.function);
|
||||
let signed = context.lookup(signed)?;
|
||||
if !crate::verify_encoded_lazy(&signature, &payload, &signed) {
|
||||
Some((signed, signature, extra)) => {
|
||||
let additional_signed = extra.additional_signed()?;
|
||||
let raw_payload = (self.function, extra, additional_signed);
|
||||
let signed = lookup.lookup(signed)?;
|
||||
if !raw_payload.using_encoded(|payload| {
|
||||
if payload.len() > 256 {
|
||||
signature.verify(&blake2_256(payload)[..], &signed)
|
||||
} else {
|
||||
signature.verify(payload, &signed)
|
||||
}
|
||||
}) {
|
||||
return Err(crate::BAD_SIGNATURE)
|
||||
}
|
||||
CheckedExtrinsic {
|
||||
signed: Some((signed, payload.0)),
|
||||
function: payload.1,
|
||||
signed: Some((signed, raw_payload.1)),
|
||||
function: raw_payload.0,
|
||||
}
|
||||
}
|
||||
None => CheckedExtrinsic {
|
||||
@@ -108,25 +123,13 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
Address: Codec,
|
||||
Index: HasCompact + Codec,
|
||||
Signature: Codec,
|
||||
Call,
|
||||
> Extrinsic for UncheckedExtrinsic<Address, Index, Call, Signature> {
|
||||
type Call = Call;
|
||||
|
||||
fn is_signed(&self) -> Option<bool> {
|
||||
Some(self.signature.is_some())
|
||||
}
|
||||
|
||||
fn new_unsigned(call: Self::Call) -> Option<Self> {
|
||||
Some(UncheckedExtrinsic::new_unsigned(call))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Address: Codec, Index: HasCompact + Codec, Signature: Codec, Call: Decode> Decode
|
||||
for UncheckedExtrinsic<Address, Index, Call, Signature>
|
||||
impl<Address, Call, Signature, Extra> Decode
|
||||
for UncheckedExtrinsic<Address, Call, Signature, Extra>
|
||||
where
|
||||
Address: Decode,
|
||||
Signature: Decode,
|
||||
Call: Decode,
|
||||
Extra: SignedExtension,
|
||||
{
|
||||
fn decode<I: Input>(input: &mut I) -> Option<Self> {
|
||||
// This is a little more complicated than usual since the binary format must be compatible
|
||||
@@ -135,70 +138,191 @@ impl<Address: Codec, Index: HasCompact + Codec, Signature: Codec, Call: Decode>
|
||||
// to use this).
|
||||
let _length_do_not_remove_me_see_above: Vec<()> = Decode::decode(input)?;
|
||||
|
||||
let version = input.read_byte()?;
|
||||
|
||||
let is_signed = version & 0b1000_0000 != 0;
|
||||
let version = version & 0b0111_1111;
|
||||
if version != TRANSACTION_VERSION {
|
||||
return None
|
||||
}
|
||||
|
||||
Some(UncheckedExtrinsic {
|
||||
signature: Decode::decode(input)?,
|
||||
signature: if is_signed { Some(Decode::decode(input)?) } else { None },
|
||||
function: Decode::decode(input)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<Address: Codec, Index: HasCompact + Codec, Signature: Codec, Call: Encode> Encode
|
||||
for UncheckedExtrinsic<Address, Index, Call, Signature>
|
||||
impl<Address, Call, Signature, Extra> Encode
|
||||
for UncheckedExtrinsic<Address, Call, Signature, Extra>
|
||||
where
|
||||
Address: Encode,
|
||||
Signature: Encode,
|
||||
Call: Encode,
|
||||
Extra: SignedExtension,
|
||||
{
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
super::encode_with_vec_prefix::<Self, _>(|v| {
|
||||
self.signature.encode_to(v);
|
||||
// 1 byte version id.
|
||||
match self.signature.as_ref() {
|
||||
Some(s) => {
|
||||
v.push(TRANSACTION_VERSION | 0b1000_0000);
|
||||
s.encode_to(v);
|
||||
}
|
||||
None => {
|
||||
v.push(TRANSACTION_VERSION & 0b0111_1111);
|
||||
}
|
||||
}
|
||||
self.function.encode_to(v);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<Address: Codec, Index: HasCompact + Codec, Signature: Codec, Call: Encode> serde::Serialize
|
||||
for UncheckedExtrinsic<Address, Index, Call, Signature>
|
||||
impl<Address: Encode, Signature: Encode, Call: Encode, Extra: SignedExtension> serde::Serialize
|
||||
for UncheckedExtrinsic<Address, Call, Signature, Extra>
|
||||
{
|
||||
fn serialize<S>(&self, seq: S) -> Result<S::Ok, S::Error> where S: ::serde::Serializer {
|
||||
self.using_encoded(|bytes| ::substrate_primitives::bytes::serialize(bytes, seq))
|
||||
self.using_encoded(|bytes| seq.serialize_bytes(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<Address, Index, Signature, Call> fmt::Debug
|
||||
for UncheckedExtrinsic<Address, Index, Call, Signature>
|
||||
impl<Address, Call, Signature, Extra> fmt::Debug
|
||||
for UncheckedExtrinsic<Address, Call, Signature, Extra>
|
||||
where
|
||||
Address: fmt::Debug + Codec,
|
||||
Index: fmt::Debug + HasCompact + Codec,
|
||||
Signature: Codec,
|
||||
Address: fmt::Debug,
|
||||
Call: fmt::Debug,
|
||||
Extra: SignedExtension,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "UncheckedExtrinsic({:?}, {:?})", self.signature.as_ref().map(|x| (&x.signed, &x.index)), self.function)
|
||||
write!(f, "UncheckedExtrinsic({:?}, {:?})", self.signature.as_ref().map(|x| (&x.0, &x.2)), self.function)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::codec::{Decode, Encode};
|
||||
use super::UncheckedExtrinsic;
|
||||
mod tests {
|
||||
use super::*;
|
||||
use runtime_io::blake2_256;
|
||||
use crate::codec::{Encode, Decode};
|
||||
use crate::traits::{SignedExtension, BlockNumberToHash, Lookup, CurrentHeight};
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
struct TestContext;
|
||||
impl Lookup for TestContext {
|
||||
type Source = u64;
|
||||
type Target = u64;
|
||||
fn lookup(&self, s: u64) -> Result<u64, &'static str> { Ok(s) }
|
||||
}
|
||||
impl CurrentHeight for TestContext {
|
||||
type BlockNumber = u64;
|
||||
fn current_height(&self) -> u64 { 42 }
|
||||
}
|
||||
impl BlockNumberToHash for TestContext {
|
||||
type BlockNumber = u64;
|
||||
type Hash = u64;
|
||||
fn block_number_to_hash(&self, n: u64) -> Option<u64> { Some(n) }
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Encode, Decode)]
|
||||
struct TestSig(u64, Vec<u8>);
|
||||
impl traits::Verify for TestSig {
|
||||
type Signer = u64;
|
||||
fn verify<L: traits::Lazy<[u8]>>(&self, mut msg: L, signer: &Self::Signer) -> bool {
|
||||
*signer == self.0 && msg.get() == &self.1[..]
|
||||
}
|
||||
}
|
||||
|
||||
type TestAccountId = u64;
|
||||
type TestCall = Vec<u8>;
|
||||
|
||||
const TEST_ACCOUNT: TestAccountId = 0;
|
||||
|
||||
// NOTE: this is demonstration. One can simply use `()` for testing.
|
||||
#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||||
struct TestExtra;
|
||||
impl SignedExtension for TestExtra {
|
||||
type AccountId = u64;
|
||||
type AdditionalSigned = ();
|
||||
fn additional_signed(&self) -> rstd::result::Result<(), &'static str> { Ok(()) }
|
||||
}
|
||||
|
||||
type Ex = UncheckedExtrinsic<TestAccountId, TestCall, TestSig, TestExtra>;
|
||||
type CEx = CheckedExtrinsic<TestAccountId, TestCall, TestExtra>;
|
||||
|
||||
#[test]
|
||||
fn unsigned_codec_should_work() {
|
||||
let ux = Ex::new_unsigned(vec![0u8; 0]);
|
||||
let encoded = ux.encode();
|
||||
assert_eq!(Ex::decode(&mut &encoded[..]), Some(ux));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signed_codec_should_work() {
|
||||
let ux = Ex::new_signed(
|
||||
vec![0u8; 0],
|
||||
TEST_ACCOUNT,
|
||||
TestSig(TEST_ACCOUNT, (vec![0u8; 0], TestExtra).encode()),
|
||||
TestExtra
|
||||
);
|
||||
let encoded = ux.encode();
|
||||
assert_eq!(Ex::decode(&mut &encoded[..]), Some(ux));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn large_signed_codec_should_work() {
|
||||
let ux = Ex::new_signed(
|
||||
vec![0u8; 0],
|
||||
TEST_ACCOUNT,
|
||||
TestSig(TEST_ACCOUNT, (vec![0u8; 257], TestExtra)
|
||||
.using_encoded(blake2_256)[..].to_owned()),
|
||||
TestExtra
|
||||
);
|
||||
let encoded = ux.encode();
|
||||
assert_eq!(Ex::decode(&mut &encoded[..]), Some(ux));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsigned_check_should_work() {
|
||||
let ux = Ex::new_unsigned(vec![0u8; 0]);
|
||||
assert!(!ux.is_signed().unwrap_or(false));
|
||||
assert!(<Ex as Checkable<TestContext>>::check(ux, &TestContext).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn badly_signed_check_should_fail() {
|
||||
let ux = Ex::new_signed(
|
||||
vec![0u8; 0],
|
||||
TEST_ACCOUNT,
|
||||
TestSig(TEST_ACCOUNT, vec![0u8; 0]),
|
||||
TestExtra
|
||||
);
|
||||
assert!(ux.is_signed().unwrap_or(false));
|
||||
assert_eq!(<Ex as Checkable<TestContext>>::check(ux, &TestContext), Err(crate::BAD_SIGNATURE));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signed_check_should_work() {
|
||||
let ux = Ex::new_signed(
|
||||
vec![0u8; 0],
|
||||
TEST_ACCOUNT,
|
||||
TestSig(TEST_ACCOUNT, (vec![0u8; 0], TestExtra).encode()),
|
||||
TestExtra
|
||||
);
|
||||
assert!(ux.is_signed().unwrap_or(false));
|
||||
assert_eq!(
|
||||
<Ex as Checkable<TestContext>>::check(ux, &TestContext),
|
||||
Ok(CEx { signed: Some((TEST_ACCOUNT, TestExtra)), function: vec![0u8; 0] })
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encoding_matches_vec() {
|
||||
type Extrinsic = UncheckedExtrinsic<u32, u32, u32, u32>;
|
||||
let ex = Extrinsic::new_unsigned(42);
|
||||
let ex = Ex::new_unsigned(vec![0u8; 0]);
|
||||
let encoded = ex.encode();
|
||||
let decoded = Extrinsic::decode(&mut encoded.as_slice()).unwrap();
|
||||
let decoded = Ex::decode(&mut encoded.as_slice()).unwrap();
|
||||
assert_eq!(decoded, ex);
|
||||
let as_vec: Vec<u8> = Decode::decode(&mut encoded.as_slice()).unwrap();
|
||||
assert_eq!(as_vec.encode(), encoded);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "std")]
|
||||
fn serialization_of_unchecked_extrinsics() {
|
||||
type Extrinsic = UncheckedExtrinsic<u32, u32, u32, u32>;
|
||||
let ex = Extrinsic::new_unsigned(42);
|
||||
|
||||
assert_eq!(serde_json::to_string(&ex).unwrap(), "\"0x14002a000000\"");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,312 +0,0 @@
|
||||
// Copyright 2017-2019 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Generic implementation of an unchecked (pre-verification) extrinsic.
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use std::fmt;
|
||||
|
||||
use rstd::prelude::*;
|
||||
use runtime_io::blake2_256;
|
||||
use crate::codec::{Decode, Encode, Input, Compact};
|
||||
use crate::traits::{self, Member, SimpleArithmetic, MaybeDisplay, CurrentHeight, BlockNumberToHash,
|
||||
Lookup, Checkable, Extrinsic, SaturatedConversion};
|
||||
use super::{CheckedExtrinsic, Era};
|
||||
|
||||
const TRANSACTION_VERSION: u8 = 1;
|
||||
|
||||
/// A extrinsic right from the external world. This is unchecked and so
|
||||
/// can contain a signature.
|
||||
#[derive(PartialEq, Eq, Clone)]
|
||||
pub struct UncheckedMortalCompactExtrinsic<Address, Index, Call, Signature> {
|
||||
/// The signature, address, number of extrinsics have come before from
|
||||
/// the same signer and an era describing the longevity of this transaction,
|
||||
/// if this is a signed extrinsic.
|
||||
pub signature: Option<(Address, Signature, Compact<Index>, Era)>,
|
||||
/// The function that should be called.
|
||||
pub function: Call,
|
||||
}
|
||||
|
||||
impl<Address, Index, Call, Signature> UncheckedMortalCompactExtrinsic<Address, Index, Call, Signature> {
|
||||
/// New instance of a signed extrinsic aka "transaction".
|
||||
pub fn new_signed(index: Index, function: Call, signed: Address, signature: Signature, era: Era) -> Self {
|
||||
UncheckedMortalCompactExtrinsic {
|
||||
signature: Some((signed, signature, index.into(), era)),
|
||||
function,
|
||||
}
|
||||
}
|
||||
|
||||
/// New instance of an unsigned extrinsic aka "inherent".
|
||||
pub fn new_unsigned(function: Call) -> Self {
|
||||
UncheckedMortalCompactExtrinsic {
|
||||
signature: None,
|
||||
function,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Address: Encode, Index: Encode, Call: Encode, Signature: Encode> Extrinsic for UncheckedMortalCompactExtrinsic<Address, Index, Call, Signature> {
|
||||
type Call = Call;
|
||||
|
||||
fn is_signed(&self) -> Option<bool> {
|
||||
Some(self.signature.is_some())
|
||||
}
|
||||
|
||||
fn new_unsigned(call: Self::Call) -> Option<Self> {
|
||||
Some(UncheckedMortalCompactExtrinsic::new_unsigned(call))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Address, AccountId, Index, Call, Signature, Context, Hash, BlockNumber> Checkable<Context>
|
||||
for UncheckedMortalCompactExtrinsic<Address, Index, Call, Signature>
|
||||
where
|
||||
Address: Member + MaybeDisplay,
|
||||
Index: Member + MaybeDisplay + SimpleArithmetic,
|
||||
Compact<Index>: Encode,
|
||||
Call: Encode + Member,
|
||||
Signature: Member + traits::Verify<Signer=AccountId>,
|
||||
AccountId: Member + MaybeDisplay,
|
||||
BlockNumber: SimpleArithmetic,
|
||||
Hash: Encode,
|
||||
Context: Lookup<Source=Address, Target=AccountId>
|
||||
+ CurrentHeight<BlockNumber=BlockNumber>
|
||||
+ BlockNumberToHash<BlockNumber=BlockNumber, Hash=Hash>,
|
||||
{
|
||||
type Checked = CheckedExtrinsic<AccountId, Index, Call>;
|
||||
|
||||
fn check(self, context: &Context) -> Result<Self::Checked, &'static str> {
|
||||
Ok(match self.signature {
|
||||
Some((signed, signature, index, era)) => {
|
||||
let current_u64 = context.current_height().saturated_into::<u64>();
|
||||
let h = context.block_number_to_hash(era.birth(current_u64).saturated_into())
|
||||
.ok_or("transaction birth block ancient")?;
|
||||
let signed = context.lookup(signed)?;
|
||||
let raw_payload = (index, self.function, era, h);
|
||||
if !raw_payload.using_encoded(|payload| {
|
||||
if payload.len() > 256 {
|
||||
signature.verify(&blake2_256(payload)[..], &signed)
|
||||
} else {
|
||||
signature.verify(payload, &signed)
|
||||
}
|
||||
}) {
|
||||
return Err(crate::BAD_SIGNATURE)
|
||||
}
|
||||
CheckedExtrinsic {
|
||||
signed: Some((signed, (raw_payload.0).0)),
|
||||
function: raw_payload.1,
|
||||
}
|
||||
}
|
||||
None => CheckedExtrinsic {
|
||||
signed: None,
|
||||
function: self.function,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<Address, Index, Call, Signature> Decode
|
||||
for UncheckedMortalCompactExtrinsic<Address, Index, Call, Signature>
|
||||
where
|
||||
Address: Decode,
|
||||
Signature: Decode,
|
||||
Compact<Index>: Decode,
|
||||
Call: Decode,
|
||||
{
|
||||
fn decode<I: Input>(input: &mut I) -> Option<Self> {
|
||||
// This is a little more complicated than usual since the binary format must be compatible
|
||||
// with substrate's generic `Vec<u8>` type. Basically this just means accepting that there
|
||||
// will be a prefix of vector length (we don't need
|
||||
// to use this).
|
||||
let _length_do_not_remove_me_see_above: Vec<()> = Decode::decode(input)?;
|
||||
|
||||
let version = input.read_byte()?;
|
||||
|
||||
let is_signed = version & 0b1000_0000 != 0;
|
||||
let version = version & 0b0111_1111;
|
||||
if version != TRANSACTION_VERSION {
|
||||
return None
|
||||
}
|
||||
|
||||
Some(UncheckedMortalCompactExtrinsic {
|
||||
signature: if is_signed { Some(Decode::decode(input)?) } else { None },
|
||||
function: Decode::decode(input)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<Address, Index, Call, Signature> Encode
|
||||
for UncheckedMortalCompactExtrinsic<Address, Index, Call, Signature>
|
||||
where
|
||||
Address: Encode,
|
||||
Signature: Encode,
|
||||
Compact<Index>: Encode,
|
||||
Call: Encode,
|
||||
{
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
super::encode_with_vec_prefix::<Self, _>(|v| {
|
||||
// 1 byte version id.
|
||||
match self.signature.as_ref() {
|
||||
Some(s) => {
|
||||
v.push(TRANSACTION_VERSION | 0b1000_0000);
|
||||
s.encode_to(v);
|
||||
}
|
||||
None => {
|
||||
v.push(TRANSACTION_VERSION & 0b0111_1111);
|
||||
}
|
||||
}
|
||||
self.function.encode_to(v);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<Address: Encode, Index, Signature: Encode, Call: Encode> serde::Serialize
|
||||
for UncheckedMortalCompactExtrinsic<Address, Index, Call, Signature>
|
||||
where Compact<Index>: Encode
|
||||
{
|
||||
fn serialize<S>(&self, seq: S) -> Result<S::Ok, S::Error> where S: ::serde::Serializer {
|
||||
self.using_encoded(|bytes| seq.serialize_bytes(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<Address, Index, Call, Signature> fmt::Debug for UncheckedMortalCompactExtrinsic<Address, Index, Call, Signature> where
|
||||
Address: fmt::Debug,
|
||||
Index: fmt::Debug,
|
||||
Call: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "UncheckedMortalCompactExtrinsic({:?}, {:?})", self.signature.as_ref().map(|x| (&x.0, &x.2)), self.function)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use runtime_io::blake2_256;
|
||||
use crate::codec::{Encode, Decode};
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
struct TestContext;
|
||||
impl Lookup for TestContext {
|
||||
type Source = u64;
|
||||
type Target = u64;
|
||||
fn lookup(&self, s: u64) -> Result<u64, &'static str> { Ok(s) }
|
||||
}
|
||||
impl CurrentHeight for TestContext {
|
||||
type BlockNumber = u64;
|
||||
fn current_height(&self) -> u64 { 42 }
|
||||
}
|
||||
impl BlockNumberToHash for TestContext {
|
||||
type BlockNumber = u64;
|
||||
type Hash = u64;
|
||||
fn block_number_to_hash(&self, n: u64) -> Option<u64> { Some(n) }
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Encode, Decode)]
|
||||
struct TestSig(u64, Vec<u8>);
|
||||
impl traits::Verify for TestSig {
|
||||
type Signer = u64;
|
||||
fn verify<L: traits::Lazy<[u8]>>(&self, mut msg: L, signer: &Self::Signer) -> bool {
|
||||
*signer == self.0 && msg.get() == &self.1[..]
|
||||
}
|
||||
}
|
||||
|
||||
const DUMMY_ACCOUNTID: u64 = 0;
|
||||
|
||||
type Ex = UncheckedMortalCompactExtrinsic<u64, u64, Vec<u8>, TestSig>;
|
||||
type CEx = CheckedExtrinsic<u64, u64, Vec<u8>>;
|
||||
|
||||
#[test]
|
||||
fn unsigned_codec_should_work() {
|
||||
let ux = Ex::new_unsigned(vec![0u8;0]);
|
||||
let encoded = ux.encode();
|
||||
assert_eq!(Ex::decode(&mut &encoded[..]), Some(ux));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signed_codec_should_work() {
|
||||
let ux = Ex::new_signed(0, vec![0u8;0], DUMMY_ACCOUNTID, TestSig(DUMMY_ACCOUNTID, (DUMMY_ACCOUNTID, vec![0u8;0], Era::immortal(), 0u64).encode()), Era::immortal());
|
||||
let encoded = ux.encode();
|
||||
assert_eq!(Ex::decode(&mut &encoded[..]), Some(ux));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn large_signed_codec_should_work() {
|
||||
let ux = Ex::new_signed(0, vec![0u8;0], DUMMY_ACCOUNTID, TestSig(DUMMY_ACCOUNTID, (DUMMY_ACCOUNTID, vec![0u8; 257], Era::immortal(), 0u64).using_encoded(blake2_256)[..].to_owned()), Era::immortal());
|
||||
let encoded = ux.encode();
|
||||
assert_eq!(Ex::decode(&mut &encoded[..]), Some(ux));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsigned_check_should_work() {
|
||||
let ux = Ex::new_unsigned(vec![0u8;0]);
|
||||
assert!(!ux.is_signed().unwrap_or(false));
|
||||
assert!(<Ex as Checkable<TestContext>>::check(ux, &TestContext).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn badly_signed_check_should_fail() {
|
||||
let ux = Ex::new_signed(0, vec![0u8;0], DUMMY_ACCOUNTID, TestSig(DUMMY_ACCOUNTID, vec![0u8]), Era::immortal());
|
||||
assert!(ux.is_signed().unwrap_or(false));
|
||||
assert_eq!(<Ex as Checkable<TestContext>>::check(ux, &TestContext), Err(crate::BAD_SIGNATURE));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn immortal_signed_check_should_work() {
|
||||
let ux = Ex::new_signed(0, vec![0u8;0], DUMMY_ACCOUNTID, TestSig(DUMMY_ACCOUNTID, (Compact::from(DUMMY_ACCOUNTID), vec![0u8;0], Era::immortal(), 0u64).encode()), Era::immortal());
|
||||
assert!(ux.is_signed().unwrap_or(false));
|
||||
assert_eq!(<Ex as Checkable<TestContext>>::check(ux, &TestContext), Ok(CEx { signed: Some((DUMMY_ACCOUNTID, 0)), function: vec![0u8;0] }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mortal_signed_check_should_work() {
|
||||
let ux = Ex::new_signed(0, vec![0u8;0], DUMMY_ACCOUNTID, TestSig(DUMMY_ACCOUNTID, (Compact::from(DUMMY_ACCOUNTID), vec![0u8;0], Era::mortal(32, 42), 42u64).encode()), Era::mortal(32, 42));
|
||||
assert!(ux.is_signed().unwrap_or(false));
|
||||
assert_eq!(<Ex as Checkable<TestContext>>::check(ux, &TestContext), Ok(CEx { signed: Some((DUMMY_ACCOUNTID, 0)), function: vec![0u8;0] }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn later_mortal_signed_check_should_work() {
|
||||
let ux = Ex::new_signed(0, vec![0u8;0], DUMMY_ACCOUNTID, TestSig(DUMMY_ACCOUNTID, (Compact::from(DUMMY_ACCOUNTID), vec![0u8;0], Era::mortal(32, 11), 11u64).encode()), Era::mortal(32, 11));
|
||||
assert!(ux.is_signed().unwrap_or(false));
|
||||
assert_eq!(<Ex as Checkable<TestContext>>::check(ux, &TestContext), Ok(CEx { signed: Some((DUMMY_ACCOUNTID, 0)), function: vec![0u8;0] }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn too_late_mortal_signed_check_should_fail() {
|
||||
let ux = Ex::new_signed(0, vec![0u8;0], DUMMY_ACCOUNTID, TestSig(DUMMY_ACCOUNTID, (DUMMY_ACCOUNTID, vec![0u8;0], Era::mortal(32, 10), 10u64).encode()), Era::mortal(32, 10));
|
||||
assert!(ux.is_signed().unwrap_or(false));
|
||||
assert_eq!(<Ex as Checkable<TestContext>>::check(ux, &TestContext), Err(crate::BAD_SIGNATURE));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn too_early_mortal_signed_check_should_fail() {
|
||||
let ux = Ex::new_signed(0, vec![0u8;0], DUMMY_ACCOUNTID, TestSig(DUMMY_ACCOUNTID, (DUMMY_ACCOUNTID, vec![0u8;0], Era::mortal(32, 43), 43u64).encode()), Era::mortal(32, 43));
|
||||
assert!(ux.is_signed().unwrap_or(false));
|
||||
assert_eq!(<Ex as Checkable<TestContext>>::check(ux, &TestContext), Err(crate::BAD_SIGNATURE));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encoding_matches_vec() {
|
||||
let ex = Ex::new_unsigned(vec![0u8;0]);
|
||||
let encoded = ex.encode();
|
||||
let decoded = Ex::decode(&mut encoded.as_slice()).unwrap();
|
||||
assert_eq!(decoded, ex);
|
||||
let as_vec: Vec<u8> = Decode::decode(&mut encoded.as_slice()).unwrap();
|
||||
assert_eq!(as_vec.encode(), encoded);
|
||||
}
|
||||
}
|
||||
@@ -1,313 +0,0 @@
|
||||
// Copyright 2017-2019 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Generic implementation of an unchecked (pre-verification) extrinsic.
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use std::fmt;
|
||||
|
||||
use rstd::prelude::*;
|
||||
use runtime_io::blake2_256;
|
||||
use crate::codec::{Decode, Encode, Input};
|
||||
use crate::traits::{
|
||||
self, Member, SimpleArithmetic, MaybeDisplay, CurrentHeight, BlockNumberToHash,
|
||||
Lookup, Checkable, Extrinsic, SaturatedConversion
|
||||
};
|
||||
use super::{CheckedExtrinsic, Era};
|
||||
|
||||
const TRANSACTION_VERSION: u8 = 1;
|
||||
|
||||
/// A extrinsic right from the external world. This is unchecked and so
|
||||
/// can contain a signature.
|
||||
#[derive(PartialEq, Eq, Clone)]
|
||||
pub struct UncheckedMortalExtrinsic<Address, Index, Call, Signature> {
|
||||
/// The signature, address, number of extrinsics have come before from
|
||||
/// the same signer and an era describing the longevity of this transaction,
|
||||
/// if this is a signed extrinsic.
|
||||
pub signature: Option<(Address, Signature, Index, Era)>,
|
||||
/// The function that should be called.
|
||||
pub function: Call,
|
||||
}
|
||||
|
||||
impl<Address, Index, Call, Signature> UncheckedMortalExtrinsic<Address, Index, Call, Signature> {
|
||||
/// New instance of a signed extrinsic aka "transaction".
|
||||
pub fn new_signed(index: Index, function: Call, signed: Address, signature: Signature, era: Era) -> Self {
|
||||
UncheckedMortalExtrinsic {
|
||||
signature: Some((signed, signature, index, era)),
|
||||
function,
|
||||
}
|
||||
}
|
||||
|
||||
/// New instance of an unsigned extrinsic aka "inherent".
|
||||
pub fn new_unsigned(function: Call) -> Self {
|
||||
UncheckedMortalExtrinsic {
|
||||
signature: None,
|
||||
function,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Address: Encode, Index: Encode, Call: Encode, Signature: Encode> Extrinsic for UncheckedMortalExtrinsic<Address, Index, Call, Signature> {
|
||||
type Call = Call;
|
||||
|
||||
fn is_signed(&self) -> Option<bool> {
|
||||
Some(self.signature.is_some())
|
||||
}
|
||||
|
||||
fn new_unsigned(call: Self::Call) -> Option<Self> {
|
||||
Some(UncheckedMortalExtrinsic::new_unsigned(call))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Address, AccountId, Index, Call, Signature, Context, Hash, BlockNumber> Checkable<Context>
|
||||
for UncheckedMortalExtrinsic<Address, Index, Call, Signature>
|
||||
where
|
||||
Address: Member + MaybeDisplay,
|
||||
Index: Encode + Member + MaybeDisplay + SimpleArithmetic,
|
||||
Call: Encode + Member,
|
||||
Signature: Member + traits::Verify<Signer=AccountId>,
|
||||
AccountId: Member + MaybeDisplay,
|
||||
BlockNumber: SimpleArithmetic,
|
||||
Hash: Encode,
|
||||
Context: Lookup<Source=Address, Target=AccountId>
|
||||
+ CurrentHeight<BlockNumber=BlockNumber>
|
||||
+ BlockNumberToHash<BlockNumber=BlockNumber, Hash=Hash>,
|
||||
{
|
||||
type Checked = CheckedExtrinsic<AccountId, Index, Call>;
|
||||
|
||||
fn check(self, context: &Context) -> Result<Self::Checked, &'static str> {
|
||||
Ok(match self.signature {
|
||||
Some((signed, signature, index, era)) => {
|
||||
let current_u64 = context.current_height().saturated_into::<u64>();
|
||||
let h = context.block_number_to_hash(era.birth(current_u64).saturated_into())
|
||||
.ok_or("transaction birth block ancient")?;
|
||||
let signed = context.lookup(signed)?;
|
||||
let raw_payload = (index, self.function, era, h);
|
||||
|
||||
if !raw_payload.using_encoded(|payload| {
|
||||
if payload.len() > 256 {
|
||||
signature.verify(&blake2_256(payload)[..], &signed)
|
||||
} else {
|
||||
signature.verify(payload, &signed)
|
||||
}
|
||||
}) {
|
||||
return Err(crate::BAD_SIGNATURE)
|
||||
}
|
||||
CheckedExtrinsic {
|
||||
signed: Some((signed, raw_payload.0)),
|
||||
function: raw_payload.1,
|
||||
}
|
||||
}
|
||||
None => CheckedExtrinsic {
|
||||
signed: None,
|
||||
function: self.function,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<Address, Index, Call, Signature> Decode
|
||||
for UncheckedMortalExtrinsic<Address, Index, Call, Signature>
|
||||
where
|
||||
Address: Decode,
|
||||
Signature: Decode,
|
||||
Index: Decode,
|
||||
Call: Decode,
|
||||
{
|
||||
fn decode<I: Input>(input: &mut I) -> Option<Self> {
|
||||
// This is a little more complicated than usual since the binary format must be compatible
|
||||
// with substrate's generic `Vec<u8>` type. Basically this just means accepting that there
|
||||
// will be a prefix of vector length (we don't need
|
||||
// to use this).
|
||||
let _length_do_not_remove_me_see_above: Vec<()> = Decode::decode(input)?;
|
||||
|
||||
let version = input.read_byte()?;
|
||||
|
||||
let is_signed = version & 0b1000_0000 != 0;
|
||||
let version = version & 0b0111_1111;
|
||||
if version != TRANSACTION_VERSION {
|
||||
return None
|
||||
}
|
||||
|
||||
Some(UncheckedMortalExtrinsic {
|
||||
signature: if is_signed { Some(Decode::decode(input)?) } else { None },
|
||||
function: Decode::decode(input)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<Address, Index, Call, Signature> Encode
|
||||
for UncheckedMortalExtrinsic<Address, Index, Call, Signature>
|
||||
where
|
||||
Address: Encode,
|
||||
Signature: Encode,
|
||||
Index: Encode,
|
||||
Call: Encode,
|
||||
{
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
super::encode_with_vec_prefix::<Self, _>(|v| {
|
||||
// 1 byte version id.
|
||||
match self.signature.as_ref() {
|
||||
Some(s) => {
|
||||
v.push(TRANSACTION_VERSION | 0b1000_0000);
|
||||
s.encode_to(v);
|
||||
}
|
||||
None => {
|
||||
v.push(TRANSACTION_VERSION & 0b0111_1111);
|
||||
}
|
||||
}
|
||||
self.function.encode_to(v);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<Address: Encode, Index: Encode, Signature: Encode, Call: Encode> serde::Serialize
|
||||
for UncheckedMortalExtrinsic<Address, Index, Call, Signature>
|
||||
{
|
||||
fn serialize<S>(&self, seq: S) -> Result<S::Ok, S::Error> where S: ::serde::Serializer {
|
||||
self.using_encoded(|bytes| seq.serialize_bytes(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<Address, Index, Call, Signature> fmt::Debug for UncheckedMortalExtrinsic<Address, Index, Call, Signature> where
|
||||
Address: fmt::Debug,
|
||||
Index: fmt::Debug,
|
||||
Call: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "UncheckedMortalExtrinsic({:?}, {:?})", self.signature.as_ref().map(|x| (&x.0, &x.2)), self.function)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use runtime_io::blake2_256;
|
||||
use crate::codec::{Encode, Decode};
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
struct TestContext;
|
||||
impl Lookup for TestContext {
|
||||
type Source = u64;
|
||||
type Target = u64;
|
||||
fn lookup(&self, s: u64) -> Result<u64, &'static str> { Ok(s) }
|
||||
}
|
||||
impl CurrentHeight for TestContext {
|
||||
type BlockNumber = u64;
|
||||
fn current_height(&self) -> u64 { 42 }
|
||||
}
|
||||
impl BlockNumberToHash for TestContext {
|
||||
type BlockNumber = u64;
|
||||
type Hash = u64;
|
||||
fn block_number_to_hash(&self, n: u64) -> Option<u64> { Some(n) }
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Encode, Decode)]
|
||||
struct TestSig(u64, Vec<u8>);
|
||||
impl traits::Verify for TestSig {
|
||||
type Signer = u64;
|
||||
fn verify<L: traits::Lazy<[u8]>>(&self, mut msg: L, signer: &Self::Signer) -> bool {
|
||||
*signer == self.0 && msg.get() == &self.1[..]
|
||||
}
|
||||
}
|
||||
|
||||
const DUMMY_ACCOUNTID: u64 = 0;
|
||||
|
||||
type Ex = UncheckedMortalExtrinsic<u64, u64, Vec<u8>, TestSig>;
|
||||
type CEx = CheckedExtrinsic<u64, u64, Vec<u8>>;
|
||||
|
||||
#[test]
|
||||
fn unsigned_codec_should_work() {
|
||||
let ux = Ex::new_unsigned(vec![0u8;0]);
|
||||
let encoded = ux.encode();
|
||||
assert_eq!(Ex::decode(&mut &encoded[..]), Some(ux));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signed_codec_should_work() {
|
||||
let ux = Ex::new_signed(0, vec![0u8;0], DUMMY_ACCOUNTID, TestSig(DUMMY_ACCOUNTID, (DUMMY_ACCOUNTID, vec![0u8;0], Era::immortal(), 0u64).encode()), Era::immortal());
|
||||
let encoded = ux.encode();
|
||||
assert_eq!(Ex::decode(&mut &encoded[..]), Some(ux));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn large_signed_codec_should_work() {
|
||||
let ux = Ex::new_signed(0, vec![0u8;0], DUMMY_ACCOUNTID, TestSig(DUMMY_ACCOUNTID, (DUMMY_ACCOUNTID, vec![0u8; 257], Era::immortal(), 0u64).using_encoded(blake2_256)[..].to_owned()), Era::immortal());
|
||||
let encoded = ux.encode();
|
||||
assert_eq!(Ex::decode(&mut &encoded[..]), Some(ux));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsigned_check_should_work() {
|
||||
let ux = Ex::new_unsigned(vec![0u8;0]);
|
||||
assert!(!ux.is_signed().unwrap_or(false));
|
||||
assert!(<Ex as Checkable<TestContext>>::check(ux, &TestContext).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn badly_signed_check_should_fail() {
|
||||
let ux = Ex::new_signed(0, vec![0u8;0], DUMMY_ACCOUNTID, TestSig(DUMMY_ACCOUNTID, vec![0u8]), Era::immortal());
|
||||
assert!(ux.is_signed().unwrap_or(false));
|
||||
assert_eq!(<Ex as Checkable<TestContext>>::check(ux, &TestContext), Err(crate::BAD_SIGNATURE));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn immortal_signed_check_should_work() {
|
||||
let ux = Ex::new_signed(0, vec![0u8;0], DUMMY_ACCOUNTID, TestSig(DUMMY_ACCOUNTID, (DUMMY_ACCOUNTID, vec![0u8;0], Era::immortal(), 0u64).encode()), Era::immortal());
|
||||
assert!(ux.is_signed().unwrap_or(false));
|
||||
assert_eq!(<Ex as Checkable<TestContext>>::check(ux, &TestContext), Ok(CEx { signed: Some((DUMMY_ACCOUNTID, 0)), function: vec![0u8;0] }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mortal_signed_check_should_work() {
|
||||
let ux = Ex::new_signed(0, vec![0u8;0], DUMMY_ACCOUNTID, TestSig(DUMMY_ACCOUNTID, (DUMMY_ACCOUNTID, vec![0u8;0], Era::mortal(32, 42), 42u64).encode()), Era::mortal(32, 42));
|
||||
assert!(ux.is_signed().unwrap_or(false));
|
||||
assert_eq!(<Ex as Checkable<TestContext>>::check(ux, &TestContext), Ok(CEx { signed: Some((DUMMY_ACCOUNTID, 0)), function: vec![0u8;0] }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn later_mortal_signed_check_should_work() {
|
||||
let ux = Ex::new_signed(0, vec![0u8;0], DUMMY_ACCOUNTID, TestSig(DUMMY_ACCOUNTID, (DUMMY_ACCOUNTID, vec![0u8;0], Era::mortal(32, 11), 11u64).encode()), Era::mortal(32, 11));
|
||||
assert!(ux.is_signed().unwrap_or(false));
|
||||
assert_eq!(<Ex as Checkable<TestContext>>::check(ux, &TestContext), Ok(CEx { signed: Some((DUMMY_ACCOUNTID, 0)), function: vec![0u8;0] }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn too_late_mortal_signed_check_should_fail() {
|
||||
let ux = Ex::new_signed(0, vec![0u8;0], DUMMY_ACCOUNTID, TestSig(DUMMY_ACCOUNTID, (DUMMY_ACCOUNTID, vec![0u8;0], Era::mortal(32, 10), 10u64).encode()), Era::mortal(32, 10));
|
||||
assert!(ux.is_signed().unwrap_or(false));
|
||||
assert_eq!(<Ex as Checkable<TestContext>>::check(ux, &TestContext), Err(crate::BAD_SIGNATURE));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn too_early_mortal_signed_check_should_fail() {
|
||||
let ux = Ex::new_signed(0, vec![0u8;0], DUMMY_ACCOUNTID, TestSig(DUMMY_ACCOUNTID, (DUMMY_ACCOUNTID, vec![0u8;0], Era::mortal(32, 43), 43u64).encode()), Era::mortal(32, 43));
|
||||
assert!(ux.is_signed().unwrap_or(false));
|
||||
assert_eq!(<Ex as Checkable<TestContext>>::check(ux, &TestContext), Err(crate::BAD_SIGNATURE));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encoding_matches_vec() {
|
||||
let ex = Ex::new_unsigned(vec![0u8;0]);
|
||||
let encoded = ex.encode();
|
||||
let decoded = Ex::decode(&mut encoded.as_slice()).unwrap();
|
||||
assert_eq!(decoded, ex);
|
||||
let as_vec: Vec<u8> = Decode::decode(&mut encoded.as_slice()).unwrap();
|
||||
assert_eq!(as_vec.encode(), encoded);
|
||||
}
|
||||
}
|
||||
@@ -19,12 +19,16 @@
|
||||
use serde::{Serialize, Serializer, Deserialize, de::Error as DeError, Deserializer};
|
||||
use std::{fmt::Debug, ops::Deref, fmt};
|
||||
use crate::codec::{Codec, Encode, Decode};
|
||||
use crate::traits::{self, Checkable, Applyable, BlakeTwo256, OpaqueKeys, TypedKey};
|
||||
use crate::traits::{
|
||||
self, Checkable, Applyable, BlakeTwo256, OpaqueKeys, TypedKey, DispatchError, DispatchResult,
|
||||
ValidateUnsigned, SignedExtension, Dispatchable,
|
||||
};
|
||||
use crate::{generic, KeyTypeId};
|
||||
use crate::weights::{Weighable, Weight};
|
||||
use crate::weights::{GetDispatchInfo, DispatchInfo};
|
||||
pub use substrate_primitives::H256;
|
||||
use substrate_primitives::U256;
|
||||
use substrate_primitives::ed25519::{Public as AuthorityId};
|
||||
use crate::transaction_validity::TransactionValidity;
|
||||
|
||||
/// Authority Id
|
||||
#[derive(Default, PartialEq, Eq, Clone, Encode, Decode, Debug)]
|
||||
@@ -204,52 +208,82 @@ impl<'a, Xt> Deserialize<'a> for Block<Xt> where Block<Xt>: Decode {
|
||||
}
|
||||
}
|
||||
|
||||
/// Test transaction, tuple of (sender, index, call)
|
||||
/// Test transaction, tuple of (sender, call, signed_extra)
|
||||
/// with index only used if sender is some.
|
||||
///
|
||||
/// If sender is some then the transaction is signed otherwise it is unsigned.
|
||||
#[derive(PartialEq, Eq, Clone, Encode, Decode)]
|
||||
pub struct TestXt<Call>(pub Option<u64>, pub u64, pub Call);
|
||||
pub struct TestXt<Call, Extra>(pub Option<(u64, Extra)>, pub Call);
|
||||
|
||||
impl<Call> Serialize for TestXt<Call> where TestXt<Call>: Encode
|
||||
{
|
||||
impl<Call, Extra> Serialize for TestXt<Call, Extra> where TestXt<Call, Extra>: Encode {
|
||||
fn serialize<S>(&self, seq: S) -> Result<S::Ok, S::Error> where S: Serializer {
|
||||
self.using_encoded(|bytes| seq.serialize_bytes(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Call> Debug for TestXt<Call> {
|
||||
impl<Call, Extra> Debug for TestXt<Call, Extra> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "TestXt({:?}, {:?})", self.0, self.1)
|
||||
write!(f, "TestXt({:?}, ...)", self.0.as_ref().map(|x| &x.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Call: Codec + Sync + Send, Context> Checkable<Context> for TestXt<Call> {
|
||||
impl<Call: Codec + Sync + Send, Context, Extra> Checkable<Context> for TestXt<Call, Extra> {
|
||||
type Checked = Self;
|
||||
fn check(self, _: &Context) -> Result<Self::Checked, &'static str> { Ok(self) }
|
||||
}
|
||||
impl<Call: Codec + Sync + Send> traits::Extrinsic for TestXt<Call> {
|
||||
impl<Call: Codec + Sync + Send, Extra> traits::Extrinsic for TestXt<Call, Extra> {
|
||||
type Call = Call;
|
||||
|
||||
fn is_signed(&self) -> Option<bool> {
|
||||
Some(self.0.is_some())
|
||||
}
|
||||
|
||||
fn new_unsigned(_c: Call) -> Option<Self> {
|
||||
None
|
||||
}
|
||||
}
|
||||
impl<Call> Applyable for TestXt<Call> where
|
||||
Call: 'static + Sized + Send + Sync + Clone + Eq + Codec + Debug,
|
||||
|
||||
impl<Origin, Call, Extra> Applyable for TestXt<Call, Extra> where
|
||||
Call: 'static + Sized + Send + Sync + Clone + Eq + Codec + Debug + Dispatchable<Origin=Origin>,
|
||||
Extra: SignedExtension<AccountId=u64>,
|
||||
Origin: From<Option<u64>>
|
||||
{
|
||||
type AccountId = u64;
|
||||
type Index = u64;
|
||||
type Call = Call;
|
||||
fn sender(&self) -> Option<&u64> { self.0.as_ref() }
|
||||
fn index(&self) -> Option<&u64> { self.0.as_ref().map(|_| &self.1) }
|
||||
fn deconstruct(self) -> (Self::Call, Option<Self::AccountId>) {
|
||||
(self.2, self.0)
|
||||
|
||||
fn sender(&self) -> Option<&u64> { self.0.as_ref().map(|x| &x.0) }
|
||||
|
||||
/// Checks to see if this is a valid *transaction*. It returns information on it if so.
|
||||
fn validate<U: ValidateUnsigned<Call=Self::Call>>(&self,
|
||||
_info: DispatchInfo,
|
||||
_len: usize,
|
||||
) -> TransactionValidity {
|
||||
TransactionValidity::Valid(Default::default())
|
||||
}
|
||||
|
||||
/// Executes all necessary logic needed prior to dispatch and deconstructs into function call,
|
||||
/// index and sender.
|
||||
fn dispatch(self,
|
||||
info: DispatchInfo,
|
||||
len: usize,
|
||||
) -> Result<DispatchResult, DispatchError> {
|
||||
let maybe_who = if let Some((who, extra)) = self.0 {
|
||||
Extra::pre_dispatch(extra, &who, info, len)?;
|
||||
Some(who)
|
||||
} else {
|
||||
Extra::pre_dispatch_unsigned(info, len)?;
|
||||
None
|
||||
};
|
||||
Ok(self.1.dispatch(maybe_who.into()))
|
||||
}
|
||||
}
|
||||
impl<Call> Weighable for TestXt<Call> {
|
||||
fn weight(&self, len: usize) -> Weight {
|
||||
|
||||
impl<Call: Encode, Extra: Encode> GetDispatchInfo for TestXt<Call, Extra> {
|
||||
fn get_dispatch_info(&self) -> DispatchInfo {
|
||||
// for testing: weight == size.
|
||||
len as Weight
|
||||
DispatchInfo {
|
||||
weight: self.encode().len() as u32,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,9 @@ use runtime_io;
|
||||
#[cfg(feature = "std")] use serde::{Serialize, Deserialize, de::DeserializeOwned};
|
||||
use substrate_primitives::{self, Hasher, Blake2Hasher};
|
||||
use crate::codec::{Codec, Encode, Decode, HasCompact};
|
||||
use crate::transaction_validity::TransactionValidity;
|
||||
use crate::transaction_validity::{ValidTransaction, TransactionValidity};
|
||||
use crate::generic::{Digest, DigestItem};
|
||||
use crate::weights::DispatchInfo;
|
||||
pub use substrate_primitives::crypto::TypedKey;
|
||||
pub use integer_sqrt::IntegerSquareRoot;
|
||||
pub use num_traits::{
|
||||
@@ -716,7 +717,8 @@ pub trait Extrinsic: Sized {
|
||||
/// If no information are available about signed/unsigned, `None` should be returned.
|
||||
fn is_signed(&self) -> Option<bool> { None }
|
||||
|
||||
/// New instance of an unsigned extrinsic aka "inherent".
|
||||
/// New instance of an unsigned extrinsic aka "inherent". `None` if this is an opaque
|
||||
/// extrinsic type.
|
||||
fn new_unsigned(_call: Self::Call) -> Option<Self> { None }
|
||||
}
|
||||
|
||||
@@ -761,6 +763,184 @@ impl<T: BlindCheckable, Context> Checkable<Context> for T {
|
||||
}
|
||||
}
|
||||
|
||||
/// An abstract error concerning an attempt to verify, check or dispatch the transaction. This
|
||||
/// cannot be more concrete because it's designed to work reasonably well over a broad range of
|
||||
/// possible transaction types.
|
||||
#[cfg_attr(feature = "std", derive(Debug))]
|
||||
pub enum DispatchError {
|
||||
/// General error to do with the inability to pay some fees (e.g. account balance too low).
|
||||
Payment,
|
||||
|
||||
/// General error to do with the permissions of the sender.
|
||||
NoPermission,
|
||||
|
||||
/// General error to do with the state of the system in general.
|
||||
BadState,
|
||||
|
||||
/// General error to do with the transaction being outdated (e.g. nonce too low).
|
||||
Stale,
|
||||
|
||||
/// General error to do with the transaction not yet being valid (e.g. nonce too high).
|
||||
Future,
|
||||
|
||||
/// General error to do with the transaction's proofs (e.g. signature).
|
||||
BadProof,
|
||||
|
||||
/* /// General error to do with actually executing the dispatched logic.
|
||||
User(&'static str),*/
|
||||
}
|
||||
|
||||
impl From<DispatchError> for i8 {
|
||||
fn from(e: DispatchError) -> i8 {
|
||||
match e {
|
||||
DispatchError::Payment => -64,
|
||||
DispatchError::NoPermission => -65,
|
||||
DispatchError::BadState => -66,
|
||||
DispatchError::Stale => -67,
|
||||
DispatchError::Future => -68,
|
||||
DispatchError::BadProof => -69,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of a module function call; either nothing (functions are only called for "side effects")
|
||||
/// or an error message.
|
||||
pub type DispatchResult = result::Result<(), &'static str>;
|
||||
|
||||
/// A lazy call (module function and argument values) that can be executed via its `dispatch`
|
||||
/// method.
|
||||
pub trait Dispatchable {
|
||||
/// Every function call from your runtime has an origin, which specifies where the extrinsic was
|
||||
/// generated from. In the case of a signed extrinsic (transaction), the origin contains an
|
||||
/// identifier for the caller. The origin can be empty in the case of an inherent extrinsic.
|
||||
type Origin;
|
||||
/// ...
|
||||
type Trait;
|
||||
/// Actually dispatch this call and result the result of it.
|
||||
fn dispatch(self, origin: Self::Origin) -> DispatchResult;
|
||||
}
|
||||
|
||||
/// Means by which a transaction may be extended. This type embodies both the data and the logic
|
||||
/// that should be additionally associated with the transaction. It should be plain old data.
|
||||
pub trait SignedExtension:
|
||||
Codec + MaybeDebug + Sync + Send + Clone + Eq + PartialEq
|
||||
{
|
||||
/// The type which encodes the sender identity.
|
||||
type AccountId;
|
||||
|
||||
/// Any additional data that will go into the signed payload. This may be created dynamically
|
||||
/// from the transaction using the `additional_signed` function.
|
||||
type AdditionalSigned: Encode;
|
||||
|
||||
/// Construct any additional data that should be in the signed payload of the transaction. Can
|
||||
/// also perform any pre-signature-verification checks and return an error if needed.
|
||||
fn additional_signed(&self) -> Result<Self::AdditionalSigned, &'static str>;
|
||||
|
||||
/// Validate a signed transaction for the transaction queue.
|
||||
fn validate(
|
||||
&self,
|
||||
_who: &Self::AccountId,
|
||||
_info: DispatchInfo,
|
||||
_len: usize,
|
||||
) -> Result<ValidTransaction, DispatchError> { Ok(Default::default()) }
|
||||
|
||||
/// Do any pre-flight stuff for a signed transaction.
|
||||
fn pre_dispatch(
|
||||
self,
|
||||
who: &Self::AccountId,
|
||||
info: DispatchInfo,
|
||||
len: usize,
|
||||
) -> Result<(), DispatchError> { self.validate(who, info, len).map(|_| ()) }
|
||||
|
||||
/// Validate an unsigned transaction for the transaction queue. Normally the default
|
||||
/// implementation is fine since `ValidateUnsigned` is a better way of recognising and
|
||||
/// validating unsigned transactions.
|
||||
fn validate_unsigned(
|
||||
_info: DispatchInfo,
|
||||
_len: usize,
|
||||
) -> Result<ValidTransaction, DispatchError> { Ok(Default::default()) }
|
||||
|
||||
/// Do any pre-flight stuff for a unsigned transaction.
|
||||
fn pre_dispatch_unsigned(
|
||||
info: DispatchInfo,
|
||||
len: usize,
|
||||
) -> Result<(), DispatchError> { Self::validate_unsigned(info, len).map(|_| ()) }
|
||||
}
|
||||
|
||||
macro_rules! tuple_impl_indexed {
|
||||
($first:ident, $($rest:ident,)+ ; $first_index:tt, $($rest_index:tt,)+) => {
|
||||
tuple_impl_indexed!([$first] [$($rest)+] ; [$first_index,] [$($rest_index,)+]);
|
||||
};
|
||||
([$($direct:ident)+] ; [$($index:tt,)+]) => {
|
||||
impl<
|
||||
AccountId,
|
||||
$($direct: SignedExtension<AccountId=AccountId>),+
|
||||
> SignedExtension for ($($direct),+,) {
|
||||
type AccountId = AccountId;
|
||||
type AdditionalSigned = ($($direct::AdditionalSigned,)+);
|
||||
fn additional_signed(&self) -> Result<Self::AdditionalSigned, &'static str> {
|
||||
Ok(( $(self.$index.additional_signed()?,)+ ))
|
||||
}
|
||||
fn validate(
|
||||
&self,
|
||||
who: &Self::AccountId,
|
||||
info: DispatchInfo,
|
||||
len: usize,
|
||||
) -> Result<ValidTransaction, DispatchError> {
|
||||
let aggregator = vec![$(<$direct as SignedExtension>::validate(&self.$index, who, info, len)?),+];
|
||||
Ok(aggregator.into_iter().fold(ValidTransaction::default(), |acc, a| acc.combine_with(a)))
|
||||
}
|
||||
fn pre_dispatch(
|
||||
self,
|
||||
who: &Self::AccountId,
|
||||
info: DispatchInfo,
|
||||
len: usize,
|
||||
) -> Result<(), DispatchError> {
|
||||
$(self.$index.pre_dispatch(who, info, len)?;)+
|
||||
Ok(())
|
||||
}
|
||||
fn validate_unsigned(
|
||||
info: DispatchInfo,
|
||||
len: usize,
|
||||
) -> Result<ValidTransaction, DispatchError> {
|
||||
let aggregator = vec![$($direct::validate_unsigned(info, len)?),+];
|
||||
Ok(aggregator.into_iter().fold(ValidTransaction::default(), |acc, a| acc.combine_with(a)))
|
||||
}
|
||||
fn pre_dispatch_unsigned(
|
||||
info: DispatchInfo,
|
||||
len: usize,
|
||||
) -> Result<(), DispatchError> {
|
||||
$($direct::pre_dispatch_unsigned(info, len)?;)+
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
([$($direct:ident)+] [] ; [$($index:tt,)+] []) => {
|
||||
tuple_impl_indexed!([$($direct)+] ; [$($index,)+]);
|
||||
};
|
||||
(
|
||||
[$($direct:ident)+] [$first:ident $($rest:ident)*]
|
||||
;
|
||||
[$($index:tt,)+] [$first_index:tt, $($rest_index:tt,)*]
|
||||
) => {
|
||||
tuple_impl_indexed!([$($direct)+] ; [$($index,)+]);
|
||||
tuple_impl_indexed!([$($direct)+ $first] [$($rest)*] ; [$($index,)+ $first_index,] [$($rest_index,)*]);
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: merge this into `tuple_impl` once codec supports `trait Codec` for longer tuple lengths. #3152
|
||||
#[allow(non_snake_case)]
|
||||
tuple_impl_indexed!(A, B, C, D, E, F, G, H, I, J, ; 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,);
|
||||
|
||||
/// Only for base bone testing when you don't care about signed extensions at all.\
|
||||
#[cfg(feature = "std")]
|
||||
impl SignedExtension for () {
|
||||
type AccountId = u64;
|
||||
type AdditionalSigned = ();
|
||||
fn additional_signed(&self) -> rstd::result::Result<(), &'static str> { Ok(()) }
|
||||
}
|
||||
|
||||
/// An "executable" piece of information, used by the standard Substrate Executive in order to
|
||||
/// enact a piece of extrinsic information by marshalling and dispatching to a named function
|
||||
/// call.
|
||||
@@ -770,16 +950,25 @@ impl<T: BlindCheckable, Context> Checkable<Context> for T {
|
||||
pub trait Applyable: Sized + Send + Sync {
|
||||
/// Id of the account that is responsible for this piece of information (sender).
|
||||
type AccountId: Member + MaybeDisplay;
|
||||
/// Index allowing to disambiguate other `Applyable`s from the same `AccountId`.
|
||||
type Index: Member + MaybeDisplay + SimpleArithmetic;
|
||||
/// Function call.
|
||||
type Call: Member;
|
||||
/// Returns a reference to the index if any.
|
||||
fn index(&self) -> Option<&Self::Index>;
|
||||
|
||||
/// Type by which we can dispatch. Restricts the UnsignedValidator type.
|
||||
type Call;
|
||||
|
||||
/// Returns a reference to the sender if any.
|
||||
fn sender(&self) -> Option<&Self::AccountId>;
|
||||
/// Deconstructs into function call and sender.
|
||||
fn deconstruct(self) -> (Self::Call, Option<Self::AccountId>);
|
||||
|
||||
/// Checks to see if this is a valid *transaction*. It returns information on it if so.
|
||||
fn validate<V: ValidateUnsigned<Call=Self::Call>>(&self,
|
||||
info: DispatchInfo,
|
||||
len: usize,
|
||||
) -> TransactionValidity;
|
||||
|
||||
/// Executes all necessary logic needed prior to dispatch and deconstructs into function call,
|
||||
/// index and sender.
|
||||
fn dispatch(self,
|
||||
info: DispatchInfo,
|
||||
len: usize,
|
||||
) -> Result<DispatchResult, DispatchError>;
|
||||
}
|
||||
|
||||
/// Auxiliary wrapper that holds an api instance and binds it to the given lifetime.
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
use rstd::prelude::*;
|
||||
use crate::codec::{Encode, Decode};
|
||||
use crate::traits::DispatchError;
|
||||
|
||||
/// Priority for a transaction. Additive. Higher is better.
|
||||
pub type TransactionPriority = u64;
|
||||
@@ -36,40 +37,81 @@ pub enum TransactionValidity {
|
||||
/// Transaction is invalid. Details are described by the error code.
|
||||
Invalid(i8),
|
||||
/// Transaction is valid.
|
||||
Valid {
|
||||
/// Priority of the transaction.
|
||||
///
|
||||
/// Priority determines the ordering of two transactions that have all
|
||||
/// their dependencies (required tags) satisfied.
|
||||
priority: TransactionPriority,
|
||||
/// Transaction dependencies
|
||||
///
|
||||
/// A non-empty list signifies that some other transactions which provide
|
||||
/// given tags are required to be included before that one.
|
||||
requires: Vec<TransactionTag>,
|
||||
/// Provided tags
|
||||
///
|
||||
/// A list of tags this transaction provides. Successfully importing the transaction
|
||||
/// will enable other transactions that depend on (require) those tags to be included as well.
|
||||
/// Provided and required tags allow Substrate to build a dependency graph of transactions
|
||||
/// and import them in the right (linear) order.
|
||||
provides: Vec<TransactionTag>,
|
||||
/// Transaction longevity
|
||||
///
|
||||
/// Longevity describes minimum number of blocks the validity is correct.
|
||||
/// After this period transaction should be removed from the pool or revalidated.
|
||||
longevity: TransactionLongevity,
|
||||
/// A flag indicating if the transaction should be propagated to other peers.
|
||||
///
|
||||
/// By setting `false` here the transaction will still be considered for
|
||||
/// including in blocks that are authored on the current node, but will
|
||||
/// never be sent to other peers.
|
||||
propagate: bool,
|
||||
},
|
||||
Valid(ValidTransaction),
|
||||
/// Transaction validity can't be determined.
|
||||
Unknown(i8),
|
||||
}
|
||||
|
||||
impl From<Result<ValidTransaction, DispatchError>> for TransactionValidity {
|
||||
fn from(r: Result<ValidTransaction, DispatchError>) -> Self {
|
||||
match r {
|
||||
Ok(v) => TransactionValidity::Valid(v),
|
||||
Err(e) => TransactionValidity::Invalid(e.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Information concerning a valid transaction.
|
||||
#[derive(Clone, PartialEq, Eq, Encode)]
|
||||
#[cfg_attr(feature = "std", derive(Debug))]
|
||||
pub struct ValidTransaction {
|
||||
/// Priority of the transaction.
|
||||
///
|
||||
/// Priority determines the ordering of two transactions that have all
|
||||
/// their dependencies (required tags) satisfied.
|
||||
pub priority: TransactionPriority,
|
||||
/// Transaction dependencies
|
||||
///
|
||||
/// A non-empty list signifies that some other transactions which provide
|
||||
/// given tags are required to be included before that one.
|
||||
pub requires: Vec<TransactionTag>,
|
||||
/// Provided tags
|
||||
///
|
||||
/// A list of tags this transaction provides. Successfully importing the transaction
|
||||
/// will enable other transactions that depend on (require) those tags to be included as well.
|
||||
/// Provided and required tags allow Substrate to build a dependency graph of transactions
|
||||
/// and import them in the right (linear) order.
|
||||
pub provides: Vec<TransactionTag>,
|
||||
/// Transaction longevity
|
||||
///
|
||||
/// Longevity describes minimum number of blocks the validity is correct.
|
||||
/// After this period transaction should be removed from the pool or revalidated.
|
||||
pub longevity: TransactionLongevity,
|
||||
/// A flag indicating if the transaction should be propagated to other peers.
|
||||
///
|
||||
/// By setting `false` here the transaction will still be considered for
|
||||
/// including in blocks that are authored on the current node, but will
|
||||
/// never be sent to other peers.
|
||||
pub propagate: bool,
|
||||
}
|
||||
|
||||
impl Default for ValidTransaction {
|
||||
fn default() -> Self {
|
||||
ValidTransaction {
|
||||
priority: 0,
|
||||
requires: vec![],
|
||||
provides: vec![],
|
||||
longevity: TransactionLongevity::max_value(),
|
||||
propagate: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ValidTransaction {
|
||||
/// Combine two instances into one, as a best effort. This will take the superset of each of the
|
||||
/// `provides` and `requires` tags, it will sum the priorities, take the minimum longevity and
|
||||
/// the logic *And* of the propagate flags.
|
||||
pub fn combine_with(mut self, mut other: ValidTransaction) -> Self {
|
||||
ValidTransaction {
|
||||
priority: self.priority.saturating_add(other.priority),
|
||||
requires: { self.requires.append(&mut other.requires); self.requires },
|
||||
provides: { self.provides.append(&mut other.provides); self.provides },
|
||||
longevity: self.longevity.min(other.longevity),
|
||||
propagate: self.propagate && other.propagate,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for TransactionValidity {
|
||||
fn decode<I: crate::codec::Input>(value: &mut I) -> Option<Self> {
|
||||
match value.read_byte()? {
|
||||
@@ -81,9 +123,9 @@ impl Decode for TransactionValidity {
|
||||
let longevity = TransactionLongevity::decode(value)?;
|
||||
let propagate = bool::decode(value).unwrap_or(true);
|
||||
|
||||
Some(TransactionValidity::Valid {
|
||||
Some(TransactionValidity::Valid(ValidTransaction {
|
||||
priority, requires, provides, longevity, propagate,
|
||||
})
|
||||
}))
|
||||
},
|
||||
2 => Some(TransactionValidity::Unknown(i8::decode(value)?)),
|
||||
_ => None,
|
||||
@@ -101,24 +143,24 @@ mod tests {
|
||||
1, 5, 0, 0, 0, 0, 0, 0, 0, 4, 16, 1, 2, 3, 4, 4, 12, 4, 5, 6, 42, 0, 0, 0, 0, 0, 0, 0
|
||||
];
|
||||
|
||||
assert_eq!(TransactionValidity::decode(&mut &*old_encoding), Some(TransactionValidity::Valid {
|
||||
assert_eq!(TransactionValidity::decode(&mut &*old_encoding), Some(TransactionValidity::Valid(ValidTransaction {
|
||||
priority: 5,
|
||||
requires: vec![vec![1, 2, 3, 4]],
|
||||
provides: vec![vec![4, 5, 6]],
|
||||
longevity: 42,
|
||||
propagate: true,
|
||||
}));
|
||||
})));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_encode_and_decode() {
|
||||
let v = TransactionValidity::Valid {
|
||||
let v = TransactionValidity::Valid(ValidTransaction {
|
||||
priority: 5,
|
||||
requires: vec![vec![1, 2, 3, 4]],
|
||||
provides: vec![vec![4, 5, 6]],
|
||||
longevity: 42,
|
||||
propagate: false,
|
||||
};
|
||||
});
|
||||
|
||||
let encoded = v.encode();
|
||||
assert_eq!(
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
//! Primitives for transaction weighting.
|
||||
//!
|
||||
//! Each dispatch function within `decl_module!` can have an optional `#[weight = $x]` attribute.
|
||||
//! $x can be any object that implements the `Weighable` trait. By default, All transactions are
|
||||
//! annotated by `#[weight = TransactionWeight::default()]`.
|
||||
//! `$x` can be any type that implements the `ClassifyDispatch<T>` and `WeighData<T>` traits. By
|
||||
//! default, All transactions are annotated with `#[weight = SimpleDispatchInfo::default()]`.
|
||||
//!
|
||||
//! Note that the decl_module macro _cannot_ enforce this and will simply fail if an invalid struct
|
||||
//! (something that does not implement `Weighable`) is passed in.
|
||||
@@ -26,59 +26,147 @@
|
||||
use crate::{Fixed64, traits::Saturating};
|
||||
use crate::codec::{Encode, Decode};
|
||||
|
||||
/// The final type that each `#[weight = $x:expr]`'s
|
||||
/// expression must evaluate to.
|
||||
pub use crate::transaction_validity::TransactionPriority;
|
||||
use crate::traits::Bounded;
|
||||
|
||||
/// Numeric range of a transaction weight.
|
||||
pub type Weight = u32;
|
||||
|
||||
/// Maximum block saturation: 4mb
|
||||
pub const MAX_TRANSACTIONS_WEIGHT: u32 = 4 * 1024 * 1024;
|
||||
/// Target block saturation: 25% of max block saturation = 1mb
|
||||
pub const IDEAL_TRANSACTIONS_WEIGHT: u32 = MAX_TRANSACTIONS_WEIGHT / 4;
|
||||
|
||||
/// A `Call` enum (aka transaction) that can be weighted using the custom weight attribute of
|
||||
/// its dispatchable functions. Is implemented by default in the `decl_module!`.
|
||||
///
|
||||
/// Both the outer Call enum and the per-module individual ones will implement this.
|
||||
/// The outer enum simply calls the inner ones based on call type.
|
||||
pub trait Weighable {
|
||||
/// Return the weight of this call.
|
||||
/// The `len` argument is the encoded length of the transaction/call.
|
||||
fn weight(&self, len: usize) -> Weight;
|
||||
/// A generalized group of dispatch types. This is only distinguishing normal, user-triggered transactions
|
||||
/// (`Normal`) and anything beyond which serves a higher purpose to the system (`Operational`).
|
||||
#[cfg_attr(feature = "std", derive(Debug))]
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
pub enum DispatchClass {
|
||||
/// A normal dispatch.
|
||||
Normal,
|
||||
/// An operational dispatch.
|
||||
Operational,
|
||||
}
|
||||
|
||||
/// Default type used as the weight representative in a `#[weight = x]` attribute.
|
||||
///
|
||||
/// A user may pass in any other type that implements [`Weighable`]. If not, the `Default`
|
||||
/// implementation of [`TransactionWeight`] is used.
|
||||
pub enum TransactionWeight {
|
||||
/// Basic weight (base, byte).
|
||||
/// The values contained are the base weight and byte weight respectively.
|
||||
Basic(Weight, Weight),
|
||||
/// Maximum fee. This implies that this transaction _might_ get included but
|
||||
/// no more transaction can be added. This can be done by setting the
|
||||
/// implementation to _maximum block weight_.
|
||||
Max,
|
||||
/// Free. The transaction does not increase the total weight
|
||||
/// (i.e. is not included in weight calculation).
|
||||
Free,
|
||||
impl Default for DispatchClass {
|
||||
fn default() -> Self {
|
||||
DispatchClass::Normal
|
||||
}
|
||||
}
|
||||
|
||||
impl Weighable for TransactionWeight {
|
||||
fn weight(&self, len: usize) -> Weight {
|
||||
match self {
|
||||
TransactionWeight::Basic(base, byte) => base + byte * len as Weight,
|
||||
TransactionWeight::Max => 3 * 1024 * 1024,
|
||||
TransactionWeight::Free => 0,
|
||||
impl From<SimpleDispatchInfo> for DispatchClass {
|
||||
fn from(tx: SimpleDispatchInfo) -> Self {
|
||||
match tx {
|
||||
SimpleDispatchInfo::FixedOperational(_) => DispatchClass::Operational,
|
||||
SimpleDispatchInfo::MaxOperational => DispatchClass::Operational,
|
||||
SimpleDispatchInfo::FreeOperational => DispatchClass::Operational,
|
||||
|
||||
SimpleDispatchInfo::FixedNormal(_) => DispatchClass::Normal,
|
||||
SimpleDispatchInfo::MaxNormal => DispatchClass::Normal,
|
||||
SimpleDispatchInfo::FreeNormal => DispatchClass::Normal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TransactionWeight {
|
||||
/// A bundle of static information collected from the `#[weight = $x]` attributes.
|
||||
#[cfg_attr(feature = "std", derive(PartialEq, Eq, Debug))]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct DispatchInfo {
|
||||
/// Weight of this transaction.
|
||||
pub weight: Weight,
|
||||
/// Class of this transaction.
|
||||
pub class: DispatchClass,
|
||||
}
|
||||
|
||||
impl DispatchInfo {
|
||||
/// Determine if this dispatch should pay the base length-related fee or not.
|
||||
pub fn pay_length_fee(&self) -> bool {
|
||||
match self.class {
|
||||
DispatchClass::Normal => true,
|
||||
// For now we assume all operational transactions don't pay the length fee.
|
||||
DispatchClass::Operational => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A `Dispatchable` function (aka transaction) that can carry some static information along with it, using the
|
||||
/// `#[weight]` attribute.
|
||||
pub trait GetDispatchInfo {
|
||||
/// Return a `DispatchInfo`, containing relevant information of this dispatch.
|
||||
///
|
||||
/// This is done independently of its encoded size.
|
||||
fn get_dispatch_info(&self) -> DispatchInfo;
|
||||
}
|
||||
|
||||
/// Means of weighing some particular kind of data (`T`).
|
||||
pub trait WeighData<T> {
|
||||
/// Weigh the data `T` given by `target`.
|
||||
fn weigh_data(&self, target: T) -> Weight;
|
||||
}
|
||||
|
||||
/// Means of classifying a dispatchable function.
|
||||
pub trait ClassifyDispatch<T> {
|
||||
/// Classify the dispatch function based on input data `target` of type `T`.
|
||||
fn classify_dispatch(&self, target: T) -> DispatchClass;
|
||||
}
|
||||
|
||||
/// Default type used with the `#[weight = x]` attribute in a substrate chain.
|
||||
///
|
||||
/// A user may pass in any other type that implements the correct traits. If not, the `Default`
|
||||
/// implementation of [`SimpleDispatchInfo`] is used.
|
||||
///
|
||||
/// For each generalized group (`Normal` and `Operation`):
|
||||
/// - A `Fixed` variant means weight fee is charged normally and the weight is the number
|
||||
/// specified in the inner value of the variant.
|
||||
/// - A `Free` variant is equal to `::Fixed(0)`. Note that this does not guarantee inclusion.
|
||||
/// - A `Max` variant is equal to `::Fixed(Weight::max_value())`.
|
||||
///
|
||||
/// Based on the final weight value, based on the above variants:
|
||||
/// - A _weight-fee_ is deducted.
|
||||
/// - The block weight is consumed proportionally.
|
||||
///
|
||||
/// As for the generalized groups themselves:
|
||||
/// - `Normal` variants will be assigned a priority proportional to their weight. They can only
|
||||
/// consume a portion (1/4) of the maximum block resource limits.
|
||||
/// - `Operational` variants will be assigned the maximum priority. They can potentially consume
|
||||
/// the entire block resource limit.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum SimpleDispatchInfo {
|
||||
/// A normal dispatch with fixed weight.
|
||||
FixedNormal(Weight),
|
||||
/// A normal dispatch with the maximum weight.
|
||||
MaxNormal,
|
||||
/// A normal dispatch with no weight.
|
||||
FreeNormal,
|
||||
/// An operational dispatch with fixed weight.
|
||||
FixedOperational(Weight),
|
||||
/// An operational dispatch with the maximum weight.
|
||||
MaxOperational,
|
||||
/// An operational dispatch with no weight.
|
||||
FreeOperational,
|
||||
}
|
||||
|
||||
impl<T> WeighData<T> for SimpleDispatchInfo {
|
||||
fn weigh_data(&self, _: T) -> Weight {
|
||||
match self {
|
||||
SimpleDispatchInfo::FixedNormal(w) => *w,
|
||||
SimpleDispatchInfo::MaxNormal => Bounded::max_value(),
|
||||
SimpleDispatchInfo::FreeNormal => Bounded::min_value(),
|
||||
|
||||
SimpleDispatchInfo::FixedOperational(w) => *w,
|
||||
SimpleDispatchInfo::MaxOperational => Bounded::max_value(),
|
||||
SimpleDispatchInfo::FreeOperational => Bounded::min_value(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ClassifyDispatch<T> for SimpleDispatchInfo {
|
||||
fn classify_dispatch(&self, _: T) -> DispatchClass {
|
||||
DispatchClass::from(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SimpleDispatchInfo {
|
||||
fn default() -> Self {
|
||||
// This implies that the weight is currently equal to tx-size, nothing more
|
||||
// This implies that the weight is currently equal to 100, nothing more
|
||||
// for all substrate transactions that do NOT explicitly annotate weight.
|
||||
// TODO #2431 needs to be updated with proper max values.
|
||||
TransactionWeight::Basic(0, 1)
|
||||
SimpleDispatchInfo::FixedNormal(100)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ use substrate_client::{
|
||||
use runtime_primitives::{
|
||||
ApplyResult,
|
||||
create_runtime_str,
|
||||
transaction_validity::TransactionValidity,
|
||||
transaction_validity::{TransactionValidity, ValidTransaction},
|
||||
traits::{
|
||||
BlindCheckable, BlakeTwo256, Block as BlockT, Extrinsic as ExtrinsicT,
|
||||
GetNodeBlockType, GetRuntimeBlockType, Verify
|
||||
@@ -377,13 +377,13 @@ cfg_if! {
|
||||
impl client_api::TaggedTransactionQueue<Block> for Runtime {
|
||||
fn validate_transaction(utx: <Block as BlockT>::Extrinsic) -> TransactionValidity {
|
||||
if let Extrinsic::IncludeData(data) = utx {
|
||||
return TransactionValidity::Valid {
|
||||
return TransactionValidity::Valid(ValidTransaction {
|
||||
priority: data.len() as u64,
|
||||
requires: vec![],
|
||||
provides: vec![data],
|
||||
longevity: 1,
|
||||
propagate: false,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
system::validate_transaction(utx)
|
||||
@@ -518,13 +518,13 @@ cfg_if! {
|
||||
impl client_api::TaggedTransactionQueue<Block> for Runtime {
|
||||
fn validate_transaction(utx: <Block as BlockT>::Extrinsic) -> TransactionValidity {
|
||||
if let Extrinsic::IncludeData(data) = utx {
|
||||
return TransactionValidity::Valid {
|
||||
return TransactionValidity::Valid(ValidTransaction{
|
||||
priority: data.len() as u64,
|
||||
requires: vec![],
|
||||
provides: vec![data],
|
||||
longevity: 1,
|
||||
propagate: false,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
system::validate_transaction(utx)
|
||||
|
||||
@@ -23,7 +23,8 @@ use runtime_support::storage::{self, StorageValue, StorageMap};
|
||||
use runtime_support::storage_items;
|
||||
use runtime_primitives::traits::{Hash as HashT, BlakeTwo256, Header as _};
|
||||
use runtime_primitives::generic;
|
||||
use runtime_primitives::{ApplyError, ApplyOutcome, ApplyResult, transaction_validity::TransactionValidity};
|
||||
use runtime_primitives::{ApplyError, ApplyOutcome, ApplyResult};
|
||||
use runtime_primitives::transaction_validity::{TransactionValidity, ValidTransaction};
|
||||
use parity_codec::{KeyedVec, Encode};
|
||||
use super::{
|
||||
AccountId, BlockNumber, Extrinsic, Transfer, H256 as Hash, Block, Header, Digest, AuthorityId
|
||||
@@ -175,13 +176,13 @@ pub fn validate_transaction(utx: Extrinsic) -> TransactionValidity {
|
||||
p
|
||||
};
|
||||
|
||||
TransactionValidity::Valid {
|
||||
TransactionValidity::Valid(ValidTransaction {
|
||||
priority: tx.amount,
|
||||
requires,
|
||||
provides,
|
||||
longevity: 64,
|
||||
propagate: true,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Execute a transaction outside of the block execution function.
|
||||
|
||||
@@ -129,18 +129,19 @@ impl<B: ChainApi> Pool<B> {
|
||||
}
|
||||
|
||||
match self.api.validate_transaction(at, xt.clone())? {
|
||||
TransactionValidity::Valid { priority, requires, provides, longevity, propagate } => {
|
||||
TransactionValidity::Valid(validity) => {
|
||||
Ok(base::Transaction {
|
||||
data: xt,
|
||||
bytes,
|
||||
bytes
|
||||
,
|
||||
hash,
|
||||
priority,
|
||||
requires,
|
||||
provides,
|
||||
propagate,
|
||||
priority: validity.priority,
|
||||
requires: validity.requires,
|
||||
provides: validity.provides,
|
||||
propagate: validity.propagate,
|
||||
valid_till: block_number
|
||||
.saturated_into::<u64>()
|
||||
.saturating_add(longevity),
|
||||
.saturating_add(validity.longevity),
|
||||
})
|
||||
},
|
||||
TransactionValidity::Invalid(e) => {
|
||||
@@ -233,7 +234,7 @@ impl<B: ChainApi> Pool<B> {
|
||||
|
||||
for (extrinsic, existing_in_pool) in all {
|
||||
match *existing_in_pool {
|
||||
// reuse the tags for extrinsis that were found in the pool
|
||||
// reuse the tags for extrinsics that were found in the pool
|
||||
Some(ref transaction) => {
|
||||
tags.extend(transaction.provides.iter().cloned());
|
||||
},
|
||||
@@ -242,8 +243,8 @@ impl<B: ChainApi> Pool<B> {
|
||||
None => {
|
||||
let validity = self.api.validate_transaction(parent, extrinsic.clone());
|
||||
match validity {
|
||||
Ok(TransactionValidity::Valid { mut provides, .. }) => {
|
||||
tags.append(&mut provides);
|
||||
Ok(TransactionValidity::Valid(mut validity)) => {
|
||||
tags.append(&mut validity.provides);
|
||||
},
|
||||
// silently ignore invalid extrinsics,
|
||||
// cause they might just be inherent
|
||||
@@ -306,7 +307,7 @@ impl<B: ChainApi> Pool<B> {
|
||||
let hashes = status.pruned.iter().map(|tx| tx.hash.clone()).collect::<Vec<_>>();
|
||||
let results = self.submit_at(at, status.pruned.into_iter().map(|tx| tx.data.clone()))?;
|
||||
|
||||
// Collect the hashes of transactions that now became invalid (meaning that they are succesfully pruned).
|
||||
// Collect the hashes of transactions that now became invalid (meaning that they are successfully pruned).
|
||||
let hashes = results.into_iter().enumerate().filter_map(|(idx, r)| match r.map_err(error::IntoPoolError::into_pool_error) {
|
||||
Err(Ok(error::Error::InvalidTransaction(_))) => Some(hashes[idx].clone()),
|
||||
_ => None,
|
||||
@@ -451,6 +452,7 @@ fn fire_events<H, H2, Ex>(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use sr_primitives::transaction_validity::ValidTransaction;
|
||||
use futures::Stream;
|
||||
use parity_codec::Encode;
|
||||
use test_runtime::{Block, Extrinsic, Transfer, H256, AccountId};
|
||||
@@ -486,13 +488,13 @@ mod tests {
|
||||
if nonce < block_number {
|
||||
Ok(TransactionValidity::Invalid(0))
|
||||
} else {
|
||||
Ok(TransactionValidity::Valid {
|
||||
Ok(TransactionValidity::Valid(ValidTransaction {
|
||||
priority: 4,
|
||||
requires: if nonce > block_number { vec![vec![nonce as u8 - 1]] } else { vec![] },
|
||||
provides: vec![vec![nonce as u8]],
|
||||
longevity: 3,
|
||||
propagate: true,
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ use test_client::{runtime::{AccountId, Block, Hash, Index, Extrinsic, Transfer},
|
||||
use sr_primitives::{
|
||||
generic::{self, BlockId},
|
||||
traits::{Hash as HashT, BlakeTwo256},
|
||||
transaction_validity::TransactionValidity,
|
||||
transaction_validity::{TransactionValidity, ValidTransaction},
|
||||
};
|
||||
|
||||
struct TestApi;
|
||||
@@ -48,13 +48,13 @@ impl txpool::ChainApi for TestApi {
|
||||
};
|
||||
let provides = vec![vec![uxt.transfer().nonce as u8]];
|
||||
|
||||
Ok(TransactionValidity::Valid {
|
||||
Ok(TransactionValidity::Valid(ValidTransaction {
|
||||
priority: 1,
|
||||
requires,
|
||||
provides,
|
||||
longevity: 64,
|
||||
propagate: true,
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
fn block_id_to_number(&self, at: &BlockId<Self::Block>) -> error::Result<Option<txpool::NumberFor<Self>>> {
|
||||
|
||||
@@ -17,7 +17,7 @@ use primitives::bytes;
|
||||
use primitives::{ed25519, sr25519, OpaqueMetadata};
|
||||
use sr_primitives::{
|
||||
ApplyResult, transaction_validity::TransactionValidity, generic, create_runtime_str,
|
||||
traits::{self, NumberFor, BlakeTwo256, Block as BlockT, StaticLookup, Verify}
|
||||
traits::{self, NumberFor, BlakeTwo256, Block as BlockT, StaticLookup, Verify}, weights::Weight,
|
||||
};
|
||||
use client::{
|
||||
block_builder::api::{CheckInherentsResult, InherentData, self as block_builder_api},
|
||||
@@ -69,25 +69,8 @@ mod template;
|
||||
pub mod opaque {
|
||||
use super::*;
|
||||
|
||||
/// Opaque, encoded, unchecked extrinsic.
|
||||
#[derive(PartialEq, Eq, Clone, Default, Encode, Decode)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
pub struct UncheckedExtrinsic(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec<u8>);
|
||||
#[cfg(feature = "std")]
|
||||
impl std::fmt::Debug for UncheckedExtrinsic {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(fmt, "{}", primitives::hexdisplay::HexDisplay::from(&self.0))
|
||||
}
|
||||
}
|
||||
impl traits::Extrinsic for UncheckedExtrinsic {
|
||||
type Call = ();
|
||||
fn is_signed(&self) -> Option<bool> {
|
||||
None
|
||||
}
|
||||
fn new_unsigned(_call: Self::Call) -> Option<Self> {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub use sr_primitives::OpaqueExtrinsic as UncheckedExtrinsic;
|
||||
|
||||
/// Opaque block header type.
|
||||
pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
|
||||
/// Opaque block type.
|
||||
@@ -119,6 +102,8 @@ pub fn native_version() -> NativeVersion {
|
||||
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: BlockNumber = 250;
|
||||
pub const MaximumBlockWeight: Weight = 4 * 1024 * 1024;
|
||||
pub const MaximumBlockLength: u32 = 4 * 1024 * 1024;
|
||||
}
|
||||
|
||||
impl system::Trait for Runtime {
|
||||
@@ -144,6 +129,10 @@ impl system::Trait for Runtime {
|
||||
type Origin = Origin;
|
||||
/// Maximum number of block number to block hash mappings to keep (oldest pruned first).
|
||||
type BlockHashCount = BlockHashCount;
|
||||
/// Maximum weight of each block. With a default weight system of 1byte == 1weight, 4mb is ok.
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
/// Maximum size of all encoded transactions (in bytes) that are allowed in one block.
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
}
|
||||
|
||||
impl aura::Trait for Runtime {
|
||||
@@ -239,12 +228,14 @@ pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
|
||||
pub type Block = generic::Block<Header, UncheckedExtrinsic>;
|
||||
/// BlockId type as expected by this runtime.
|
||||
pub type BlockId = generic::BlockId<Block>;
|
||||
/// The SignedExtension to the basic transaction logic.
|
||||
pub type SignedExtra = (system::CheckNonce<Runtime>, system::CheckWeight<Runtime>, balances::TakeFees<Runtime>);
|
||||
/// Unchecked extrinsic type as expected by this runtime.
|
||||
pub type UncheckedExtrinsic = generic::UncheckedMortalCompactExtrinsic<Address, Nonce, Call, AccountSignature>;
|
||||
pub type UncheckedExtrinsic = generic::UncheckedExtrinsic<Address, Call, AccountSignature, SignedExtra>;
|
||||
/// Extrinsic type that has already been checked.
|
||||
pub type CheckedExtrinsic = generic::CheckedExtrinsic<AccountId, Nonce, Call>;
|
||||
pub type CheckedExtrinsic = generic::CheckedExtrinsic<AccountId, Call, SignedExtra>;
|
||||
/// Executive: handles dispatch to the various modules.
|
||||
pub type Executive = executive::Executive<Runtime, Block, Context, Balances, Runtime, AllModules>;
|
||||
pub type Executive = executive::Executive<Runtime, Block, Context, Runtime, AllModules>;
|
||||
|
||||
// Implement our runtime API endpoints. This is just a bunch of proxying.
|
||||
impl_runtime_apis! {
|
||||
|
||||
@@ -73,6 +73,7 @@ mod tests {
|
||||
use primitives::{H256, Blake2Hasher};
|
||||
use support::{impl_outer_origin, assert_ok, parameter_types};
|
||||
use sr_primitives::{traits::{BlakeTwo256, IdentityLookup}, testing::Header};
|
||||
use sr_primitives::weights::Weight;
|
||||
|
||||
impl_outer_origin! {
|
||||
pub enum Origin for Test {}
|
||||
@@ -85,6 +86,8 @@ mod tests {
|
||||
pub struct Test;
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: Weight = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
}
|
||||
impl system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
@@ -98,6 +101,8 @@ mod tests {
|
||||
type WeightMultiplierUpdate = ();
|
||||
type Event = ();
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
}
|
||||
impl Trait for Test {
|
||||
type Event = ();
|
||||
|
||||
@@ -40,6 +40,8 @@ timestamp = { package = "srml-timestamp", path = "../../srml/timestamp", default
|
||||
rand = "0.6"
|
||||
finality_tracker = { package = "srml-finality-tracker", path = "../../srml/finality-tracker", default-features = false }
|
||||
contracts = { package = "srml-contracts", path = "../../srml/contracts" }
|
||||
system = { package = "srml-system", path = "../../srml/system" }
|
||||
balances = { package = "srml-balances", path = "../../srml/balances" }
|
||||
|
||||
[dev-dependencies]
|
||||
consensus-common = { package = "substrate-consensus-common", path = "../../core/consensus/common" }
|
||||
|
||||
@@ -23,13 +23,10 @@ use rand::rngs::StdRng;
|
||||
|
||||
use parity_codec::Decode;
|
||||
use keyring::sr25519::Keyring;
|
||||
use node_primitives::Hash;
|
||||
use node_runtime::{Call, CheckedExtrinsic, UncheckedExtrinsic, BalancesCall};
|
||||
use primitives::sr25519;
|
||||
use primitives::crypto::Pair;
|
||||
use node_runtime::{Call, CheckedExtrinsic, UncheckedExtrinsic, SignedExtra, BalancesCall};
|
||||
use primitives::{sr25519, crypto::Pair};
|
||||
use parity_codec::Encode;
|
||||
use sr_primitives::generic::Era;
|
||||
use sr_primitives::traits::{Block as BlockT, Header as HeaderT};
|
||||
use sr_primitives::{generic::Era, traits::{Block as BlockT, Header as HeaderT, SignedExtension}};
|
||||
use substrate_service::ServiceFactory;
|
||||
use transaction_factory::RuntimeAdapter;
|
||||
use transaction_factory::modes::Mode;
|
||||
@@ -54,6 +51,17 @@ pub struct FactoryState<N> {
|
||||
|
||||
type Number = <<node_primitives::Block as BlockT>::Header as HeaderT>::Number;
|
||||
|
||||
impl<Number> FactoryState<Number> {
|
||||
fn build_extra(index: node_primitives::Index, phase: u64) -> node_runtime::SignedExtra {
|
||||
(
|
||||
system::CheckEra::from(Era::mortal(256, phase)),
|
||||
system::CheckNonce::from(index),
|
||||
system::CheckWeight::from(),
|
||||
balances::TakeFees::from(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl RuntimeAdapter for FactoryState<Number> {
|
||||
type AccountId = node_primitives::AccountId;
|
||||
type Balance = node_primitives::Balance;
|
||||
@@ -132,16 +140,14 @@ impl RuntimeAdapter for FactoryState<Number> {
|
||||
let phase = self.extract_phase(*prior_block_hash);
|
||||
|
||||
sign::<service::Factory, Self>(CheckedExtrinsic {
|
||||
signed: Some((sender.clone(), index)),
|
||||
signed: Some((sender.clone(), Self::build_extra(index, phase))),
|
||||
function: Call::Balances(
|
||||
BalancesCall::transfer(
|
||||
indices::address::Address::Id(
|
||||
destination.clone().into()
|
||||
),
|
||||
indices::address::Address::Id(destination.clone().into()),
|
||||
(*amount).into()
|
||||
)
|
||||
)
|
||||
}, key, &prior_block_hash, phase)
|
||||
}, key, (prior_block_hash.clone(), (), (), ()))
|
||||
}
|
||||
|
||||
fn inherent_extrinsics(&self) -> InherentData {
|
||||
@@ -229,13 +235,11 @@ fn gen_seed_bytes(seed: u64) -> [u8; 32] {
|
||||
fn sign<F: ServiceFactory, RA: RuntimeAdapter>(
|
||||
xt: CheckedExtrinsic,
|
||||
key: &sr25519::Pair,
|
||||
prior_block_hash: &Hash,
|
||||
phase: u64,
|
||||
additional_signed: <SignedExtra as SignedExtension>::AdditionalSigned,
|
||||
) -> <RA::Block as BlockT>::Extrinsic {
|
||||
let s = match xt.signed {
|
||||
Some((signed, index)) => {
|
||||
let era = Era::mortal(256, phase);
|
||||
let payload = (index.into(), xt.function, era, prior_block_hash);
|
||||
Some((signed, extra)) => {
|
||||
let payload = (xt.function, extra.clone(), additional_signed);
|
||||
let signature = payload.using_encoded(|b| {
|
||||
if b.len() > 256 {
|
||||
key.sign(&sr_io::blake2_256(b))
|
||||
@@ -244,8 +248,8 @@ fn sign<F: ServiceFactory, RA: RuntimeAdapter>(
|
||||
}
|
||||
}).into();
|
||||
UncheckedExtrinsic {
|
||||
signature: Some((indices::address::Address::Id(signed), signature, payload.0, era)),
|
||||
function: payload.1,
|
||||
signature: Some((indices::address::Address::Id(signed), signature, extra)),
|
||||
function: payload.0,
|
||||
}
|
||||
}
|
||||
None => UncheckedExtrinsic {
|
||||
|
||||
@@ -220,7 +220,7 @@ mod tests {
|
||||
use consensus_common::{Environment, Proposer, BlockImportParams, BlockOrigin, ForkChoiceStrategy};
|
||||
use node_primitives::DigestItem;
|
||||
use node_runtime::{BalancesCall, Call, CENTS, SECS_PER_BLOCK, UncheckedExtrinsic};
|
||||
use parity_codec::{Compact, Encode, Decode};
|
||||
use parity_codec::{Encode, Decode};
|
||||
use primitives::{
|
||||
crypto::Pair as CryptoPair, ed25519::Pair, blake2_256,
|
||||
sr25519::Public as AddressPublic, H256,
|
||||
@@ -358,19 +358,24 @@ mod tests {
|
||||
let signer = charlie.clone();
|
||||
|
||||
let function = Call::Balances(BalancesCall::transfer(to.into(), amount));
|
||||
let era = Era::immortal();
|
||||
let raw_payload = (Compact(index), function, era, genesis_hash);
|
||||
|
||||
let check_era = system::CheckEra::from(Era::Immortal);
|
||||
let check_nonce = system::CheckNonce::from(index);
|
||||
let check_weight = system::CheckWeight::from();
|
||||
let take_fees = balances::TakeFees::from(0);
|
||||
let extra = (check_era, check_nonce, check_weight, take_fees);
|
||||
|
||||
let raw_payload = (function, extra.clone(), genesis_hash);
|
||||
let signature = raw_payload.using_encoded(|payload| if payload.len() > 256 {
|
||||
signer.sign(&blake2_256(payload)[..])
|
||||
} else {
|
||||
signer.sign(payload)
|
||||
});
|
||||
let xt = UncheckedExtrinsic::new_signed(
|
||||
index,
|
||||
raw_payload.1,
|
||||
raw_payload.0,
|
||||
from.into(),
|
||||
signature.into(),
|
||||
era,
|
||||
extra,
|
||||
).encode();
|
||||
let v: Vec<u8> = Decode::decode(&mut xt.as_slice()).unwrap();
|
||||
|
||||
|
||||
@@ -40,24 +40,25 @@ mod tests {
|
||||
use substrate_executor::{WasmExecutor, NativeExecutionDispatch};
|
||||
use parity_codec::{Encode, Decode, Joiner};
|
||||
use keyring::{AuthorityKeyring, AccountKeyring};
|
||||
use runtime_support::{Hashable, StorageValue, StorageMap, traits::Currency};
|
||||
use runtime_support::{Hashable, StorageValue, StorageMap, traits::{Currency, Get}};
|
||||
use state_machine::{CodeExecutor, Externalities, TestExternalities as CoreTestExternalities};
|
||||
use primitives::{
|
||||
twox_128, blake2_256, Blake2Hasher, ChangesTrieConfiguration, NeverNativeValue,
|
||||
NativeOrEncoded
|
||||
};
|
||||
use node_primitives::{Hash, BlockNumber, AccountId};
|
||||
use node_primitives::{Hash, BlockNumber, AccountId, Balance, Index};
|
||||
use runtime_primitives::traits::{Header as HeaderT, Hash as HashT};
|
||||
use runtime_primitives::{generic::Era, ApplyOutcome, ApplyError, ApplyResult, Perbill};
|
||||
use runtime_primitives::weights::{IDEAL_TRANSACTIONS_WEIGHT, WeightMultiplier};
|
||||
use runtime_primitives::weights::{WeightMultiplier, SimpleDispatchInfo, WeighData};
|
||||
use {balances, contracts, indices, staking, system, timestamp};
|
||||
use contracts::ContractAddressFor;
|
||||
use system::{EventRecord, Phase};
|
||||
use node_runtime::{
|
||||
Header, Block, UncheckedExtrinsic, CheckedExtrinsic, Call, Runtime, Balances, BuildStorage,
|
||||
GenesisConfig, BalancesConfig, SessionConfig, StakingConfig, System, SystemConfig,
|
||||
GrandpaConfig, IndicesConfig, ContractsConfig, Event, SessionKeys,
|
||||
CENTS, DOLLARS, MILLICENTS,
|
||||
GrandpaConfig, IndicesConfig, ContractsConfig, Event, SessionKeys, CreationFee,
|
||||
CENTS, DOLLARS, MILLICENTS, SignedExtra, TransactionBaseFee, TransactionByteFee,
|
||||
MaximumBlockWeight,
|
||||
};
|
||||
use wabt;
|
||||
use primitives::map;
|
||||
@@ -78,13 +79,25 @@ mod tests {
|
||||
const BLOATY_CODE: &[u8] = node_runtime::WASM_BINARY_BLOATY;
|
||||
|
||||
const GENESIS_HASH: [u8; 32] = [69u8; 32];
|
||||
// from weight
|
||||
const TX_FEE: u128 = 146;
|
||||
// regardless of creation of transfer
|
||||
const TRANSFER_FEE: u128 = 1 * CENTS;
|
||||
|
||||
type TestExternalities<H> = CoreTestExternalities<H, u64>;
|
||||
|
||||
fn transfer_fee<E: Encode>(extrinsic: &E) -> Balance {
|
||||
let length_fee = <TransactionBaseFee as Get<Balance>>::get() +
|
||||
<TransactionByteFee as Get<Balance>>::get() *
|
||||
(extrinsic.encode().len() as Balance);
|
||||
let weight_fee = SimpleDispatchInfo::default().weigh_data(()) as Balance;
|
||||
length_fee + weight_fee
|
||||
}
|
||||
|
||||
fn multiplier_ideal() -> u32 {
|
||||
<MaximumBlockWeight as Get<u32>>::get() / 4 / 4
|
||||
}
|
||||
|
||||
fn creation_fee() -> Balance {
|
||||
<CreationFee as Get<Balance>>::get()
|
||||
}
|
||||
|
||||
fn alice() -> AccountId {
|
||||
AccountKeyring::Alice.into()
|
||||
}
|
||||
@@ -111,9 +124,8 @@ mod tests {
|
||||
|
||||
fn sign(xt: CheckedExtrinsic) -> UncheckedExtrinsic {
|
||||
match xt.signed {
|
||||
Some((signed, index)) => {
|
||||
let era = Era::mortal(256, 0);
|
||||
let payload = (index.into(), xt.function, era, GENESIS_HASH);
|
||||
Some((signed, extra)) => {
|
||||
let payload = (xt.function, extra.clone(), GENESIS_HASH);
|
||||
let key = AccountKeyring::from_public(&signed).unwrap();
|
||||
let signature = payload.using_encoded(|b| {
|
||||
if b.len() > 256 {
|
||||
@@ -123,8 +135,8 @@ mod tests {
|
||||
}
|
||||
}).into();
|
||||
UncheckedExtrinsic {
|
||||
signature: Some((indices::address::Address::Id(signed), signature, payload.0, era)),
|
||||
function: payload.1,
|
||||
signature: Some((indices::address::Address::Id(signed), signature, extra)),
|
||||
function: payload.0,
|
||||
}
|
||||
}
|
||||
None => UncheckedExtrinsic {
|
||||
@@ -134,9 +146,18 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn signed_extra(nonce: Index, extra_fee: Balance) -> SignedExtra {
|
||||
(
|
||||
system::CheckEra::from(Era::mortal(256, 0)),
|
||||
system::CheckNonce::from(nonce),
|
||||
system::CheckWeight::from(),
|
||||
balances::TakeFees::from(extra_fee)
|
||||
)
|
||||
}
|
||||
|
||||
fn xt() -> UncheckedExtrinsic {
|
||||
sign(CheckedExtrinsic {
|
||||
signed: Some((alice(), 0)),
|
||||
signed: Some((alice(), signed_extra(0, 0))),
|
||||
function: Call::Balances(balances::Call::transfer::<Runtime>(bob().into(), 69 * DOLLARS)),
|
||||
})
|
||||
}
|
||||
@@ -252,7 +273,7 @@ mod tests {
|
||||
assert!(r.is_ok());
|
||||
|
||||
runtime_io::with_externalities(&mut t, || {
|
||||
assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - TX_FEE - TRANSFER_FEE);
|
||||
assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - transfer_fee(&xt()) - creation_fee());
|
||||
assert_eq!(Balances::total_balance(&bob()), 69 * DOLLARS);
|
||||
});
|
||||
}
|
||||
@@ -288,7 +309,7 @@ mod tests {
|
||||
assert!(r.is_ok());
|
||||
|
||||
runtime_io::with_externalities(&mut t, || {
|
||||
assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - TX_FEE - TRANSFER_FEE);
|
||||
assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - transfer_fee(&xt()) - creation_fee());
|
||||
assert_eq!(Balances::total_balance(&bob()), 69 * DOLLARS);
|
||||
});
|
||||
}
|
||||
@@ -433,7 +454,7 @@ mod tests {
|
||||
function: Call::Timestamp(timestamp::Call::set(42)),
|
||||
},
|
||||
CheckedExtrinsic {
|
||||
signed: Some((alice(), 0)),
|
||||
signed: Some((alice(), signed_extra(0, 0))),
|
||||
function: Call::Balances(balances::Call::transfer(bob().into(), 69 * DOLLARS)),
|
||||
},
|
||||
]
|
||||
@@ -455,7 +476,7 @@ mod tests {
|
||||
function: Call::Timestamp(timestamp::Call::set(42)),
|
||||
},
|
||||
CheckedExtrinsic {
|
||||
signed: Some((alice(), 0)),
|
||||
signed: Some((alice(), signed_extra(0, 0))),
|
||||
function: Call::Balances(balances::Call::transfer(bob().into(), 69 * DOLLARS)),
|
||||
},
|
||||
]
|
||||
@@ -470,11 +491,11 @@ mod tests {
|
||||
function: Call::Timestamp(timestamp::Call::set(52)),
|
||||
},
|
||||
CheckedExtrinsic {
|
||||
signed: Some((bob(), 0)),
|
||||
signed: Some((bob(), signed_extra(0, 0))),
|
||||
function: Call::Balances(balances::Call::transfer(alice().into(), 5 * DOLLARS)),
|
||||
},
|
||||
CheckedExtrinsic {
|
||||
signed: Some((alice(), 1)),
|
||||
signed: Some((alice(), signed_extra(1, 0))),
|
||||
function: Call::Balances(balances::Call::transfer(bob().into(), 15 * DOLLARS)),
|
||||
}
|
||||
]
|
||||
@@ -498,7 +519,7 @@ mod tests {
|
||||
function: Call::Timestamp(timestamp::Call::set(time)),
|
||||
},
|
||||
CheckedExtrinsic {
|
||||
signed: Some((charlie(), nonce)),
|
||||
signed: Some((alice(), signed_extra(nonce, 0))),
|
||||
function: Call::System(system::Call::remark(vec![0; size])),
|
||||
}
|
||||
]
|
||||
@@ -520,9 +541,7 @@ mod tests {
|
||||
).0.unwrap();
|
||||
|
||||
runtime_io::with_externalities(&mut t, || {
|
||||
// block1 transfers from alice 69 to bob.
|
||||
// -1 is the default fee
|
||||
assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - TX_FEE - TRANSFER_FEE);
|
||||
assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - transfer_fee(&xt()) - creation_fee());
|
||||
assert_eq!(Balances::total_balance(&bob()), 169 * DOLLARS);
|
||||
let events = vec![
|
||||
EventRecord {
|
||||
@@ -557,11 +576,9 @@ mod tests {
|
||||
).0.unwrap();
|
||||
|
||||
runtime_io::with_externalities(&mut t, || {
|
||||
// bob sends 5, alice sends 15 | bob += 10, alice -= 10
|
||||
// 111 - 69 - 10 = 32
|
||||
assert_eq!(Balances::total_balance(&alice()), 32 * DOLLARS - 2 * (TX_FEE + TRANSFER_FEE));
|
||||
// 100 + 69 + 10 = 179
|
||||
assert_eq!(Balances::total_balance(&bob()), 179 * DOLLARS - TX_FEE - TRANSFER_FEE);
|
||||
// TODO TODO: this needs investigating: why are we deducting creation fee twice here? and why bob also pays it?
|
||||
assert_eq!(Balances::total_balance(&alice()), 32 * DOLLARS - 2 * transfer_fee(&xt()) - 2 * creation_fee());
|
||||
assert_eq!(Balances::total_balance(&bob()), 179 * DOLLARS - transfer_fee(&xt()) - creation_fee());
|
||||
let events = vec![
|
||||
EventRecord {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
@@ -616,19 +633,15 @@ mod tests {
|
||||
WasmExecutor::new().call(&mut t, 8, COMPACT_CODE, "Core_execute_block", &block1.0).unwrap();
|
||||
|
||||
runtime_io::with_externalities(&mut t, || {
|
||||
// block1 transfers from alice 69 to bob.
|
||||
assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - (TX_FEE + TRANSFER_FEE));
|
||||
assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - transfer_fee(&xt()) - creation_fee());
|
||||
assert_eq!(Balances::total_balance(&bob()), 169 * DOLLARS);
|
||||
});
|
||||
|
||||
WasmExecutor::new().call(&mut t, 8, COMPACT_CODE, "Core_execute_block", &block2.0).unwrap();
|
||||
|
||||
runtime_io::with_externalities(&mut t, || {
|
||||
// bob sends 5, alice sends 15 | bob += 10, alice -= 10
|
||||
// 111 - 69 - 10 = 32
|
||||
assert_eq!(Balances::total_balance(&alice()), 32 * DOLLARS - 2 * (TX_FEE + TRANSFER_FEE));
|
||||
// 100 + 69 + 10 = 179
|
||||
assert_eq!(Balances::total_balance(&bob()), 179 * DOLLARS - (TX_FEE + TRANSFER_FEE));
|
||||
assert_eq!(Balances::total_balance(&alice()), 32 * DOLLARS - 2 * transfer_fee(&xt()) - 2 * creation_fee());
|
||||
assert_eq!(Balances::total_balance(&bob()), 179 * DOLLARS - 1 * transfer_fee(&xt()) - creation_fee());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -745,19 +758,19 @@ mod tests {
|
||||
function: Call::Timestamp(timestamp::Call::set(42)),
|
||||
},
|
||||
CheckedExtrinsic {
|
||||
signed: Some((charlie(), 0)),
|
||||
signed: Some((charlie(), signed_extra(0, 0))),
|
||||
function: Call::Contracts(
|
||||
contracts::Call::put_code::<Runtime>(10_000, transfer_code)
|
||||
),
|
||||
},
|
||||
CheckedExtrinsic {
|
||||
signed: Some((charlie(), 1)),
|
||||
signed: Some((charlie(), signed_extra(1, 0))),
|
||||
function: Call::Contracts(
|
||||
contracts::Call::create::<Runtime>(1 * DOLLARS, 10_000, transfer_ch, Vec::new())
|
||||
),
|
||||
},
|
||||
CheckedExtrinsic {
|
||||
signed: Some((charlie(), 2)),
|
||||
signed: Some((charlie(), signed_extra(2, 0))),
|
||||
function: Call::Contracts(
|
||||
contracts::Call::call::<Runtime>(
|
||||
indices::address::Address::Id(addr.clone()),
|
||||
@@ -873,7 +886,7 @@ mod tests {
|
||||
assert_eq!(r, Ok(ApplyOutcome::Success));
|
||||
|
||||
runtime_io::with_externalities(&mut t, || {
|
||||
assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - TX_FEE - TRANSFER_FEE);
|
||||
assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - 1 * transfer_fee(&xt()) - creation_fee());
|
||||
assert_eq!(Balances::total_balance(&bob()), 69 * DOLLARS);
|
||||
});
|
||||
}
|
||||
@@ -923,7 +936,8 @@ mod tests {
|
||||
|
||||
|
||||
#[test]
|
||||
fn weight_multiplier_increases_on_big_block() {
|
||||
#[ignore]
|
||||
fn weight_multiplier_increases_and_decreases_on_big_weight() {
|
||||
let mut t = new_test_ext(COMPACT_CODE, false);
|
||||
|
||||
let mut prev_multiplier = WeightMultiplier::default();
|
||||
@@ -933,21 +947,28 @@ mod tests {
|
||||
});
|
||||
|
||||
let mut tt = new_test_ext(COMPACT_CODE, false);
|
||||
// NOTE: This assumes that system::remark has the default.
|
||||
let num_to_exhaust = multiplier_ideal() * 2 / SimpleDispatchInfo::default().weigh_data(());
|
||||
println!("++ Generating {} transactions to fill {} weight units", num_to_exhaust, multiplier_ideal() * 2);
|
||||
|
||||
let mut xts = (0..num_to_exhaust).map(|i| CheckedExtrinsic {
|
||||
signed: Some((charlie(), signed_extra(i.into(), 0))),
|
||||
function: Call::System(system::Call::remark(vec![0; 1])),
|
||||
}).collect::<Vec<CheckedExtrinsic>>();
|
||||
xts.insert(0, CheckedExtrinsic {
|
||||
signed: None,
|
||||
function: Call::Timestamp(timestamp::Call::set(42)),
|
||||
});
|
||||
|
||||
// big one in terms of weight.
|
||||
let block1 = construct_block(
|
||||
&mut tt,
|
||||
1,
|
||||
GENESIS_HASH.into(),
|
||||
vec![
|
||||
CheckedExtrinsic {
|
||||
signed: None,
|
||||
function: Call::Timestamp(timestamp::Call::set(42)),
|
||||
},
|
||||
CheckedExtrinsic {
|
||||
signed: Some((charlie(), 0)),
|
||||
function: Call::System(system::Call::remark(vec![0; (IDEAL_TRANSACTIONS_WEIGHT*2) as usize])),
|
||||
}
|
||||
]
|
||||
xts
|
||||
);
|
||||
|
||||
// small one in terms of weight.
|
||||
let block2 = construct_block(
|
||||
&mut tt,
|
||||
2,
|
||||
@@ -958,8 +979,8 @@ mod tests {
|
||||
function: Call::Timestamp(timestamp::Call::set(52)),
|
||||
},
|
||||
CheckedExtrinsic {
|
||||
signed: Some((charlie(), 1)),
|
||||
function: Call::System(system::Call::remark(vec![0; (IDEAL_TRANSACTIONS_WEIGHT*2) as usize])),
|
||||
signed: Some((charlie(), signed_extra(num_to_exhaust.into(), 0))),
|
||||
function: Call::System(system::Call::remark(vec![0; 1])),
|
||||
}
|
||||
]
|
||||
);
|
||||
@@ -990,82 +1011,6 @@ mod tests {
|
||||
None,
|
||||
).0.unwrap();
|
||||
|
||||
// weight multiplier is increased for next block.
|
||||
runtime_io::with_externalities(&mut t, || {
|
||||
let fm = System::next_weight_multiplier();
|
||||
println!("After a big block: {:?} -> {:?}", prev_multiplier, fm);
|
||||
assert!(fm > prev_multiplier);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn weight_multiplier_decreases_on_small_block() {
|
||||
let mut t = new_test_ext(COMPACT_CODE, false);
|
||||
|
||||
let mut prev_multiplier = WeightMultiplier::default();
|
||||
|
||||
runtime_io::with_externalities(&mut t, || {
|
||||
assert_eq!(System::next_weight_multiplier(), prev_multiplier);
|
||||
});
|
||||
|
||||
let mut tt = new_test_ext(COMPACT_CODE, false);
|
||||
let block1 = construct_block(
|
||||
&mut tt,
|
||||
1,
|
||||
GENESIS_HASH.into(),
|
||||
vec![
|
||||
CheckedExtrinsic {
|
||||
signed: None,
|
||||
function: Call::Timestamp(timestamp::Call::set(42)),
|
||||
},
|
||||
CheckedExtrinsic {
|
||||
signed: Some((charlie(), 0)),
|
||||
function: Call::System(system::Call::remark(vec![0; 120])),
|
||||
}
|
||||
]
|
||||
);
|
||||
let block2 = construct_block(
|
||||
&mut tt,
|
||||
2,
|
||||
block1.1.clone(),
|
||||
vec![
|
||||
CheckedExtrinsic {
|
||||
signed: None,
|
||||
function: Call::Timestamp(timestamp::Call::set(52)),
|
||||
},
|
||||
CheckedExtrinsic {
|
||||
signed: Some((charlie(), 1)),
|
||||
function: Call::System(system::Call::remark(vec![0; 120])),
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
// execute a big block.
|
||||
executor().call::<_, NeverNativeValue, fn() -> _>(
|
||||
&mut t,
|
||||
"Core_execute_block",
|
||||
&block1.0,
|
||||
true,
|
||||
None,
|
||||
).0.unwrap();
|
||||
|
||||
// weight multiplier is increased for next block.
|
||||
runtime_io::with_externalities(&mut t, || {
|
||||
let fm = System::next_weight_multiplier();
|
||||
println!("After a small block: {:?} -> {:?}", prev_multiplier, fm);
|
||||
assert!(fm < prev_multiplier);
|
||||
prev_multiplier = fm;
|
||||
});
|
||||
|
||||
// execute a big block.
|
||||
executor().call::<_, NeverNativeValue, fn() -> _>(
|
||||
&mut t,
|
||||
"Core_execute_block",
|
||||
&block2.0,
|
||||
true,
|
||||
None,
|
||||
).0.unwrap();
|
||||
|
||||
// weight multiplier is increased for next block.
|
||||
runtime_io::with_externalities(&mut t, || {
|
||||
let fm = System::next_weight_multiplier();
|
||||
|
||||
@@ -17,10 +17,11 @@
|
||||
//! Some configurable implementations as associated type for the substrate runtime.
|
||||
|
||||
use node_primitives::Balance;
|
||||
use runtime_primitives::weights::{Weight, WeightMultiplier, MAX_TRANSACTIONS_WEIGHT, IDEAL_TRANSACTIONS_WEIGHT};
|
||||
use runtime_primitives::weights::{Weight, WeightMultiplier};
|
||||
use runtime_primitives::traits::{Convert, Saturating};
|
||||
use runtime_primitives::Fixed64;
|
||||
use crate::Balances;
|
||||
use support::traits::Get;
|
||||
use crate::{Balances, MaximumBlockWeight};
|
||||
|
||||
/// Struct that handles the conversion of Balance -> `u64`. This is used for staking's election
|
||||
/// calculation.
|
||||
@@ -53,14 +54,19 @@ pub struct WeightMultiplierUpdateHandler;
|
||||
impl Convert<(Weight, WeightMultiplier), WeightMultiplier> for WeightMultiplierUpdateHandler {
|
||||
fn convert(previous_state: (Weight, WeightMultiplier)) -> WeightMultiplier {
|
||||
let (block_weight, multiplier) = previous_state;
|
||||
let ideal = IDEAL_TRANSACTIONS_WEIGHT as u128;
|
||||
// CRITICAL NOTE: what the system module interprets as maximum block weight, and a portion
|
||||
// of it (1/4 usually) as ideal weight demonstrate the gap in block weights for operational
|
||||
// transactions. What this weight multiplier interprets as the maximum, is actually the
|
||||
// maximum that is available to normal transactions. Hence,
|
||||
let max_weight = <MaximumBlockWeight as Get<u32>>::get() / 4;
|
||||
let ideal_weight = (max_weight / 4) as u128;
|
||||
let block_weight = block_weight as u128;
|
||||
|
||||
// determines if the first_term is positive
|
||||
let positive = block_weight >= ideal;
|
||||
let diff_abs = block_weight.max(ideal) - block_weight.min(ideal);
|
||||
let positive = block_weight >= ideal_weight;
|
||||
let diff_abs = block_weight.max(ideal_weight) - block_weight.min(ideal_weight);
|
||||
// diff is within u32, safe.
|
||||
let diff = Fixed64::from_rational(diff_abs as i64, MAX_TRANSACTIONS_WEIGHT as u64);
|
||||
let diff = Fixed64::from_rational(diff_abs as i64, max_weight as u64);
|
||||
let diff_squared = diff.saturating_mul(diff);
|
||||
|
||||
// 0.00004 = 4/100_000 = 40_000/10^9
|
||||
@@ -94,9 +100,17 @@ impl Convert<(Weight, WeightMultiplier), WeightMultiplier> for WeightMultiplierU
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use runtime_primitives::weights::{MAX_TRANSACTIONS_WEIGHT, IDEAL_TRANSACTIONS_WEIGHT, Weight};
|
||||
use runtime_primitives::weights::Weight;
|
||||
use runtime_primitives::Perbill;
|
||||
|
||||
fn max() -> Weight {
|
||||
<MaximumBlockWeight as Get<Weight>>::get()
|
||||
}
|
||||
|
||||
fn ideal() -> Weight {
|
||||
max() / 4 / 4
|
||||
}
|
||||
|
||||
// poc reference implementation.
|
||||
#[allow(dead_code)]
|
||||
fn weight_multiplier_update(block_weight: Weight) -> Perbill {
|
||||
@@ -104,9 +118,9 @@ mod tests {
|
||||
let v: f32 = 0.00004;
|
||||
|
||||
// maximum tx weight
|
||||
let m = MAX_TRANSACTIONS_WEIGHT as f32;
|
||||
let m = max() as f32;
|
||||
// Ideal saturation in terms of weight
|
||||
let ss = IDEAL_TRANSACTIONS_WEIGHT as f32;
|
||||
let ss = ideal() as f32;
|
||||
// Current saturation in terms of weight
|
||||
let s = block_weight;
|
||||
|
||||
@@ -124,22 +138,22 @@ mod tests {
|
||||
fn stateless_weight_mul() {
|
||||
// Light block. Fee is reduced a little.
|
||||
assert_eq!(
|
||||
WeightMultiplierUpdateHandler::convert((1024, WeightMultiplier::default())),
|
||||
fm(-9990)
|
||||
WeightMultiplierUpdateHandler::convert((ideal() / 4, WeightMultiplier::default())),
|
||||
fm(-7500)
|
||||
);
|
||||
// a bit more. Fee is decreased less, meaning that the fee increases as the block grows.
|
||||
assert_eq!(
|
||||
WeightMultiplierUpdateHandler::convert((1024 * 4, WeightMultiplier::default())),
|
||||
fm(-9960)
|
||||
WeightMultiplierUpdateHandler::convert((ideal() / 2, WeightMultiplier::default())),
|
||||
fm(-5000)
|
||||
);
|
||||
// ideal. Original fee.
|
||||
// ideal. Original fee. No changes.
|
||||
assert_eq!(
|
||||
WeightMultiplierUpdateHandler::convert((IDEAL_TRANSACTIONS_WEIGHT as u32, WeightMultiplier::default())),
|
||||
WeightMultiplierUpdateHandler::convert((ideal() as u32, WeightMultiplier::default())),
|
||||
fm(0)
|
||||
);
|
||||
// // More than ideal. Fee is increased.
|
||||
assert_eq!(
|
||||
WeightMultiplierUpdateHandler::convert(((IDEAL_TRANSACTIONS_WEIGHT * 2) as u32, WeightMultiplier::default())),
|
||||
WeightMultiplierUpdateHandler::convert(((ideal() * 2) as u32, WeightMultiplier::default())),
|
||||
fm(10000)
|
||||
);
|
||||
}
|
||||
@@ -147,20 +161,20 @@ mod tests {
|
||||
#[test]
|
||||
fn stateful_weight_mul_grow_to_infinity() {
|
||||
assert_eq!(
|
||||
WeightMultiplierUpdateHandler::convert((IDEAL_TRANSACTIONS_WEIGHT * 2, WeightMultiplier::default())),
|
||||
WeightMultiplierUpdateHandler::convert((ideal() * 2, WeightMultiplier::default())),
|
||||
fm(10000)
|
||||
);
|
||||
assert_eq!(
|
||||
WeightMultiplierUpdateHandler::convert((IDEAL_TRANSACTIONS_WEIGHT * 2, fm(10000))),
|
||||
WeightMultiplierUpdateHandler::convert((ideal() * 2, fm(10000))),
|
||||
fm(20000)
|
||||
);
|
||||
assert_eq!(
|
||||
WeightMultiplierUpdateHandler::convert((IDEAL_TRANSACTIONS_WEIGHT * 2, fm(20000))),
|
||||
WeightMultiplierUpdateHandler::convert((ideal() * 2, fm(20000))),
|
||||
fm(30000)
|
||||
);
|
||||
// ...
|
||||
assert_eq!(
|
||||
WeightMultiplierUpdateHandler::convert((IDEAL_TRANSACTIONS_WEIGHT * 2, fm(1_000_000_000))),
|
||||
WeightMultiplierUpdateHandler::convert((ideal() * 2, fm(1_000_000_000))),
|
||||
fm(1_000_000_000 + 10000)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ use client::{
|
||||
};
|
||||
use runtime_primitives::{ApplyResult, impl_opaque_keys, generic, create_runtime_str, key_types};
|
||||
use runtime_primitives::transaction_validity::TransactionValidity;
|
||||
use runtime_primitives::weights::Weight;
|
||||
use runtime_primitives::traits::{
|
||||
BlakeTwo256, Block as BlockT, DigestFor, NumberFor, StaticLookup,
|
||||
};
|
||||
@@ -74,8 +75,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: 116,
|
||||
impl_version: 116,
|
||||
spec_version: 117,
|
||||
impl_version: 117,
|
||||
apis: RUNTIME_API_VERSIONS,
|
||||
};
|
||||
|
||||
@@ -116,6 +117,8 @@ pub const DAYS: Moment = HOURS * 24;
|
||||
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: BlockNumber = 250;
|
||||
pub const MaximumBlockWeight: Weight = 4 * 1024 * 1024;
|
||||
pub const MaximumBlockLength: u32 = 4 * 1024 * 1024;
|
||||
}
|
||||
|
||||
impl system::Trait for Runtime {
|
||||
@@ -130,6 +133,8 @@ impl system::Trait for Runtime {
|
||||
type WeightMultiplierUpdate = WeightMultiplierUpdateHandler;
|
||||
type Event = Event;
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
}
|
||||
|
||||
impl aura::Trait for Runtime {
|
||||
@@ -432,12 +437,19 @@ pub type Block = generic::Block<Header, UncheckedExtrinsic>;
|
||||
pub type SignedBlock = generic::SignedBlock<Block>;
|
||||
/// BlockId type as expected by this runtime.
|
||||
pub type BlockId = generic::BlockId<Block>;
|
||||
/// The SignedExtension to the basic transaction logic.
|
||||
pub type SignedExtra = (
|
||||
system::CheckEra<Runtime>,
|
||||
system::CheckNonce<Runtime>,
|
||||
system::CheckWeight<Runtime>,
|
||||
balances::TakeFees<Runtime>
|
||||
);
|
||||
/// Unchecked extrinsic type as expected by this runtime.
|
||||
pub type UncheckedExtrinsic = generic::UncheckedMortalCompactExtrinsic<Address, Index, Call, Signature>;
|
||||
pub type UncheckedExtrinsic = generic::UncheckedExtrinsic<Address, Call, Signature, SignedExtra>;
|
||||
/// Extrinsic type that has already been checked.
|
||||
pub type CheckedExtrinsic = generic::CheckedExtrinsic<AccountId, Index, Call>;
|
||||
pub type CheckedExtrinsic = generic::CheckedExtrinsic<AccountId, Call, SignedExtra>;
|
||||
/// Executive: handles dispatch to the various modules.
|
||||
pub type Executive = executive::Executive<Runtime, Block, system::ChainContext<Runtime>, Balances, Runtime, AllModules>;
|
||||
pub type Executive = executive::Executive<Runtime, Block, system::ChainContext<Runtime>, Runtime, AllModules>;
|
||||
|
||||
impl_runtime_apis! {
|
||||
impl client_api::Core<Block> for Runtime {
|
||||
|
||||
@@ -257,6 +257,8 @@ mod tests {
|
||||
pub struct Test;
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: u32 = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
}
|
||||
impl system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
@@ -270,6 +272,8 @@ mod tests {
|
||||
type WeightMultiplierUpdate = ();
|
||||
type Event = ();
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
}
|
||||
impl Trait for Test {
|
||||
type Event = ();
|
||||
|
||||
@@ -37,6 +37,8 @@ pub struct Test;
|
||||
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: u32 = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
pub const MinimumPeriod: u64 = 1;
|
||||
}
|
||||
|
||||
@@ -52,6 +54,8 @@ impl system::Trait for Test {
|
||||
type WeightMultiplierUpdate = ();
|
||||
type Event = ();
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
}
|
||||
|
||||
impl timestamp::Trait for Test {
|
||||
|
||||
@@ -337,6 +337,8 @@ mod tests {
|
||||
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: u32 = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
}
|
||||
|
||||
impl system::Trait for Test {
|
||||
@@ -351,6 +353,8 @@ mod tests {
|
||||
type WeightMultiplierUpdate = ();
|
||||
type Event = ();
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
}
|
||||
|
||||
impl Trait for Test {
|
||||
|
||||
@@ -154,16 +154,17 @@ use rstd::{cmp, result, mem};
|
||||
use parity_codec::{Codec, Encode, Decode};
|
||||
use srml_support::{StorageValue, StorageMap, Parameter, decl_event, decl_storage, decl_module};
|
||||
use srml_support::traits::{
|
||||
UpdateBalanceOutcome, Currency, OnFreeBalanceZero, MakePayment, OnUnbalanced,
|
||||
UpdateBalanceOutcome, Currency, OnFreeBalanceZero, OnUnbalanced,
|
||||
WithdrawReason, WithdrawReasons, LockIdentifier, LockableCurrency, ExistenceRequirement,
|
||||
Imbalance, SignedImbalance, ReservableCurrency
|
||||
Imbalance, SignedImbalance, ReservableCurrency, Get,
|
||||
};
|
||||
use srml_support::{dispatch::Result, traits::Get};
|
||||
use srml_support::dispatch::Result;
|
||||
use primitives::traits::{
|
||||
Zero, SimpleArithmetic, StaticLookup, Member, CheckedAdd, CheckedSub,
|
||||
MaybeSerializeDebug, Saturating, Bounded
|
||||
Zero, SimpleArithmetic, StaticLookup, Member, CheckedAdd, CheckedSub, MaybeSerializeDebug,
|
||||
Saturating, Bounded, SignedExtension, SaturatedConversion, DispatchError
|
||||
};
|
||||
use primitives::weights::Weight;
|
||||
use primitives::transaction_validity::{TransactionPriority, ValidTransaction};
|
||||
use primitives::weights::DispatchInfo;
|
||||
use system::{IsDeadAccount, OnNewAccount, ensure_signed, ensure_root};
|
||||
|
||||
mod mock;
|
||||
@@ -763,6 +764,8 @@ impl<T: Subtrait<I>, I: Instance> system::Trait for ElevatedTrait<T, I> {
|
||||
type WeightMultiplierUpdate = T::WeightMultiplierUpdate;
|
||||
type Event = ();
|
||||
type BlockHashCount = T::BlockHashCount;
|
||||
type MaximumBlockWeight = T::MaximumBlockWeight;
|
||||
type MaximumBlockLength = T::MaximumBlockLength;
|
||||
}
|
||||
impl<T: Subtrait<I>, I: Instance> Trait<I> for ElevatedTrait<T, I> {
|
||||
type Balance = T::Balance;
|
||||
@@ -1146,17 +1149,79 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait<I>, I: Instance> MakePayment<T::AccountId> for Module<T, I> {
|
||||
fn make_payment(transactor: &T::AccountId, weight: Weight) -> Result {
|
||||
let transaction_fee = T::Balance::from(weight);
|
||||
let imbalance = Self::withdraw(
|
||||
transactor,
|
||||
transaction_fee,
|
||||
/// Require the transactor pay for themselves and maybe include a tip to gain additional priority
|
||||
/// in the queue.
|
||||
#[derive(Encode, Decode, Clone, Eq, PartialEq)]
|
||||
pub struct TakeFees<T: Trait<I>, I: Instance = DefaultInstance>(#[codec(compact)] T::Balance);
|
||||
|
||||
impl<T: Trait<I>, I: Instance> TakeFees<T, I> {
|
||||
/// utility constructor. Used only in client/factory code.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn from(fee: T::Balance) -> Self {
|
||||
Self(fee)
|
||||
}
|
||||
|
||||
/// Compute the final fee value for a particular transaction.
|
||||
///
|
||||
/// The final fee is composed of:
|
||||
/// - _length-fee_: This is the amount paid merely to pay for size of the transaction.
|
||||
/// - _weight-fee_: This amount is computed based on the weight of the transaction. Unlike
|
||||
/// size-fee, this is not input dependent and reflects the _complexity_ of the execution
|
||||
/// and the time it consumes.
|
||||
/// - (optional) _tip_: if included in the transaction, it will be added on top. Only signed
|
||||
/// transactions can have a tip.
|
||||
fn compute_fee(len: usize, info: DispatchInfo, tip: T::Balance) -> T::Balance {
|
||||
// length fee
|
||||
let len_fee = if info.pay_length_fee() {
|
||||
let len = T::Balance::from(len as u32);
|
||||
let base = T::TransactionBaseFee::get();
|
||||
let byte = T::TransactionByteFee::get();
|
||||
base.saturating_add(byte.saturating_mul(len))
|
||||
} else {
|
||||
Zero::zero()
|
||||
};
|
||||
|
||||
// weight fee
|
||||
let weight = info.weight;
|
||||
let weight_fee: T::Balance = <system::Module<T>>::next_weight_multiplier().apply_to(weight).into();
|
||||
|
||||
len_fee.saturating_add(weight_fee).saturating_add(tip)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<T: Trait<I>, I: Instance> rstd::fmt::Debug for TakeFees<T, I> {
|
||||
fn fmt(&self, f: &mut rstd::fmt::Formatter) -> rstd::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait<I>, I: Instance + Clone + Eq> SignedExtension for TakeFees<T, I> {
|
||||
type AccountId = T::AccountId;
|
||||
type AdditionalSigned = ();
|
||||
fn additional_signed(&self) -> rstd::result::Result<(), &'static str> { Ok(()) }
|
||||
|
||||
fn validate(
|
||||
&self,
|
||||
who: &Self::AccountId,
|
||||
info: DispatchInfo,
|
||||
len: usize,
|
||||
) -> rstd::result::Result<ValidTransaction, DispatchError> {
|
||||
// pay any fees.
|
||||
let fee = Self::compute_fee(len, info, self.0);
|
||||
let imbalance = <Module<T, I>>::withdraw(
|
||||
who,
|
||||
fee,
|
||||
WithdrawReason::TransactionPayment,
|
||||
ExistenceRequirement::KeepAlive
|
||||
)?;
|
||||
ExistenceRequirement::KeepAlive,
|
||||
).map_err(|_| DispatchError::Payment)?;
|
||||
T::TransactionPayment::on_unbalanced(imbalance);
|
||||
Ok(())
|
||||
|
||||
let mut r = ValidTransaction::default();
|
||||
// NOTE: we probably want to maximize the _fee (of any type) per weight unit_ here, which
|
||||
// will be a bit more than setting the priority to tip. For now, this is enough.
|
||||
r.priority = fee.saturated_into::<TransactionPriority>();
|
||||
Ok(r)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,11 @@
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use primitives::{traits::IdentityLookup, testing::Header};
|
||||
use primitives::{traits::IdentityLookup, testing::Header, weights::{DispatchInfo, Weight}};
|
||||
use substrate_primitives::{H256, Blake2Hasher};
|
||||
use runtime_io;
|
||||
use srml_support::{impl_outer_origin, parameter_types, traits::Get};
|
||||
use srml_support::{impl_outer_origin, parameter_types};
|
||||
use srml_support::traits::Get;
|
||||
use std::cell::RefCell;
|
||||
use crate::{GenesisConfig, Module, Trait};
|
||||
|
||||
@@ -34,7 +35,7 @@ thread_local! {
|
||||
static TRANSFER_FEE: RefCell<u64> = RefCell::new(0);
|
||||
static CREATION_FEE: RefCell<u64> = RefCell::new(0);
|
||||
static TRANSACTION_BASE_FEE: RefCell<u64> = RefCell::new(0);
|
||||
static TRANSACTION_BYTE_FEE: RefCell<u64> = RefCell::new(0);
|
||||
static TRANSACTION_BYTE_FEE: RefCell<u64> = RefCell::new(1);
|
||||
}
|
||||
|
||||
pub struct ExistentialDeposit;
|
||||
@@ -67,6 +68,8 @@ impl Get<u64> for TransactionByteFee {
|
||||
pub struct Runtime;
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: u32 = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
}
|
||||
impl system::Trait for Runtime {
|
||||
type Origin = Origin;
|
||||
@@ -80,6 +83,8 @@ impl system::Trait for Runtime {
|
||||
type WeightMultiplierUpdate = ();
|
||||
type Event = ();
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
}
|
||||
impl Trait for Runtime {
|
||||
type Balance = u64;
|
||||
@@ -186,3 +191,8 @@ impl ExtBuilder {
|
||||
|
||||
pub type System = system::Module<Runtime>;
|
||||
pub type Balances = Module<Runtime>;
|
||||
|
||||
/// create a transaction info struct from weight. Handy to avoid building the whole struct.
|
||||
pub fn info_from_weight(w: Weight) -> DispatchInfo {
|
||||
DispatchInfo { weight: w, ..Default::default() }
|
||||
}
|
||||
|
||||
@@ -19,12 +19,12 @@
|
||||
#![cfg(test)]
|
||||
|
||||
use super::*;
|
||||
use mock::{Balances, ExtBuilder, Runtime, System};
|
||||
use mock::{Balances, ExtBuilder, Runtime, System, info_from_weight};
|
||||
use runtime_io::with_externalities;
|
||||
use srml_support::{
|
||||
assert_noop, assert_ok, assert_err,
|
||||
traits::{LockableCurrency, LockIdentifier, WithdrawReason, WithdrawReasons,
|
||||
Currency, MakePayment, ReservableCurrency}
|
||||
Currency, ReservableCurrency}
|
||||
};
|
||||
|
||||
const ID_1: LockIdentifier = *b"1 ";
|
||||
@@ -123,7 +123,13 @@ fn lock_reasons_should_work() {
|
||||
"account liquidity restrictions prevent withdrawal"
|
||||
);
|
||||
assert_ok!(<Balances as ReservableCurrency<_>>::reserve(&1, 1));
|
||||
assert_ok!(<Balances as MakePayment<_>>::make_payment(&1, 1));
|
||||
// NOTE: this causes a fee payment.
|
||||
assert!(<TakeFees<Runtime> as SignedExtension>::pre_dispatch(
|
||||
TakeFees::from(1),
|
||||
&1,
|
||||
info_from_weight(1),
|
||||
0,
|
||||
).is_ok());
|
||||
|
||||
Balances::set_lock(ID_1, &1, 10, u64::max_value(), WithdrawReason::Reserve.into());
|
||||
assert_ok!(<Balances as Currency<_>>::transfer(&1, &2, 1));
|
||||
@@ -131,15 +137,22 @@ fn lock_reasons_should_work() {
|
||||
<Balances as ReservableCurrency<_>>::reserve(&1, 1),
|
||||
"account liquidity restrictions prevent withdrawal"
|
||||
);
|
||||
assert_ok!(<Balances as MakePayment<_>>::make_payment(&1, 1));
|
||||
assert!(<TakeFees<Runtime> as SignedExtension>::pre_dispatch(
|
||||
TakeFees::from(1),
|
||||
&1,
|
||||
info_from_weight(1),
|
||||
0,
|
||||
).is_ok());
|
||||
|
||||
Balances::set_lock(ID_1, &1, 10, u64::max_value(), WithdrawReason::TransactionPayment.into());
|
||||
assert_ok!(<Balances as Currency<_>>::transfer(&1, &2, 1));
|
||||
assert_ok!(<Balances as ReservableCurrency<_>>::reserve(&1, 1));
|
||||
assert_noop!(
|
||||
<Balances as MakePayment<_>>::make_payment(&1, 1),
|
||||
"account liquidity restrictions prevent withdrawal"
|
||||
);
|
||||
assert!(<TakeFees<Runtime> as SignedExtension>::pre_dispatch(
|
||||
TakeFees::from(1),
|
||||
&1,
|
||||
info_from_weight(1),
|
||||
0,
|
||||
).is_err());
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -733,3 +746,21 @@ fn liquid_funds_should_transfer_with_delayed_vesting() {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signed_extension_take_fees_work() {
|
||||
with_externalities(
|
||||
&mut ExtBuilder::default()
|
||||
.existential_deposit(10)
|
||||
.transaction_fees(10, 1)
|
||||
.monied(true)
|
||||
.build(),
|
||||
|| {
|
||||
let len = 10;
|
||||
assert!(TakeFees::<Runtime>::from(0).pre_dispatch(&1, info_from_weight(0), len).is_ok());
|
||||
assert_eq!(Balances::free_balance(&1), 100 - 20);
|
||||
assert!(TakeFees::<Runtime>::from(5 /* tipped */).pre_dispatch(&1, info_from_weight(0), len).is_ok());
|
||||
assert_eq!(Balances::free_balance(&1), 100 - 20 - 25);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -399,6 +399,8 @@ mod tests {
|
||||
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: u32 = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
}
|
||||
impl system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
@@ -412,6 +414,8 @@ mod tests {
|
||||
type Event = Event;
|
||||
type WeightMultiplierUpdate = ();
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
}
|
||||
impl Trait<Instance1> for Test {
|
||||
type Origin = Origin;
|
||||
@@ -425,7 +429,7 @@ mod tests {
|
||||
}
|
||||
|
||||
pub type Block = primitives::generic::Block<Header, UncheckedExtrinsic>;
|
||||
pub type UncheckedExtrinsic = primitives::generic::UncheckedMortalCompactExtrinsic<u32, u64, Call, ()>;
|
||||
pub type UncheckedExtrinsic = primitives::generic::UncheckedExtrinsic<u32, u64, Call, ()>;
|
||||
|
||||
srml_support::construct_runtime!(
|
||||
pub enum Test where
|
||||
|
||||
@@ -96,6 +96,8 @@ impl Get<u64> for BlockGasLimit {
|
||||
pub struct Test;
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: u32 = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
pub const BalancesTransactionBaseFee: u64 = 0;
|
||||
pub const BalancesTransactionByteFee: u64 = 0;
|
||||
}
|
||||
@@ -111,6 +113,8 @@ impl system::Trait for Test {
|
||||
type WeightMultiplierUpdate = ();
|
||||
type Event = MetaEvent;
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
}
|
||||
impl balances::Trait for Test {
|
||||
type Balance = u64;
|
||||
|
||||
@@ -98,6 +98,8 @@ mod tests {
|
||||
pub struct Test;
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: u32 = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
}
|
||||
impl system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
@@ -111,6 +113,8 @@ mod tests {
|
||||
type WeightMultiplierUpdate = ();
|
||||
type Event = Event;
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
}
|
||||
parameter_types! {
|
||||
pub const ExistentialDeposit: u64 = 0;
|
||||
|
||||
@@ -998,6 +998,8 @@ mod tests {
|
||||
pub struct Test;
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: u32 = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
}
|
||||
impl system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
@@ -1011,6 +1013,8 @@ mod tests {
|
||||
type WeightMultiplierUpdate = ();
|
||||
type Event = ();
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
}
|
||||
parameter_types! {
|
||||
pub const ExistentialDeposit: u64 = 0;
|
||||
|
||||
@@ -1108,6 +1108,8 @@ mod tests {
|
||||
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: u32 = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
}
|
||||
impl system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
@@ -1121,6 +1123,8 @@ mod tests {
|
||||
type Event = Event;
|
||||
type WeightMultiplierUpdate = ();
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
}
|
||||
parameter_types! {
|
||||
pub const ExistentialDeposit: u64 = 0;
|
||||
@@ -1212,7 +1216,7 @@ mod tests {
|
||||
}
|
||||
|
||||
pub type Block = primitives::generic::Block<Header, UncheckedExtrinsic>;
|
||||
pub type UncheckedExtrinsic = primitives::generic::UncheckedMortalCompactExtrinsic<u32, u64, Call, ()>;
|
||||
pub type UncheckedExtrinsic = primitives::generic::UncheckedExtrinsic<u32, u64, Call, ()>;
|
||||
|
||||
srml_support::construct_runtime!(
|
||||
pub enum Test where
|
||||
|
||||
@@ -255,7 +255,7 @@
|
||||
|
||||
use srml_support::{StorageValue, dispatch::Result, decl_module, decl_storage, decl_event};
|
||||
use system::{ensure_signed, ensure_root};
|
||||
use sr_primitives::weights::TransactionWeight;
|
||||
use sr_primitives::weights::SimpleDispatchInfo;
|
||||
|
||||
/// Our module's configuration trait. All our types and consts go in here. If the
|
||||
/// module is dependent on specific other modules, then their configuration traits
|
||||
@@ -396,19 +396,18 @@ decl_module! {
|
||||
//
|
||||
// If you don't respect these rules, it is likely that your chain will be attackable.
|
||||
//
|
||||
// Each transaction can optionally indicate a weight. The weight is passed in as a
|
||||
// custom attribute and the value can be anything that implements the `Weighable`
|
||||
// trait. Most often using substrate's default `TransactionWeight` is enough for you.
|
||||
// Each transaction can define an optional `#[weight]` attribute to convey a set of static
|
||||
// information about its dispatch. The `system` and `executive` module then use this
|
||||
// information to properly execute the transaction, whilst keeping the total load of the
|
||||
// chain in a moderate rate.
|
||||
//
|
||||
// A basic weight is a tuple of `(base_weight, byte_weight)`. Upon including each transaction
|
||||
// in a block, the final weight is calculated as `base_weight + byte_weight * tx_size`.
|
||||
// If this value, added to the weight of all included transactions, exceeds `MAX_TRANSACTION_WEIGHT`,
|
||||
// the transaction is not included. If no weight attribute is provided, the `::default()`
|
||||
// implementation of `TransactionWeight` is used.
|
||||
//
|
||||
// The example below showcases a transaction which is relatively costly, but less dependent on
|
||||
// the input, hence `byte_weight` is configured smaller.
|
||||
#[weight = TransactionWeight::Basic(100_000, 10)]
|
||||
// The _right-hand-side_ value of the `#[weight]` attribute can be any type that implements
|
||||
// a set of traits, namely [`WeighData`] and [`ClassifyDispatch`]. The former conveys the
|
||||
// weight (a numeric representation of pure execution time and difficulty) of the
|
||||
// transaction and the latter demonstrates the `DispatchClass` of the call. A higher weight
|
||||
// means a larger transaction (less of which can be placed in a single block). See the
|
||||
// `CheckWeight` signed extension struct in the `system` module for more information.
|
||||
#[weight = SimpleDispatchInfo::FixedNormal(10_000)]
|
||||
fn accumulate_dummy(origin, increase_by: T::Balance) -> Result {
|
||||
// This is a public call, so we ensure that the origin is some signed account.
|
||||
let _sender = ensure_signed(origin)?;
|
||||
@@ -525,6 +524,8 @@ mod tests {
|
||||
pub struct Test;
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: u32 = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
}
|
||||
impl system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
@@ -538,6 +539,8 @@ mod tests {
|
||||
type WeightMultiplierUpdate = ();
|
||||
type Event = ();
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
}
|
||||
parameter_types! {
|
||||
pub const ExistentialDeposit: u64 = 0;
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
//! # }
|
||||
//! # }
|
||||
//! /// Executive: handles dispatch to the various modules.
|
||||
//! pub type Executive = executive::Executive<Runtime, Block, Context, Balances, Runtime, AllModules>;
|
||||
//! pub type Executive = executive::Executive<Runtime, Block, Context, Runtime, AllModules>;
|
||||
//! ```
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
@@ -79,20 +79,17 @@ use rstd::marker::PhantomData;
|
||||
use rstd::result;
|
||||
use primitives::{generic::Digest, traits::{
|
||||
self, Header, Zero, One, Checkable, Applyable, CheckEqual, OnFinalize,
|
||||
OnInitialize, NumberFor, Block as BlockT, OffchainWorker,
|
||||
ValidateUnsigned,
|
||||
OnInitialize, NumberFor, Block as BlockT, OffchainWorker, ValidateUnsigned
|
||||
}};
|
||||
use srml_support::{Dispatchable, traits::MakePayment};
|
||||
use srml_support::Dispatchable;
|
||||
use parity_codec::{Codec, Encode};
|
||||
use system::{extrinsics_root, DigestOf};
|
||||
use primitives::{ApplyOutcome, ApplyError};
|
||||
use primitives::transaction_validity::{TransactionValidity, TransactionPriority, TransactionLongevity};
|
||||
use primitives::weights::{Weighable, MAX_TRANSACTIONS_WEIGHT};
|
||||
|
||||
mod mock;
|
||||
mod tests;
|
||||
use primitives::transaction_validity::TransactionValidity;
|
||||
use primitives::weights::GetDispatchInfo;
|
||||
|
||||
mod internal {
|
||||
use primitives::traits::DispatchError;
|
||||
|
||||
pub enum ApplyError {
|
||||
BadSignature(&'static str),
|
||||
@@ -106,6 +103,19 @@ mod internal {
|
||||
Success,
|
||||
Fail(&'static str),
|
||||
}
|
||||
|
||||
impl From<DispatchError> for ApplyError {
|
||||
fn from(d: DispatchError) -> Self {
|
||||
match d {
|
||||
DispatchError::Payment => ApplyError::CantPay,
|
||||
DispatchError::NoPermission => ApplyError::CantPay,
|
||||
DispatchError::BadState => ApplyError::CantPay,
|
||||
DispatchError::Stale => ApplyError::Stale,
|
||||
DispatchError::Future => ApplyError::Future,
|
||||
DispatchError::BadProof => ApplyError::BadSignature(""),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait that can be used to execute a block.
|
||||
@@ -118,27 +128,26 @@ pub type CheckedOf<E, C> = <E as Checkable<C>>::Checked;
|
||||
pub type CallOf<E, C> = <CheckedOf<E, C> as Applyable>::Call;
|
||||
pub type OriginOf<E, C> = <CallOf<E, C> as Dispatchable>::Origin;
|
||||
|
||||
pub struct Executive<System, Block, Context, Payment, UnsignedValidator, AllModules>(
|
||||
PhantomData<(System, Block, Context, Payment, UnsignedValidator, AllModules)>
|
||||
pub struct Executive<System, Block, Context, UnsignedValidator, AllModules>(
|
||||
PhantomData<(System, Block, Context, UnsignedValidator, AllModules)>
|
||||
);
|
||||
|
||||
impl<
|
||||
System: system::Trait,
|
||||
Block: traits::Block<Header=System::Header, Hash=System::Hash>,
|
||||
Context: Default,
|
||||
Payment: MakePayment<System::AccountId>,
|
||||
UnsignedValidator,
|
||||
AllModules: OnInitialize<System::BlockNumber> + OnFinalize<System::BlockNumber> + OffchainWorker<System::BlockNumber>,
|
||||
> ExecuteBlock<Block> for Executive<System, Block, Context, Payment, UnsignedValidator, AllModules>
|
||||
> ExecuteBlock<Block> for Executive<System, Block, Context, UnsignedValidator, AllModules>
|
||||
where
|
||||
Block::Extrinsic: Checkable<Context> + Codec,
|
||||
CheckedOf<Block::Extrinsic, Context>: Applyable<Index=System::Index, AccountId=System::AccountId> + Weighable,
|
||||
CheckedOf<Block::Extrinsic, Context>: Applyable<AccountId=System::AccountId> + GetDispatchInfo,
|
||||
CallOf<Block::Extrinsic, Context>: Dispatchable,
|
||||
OriginOf<Block::Extrinsic, Context>: From<Option<System::AccountId>>,
|
||||
UnsignedValidator: ValidateUnsigned<Call=CallOf<Block::Extrinsic, Context>>,
|
||||
{
|
||||
fn execute_block(block: Block) {
|
||||
Executive::<System, Block, Context, Payment, UnsignedValidator, AllModules>::execute_block(block);
|
||||
Executive::<System, Block, Context, UnsignedValidator, AllModules>::execute_block(block);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,13 +155,12 @@ impl<
|
||||
System: system::Trait,
|
||||
Block: traits::Block<Header=System::Header, Hash=System::Hash>,
|
||||
Context: Default,
|
||||
Payment: MakePayment<System::AccountId>,
|
||||
UnsignedValidator,
|
||||
AllModules: OnInitialize<System::BlockNumber> + OnFinalize<System::BlockNumber> + OffchainWorker<System::BlockNumber>,
|
||||
> Executive<System, Block, Context, Payment, UnsignedValidator, AllModules>
|
||||
> Executive<System, Block, Context, UnsignedValidator, AllModules>
|
||||
where
|
||||
Block::Extrinsic: Checkable<Context> + Codec,
|
||||
CheckedOf<Block::Extrinsic, Context>: Applyable<Index=System::Index, AccountId=System::AccountId> + Weighable,
|
||||
CheckedOf<Block::Extrinsic, Context>: Applyable<AccountId=System::AccountId> + GetDispatchInfo,
|
||||
CallOf<Block::Extrinsic, Context>: Dispatchable,
|
||||
OriginOf<Block::Extrinsic, Context>: From<Option<System::AccountId>>,
|
||||
UnsignedValidator: ValidateUnsigned<Call=CallOf<Block::Extrinsic, Context>>,
|
||||
@@ -266,39 +274,21 @@ where
|
||||
// Verify that the signature is good.
|
||||
let xt = uxt.check(&Default::default()).map_err(internal::ApplyError::BadSignature)?;
|
||||
|
||||
// Check the weight of the block if that extrinsic is applied.
|
||||
let weight = xt.weight(encoded_len);
|
||||
if <system::Module<System>>::all_extrinsics_weight() + weight > MAX_TRANSACTIONS_WEIGHT {
|
||||
return Err(internal::ApplyError::FullBlock);
|
||||
}
|
||||
|
||||
if let (Some(sender), Some(index)) = (xt.sender(), xt.index()) {
|
||||
// check index
|
||||
let expected_index = <system::Module<System>>::account_nonce(sender);
|
||||
if index != &expected_index { return Err(
|
||||
if index < &expected_index { internal::ApplyError::Stale } else { internal::ApplyError::Future }
|
||||
) }
|
||||
// pay any fees
|
||||
let weight_multiplier = <system::Module<System>>::next_weight_multiplier();
|
||||
Payment::make_payment(sender, weight_multiplier.apply_to(weight)).map_err(|_| internal::ApplyError::CantPay)?;
|
||||
|
||||
// AUDIT: Under no circumstances may this function panic from here onwards.
|
||||
// FIXME: ensure this at compile-time (such as by not defining a panic function, forcing
|
||||
// a linker error unless the compiler can prove it cannot be called).
|
||||
// increment nonce in storage
|
||||
<system::Module<System>>::inc_account_nonce(sender);
|
||||
}
|
||||
|
||||
// Make sure to `note_extrinsic` only after we know it's going to be executed
|
||||
// to prevent it from leaking in storage.
|
||||
// We don't need to make sure to `note_extrinsic` only after we know it's going to be
|
||||
// executed to prevent it from leaking in storage since at this point, it will either
|
||||
// execute or panic (and revert storage changes).
|
||||
if let Some(encoded) = to_note {
|
||||
<system::Module<System>>::note_extrinsic(encoded);
|
||||
}
|
||||
|
||||
// AUDIT: Under no circumstances may this function panic from here onwards.
|
||||
|
||||
// Decode parameters and dispatch
|
||||
let (f, s) = xt.deconstruct();
|
||||
let r = f.dispatch(s.into());
|
||||
<system::Module<System>>::note_applied_extrinsic(&r, weight);
|
||||
let dispatch_info = xt.get_dispatch_info();
|
||||
let r = Applyable::dispatch(xt, dispatch_info, encoded_len)
|
||||
.map_err(internal::ApplyError::from)?;
|
||||
|
||||
<system::Module<System>>::note_applied_extrinsic(&r, encoded_len as u32);
|
||||
|
||||
r.map(|_| internal::ApplyOutcome::Success).or_else(|e| match e {
|
||||
primitives::BLOCK_FULL => Err(internal::ApplyError::FullBlock),
|
||||
@@ -335,11 +325,9 @@ where
|
||||
pub fn validate_transaction(uxt: Block::Extrinsic) -> TransactionValidity {
|
||||
// Note errors > 0 are from ApplyError
|
||||
const UNKNOWN_ERROR: i8 = -127;
|
||||
const MISSING_SENDER: i8 = -20;
|
||||
const INVALID_INDEX: i8 = -10;
|
||||
|
||||
let encoded_len = uxt.encode().len();
|
||||
|
||||
let encoded_len = uxt.using_encoded(|d| d.len());
|
||||
let xt = match uxt.check(&Default::default()) {
|
||||
// Checks out. Carry on.
|
||||
Ok(xt) => xt,
|
||||
@@ -351,42 +339,8 @@ where
|
||||
Err(_) => return TransactionValidity::Invalid(UNKNOWN_ERROR),
|
||||
};
|
||||
|
||||
match (xt.sender(), xt.index()) {
|
||||
(Some(sender), Some(index)) => {
|
||||
let weight = xt.weight(encoded_len);
|
||||
|
||||
// pay any fees
|
||||
let weight_multiplier = <system::Module<System>>::next_weight_multiplier();
|
||||
if Payment::make_payment(sender, weight_multiplier.apply_to(weight)).is_err() {
|
||||
return TransactionValidity::Invalid(ApplyError::CantPay as i8)
|
||||
}
|
||||
|
||||
// check index
|
||||
let expected_index = <system::Module<System>>::account_nonce(sender);
|
||||
if index < &expected_index {
|
||||
return TransactionValidity::Invalid(ApplyError::Stale as i8)
|
||||
}
|
||||
|
||||
let index = *index;
|
||||
let provides = vec![(sender, index).encode()];
|
||||
let requires = if expected_index < index {
|
||||
vec![(sender, index - One::one()).encode()]
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
TransactionValidity::Valid {
|
||||
priority: encoded_len as TransactionPriority,
|
||||
requires,
|
||||
provides,
|
||||
longevity: TransactionLongevity::max_value(),
|
||||
propagate: true,
|
||||
}
|
||||
},
|
||||
(None, None) => UnsignedValidator::validate_unsigned(&xt.deconstruct().0),
|
||||
(Some(_), None) => TransactionValidity::Invalid(INVALID_INDEX),
|
||||
(None, Some(_)) => TransactionValidity::Invalid(MISSING_SENDER),
|
||||
}
|
||||
let dispatch_info = xt.get_dispatch_info();
|
||||
xt.validate::<UnsignedValidator>(dispatch_info, encoded_len)
|
||||
}
|
||||
|
||||
/// Start an offchain worker and generate extrinsics.
|
||||
@@ -394,3 +348,319 @@ where
|
||||
<AllModules as OffchainWorker<System::BlockNumber>>::generate_extrinsics(n)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use balances::Call;
|
||||
use runtime_io::with_externalities;
|
||||
use substrate_primitives::{H256, Blake2Hasher};
|
||||
use primitives::generic::Era;
|
||||
use primitives::traits::{Header as HeaderT, BlakeTwo256, IdentityLookup};
|
||||
use primitives::testing::{Digest, Header, Block};
|
||||
use srml_support::{impl_outer_event, impl_outer_origin, parameter_types};
|
||||
use srml_support::traits::{Currency, LockIdentifier, LockableCurrency, WithdrawReasons, WithdrawReason, Get};
|
||||
use system;
|
||||
use hex_literal::hex;
|
||||
|
||||
impl_outer_origin! {
|
||||
pub enum Origin for Runtime {
|
||||
}
|
||||
}
|
||||
|
||||
impl_outer_event!{
|
||||
pub enum MetaEvent for Runtime {
|
||||
balances<T>,
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct Runtime;
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: u32 = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
}
|
||||
impl system::Trait for Runtime {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Hash = substrate_primitives::H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<u64>;
|
||||
type Header = Header;
|
||||
type Event = MetaEvent;
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type WeightMultiplierUpdate = ();
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
}
|
||||
parameter_types! {
|
||||
pub const ExistentialDeposit: u64 = 0;
|
||||
pub const TransferFee: u64 = 0;
|
||||
pub const CreationFee: u64 = 0;
|
||||
pub const TransactionBaseFee: u64 = 10;
|
||||
pub const TransactionByteFee: u64 = 0;
|
||||
}
|
||||
impl balances::Trait for Runtime {
|
||||
type Balance = u64;
|
||||
type OnFreeBalanceZero = ();
|
||||
type OnNewAccount = ();
|
||||
type Event = MetaEvent;
|
||||
type TransactionPayment = ();
|
||||
type DustRemoval = ();
|
||||
type TransferPayment = ();
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type TransferFee = TransferFee;
|
||||
type CreationFee = CreationFee;
|
||||
type TransactionBaseFee = TransactionBaseFee;
|
||||
type TransactionByteFee = TransactionByteFee;
|
||||
}
|
||||
|
||||
impl ValidateUnsigned for Runtime {
|
||||
type Call = Call<Runtime>;
|
||||
|
||||
fn validate_unsigned(call: &Self::Call) -> TransactionValidity {
|
||||
match call {
|
||||
Call::set_balance(_, _, _) => TransactionValidity::Valid(Default::default()),
|
||||
_ => TransactionValidity::Invalid(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type SignedExtra = (
|
||||
system::CheckEra<Runtime>,
|
||||
system::CheckNonce<Runtime>,
|
||||
system::CheckWeight<Runtime>,
|
||||
balances::TakeFees<Runtime>
|
||||
);
|
||||
type TestXt = primitives::testing::TestXt<Call<Runtime>, SignedExtra>;
|
||||
type Executive = super::Executive<Runtime, Block<TestXt>, system::ChainContext<Runtime>, Runtime, ()>;
|
||||
|
||||
fn extra(nonce: u64, fee: u64) -> SignedExtra {
|
||||
(
|
||||
system::CheckEra::from(Era::Immortal),
|
||||
system::CheckNonce::from(nonce),
|
||||
system::CheckWeight::from(),
|
||||
balances::TakeFees::from(fee)
|
||||
)
|
||||
}
|
||||
|
||||
fn sign_extra(who: u64, nonce: u64, fee: u64) -> Option<(u64, SignedExtra)> {
|
||||
Some((who, extra(nonce, fee)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn balance_transfer_dispatch_works() {
|
||||
let mut t = system::GenesisConfig::default().build_storage::<Runtime>().unwrap();
|
||||
balances::GenesisConfig::<Runtime> {
|
||||
balances: vec![(1, 111)],
|
||||
vesting: vec![],
|
||||
}.assimilate_storage(&mut t.0, &mut t.1).unwrap();
|
||||
let xt = primitives::testing::TestXt(sign_extra(1, 0, 0), Call::transfer(2, 69));
|
||||
let weight = xt.get_dispatch_info().weight as u64;
|
||||
let mut t = runtime_io::TestExternalities::<Blake2Hasher>::new_with_children(t);
|
||||
with_externalities(&mut t, || {
|
||||
Executive::initialize_block(&Header::new(
|
||||
1,
|
||||
H256::default(),
|
||||
H256::default(),
|
||||
[69u8; 32].into(),
|
||||
Digest::default(),
|
||||
));
|
||||
let r = Executive::apply_extrinsic(xt);
|
||||
assert_eq!(r, Ok(ApplyOutcome::Success));
|
||||
assert_eq!(<balances::Module<Runtime>>::total_balance(&1), 42 - 10 - weight);
|
||||
assert_eq!(<balances::Module<Runtime>>::total_balance(&2), 69);
|
||||
});
|
||||
}
|
||||
|
||||
fn new_test_ext(balance_factor: u64) -> runtime_io::TestExternalities<Blake2Hasher> {
|
||||
let mut t = system::GenesisConfig::default().build_storage::<Runtime>().unwrap().0;
|
||||
t.extend(balances::GenesisConfig::<Runtime> {
|
||||
balances: vec![(1, 111 * balance_factor)],
|
||||
vesting: vec![],
|
||||
}.build_storage().unwrap().0);
|
||||
t.into()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_import_works() {
|
||||
with_externalities(&mut new_test_ext(1), || {
|
||||
Executive::execute_block(Block {
|
||||
header: Header {
|
||||
parent_hash: [69u8; 32].into(),
|
||||
number: 1,
|
||||
state_root: hex!("ba811447b8ae3bf798a07a18f5355ea59926917c8a9cc7527ede20b261aacfdf").into(),
|
||||
extrinsics_root: hex!("03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314").into(),
|
||||
digest: Digest { logs: vec![], },
|
||||
},
|
||||
extrinsics: vec![],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn block_import_of_bad_state_root_fails() {
|
||||
with_externalities(&mut new_test_ext(1), || {
|
||||
Executive::execute_block(Block {
|
||||
header: Header {
|
||||
parent_hash: [69u8; 32].into(),
|
||||
number: 1,
|
||||
state_root: [0u8; 32].into(),
|
||||
extrinsics_root: hex!("03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314").into(),
|
||||
digest: Digest { logs: vec![], },
|
||||
},
|
||||
extrinsics: vec![],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn block_import_of_bad_extrinsic_root_fails() {
|
||||
with_externalities(&mut new_test_ext(1), || {
|
||||
Executive::execute_block(Block {
|
||||
header: Header {
|
||||
parent_hash: [69u8; 32].into(),
|
||||
number: 1,
|
||||
state_root: hex!("49cd58a254ccf6abc4a023d9a22dcfc421e385527a250faec69f8ad0d8ed3e48").into(),
|
||||
extrinsics_root: [0u8; 32].into(),
|
||||
digest: Digest { logs: vec![], },
|
||||
},
|
||||
extrinsics: vec![],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_extrinsic_not_inserted() {
|
||||
let mut t = new_test_ext(1);
|
||||
// bad nonce check!
|
||||
let xt = primitives::testing::TestXt(sign_extra(1, 30, 0), Call::transfer(33, 69));
|
||||
with_externalities(&mut t, || {
|
||||
Executive::initialize_block(&Header::new(
|
||||
1,
|
||||
H256::default(),
|
||||
H256::default(),
|
||||
[69u8; 32].into(),
|
||||
Digest::default(),
|
||||
));
|
||||
assert!(Executive::apply_extrinsic(xt).is_err());
|
||||
assert_eq!(<system::Module<Runtime>>::extrinsic_index(), Some(0));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_weight_limit_enforced() {
|
||||
let mut t = new_test_ext(10000);
|
||||
// given: TestXt uses the encoded len as fixed Len:
|
||||
let xt = primitives::testing::TestXt(sign_extra(1, 0, 0), Call::transfer::<Runtime>(33, 0));
|
||||
let encoded = xt.encode();
|
||||
let encoded_len = encoded.len() as u32;
|
||||
let limit = <MaximumBlockWeight as Get<u32>>::get() / 4;
|
||||
let num_to_exhaust_block = limit / encoded_len;
|
||||
with_externalities(&mut t, || {
|
||||
Executive::initialize_block(&Header::new(
|
||||
1,
|
||||
H256::default(),
|
||||
H256::default(),
|
||||
[69u8; 32].into(),
|
||||
Digest::default(),
|
||||
));
|
||||
assert_eq!(<system::Module<Runtime>>::all_extrinsics_weight(), 0);
|
||||
|
||||
for nonce in 0..=num_to_exhaust_block {
|
||||
let xt = primitives::testing::TestXt(sign_extra(1, nonce.into(), 0), Call::transfer::<Runtime>(33, 0));
|
||||
let res = Executive::apply_extrinsic(xt);
|
||||
if nonce != num_to_exhaust_block {
|
||||
assert_eq!(res.unwrap(), ApplyOutcome::Success);
|
||||
assert_eq!(<system::Module<Runtime>>::all_extrinsics_weight(), encoded_len * (nonce + 1));
|
||||
assert_eq!(<system::Module<Runtime>>::extrinsic_index(), Some(nonce + 1));
|
||||
} else {
|
||||
assert_eq!(res, Err(ApplyError::CantPay));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_weight_and_size_is_stored_per_tx() {
|
||||
let xt = primitives::testing::TestXt(sign_extra(1, 0, 0), Call::transfer(33, 0));
|
||||
let x1 = primitives::testing::TestXt(sign_extra(1, 1, 0), Call::transfer(33, 0));
|
||||
let x2 = primitives::testing::TestXt(sign_extra(1, 2, 0), Call::transfer(33, 0));
|
||||
let len = xt.clone().encode().len() as u32;
|
||||
let mut t = new_test_ext(1);
|
||||
with_externalities(&mut t, || {
|
||||
assert_eq!(<system::Module<Runtime>>::all_extrinsics_weight(), 0);
|
||||
assert_eq!(<system::Module<Runtime>>::all_extrinsics_weight(), 0);
|
||||
|
||||
assert_eq!(Executive::apply_extrinsic(xt.clone()).unwrap(), ApplyOutcome::Success);
|
||||
assert_eq!(Executive::apply_extrinsic(x1.clone()).unwrap(), ApplyOutcome::Success);
|
||||
assert_eq!(Executive::apply_extrinsic(x2.clone()).unwrap(), ApplyOutcome::Success);
|
||||
|
||||
// default weight for `TestXt` == encoded length.
|
||||
assert_eq!( <system::Module<Runtime>>::all_extrinsics_weight(), 3 * len);
|
||||
assert_eq!(<system::Module<Runtime>>::all_extrinsics_len(), 3 * len);
|
||||
|
||||
let _ = <system::Module<Runtime>>::finalize();
|
||||
|
||||
assert_eq!(<system::Module<Runtime>>::all_extrinsics_weight(), 0);
|
||||
assert_eq!(<system::Module<Runtime>>::all_extrinsics_weight(), 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_unsigned() {
|
||||
let xt = primitives::testing::TestXt(None, Call::set_balance(33, 69, 69));
|
||||
let valid = TransactionValidity::Valid(Default::default());
|
||||
let mut t = new_test_ext(1);
|
||||
|
||||
with_externalities(&mut t, || {
|
||||
assert_eq!(Executive::validate_transaction(xt.clone()), valid);
|
||||
assert_eq!(Executive::apply_extrinsic(xt), Ok(ApplyOutcome::Fail));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_pay_for_tx_fee_on_full_lock() {
|
||||
let id: LockIdentifier = *b"0 ";
|
||||
let execute_with_lock = |lock: WithdrawReasons| {
|
||||
let mut t = new_test_ext(1);
|
||||
with_externalities(&mut t, || {
|
||||
<balances::Module<Runtime> as LockableCurrency<u64>>::set_lock(
|
||||
id,
|
||||
&1,
|
||||
110,
|
||||
10,
|
||||
lock,
|
||||
);
|
||||
let xt = primitives::testing::TestXt(sign_extra(1, 0, 0), Call::transfer(2, 10));
|
||||
let weight = xt.get_dispatch_info().weight as u64;
|
||||
Executive::initialize_block(&Header::new(
|
||||
1,
|
||||
H256::default(),
|
||||
H256::default(),
|
||||
[69u8; 32].into(),
|
||||
Digest::default(),
|
||||
));
|
||||
|
||||
if lock == WithdrawReasons::except(WithdrawReason::TransactionPayment) {
|
||||
assert_eq!(Executive::apply_extrinsic(xt).unwrap(), ApplyOutcome::Fail);
|
||||
// but tx fee has been deducted. the transaction failed on transfer, not on fee.
|
||||
assert_eq!(<balances::Module<Runtime>>::total_balance(&1), 111 - 10 - weight);
|
||||
} else {
|
||||
assert_eq!(Executive::apply_extrinsic(xt), Err(ApplyError::CantPay));
|
||||
assert_eq!(<balances::Module<Runtime>>::total_balance(&1), 111);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
execute_with_lock(WithdrawReasons::all());
|
||||
execute_with_lock(WithdrawReasons::except(WithdrawReason::TransactionPayment));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
// Copyright 2019 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Test utilities
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use super::*;
|
||||
use runtime_io;
|
||||
use substrate_primitives::{Blake2Hasher};
|
||||
use srml_support::{impl_outer_origin, impl_outer_event, impl_outer_dispatch, parameter_types};
|
||||
use primitives::traits::{IdentityLookup, BlakeTwo256};
|
||||
use primitives::testing::{Header, Block};
|
||||
use system;
|
||||
pub use balances::Call as balancesCall;
|
||||
pub use system::Call as systemCall;
|
||||
|
||||
impl_outer_origin! {
|
||||
pub enum Origin for Runtime {
|
||||
}
|
||||
}
|
||||
|
||||
impl_outer_event!{
|
||||
pub enum MetaEvent for Runtime {
|
||||
balances<T>,
|
||||
}
|
||||
}
|
||||
|
||||
impl_outer_dispatch! {
|
||||
pub enum Call for Runtime where origin: Origin {
|
||||
balances::Balances,
|
||||
system::System,
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct Runtime;
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
}
|
||||
|
||||
impl system::Trait for Runtime {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Hash = substrate_primitives::H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<u64>;
|
||||
type Header = Header;
|
||||
type WeightMultiplierUpdate = ();
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type Event = MetaEvent;
|
||||
}
|
||||
parameter_types! {
|
||||
pub const ExistentialDeposit: u64 = 0;
|
||||
pub const TransferFee: u64 = 0;
|
||||
pub const CreationFee: u64 = 0;
|
||||
pub const TransactionBaseFee: u64 = 0;
|
||||
pub const TransactionByteFee: u64 = 0;
|
||||
}
|
||||
impl balances::Trait for Runtime {
|
||||
type Balance = u64;
|
||||
type OnFreeBalanceZero = ();
|
||||
type OnNewAccount = ();
|
||||
type Event = MetaEvent;
|
||||
type TransactionPayment = ();
|
||||
type DustRemoval = ();
|
||||
type TransferPayment = ();
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type TransferFee = TransferFee;
|
||||
type CreationFee = CreationFee;
|
||||
type TransactionBaseFee = TransactionBaseFee;
|
||||
type TransactionByteFee = TransactionByteFee;
|
||||
}
|
||||
|
||||
impl ValidateUnsigned for Runtime {
|
||||
type Call = Call;
|
||||
|
||||
fn validate_unsigned(call: &Self::Call) -> TransactionValidity {
|
||||
match call {
|
||||
Call::Balances(balancesCall::set_balance(_, _, _)) => TransactionValidity::Valid {
|
||||
priority: 0,
|
||||
requires: vec![],
|
||||
provides: vec![],
|
||||
longevity: std::u64::MAX,
|
||||
propagate: false,
|
||||
},
|
||||
_ => TransactionValidity::Invalid(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_test_ext() -> runtime_io::TestExternalities<Blake2Hasher> {
|
||||
let mut t = system::GenesisConfig::default().build_storage::<Runtime>().unwrap().0;
|
||||
t.extend(balances::GenesisConfig::<Runtime> {
|
||||
balances: vec![(1, 1000_000_000)],
|
||||
vesting: vec![],
|
||||
}.build_storage().unwrap().0);
|
||||
t.into()
|
||||
}
|
||||
|
||||
type Balances = balances::Module<Runtime>;
|
||||
type System = system::Module<Runtime>;
|
||||
|
||||
pub type TestXt = primitives::testing::TestXt<Call>;
|
||||
pub type Executive = super::Executive<
|
||||
Runtime,
|
||||
Block<TestXt>,
|
||||
system::ChainContext<Runtime>,
|
||||
balances::Module<Runtime>,
|
||||
Runtime,
|
||||
()
|
||||
>;
|
||||
@@ -1,246 +0,0 @@
|
||||
// Copyright 2019 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Tests for the module.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use super::*;
|
||||
use mock::{new_test_ext, TestXt, Executive, Runtime, Call, systemCall, balancesCall};
|
||||
use system;
|
||||
use primitives::weights::{MAX_TRANSACTIONS_WEIGHT};
|
||||
use primitives::testing::{Digest, Header, Block};
|
||||
use primitives::traits::{Header as HeaderT};
|
||||
use substrate_primitives::{Blake2Hasher, H256};
|
||||
use runtime_io::with_externalities;
|
||||
use srml_support::traits::Currency;
|
||||
use hex_literal::hex;
|
||||
|
||||
#[test]
|
||||
fn balance_transfer_dispatch_works() {
|
||||
let mut t = system::GenesisConfig::default().build_storage::<Runtime>().unwrap().0;
|
||||
t.extend(balances::GenesisConfig::<Runtime> {
|
||||
balances: vec![(1, 129)],
|
||||
vesting: vec![],
|
||||
}.build_storage().unwrap().0);
|
||||
let xt = primitives::testing::TestXt(Some(1), 0, Call::Balances(balancesCall::transfer(2, 69)));
|
||||
let mut t = runtime_io::TestExternalities::<Blake2Hasher>::new(t);
|
||||
with_externalities(&mut t, || {
|
||||
Executive::initialize_block(
|
||||
&Header::new(1, H256::default(), H256::default(),[69u8; 32].into(), Digest::default())
|
||||
);
|
||||
assert_eq!(Executive::apply_extrinsic(xt.clone()).unwrap(), ApplyOutcome::Success);
|
||||
// default fee.
|
||||
assert_eq!(<balances::Module<Runtime>>::total_balance(&1), 129 - 69 - 29);
|
||||
assert_eq!(<balances::Module<Runtime>>::total_balance(&2), 69);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_import_works() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
Executive::execute_block(Block {
|
||||
header: Header {
|
||||
parent_hash: [69u8; 32].into(),
|
||||
number: 1,
|
||||
state_root: hex!("5518fc5383e35df8bf7cda7d6467d1307cc907424b7c8633148163aba5ee6aa8").into(),
|
||||
extrinsics_root: hex!("03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314").into(),
|
||||
digest: Digest { logs: vec![], },
|
||||
},
|
||||
extrinsics: vec![],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn block_import_of_bad_state_root_fails() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
Executive::execute_block(Block {
|
||||
header: Header {
|
||||
parent_hash: [69u8; 32].into(),
|
||||
number: 1,
|
||||
state_root: [0u8; 32].into(),
|
||||
extrinsics_root: hex!("03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314").into(),
|
||||
digest: Digest { logs: vec![], },
|
||||
},
|
||||
extrinsics: vec![],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn block_import_of_bad_extrinsic_root_fails() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
Executive::execute_block(Block {
|
||||
header: Header {
|
||||
parent_hash: [69u8; 32].into(),
|
||||
number: 1,
|
||||
state_root: hex!("49cd58a254ccf6abc4a023d9a22dcfc421e385527a250faec69f8ad0d8ed3e48").into(),
|
||||
extrinsics_root: [0u8; 32].into(),
|
||||
digest: Digest { logs: vec![], },
|
||||
},
|
||||
extrinsics: vec![],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_extrinsic_not_inserted() {
|
||||
let mut t = new_test_ext();
|
||||
let xt = primitives::testing::TestXt(Some(1), 42, Call::Balances(balancesCall::transfer(33, 69)));
|
||||
with_externalities(&mut t, || {
|
||||
Executive::initialize_block(&Header::new(1, H256::default(), H256::default(),
|
||||
[69u8; 32].into(), Digest::default()));
|
||||
assert!(Executive::apply_extrinsic(xt).is_err());
|
||||
assert_eq!(<system::Module<Runtime>>::extrinsic_index(), Some(0));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_weight_limit_enforced() {
|
||||
let run_test = |should_fail: bool| {
|
||||
let mut t = system::GenesisConfig::default().build_storage::<Runtime>().unwrap().0;
|
||||
t.extend(balances::GenesisConfig::<Runtime> {
|
||||
balances: vec![(1, 129)],
|
||||
vesting: vec![],
|
||||
}.build_storage().unwrap().0);
|
||||
let mut t = runtime_io::TestExternalities::<Blake2Hasher>::new(t);
|
||||
let xt = primitives::testing::TestXt(Some(1), 0, Call::Balances(balancesCall::transfer(1, 15)));
|
||||
let xt2 = primitives::testing::TestXt(Some(1), 1, Call::Balances(balancesCall::transfer(2, 30)));
|
||||
let encoded = xt2.encode();
|
||||
let len = if should_fail { (MAX_TRANSACTIONS_WEIGHT - 1) as usize } else { encoded.len() };
|
||||
with_externalities(&mut t, || {
|
||||
Executive::initialize_block(&Header::new(
|
||||
1,
|
||||
H256::default(),
|
||||
H256::default(),
|
||||
[69u8; 32].into(),
|
||||
Digest::default()
|
||||
));
|
||||
assert_eq!(<system::Module<Runtime>>::all_extrinsics_weight(), 0);
|
||||
assert_eq!(Executive::apply_extrinsic(xt).unwrap(), ApplyOutcome::Success);
|
||||
let res = Executive::apply_extrinsic_with_len(xt2, len, Some(encoded));
|
||||
|
||||
if should_fail {
|
||||
assert!(res.is_err());
|
||||
assert_eq!(<system::Module<Runtime>>::all_extrinsics_weight(), 28);
|
||||
assert_eq!(<system::Module<Runtime>>::extrinsic_index(), Some(1));
|
||||
} else {
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(<system::Module<Runtime>>::all_extrinsics_weight(), 56);
|
||||
assert_eq!(<system::Module<Runtime>>::extrinsic_index(), Some(2));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
run_test(false);
|
||||
run_test(true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exceeding_block_weight_fails() {
|
||||
let mut t = new_test_ext();
|
||||
let xt = |i: u32|
|
||||
primitives::testing::TestXt(
|
||||
Some(1),
|
||||
i.into(),
|
||||
Call::System(systemCall::remark(vec![0_u8; 1024 * 128]))
|
||||
);
|
||||
with_externalities(&mut t, || {
|
||||
let len = xt(0).clone().encode().len() as u32;
|
||||
let xts_to_overflow = MAX_TRANSACTIONS_WEIGHT / len;
|
||||
let xts: Vec<TestXt> = (0..xts_to_overflow).map(|i| xt(i) ).collect::<_>();
|
||||
|
||||
assert_eq!(<system::Module<Runtime>>::all_extrinsics_weight(), 0);
|
||||
let _ = xts.into_iter().for_each(|x| assert_eq!(
|
||||
Executive::apply_extrinsic(x).unwrap(),
|
||||
ApplyOutcome::Success
|
||||
));
|
||||
assert_eq!(<system::Module<Runtime>>::all_extrinsics_weight(), xts_to_overflow * len);
|
||||
|
||||
// next one will be rejected.
|
||||
assert_eq!(Executive::apply_extrinsic(xt(xts_to_overflow)).err().unwrap(), ApplyError::FullBlock);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_block_weight() {
|
||||
let len = primitives::testing::TestXt(Some(1), 0, Call::Balances(balancesCall::transfer(69, 10)))
|
||||
.encode()
|
||||
.len() as u32;
|
||||
let mut t = new_test_ext();
|
||||
with_externalities(&mut t, || {
|
||||
assert_eq!(
|
||||
Executive::apply_extrinsic(
|
||||
primitives::testing::TestXt(Some(1), 0, Call::Balances(balancesCall::transfer(69, 10)))
|
||||
).unwrap(),
|
||||
ApplyOutcome::Success
|
||||
);
|
||||
assert_eq!(
|
||||
Executive::apply_extrinsic(
|
||||
primitives::testing::TestXt(Some(1), 1, Call::Balances(balancesCall::transfer(69, 10)))
|
||||
).unwrap(),
|
||||
ApplyOutcome::Success
|
||||
);
|
||||
assert_eq!(
|
||||
Executive::apply_extrinsic(
|
||||
primitives::testing::TestXt(Some(1), 2, Call::Balances(balancesCall::transfer(69, 10)))
|
||||
).unwrap(),
|
||||
ApplyOutcome::Success
|
||||
);
|
||||
assert_eq!(
|
||||
<system::Module<Runtime>>::all_extrinsics_weight(),
|
||||
3 * (0 /*base*/ + len /*len*/ * 1 /*byte*/)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fail_on_bad_nonce() {
|
||||
let mut t = system::GenesisConfig::default().build_storage::<Runtime>().unwrap().0;
|
||||
t.extend(balances::GenesisConfig::<Runtime> {
|
||||
balances: vec![(1, 129)],
|
||||
vesting: vec![],
|
||||
}.build_storage().unwrap().0);
|
||||
let mut t = runtime_io::TestExternalities::<Blake2Hasher>::new(t);
|
||||
let xt = primitives::testing::TestXt(Some(1), 0, Call::Balances(balancesCall::transfer(1, 15)));
|
||||
let xt2 = primitives::testing::TestXt(Some(1), 10, Call::Balances(balancesCall::transfer(1, 15)));
|
||||
with_externalities(&mut t, || {
|
||||
Executive::apply_extrinsic(xt).unwrap();
|
||||
let res = Executive::apply_extrinsic(xt2);
|
||||
assert_eq!(res, Err(ApplyError::Future));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_unsigned() {
|
||||
let xt = primitives::testing::TestXt(None, 0, Call::Balances(balancesCall::set_balance(33, 69, 69)));
|
||||
let valid = TransactionValidity::Valid {
|
||||
priority: 0,
|
||||
requires: vec![],
|
||||
provides: vec![],
|
||||
longevity: 18446744073709551615,
|
||||
propagate: false,
|
||||
};
|
||||
let mut t = new_test_ext();
|
||||
|
||||
with_externalities(&mut t, || {
|
||||
assert_eq!(Executive::validate_transaction(xt.clone()), valid);
|
||||
assert_eq!(Executive::apply_extrinsic(xt), Ok(ApplyOutcome::Fail));
|
||||
});
|
||||
}
|
||||
@@ -299,6 +299,8 @@ mod tests {
|
||||
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: u32 = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
}
|
||||
impl system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
@@ -312,6 +314,8 @@ mod tests {
|
||||
type WeightMultiplierUpdate = ();
|
||||
type Event = ();
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
}
|
||||
parameter_types! {
|
||||
pub const WindowSize: u64 = 11;
|
||||
|
||||
@@ -1056,6 +1056,8 @@ impl<T: Subtrait> system::Trait for ElevatedTrait<T> {
|
||||
type Lookup = T::Lookup;
|
||||
type Header = T::Header;
|
||||
type Event = ();
|
||||
type MaximumBlockWeight = T::MaximumBlockWeight;
|
||||
type MaximumBlockLength = T::MaximumBlockLength;
|
||||
type WeightMultiplierUpdate = ();
|
||||
type BlockHashCount = T::BlockHashCount;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,8 @@ impl_outer_origin! {
|
||||
pub struct Test;
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: u32 = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
}
|
||||
impl system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
@@ -51,6 +53,8 @@ impl system::Trait for Test {
|
||||
type Lookup = IdentityLookup<u64>;
|
||||
type Header = Header;
|
||||
type Event = TestEvent;
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
type WeightMultiplierUpdate = ();
|
||||
type BlockHashCount = BlockHashCount;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ impl Trait for Test {
|
||||
}
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: u32 = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
}
|
||||
impl system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
@@ -56,6 +58,8 @@ impl system::Trait for Test {
|
||||
type WeightMultiplierUpdate = ();
|
||||
type Event = TestEvent;
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
}
|
||||
|
||||
mod grandpa {
|
||||
|
||||
@@ -77,7 +77,7 @@ use substrate_primitives::{
|
||||
use parity_codec::{Encode, Decode};
|
||||
use primitives::{
|
||||
ApplyError, traits::{Member, IsMember, Extrinsic as ExtrinsicT},
|
||||
transaction_validity::{TransactionValidity, TransactionLongevity},
|
||||
transaction_validity::{TransactionValidity, TransactionLongevity, ValidTransaction},
|
||||
};
|
||||
use rstd::prelude::*;
|
||||
use session::SessionIndex;
|
||||
@@ -411,13 +411,13 @@ impl<T: Trait> srml_support::unsigned::ValidateUnsigned for Module<T> {
|
||||
return TransactionValidity::Invalid(ApplyError::BadSignature as i8);
|
||||
}
|
||||
|
||||
return srml_support::unsigned::TransactionValidity::Valid {
|
||||
return TransactionValidity::Valid(ValidTransaction {
|
||||
priority: 0,
|
||||
requires: vec![],
|
||||
provides: vec![encoded_heartbeat],
|
||||
longevity: TransactionLongevity::max_value(),
|
||||
propagate: true,
|
||||
}
|
||||
})
|
||||
}
|
||||
TransactionValidity::Invalid(0)
|
||||
}
|
||||
|
||||
@@ -66,6 +66,8 @@ impl ResolveHint<u64, u64> for TestResolveHint {
|
||||
pub struct Runtime;
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: u32 = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
}
|
||||
impl system::Trait for Runtime {
|
||||
type Origin = Origin;
|
||||
@@ -79,6 +81,8 @@ impl system::Trait for Runtime {
|
||||
type WeightMultiplierUpdate = ();
|
||||
type Event = ();
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
}
|
||||
impl Trait for Runtime {
|
||||
type AccountIndex = u64;
|
||||
|
||||
@@ -109,6 +109,8 @@ pub fn set_next_validators(next: Vec<u64>) {
|
||||
pub struct Test;
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: u32 = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
pub const MinimumPeriod: u64 = 5;
|
||||
}
|
||||
impl system::Trait for Test {
|
||||
@@ -123,6 +125,8 @@ impl system::Trait for Test {
|
||||
type WeightMultiplierUpdate = ();
|
||||
type Event = ();
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
}
|
||||
impl timestamp::Trait for Test {
|
||||
type Moment = u64;
|
||||
|
||||
@@ -87,6 +87,8 @@ impl_outer_origin!{
|
||||
pub struct Test;
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: u32 = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
}
|
||||
impl system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
@@ -100,6 +102,8 @@ impl system::Trait for Test {
|
||||
type WeightMultiplierUpdate = ();
|
||||
type Event = ();
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
}
|
||||
parameter_types! {
|
||||
pub const TransferFee: Balance = 0;
|
||||
|
||||
@@ -20,7 +20,7 @@ bitmask = { version = "0.5", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.6.1"
|
||||
srml-system = { path = "../system", default-features = false }
|
||||
srml-system = { path = "../system" }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
|
||||
@@ -25,25 +25,18 @@ pub use srml_metadata::{
|
||||
FunctionMetadata, DecodeDifferent, DecodeDifferentArray, FunctionArgumentMetadata,
|
||||
ModuleConstantMetadata, DefaultByte, DefaultByteGetter,
|
||||
};
|
||||
pub use sr_primitives::weights::{TransactionWeight, Weighable, Weight};
|
||||
pub use sr_primitives::weights::{SimpleDispatchInfo, GetDispatchInfo, DispatchInfo, WeighData,
|
||||
ClassifyDispatch,
|
||||
TransactionPriority
|
||||
};
|
||||
pub use sr_primitives::traits::{Dispatchable, DispatchResult};
|
||||
|
||||
/// A type that cannot be instantiated.
|
||||
pub enum Never {}
|
||||
|
||||
/// Result of a module function call; either nothing (functions are only called for "side effects")
|
||||
/// or an error message.
|
||||
pub type Result = result::Result<(), &'static str>;
|
||||
|
||||
/// A lazy call (module function and argument values) that can be executed via its `dispatch`
|
||||
/// method.
|
||||
pub trait Dispatchable {
|
||||
/// Every function call from your runtime has an origin, which specifies where the extrinsic was
|
||||
/// generated from. In the case of a signed extrinsic (transaction), the origin contains an
|
||||
/// identifier for the caller. The origin can be empty in the case of an inherent extrinsic.
|
||||
type Origin;
|
||||
type Trait;
|
||||
fn dispatch(self, origin: Self::Origin) -> Result;
|
||||
}
|
||||
pub type Result = DispatchResult;
|
||||
|
||||
/// Serializable version of Dispatchable.
|
||||
/// This value can be used as a "function" in an extrinsic.
|
||||
@@ -593,7 +586,7 @@ macro_rules! decl_module {
|
||||
{ $( $constants )* }
|
||||
[ $( $dispatchables )* ]
|
||||
$(#[doc = $doc_attr])*
|
||||
#[weight = $crate::dispatch::TransactionWeight::default()]
|
||||
#[weight = $crate::dispatch::SimpleDispatchInfo::default()]
|
||||
$fn_vis fn $fn_name(
|
||||
$from $(, $(#[$codec_attr])* $param_name : $param )*
|
||||
) $( -> $result )* { $( $impl )* }
|
||||
@@ -1117,14 +1110,38 @@ macro_rules! decl_module {
|
||||
}
|
||||
|
||||
// Implement weight calculation function for Call
|
||||
impl<$trait_instance: $trait_name $(<I>, $instance: $instantiable)?> $crate::dispatch::Weighable
|
||||
impl<$trait_instance: $trait_name $(<I>, $instance: $instantiable)?> $crate::dispatch::GetDispatchInfo
|
||||
for $call_type<$trait_instance $(, $instance)?> where $( $other_where_bounds )*
|
||||
{
|
||||
fn weight(&self, _len: usize) -> $crate::dispatch::Weight {
|
||||
match self {
|
||||
$( $call_type::$fn_name(..) => $crate::dispatch::Weighable::weight(&$weight, _len), )*
|
||||
$call_type::__PhantomItem(_, _) => { unreachable!("__PhantomItem should never be used.") },
|
||||
}
|
||||
fn get_dispatch_info(&self) -> $crate::dispatch::DispatchInfo {
|
||||
$(
|
||||
if let $call_type::$fn_name($( ref $param_name ),*) = self {
|
||||
let weight = <dyn $crate::dispatch::WeighData<( $( & $param, )* )>>::weigh_data(
|
||||
&$weight,
|
||||
($( $param_name, )*)
|
||||
);
|
||||
let class = <dyn $crate::dispatch::ClassifyDispatch<( $( & $param, )* )>>::classify_dispatch(
|
||||
&$weight,
|
||||
($( $param_name, )*)
|
||||
);
|
||||
return $crate::dispatch::DispatchInfo { weight, class };
|
||||
}
|
||||
if let $call_type::__PhantomItem(_, _) = self { unreachable!("__PhantomItem should never be used.") }
|
||||
)*
|
||||
// Defensive only: this function must have already returned at this point.
|
||||
// all dispatchable function will have a weight which has the `::default`
|
||||
// implementation of `SimpleDispatchInfo`. Nonetheless, we create one if it does
|
||||
// not exist.
|
||||
let weight = <dyn $crate::dispatch::WeighData<_>>::weigh_data(
|
||||
&$crate::dispatch::SimpleDispatchInfo::default(),
|
||||
()
|
||||
);
|
||||
let class = <dyn $crate::dispatch::ClassifyDispatch<_>>::classify_dispatch(
|
||||
&$crate::dispatch::SimpleDispatchInfo::default(),
|
||||
()
|
||||
);
|
||||
$crate::dispatch::DispatchInfo { weight, class }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1269,10 +1286,10 @@ macro_rules! impl_outer_dispatch {
|
||||
$camelcase ( $crate::dispatch::CallableCallFor<$camelcase, $runtime> )
|
||||
,)*
|
||||
}
|
||||
impl $crate::dispatch::Weighable for $call_type {
|
||||
fn weight(&self, len: usize) -> $crate::dispatch::Weight {
|
||||
impl $crate::dispatch::GetDispatchInfo for $call_type {
|
||||
fn get_dispatch_info(&self) -> $crate::dispatch::DispatchInfo {
|
||||
match self {
|
||||
$( $call_type::$camelcase(call) => call.weight(len), )*
|
||||
$( $call_type::$camelcase(call) => call.get_dispatch_info(), )*
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1579,6 +1596,7 @@ macro_rules! __check_reserved_fn_name {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::runtime_primitives::traits::{OnInitialize, OnFinalize};
|
||||
use sr_primitives::weights::{DispatchInfo, DispatchClass};
|
||||
|
||||
pub trait Trait: system::Trait + Sized where Self::AccountId: From<u32> {
|
||||
type Origin;
|
||||
@@ -1604,7 +1622,7 @@ mod tests {
|
||||
fn aux_0(_origin) -> Result { unreachable!() }
|
||||
fn aux_1(_origin, #[compact] _data: u32) -> Result { unreachable!() }
|
||||
fn aux_2(_origin, _data: i32, _data2: String) -> Result { unreachable!() }
|
||||
#[weight = TransactionWeight::Basic(10, 100)]
|
||||
#[weight = SimpleDispatchInfo::FixedNormal(10)]
|
||||
fn aux_3(_origin) -> Result { unreachable!() }
|
||||
fn aux_4(_origin, _data: i32) -> Result { unreachable!() }
|
||||
fn aux_5(_origin, _data: i32, #[compact] _data2: u32) -> Result { unreachable!() }
|
||||
@@ -1613,8 +1631,8 @@ mod tests {
|
||||
fn on_finalize(n: T::BlockNumber) { if n.into() == 42 { panic!("on_finalize") } }
|
||||
fn offchain_worker() {}
|
||||
|
||||
#[weight = TransactionWeight::Max]
|
||||
fn weighted(_origin) { unreachable!() }
|
||||
#[weight = SimpleDispatchInfo::FixedOperational(5)]
|
||||
fn operational(_origin) { unreachable!() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1680,7 +1698,7 @@ mod tests {
|
||||
documentation: DecodeDifferent::Encode(&[]),
|
||||
},
|
||||
FunctionMetadata {
|
||||
name: DecodeDifferent::Encode("weighted"),
|
||||
name: DecodeDifferent::Encode("operational"),
|
||||
arguments: DecodeDifferent::Encode(&[]),
|
||||
documentation: DecodeDifferent::Encode(&[]),
|
||||
},
|
||||
@@ -1755,10 +1773,19 @@ mod tests {
|
||||
#[test]
|
||||
fn weight_should_attach_to_call_enum() {
|
||||
// max weight. not dependent on input.
|
||||
assert_eq!(Call::<TraitImpl>::weighted().weight(100), 3 * 1024 * 1024);
|
||||
assert_eq!(
|
||||
Call::<TraitImpl>::operational().get_dispatch_info(),
|
||||
DispatchInfo { weight: 5, class: DispatchClass::Operational },
|
||||
);
|
||||
// default weight.
|
||||
assert_eq!(Call::<TraitImpl>::aux_0().weight(5), 5 /*tx-len*/);
|
||||
assert_eq!(
|
||||
Call::<TraitImpl>::aux_0().get_dispatch_info(),
|
||||
DispatchInfo { weight: 100, class: DispatchClass::Normal },
|
||||
);
|
||||
// custom basic
|
||||
assert_eq!(Call::<TraitImpl>::aux_3().weight(5), 10 + 100 * 5 );
|
||||
assert_eq!(
|
||||
Call::<TraitImpl>::aux_3().get_dispatch_info(),
|
||||
DispatchInfo { weight: 10, class: DispatchClass::Normal },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ use crate::codec::{Codec, Encode, Decode};
|
||||
use substrate_primitives::u32_trait::Value as U32;
|
||||
use crate::runtime_primitives::traits::{MaybeSerializeDebug, SimpleArithmetic, Saturating};
|
||||
use crate::runtime_primitives::ConsensusEngineId;
|
||||
use crate::runtime_primitives::weights::Weight;
|
||||
|
||||
use super::for_each_tuple;
|
||||
|
||||
@@ -90,19 +89,6 @@ pub enum UpdateBalanceOutcome {
|
||||
AccountKilled,
|
||||
}
|
||||
|
||||
/// Simple trait designed for hooking into a transaction payment.
|
||||
///
|
||||
/// It operates over a single generic `AccountId` type.
|
||||
pub trait MakePayment<AccountId> {
|
||||
/// Make transaction payment from `who` for an extrinsic of encoded length
|
||||
/// `encoded_len` bytes. Return `Ok` iff the payment was successful.
|
||||
fn make_payment(who: &AccountId, weight: Weight) -> Result<(), &'static str>;
|
||||
}
|
||||
|
||||
impl<T> MakePayment<T> for () {
|
||||
fn make_payment(_: &T, _: Weight) -> Result<(), &'static str> { Ok(()) }
|
||||
}
|
||||
|
||||
/// A trait for finding the author of a block header based on the `PreRuntime` digests contained
|
||||
/// within it.
|
||||
pub trait FindAuthor<Author> {
|
||||
|
||||
@@ -269,7 +269,7 @@ srml_support::construct_runtime!(
|
||||
|
||||
pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
|
||||
pub type Block = generic::Block<Header, UncheckedExtrinsic>;
|
||||
pub type UncheckedExtrinsic = generic::UncheckedMortalCompactExtrinsic<u32, Index, Call, Signature>;
|
||||
pub type UncheckedExtrinsic = generic::UncheckedExtrinsic<u32, Call, Signature, ()>;
|
||||
|
||||
fn new_test_ext() -> runtime_io::TestExternalities<Blake2Hasher> {
|
||||
GenesisConfig{
|
||||
@@ -407,4 +407,4 @@ fn storage_with_instance_basic_operation() {
|
||||
DoubleMap::remove(key1, key2);
|
||||
assert_eq!(DoubleMap::get(key1, key2), 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ pub type BlockNumber = u64;
|
||||
pub type Index = u64;
|
||||
pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
|
||||
pub type Block = generic::Block<Header, UncheckedExtrinsic>;
|
||||
pub type UncheckedExtrinsic = generic::UncheckedMortalCompactExtrinsic<u32, Index, Call, Signature>;
|
||||
pub type UncheckedExtrinsic = generic::UncheckedExtrinsic<u32, Call, Signature, ()>;
|
||||
|
||||
impl system::Trait for Runtime {
|
||||
type Hash = H256;
|
||||
@@ -183,4 +183,4 @@ fn create_genesis_config() {
|
||||
enable_storage_role: true,
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,16 +76,19 @@ use serde::Serialize;
|
||||
use rstd::prelude::*;
|
||||
#[cfg(any(feature = "std", test))]
|
||||
use rstd::map;
|
||||
use primitives::weights::{Weight, WeightMultiplier};
|
||||
use primitives::{generic, traits::{self, CheckEqual, SimpleArithmetic,
|
||||
use rstd::marker::PhantomData;
|
||||
use primitives::generic::{self, Era};
|
||||
use primitives::weights::{Weight, DispatchInfo, DispatchClass, WeightMultiplier};
|
||||
use primitives::transaction_validity::{ValidTransaction, TransactionPriority, TransactionLongevity};
|
||||
use primitives::traits::{self, CheckEqual, SimpleArithmetic, Zero, SignedExtension, Convert,
|
||||
SimpleBitOps, Hash, Member, MaybeDisplay, EnsureOrigin, CurrentHeight, BlockNumberToHash,
|
||||
MaybeSerializeDebugButNotDeserialize, MaybeSerializeDebug, StaticLookup, One, Bounded, Lookup,
|
||||
Zero, Convert,
|
||||
}};
|
||||
MaybeSerializeDebugButNotDeserialize, MaybeSerializeDebug, StaticLookup, One, Bounded,
|
||||
Lookup, DispatchError, SaturatedConversion,
|
||||
};
|
||||
use substrate_primitives::storage::well_known_keys;
|
||||
use srml_support::{
|
||||
storage, decl_module, decl_event, decl_storage, StorageDoubleMap, StorageValue,
|
||||
StorageMap, Parameter, for_each_tuple, traits::{Contains, Get},
|
||||
storage, decl_module, decl_event, decl_storage, StorageDoubleMap, StorageValue, StorageMap,
|
||||
Parameter, for_each_tuple, traits::{Contains, Get}
|
||||
};
|
||||
use safe_mix::TripletMix;
|
||||
use parity_codec::{Encode, Decode};
|
||||
@@ -194,6 +197,12 @@ pub trait Trait: 'static + Eq + Clone {
|
||||
|
||||
/// Maximum number of block number to block hash mappings to keep (oldest pruned first).
|
||||
type BlockHashCount: Get<Self::BlockNumber>;
|
||||
|
||||
/// The maximum weight of a block.
|
||||
type MaximumBlockWeight: Get<Weight>;
|
||||
|
||||
/// The maximum length of a block (in bytes).
|
||||
type MaximumBlockLength: Get<u32>;
|
||||
}
|
||||
|
||||
pub type DigestOf<T> = generic::Digest<<T as Trait>::Hash>;
|
||||
@@ -325,6 +334,8 @@ decl_storage! {
|
||||
ExtrinsicCount: Option<u32>;
|
||||
/// Total weight for all extrinsics put together, for the current block.
|
||||
AllExtrinsicsWeight: Option<Weight>;
|
||||
/// Total length (in bytes) for all extrinsics put together, for the current block.
|
||||
AllExtrinsicsLen: Option<u32>;
|
||||
/// The next weight multiplier. This should be updated at the end of each block based on the
|
||||
/// saturation level (weight).
|
||||
pub NextWeightMultiplier get(next_weight_multiplier): WeightMultiplier = Default::default();
|
||||
@@ -549,6 +560,10 @@ impl<T: Trait> Module<T> {
|
||||
AllExtrinsicsWeight::get().unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn all_extrinsics_len() -> u32 {
|
||||
AllExtrinsicsLen::get().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Update the next weight multiplier.
|
||||
///
|
||||
/// This should be called at then end of each block, before `all_extrinsics_weight` is cleared.
|
||||
@@ -590,6 +605,7 @@ impl<T: Trait> Module<T> {
|
||||
ExtrinsicCount::kill();
|
||||
Self::update_weight_multiplier();
|
||||
AllExtrinsicsWeight::kill();
|
||||
AllExtrinsicsLen::kill();
|
||||
|
||||
let number = <Number<T>>::take();
|
||||
let parent_hash = <ParentHash<T>>::take();
|
||||
@@ -744,17 +760,15 @@ impl<T: Trait> Module<T> {
|
||||
}
|
||||
|
||||
/// To be called immediately after an extrinsic has been applied.
|
||||
pub fn note_applied_extrinsic(r: &Result<(), &'static str>, weight: Weight) {
|
||||
pub fn note_applied_extrinsic(r: &Result<(), &'static str>, _encoded_len: u32) {
|
||||
Self::deposit_event(match r {
|
||||
Ok(_) => Event::ExtrinsicSuccess,
|
||||
Err(_) => Event::ExtrinsicFailed,
|
||||
}.into());
|
||||
|
||||
let next_extrinsic_index = Self::extrinsic_index().unwrap_or_default() + 1u32;
|
||||
let total_weight = weight.saturating_add(Self::all_extrinsics_weight());
|
||||
|
||||
storage::unhashed::put(well_known_keys::EXTRINSIC_INDEX, &next_extrinsic_index);
|
||||
AllExtrinsicsWeight::put(&total_weight);
|
||||
}
|
||||
|
||||
/// To be called immediately after `note_applied_extrinsic` of the last extrinsic of the block
|
||||
@@ -772,6 +786,207 @@ impl<T: Trait> Module<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// resource limit check.
|
||||
#[derive(Encode, Decode, Clone, Eq, PartialEq)]
|
||||
pub struct CheckWeight<T: Trait + Send + Sync>(PhantomData<T>);
|
||||
|
||||
impl<T: Trait + Send + Sync> CheckWeight<T> {
|
||||
|
||||
/// Get the quota divisor of each dispatch class type. This indicates that all operational
|
||||
/// dispatches can use the full capacity of any resource, while user-triggered ones can consume
|
||||
/// a quarter.
|
||||
fn get_dispatch_limit_divisor(class: DispatchClass) -> Weight {
|
||||
match class {
|
||||
DispatchClass::Operational => 1,
|
||||
DispatchClass::Normal => 4,
|
||||
}
|
||||
}
|
||||
/// Checks if the current extrinsic can fit into the block with respect to block weight limits.
|
||||
///
|
||||
/// Upon successes, it returns the new block weight as a `Result`.
|
||||
fn check_weight(info: DispatchInfo) -> Result<Weight, DispatchError> {
|
||||
let current_weight = Module::<T>::all_extrinsics_weight();
|
||||
let maximum_weight = T::MaximumBlockWeight::get();
|
||||
let limit = maximum_weight / Self::get_dispatch_limit_divisor(info.class);
|
||||
let added_weight = info.weight.min(limit);
|
||||
let next_weight = current_weight.saturating_add(added_weight);
|
||||
if next_weight > limit {
|
||||
return Err(DispatchError::BadState)
|
||||
}
|
||||
Ok(next_weight)
|
||||
}
|
||||
|
||||
/// Checks if the current extrinsic can fit into the block with respect to block length limits.
|
||||
///
|
||||
/// Upon successes, it returns the new block length as a `Result`.
|
||||
fn check_block_length(info: DispatchInfo, len: usize) -> Result<u32, DispatchError> {
|
||||
let current_len = Module::<T>::all_extrinsics_len();
|
||||
let maximum_len = T::MaximumBlockLength::get();
|
||||
let limit = maximum_len / Self::get_dispatch_limit_divisor(info.class);
|
||||
let added_len = len as u32;
|
||||
let next_len = current_len.saturating_add(added_len);
|
||||
if next_len > limit {
|
||||
return Err(DispatchError::BadState)
|
||||
}
|
||||
Ok(next_len)
|
||||
}
|
||||
|
||||
/// get the priority of an extrinsic denoted by `info`.
|
||||
fn get_priority(info: DispatchInfo) -> TransactionPriority {
|
||||
match info.class {
|
||||
DispatchClass::Normal => info.weight.into(),
|
||||
DispatchClass::Operational => Bounded::max_value()
|
||||
}
|
||||
}
|
||||
|
||||
/// Utility constructor for tests and client code.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn from() -> Self {
|
||||
Self(PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait + Send + Sync> SignedExtension for CheckWeight<T> {
|
||||
type AccountId = T::AccountId;
|
||||
type AdditionalSigned = ();
|
||||
|
||||
fn additional_signed(&self) -> rstd::result::Result<(), &'static str> { Ok(()) }
|
||||
|
||||
fn pre_dispatch(
|
||||
self,
|
||||
_who: &Self::AccountId,
|
||||
info: DispatchInfo,
|
||||
len: usize,
|
||||
) -> Result<(), DispatchError> {
|
||||
let next_len = Self::check_block_length(info, len)?;
|
||||
AllExtrinsicsLen::put(next_len);
|
||||
let next_weight = Self::check_weight(info)?;
|
||||
AllExtrinsicsWeight::put(next_weight);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate(
|
||||
&self,
|
||||
_who: &Self::AccountId,
|
||||
info: DispatchInfo,
|
||||
len: usize,
|
||||
) -> Result<ValidTransaction, DispatchError> {
|
||||
// There is no point in writing to storage here since changes are discarded. This basically
|
||||
// discards any transaction which is bigger than the length or weight limit alone, which is
|
||||
// a guarantee that it will fail in the pre-dispatch phase.
|
||||
let _ = Self::check_block_length(info, len)?;
|
||||
let _ = Self::check_weight(info)?;
|
||||
Ok(ValidTransaction { priority: Self::get_priority(info), ..Default::default() })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<T: Trait + Send + Sync> rstd::fmt::Debug for CheckWeight<T> {
|
||||
fn fmt(&self, f: &mut rstd::fmt::Formatter) -> rstd::fmt::Result {
|
||||
write!(f, "CheckWeight<T>")
|
||||
}
|
||||
}
|
||||
|
||||
/// Nonce check and increment to give replay protection for transactions.
|
||||
#[derive(Encode, Decode, Clone, Eq, PartialEq)]
|
||||
pub struct CheckNonce<T: Trait>(#[codec(compact)] T::Index);
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<T: Trait> CheckNonce<T> {
|
||||
/// utility constructor. Used only in client/factory code.
|
||||
pub fn from(nonce: T::Index) -> Self {
|
||||
Self(nonce)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<T: Trait> rstd::fmt::Debug for CheckNonce<T> {
|
||||
fn fmt(&self, f: &mut rstd::fmt::Formatter) -> rstd::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> SignedExtension for CheckNonce<T> {
|
||||
type AccountId = T::AccountId;
|
||||
type AdditionalSigned = ();
|
||||
|
||||
fn additional_signed(&self) -> rstd::result::Result<(), &'static str> { Ok(()) }
|
||||
|
||||
fn pre_dispatch(
|
||||
self,
|
||||
who: &Self::AccountId,
|
||||
_info: DispatchInfo,
|
||||
_len: usize,
|
||||
) -> Result<(), DispatchError> {
|
||||
let expected = <AccountNonce<T>>::get(who);
|
||||
if self.0 != expected {
|
||||
return Err(
|
||||
if self.0 < expected { DispatchError::Stale } else { DispatchError::Future }
|
||||
)
|
||||
}
|
||||
<AccountNonce<T>>::insert(who, expected + T::Index::one());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate(
|
||||
&self,
|
||||
who: &Self::AccountId,
|
||||
info: DispatchInfo,
|
||||
_len: usize,
|
||||
) -> Result<ValidTransaction, DispatchError> {
|
||||
// check index
|
||||
let expected = <AccountNonce<T>>::get(who);
|
||||
if self.0 < expected {
|
||||
return Err(DispatchError::Stale)
|
||||
}
|
||||
|
||||
let provides = vec![Encode::encode(&(who, self.0))];
|
||||
let requires = if expected < self.0 {
|
||||
vec![Encode::encode(&(who, self.0 - One::one()))]
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
Ok(ValidTransaction {
|
||||
priority: info.weight as TransactionPriority,
|
||||
requires,
|
||||
provides,
|
||||
longevity: TransactionLongevity::max_value(),
|
||||
propagate: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Nonce check and increment to give replay protection for transactions.
|
||||
#[derive(Encode, Decode, Clone, Eq, PartialEq)]
|
||||
pub struct CheckEra<T: Trait + Send + Sync>((Era, rstd::marker::PhantomData<T>));
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<T: Trait + Send + Sync> CheckEra<T> {
|
||||
/// utility constructor. Used only in client/factory code.
|
||||
pub fn from(era: Era) -> Self {
|
||||
Self((era, rstd::marker::PhantomData))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<T: Trait + Send + Sync> rstd::fmt::Debug for CheckEra<T> {
|
||||
fn fmt(&self, f: &mut rstd::fmt::Formatter) -> rstd::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait + Send + Sync> SignedExtension for CheckEra<T> {
|
||||
type AccountId = T::AccountId;
|
||||
type AdditionalSigned = T::Hash;
|
||||
fn additional_signed(&self) -> Result<Self::AdditionalSigned, &'static str> {
|
||||
let current_u64 = <Module<T>>::block_number().saturated_into::<u64>();
|
||||
let n = (self.0).0.birth(current_u64).saturated_into::<T::BlockNumber>();
|
||||
if !<BlockHash<T>>::exists(n) { Err("transaction birth block ancient")? }
|
||||
Ok(<Module<T>>::block_hash(n))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ChainContext<T>(::rstd::marker::PhantomData<T>);
|
||||
impl<T> Default for ChainContext<T> {
|
||||
fn default() -> Self {
|
||||
@@ -819,6 +1034,8 @@ mod tests {
|
||||
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 10;
|
||||
pub const MaximumBlockWeight: Weight = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
}
|
||||
|
||||
impl Trait for Test {
|
||||
@@ -833,6 +1050,8 @@ mod tests {
|
||||
type WeightMultiplierUpdate = ();
|
||||
type Event = u16;
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
}
|
||||
|
||||
impl From<Event> for u16 {
|
||||
@@ -983,4 +1202,147 @@ mod tests {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signed_ext_check_nonce_works() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
<AccountNonce<Test>>::insert(1, 1);
|
||||
let info = DispatchInfo::default();
|
||||
let len = 0_usize;
|
||||
// stale
|
||||
assert!(CheckNonce::<Test>(0).validate(&1, info, len).is_err());
|
||||
assert!(CheckNonce::<Test>(0).pre_dispatch(&1, info, len).is_err());
|
||||
// correct
|
||||
assert!(CheckNonce::<Test>(1).validate(&1, info, len).is_ok());
|
||||
assert!(CheckNonce::<Test>(1).pre_dispatch(&1, info, len).is_ok());
|
||||
// future
|
||||
assert!(CheckNonce::<Test>(5).validate(&1, info, len).is_ok());
|
||||
assert!(CheckNonce::<Test>(5).pre_dispatch(&1, info, len).is_err());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signed_ext_check_weight_works_user_tx() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
let small = DispatchInfo { weight: 100, ..Default::default() };
|
||||
let medium = DispatchInfo {
|
||||
weight: <MaximumBlockWeight as Get<Weight>>::get() / 4 - 1,
|
||||
..Default::default()
|
||||
};
|
||||
let big = DispatchInfo {
|
||||
weight: <MaximumBlockWeight as Get<Weight>>::get() / 4 + 1,
|
||||
..Default::default()
|
||||
};
|
||||
let len = 0_usize;
|
||||
|
||||
let reset_check_weight = |i, f, s| {
|
||||
AllExtrinsicsWeight::put(s);
|
||||
let r = CheckWeight::<Test>(PhantomData).pre_dispatch(&1, i, len);
|
||||
if f { assert!(r.is_err()) } else { assert!(r.is_ok()) }
|
||||
};
|
||||
|
||||
reset_check_weight(small, false, 0);
|
||||
reset_check_weight(medium, false, 0);
|
||||
reset_check_weight(big, true, 1);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signed_ext_check_weight_fee_works() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
let free = DispatchInfo { weight: 0, ..Default::default() };
|
||||
let len = 0_usize;
|
||||
|
||||
assert_eq!(System::all_extrinsics_weight(), 0);
|
||||
let r = CheckWeight::<Test>(PhantomData).pre_dispatch(&1, free, len);
|
||||
assert!(r.is_ok());
|
||||
assert_eq!(System::all_extrinsics_weight(), 0);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signed_ext_check_weight_max_works() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
let max = DispatchInfo { weight: Weight::max_value(), ..Default::default() };
|
||||
let len = 0_usize;
|
||||
|
||||
assert_eq!(System::all_extrinsics_weight(), 0);
|
||||
let r = CheckWeight::<Test>(PhantomData).pre_dispatch(&1, max, len);
|
||||
assert!(r.is_ok());
|
||||
assert_eq!(System::all_extrinsics_weight(), <MaximumBlockWeight as Get<Weight>>::get() / 4);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signed_ext_check_weight_works_operational_tx() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
let normal = DispatchInfo { weight: 100, ..Default::default() };
|
||||
let op = DispatchInfo { weight: 100, class: DispatchClass::Operational };
|
||||
let len = 0_usize;
|
||||
|
||||
// given almost full block
|
||||
AllExtrinsicsWeight::put(<MaximumBlockWeight as Get<Weight>>::get() / 4);
|
||||
// will not fit.
|
||||
assert!(CheckWeight::<Test>(PhantomData).pre_dispatch(&1, normal, len).is_err());
|
||||
// will fit.
|
||||
assert!(CheckWeight::<Test>(PhantomData).pre_dispatch(&1, op, len).is_ok());
|
||||
|
||||
// likewise for length limit.
|
||||
let len = 100_usize;
|
||||
AllExtrinsicsLen::put(<MaximumBlockLength as Get<Weight>>::get() / 4);
|
||||
assert!(CheckWeight::<Test>(PhantomData).pre_dispatch(&1, normal, len).is_err());
|
||||
assert!(CheckWeight::<Test>(PhantomData).pre_dispatch(&1, op, len).is_ok());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signed_ext_check_weight_priority_works() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
let normal = DispatchInfo { weight: 100, class: DispatchClass::Normal };
|
||||
let op = DispatchInfo { weight: 100, class: DispatchClass::Operational };
|
||||
let len = 0_usize;
|
||||
|
||||
assert_eq!(
|
||||
CheckWeight::<Test>(PhantomData).validate(&1, normal, len).unwrap().priority,
|
||||
100,
|
||||
);
|
||||
assert_eq!(
|
||||
CheckWeight::<Test>(PhantomData).validate(&1, op, len).unwrap().priority,
|
||||
Bounded::max_value(),
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signed_ext_check_weight_block_size_works() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
let tx = DispatchInfo::default();
|
||||
|
||||
let reset_check_weight = |s, f| {
|
||||
AllExtrinsicsLen::put(0);
|
||||
let r = CheckWeight::<Test>(PhantomData).pre_dispatch(&1, tx, s);
|
||||
if f { assert!(r.is_err()) } else { assert!(r.is_ok()) }
|
||||
};
|
||||
|
||||
reset_check_weight(128, false);
|
||||
reset_check_weight(512, false);
|
||||
reset_check_weight(513, true);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signed_ext_check_era_should_work() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
// future
|
||||
assert_eq!(
|
||||
CheckEra::<Test>::from(Era::mortal(4, 2)).additional_signed().err().unwrap(),
|
||||
"transaction birth block ancient"
|
||||
);
|
||||
|
||||
// correct
|
||||
System::set_block_number(13);
|
||||
<BlockHash<Test>>::insert(12, H256::repeat_byte(1));
|
||||
assert!(CheckEra::<Test>::from(Era::mortal(4, 12)).additional_signed().is_ok());
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,6 +338,8 @@ mod tests {
|
||||
pub struct Test;
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: u32 = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
}
|
||||
impl system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
@@ -351,6 +353,8 @@ mod tests {
|
||||
type WeightMultiplierUpdate = ();
|
||||
type Event = ();
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
}
|
||||
parameter_types! {
|
||||
pub const MinimumPeriod: u64 = 5;
|
||||
|
||||
@@ -370,6 +370,8 @@ mod tests {
|
||||
pub struct Test;
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: u32 = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
}
|
||||
impl system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
@@ -383,6 +385,8 @@ mod tests {
|
||||
type WeightMultiplierUpdate = ();
|
||||
type Event = ();
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
}
|
||||
parameter_types! {
|
||||
pub const ExistentialDeposit: u64 = 0;
|
||||
|
||||
@@ -18,6 +18,8 @@ schnorrkel = "0.1.1"
|
||||
hex = "0.3"
|
||||
hex-literal = "0.2"
|
||||
parity-codec = "4.1.1"
|
||||
system = { package = "srml-system", path = "../srml/system" }
|
||||
balances = { package = "srml-balances", path = "../srml/balances" }
|
||||
|
||||
[features]
|
||||
bench = []
|
||||
|
||||
@@ -23,8 +23,8 @@ use hex_literal::hex;
|
||||
use clap::load_yaml;
|
||||
use bip39::{Mnemonic, Language, MnemonicType};
|
||||
use substrate_primitives::{
|
||||
ed25519, sr25519, hexdisplay::HexDisplay, Pair, Public,
|
||||
crypto::{Ss58Codec, set_default_ss58_version, Ss58AddressFormat}, blake2_256
|
||||
ed25519, sr25519, hexdisplay::HexDisplay, Pair, Public, blake2_256,
|
||||
crypto::{Ss58Codec, set_default_ss58_version, Ss58AddressFormat}
|
||||
};
|
||||
use parity_codec::{Encode, Decode, Compact};
|
||||
use sr_primitives::generic::Era;
|
||||
@@ -90,6 +90,14 @@ fn execute<C: Crypto>(matches: clap::ArgMatches) where
|
||||
<<C as Crypto>::Pair as Pair>::Signature: AsRef<[u8]> + AsMut<[u8]> + Default,
|
||||
<<C as Crypto>::Pair as Pair>::Public: Sized + AsRef<[u8]> + Ss58Codec + AsRef<<<C as Crypto>::Pair as Pair>::Public>,
|
||||
{
|
||||
// let extra = |i: Index, f: Balance| {
|
||||
// (
|
||||
// system::CheckEra::<Runtime>::from(Era::Immortal),
|
||||
// system::CheckNonce::<Runtime>::from(i),
|
||||
// system::CheckWeight::<Runtime>::from(),
|
||||
// balances::TakeFees::<Runtime>::from(f),
|
||||
// )
|
||||
// };
|
||||
let password = matches.value_of("password");
|
||||
let maybe_network = matches.value_of("network");
|
||||
if let Some(network) = maybe_network {
|
||||
@@ -162,8 +170,7 @@ fn execute<C: Crypto>(matches: clap::ArgMatches) where
|
||||
|
||||
println!("Using a genesis hash of {}", HexDisplay::from(&genesis_hash.as_ref()));
|
||||
|
||||
let era = Era::immortal();
|
||||
let raw_payload = (Compact(index), function, era, genesis_hash);
|
||||
let raw_payload = (function, extra(index, 0), genesis_hash);
|
||||
let signature = raw_payload.using_encoded(|payload| if payload.len() > 256 {
|
||||
signer.sign(&blake2_256(payload)[..])
|
||||
} else {
|
||||
@@ -171,11 +178,10 @@ fn execute<C: Crypto>(matches: clap::ArgMatches) where
|
||||
signer.sign(payload)
|
||||
});
|
||||
let extrinsic = UncheckedExtrinsic::new_signed(
|
||||
raw_payload.1,
|
||||
raw_payload.0,
|
||||
signer.public().into(),
|
||||
signature.into(),
|
||||
era,
|
||||
(CheckNonce(index), TakeFees(0)),
|
||||
extra(index, 0),
|
||||
);
|
||||
println!("0x{}", hex::encode(&extrinsic.encode()));
|
||||
}
|
||||
@@ -202,7 +208,7 @@ fn execute<C: Crypto>(matches: clap::ArgMatches) where
|
||||
|
||||
let era = Era::immortal();
|
||||
|
||||
let raw_payload = (Compact(index), function, era, prior_block_hash);
|
||||
let raw_payload = (function, era, prior_block_hash, extra(index, 0));
|
||||
let signature = raw_payload.using_encoded(|payload|
|
||||
if payload.len() > 256 {
|
||||
signer.sign(&blake2_256(payload)[..])
|
||||
@@ -212,11 +218,10 @@ fn execute<C: Crypto>(matches: clap::ArgMatches) where
|
||||
);
|
||||
|
||||
let extrinsic = UncheckedExtrinsic::new_signed(
|
||||
raw_payload.1,
|
||||
raw_payload.0,
|
||||
signer.public().into(),
|
||||
signature.into(),
|
||||
era,
|
||||
(CheckNonce(index), TakeFees(0)),
|
||||
extra(index, 0),
|
||||
);
|
||||
|
||||
println!("0x{}", hex::encode(&extrinsic.encode()));
|
||||
|
||||
Reference in New Issue
Block a user