Skip to content

Commit 7de8a10

Browse files
authored
Merge pull request #2307 from opentensor/fix-evm-priority
Stop EVM frontrunning
2 parents f7475e3 + 5845cbb commit 7de8a10

File tree

2 files changed

+104
-16
lines changed

2 files changed

+104
-16
lines changed

runtime/src/lib.rs

Lines changed: 97 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ use sp_runtime::{
6161
AccountIdLookup, BlakeTwo256, Block as BlockT, DispatchInfoOf, Dispatchable, One,
6262
PostDispatchInfoOf, UniqueSaturatedInto, Verify,
6363
},
64-
transaction_validity::{TransactionSource, TransactionValidity, TransactionValidityError},
64+
transaction_validity::{
65+
TransactionPriority, TransactionSource, TransactionValidity, TransactionValidityError,
66+
},
6567
};
6668
use sp_std::cmp::Ordering;
6769
use sp_std::prelude::*;
@@ -1224,6 +1226,10 @@ impl<F: FindAuthor<u32>> FindAuthor<H160> for FindAuthorTruncated<F> {
12241226
}
12251227

12261228
const BLOCK_GAS_LIMIT: u64 = 75_000_000;
1229+
pub const NORMAL_DISPATCH_BASE_PRIORITY: TransactionPriority = 1;
1230+
pub const OPERATIONAL_DISPATCH_PRIORITY: TransactionPriority = 10_000_000_000;
1231+
const EVM_TRANSACTION_BASE_PRIORITY: TransactionPriority = NORMAL_DISPATCH_BASE_PRIORITY;
1232+
const EVM_LOG_TARGET: &str = "runtime::ethereum";
12271233

12281234
/// `WeightPerGas` is an approximate ratio of the amount of Weight per Gas.
12291235
///
@@ -1387,6 +1393,35 @@ impl<B: BlockT> fp_rpc::ConvertTransaction<<B as BlockT>::Extrinsic> for Transac
13871393
}
13881394
}
13891395

