mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-10 05:17:59 +00:00
Transaction eras (#758)
* Initial groundwork * A mess. * Integrate * Fix tests * Unit tests * Tests for unchecked_extrisnic * fix tab * Improve binary format. * fix tests * Rename extrinsic-pool -> transaction-pool Closes #770 * Implement unimplemented. * typo
This commit is contained in:
@@ -25,10 +25,9 @@ use traits::{self, Member, SimpleArithmetic, MaybeDisplay};
|
||||
#[derive(PartialEq, Eq, Clone)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
|
||||
pub struct CheckedExtrinsic<AccountId, Index, Call> {
|
||||
/// Who this purports to be from, if anyone (note this is not a signature).
|
||||
pub signed: Option<AccountId>,
|
||||
/// The number of extrinsics have come before from the same signer.
|
||||
pub index: Index,
|
||||
/// Who this purports to be from and the number of extrinsics have come before
|
||||
/// from the same signer, if anyone (note this is not a signature).
|
||||
pub signed: Option<(AccountId, Index)>,
|
||||
/// The function that should be called.
|
||||
pub function: Call,
|
||||
}
|
||||
@@ -44,15 +43,15 @@ where
|
||||
type AccountId = AccountId;
|
||||
type Call = Call;
|
||||
|
||||
fn index(&self) -> &Self::Index {
|
||||
&self.index
|
||||
fn index(&self) -> Option<&Self::Index> {
|
||||
self.signed.as_ref().map(|x| &x.1)
|
||||
}
|
||||
|
||||
fn sender(&self) -> Option<&Self::AccountId> {
|
||||
self.signed.as_ref()
|
||||
self.signed.as_ref().map(|x| &x.0)
|
||||
}
|
||||
|
||||
fn deconstruct(self) -> (Self::Call, Option<Self::AccountId>) {
|
||||
(self.function, self.signed)
|
||||
(self.function, self.signed.map(|x| x.0))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
use codec::{Decode, Encode, Input, Output};
|
||||
|
||||
pub type Period = u64;
|
||||
pub type Phase = u64;
|
||||
|
||||
/// An era to describe the longevity of a transaction.
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
|
||||
pub enum Era {
|
||||
/// The transaction is valid forever. The genesis hash must be present in the signed content.
|
||||
Immortal,
|
||||
|
||||
/// Period and phase are encoded:
|
||||
/// - The period of validity from the block hash found in the signing material.
|
||||
/// - The phase in the period that this transaction's lifetime begins (and, importantly,
|
||||
/// implies which block hash is included in the signature material). If the `period` is
|
||||
/// greater than 1 << 12, then it will be a factor of the times greater than 1<<12 that
|
||||
/// `period` is.
|
||||
Mortal(Period, Phase),
|
||||
}
|
||||
|
||||
/*
|
||||
E.g. with period == 4:
|
||||
0 10 20 30 40
|
||||
0123456789012345678901234567890123456789012
|
||||
|...|
|
||||
authored -/ \- expiry
|
||||
phase = 1
|
||||
n = Q(current - phase, period) + phase
|
||||
*/
|
||||
impl Era {
|
||||
/// Create a new era based on a period (which should be a power of two between 4 and 65536 inclusive)
|
||||
/// and a block number on which it should start (or, for long periods, be shortly after the start).
|
||||
pub fn mortal(period: u64, current: u64) -> Self {
|
||||
let period = period.checked_next_power_of_two()
|
||||
.unwrap_or(1 << 16)
|
||||
.max(4)
|
||||
.min(1 << 16);
|
||||
let phase = current % period;
|
||||
let quantize_factor = (period >> 12).max(1);
|
||||
let quantized_phase = phase / quantize_factor * quantize_factor;
|
||||
|
||||
Era::Mortal(period, quantized_phase)
|
||||
}
|
||||
|
||||
/// Create an "immortal" transaction.
|
||||
pub fn immortal() -> Self {
|
||||
Era::Immortal
|
||||
}
|
||||
|
||||
/// `true` if this is an immortal transaction.
|
||||
pub fn is_immortal(&self) -> bool {
|
||||
match self {
|
||||
Era::Immortal => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the block number of the start of the era whose properties this object
|
||||
/// describes that `current` belongs to.
|
||||
pub fn birth(self, current: u64) -> u64 {
|
||||
match self {
|
||||
Era::Immortal => 0,
|
||||
Era::Mortal(period, phase) => (current - phase) / period * period + phase,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the block number of the first block at which the era has ended.
|
||||
pub fn death(self, current: u64) -> u64 {
|
||||
match self {
|
||||
Era::Immortal => u64::max_value(),
|
||||
Era::Mortal(period, _) => self.birth(current) + period,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for Era {
|
||||
fn encode_to<T: Output>(&self, output: &mut T) {
|
||||
match self {
|
||||
Era::Immortal => output.push_byte(0),
|
||||
Era::Mortal(period, phase) => {
|
||||
let quantize_factor = (*period as u64 >> 12).max(1);
|
||||
let encoded = (period.trailing_zeros() - 1).max(1).min(15) as u16 | ((phase / quantize_factor) << 4) as u16;
|
||||
output.push(&encoded);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for Era {
|
||||
fn decode<I: Input>(input: &mut I) -> Option<Self> {
|
||||
let first = input.read_byte()?;
|
||||
if first == 0 {
|
||||
Some(Era::Immortal)
|
||||
} else {
|
||||
let encoded = first as u64 + ((input.read_byte()? as u64) << 8);
|
||||
let period = 2 << (encoded % (1 << 4));
|
||||
let quantize_factor = (period >> 12).max(1);
|
||||
let phase = (encoded >> 4) * quantize_factor;
|
||||
if period >= 4 && phase < period {
|
||||
Some(Era::Mortal(period, phase))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn immortal_works() {
|
||||
let e = Era::immortal();
|
||||
assert_eq!(e.birth(0), 0);
|
||||
assert_eq!(e.death(0), u64::max_value());
|
||||
assert_eq!(e.birth(1), 0);
|
||||
assert_eq!(e.death(1), u64::max_value());
|
||||
assert_eq!(e.birth(u64::max_value()), 0);
|
||||
assert_eq!(e.death(u64::max_value()), u64::max_value());
|
||||
assert!(e.is_immortal());
|
||||
|
||||
assert_eq!(e.encode(), vec![0u8]);
|
||||
assert_eq!(e, Era::decode(&mut&[0u8][..]).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mortal_codec_works() {
|
||||
let e = Era::mortal(64, 42);
|
||||
assert!(!e.is_immortal());
|
||||
|
||||
let expected = vec![5 + 42 % 16 * 16, 42 / 16];
|
||||
assert_eq!(e.encode(), expected);
|
||||
assert_eq!(e, Era::decode(&mut&expected[..]).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn long_period_mortal_codec_works() {
|
||||
let e = Era::mortal(32768, 20000);
|
||||
|
||||
let expected = vec![(14 + 2500 % 16 * 16) as u8, (2500 / 16) as u8];
|
||||
assert_eq!(e.encode(), expected);
|
||||
assert_eq!(e, Era::decode(&mut&expected[..]).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn era_initialisation_works() {
|
||||
assert_eq!(Era::mortal(64, 42), Era::Mortal(64, 42));
|
||||
assert_eq!(Era::mortal(32768, 20000), Era::Mortal(32768, 20000));
|
||||
assert_eq!(Era::mortal(200, 513), Era::Mortal(256, 1));
|
||||
assert_eq!(Era::mortal(2, 1), Era::Mortal(4, 1));
|
||||
assert_eq!(Era::mortal(4, 5), Era::Mortal(4, 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quantised_clamped_era_initialisation_works() {
|
||||
// clamp 1000000 to 65536, quantise 1000001 % 65536 to the nearest 4
|
||||
assert_eq!(Era::mortal(1000000, 1000001), Era::Mortal(65536, 1000001 % 65536 / 4 * 4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mortal_birth_death_works() {
|
||||
let e = Era::mortal(4, 6);
|
||||
for i in 6..10 {
|
||||
assert_eq!(e.birth(i), 6);
|
||||
assert_eq!(e.death(i), 10);
|
||||
}
|
||||
|
||||
// wrong because it's outside of the (current...current + period) range
|
||||
assert_ne!(e.birth(10), 6);
|
||||
assert_ne!(e.birth(5), 6);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@
|
||||
// end::description[]
|
||||
|
||||
mod unchecked_extrinsic;
|
||||
mod unchecked_mortal_extrinsic;
|
||||
mod era;
|
||||
mod checked_extrinsic;
|
||||
mod header;
|
||||
mod block;
|
||||
@@ -27,6 +29,8 @@ mod digest;
|
||||
mod tests;
|
||||
|
||||
pub use self::unchecked_extrinsic::UncheckedExtrinsic;
|
||||
pub use self::unchecked_mortal_extrinsic::UncheckedMortalExtrinsic;
|
||||
pub use self::era::Era;
|
||||
pub use self::checked_extrinsic::CheckedExtrinsic;
|
||||
pub use self::header::Header;
|
||||
pub use self::block::{Block, SignedBlock, BlockId};
|
||||
|
||||
@@ -21,7 +21,7 @@ use std::fmt;
|
||||
|
||||
use rstd::prelude::*;
|
||||
use codec::{Decode, Encode, Input};
|
||||
use traits::{self, Member, SimpleArithmetic, MaybeDisplay};
|
||||
use traits::{self, Member, SimpleArithmetic, MaybeDisplay, Lookup};
|
||||
use super::CheckedExtrinsic;
|
||||
|
||||
/// A extrinsic right from the external world. This is unchecked and so
|
||||
@@ -29,10 +29,9 @@ use super::CheckedExtrinsic;
|
||||
#[derive(PartialEq, Eq, Clone)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
pub struct UncheckedExtrinsic<Address, Index, Call, Signature> {
|
||||
/// The signature and address, if this is a signed extrinsic.
|
||||
pub signature: Option<(Address, Signature)>,
|
||||
/// The number of extrinsics have come before from the same signer.
|
||||
pub index: Index,
|
||||
/// The signature, address and number of extrinsics have come before from
|
||||
/// the same signer, if this is a signed extrinsic.
|
||||
pub signature: Option<(Address, Signature, Index)>,
|
||||
/// The function that should be called.
|
||||
pub function: Call,
|
||||
}
|
||||
@@ -41,17 +40,15 @@ impl<Address, Index, Call, Signature> UncheckedExtrinsic<Address, Index, Call, S
|
||||
/// New instance of a signed extrinsic aka "transaction".
|
||||
pub fn new_signed(index: Index, function: Call, signed: Address, signature: Signature) -> Self {
|
||||
UncheckedExtrinsic {
|
||||
signature: Some((signed, signature)),
|
||||
index,
|
||||
signature: Some((signed, signature, index)),
|
||||
function,
|
||||
}
|
||||
}
|
||||
|
||||
/// New instance of an unsigned extrinsic aka "inherent".
|
||||
pub fn new_unsigned(index: Index, function: Call) -> Self {
|
||||
pub fn new_unsigned(function: Call) -> Self {
|
||||
UncheckedExtrinsic {
|
||||
signature: None,
|
||||
index,
|
||||
function,
|
||||
}
|
||||
}
|
||||
@@ -62,7 +59,7 @@ impl<Address, Index, Call, Signature> UncheckedExtrinsic<Address, Index, Call, S
|
||||
}
|
||||
}
|
||||
|
||||
impl<Address, AccountId, Index, Call, Signature, ThisLookup> traits::Checkable<ThisLookup>
|
||||
impl<Address, AccountId, Index, Call, Signature, Context> traits::Checkable<Context>
|
||||
for UncheckedExtrinsic<Address, Index, Call, Signature>
|
||||
where
|
||||
Address: Member + MaybeDisplay,
|
||||
@@ -70,27 +67,25 @@ where
|
||||
Call: Encode + Member,
|
||||
Signature: Member + traits::Verify<Signer=AccountId>,
|
||||
AccountId: Member + MaybeDisplay,
|
||||
ThisLookup: FnOnce(Address) -> Result<AccountId, &'static str>,
|
||||
Context: Lookup<Source=Address, Target=AccountId>,
|
||||
{
|
||||
type Checked = CheckedExtrinsic<AccountId, Index, Call>;
|
||||
|
||||
fn check_with(self, lookup: ThisLookup) -> Result<Self::Checked, &'static str> {
|
||||
fn check(self, context: &Context) -> Result<Self::Checked, &'static str> {
|
||||
Ok(match self.signature {
|
||||
Some((signed, signature)) => {
|
||||
let payload = (self.index, self.function);
|
||||
let signed = lookup(signed)?;
|
||||
Some((signed, signature, index)) => {
|
||||
let payload = (index, self.function);
|
||||
let signed = context.lookup(signed)?;
|
||||
if !::verify_encoded_lazy(&signature, &payload, &signed) {
|
||||
return Err("bad signature in extrinsic")
|
||||
}
|
||||
CheckedExtrinsic {
|
||||
signed: Some(signed),
|
||||
index: payload.0,
|
||||
signed: Some((signed, payload.0)),
|
||||
function: payload.1,
|
||||
}
|
||||
}
|
||||
None => CheckedExtrinsic {
|
||||
signed: None,
|
||||
index: self.index,
|
||||
function: self.function,
|
||||
},
|
||||
})
|
||||
@@ -114,7 +109,6 @@ where
|
||||
|
||||
Some(UncheckedExtrinsic {
|
||||
signature: Decode::decode(input)?,
|
||||
index: Decode::decode(input)?,
|
||||
function: Decode::decode(input)?,
|
||||
})
|
||||
}
|
||||
@@ -136,7 +130,6 @@ where
|
||||
v.extend(&[0u8; 4]);
|
||||
|
||||
self.signature.encode_to(&mut v);
|
||||
self.index.encode_to(&mut v);
|
||||
self.function.encode_to(&mut v);
|
||||
|
||||
let length = (v.len() - 4) as u32;
|
||||
@@ -154,6 +147,6 @@ impl<Address, Index, Call, Signature> fmt::Debug for UncheckedExtrinsic<Address,
|
||||
Call: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "UncheckedExtrinsic({:?}, {:?}, {:?})", self.signature.as_ref().map(|x| &x.0), self.function, self.index)
|
||||
write!(f, "UncheckedExtrinsic({:?}, {:?})", self.signature.as_ref().map(|x| (&x.0, &x.2)), self.function)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
// Copyright 2017 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 codec::{Decode, Encode, Input};
|
||||
use traits::{self, Member, SimpleArithmetic, MaybeDisplay, GetHeight, BlockNumberToHash, Lookup,
|
||||
Checkable};
|
||||
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)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
/// `true` if there is a signature.
|
||||
pub fn is_signed(&self) -> bool {
|
||||
self.signature.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
+ GetHeight<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 h = context.block_number_to_hash(BlockNumber::sa(era.birth(context.get_height().as_())))
|
||||
.ok_or("transaction birth block ancient")?;
|
||||
let payload = (index, self.function, 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)),
|
||||
function: 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 u32, which has the total number of bytes following (we don't need
|
||||
// to use this).
|
||||
let _length_do_not_remove_me_see_above: u32 = 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> {
|
||||
let mut v = Vec::new();
|
||||
|
||||
// need to prefix with the total length as u32 to ensure it's binary comptible with
|
||||
// Vec<u8>. we'll make room for it here, then overwrite once we know the length.
|
||||
v.extend(&[0u8; 4]);
|
||||
|
||||
// 1 byte version id.
|
||||
match self.signature.as_ref() {
|
||||
Some(s) => {
|
||||
v.push(TRANSACTION_VERSION | 0b1000_0000);
|
||||
s.encode_to(&mut v);
|
||||
}
|
||||
None => {
|
||||
v.push(TRANSACTION_VERSION & 0b0111_1111);
|
||||
}
|
||||
}
|
||||
self.function.encode_to(&mut v);
|
||||
|
||||
let length = (v.len() - 4) as u32;
|
||||
length.using_encoded(|s| v[0..4].copy_from_slice(s));
|
||||
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
/// TODO: use derive when possible.
|
||||
#[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::*;
|
||||
|
||||
struct TestContext;
|
||||
impl Lookup for TestContext {
|
||||
type Source = u64;
|
||||
type Target = u64;
|
||||
fn lookup(&self, s: u64) -> Result<u64, &'static str> { Ok(s) }
|
||||
}
|
||||
impl GetHeight for TestContext {
|
||||
type BlockNumber = u64;
|
||||
fn get_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_FUNCTION: u64 = 0;
|
||||
const DUMMY_ACCOUNTID: u64 = 0;
|
||||
|
||||
type Ex = UncheckedMortalExtrinsic<u64, u64, u64, TestSig>;
|
||||
type CEx = CheckedExtrinsic<u64, u64, u64>;
|
||||
|
||||
#[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, 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());
|
||||
assert!(<Ex as Checkable<TestContext>>::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());
|
||||
assert_eq!(<Ex as Checkable<TestContext>>::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, (DUMMY_ACCOUNTID, DUMMY_FUNCTION, 0u64).encode()), Era::immortal());
|
||||
assert!(ux.is_signed());
|
||||
assert_eq!(<Ex as Checkable<TestContext>>::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, (DUMMY_ACCOUNTID, DUMMY_FUNCTION, 42u64).encode()), Era::mortal(32, 42));
|
||||
assert!(ux.is_signed());
|
||||
assert_eq!(<Ex as Checkable<TestContext>>::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, (DUMMY_ACCOUNTID, DUMMY_FUNCTION, 11u64).encode()), Era::mortal(32, 11));
|
||||
assert!(ux.is_signed());
|
||||
assert_eq!(<Ex as Checkable<TestContext>>::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, 10u64).encode()), Era::mortal(32, 10));
|
||||
assert!(ux.is_signed());
|
||||
assert_eq!(<Ex as Checkable<TestContext>>::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, 43u64).encode()), Era::mortal(32, 43));
|
||||
assert!(ux.is_signed());
|
||||
assert_eq!(<Ex as Checkable<TestContext>>::check(ux, &TestContext), Err("bad signature in extrinsic"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user