diff --git a/substrate/core/client/src/client.rs b/substrate/core/client/src/client.rs index a37bd163b2..647e48405a 100644 --- a/substrate/core/client/src/client.rs +++ b/substrate/core/client/src/client.rs @@ -284,7 +284,7 @@ impl Client where pub fn authorities_at(&self, id: &BlockId) -> error::Result> { match self.backend.blockchain().cache().and_then(|cache| cache.authorities_at(*id)) { Some(cached_value) => Ok(cached_value), - None => self.executor.call(id, "Core_authorities",&[]) + None => self.executor.call(id, "Core_authorities", &[]) .and_then(|r| Vec::::decode(&mut &r.return_data[..]) .ok_or(error::ErrorKind::InvalidAuthoritiesSet.into())) } diff --git a/substrate/core/sr-primitives/src/generic/mod.rs b/substrate/core/sr-primitives/src/generic/mod.rs index 69b317f6ee..1e1778204a 100644 --- a/substrate/core/sr-primitives/src/generic/mod.rs +++ b/substrate/core/sr-primitives/src/generic/mod.rs @@ -20,6 +20,7 @@ mod unchecked_extrinsic; mod unchecked_mortal_extrinsic; +mod unchecked_mortal_compact_extrinsic; mod era; mod checked_extrinsic; mod header; @@ -30,6 +31,7 @@ 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; pub use self::checked_extrinsic::CheckedExtrinsic; pub use self::header::Header; diff --git a/substrate/core/sr-primitives/src/generic/unchecked_mortal_compact_extrinsic.rs b/substrate/core/sr-primitives/src/generic/unchecked_mortal_compact_extrinsic.rs new file mode 100644 index 0000000000..9ffac3d3f9 --- /dev/null +++ b/substrate/core/sr-primitives/src/generic/unchecked_mortal_compact_extrinsic.rs @@ -0,0 +1,290 @@ +// Copyright 2017-2018 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. + +#[cfg(feature = "std")] +use std::fmt; + +use rstd::prelude::*; +use codec::{Decode, Encode, Input, Compact}; +use traits::{self, Member, SimpleArithmetic, MaybeDisplay, CurrentHeight, BlockNumberToHash, Lookup, + Checkable, Extrinsic}; +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 { + /// 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, +} + +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 { + 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 Extrinsic for UncheckedMortalCompactExtrinsic { + fn is_signed(&self) -> Option { + Some(self.signature.is_some()) + } +} + +impl Checkable + for UncheckedMortalCompactExtrinsic +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, +{ + type Checked = CheckedExtrinsic; + + fn check(self, context: &Context) -> Result { + Ok(match self.signature { + Some((signed, signature, index, era)) => { + let h = context.block_number_to_hash(BlockNumber::sa(era.birth(context.current_height().as_()))) + .ok_or("transaction birth block ancient")?; + let payload = (index, self.function, era, h); + let signed = context.lookup(signed)?; + if !::verify_encoded_lazy(&signature, &payload, &signed) { + return Err("bad signature in extrinsic") + } + CheckedExtrinsic { + signed: Some((signed, (payload.0).0)), + function: payload.1, + } + } + None => CheckedExtrinsic { + signed: None, + function: self.function, + }, + }) + } +} + +impl Decode + for UncheckedMortalCompactExtrinsic +where + Address: Decode, + Signature: Decode, + Compact: Decode, + Call: 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(UncheckedMortalCompactExtrinsic { + signature: if is_signed { Some(Decode::decode(input)?) } else { None }, + function: Decode::decode(input)?, + }) + } +} + +impl Encode + for UncheckedMortalCompactExtrinsic +where + Address: Encode, + Signature: Encode, + Compact: Encode, + Call: 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); + }) + } +} + +#[cfg(feature = "std")] +impl serde::Serialize + for UncheckedMortalCompactExtrinsic + where Compact: Encode +{ + fn serialize(&self, seq: S) -> Result where S: ::serde::Serializer { + self.using_encoded(|bytes| seq.serialize_bytes(bytes)) + } +} + +/// TODO: use derive when possible. +#[cfg(feature = "std")] +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) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + 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[..] + } + } + + const DUMMY_FUNCTION: u64 = 0; + const DUMMY_ACCOUNTID: u64 = 0; + + type Ex = UncheckedMortalCompactExtrinsic; + type CEx = CheckedExtrinsic; + + #[test] + fn unsigned_codec_should_work() { + let ux = Ex::new_unsigned(DUMMY_FUNCTION); + let encoded = ux.encode(); + assert_eq!(Ex::decode(&mut &encoded[..]), Some(ux)); + } + + #[test] + fn signed_codec_should_work() { + let ux = Ex::new_signed(0, DUMMY_FUNCTION, DUMMY_ACCOUNTID, TestSig(DUMMY_ACCOUNTID, (DUMMY_ACCOUNTID, DUMMY_FUNCTION, Era::immortal(), 0u64).encode()), 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(DUMMY_FUNCTION); + 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, DUMMY_FUNCTION, DUMMY_ACCOUNTID, TestSig(DUMMY_ACCOUNTID, vec![0u8]), Era::immortal()); + assert!(ux.is_signed().unwrap_or(false)); + assert_eq!(>::check(ux, &TestContext), Err("bad signature in extrinsic")); + } + + #[test] + fn immortal_signed_check_should_work() { + let ux = Ex::new_signed(0, DUMMY_FUNCTION, DUMMY_ACCOUNTID, TestSig(DUMMY_ACCOUNTID, (Compact::from(DUMMY_ACCOUNTID), DUMMY_FUNCTION, 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: DUMMY_FUNCTION })); + } + + #[test] + fn mortal_signed_check_should_work() { + let ux = Ex::new_signed(0, DUMMY_FUNCTION, DUMMY_ACCOUNTID, TestSig(DUMMY_ACCOUNTID, (Compact::from(DUMMY_ACCOUNTID), DUMMY_FUNCTION, 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: DUMMY_FUNCTION })); + } + + #[test] + fn later_mortal_signed_check_should_work() { + let ux = Ex::new_signed(0, DUMMY_FUNCTION, DUMMY_ACCOUNTID, TestSig(DUMMY_ACCOUNTID, (Compact::from(DUMMY_ACCOUNTID), DUMMY_FUNCTION, 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: DUMMY_FUNCTION })); + } + + #[test] + fn too_late_mortal_signed_check_should_fail() { + let ux = Ex::new_signed(0, DUMMY_FUNCTION, DUMMY_ACCOUNTID, TestSig(DUMMY_ACCOUNTID, (DUMMY_ACCOUNTID, DUMMY_FUNCTION, Era::mortal(32, 10), 10u64).encode()), Era::mortal(32, 10)); + assert!(ux.is_signed().unwrap_or(false)); + assert_eq!(>::check(ux, &TestContext), Err("bad signature in extrinsic")); + } + + #[test] + fn too_early_mortal_signed_check_should_fail() { + let ux = Ex::new_signed(0, DUMMY_FUNCTION, DUMMY_ACCOUNTID, TestSig(DUMMY_ACCOUNTID, (DUMMY_ACCOUNTID, DUMMY_FUNCTION, Era::mortal(32, 43), 43u64).encode()), Era::mortal(32, 43)); + assert!(ux.is_signed().unwrap_or(false)); + assert_eq!(>::check(ux, &TestContext), Err("bad signature in extrinsic")); + } + + #[test] + fn encoding_matches_vec() { + let ex = Ex::new_unsigned(DUMMY_FUNCTION); + 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); + } +}