mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat: validate withdrawal when validating block. (#1521)
Signed-off-by: grapebaba <281165273@qq.com>
This commit is contained in:
97
Cargo.lock
generated
97
Cargo.lock
generated
@ -1369,6 +1369,12 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "difflib"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.8.1"
|
||||
@ -1492,6 +1498,12 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "downcast"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1"
|
||||
|
||||
[[package]]
|
||||
name = "dunce"
|
||||
version = "1.0.3"
|
||||
@ -2032,6 +2044,15 @@ dependencies = [
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "float-cmp"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
@ -2047,6 +2068,12 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fragile"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa"
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "2.0.0"
|
||||
@ -3359,6 +3386,33 @@ dependencies = [
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mockall"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50e4a1c770583dac7ab5e2f6c139153b783a53a1bbee9729613f193e59828326"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"downcast",
|
||||
"fragile",
|
||||
"lazy_static",
|
||||
"mockall_derive",
|
||||
"predicates",
|
||||
"predicates-tree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mockall_derive"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "832663583d5fa284ca8810bf7015e46c9fff9622d3cf34bd1eea5003fec06dd0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"proc-macro2 1.0.51",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "modular-bitfield"
|
||||
version = "0.11.2"
|
||||
@ -3417,6 +3471,12 @@ dependencies = [
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "normalize-line-endings"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
@ -3935,6 +3995,36 @@ version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "predicates"
|
||||
version = "2.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd"
|
||||
dependencies = [
|
||||
"difflib",
|
||||
"float-cmp",
|
||||
"itertools 0.10.5",
|
||||
"normalize-line-endings",
|
||||
"predicates-core",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "predicates-core"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72f883590242d3c6fc5bf50299011695fa6590c2c70eac95ee1bdb9a733ad1a2"
|
||||
|
||||
[[package]]
|
||||
name = "predicates-tree"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54ff541861505aabf6ea722d2131ee980b8276e10a1297b94e896dd8b621850d"
|
||||
dependencies = [
|
||||
"predicates-core",
|
||||
"termtree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.1.23"
|
||||
@ -4414,6 +4504,7 @@ name = "reth-consensus"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"mockall",
|
||||
"reth-interfaces",
|
||||
"reth-primitives",
|
||||
"reth-provider",
|
||||
@ -6145,6 +6236,12 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termtree"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95059e91184749cb66be6dc994f67f182b6d897cb3df74a5bf66b5e709295fd8"
|
||||
|
||||
[[package]]
|
||||
name = "test-fuzz"
|
||||
version = "3.0.5"
|
||||
|
||||
@ -19,3 +19,4 @@ tokio = { version = "1", features = ["sync"] }
|
||||
reth-interfaces = { path = "../interfaces", features = ["test-utils"] }
|
||||
reth-provider = { path = "../storage/provider", features = ["test-utils"] }
|
||||
assert_matches = "1.5.0"
|
||||
mockall = "0.11.3"
|
||||
|
||||
@ -4,7 +4,7 @@ use reth_primitives::{
|
||||
BlockNumber, ChainSpec, Hardfork, Header, InvalidTransactionError, SealedBlock, SealedHeader,
|
||||
Transaction, TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxLegacy,
|
||||
};
|
||||
use reth_provider::{AccountProvider, HeaderProvider};
|
||||
use reth_provider::{AccountProvider, HeaderProvider, WithdrawalsProvider};
|
||||
use std::{
|
||||
collections::{hash_map::Entry, HashMap},
|
||||
time::SystemTime,
|
||||
@ -348,9 +348,10 @@ pub fn validate_header_regarding_parent(
|
||||
/// Checks:
|
||||
/// If we already know the block.
|
||||
/// If parent is known
|
||||
/// If withdarwals are valid
|
||||
///
|
||||
/// Returns parent block header
|
||||
pub fn validate_block_regarding_chain<PROV: HeaderProvider>(
|
||||
pub fn validate_block_regarding_chain<PROV: HeaderProvider + WithdrawalsProvider>(
|
||||
block: &SealedBlock,
|
||||
provider: &PROV,
|
||||
) -> RethResult<SealedHeader> {
|
||||
@ -366,12 +367,39 @@ pub fn validate_block_regarding_chain<PROV: HeaderProvider>(
|
||||
.header(&block.parent_hash)?
|
||||
.ok_or(ConsensusError::ParentUnknown { hash: block.parent_hash })?;
|
||||
|
||||
// Check if withdrawals are valid.
|
||||
if let Some(withdrawals) = &block.withdrawals {
|
||||
if !withdrawals.is_empty() {
|
||||
let latest_withdrawal = provider.latest_withdrawal()?;
|
||||
match latest_withdrawal {
|
||||
Some(withdrawal) => {
|
||||
if withdrawal.index + 1 != withdrawals.first().unwrap().index {
|
||||
return Err(ConsensusError::WithdrawalIndexInvalid {
|
||||
got: withdrawals.first().unwrap().index,
|
||||
expected: withdrawal.index + 1,
|
||||
}
|
||||
.into())
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if withdrawals.first().unwrap().index != 0 {
|
||||
return Err(ConsensusError::WithdrawalIndexInvalid {
|
||||
got: withdrawals.first().unwrap().index,
|
||||
expected: 0,
|
||||
}
|
||||
.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return parent header.
|
||||
Ok(parent.seal(block.parent_hash))
|
||||
}
|
||||
|
||||
/// Full validation of block before execution.
|
||||
pub fn full_validation<Provider: HeaderProvider + AccountProvider>(
|
||||
pub fn full_validation<Provider: HeaderProvider + AccountProvider + WithdrawalsProvider>(
|
||||
block: &SealedBlock,
|
||||
provider: Provider,
|
||||
chain_spec: &ChainSpec,
|
||||
@ -401,10 +429,11 @@ pub fn full_validation<Provider: HeaderProvider + AccountProvider>(
|
||||
mod tests {
|
||||
use super::*;
|
||||
use assert_matches::assert_matches;
|
||||
use reth_interfaces::Result;
|
||||
use mockall::mock;
|
||||
use reth_interfaces::{Error::Consensus, Result};
|
||||
use reth_primitives::{
|
||||
hex_literal::hex, proofs, Account, Address, BlockHash, Bytes, ChainSpecBuilder, Header,
|
||||
Signature, TransactionKind, TransactionSigned, Withdrawal, MAINNET, U256,
|
||||
hex_literal::hex, proofs, Account, Address, BlockHash, BlockId, Bytes, ChainSpecBuilder,
|
||||
Header, Signature, TransactionKind, TransactionSigned, Withdrawal, MAINNET, U256,
|
||||
};
|
||||
use std::ops::RangeBounds;
|
||||
|
||||
@ -435,20 +464,45 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
mock! {
|
||||
WithdrawalsProvider {}
|
||||
|
||||
impl WithdrawalsProvider for WithdrawalsProvider {
|
||||
fn latest_withdrawal(&self) -> Result<Option<Withdrawal>> ;
|
||||
|
||||
fn withdrawals_by_block(
|
||||
&self,
|
||||
_id: BlockId,
|
||||
_timestamp: u64,
|
||||
) -> RethResult<Option<Vec<Withdrawal>>> ;
|
||||
}
|
||||
}
|
||||
|
||||
struct Provider {
|
||||
is_known: bool,
|
||||
parent: Option<Header>,
|
||||
account: Option<Account>,
|
||||
withdrawals_provider: MockWithdrawalsProvider,
|
||||
}
|
||||
|
||||
impl Provider {
|
||||
/// New provider with parent
|
||||
fn new(parent: Option<Header>) -> Self {
|
||||
Self { is_known: false, parent, account: None }
|
||||
Self {
|
||||
is_known: false,
|
||||
parent,
|
||||
account: None,
|
||||
withdrawals_provider: MockWithdrawalsProvider::new(),
|
||||
}
|
||||
}
|
||||
/// New provider where is_known is always true
|
||||
fn new_known() -> Self {
|
||||
Self { is_known: true, parent: None, account: None }
|
||||
Self {
|
||||
is_known: true,
|
||||
parent: None,
|
||||
account: None,
|
||||
withdrawals_provider: MockWithdrawalsProvider::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -484,6 +538,20 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
impl WithdrawalsProvider for Provider {
|
||||
fn latest_withdrawal(&self) -> Result<Option<Withdrawal>> {
|
||||
self.withdrawals_provider.latest_withdrawal()
|
||||
}
|
||||
|
||||
fn withdrawals_by_block(
|
||||
&self,
|
||||
_id: BlockId,
|
||||
_timestamp: u64,
|
||||
) -> RethResult<Option<Vec<Withdrawal>>> {
|
||||
self.withdrawals_provider.withdrawals_by_block(_id, _timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
fn mock_tx(nonce: u64) -> TransactionSignedEcRecovered {
|
||||
let request = Transaction::Eip2930(TxEip2930 {
|
||||
chain_id: 1u64,
|
||||
@ -525,7 +593,7 @@ mod tests {
|
||||
mix_hash: hex!("0000000000000000000000000000000000000000000000000000000000000000").into(),
|
||||
nonce: 0x0000000000000000,
|
||||
base_fee_per_gas: 0x28f0001df.into(),
|
||||
withdrawals_root: None
|
||||
withdrawals_root: None,
|
||||
};
|
||||
// size: 0x9b5
|
||||
|
||||
@ -656,6 +724,39 @@ mod tests {
|
||||
validate_block_standalone(&block, &chain_spec),
|
||||
Err(ConsensusError::WithdrawalIndexInvalid { .. })
|
||||
);
|
||||
|
||||
let (_, parent) = mock_block();
|
||||
let mut provider = Provider::new(Some(parent.clone()));
|
||||
// Withdrawal index should be 0 if there are no withdrawals in the chain
|
||||
let block = create_block_with_withdrawals(&[1, 2, 3]);
|
||||
provider.withdrawals_provider.expect_latest_withdrawal().return_const(Ok(None));
|
||||
assert_matches!(
|
||||
validate_block_regarding_chain(&block, &provider),
|
||||
Err(Consensus(ConsensusError::WithdrawalIndexInvalid { got: 1, expected: 0 }))
|
||||
);
|
||||
let block = create_block_with_withdrawals(&[0, 1, 2]);
|
||||
let res = validate_block_regarding_chain(&block, &provider);
|
||||
assert!(res.is_ok());
|
||||
|
||||
// Withdrawal index should be the last withdrawal index + 1
|
||||
let mut provider = Provider::new(Some(parent.clone()));
|
||||
let block = create_block_with_withdrawals(&[4, 5, 6]);
|
||||
provider
|
||||
.withdrawals_provider
|
||||
.expect_latest_withdrawal()
|
||||
.return_const(Ok(Some(Withdrawal { index: 2, ..Default::default() })));
|
||||
assert_matches!(
|
||||
validate_block_regarding_chain(&block, &provider),
|
||||
Err(Consensus(ConsensusError::WithdrawalIndexInvalid { got: 4, expected: 3 }))
|
||||
);
|
||||
|
||||
let block = create_block_with_withdrawals(&[3, 4, 5]);
|
||||
provider
|
||||
.withdrawals_provider
|
||||
.expect_latest_withdrawal()
|
||||
.return_const(Ok(Some(Withdrawal { index: 2, ..Default::default() })));
|
||||
let res = validate_block_regarding_chain(&block, &provider);
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@ -267,6 +267,17 @@ impl<DB: Database> WithdrawalsProvider for ShareableDatabase<DB> {
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn latest_withdrawal(&self) -> Result<Option<Withdrawal>> {
|
||||
let latest_block_withdrawal =
|
||||
self.db.view(|tx| tx.cursor_read::<tables::BlockWithdrawals>()?.last())?;
|
||||
latest_block_withdrawal
|
||||
.map(|block_withdrawal_pair| {
|
||||
block_withdrawal_pair
|
||||
.and_then(|(_, block_withdrawal)| block_withdrawal.withdrawals.last().cloned())
|
||||
})
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB: Database> EvmEnvProvider for ShareableDatabase<DB> {
|
||||
|
||||
@ -5,4 +5,7 @@ use reth_primitives::{BlockId, Withdrawal};
|
||||
pub trait WithdrawalsProvider: Send + Sync {
|
||||
/// Get withdrawals by block id.
|
||||
fn withdrawals_by_block(&self, id: BlockId, timestamp: u64) -> Result<Option<Vec<Withdrawal>>>;
|
||||
|
||||
/// Get latest withdrawal from this block or earlier .
|
||||
fn latest_withdrawal(&self) -> Result<Option<Withdrawal>>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user