Skip to content

Commit

Permalink
update evm runtimes to account for nonce non updates during the pre_d…
Browse files Browse the repository at this point in the history
…ispatch
  • Loading branch information
vedhavyas committed Apr 26, 2024
1 parent db5e0a5 commit e54a510
Show file tree
Hide file tree
Showing 7 changed files with 316 additions and 18 deletions.
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions domains/pallets/evm_nonce_tracker/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[package]
name = "pallet-evm-nonce-tracker"
version = "0.1.0"
authors = ["Subspace Labs <https://subspace.network>"]
edition = "2021"
license = "Apache-2.0"
homepage = "https://subspace.network"
repository = "https://github.com/subspace/subspace"
description = "Subspace node pallet for EVM account nonce tracker."
include = [
"/src",
"/Cargo.toml",
]

[dependencies]
codec = { package = "parity-scale-codec", version = "3.6.5", default-features = false, features = ["derive"] }
frame-support = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "44d742b90e7852aed1f08ab5299d5d88cfa1c6ed" }
frame-system = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "44d742b90e7852aed1f08ab5299d5d88cfa1c6ed" }
scale-info = { version = "2.11.1", default-features = false, features = ["derive"] }
sp-core = { default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "44d742b90e7852aed1f08ab5299d5d88cfa1c6ed" }

[features]
default = ["std"]
std = [
"codec/std",
"frame-support/std",
"frame-system/std",
"scale-info/std",
"sp-core/std",
]
63 changes: 63 additions & 0 deletions domains/pallets/evm_nonce_tracker/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (C) 2023 Subspace Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Pallet EVM Account nonce tracker
#![cfg_attr(not(feature = "std"), no_std)]

pub use pallet::*;
use sp_core::{H160, U256};

#[frame_support::pallet]
mod pallet {
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::BlockNumberFor;
use sp_core::{H160, U256};

#[pallet::config]
pub trait Config: frame_system::Config {}

/// Storage to hold evm account nonces.
/// This is only used for pre_dispatch since EVM pre_dispatch does not
/// increment account nonce.
#[pallet::storage]
pub(super) type AccountNonce<T> = StorageMap<_, Identity, H160, U256, OptionQuery>;

/// Pallet EVM account nonce tracker.
#[pallet::pallet]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);

#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_finalize(_now: BlockNumberFor<T>) {
// clear the storage since we would start with updated nonce
// during the pre_dispatch in next block
let _ = AccountNonce::<T>::clear(u32::MAX, None);
}
}
}

