reth_ethereum_cli/debug_cmd/
build_block.rs1use alloy_consensus::BlockHeader;
3use alloy_eips::{
4 eip2718::Encodable2718, eip4844::env_settings::EnvKzgSettings,
5 eip7594::BlobTransactionSidecarVariant,
6};
7use alloy_primitives::{Address, Bytes, B256};
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, TransactionSigned};
21use reth_evm::{execute::Executor, ConfigureEvm};
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};
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#[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 #[arg(long, value_delimiter = ',')]
67 transactions: Vec<String>,
68
69 #[arg(long)]
71 blobs_bundle_path: Option<PathBuf>,
72}
73
74impl<C: ChainSpecParser<ChainSpec = ChainSpec>> Command<C> {
75 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 const fn kzg_settings(&self) -> eyre::Result<EnvKzgSettings> {
100 Ok(EnvKzgSettings::Default)
101 }
102
103 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 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_into_recovered()
147 .map_err(|tx| eyre::eyre!("failed to recover tx: {}", tx.tx_hash()))?;
148
149 let encoded_length = match transaction.inner() {
150 TransactionSigned::Eip4844(tx) => {
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: BlobTransactionSidecarVariant =
156 BlobTransactionSidecarVariant::Eip4844(
157 blobs_bundle.pop_sidecar(tx.tx().blob_versioned_hashes.len()),
158 );
159
160 let pooled = transaction
161 .clone()
162 .into_inner()
163 .try_into_pooled_eip4844(sidecar.clone())
164 .expect("should not fail to convert blob tx if it is already eip4844");
165 let encoded_length = pooled.encode_2718_len();
166
167 blob_store.insert(*transaction.tx_hash(), sidecar)?;
169
170 encoded_length
171 }
172 _ => transaction.encode_2718_len(),
173 };
174
175 debug!(target: "reth::cli", ?transaction, "Adding transaction to the pool");
176 transaction_pool
177 .add_transaction(
178 TransactionOrigin::External,
179 EthPooledTransaction::new(transaction, encoded_length),
180 )
181 .await?;
182 }
183
184 let payload_attrs = PayloadAttributes {
185 parent_beacon_block_root: self.parent_beacon_block_root,
186 prev_randao: self.prev_randao,
187 timestamp: self.timestamp,
188 suggested_fee_recipient: self.suggested_fee_recipient,
189 withdrawals: provider_factory
191 .chain_spec()
192 .is_shanghai_active_at_timestamp(self.timestamp)
193 .then(Vec::new),
194 };
195 let payload_config = PayloadConfig::new(
196 Arc::new(SealedHeader::new(best_block.header().clone(), best_block.hash())),
197 reth_payload_builder::EthPayloadBuilderAttributes::try_new(
198 best_block.hash(),
199 payload_attrs,
200 EngineApiMessageVersion::default() as u8,
201 )?,
202 );
203
204 let args = BuildArguments::new(
205 CachedReads::default(),
206 payload_config,
207 CancelOnDrop::default(),
208 None,
209 );
210
211 let payload_builder = reth_ethereum_payload_builder::EthereumPayloadBuilder::new(
212 blockchain_db.clone(),
213 transaction_pool,
214 EthEvmConfig::new(provider_factory.chain_spec()),
215 EthereumBuilderConfig::new(),
216 );
217
218 match payload_builder.try_build(args)? {
219 BuildOutcome::Better { payload, .. } => {
220 let block = payload.block();
221 debug!(target: "reth::cli", ?block, "Built new payload");
222
223 consensus.validate_header(block.sealed_header())?;
224 consensus.validate_block_pre_execution(block)?;
225
226 let block_with_senders = block.clone().try_recover().unwrap();
227
228 let state_provider = blockchain_db.latest()?;
229 let db = StateProviderDatabase::new(&state_provider);
230 let evm_config = EthEvmConfig::ethereum(provider_factory.chain_spec());
231 let executor = evm_config.batch_executor(db);
232
233 let block_execution_output = executor.execute(&block_with_senders)?;
234 let execution_outcome =
235 ExecutionOutcome::from((block_execution_output, block.number));
236 debug!(target: "reth::cli", ?execution_outcome, "Executed block");
237
238 let hashed_post_state = state_provider.hashed_post_state(execution_outcome.state());
239 let (state_root, trie_updates) = StateRoot::overlay_root_with_updates(
240 provider_factory.provider()?.tx_ref(),
241 hashed_post_state.clone(),
242 )?;
243
244 if state_root != block_with_senders.state_root() {
245 eyre::bail!(
246 "state root mismatch. expected: {}. got: {}",
247 block_with_senders.state_root,
248 state_root
249 );
250 }
251
252 let provider_rw = provider_factory.provider_rw()?;
254 provider_rw.append_blocks_with_state(
255 Vec::from([block_with_senders]),
256 &execution_outcome,
257 hashed_post_state.into_sorted(),
258 trie_updates,
259 )?;
260 info!(target: "reth::cli", "Successfully appended built block");
261 }
262 _ => unreachable!("other outcomes are unreachable"),
263 };
264
265 Ok(())
266 }
267}
268
269impl<C: ChainSpecParser> Command<C> {
270 pub const fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
272 Some(&self.env.chain)
273 }
274}