reth/commands/debug_cmd/
build_block.rs

1//! Command for debugging block building.
2use alloy_consensus::{BlockHeader, TxEip4844};
3use alloy_eips::{
4    eip2718::Encodable2718,
5    eip4844::{env_settings::EnvKzgSettings, BlobTransactionSidecar},
6};
7use alloy_primitives::{Address, Bytes, B256, U256};
8use alloy_rlp::Decodable;
9use alloy_rpc_types::engine::{BlobsBundleV1, PayloadAttributes};
10use clap::Parser;
11use eyre::Context;
12use reth_basic_payload_builder::{BuildArguments, BuildOutcome, PayloadBuilder, PayloadConfig};
13use reth_chainspec::{ChainSpec, EthereumHardforks};
14use reth_cli::chainspec::ChainSpecParser;
15use reth_cli_commands::common::{AccessRights, CliNodeTypes, Environment, EnvironmentArgs};
16use reth_cli_runner::CliContext;
17use reth_consensus::{Consensus, FullConsensus};
18use reth_errors::{ConsensusError, RethResult};
19use reth_ethereum_payload_builder::EthereumBuilderConfig;
20use reth_ethereum_primitives::{EthPrimitives, Transaction, TransactionSigned};
21use reth_evm::execute::{BlockExecutorProvider, Executor};
22use reth_execution_types::ExecutionOutcome;
23use reth_fs_util as fs;
24use reth_node_api::{BlockTy, EngineApiMessageVersion, PayloadBuilderAttributes};
25use reth_node_ethereum::{consensus::EthBeaconConsensus, EthEvmConfig, EthExecutorProvider};
26use reth_primitives_traits::{Block as _, SealedBlock, SealedHeader, SignedTransaction};
27use reth_provider::{
28    providers::{BlockchainProvider, ProviderNodeTypes},
29    BlockHashReader, BlockReader, BlockWriter, ChainSpecProvider, ProviderFactory,
30    StageCheckpointReader, StateProviderFactory,
31};
32use reth_revm::{cached::CachedReads, cancelled::CancelOnDrop, database::StateProviderDatabase};
33use reth_stages::StageId;
34use reth_transaction_pool::{
35    blobstore::InMemoryBlobStore, BlobStore, EthPooledTransaction, PoolConfig, TransactionOrigin,
36    TransactionPool, TransactionValidationTaskExecutor,
37};
38use reth_trie::StateRoot;
39use reth_trie_db::DatabaseStateRoot;
40use std::{path::PathBuf, str::FromStr, sync::Arc};
41use tracing::*;
42
43/// `reth debug build-block` command
44/// This debug routine requires that the node is positioned at the block before the target.
45/// The script will then parse the block and attempt to build a similar one.
46#[derive(Debug, Parser)]
47pub struct Command<C: ChainSpecParser> {
48    #[command(flatten)]
49    env: EnvironmentArgs<C>,
50
51    #[arg(long)]
52    parent_beacon_block_root: Option<B256>,
53
54    #[arg(long)]
55    prev_randao: B256,
56
57    #[arg(long)]
58    timestamp: u64,
59
60    #[arg(long)]
61    suggested_fee_recipient: Address,
62
63    /// Array of transactions.
64    /// NOTE: 4844 transactions must be provided in the same order as they appear in the blobs
65    /// bundle.
66    #[arg(long, value_delimiter = ',')]
67    transactions: Vec<String>,
68
69    /// Path to the file that contains a corresponding blobs bundle.
70    #[arg(long)]
71    blobs_bundle_path: Option<PathBuf>,
72}
73
74impl<C: ChainSpecParser<ChainSpec = ChainSpec>> Command<C> {
75    /// Fetches the best block from the database.
76    ///
77    /// If the database is empty, returns the genesis block.
78    fn lookup_best_block<N: ProviderNodeTypes<ChainSpec = C::ChainSpec>>(
79        &self,
80        factory: ProviderFactory<N>,
81    ) -> RethResult<Arc<SealedBlock<BlockTy<N>>>> {
82        let provider = factory.provider()?;
83
84        let best_number =
85            provider.get_stage_checkpoint(StageId::Finish)?.unwrap_or_default().block_number;
86        let best_hash = provider
87            .block_hash(best_number)?
88            .expect("the hash for the latest block is missing, database is corrupt");
89
90        Ok(Arc::new(
91            provider
92                .block(best_number.into())?
93                .expect("the header for the latest block is missing, database is corrupt")
94                .seal_unchecked(best_hash),
95        ))
96    }
97
98    /// Returns the default KZG settings
99    const fn kzg_settings(&self) -> eyre::Result<EnvKzgSettings> {
100        Ok(EnvKzgSettings::Default)
101    }
102
103    /// Execute `debug in-memory-merkle` command
104    pub async fn execute<N: CliNodeTypes<ChainSpec = C::ChainSpec, Primitives = EthPrimitives>>(
105        self,
106        ctx: CliContext,
107    ) -> eyre::Result<()> {
108        let Environment { provider_factory, .. } = self.env.init::<N>(AccessRights::RW)?;
109
110        let consensus: Arc<dyn FullConsensus<EthPrimitives, Error = ConsensusError>> =
111            Arc::new(EthBeaconConsensus::new(provider_factory.chain_spec()));
112
113        // fetch the best block from the database
114        let best_block = self
115            .lookup_best_block(provider_factory.clone())
116            .wrap_err("the head block is missing")?;
117
118        let blockchain_db = BlockchainProvider::new(provider_factory.clone())?;
119        let blob_store = InMemoryBlobStore::default();
120
121        let validator = TransactionValidationTaskExecutor::eth_builder(blockchain_db.clone())
122            .with_head_timestamp(best_block.timestamp)
123            .kzg_settings(self.kzg_settings()?)
124            .with_additional_tasks(1)
125            .build_with_tasks(ctx.task_executor.clone(), blob_store.clone());
126
127        let transaction_pool = reth_transaction_pool::Pool::eth_pool(
128            validator,
129            blob_store.clone(),
130            PoolConfig::default(),
131        );
132        info!(target: "reth::cli", "Transaction pool initialized");
133
134        let mut blobs_bundle = self
135            .blobs_bundle_path
136            .map(|path| -> eyre::Result<BlobsBundleV1> {
137                let contents = fs::read_to_string(&path)
138                    .wrap_err(format!("could not read {}", path.display()))?;
139                serde_json::from_str(&contents).wrap_err("failed to deserialize blobs bundle")
140            })
141            .transpose()?;
142
143        for tx_bytes in &self.transactions {
144            debug!(target: "reth::cli", bytes = ?tx_bytes, "Decoding transaction");
145            let transaction = TransactionSigned::decode(&mut &Bytes::from_str(tx_bytes)?[..])?
146                .try_clone_into_recovered()
147                .map_err(|e| eyre::eyre!("failed to recover tx: {e}"))?;
148
149            let encoded_length = match transaction.transaction() {
150                Transaction::Eip4844(TxEip4844 { blob_versioned_hashes, .. }) => {
151                    let blobs_bundle = blobs_bundle.as_mut().ok_or_else(|| {
152                        eyre::eyre!("encountered a blob tx. `--blobs-bundle-path` must be provided")
153                    })?;
154
155                    let sidecar: BlobTransactionSidecar =
156                        blobs_bundle.pop_sidecar(blob_versioned_hashes.len());
157
158                    let pooled = transaction
159                        .clone()
160                        .into_inner()
161                        .try_into_pooled_eip4844(sidecar.clone())
162                        .expect("should not fail to convert blob tx if it is already eip4844");
163                    let encoded_length = pooled.encode_2718_len();
164
165                    // insert the blob into the store
166                    blob_store.insert(*transaction.tx_hash(), sidecar)?;
167
168                    encoded_length
169                }
170                _ => transaction.encode_2718_len(),
171            };
172
173            debug!(target: "reth::cli", ?transaction, "Adding transaction to the pool");
174            transaction_pool
175                .add_transaction(
176                    TransactionOrigin::External,
177                    EthPooledTransaction::new(transaction, encoded_length),
178                )
179                .await?;
180        }
181
182        let payload_attrs = PayloadAttributes {
183            parent_beacon_block_root: self.parent_beacon_block_root,
184            prev_randao: self.prev_randao,
185            timestamp: self.timestamp,
186            suggested_fee_recipient: self.suggested_fee_recipient,
187            // Set empty withdrawals vector if Shanghai is active, None otherwise
188            withdrawals: provider_factory
189                .chain_spec()
190                .is_shanghai_active_at_timestamp(self.timestamp)
191                .then(Vec::new),
192        };
193        let payload_config = PayloadConfig::new(
194            Arc::new(SealedHeader::new(best_block.header().clone(), best_block.hash())),
195            reth_payload_builder::EthPayloadBuilderAttributes::try_new(
196                best_block.hash(),
197                payload_attrs,
198                EngineApiMessageVersion::default() as u8,
199            )?,
200        );
201
202        let args = BuildArguments::new(
203            CachedReads::default(),
204            payload_config,
205            CancelOnDrop::default(),
206            None,
207        );
208
209        let payload_builder = reth_ethereum_payload_builder::EthereumPayloadBuilder::new(
210            blockchain_db.clone(),
211            transaction_pool,
212            EthEvmConfig::new(provider_factory.chain_spec()),
213            EthereumBuilderConfig::new(),
214        );
215
216        match payload_builder.try_build(args)? {
217            BuildOutcome::Better { payload, .. } => {
218                let block = payload.block();
219                debug!(target: "reth::cli", ?block, "Built new payload");
220
221                consensus.validate_header_with_total_difficulty(block, U256::MAX)?;
222                consensus.validate_header(block.sealed_header())?;
223                consensus.validate_block_pre_execution(block)?;
224
225                let block_with_senders = block.clone().try_recover().unwrap();
226
227                let state_provider = blockchain_db.latest()?;
228                let db = StateProviderDatabase::new(&state_provider);
229                let executor =
230                    EthExecutorProvider::ethereum(provider_factory.chain_spec()).executor(db);
231
232                let block_execution_output = executor.execute(&block_with_senders)?;
233                let execution_outcome =
234                    ExecutionOutcome::from((block_execution_output, block.number));
235                debug!(target: "reth::cli", ?execution_outcome, "Executed block");
236
237                let hashed_post_state = state_provider.hashed_post_state(execution_outcome.state());
238                let (state_root, trie_updates) = StateRoot::overlay_root_with_updates(
239                    provider_factory.provider()?.tx_ref(),
240                    hashed_post_state.clone(),
241                )?;
242
243                if state_root != block_with_senders.state_root() {
244                    eyre::bail!(
245                        "state root mismatch. expected: {}. got: {}",
246                        block_with_senders.state_root,
247                        state_root
248                    );
249                }
250
251                // Attempt to insert new block without committing
252                let provider_rw = provider_factory.provider_rw()?;
253                provider_rw.append_blocks_with_state(
254                    Vec::from([block_with_senders]),
255                    &execution_outcome,
256                    hashed_post_state.into_sorted(),
257                    trie_updates,
258                )?;
259                info!(target: "reth::cli", "Successfully appended built block");
260            }
261            _ => unreachable!("other outcomes are unreachable"),
262        };
263
264        Ok(())
265    }
266    /// Returns the underlying chain being used to run this command
267    pub const fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
268        Some(&self.env.chain)
269    }
270}