feat: validate withdrawal when validating block. (#1521)

Signed-off-by: grapebaba <281165273@qq.com>
This commit is contained in:
Chen Kai
2023-03-15 09:34:11 +08:00
committed by GitHub
parent 880759ac57
commit 91f26f455f
5 changed files with 222 additions and 9 deletions

97
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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]

View File

@ -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> {

View File

@ -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>>;
}