1396+
fn adjust_evm_priority_and_warn(
1397+
validity: &mut Option<TransactionValidity>,
1398+
priority_fee: Option<U256>,
1399+
info: &H160,
1400+
) {
1401+
if let Some(Ok(valid_transaction)) = validity.as_mut() {
1402+
let original_priority = valid_transaction.priority;
1403+
valid_transaction.priority = EVM_TRANSACTION_BASE_PRIORITY;
1404+
1405+
let has_priority_fee = priority_fee.is_some_and(|fee| !fee.is_zero());
1406+
if has_priority_fee {
1407+
log::warn!(
1408+
target: EVM_LOG_TARGET,
1409+
"Priority fee/tip from {:?} (max_priority_fee_per_gas: {:?}) is ignored for transaction ordering",
1410+
info,
1411+
priority_fee.unwrap_or_default(),
1412+
);
1413+
} else if original_priority > EVM_TRANSACTION_BASE_PRIORITY {
1414+
log::warn!(
1415+
target: EVM_LOG_TARGET,
1416+
"EVM transaction priority from {:?} reduced from {} to {}; priority tips are ignored for ordering",
1417+
info,
1418+
original_priority,
1419+
EVM_TRANSACTION_BASE_PRIORITY,
1420+
);
1421+
}
1422+
}
1423+
}
1424+
13901425
impl fp_self_contained::SelfContainedCall for RuntimeCall {
13911426
type SignedInfo = H160;
13921427

@@ -1411,7 +1446,21 @@ impl fp_self_contained::SelfContainedCall for RuntimeCall {
14111446
len: usize,
14121447
) -> Option<TransactionValidity> {
14131448
match self {
1414-
RuntimeCall::Ethereum(call) => call.validate_self_contained(info, dispatch_info, len),
1449+
RuntimeCall::Ethereum(call) => {
1450+
let priority_fee = match call {
1451+
pallet_ethereum::Call::transact { transaction } => match transaction {
1452+
EthereumTransaction::EIP1559(tx) => Some(tx.max_priority_fee_per_gas),
1453+
EthereumTransaction::EIP7702(tx) => Some(tx.max_priority_fee_per_gas),
1454+
_ => None,
1455+
},
1456+
_ => None,
1457+
};
1458+
1459+
let mut validity = call.validate_self_contained(info, dispatch_info, len);
1460+
adjust_evm_priority_and_warn(&mut validity, priority_fee, info);
1461+
1462+
validity
1463+
}
14151464
_ => None,
14161465
}
14171466
}
@@ -2622,6 +2671,52 @@ fn test_into_substrate_balance_zero_value() {
26222671
assert_eq!(result, Some(expected_substrate_balance));
26232672
}
26242673

2674+
#[test]
2675+
fn evm_priority_overrides_tip_to_base() {
2676+
let mut validity: Option<TransactionValidity> =
2677+
Some(Ok(sp_runtime::transaction_validity::ValidTransaction {
2678+
priority: 99,
2679+
requires: vec![],
2680+
provides: vec![],
2681+
longevity: sp_runtime::transaction_validity::TransactionLongevity::MAX,
2682+
propagate: true,
2683+
}));
2684+
2685+
let signer = H160::repeat_byte(1);
2686+
adjust_evm_priority_and_warn(&mut validity, Some(U256::from(10)), &signer);
2687+
2688+
let adjusted_priority = validity
2689+
.as_ref()
2690+
.and_then(|v| v.as_ref().ok())
2691+
.map(|v| v.priority);
2692+
2693+
assert_eq!(adjusted_priority, Some(EVM_TRANSACTION_BASE_PRIORITY));
2694+
}
2695+
2696+
#[test]
2697+
fn evm_priority_cannot_overtake_unstake() {
2698+
// Unstake is a normal-class extrinsic (priority = NORMAL_DISPATCH_BASE_PRIORITY).
2699+
let unstake_priority: TransactionPriority = NORMAL_DISPATCH_BASE_PRIORITY;
2700+
let evm_priority: TransactionPriority = EVM_TRANSACTION_BASE_PRIORITY;
2701+
2702+
// Clamp guarantees the EVM tx is never above the unstake priority.
2703+
assert!(evm_priority <= unstake_priority);
2704+
2705+
// If both arrive with equal priority, arrival order keeps unstake first.
2706+
let mut queue: Vec<(&str, TransactionPriority, usize)> = vec![
2707+
("unstake", unstake_priority, 0), // arrives first
2708+
("evm", evm_priority, 1), // arrives later
2709+
];
2710+
2711+
queue.sort_by(|a, b| {
2712+
b.1.cmp(&a.1) // higher priority first
2713+
.then_with(|| a.2.cmp(&b.2)) // earlier arrival first when equal
2714+
});
2715+
2716+
let first = queue.first().map(|entry| entry.0);
2717+
assert_eq!(first, Some("unstake"));
2718+
}
2719+
26252720
#[test]
26262721
fn test_into_evm_balance_valid() {
26272722
// Valid conversion from Substrate to EVM

runtime/src/transaction_payment_wrapper.rs

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::Weight;
1+
use crate::{NORMAL_DISPATCH_BASE_PRIORITY, OPERATIONAL_DISPATCH_PRIORITY, Weight};
22
use codec::{Decode, DecodeWithMemTracking, Encode};
33
use frame_election_provider_support::private::sp_arithmetic::traits::SaturatedConversion;
44
use frame_support::dispatch::{DispatchClass, DispatchInfo, PostDispatchInfo};
@@ -77,19 +77,12 @@ where
7777
match inner_validate {
7878
Ok((mut valid_transaction, val, origin)) => {
7979
let overridden_priority = {
80-
match info.class {
81-
DispatchClass::Normal => 1u64,
82-
DispatchClass::Mandatory => {
83-
// Mandatory extrinsics should be prohibited (e.g. by the [`CheckWeight`]
84-
// extensions), but just to be safe let's return the same priority as `Normal` here.
85-
1u64
86-
}
87-
DispatchClass::Operational => {
88-
// System calls
89-
10_000_000_000u64
90-
}
91-
}
92-
.saturated_into::<TransactionPriority>()
80+
let base: TransactionPriority = match info.class {
81+
DispatchClass::Normal => NORMAL_DISPATCH_BASE_PRIORITY,
82+
DispatchClass::Mandatory => NORMAL_DISPATCH_BASE_PRIORITY,
83+
DispatchClass::Operational => OPERATIONAL_DISPATCH_PRIORITY,
84+
};
85+
base.saturated_into::<TransactionPriority>()
9386
};
9487

9588
valid_transaction.priority = overridden_priority;

0 commit comments

Comments
 (0)