diff --git a/core/consensus/rhd/src/lib.rs b/core/consensus/rhd/src/lib.rs index 4a3e03759b4d4..5ef19aef22ed7 100644 --- a/core/consensus/rhd/src/lib.rs +++ b/core/consensus/rhd/src/lib.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . +//! DEPRECATED. +//! //! BFT Agreement based on a rotating proposer in different rounds. //! //! Where this crate refers to input stream, should never logically conclude. diff --git a/core/sr-primitives/src/generic/checked_extrinsic.rs b/core/sr-primitives/src/generic/checked_extrinsic.rs index ee43b3af2e951..04542a3084a9d 100644 --- a/core/sr-primitives/src/generic/checked_extrinsic.rs +++ b/core/sr-primitives/src/generic/checked_extrinsic.rs @@ -19,25 +19,33 @@ use crate::traits::{self, Member, SimpleArithmetic, MaybeDisplay}; use crate::weights::{Weighable, Weight}; +use crate::generic::tip::{Tip, Tippable}; /// 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 { +pub struct CheckedExtrinsic { /// 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)>, /// The function that should be called. pub function: Call, + /// An optional tip value that may or may not exist based on the underlying unchecked extrinsic. + /// + /// Most often this is: + /// - `None` if the unchecked extrinsic does not have a tip OR it is unsigned. + /// - `Some` if the opposite. + pub tip: Option>, } -impl traits::Applyable for CheckedExtrinsic +impl traits::Applyable for CheckedExtrinsic where AccountId: Member + MaybeDisplay, Index: Member + MaybeDisplay + SimpleArithmetic, Call: Member, + Balance: Member, { type Index = Index; type AccountId = AccountId; @@ -56,7 +64,7 @@ where } } -impl Weighable for CheckedExtrinsic +impl Weighable for CheckedExtrinsic where Call: Weighable, { @@ -64,3 +72,32 @@ where self.function.weight(len) } } + +/// `ExtBalance` is the balance type fed by the `check()` implementation of various unchecked +/// extrinsics. `NodeBalance` is the actual balance type used as a primitive type of the substrate +/// node. +/// +/// In practice, if they underlying unchecked transaction is tip-aware, they are the same. Otherwise, +/// the tip is always `None` and the type is of no importance. +impl Tippable + for CheckedExtrinsic +where + ExtBalance: Clone + Copy, + NodeBalance: From, +{ + fn tip(&self) -> Option> { + // This is a hacky way to prevent `unchecked_mortal[_compact]_extrinsic` types, which + // don't have a tip, become generic over a balance type. + // Basically, this CheckedExtrinsic is built either 1- from an + // `UncheckedMortalCompactTippedExtrinsic`, which is tip-aware and hence, the second arm + // will be trivially executed and the type conversion will be safe (the compiler is probably + // smart enough to remove it in fact). In this case, NodeBalance and ExtBalance are the same. + // Or 2- this is built from all other types of uncheckedextrinsic which do not have tip and + // hence are not tip-aware. These modules will naively place a u32 (can be `()` in practice) + // as the type and it does not matter since `None` is used in this case (first arm). + match &self.tip { + None => None, + Some(Tip::Sender(v)) => Some(Tip::Sender(NodeBalance::from(*v))), + } + } +} diff --git a/core/sr-primitives/src/generic/mod.rs b/core/sr-primitives/src/generic/mod.rs index a4e4106780efc..e03be06bcd02b 100644 --- a/core/sr-primitives/src/generic/mod.rs +++ b/core/sr-primitives/src/generic/mod.rs @@ -21,24 +21,26 @@ mod unchecked_extrinsic; mod unchecked_mortal_extrinsic; mod unchecked_mortal_compact_extrinsic; +mod unchecked_mortal_compact_tipped_extrinsic; mod era; mod checked_extrinsic; mod header; mod block; mod digest; +mod tip; #[cfg(test)] 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::unchecked_mortal_compact_tipped_extrinsic::UncheckedMortalCompactTippedExtrinsic; pub use self::era::{Era, Phase}; pub use self::checked_extrinsic::CheckedExtrinsic; pub use self::header::Header; pub use self::block::{Block, SignedBlock, BlockId}; -pub use self::digest::{ - Digest, DigestItem, DigestItemRef, OpaqueDigestItemId -}; +pub use self::digest::{Digest, DigestItem, DigestItemRef, OpaqueDigestItemId}; +pub use self::tip::{Tip, Tippable, NoTipBalance}; use crate::codec::Encode; use rstd::prelude::*; diff --git a/core/sr-primitives/src/generic/tip.rs b/core/sr-primitives/src/generic/tip.rs new file mode 100644 index 0000000000000..93b608952ff9d --- /dev/null +++ b/core/sr-primitives/src/generic/tip.rs @@ -0,0 +1,55 @@ +// 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 . + +//! Tip structure for a transaction. + +use crate::codec::{Encode, Decode}; +use crate::traits::Zero; + +/// A placeholder tip type that is used to fulfill the generic requirements of `checked_extrinsic` +/// when the underlying `unchecked_extrinsic` actually does not have a tip. +pub type NoTipBalance = u32; + +/// Representation of a transaction tip. +/// +/// Provided as an enum to support potential future use cases such as: +/// - Tipped by a third party (software or exchange). +/// - Unsigned tip. +#[cfg_attr(feature = "std", derive(Debug))] +#[derive(Clone, Copy, Eq, PartialEq, Encode, Decode)] +pub enum Tip { + /// The sender of the transaction has included some tip. + /// + /// this must be signed and included in the signature payload. + Sender(Balance), +} + +impl Tip { + /// Return the raw value of the tip (to be burned or consumed) regardless of any logic that the + /// Tip enum variant might embody. + pub fn value(&self) -> Balance { + match *self { + Tip::Sender(value) => value, + } + } +} + +/// A trait for a generic transaction that contains a tip. The tip itself might yield something +/// that translates to "no tip". +pub trait Tippable { + /// Return the tip associated with this transaction. + fn tip(&self) -> Option>; +} diff --git a/core/sr-primitives/src/generic/unchecked_extrinsic.rs b/core/sr-primitives/src/generic/unchecked_extrinsic.rs index d6e0d60e2c218..6a16694aef5a1 100644 --- a/core/sr-primitives/src/generic/unchecked_extrinsic.rs +++ b/core/sr-primitives/src/generic/unchecked_extrinsic.rs @@ -22,6 +22,7 @@ use std::fmt; use rstd::prelude::*; use crate::codec::{Decode, Encode, Codec, Input, HasCompact}; use crate::traits::{self, Member, SimpleArithmetic, MaybeDisplay, Lookup, Extrinsic}; +use crate::generic::tip::NoTipBalance; use super::CheckedExtrinsic; #[derive(PartialEq, Eq, Clone, Encode, Decode)] @@ -85,7 +86,7 @@ where AccountId: Member + MaybeDisplay, Context: Lookup, { - type Checked = CheckedExtrinsic; + type Checked = CheckedExtrinsic; fn check(self, context: &Context) -> Result { Ok(match self.signature { @@ -98,11 +99,13 @@ where CheckedExtrinsic { signed: Some((signed, payload.0)), function: payload.1, + tip: None, } } None => CheckedExtrinsic { signed: None, function: self.function, + tip: None, }, }) } diff --git a/core/sr-primitives/src/generic/unchecked_mortal_compact_extrinsic.rs b/core/sr-primitives/src/generic/unchecked_mortal_compact_extrinsic.rs index 36e17fc277cde..a66ffafb89f59 100644 --- a/core/sr-primitives/src/generic/unchecked_mortal_compact_extrinsic.rs +++ b/core/sr-primitives/src/generic/unchecked_mortal_compact_extrinsic.rs @@ -25,6 +25,7 @@ use crate::codec::{Decode, Encode, Input, Compact}; use crate::traits::{self, Member, SimpleArithmetic, MaybeDisplay, CurrentHeight, BlockNumberToHash, Lookup, Checkable, Extrinsic, SaturatedConversion}; use super::{CheckedExtrinsic, Era}; +use crate::generic::tip::NoTipBalance; const TRANSACTION_VERSION: u8 = 1; @@ -40,7 +41,9 @@ pub struct UncheckedMortalCompactExtrinsic { pub function: Call, } -impl UncheckedMortalCompactExtrinsic { +impl + UncheckedMortalCompactExtrinsic +{ /// New instance of a signed extrinsic aka "transaction". pub fn new_signed(index: Index, function: Call, signed: Address, signature: Signature, era: Era) -> Self { UncheckedMortalCompactExtrinsic { @@ -58,7 +61,9 @@ impl UncheckedMortalCompactExtrinsic Extrinsic for UncheckedMortalCompactExtrinsic { +impl Extrinsic + for UncheckedMortalCompactExtrinsic +{ fn is_signed(&self) -> Option { Some(self.signature.is_some()) } @@ -79,7 +84,9 @@ where + CurrentHeight + BlockNumberToHash, { - type Checked = CheckedExtrinsic; + /// NOTE: this transaction is not tipped i.e. the tip value will be `None`. It does not really + /// matter what the last generic is since it is always `None`. + type Checked = CheckedExtrinsic; fn check(self, context: &Context) -> Result { Ok(match self.signature { @@ -101,11 +108,13 @@ where CheckedExtrinsic { signed: Some((signed, (raw_payload.0).0)), function: raw_payload.1, + tip: None, } } None => CheckedExtrinsic { signed: None, function: self.function, + tip: None, }, }) } @@ -177,13 +186,19 @@ impl serde::Serialize } #[cfg(feature = "std")] -impl fmt::Debug for UncheckedMortalCompactExtrinsic where +impl fmt::Debug for UncheckedMortalCompactExtrinsic +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) + write!( + f, + "UncheckedMortalCompactExtrinsic({:?}, {:?})", + self.signature.as_ref().map(|x| (&x.0, &x.2)), + self.function + ) } } @@ -222,7 +237,7 @@ mod tests { const DUMMY_ACCOUNTID: u64 = 0; type Ex = UncheckedMortalCompactExtrinsic, TestSig>; - type CEx = CheckedExtrinsic>; + type CEx = CheckedExtrinsic, NoTipBalance>; #[test] fn unsigned_codec_should_work() { @@ -263,21 +278,30 @@ mod tests { 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!(>::check(ux, &TestContext), Ok(CEx { signed: Some((DUMMY_ACCOUNTID, 0)), function: vec![0u8;0] })); + assert_eq!( + >::check(ux, &TestContext), + Ok(CEx { signed: Some((DUMMY_ACCOUNTID, 0)), function: vec![0u8;0], tip: None }) + ); } #[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!(>::check(ux, &TestContext), Ok(CEx { signed: Some((DUMMY_ACCOUNTID, 0)), function: vec![0u8;0] })); + assert_eq!( + >::check(ux, &TestContext), + Ok(CEx { signed: Some((DUMMY_ACCOUNTID, 0)), function: vec![0u8;0], tip: None }) + ); } #[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!(>::check(ux, &TestContext), Ok(CEx { signed: Some((DUMMY_ACCOUNTID, 0)), function: vec![0u8;0] })); + assert_eq!( + >::check(ux, &TestContext), + Ok(CEx { signed: Some((DUMMY_ACCOUNTID, 0)), function: vec![0u8;0], tip: None }) + ); } #[test] diff --git a/core/sr-primitives/src/generic/unchecked_mortal_compact_tipped_extrinsic.rs b/core/sr-primitives/src/generic/unchecked_mortal_compact_tipped_extrinsic.rs new file mode 100644 index 0000000000000..b5385deab5ca5 --- /dev/null +++ b/core/sr-primitives/src/generic/unchecked_mortal_compact_tipped_extrinsic.rs @@ -0,0 +1,469 @@ +// 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 . + +//! Generic implementation of an unchecked (pre-verification) extrinsic with tip. + +#[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, Tip}; + +const TRANSACTION_VERSION: u8 = 64; + +/// A extrinsic right from the external world. This is unchecked and so +/// can contain a signature. +/// +/// This type transaction: +/// - _Must_ always have a valid `Tip` (not an option!) encoded, and included in the signature +/// payload if it is signed. +/// - _Must_ not provide any `Tip` if it is unsigned. +#[derive(PartialEq, Eq, Clone)] +pub struct UncheckedMortalCompactTippedExtrinsic { + /// 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, Era)>, + /// The function that should be called. + pub function: Call, + /// The tip for this transaction. This is always `Some` for a signed transaction and always + /// `None` for unsigned. + pub tip: Option>, +} + +impl + UncheckedMortalCompactTippedExtrinsic +{ + /// New instance of a signed extrinsic aka "transaction". + pub fn new_signed( + index: Index, + function: Call, + signed: Address, + signature: Signature, + era: Era, + tip: Tip, + ) -> Self { + UncheckedMortalCompactTippedExtrinsic { + signature: Some((signed, signature, index.into(), era)), + function, + tip: Some(tip), + } + } + + /// New instance of an unsigned extrinsic aka "inherent". + pub fn new_unsigned(function: Call) -> Self { + UncheckedMortalCompactTippedExtrinsic { + signature: None, + function, + tip: None, + } + } +} + +impl Extrinsic + for UncheckedMortalCompactTippedExtrinsic +{ + fn is_signed(&self) -> Option { + Some(self.signature.is_some()) + } +} + +impl Checkable + for UncheckedMortalCompactTippedExtrinsic +where + Address: Member + MaybeDisplay, + Index: Member + MaybeDisplay + SimpleArithmetic, + Compact: Encode, + Call: Encode + Member, + Signature: Member + traits::Verify, + AccountId: Member + MaybeDisplay, + BlockNumber: SimpleArithmetic, + Hash: Encode, + Context: Lookup + + CurrentHeight + + BlockNumberToHash, + Balance: Member + Encode, +{ + type Checked = CheckedExtrinsic; + + fn check(self, context: &Context) -> Result { + Ok(match self.signature { + Some((signed, signature, index, era)) => { + let tip = self.tip.ok_or("signed transaction must always have a tip.")?; + let current_u64 = context.current_height().saturated_into::(); + 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, tip, 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, + tip: Some(raw_payload.2), + } + } + None => { + // An unsigned transaction cannot have a tip. The decode code should replace it with + // `None` and ignore the input bytes. + if self.tip.is_some() { + return Err(crate::UNSIGNED_TIP); + } + CheckedExtrinsic { + signed: None, + function: self.function, + tip: None + } + }, + }) + } +} + +impl Decode + for UncheckedMortalCompactTippedExtrinsic +where + Address: Decode, + Signature: Decode, + Compact: Decode, + Call: Decode, + Balance: Decode, + +{ + fn decode(input: &mut I) -> Option { + // This is a little more complicated than usual since the binary format must be compatible + // with substrate's generic `Vec` 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(UncheckedMortalCompactTippedExtrinsic { + signature: if is_signed { Some(Decode::decode(input)?) } else { None }, + function: Decode::decode(input)?, + tip: if is_signed { Some(Decode::decode(input)?) } else { None }, + }) + } +} + +impl Encode + for UncheckedMortalCompactTippedExtrinsic +where + Address: Encode, + Signature: Encode, + Compact: Encode, + Call: Encode, + Balance: Encode, +{ + fn encode(&self) -> Vec { + super::encode_with_vec_prefix::(|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); + if self.signature.is_some() { + self.tip.as_ref().unwrap().encode_to(v); + } + }) + } +} + +#[cfg(feature = "std")] +impl serde::Serialize + for UncheckedMortalCompactTippedExtrinsic + where Compact: Encode +{ + fn serialize(&self, seq: S) -> Result where S: ::serde::Serializer { + self.using_encoded(|bytes| seq.serialize_bytes(bytes)) + } +} + +#[cfg(feature = "std")] +impl fmt::Debug + for UncheckedMortalCompactTippedExtrinsic +where + Address: fmt::Debug, + Index: fmt::Debug, + Call: fmt::Debug, + Balance: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "UncheckedMortalCompactTippedExtrinsic({:?}, {:?}, {:?})", + self.signature.as_ref().map(|x| (&x.0, &x.2)), + self.function, + self.tip, + ) + } +} + +#[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 { 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 { Some(n) } + } + + #[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Encode, Decode)] + struct TestSig(u64, Vec); + impl traits::Verify for TestSig { + type Signer = u64; + fn verify>(&self, mut msg: L, signer: &Self::Signer) -> bool { + *signer == self.0 && msg.get() == &self.1[..] + } + } + + type Balance = u64; + type TipType = Tip; + const DUMMY_ACCOUNTID: u64 = 0; + const TIP: TipType = Tip::Sender(66); + + type Ex = UncheckedMortalCompactTippedExtrinsic, TestSig, Balance>; + type CEx = CheckedExtrinsic, Balance>; + + #[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], TIP, Era::immortal(), 0u64).encode()), + Era::immortal(), + TIP + ); + 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], TIP, Era::immortal(), 0u64).using_encoded(blake2_256)[..].to_owned() + ), + Era::immortal(), + TIP + ); + 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!(>::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(), + TIP, + ); + assert!(ux.is_signed().unwrap_or(false)); + assert_eq!(>::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], TIP, Era::immortal(), 0u64).encode() + ), + Era::immortal(), + TIP, + ); + assert!(ux.is_signed().unwrap_or(false)); + assert_eq!( + >::check(ux, &TestContext), + Ok(CEx { signed: Some((DUMMY_ACCOUNTID, 0)), function: vec![0u8;0], tip: Some(TIP) }) + ); + } + + #[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], TIP, Era::mortal(32, 42), 42u64).encode() + ), + Era::mortal(32, 42), + TIP, + ); + assert!(ux.is_signed().unwrap_or(false)); + assert_eq!( + >::check(ux, &TestContext), + Ok(CEx { signed: Some((DUMMY_ACCOUNTID, 0)), function: vec![0u8;0], tip: Some(TIP) })); + } + + #[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], TIP, Era::mortal(32, 11), 11u64).encode() + ), + Era::mortal(32, 11), + TIP, + ); + assert!(ux.is_signed().unwrap_or(false)); + assert_eq!( + >::check(ux, &TestContext), + Ok(CEx { signed: Some((DUMMY_ACCOUNTID, 0)), function: vec![0u8;0], tip: Some(TIP) }) + ); + } + + #[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], TIP, Era::mortal(32, 10), 10u64).encode() + ), + Era::mortal(32, 10), + TIP, + ); + assert!(ux.is_signed().unwrap_or(false)); + assert_eq!(>::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], TIP, Era::mortal(32, 43), 43u64).encode() + ), + Era::mortal(32, 43), + TIP, + ); + assert!(ux.is_signed().unwrap_or(false)); + assert_eq!(>::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 = Decode::decode(&mut encoded.as_slice()).unwrap(); + assert_eq!(as_vec.encode(), encoded); + } + + #[test] + fn missing_tip_in_signature_payload_should_fail() { + 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(), + TIP, + ); + assert!(ux.is_signed().unwrap_or(false)); + assert_eq!(>::check(ux, &TestContext), Err(crate::BAD_SIGNATURE)); + } + + #[test] + fn unsigned_cannot_have_tip() { + let ux = UncheckedMortalCompactTippedExtrinsic { + signature: None, + tip: Some(Tip::Sender(100)), + function: vec![0u8;0] + }; + assert_eq!( + >::check(ux, &TestContext), + Err(crate::UNSIGNED_TIP) + ); + } +} diff --git a/core/sr-primitives/src/generic/unchecked_mortal_extrinsic.rs b/core/sr-primitives/src/generic/unchecked_mortal_extrinsic.rs index 7f92b20edd0c3..185abf4b9e925 100644 --- a/core/sr-primitives/src/generic/unchecked_mortal_extrinsic.rs +++ b/core/sr-primitives/src/generic/unchecked_mortal_extrinsic.rs @@ -26,6 +26,7 @@ use crate::traits::{ self, Member, SimpleArithmetic, MaybeDisplay, CurrentHeight, BlockNumberToHash, Lookup, Checkable, Extrinsic, SaturatedConversion }; +use crate::generic::tip::NoTipBalance; use super::{CheckedExtrinsic, Era}; const TRANSACTION_VERSION: u8 = 1; @@ -60,7 +61,9 @@ impl UncheckedMortalExtrinsic Extrinsic for UncheckedMortalExtrinsic { +impl Extrinsic + for UncheckedMortalExtrinsic +{ fn is_signed(&self) -> Option { Some(self.signature.is_some()) } @@ -80,7 +83,9 @@ where + CurrentHeight + BlockNumberToHash, { - type Checked = CheckedExtrinsic; + /// NOTE: this transaction is not tipped i.e. the tip value will be `None`. It does not really + /// matter what the last generic is since it is always `None`. + type Checked = CheckedExtrinsic; fn check(self, context: &Context) -> Result { Ok(match self.signature { @@ -103,11 +108,13 @@ where CheckedExtrinsic { signed: Some((signed, raw_payload.0)), function: raw_payload.1, + tip: None, } } None => CheckedExtrinsic { signed: None, function: self.function, + tip: None, }, }) } @@ -178,13 +185,20 @@ impl serde::Ser } #[cfg(feature = "std")] -impl fmt::Debug for UncheckedMortalExtrinsic where +impl fmt::Debug + for UncheckedMortalExtrinsic +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) + write!( + f, + "UncheckedMortalExtrinsic({:?}, {:?})", + self.signature.as_ref().map(|x| (&x.0, &x.2)), + self.function + ) } } @@ -223,7 +237,7 @@ mod tests { const DUMMY_ACCOUNTID: u64 = 0; type Ex = UncheckedMortalExtrinsic, TestSig>; - type CEx = CheckedExtrinsic>; + type CEx = CheckedExtrinsic, NoTipBalance>; #[test] fn unsigned_codec_should_work() { @@ -264,21 +278,30 @@ mod tests { 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!(>::check(ux, &TestContext), Ok(CEx { signed: Some((DUMMY_ACCOUNTID, 0)), function: vec![0u8;0] })); + assert_eq!( + >::check(ux, &TestContext), + Ok(CEx { signed: Some((DUMMY_ACCOUNTID, 0)), function: vec![0u8;0], tip: None }) + ); } #[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!(>::check(ux, &TestContext), Ok(CEx { signed: Some((DUMMY_ACCOUNTID, 0)), function: vec![0u8;0] })); + assert_eq!( + >::check(ux, &TestContext), + Ok(CEx { signed: Some((DUMMY_ACCOUNTID, 0)), function: vec![0u8;0], tip: None }) + ); } #[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!(>::check(ux, &TestContext), Ok(CEx { signed: Some((DUMMY_ACCOUNTID, 0)), function: vec![0u8;0] })); + assert_eq!( + >::check(ux, &TestContext), + Ok(CEx { signed: Some((DUMMY_ACCOUNTID, 0)), function: vec![0u8;0], tip: None }) + ); } #[test] diff --git a/core/sr-primitives/src/lib.rs b/core/sr-primitives/src/lib.rs index 607fdff376d1b..36436a1904edf 100644 --- a/core/sr-primitives/src/lib.rs +++ b/core/sr-primitives/src/lib.rs @@ -67,6 +67,9 @@ pub const BAD_SIGNATURE: &str = "bad signature in extrinsic"; /// Example: block gas limit is reached (the transaction can be retried in the next block though). pub const BLOCK_FULL: &str = "block size limit is reached"; +/// Unsigned message containing tip error message. +pub const UNSIGNED_TIP: &str = "unsigned extrinsic with tip not allowed"; + /// Justification type. pub type Justification = Vec; diff --git a/core/sr-primitives/src/testing.rs b/core/sr-primitives/src/testing.rs index f8df25ec596b0..e332e0ec05355 100644 --- a/core/sr-primitives/src/testing.rs +++ b/core/sr-primitives/src/testing.rs @@ -241,3 +241,8 @@ impl Weighable for TestXt { len as Weight } } +impl generic::Tippable for TestXt { + fn tip(&self) -> Option> { + None + } +} diff --git a/node-template/runtime/src/lib.rs b/node-template/runtime/src/lib.rs index 5cf774a5e9015..6292c1c991e43 100644 --- a/node-template/runtime/src/lib.rs +++ b/node-template/runtime/src/lib.rs @@ -57,6 +57,9 @@ pub type BlockNumber = u64; /// Index of an account's extrinsic in the chain. pub type Nonce = u64; +/// Numeric type of an account's balance. +pub type Balance = u128; + /// Used for the module template in `./template.rs` mod template; @@ -171,7 +174,7 @@ parameter_types! { impl balances::Trait for Runtime { /// The type for recording an account's balance. - type Balance = u128; + type Balance = Balance; /// What to do if an account's free balance gets zeroed. type OnFreeBalanceZero = (); /// What to do if a new account is created. @@ -230,7 +233,7 @@ pub type BlockId = generic::BlockId; /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = generic::UncheckedMortalCompactExtrinsic; /// Extrinsic type that has already been checked. -pub type CheckedExtrinsic = generic::CheckedExtrinsic; +pub type CheckedExtrinsic = generic::CheckedExtrinsic; /// Executive: handles dispatch to the various modules. pub type Executive = executive::Executive; diff --git a/node/cli/src/factory_impl.rs b/node/cli/src/factory_impl.rs index 0d94610362fdc..0c5e3a8e02578 100644 --- a/node/cli/src/factory_impl.rs +++ b/node/cli/src/factory_impl.rs @@ -140,7 +140,8 @@ impl RuntimeAdapter for FactoryState { ), (*amount).into() ) - ) + ), + tip: None, }, key, &prior_block_hash, phase) } diff --git a/node/executor/src/lib.rs b/node/executor/src/lib.rs index 1d6f1d1794b3f..bd70fd9fc3248 100644 --- a/node/executor/src/lib.rs +++ b/node/executor/src/lib.rs @@ -135,6 +135,7 @@ mod tests { sign(CheckedExtrinsic { signed: Some((alice(), 0)), function: Call::Balances(balances::Call::transfer::(bob().into(), 69 * DOLLARS)), + tip: None, }) } @@ -426,10 +427,12 @@ mod tests { CheckedExtrinsic { signed: None, function: Call::Timestamp(timestamp::Call::set(42)), + tip: None, }, CheckedExtrinsic { signed: Some((alice(), 0)), function: Call::Balances(balances::Call::transfer(bob().into(), 69 * DOLLARS)), + tip: None, }, ] ) @@ -448,10 +451,12 @@ mod tests { CheckedExtrinsic { signed: None, function: Call::Timestamp(timestamp::Call::set(42)), + tip: None, }, CheckedExtrinsic { signed: Some((alice(), 0)), function: Call::Balances(balances::Call::transfer(bob().into(), 69 * DOLLARS)), + tip: None, }, ] ); @@ -463,14 +468,17 @@ mod tests { CheckedExtrinsic { signed: None, function: Call::Timestamp(timestamp::Call::set(52)), + tip: None, }, CheckedExtrinsic { signed: Some((bob(), 0)), function: Call::Balances(balances::Call::transfer(alice().into(), 5 * DOLLARS)), + tip: None, }, CheckedExtrinsic { signed: Some((alice(), 1)), function: Call::Balances(balances::Call::transfer(bob().into(), 15 * DOLLARS)), + tip: None, } ] ); @@ -492,10 +500,12 @@ mod tests { CheckedExtrinsic { signed: None, function: Call::Timestamp(timestamp::Call::set(42)), + tip: None, }, CheckedExtrinsic { signed: Some((alice(), 0)), function: Call::System(system::Call::remark(vec![0; 120000])), + tip: None, } ] ) @@ -741,18 +751,21 @@ mod tests { CheckedExtrinsic { signed: None, function: Call::Timestamp(timestamp::Call::set(42)), + tip: None, }, CheckedExtrinsic { signed: Some((charlie(), 0)), function: Call::Contracts( contracts::Call::put_code::(10_000, transfer_code) ), + tip: None, }, CheckedExtrinsic { signed: Some((charlie(), 1)), function: Call::Contracts( contracts::Call::create::(1 * DOLLARS, 10_000, transfer_ch, Vec::new()) ), + tip: None, }, CheckedExtrinsic { signed: Some((charlie(), 2)), @@ -764,6 +777,7 @@ mod tests { vec![0x00, 0x01, 0x02, 0x03] ) ), + tip: None, }, ] ); diff --git a/node/runtime/src/lib.rs b/node/runtime/src/lib.rs index 4382227de4192..cc25f832510fa 100644 --- a/node/runtime/src/lib.rs +++ b/node/runtime/src/lib.rs @@ -71,8 +71,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: 107, - impl_version: 107, + spec_version: 108, + impl_version: 108, apis: RUNTIME_API_VERSIONS, }; @@ -430,7 +430,7 @@ pub type BlockId = generic::BlockId; /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = generic::UncheckedMortalCompactExtrinsic; /// Extrinsic type that has already been checked. -pub type CheckedExtrinsic = generic::CheckedExtrinsic; +pub type CheckedExtrinsic = generic::CheckedExtrinsic; /// Executive: handles dispatch to the various modules. pub type Executive = executive::Executive, Balances, Runtime, AllModules>; diff --git a/srml/balances/src/lib.rs b/srml/balances/src/lib.rs index 84e023ee5b2aa..a4f1aced0b527 100644 --- a/srml/balances/src/lib.rs +++ b/srml/balances/src/lib.rs @@ -1131,12 +1131,19 @@ where } impl, I: Instance> MakePayment for Module { + type Balance = T::Balance; + fn make_payment(transactor: &T::AccountId, encoded_len: usize) -> Result { let encoded_len = T::Balance::from(encoded_len as u32); let transaction_fee = T::TransactionBaseFee::get() + T::TransactionByteFee::get() * encoded_len; + Self::make_raw_payment(transactor, transaction_fee)?; + Ok(()) + } + + fn make_raw_payment(transactor: &T::AccountId, value: Self::Balance) -> Result { let imbalance = Self::withdraw( transactor, - transaction_fee, + value, WithdrawReason::TransactionPayment, ExistenceRequirement::KeepAlive )?; diff --git a/srml/executive/src/lib.rs b/srml/executive/src/lib.rs index 6ee9da3c54f61..8933088d13d35 100644 --- a/srml/executive/src/lib.rs +++ b/srml/executive/src/lib.rs @@ -74,20 +74,18 @@ #![cfg_attr(not(feature = "std"), no_std)] -use rstd::prelude::*; -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, -}}; +use rstd::{prelude::*, marker::PhantomData, result}; +use primitives::{ApplyOutcome, ApplyError, + generic::{Digest, Tippable}, + weights::Weighable, + transaction_validity::{TransactionValidity, TransactionPriority, TransactionLongevity}, + traits::{self, Header, Zero, One, Checkable, Applyable, CheckEqual, OnFinalize, OnInitialize, + NumberFor, Block as BlockT, OffchainWorker, ValidateUnsigned, + }, +}; use srml_support::{Dispatchable, traits::MakePayment}; 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; mod internal { pub const MAX_TRANSACTIONS_WEIGHT: u32 = 4 * 1024 * 1024; @@ -115,6 +113,7 @@ pub trait ExecuteBlock { pub type CheckedOf = >::Checked; pub type CallOf = as Applyable>::Call; pub type OriginOf = as Dispatchable>::Origin; +pub type BalanceOf =

