mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 16:21:06 +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:
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user