Composite accounts (#4820)

* Basic account composition.

* Add try_mutate_exists

* De-duplicate

* Refactor away the UpdateBalanceOutcome

* Expunge final UpdateBalanceOutcome refs

* Refactor transfer

* Refactor reservable currency stuff.

* Test with the alternative setup.

* Fixes

* Test with both setups.

* Fixes

* Fix

* Fix macros

* Make indices opt-in

* Remove CreationFee, and make indices opt-in.

* Fix construct_runtime

* Fix last few bits

* Fix tests

* Update trait impls

* Don't hardcode the system event

* Make tests build and fix some stuff.

* Pointlessly bump runtime version

* Fix benchmark

* Another fix

* Whitespace

* Make indices module economically safe

* Migrations for indices.

* Fix

* Whilespace

* Trim defunct migrations

* Remove unused storage item

* More contains_key fixes

* Docs.

* Bump runtime

* Remove unneeded code

* Fix test

* Fix test

* Update frame/balances/src/lib.rs

Co-Authored-By: Shawn Tabrizi <shawntabrizi@gmail.com>

* Fix ED logic

* Repatriate reserved logic

* Typo

* Fix typo

* Update frame/system/src/lib.rs

Co-Authored-By: Shawn Tabrizi <shawntabrizi@gmail.com>

* Update frame/system/src/lib.rs

Co-Authored-By: Shawn Tabrizi <shawntabrizi@gmail.com>

* Last few fixes

* Another fix

* Build fix

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
Co-authored-by: Jaco Greeff <jacogr@gmail.com>
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
This commit is contained in:
Gavin Wood
2020-02-14 00:47:51 +00:00
committed by GitHub
parent d3fa8c91af
commit 5b7512e2e4
79 changed files with 2459 additions and 2100 deletions
+320 -183
View File
@@ -113,35 +113,19 @@ use sp_runtime::{
use sp_core::{ChangesTrieConfiguration, storage::well_known_keys};
use frame_support::{
decl_module, decl_event, decl_storage, decl_error, storage, Parameter,
traits::{Contains, Get, ModuleToIndex, OnReapAccount},
traits::{
Contains, Get, ModuleToIndex, OnNewAccount, OnReapAccount, IsDeadAccount, Happened,
StoredMap
},
weights::{Weight, DispatchInfo, DispatchClass, SimpleDispatchInfo},
};
use codec::{Encode, Decode};
use codec::{Encode, Decode, FullCodec, EncodeLike};
#[cfg(any(feature = "std", test))]
use sp_io::TestExternalities;
pub mod offchain;
/// Handler for when a new account has been created.
#[impl_trait_for_tuples::impl_for_tuples(30)]
pub trait OnNewAccount<AccountId> {
/// A new account `who` has been registered.
fn on_new_account(who: &AccountId);
}
/// Determiner to say whether a given account is unused.
pub trait IsDeadAccount<AccountId> {
/// Is the given account dead?
fn is_dead_account(who: &AccountId) -> bool;
}
impl<AccountId> IsDeadAccount<AccountId> for () {
fn is_dead_account(_who: &AccountId) -> bool {
true
}
}
/// Compute the trie root of a list of extrinsics.
pub fn extrinsics_root<H: Hash, E: codec::Encode>(extrinsics: &[E]) -> H::Output {
extrinsics_data_root::<H>(extrinsics.iter().map(codec::Encode::encode).collect())
@@ -200,7 +184,7 @@ pub trait Trait: 'static + Eq + Clone {
>;
/// The aggregated event type of the runtime.
type Event: Parameter + Member + From<Event> + Debug;
type Event: Parameter + Member + From<Event<Self>> + Debug;
/// Maximum number of block number to block hash mappings to keep (oldest pruned first).
type BlockHashCount: Get<Self::BlockNumber>;
@@ -224,6 +208,18 @@ pub trait Trait: 'static + Eq + Clone {
/// Expects the `ModuleToIndex` type that is being generated by `construct_runtime!` in the
/// runtime. For tests it is okay to use `()` as type (returns `0` for each input).
type ModuleToIndex: ModuleToIndex;
/// Data to be associated with an account (other than nonce/transaction counter, which this
/// module does regardless).
type AccountData: Member + FullCodec + Clone + Default;
/// Handler for when a new account has just been created.
type OnNewAccount: OnNewAccount<Self::AccountId>;
/// A function that is invoked when an account has been determined to be dead.
///
/// All resources should be cleaned up associated with the given account.
type OnReapAccount: OnReapAccount<Self::AccountId>;
}
pub type DigestOf<T> = generic::Digest<<T as Trait>::Hash>;
@@ -232,111 +228,6 @@ pub type DigestItemOf<T> = generic::DigestItem<<T as Trait>::Hash>;
pub type Key = Vec<u8>;
pub type KeyValue = (Vec<u8>, Vec<u8>);
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
type Error = Error<T>;
/// A big dispatch that will disallow any other transaction to be included.
// TODO: this must be preferable available for testing really (not possible at the moment).
#[weight = SimpleDispatchInfo::MaxOperational]
fn fill_block(origin) {
ensure_root(origin)?;
}
/// Make some on-chain remark.
#[weight = SimpleDispatchInfo::FixedNormal(10_000)]
fn remark(origin, _remark: Vec<u8>) {
ensure_signed(origin)?;
}
/// Set the number of pages in the WebAssembly environment's heap.
#[weight = SimpleDispatchInfo::FixedOperational(10_000)]
fn set_heap_pages(origin, pages: u64) {
ensure_root(origin)?;
storage::unhashed::put_raw(well_known_keys::HEAP_PAGES, &pages.encode());
}
/// Set the new runtime code.
#[weight = SimpleDispatchInfo::FixedOperational(200_000)]
pub fn set_code(origin, code: Vec<u8>) {
ensure_root(origin)?;
let current_version = T::Version::get();
let new_version = sp_io::misc::runtime_version(&code)
.and_then(|v| RuntimeVersion::decode(&mut &v[..]).ok())
.ok_or_else(|| Error::<T>::FailedToExtractRuntimeVersion)?;
if new_version.spec_name != current_version.spec_name {
Err(Error::<T>::InvalidSpecName)?
}
if new_version.spec_version < current_version.spec_version {
Err(Error::<T>::SpecVersionNotAllowedToDecrease)?
} else if new_version.spec_version == current_version.spec_version {
if new_version.impl_version < current_version.impl_version {
Err(Error::<T>::ImplVersionNotAllowedToDecrease)?
} else if new_version.impl_version == current_version.impl_version {
Err(Error::<T>::SpecOrImplVersionNeedToIncrease)?
}
}
storage::unhashed::put_raw(well_known_keys::CODE, &code);
Self::deposit_event(Event::CodeUpdated);
}
/// Set the new runtime code without doing any checks of the given `code`.
#[weight = SimpleDispatchInfo::FixedOperational(200_000)]
pub fn set_code_without_checks(origin, code: Vec<u8>) {
ensure_root(origin)?;
storage::unhashed::put_raw(well_known_keys::CODE, &code);
Self::deposit_event(Event::CodeUpdated);
}
/// Set the new changes trie configuration.
#[weight = SimpleDispatchInfo::FixedOperational(20_000)]
pub fn set_changes_trie_config(origin, changes_trie_config: Option<ChangesTrieConfiguration>) {
ensure_root(origin)?;
match changes_trie_config.clone() {
Some(changes_trie_config) => storage::unhashed::put_raw(
well_known_keys::CHANGES_TRIE_CONFIG,
&changes_trie_config.encode(),
),
None => storage::unhashed::kill(well_known_keys::CHANGES_TRIE_CONFIG),
}
let log = generic::DigestItem::ChangesTrieSignal(
generic::ChangesTrieSignal::NewConfiguration(changes_trie_config),
);
Self::deposit_log(log.into());
}
/// Set some items of storage.
#[weight = SimpleDispatchInfo::FixedOperational(10_000)]
fn set_storage(origin, items: Vec<KeyValue>) {
ensure_root(origin)?;
for i in &items {
storage::unhashed::put_raw(&i.0, &i.1);
}
}
/// Kill some items from storage.
#[weight = SimpleDispatchInfo::FixedOperational(10_000)]
fn kill_storage(origin, keys: Vec<Key>) {
ensure_root(origin)?;
for key in &keys {
storage::unhashed::kill(&key);
}
}
/// Kill all storage items with a key that starts with the given prefix.
#[weight = SimpleDispatchInfo::FixedOperational(10_000)]
fn kill_prefix(origin, prefix: Key) {
ensure_root(origin)?;
storage::unhashed::kill_prefix(&prefix);
}
}
}
/// A phase of a block's execution.
#[derive(Encode, Decode, RuntimeDebug)]
#[cfg_attr(feature = "std", derive(Serialize, PartialEq, Eq, Clone))]
@@ -359,40 +250,6 @@ pub struct EventRecord<E: Parameter + Member, T> {
pub topics: Vec<T>,
}
decl_event!(
/// Event for the System module.
pub enum Event {
/// An extrinsic completed successfully.
ExtrinsicSuccess(DispatchInfo),
/// An extrinsic failed.
ExtrinsicFailed(DispatchError, DispatchInfo),
/// `:code` was updated.
CodeUpdated,
}
);
decl_error! {
/// Error for the System module
pub enum Error for Module<T: Trait> {
/// The name of specification does not match between the current runtime
/// and the new runtime.
InvalidSpecName,
/// The specification version is not allowed to decrease between the current runtime
/// and the new runtime.
SpecVersionNotAllowedToDecrease,
/// The implementation version is not allowed to decrease between the current runtime
/// and the new runtime.
ImplVersionNotAllowedToDecrease,
/// The specification or the implementation version need to increase between the
/// current runtime and the new runtime.
SpecOrImplVersionNeedToIncrease,
/// Failed to extract the runtime version from the new runtime.
///
/// Either calling `Core_version` or decoding `RuntimeVersion` failed.
FailedToExtractRuntimeVersion,
}
}
/// Origin for the System module.
#[derive(PartialEq, Eq, Clone, RuntimeDebug)]
pub enum RawOrigin<AccountId> {
@@ -435,29 +292,45 @@ type EventIndex = u32;
decl_storage! {
trait Store for Module<T: Trait> as System {
/// Extrinsics nonce for accounts.
pub AccountNonce get(fn account_nonce): map hasher(blake2_256) T::AccountId => T::Index;
/// The full account information for a particular account ID.
// TODO: should be hasher(twox64_concat) - will need staged migration
// TODO: should not including T::Index (the nonce)
// https://github.com/paritytech/substrate/issues/4917
pub Account get(fn account): map hasher(blake2_256) T::AccountId => (T::Index, T::AccountData);
/// Total extrinsics count for the current block.
ExtrinsicCount: Option<u32>;
/// Total weight for all extrinsics put together, for the current block.
AllExtrinsicsWeight: Option<Weight>;
/// Total length (in bytes) for all extrinsics put together, for the current block.
AllExtrinsicsLen: Option<u32>;
/// Map of block numbers to block hashes.
// TODO: should be hasher(twox64_concat) - will need one-off migration
// https://github.com/paritytech/substrate/issues/4917
pub BlockHash get(fn block_hash) build(|_| vec![(T::BlockNumber::zero(), hash69())]):
map hasher(blake2_256) T::BlockNumber => T::Hash;
/// Extrinsics data for the current block (maps an extrinsic's index to its data).
ExtrinsicData get(fn extrinsic_data): map hasher(blake2_256) u32 => Vec<u8>;
ExtrinsicData get(fn extrinsic_data): map hasher(twox_64_concat) u32 => Vec<u8>;
/// The current block number being processed. Set by `execute_block`.
Number get(fn block_number) build(|_| 1.into()): T::BlockNumber;
/// Hash of the previous block.
ParentHash get(fn parent_hash) build(|_| hash69()): T::Hash;
/// Extrinsics root of the current block, also part of the block header.
ExtrinsicsRoot get(fn extrinsics_root): T::Hash;
/// Digest of the current block, also part of the block header.
Digest get(fn digest): DigestOf<T>;
/// Events deposited for the current block.
Events get(fn events): Vec<EventRecord<T::Event, T::Hash>>;
/// The number of events in the `Events<T>` list.
EventCount get(fn event_count): EventIndex;
@@ -499,6 +372,150 @@ decl_storage! {
}
}
decl_event!(
/// Event for the System module.
pub enum Event<T> where AccountId = <T as Trait>::AccountId {
/// An extrinsic completed successfully.
ExtrinsicSuccess(DispatchInfo),
/// An extrinsic failed.
ExtrinsicFailed(DispatchError, DispatchInfo),
/// `:code` was updated.
CodeUpdated,
/// A new account was created.
NewAccount(AccountId),
/// An account was reaped.
ReapedAccount(AccountId),
}
);
decl_error! {
/// Error for the System module
pub enum Error for Module<T: Trait> {
/// The name of specification does not match between the current runtime
/// and the new runtime.
InvalidSpecName,
/// The specification version is not allowed to decrease between the current runtime
/// and the new runtime.
SpecVersionNotAllowedToDecrease,
/// The implementation version is not allowed to decrease between the current runtime
/// and the new runtime.
ImplVersionNotAllowedToDecrease,
/// The specification or the implementation version need to increase between the
/// current runtime and the new runtime.
SpecOrImplVersionNeedToIncrease,
/// Failed to extract the runtime version from the new runtime.
///
/// Either calling `Core_version` or decoding `RuntimeVersion` failed.
FailedToExtractRuntimeVersion,
}
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
type Error = Error<T>;
/// A big dispatch that will disallow any other transaction to be included.
// TODO: This should only be available for testing, rather than in general usage, but
// that's not possible at present (since it's within the decl_module macro).
#[weight = SimpleDispatchInfo::MaxOperational]
fn fill_block(origin) {
ensure_root(origin)?;
}
/// Make some on-chain remark.
#[weight = SimpleDispatchInfo::FixedNormal(10_000)]
fn remark(origin, _remark: Vec<u8>) {
ensure_signed(origin)?;
}
/// Set the number of pages in the WebAssembly environment's heap.
#[weight = SimpleDispatchInfo::FixedOperational(10_000)]
fn set_heap_pages(origin, pages: u64) {
ensure_root(origin)?;
storage::unhashed::put_raw(well_known_keys::HEAP_PAGES, &pages.encode());
}
/// Set the new runtime code.
#[weight = SimpleDispatchInfo::FixedOperational(200_000)]
pub fn set_code(origin, code: Vec<u8>) {
ensure_root(origin)?;
let current_version = T::Version::get();
let new_version = sp_io::misc::runtime_version(&code)
.and_then(|v| RuntimeVersion::decode(&mut &v[..]).ok())
.ok_or_else(|| Error::<T>::FailedToExtractRuntimeVersion)?;
if new_version.spec_name != current_version.spec_name {
Err(Error::<T>::InvalidSpecName)?
}
if new_version.spec_version < current_version.spec_version {
Err(Error::<T>::SpecVersionNotAllowedToDecrease)?
} else if new_version.spec_version == current_version.spec_version {
if new_version.impl_version < current_version.impl_version {
Err(Error::<T>::ImplVersionNotAllowedToDecrease)?
} else if new_version.impl_version == current_version.impl_version {
Err(Error::<T>::SpecOrImplVersionNeedToIncrease)?
}
}
storage::unhashed::put_raw(well_known_keys::CODE, &code);
Self::deposit_event(RawEvent::CodeUpdated);
}
/// Set the new runtime code without doing any checks of the given `code`.
#[weight = SimpleDispatchInfo::FixedOperational(200_000)]
pub fn set_code_without_checks(origin, code: Vec<u8>) {
ensure_root(origin)?;
storage::unhashed::put_raw(well_known_keys::CODE, &code);
Self::deposit_event(RawEvent::CodeUpdated);
}
/// Set the new changes trie configuration.
#[weight = SimpleDispatchInfo::FixedOperational(20_000)]
pub fn set_changes_trie_config(origin, changes_trie_config: Option<ChangesTrieConfiguration>) {
ensure_root(origin)?;
match changes_trie_config.clone() {
Some(changes_trie_config) => storage::unhashed::put_raw(
well_known_keys::CHANGES_TRIE_CONFIG,
&changes_trie_config.encode(),
),
None => storage::unhashed::kill(well_known_keys::CHANGES_TRIE_CONFIG),
}
let log = generic::DigestItem::ChangesTrieSignal(
generic::ChangesTrieSignal::NewConfiguration(changes_trie_config),
);
Self::deposit_log(log.into());
}
/// Set some items of storage.
#[weight = SimpleDispatchInfo::FixedOperational(10_000)]
fn set_storage(origin, items: Vec<KeyValue>) {
ensure_root(origin)?;
for i in &items {
storage::unhashed::put_raw(&i.0, &i.1);
}
}
/// Kill some items from storage.
#[weight = SimpleDispatchInfo::FixedOperational(10_000)]
fn kill_storage(origin, keys: Vec<Key>) {
ensure_root(origin)?;
for key in &keys {
storage::unhashed::kill(&key);
}
}
/// Kill all storage items with a key that starts with the given prefix.
#[weight = SimpleDispatchInfo::FixedOperational(10_000)]
fn kill_prefix(origin, prefix: Key) {
ensure_root(origin)?;
storage::unhashed::kill_prefix(&prefix);
}
}
}
pub struct EnsureRoot<AccountId>(sp_std::marker::PhantomData<AccountId>);
impl<
O: Into<Result<RawOrigin<AccountId>, O>> + From<RawOrigin<AccountId>>,
@@ -622,7 +639,7 @@ impl<T: Trait> Module<T> {
Self::deposit_event_indexed(&[], event.into());
}
/// Deposits an event into this block's event record adding this event
/// Deposits an event into this block's event record adding this event
/// to the corresponding topic indexes.
///
/// This will update storage entries that correspond to the specified topics.
@@ -832,9 +849,14 @@ impl<T: Trait> Module<T> {
/// Return the chain's current runtime version.
pub fn runtime_version() -> RuntimeVersion { T::Version::get() }
/// Retrieve the account transaction counter from storage.
pub fn account_nonce(who: impl EncodeLike<T::AccountId>) -> T::Index {
Account::<T>::get(who).0
}
/// Increment a particular account's nonce by 1.
pub fn inc_account_nonce(who: &T::AccountId) {
<AccountNonce<T>>::insert(who, Self::account_nonce(who) + T::Index::one());
pub fn inc_account_nonce(who: impl EncodeLike<T::AccountId>) {
Account::<T>::mutate(who, |a| a.0 += T::Index::one());
}
/// Note what the extrinsic data of the current extrinsic index is. If this
@@ -851,10 +873,10 @@ impl<T: Trait> Module<T> {
pub fn note_applied_extrinsic(r: &DispatchOutcome, _encoded_len: u32, info: DispatchInfo) {
Self::deposit_event(
match r {
Ok(()) => Event::ExtrinsicSuccess(info),
Ok(()) => RawEvent::ExtrinsicSuccess(info),
Err(err) => {
sp_runtime::print(err);
Event::ExtrinsicFailed(err.clone(), info)
RawEvent::ExtrinsicFailed(err.clone(), info)
},
}
);
@@ -879,12 +901,118 @@ impl<T: Trait> Module<T> {
let xts_root = extrinsics_data_root::<T::Hashing>(extrinsics);
<ExtrinsicsRoot<T>>::put(xts_root);
}
/// An account is being created.
pub fn on_created_account(who: T::AccountId) {
T::OnNewAccount::on_new_account(&who);
Self::deposit_event(RawEvent::NewAccount(who));
}
/// Kill the account and reap any related information.
pub fn kill_account(who: T::AccountId) {
if Account::<T>::contains_key(&who) {
Account::<T>::remove(&who);
Self::on_killed_account(who);
}
}
/// Do anything that needs to be done after an account has been killed.
fn on_killed_account(who: T::AccountId) {
T::OnReapAccount::on_reap_account(&who);
Self::deposit_event(RawEvent::ReapedAccount(who));
}
}
impl<T: Trait> OnReapAccount<T::AccountId> for Module<T> {
/// Remove the nonce for the account. Account is considered fully removed from the system.
fn on_reap_account(who: &T::AccountId) {
<AccountNonce<T>>::remove(who);
/// Event handler which calls on_created_account when it happens.
pub struct CallOnCreatedAccount<T>(PhantomData<T>);
impl<T: Trait> Happened<T::AccountId> for CallOnCreatedAccount<T> {
fn happened(who: &T::AccountId) {
Module::<T>::on_created_account(who.clone());
}
}
/// Event handler which calls kill_account when it happens.
pub struct CallKillAccount<T>(PhantomData<T>);
impl<T: Trait> Happened<T::AccountId> for CallKillAccount<T> {
fn happened(who: &T::AccountId) {
Module::<T>::kill_account(who.clone());
}
}
// Implement StoredMap for a simple single-item, kill-account-on-remove system. This works fine for
// storing a single item which is required to not be empty/default for the account to exist.
// Anything more complex will need more sophisticated logic.
impl<T: Trait> StoredMap<T::AccountId, T::AccountData> for Module<T> {
fn get(k: &T::AccountId) -> T::AccountData {
Account::<T>::get(k).1
}
fn is_explicit(k: &T::AccountId) -> bool {
Account::<T>::contains_key(k)
}
fn insert(k: &T::AccountId, t: T::AccountData) {
let existed = Account::<T>::contains_key(k);
Account::<T>::insert(k, (T::Index::default(), t));
if !existed {
Self::on_created_account(k.clone());
}
}
fn remove(k: &T::AccountId) {
if Account::<T>::contains_key(&k) {
Self::kill_account(k.clone());
}
}
fn mutate<R>(k: &T::AccountId, f: impl FnOnce(&mut T::AccountData) -> R) -> R {
let existed = Account::<T>::contains_key(k);
let r = Account::<T>::mutate(k, |a| f(&mut a.1));
if !existed {
Self::on_created_account(k.clone());
}
r
}
fn mutate_exists<R>(k: &T::AccountId, f: impl FnOnce(&mut Option<T::AccountData>) -> R) -> R {
let (existed, exists, r) = Account::<T>::mutate_exists(k, |maybe_value| {
let existed = maybe_value.is_some();
let (maybe_nonce, mut maybe_extra) = split_inner(maybe_value.take(), |v| v);
let r = f(&mut maybe_extra);
*maybe_value = maybe_extra.map(|extra| (maybe_nonce.unwrap_or_default(), extra));
(existed, maybe_value.is_some(), r)
});
if !existed && exists {
Self::on_created_account(k.clone());
} else if existed && !exists {
Self::on_killed_account(k.clone());
}
r
}
fn try_mutate_exists<R, E>(k: &T::AccountId, f: impl FnOnce(&mut Option<T::AccountData>) -> Result<R, E>) -> Result<R, E> {
Account::<T>::try_mutate_exists(k, |maybe_value| {
let existed = maybe_value.is_some();
let (maybe_nonce, mut maybe_extra) = split_inner(maybe_value.take(), |v| v);
f(&mut maybe_extra).map(|v| {
*maybe_value = maybe_extra.map(|extra| (maybe_nonce.unwrap_or_default(), extra));
(existed, maybe_value.is_some(), v)
})
}).map(|(existed, exists, v)| {
if !existed && exists {
Self::on_created_account(k.clone());
} else if existed && !exists {
Self::on_killed_account(k.clone());
}
v
})
}
}
/// Split an `option` into two constituent options, as defined by a `splitter` function.
pub fn split_inner<T, R, S>(option: Option<T>, splitter: impl FnOnce(T) -> (R, S))
-> (Option<R>, Option<S>)
{
match option {
Some(inner) => {
let (r, s) = splitter(inner);
(Some(r), Some(s))
}
None => (None, None),
}
}
@@ -1052,7 +1180,7 @@ impl<T: Trait> SignedExtension for CheckNonce<T> {
_info: Self::DispatchInfo,
_len: usize,
) -> Result<(), TransactionValidityError> {
let expected = <AccountNonce<T>>::get(who);
let (expected, extra) = Account::<T>::get(who);
if self.0 != expected {
return Err(
if self.0 < expected {
@@ -1062,8 +1190,7 @@ impl<T: Trait> SignedExtension for CheckNonce<T> {
}.into()
)
}
<AccountNonce<T>>::insert(who, expected + T::Index::one());
Account::<T>::insert(who, (expected + T::Index::one(), extra));
Ok(())
}
@@ -1075,7 +1202,7 @@ impl<T: Trait> SignedExtension for CheckNonce<T> {
_len: usize,
) -> TransactionValidity {
// check index
let expected = <AccountNonce<T>>::get(who);
let (expected, _extra) = Account::<T>::get(who);
if self.0 < expected {
return InvalidTransaction::Stale.into()
}
@@ -1097,6 +1224,12 @@ impl<T: Trait> SignedExtension for CheckNonce<T> {
}
}
impl<T: Trait> IsDeadAccount<T::AccountId> for Module<T> {
fn is_dead_account(who: &T::AccountId) -> bool {
!Account::<T>::contains_key(who)
}
}
/// Check for transaction mortality.
#[derive(Encode, Decode, Clone, Eq, PartialEq)]
pub struct CheckEra<T: Trait + Send + Sync>((Era, sp_std::marker::PhantomData<T>));
@@ -1288,14 +1421,18 @@ mod tests {
type MaximumBlockLength = MaximumBlockLength;
type Version = Version;
type ModuleToIndex = ();
type AccountData = ();
type OnNewAccount = ();
type OnReapAccount = ();
}
impl From<Event> for u16 {
fn from(e: Event) -> u16 {
impl From<Event<Test>> for u16 {
fn from(e: Event<Test>) -> u16 {
match e {
Event::ExtrinsicSuccess(..) => 100,
Event::ExtrinsicFailed(..) => 101,
Event::CodeUpdated => 102,
Event::<Test>::ExtrinsicSuccess(..) => 100,
Event::<Test>::ExtrinsicFailed(..) => 101,
Event::<Test>::CodeUpdated => 102,
_ => 103,
}
}
}
@@ -1475,7 +1612,7 @@ mod tests {
#[test]
fn signed_ext_check_nonce_works() {
new_test_ext().execute_with(|| {
<AccountNonce<Test>>::insert(1, 1);
Account::<Test>::insert(1, (1, ()));
let info = DispatchInfo::default();
let len = 0_usize;
// stale
+6 -6
View File
@@ -20,8 +20,8 @@ use codec::Encode;
use sp_std::convert::TryInto;
use sp_std::prelude::Vec;
use sp_runtime::app_crypto::{RuntimeAppPublic, AppPublic, AppSignature};
use sp_runtime::traits::{Extrinsic as ExtrinsicT, IdentifyAccount};
use frame_support::debug;
use sp_runtime::traits::{Extrinsic as ExtrinsicT, IdentifyAccount, One};
use frame_support::{debug, storage::StorageMap};
/// Creates runtime-specific signed transaction.
///
@@ -128,19 +128,19 @@ pub trait SignAndSubmitTransaction<T: crate::Trait, Call> {
fn sign_and_submit(call: impl Into<Call>, public: PublicOf<T, Call, Self>) -> Result<(), ()> {
let call = call.into();
let id = public.clone().into_account();
let expected = <crate::Module<T>>::account_nonce(&id);
let (expected_nonce, extra) = super::Account::<T>::get(&id);
debug::native::debug!(
target: "offchain",
"Creating signed transaction from account: {:?} (nonce: {:?})",
id,
expected,
expected_nonce,
);
let (call, signature_data) = Self::CreateTransaction
::create_transaction::<Self::Signer>(call, public, id.clone(), expected)
::create_transaction::<Self::Signer>(call, public, id.clone(), expected_nonce)
.ok_or(())?;
// increment the nonce. This is fine, since the code should always
// be running in off-chain context, so we NEVER persists data.
<crate::Module<T>>::inc_account_nonce(&id);
super::Account::<T>::insert(&id, (expected_nonce + One::one(), extra));
let xt = Self::Extrinsic::new(call, Some(signature_data)).ok_or(())?;
sp_io::offchain::submit_transaction(xt.encode())