>::Balance; pub struct Executive( PhantomData<(System, Block, Context, Payment, UnsignedValidator, AllModules)> @@ -130,7 +129,10 @@ impl< > ExecuteBlock for Executive where Block::Extrinsic: Checkable + Codec, - CheckedOf: Applyable + Weighable, + CheckedOf: + Applyable + + Weighable + + Tippable>, CallOf: Dispatchable, OriginOf: From>, UnsignedValidator: ValidateUnsigned>, @@ -150,7 +152,10 @@ impl< > Executive where Block::Extrinsic: Checkable + Codec, - CheckedOf: Applyable + Weighable, + CheckedOf: + Applyable + + Weighable + + Tippable>, CallOf: Dispatchable, OriginOf: From>, UnsignedValidator: ValidateUnsigned>, @@ -279,6 +284,12 @@ where // pay any fees Payment::make_payment(sender, encoded_len).map_err(|_| internal::ApplyError::CantPay)?; + // pay any tip if provided.. + if let Some(tip) = xt.tip() { + let tip = tip.value(); + Payment::make_raw_payment(sender, tip).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). @@ -355,6 +366,14 @@ where return TransactionValidity::Invalid(ApplyError::CantPay as i8) } + // pay and burn the tip if provided. + if let Some(tip) = xt.tip() { + let tip = tip.value(); + if Payment::make_raw_payment(sender, tip).is_err() { + return TransactionValidity::Invalid(ApplyError::CantPay as i8) + } + } + // check index let expected_index = >::account_nonce(sender); if index < &expected_index { @@ -369,8 +388,9 @@ where vec![] }; + // TODO TODO: maximise (fee + tip) per weight unit here. TransactionValidity::Valid { - priority: encoded_len as TransactionPriority, + priority: (encoded_len as TransactionPriority), requires, provides, longevity: TransactionLongevity::max_value(), diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index ae33aeff464d4..41dd14a0d0395 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -92,16 +92,25 @@ pub enum UpdateBalanceOutcome { } /// Simple trait designed for hooking into a transaction payment. -/// -/// It operates over a single generic `AccountId` type. pub trait MakePayment { + /// Balance type used for payment. + type Balance: SimpleArithmetic + Copy; + /// 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, encoded_len: usize) -> Result<(), &'static str>; + + /// Make a raw transaction payment from `who` with the value `value`. + /// This is most often used to deduct optional fees from a transactor. + /// Return `Ok` iff the payment was successful. + fn make_raw_payment(who: &AccountId, value: Self::Balance) -> Result<(), &'static str>; } impl MakePayment for () { + // fine since we don't care about it. + type Balance = u32; fn make_payment(_: &T, _: usize) -> Result<(), &'static str> { Ok(()) } + fn make_raw_payment(_: &T, _: Self::Balance) -> Result<(), &'static str> { Ok(()) } } /// A trait for finding the author of a block header based on the `PreRuntime` digests contained