reth_seismic_payload_builder/
builder.rs1use alloy_consensus::{Transaction, Typed2718};
4use alloy_primitives::U256;
5use reth_basic_payload_builder::{
6 is_better_payload, BuildArguments, BuildOutcome, MissingPayloadBehaviour, PayloadBuilder,
7 PayloadConfig,
8};
9use reth_chainspec::{ChainSpec, ChainSpecProvider, EthereumHardforks};
10use reth_errors::{BlockExecutionError, BlockValidationError};
11use reth_evm::{
12 execute::{BlockBuilder, BlockBuilderOutcome},
13 ConfigureEvm, Evm, NextBlockEnvAttributes,
14};
15use reth_payload_builder::{BlobSidecars, EthBuiltPayload, EthPayloadBuilderAttributes};
16use reth_payload_builder_primitives::PayloadBuilderError;
17use reth_payload_primitives::PayloadBuilderAttributes;
18use reth_primitives_traits::SignedTransaction;
19use reth_revm::{database::StateProviderDatabase, db::State};
20use reth_seismic_evm::SeismicEvmConfig;
21use reth_seismic_primitives::{SeismicPrimitives, SeismicTransactionSigned};
22use reth_storage_api::StateProviderFactory;
23use reth_transaction_pool::{
24 error::InvalidPoolTransactionError, BestTransactions, BestTransactionsAttributes,
25 PoolTransaction, TransactionPool, ValidPoolTransaction,
26};
27use revm::context_interface::Block as _;
28use seismic_enclave::EnclaveClientBuilder;
29use std::sync::Arc;
30use tracing::{debug, trace, warn};
31
32use reth_primitives_traits::transaction::error::InvalidTransactionError;
33
34type BestTransactionsIter<Pool> = Box<
35 dyn BestTransactions<Item = Arc<ValidPoolTransaction<<Pool as TransactionPool>::Transaction>>>,
36>;
37
38use super::SeismicBuilderConfig;
39
40#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct SeismicPayloadBuilder<Pool, Client, EvmConfig = SeismicEvmConfig<EnclaveClientBuilder>> {
43 client: Client,
45 pool: Pool,
47 evm_config: EvmConfig,
49 builder_config: SeismicBuilderConfig,
51}
52
53impl<Pool, Client, EvmConfig> SeismicPayloadBuilder<Pool, Client, EvmConfig> {
54 pub const fn new(
56 client: Client,
57 pool: Pool,
58 evm_config: EvmConfig,
59 builder_config: SeismicBuilderConfig,
60 ) -> Self {
61 Self { client, pool, evm_config, builder_config }
62 }
63}
64
65impl<Pool, Client, EvmConfig> PayloadBuilder for SeismicPayloadBuilder<Pool, Client, EvmConfig>
67where
68 EvmConfig:
69 ConfigureEvm<Primitives = SeismicPrimitives, NextBlockEnvCtx = NextBlockEnvAttributes>,
70 Client: StateProviderFactory + ChainSpecProvider<ChainSpec = ChainSpec> + Clone,
71 Pool: TransactionPool<Transaction: PoolTransaction<Consensus = SeismicTransactionSigned>>,
72{
73 type Attributes = EthPayloadBuilderAttributes;
74 type BuiltPayload = EthBuiltPayload<SeismicPrimitives>;
75
76 fn try_build(
77 &self,
78 args: BuildArguments<EthPayloadBuilderAttributes, Self::BuiltPayload>,
79 ) -> Result<BuildOutcome<EthBuiltPayload<SeismicPrimitives>>, PayloadBuilderError> {
80 default_seismic_payload(
81 self.evm_config.clone(),
82 self.client.clone(),
83 self.pool.clone(),
84 self.builder_config.clone(),
85 args,
86 |attributes| self.pool.best_transactions_with_attributes(attributes),
87 )
88 }
89
90 fn on_missing_payload(
91 &self,
92 _args: BuildArguments<Self::Attributes, Self::BuiltPayload>,
93 ) -> MissingPayloadBehaviour<Self::BuiltPayload> {
94 if self.builder_config.await_payload_on_missing {
95 MissingPayloadBehaviour::AwaitInProgress
96 } else {
97 MissingPayloadBehaviour::RaceEmptyPayload
98 }
99 }
100
101 fn build_empty_payload(
102 &self,
103 config: PayloadConfig<Self::Attributes>,
104 ) -> Result<Self::BuiltPayload, PayloadBuilderError> {
105 let args = BuildArguments::new(Default::default(), config, Default::default(), None);
106
107 default_seismic_payload(
108 self.evm_config.clone(),
109 self.client.clone(),
110 self.pool.clone(),
111 self.builder_config.clone(),
112 args,
113 |attributes| self.pool.best_transactions_with_attributes(attributes),
114 )?
115 .into_payload()
116 .ok_or_else(|| PayloadBuilderError::MissingPayload)
117 }
118}
119
120#[inline]
126pub fn default_seismic_payload<EvmConfig, Client, Pool, F>(
127 evm_config: EvmConfig,
128 client: Client,
129 pool: Pool,
130 builder_config: SeismicBuilderConfig,
131 args: BuildArguments<EthPayloadBuilderAttributes, EthBuiltPayload<SeismicPrimitives>>,
132 best_txs: F,
133) -> Result<BuildOutcome<EthBuiltPayload<SeismicPrimitives>>, PayloadBuilderError>
134where
135 EvmConfig:
136 ConfigureEvm<Primitives = SeismicPrimitives, NextBlockEnvCtx = NextBlockEnvAttributes>,
137 Client: StateProviderFactory + ChainSpecProvider<ChainSpec = ChainSpec>,
138 Pool: TransactionPool<Transaction: PoolTransaction<Consensus = SeismicTransactionSigned>>,
139 F: FnOnce(BestTransactionsAttributes) -> BestTransactionsIter<Pool>,
140{
141 let BuildArguments { mut cached_reads, config, cancel, best_payload } = args;
142 let PayloadConfig { parent_header, attributes } = config;
143
144 let state_provider = client.state_by_block_hash(parent_header.hash())?;
145 let state = StateProviderDatabase::new(&state_provider);
146 let mut db =
147 State::builder().with_database(cached_reads.as_db_mut(state)).with_bundle_update().build();
148
149 let mut builder = evm_config
150 .builder_for_next_block(
151 &mut db,
152 &parent_header,
153 NextBlockEnvAttributes {
154 timestamp: attributes.timestamp(),
155 suggested_fee_recipient: attributes.suggested_fee_recipient(),
156 prev_randao: attributes.prev_randao(),
157 gas_limit: builder_config.gas_limit(parent_header.gas_limit),
158 parent_beacon_block_root: attributes.parent_beacon_block_root(),
159 withdrawals: Some(attributes.withdrawals().clone()),
160 },
161 )
162 .map_err(PayloadBuilderError::other)?;
163
164 let chain_spec = client.chain_spec();
165
166 debug!(target: "payload_builder", id=%attributes.id, parent_header = ?parent_header.hash(), parent_number = parent_header.number, "building new payload");
167 let mut cumulative_gas_used = 0;
168 let block_gas_limit: u64 = builder.evm_mut().block().gas_limit;
169 let base_fee = builder.evm_mut().block().basefee;
170
171 let mut best_txs = best_txs(BestTransactionsAttributes::new(
172 base_fee,
173 builder.evm_mut().block().blob_gasprice().map(|gasprice| gasprice as u64),
174 ));
175 let mut total_fees = U256::ZERO;
176
177 builder.apply_pre_execution_changes().map_err(|err| {
178 warn!(target: "payload_builder", %err, "failed to apply pre-execution changes");
179 PayloadBuilderError::Internal(err.into())
180 })?;
181
182 while let Some(pool_tx) = best_txs.next() {
183 if cumulative_gas_used + pool_tx.gas_limit() > block_gas_limit {
185 best_txs.mark_invalid(
189 &pool_tx,
190 InvalidPoolTransactionError::ExceedsGasLimit(pool_tx.gas_limit(), block_gas_limit),
191 );
192 continue
193 }
194
195 if cancel.is_cancelled() {
197 return Ok(BuildOutcome::Cancelled)
198 }
199
200 let tx = pool_tx.to_consensus();
202 debug!("default_seismic_payload: tx: {:?}", tx);
203
204 let gas_used = match builder.execute_transaction(tx.clone()) {
205 Ok(gas_used) => gas_used,
206 Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx {
207 error, ..
208 })) => {
209 if error.is_nonce_too_low() {
210 trace!(target: "payload_builder", %error, ?tx, "skipping nonce too low transaction");
212 } else {
213 trace!(target: "payload_builder", %error, ?tx, "skipping invalid transaction and its descendants");
216 best_txs.mark_invalid(
217 &pool_tx,
218 InvalidPoolTransactionError::Consensus(
219 InvalidTransactionError::TxTypeNotSupported,
220 ),
221 );
222 }
223 continue
224 }
225 Err(err) => return Err(PayloadBuilderError::evm(err)),
227 };
228
229 let miner_fee =
231 tx.effective_tip_per_gas(base_fee).expect("fee is always valid; execution succeeded");
232 total_fees += U256::from(miner_fee) * U256::from(gas_used);
233 cumulative_gas_used += gas_used;
234 }
235
236 if !is_better_payload(best_payload.as_ref(), total_fees) {
238 drop(builder);
240 return Ok(BuildOutcome::Aborted { fees: total_fees, cached_reads })
242 }
243
244 let BlockBuilderOutcome { execution_result, block, .. } = builder.finish(&state_provider)?;
245
246 let requests = chain_spec
247 .is_prague_active_at_timestamp(attributes.timestamp)
248 .then_some(execution_result.requests);
249
250 let mut blob_sidecars = Vec::new();
252
253 if chain_spec.is_cancun_active_at_timestamp(attributes.timestamp) {
255 blob_sidecars = pool
257 .get_all_blobs_exact(
258 block
259 .body()
260 .transactions()
261 .filter(|tx| tx.is_eip4844())
262 .map(|tx| *tx.tx_hash())
263 .collect(),
264 )
265 .map_err(PayloadBuilderError::other)?;
266 }
267
268 let mut sidecars = BlobSidecars::Empty;
269 blob_sidecars
270 .into_iter()
271 .map(Arc::unwrap_or_clone)
272 .for_each(|s| sidecars.push_sidecar_variant(s));
273
274 let sealed_block = Arc::new(block.sealed_block().clone());
275 debug!(target: "payload_builder", id=%attributes.id, sealed_block_header = ?sealed_block.sealed_header(), "sealed built block");
276
277 let payload = EthBuiltPayload::<SeismicPrimitives>::new_seismic_payload(
278 attributes.id,
279 sealed_block,
280 total_fees,
281 sidecars,
282 requests,
283 );
284
285 Ok(BuildOutcome::Better { payload, cached_reads })
286}