1use alloy_consensus::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::{
13 BuildArguments, BuildOutcome, Cancelled, PayloadBuilder, PayloadConfig,
14};
15use reth_beacon_consensus::EthBeaconConsensus;
16use reth_blockchain_tree::{
17 BlockchainTree, BlockchainTreeConfig, ShareableBlockchainTree, TreeExternals,
18};
19use reth_chainspec::ChainSpec;
20use reth_cli::chainspec::ChainSpecParser;
21use reth_cli_commands::common::{AccessRights, CliNodeTypes, Environment, EnvironmentArgs};
22use reth_cli_runner::CliContext;
23use reth_consensus::{Consensus, FullConsensus};
24use reth_errors::RethResult;
25use reth_ethereum_payload_builder::EthereumBuilderConfig;
26use reth_evm::execute::{BlockExecutorProvider, Executor};
27use reth_execution_types::ExecutionOutcome;
28use reth_fs_util as fs;
29use reth_node_api::{BlockTy, EngineApiMessageVersion, PayloadBuilderAttributes};
30use reth_node_ethereum::{EthEvmConfig, EthExecutorProvider};
31use reth_primitives::{
32 BlobTransaction, BlockExt, PooledTransactionsElement, SealedBlockFor, SealedBlockWithSenders,
33 SealedHeader, Transaction, TransactionSigned,
34};
35use reth_provider::{
36 providers::{BlockchainProvider, ProviderNodeTypes},
37 BlockHashReader, BlockReader, BlockWriter, ChainSpecProvider, ProviderFactory,
38 StageCheckpointReader, StateProviderFactory,
39};
40use reth_revm::{cached::CachedReads, database::StateProviderDatabase, primitives::KzgSettings};
41use reth_stages::StageId;
42use reth_transaction_pool::{
43 blobstore::InMemoryBlobStore, BlobStore, EthPooledTransaction, PoolConfig, TransactionOrigin,
44 TransactionPool, TransactionValidationTaskExecutor,
45};
46use reth_trie::StateRoot;
47use reth_trie_db::DatabaseStateRoot;
48use std::{path::PathBuf, str::FromStr, sync::Arc};
49use tracing::*;
50
51#[derive(Debug, Parser)]
55pub struct Command<C: ChainSpecParser> {
56 #[command(flatten)]
57 env: EnvironmentArgs<C>,
58
59 #[arg(long, value_name = "PATH")]
61 trusted_setup_file: Option<PathBuf>,
62
63 #[arg(long)]
64 parent_beacon_block_root: Option<B256>,
65
66 #[arg(long)]
67 prev_randao: B256,
68
69 #[arg(long)]
70 timestamp: u64,
71
72 #[arg(long)]
73 suggested_fee_recipient: Address,
74
75 #[arg(long, value_delimiter = ',')]
79 transactions: Vec<String>,
80
81 #[arg(long)]
83 blobs_bundle_path: Option<PathBuf>,
84}
85
86impl<C: ChainSpecParser<ChainSpec = ChainSpec>> Command<C> {
87 fn lookup_best_block<N: ProviderNodeTypes<ChainSpec = C::ChainSpec>>(
91 &self,
92 factory: ProviderFactory<N>,
93 ) -> RethResult<Arc<SealedBlockFor<BlockTy<N>>>> {
94 let provider = factory.provider()?;
95
96 let best_number =
97 provider.get_stage_checkpoint(StageId::Finish)?.unwrap_or_default().block_number;
98 let best_hash = provider
99 .block_hash(best_number)?
100 .expect("the hash for the latest block is missing, database is corrupt");
101
102 Ok(Arc::new(
103 provider
104 .block(best_number.into())?
105 .expect("the header for the latest block is missing, database is corrupt")
106 .seal(best_hash),
107 ))
108 }
109
110 fn kzg_settings(&self) -> eyre::Result<EnvKzgSettings> {
113 if let Some(ref trusted_setup_file) = self.trusted_setup_file {
114 let trusted_setup = KzgSettings::load_trusted_setup_file(trusted_setup_file)
115 .wrap_err_with(|| {
116 format!("Failed to load trusted setup file: {:?}", trusted_setup_file)
117 })?;
118 Ok(EnvKzgSettings::Custom(Arc::new(trusted_setup)))
119 } else {
120 Ok(EnvKzgSettings::Default)
121 }
122 }
123
124 pub async fn execute<N: CliNodeTypes<ChainSpec = C::ChainSpec>>(
126 self,
127 ctx: CliContext,
128 ) -> eyre::Result<()> {
129 let Environment { provider_factory, .. } = self.env.init::<N>(AccessRights::RW)?;
130
131 let consensus: Arc<dyn FullConsensus> =
132 Arc::new(EthBeaconConsensus::new(provider_factory.chain_spec()));
133
134 let executor = EthExecutorProvider::ethereum(provider_factory.chain_spec());
135
136 let tree_externals =
138 TreeExternals::new(provider_factory.clone(), Arc::clone(&consensus), executor);
139 let tree = BlockchainTree::new(tree_externals, BlockchainTreeConfig::default())?;
140 let blockchain_tree = Arc::new(ShareableBlockchainTree::new(tree));
141
142 let best_block = self
144 .lookup_best_block(provider_factory.clone())
145 .wrap_err("the head block is missing")?;
146
147 let blockchain_db =
148 BlockchainProvider::new(provider_factory.clone(), blockchain_tree.clone())?;
149 let blob_store = InMemoryBlobStore::default();
150
151 let validator =
152 TransactionValidationTaskExecutor::eth_builder(provider_factory.chain_spec())
153 .with_head_timestamp(best_block.timestamp)
154 .kzg_settings(self.kzg_settings()?)
155 .with_additional_tasks(1)
156 .build_with_tasks(
157 blockchain_db.clone(),
158 ctx.task_executor.clone(),
159 blob_store.clone(),
160 );
161
162 let transaction_pool = reth_transaction_pool::Pool::eth_pool(
163 validator,
164 blob_store.clone(),
165 PoolConfig::default(),
166 );
167 info!(target: "reth::cli", "Transaction pool initialized");
168
169 let mut blobs_bundle = self
170 .blobs_bundle_path
171 .map(|path| -> eyre::Result<BlobsBundleV1> {
172 let contents = fs::read_to_string(&path)
173 .wrap_err(format!("could not read {}", path.display()))?;
174 serde_json::from_str(&contents).wrap_err("failed to deserialize blobs bundle")
175 })
176 .transpose()?;
177
178 for tx_bytes in &self.transactions {
179 debug!(target: "reth::cli", bytes = ?tx_bytes, "Decoding transaction");
180 let transaction = TransactionSigned::decode(&mut &Bytes::from_str(tx_bytes)?[..])?
181 .into_ecrecovered()
182 .ok_or_else(|| eyre::eyre!("failed to recover tx"))?;
183
184 let encoded_length = match &transaction.transaction {
185 Transaction::Eip4844(TxEip4844 { blob_versioned_hashes, .. }) => {
186 let blobs_bundle = blobs_bundle.as_mut().ok_or_else(|| {
187 eyre::eyre!("encountered a blob tx. `--blobs-bundle-path` must be provided")
188 })?;
189
190 let sidecar: BlobTransactionSidecar =
191 blobs_bundle.pop_sidecar(blob_versioned_hashes.len());
192
193 let tx = BlobTransaction::try_from_signed(
196 transaction.as_ref().clone(),
197 sidecar.clone(),
198 )
199 .expect("should not fail to convert blob tx if it is already eip4844");
200 let pooled = PooledTransactionsElement::BlobTransaction(tx);
201 let encoded_length = pooled.encode_2718_len();
202
203 blob_store.insert(transaction.hash(), sidecar)?;
205
206 encoded_length
207 }
208 _ => transaction.encode_2718_len(),
209 };
210
211 debug!(target: "reth::cli", ?transaction, "Adding transaction to the pool");
212 transaction_pool
213 .add_transaction(
214 TransactionOrigin::External,
215 EthPooledTransaction::new(transaction, encoded_length),
216 )
217 .await?;
218 }
219
220 let payload_attrs = PayloadAttributes {
221 parent_beacon_block_root: self.parent_beacon_block_root,
222 prev_randao: self.prev_randao,
223 timestamp: self.timestamp,
224 suggested_fee_recipient: self.suggested_fee_recipient,
225 withdrawals: None,
227 target_blobs_per_block: None,
228 max_blobs_per_block: None,
229 };
230 let payload_config = PayloadConfig::new(
231 Arc::new(SealedHeader::new(best_block.header().clone(), best_block.hash())),
232 reth_payload_builder::EthPayloadBuilderAttributes::try_new(
233 best_block.hash(),
234 payload_attrs,
235 EngineApiMessageVersion::default() as u8,
236 )?,
237 );
238
239 let args = BuildArguments::new(
240 blockchain_db.clone(),
241 transaction_pool,
242 CachedReads::default(),
243 payload_config,
244 Cancelled::default(),
245 None,
246 );
247
248 let payload_builder = reth_ethereum_payload_builder::EthereumPayloadBuilder::new(
249 EthEvmConfig::new(provider_factory.chain_spec()),
250 EthereumBuilderConfig::new(Default::default()),
251 );
252
253 match payload_builder.try_build(args)? {
254 BuildOutcome::Better { payload, .. } => {
255 let block = payload.block();
256 debug!(target: "reth::cli", ?block, "Built new payload");
257
258 consensus.validate_header_with_total_difficulty(block, U256::MAX)?;
259 consensus.validate_header(block)?;
260 consensus.validate_block_pre_execution(block)?;
261
262 let senders = block.senders().expect("sender recovery failed");
263 let block_with_senders =
264 SealedBlockWithSenders::<BlockTy<N>>::new(block.clone(), senders).unwrap();
265
266 let state_provider = blockchain_db.latest()?;
267 let db = StateProviderDatabase::new(&state_provider);
268 let executor =
269 EthExecutorProvider::ethereum(provider_factory.chain_spec()).executor(db);
270
271 let block_execution_output =
272 executor.execute((&block_with_senders.clone().unseal(), U256::MAX).into())?;
273 let execution_outcome =
274 ExecutionOutcome::from((block_execution_output, block.number));
275 debug!(target: "reth::cli", ?execution_outcome, "Executed block");
276
277 let hashed_post_state = state_provider.hashed_post_state(execution_outcome.state());
278 let (state_root, trie_updates) = StateRoot::overlay_root_with_updates(
279 provider_factory.provider()?.tx_ref(),
280 hashed_post_state.clone(),
281 )?;
282
283 if state_root != block_with_senders.state_root {
284 eyre::bail!(
285 "state root mismatch. expected: {}. got: {}",
286 block_with_senders.state_root,
287 state_root
288 );
289 }
290
291 let provider_rw = provider_factory.provider_rw()?;
293 provider_rw.append_blocks_with_state(
294 Vec::from([block_with_senders]),
295 execution_outcome,
296 hashed_post_state.into_sorted(),
297 trie_updates,
298 )?;
299 info!(target: "reth::cli", "Successfully appended built block");
300 }
301 _ => unreachable!("other outcomes are unreachable"),
302 };
303
304 Ok(())
305 }
306}