impl<T: Config> Pallet<T> {
/// Returns current nonce for the given account.
pub fn account_nonce(account: H160) -> Option<U256> {
AccountNonce::<T>::get(account)
}

/// Set nonce to the account.
pub fn set_account_nonce(account: H160, nonce: U256) {
AccountNonce::<T>::set(account, Some(nonce))
}
}
2 changes: 2 additions & 0 deletions domains/runtime/evm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ pallet-domain-id = { version = "0.1.0", path = "../../pallets/domain-id", defaul
pallet-ethereum = { default-features = false, git = "https://github.com/subspace/frontier", rev = "4354330f71535a5459b8e3c7e7ed0c0d612b5e0e" }
pallet-evm = { version = "6.0.0-dev", default-features = false, git = "https://github.com/subspace/frontier", rev = "4354330f71535a5459b8e3c7e7ed0c0d612b5e0e" }
pallet-evm-chain-id = { version = "1.0.0-dev", default-features = false, git = "https://github.com/subspace/frontier", rev = "4354330f71535a5459b8e3c7e7ed0c0d612b5e0e" }
pallet-evm-nonce-tracker = { version = "0.1.0", path = "../../pallets/evm_nonce_tracker", default-features = false }
pallet-evm-precompile-modexp = { version = "2.0.0-dev", default-features = false, git = "https://github.com/subspace/frontier", rev = "4354330f71535a5459b8e3c7e7ed0c0d612b5e0e" }
pallet-evm-precompile-sha3fips = { version = "2.0.0-dev", default-features = false, git = "https://github.com/subspace/frontier", rev = "4354330f71535a5459b8e3c7e7ed0c0d612b5e0e" }
pallet-evm-precompile-simple = { version = "2.0.0-dev", default-features = false, git = "https://github.com/subspace/frontier", rev = "4354330f71535a5459b8e3c7e7ed0c0d612b5e0e" }
Expand Down Expand Up @@ -93,6 +94,7 @@ std = [
"pallet-ethereum/std",
"pallet-evm/std",
"pallet-evm-chain-id/std",
"pallet-evm-nonce-tracker/std",
"pallet-evm-precompile-modexp/std",
"pallet-evm-precompile-sha3fips/std",
"pallet-evm-precompile-simple/std",
Expand Down
112 changes: 103 additions & 9 deletions domains/runtime/evm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,12 @@ use frame_support::weights::constants::{ParityDbWeight, WEIGHT_REF_TIME_PER_SECO
use frame_support::weights::{ConstantMultiplier, IdentityFee, Weight};
use frame_support::{construct_runtime, parameter_types};
use frame_system::limits::{BlockLength, BlockWeights};
use frame_system::CheckWeight;
use pallet_block_fees::fees::OnChargeDomainTransaction;
use pallet_ethereum::Call::transact;
use pallet_ethereum::{PostLogContent, Transaction as EthereumTransaction, TransactionStatus};
use pallet_ethereum::{
Call, PostLogContent, Transaction as EthereumTransaction, TransactionData, TransactionStatus,
};
use pallet_evm::{
Account as EVMAccount, EnsureAddressNever, EnsureAddressRoot, FeeCalculator,
IdentityAddressMapping, Runner,
Expand Down Expand Up @@ -70,6 +73,7 @@ use sp_runtime::{
ExtrinsicInclusionMode,
};
pub use sp_runtime::{MultiAddress, Perbill, Permill};
use sp_std::cmp::max;
use sp_std::marker::PhantomData;
use sp_std::prelude::*;
use sp_subspace_mmr::domain_mmr_runtime_interface::verify_mmr_proof;
Expand All @@ -80,6 +84,9 @@ use subspace_runtime_primitives::{
SlowAdjustingFeeUpdate, SSC,
};

/// Custom error when nonce overflow occurs
const ERR_NONCE_OVERFLOW: u8 = 100;

/// Alias to 512-bit hash when used in the context of a transaction signature on the chain.
pub type Signature = EthereumSignature;

Expand Down Expand Up @@ -641,6 +648,8 @@ impl pallet_evm::Config for Runtime {
type WeightInfo = pallet_evm::weights::SubstrateWeight<Self>;
}

impl pallet_evm_nonce_tracker::Config for Runtime {}

parameter_types! {
pub const PostOnlyBlockHash: PostLogContent = PostLogContent::OnlyBlockHash;
}
Expand Down Expand Up @@ -712,6 +721,7 @@ construct_runtime!(
EVM: pallet_evm = 81,
EVMChainId: pallet_evm_chain_id = 82,
BaseFee: pallet_base_fee = 83,
EVMNoncetracker: pallet_evm_nonce_tracker = 84,

// domain instance stuff
SelfDomainId: pallet_domain_id = 90,
Expand Down Expand Up @@ -817,6 +827,75 @@ mod benches {
);
}

/// Custom pre_dispatch for extrinsic verification.
/// Most of the logic is same as `pre_dispatch_self_contained` except
/// - we use `validate_self_contained` instead `pre_dispatch_self_contained`
/// since the nonce is not incremented in `pre_dispatch_self_contained`
/// - Manually track the account nonce to check either Stale or Future nonce.
fn pre_dispatch_evm_transaction(
account_id: H160,
call: RuntimeCall,
dispatch_info: &DispatchInfoOf<RuntimeCall>,
len: usize,
) -> Result<(), TransactionValidityError> {
match call {
RuntimeCall::Ethereum(call) => {
// Withdraw the consensus chain storage fee from the caller and record
// it in the `BlockFees`
let consensus_storage_fee =
BlockFees::consensus_chain_byte_fee() * Balance::from(len as u32);
match <InnerEVMCurrencyAdapter as pallet_evm::OnChargeEVMTransaction<Runtime>>::withdraw_fee(
&account_id,
consensus_storage_fee.into(),
) {
Ok(None) => {}
Ok(Some(paid_consensus_storage_fee)) => {
BlockFees::note_consensus_storage_fee(paid_consensus_storage_fee.peek())
}
Err(_) => return Err(InvalidTransaction::Payment.into()),
}

if let Some(transaction_validity) =
call.validate_self_contained(&account_id, dispatch_info, len)
{
transaction_validity.map(|_| ())?;

if let Call::transact { transaction } = call {
CheckWeight::<Runtime>::do_pre_dispatch(dispatch_info, len)?;

let transaction_data: TransactionData = (&transaction).into();
let transaction_nonce = transaction_data.nonce;
// if this the first transaction from this sender, then use the
// transaction nonce as first nonce.
// if the current account nonce is more the tracked nonce, then
// pick the highest nonce
let account_nonce = {
let tracked_nonce =
EVMNoncetracker::account_nonce(account_id).unwrap_or(transaction_nonce);
let account_nonce = EVM::account_basic(&account_id).0.nonce;
max(tracked_nonce, account_nonce)
};

if transaction_nonce < account_nonce {
return Err(InvalidTransaction::Stale.into());
} else if transaction_nonce > account_nonce {
return Err(InvalidTransaction::Future.into());
}

let next_nonce = account_nonce
.checked_add(U256::one())
.ok_or(InvalidTransaction::Custom(ERR_NONCE_OVERFLOW))?;

EVMNoncetracker::set_account_nonce(account_id, next_nonce);
}
}

Ok(())
}
_ => Err(InvalidTransaction::Call.into()),
}
}

fn check_transaction_and_do_pre_dispatch_inner(
uxt: &<Block as BlockT>::Extrinsic,
) -> Result<(), TransactionValidityError> {
Expand All @@ -837,19 +916,34 @@ fn check_transaction_and_do_pre_dispatch_inner(
// which would help to maintain context across multiple transaction validity check against same
// runtime instance.
match xt.signed {
CheckedSignature::Signed(account_id, extra) => extra
.pre_dispatch(&account_id, &xt.function, &dispatch_info, encoded_len)
.map(|_| ()),
CheckedSignature::Signed(account_id, extra) => {
// if a sender sends a one evm transaction first and substrate transaction
// after with same nonce, then reject the second transaction
// if sender reverse the transaction types, substrate first and evm second,
// since substrate updates nonce in pre_dispatch, evm transaction will be rejected.
if let Some(tracked_nonce) = EVMNoncetracker::account_nonce(H160::from(account_id.0)) {
let account_nonce = U256::from(System::account_nonce(account_id));
let current_nonce = max(tracked_nonce, account_nonce);
let transaction_nonce = U256::from(extra.5 .0);
if transaction_nonce < current_nonce {
return Err(InvalidTransaction::Stale.into());
} else if transaction_nonce > current_nonce {
return Err(InvalidTransaction::Future.into());
}
}

extra
.pre_dispatch(&account_id, &xt.function, &dispatch_info, encoded_len)
.map(|_| ())
}
CheckedSignature::Unsigned => {
Runtime::pre_dispatch(&xt.function).map(|_| ())?;
SignedExtra::pre_dispatch_unsigned(&xt.function, &dispatch_info, encoded_len)
.map(|_| ())
}
CheckedSignature::SelfContained(self_contained_signing_info) => xt
.function
.pre_dispatch_self_contained(&self_contained_signing_info, &dispatch_info, encoded_len)
.ok_or(TransactionValidityError::Invalid(InvalidTransaction::Call))
.map(|_| ()),
CheckedSignature::SelfContained(account_id) => {
pre_dispatch_evm_transaction(account_id, xt.function, &dispatch_info, encoded_len)
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions domains/test/runtime/evm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pallet-domain-id = { version = "0.1.0", path = "../../../pallets/domain-id", def
pallet-ethereum = { default-features = false, git = "https://github.com/subspace/frontier", rev = "4354330f71535a5459b8e3c7e7ed0c0d612b5e0e" }
pallet-evm = { version = "6.0.0-dev", default-features = false, git = "https://github.com/subspace/frontier", rev = "4354330f71535a5459b8e3c7e7ed0c0d612b5e0e" }
pallet-evm-chain-id = { version = "1.0.0-dev", default-features = false, git = "https://github.com/subspace/frontier", rev = "4354330f71535a5459b8e3c7e7ed0c0d612b5e0e" }
pallet-evm-nonce-tracker = { version = "0.1.0", path = "../../../pallets/evm_nonce_tracker", default-features = false }
pallet-evm-precompile-modexp = { version = "2.0.0-dev", default-features = false, git = "https://github.com/subspace/frontier", rev = "4354330f71535a5459b8e3c7e7ed0c0d612b5e0e" }
pallet-evm-precompile-sha3fips = { version = "2.0.0-dev", default-features = false, git = "https://github.com/subspace/frontier", rev = "4354330f71535a5459b8e3c7e7ed0c0d612b5e0e" }
pallet-evm-precompile-simple = { version = "2.0.0-dev", default-features = false, git = "https://github.com/subspace/frontier", rev = "4354330f71535a5459b8e3c7e7ed0c0d612b5e0e" }
Expand Down Expand Up @@ -88,6 +89,7 @@ std = [
"pallet-ethereum/std",
"pallet-evm/std",
"pallet-evm-chain-id/std",
"pallet-evm-nonce-tracker/std",
"pallet-evm-precompile-modexp/std",
"pallet-evm-precompile-sha3fips/std",
"pallet-evm-precompile-simple/std",
Expand Down
Loading

0 comments on commit e54a510

Please sign in to comment.