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:
Gavin Wood
2019-07-23 01:06:49 +08:00
committed by Kian Peymani
parent 4f5654b67d
commit 78bc5edc14
55 changed files with 1965 additions and 1646 deletions
@@ -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);
}
}