1use 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#[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_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 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 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 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 pub const fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
268 Some(&self.env.chain)
269 }
270}