1use alloy_consensus::{
2 BlobTransactionValidationError, BlockHeader, EnvKzgSettings, Transaction, TxReceipt,
3};
4use alloy_eip7928::{bal::DecodedBal, compute_block_access_list_hash};
5use alloy_eips::eip7685::RequestsOrHash;
6use alloy_primitives::map::AddressSet;
7use alloy_rpc_types_beacon::relay::{
8 BidTrace, BuilderBlockValidationRequest, BuilderBlockValidationRequestV2,
9 BuilderBlockValidationRequestV3, BuilderBlockValidationRequestV4,
10 BuilderBlockValidationRequestV5, BuilderBlockValidationRequestV6,
11};
12use alloy_rpc_types_engine::{
13 BlobsBundleV1, BlobsBundleV2, CancunPayloadFields, ExecutionData, ExecutionPayload,
14 ExecutionPayloadSidecar, PraguePayloadFields,
15};
16use async_trait::async_trait;
17use core::fmt;
18use jsonrpsee::core::RpcResult;
19use jsonrpsee_types::error::ErrorObject;
20use reth_chainspec::{ChainSpecProvider, EthereumHardforks};
21use reth_consensus::{Consensus, FullConsensus};
22use reth_consensus_common::validation::MAX_RLP_BLOCK_SIZE;
23use reth_engine_primitives::PayloadValidator;
24use reth_errors::{BlockExecutionError, ConsensusError, ProviderError};
25use reth_evm::{execute::Executor, ConfigureEvm};
26use reth_execution_types::BlockExecutionOutput;
27use reth_metrics::{
28 metrics,
29 metrics::{gauge, Gauge},
30 Metrics,
31};
32use reth_node_api::{NewPayloadError, PayloadTypes};
33use reth_primitives_traits::{
34 constants::GAS_LIMIT_BOUND_DIVISOR, BlockBody, GotExpected, NodePrimitives, RecoveredBlock,
35 SealedBlock, SealedHeaderFor,
36};
37use reth_revm::{cached::CachedReads, database::StateProviderDatabase};
38use reth_rpc_api::BlockSubmissionValidationApiServer;
39use reth_rpc_server_types::result::{internal_rpc_err, invalid_params_rpc_err};
40use reth_storage_api::{BlockReaderIdExt, StateProviderFactory};
41use reth_tasks::Runtime;
42use revm_primitives::{Address, B256, U256};
43use serde::{Deserialize, Serialize};
44use sha2::{Digest, Sha256};
45use std::sync::Arc;
46use tokio::sync::{oneshot, RwLock};
47use tracing::warn;
48
49#[derive(Clone, Debug, derive_more::Deref)]
51pub struct ValidationApi<Provider, E: ConfigureEvm, T: PayloadTypes> {
52 #[deref]
53 inner: Arc<ValidationApiInner<Provider, E, T>>,
54}
55
56impl<Provider, E, T> ValidationApi<Provider, E, T>
57where
58 E: ConfigureEvm,
59 T: PayloadTypes,
60{
61 pub fn new(
63 provider: Provider,
64 consensus: Arc<dyn FullConsensus<E::Primitives>>,
65 evm_config: E,
66 config: ValidationApiConfig,
67 task_spawner: Runtime,
68 payload_validator: Arc<
69 dyn PayloadValidator<T, Block = <E::Primitives as NodePrimitives>::Block>,
70 >,
71 ) -> Self {
72 let ValidationApiConfig { disallow, validation_window } = config;
73
74 let inner = Arc::new(ValidationApiInner {
75 provider,
76 consensus,
77 payload_validator,
78 evm_config,
79 disallow,
80 validation_window,
81 cached_state: Default::default(),
82 task_spawner,
83 metrics: Default::default(),
84 });
85
86 inner.metrics.disallow_size.set(inner.disallow.len() as f64);
87
88 let disallow_hash = hash_disallow_list(&inner.disallow);
89 let hash_gauge = gauge!("builder_validation_disallow_hash", "hash" => disallow_hash);
90 hash_gauge.set(1.0);
91
92 Self { inner }
93 }
94
95 async fn cached_reads(&self, head: B256) -> CachedReads {
97 let cache = self.inner.cached_state.read().await;
98 if cache.0 == head {
99 cache.1.clone()
100 } else {
101 Default::default()
102 }
103 }
104
105 async fn update_cached_reads(&self, head: B256, cached_state: CachedReads) {
107 let mut cache = self.inner.cached_state.write().await;
108 if cache.0 == head {
109 cache.1.extend(cached_state);
110 } else {
111 *cache = (head, cached_state)
112 }
113 }
114}
115
116impl<Provider, E, T> ValidationApi<Provider, E, T>
117where
118 Provider: BlockReaderIdExt<Header = <E::Primitives as NodePrimitives>::BlockHeader>
119 + ChainSpecProvider<ChainSpec: EthereumHardforks>
120 + StateProviderFactory
121 + 'static,
122 E: ConfigureEvm + 'static,
123 T: PayloadTypes<ExecutionData = ExecutionData>,
124{
125 pub async fn validate_message_against_block(
127 &self,
128 block: RecoveredBlock<<E::Primitives as NodePrimitives>::Block>,
129 message: BidTrace,
130 registered_gas_limit: u64,
131 decoded_bal: Option<DecodedBal>,
132 ) -> Result<(), ValidationApiError> {
133 self.validate_message_against_header(block.sealed_header(), &message)?;
134
135 self.consensus.validate_header(block.sealed_header())?;
136 self.consensus.validate_block_pre_execution(block.sealed_block())?;
137
138 if !self.disallow.is_empty() {
139 if self.disallow.contains(&block.beneficiary()) {
140 return Err(ValidationApiError::Blacklist(block.beneficiary()))
141 }
142 if self.disallow.contains(&message.proposer_fee_recipient) {
143 return Err(ValidationApiError::Blacklist(message.proposer_fee_recipient))
144 }
145 for (sender, tx) in block.senders_iter().zip(block.body().transactions()) {
146 if self.disallow.contains(sender) {
147 return Err(ValidationApiError::Blacklist(*sender))
148 }
149 if let Some(to) = tx.to() &&
150 self.disallow.contains(&to)
151 {
152 return Err(ValidationApiError::Blacklist(to))
153 }
154 }
155 }
156
157 let latest_header =
158 self.provider.latest_header()?.ok_or_else(|| ValidationApiError::MissingLatestBlock)?;
159
160 let parent_header = if block.parent_hash() == latest_header.hash() {
161 latest_header
162 } else {
163 let parent_header = self
165 .provider
166 .sealed_header_by_hash(block.parent_hash())?
167 .ok_or_else(|| ValidationApiError::MissingParentBlock)?;
168
169 if latest_header.number().saturating_sub(parent_header.number()) >
170 self.validation_window
171 {
172 return Err(ValidationApiError::BlockTooOld)
173 }
174 parent_header
175 };
176
177 self.consensus.validate_header_against_parent(block.sealed_header(), &parent_header)?;
178 self.validate_gas_limit(registered_gas_limit, &parent_header, block.sealed_header())?;
179
180 if let Some(decoded_bal) = decoded_bal {
182 decoded_bal
183 .as_bal()
184 .validate_gas_limit(block.gas_limit())
185 .map_err(ConsensusError::from)?;
186 }
187
188 let parent_header_hash = parent_header.hash();
189 let state_provider = self.provider.state_by_block_hash(parent_header_hash)?;
190
191 let mut request_cache = self.cached_reads(parent_header_hash).await;
192
193 let (output, block_access_list_hash) = {
194 let cached_db = request_cache.as_db_mut(StateProviderDatabase::new(&state_provider));
195 let mut executor = self.evm_config.batch_executor(cached_db);
196
197 let result = executor.execute_one(&block)?;
198
199 let block_access_list_hash =
204 executor.take_bal().map(|bal| compute_block_access_list_hash(&bal));
205
206 let mut state = executor.into_state();
207 if !self.disallow.is_empty() {
208 for account in state.cache.accounts.keys() {
212 if self.disallow.contains(account) {
213 return Err(ValidationApiError::Blacklist(*account))
214 }
215 }
216 }
217
218 (BlockExecutionOutput { state: state.take_bundle(), result }, block_access_list_hash)
219 };
220
221 self.update_cached_reads(parent_header_hash, request_cache).await;
223
224 self.consensus.validate_block_post_execution(
225 &block,
226 &output,
227 None,
228 block_access_list_hash,
229 )?;
230
231 self.ensure_payment(&block, &output, &message)?;
232
233 let state_root =
234 state_provider.state_root(state_provider.hashed_post_state(&output.state))?;
235
236 if state_root != block.header().state_root() {
237 return Err(ConsensusError::BodyStateRootDiff(
238 GotExpected { got: state_root, expected: block.header().state_root() }.into(),
239 )
240 .into())
241 }
242
243 Ok(())
244 }
245
246 fn validate_message_against_header(
248 &self,
249 header: &SealedHeaderFor<E::Primitives>,
250 message: &BidTrace,
251 ) -> Result<(), ValidationApiError> {
252 if header.hash() != message.block_hash {
253 Err(ValidationApiError::BlockHashMismatch(GotExpected {
254 got: message.block_hash,
255 expected: header.hash(),
256 }))
257 } else if header.parent_hash() != message.parent_hash {
258 Err(ValidationApiError::ParentHashMismatch(GotExpected {
259 got: message.parent_hash,
260 expected: header.parent_hash(),
261 }))
262 } else if header.gas_limit() != message.gas_limit {
263 Err(ValidationApiError::GasLimitMismatch(GotExpected {
264 got: message.gas_limit,
265 expected: header.gas_limit(),
266 }))
267 } else if header.gas_used() != message.gas_used {
268 Err(ValidationApiError::GasUsedMismatch(GotExpected {
269 got: message.gas_used,
270 expected: header.gas_used(),
271 }))
272 } else {
273 Ok(())
274 }
275 }
276
277 fn validate_gas_limit(
282 &self,
283 registered_gas_limit: u64,
284 parent_header: &SealedHeaderFor<E::Primitives>,
285 header: &SealedHeaderFor<E::Primitives>,
286 ) -> Result<(), ValidationApiError> {
287 let max_gas_limit =
288 parent_header.gas_limit() + parent_header.gas_limit() / GAS_LIMIT_BOUND_DIVISOR - 1;
289 let min_gas_limit =
290 parent_header.gas_limit() - parent_header.gas_limit() / GAS_LIMIT_BOUND_DIVISOR + 1;
291
292 let best_gas_limit =
293 std::cmp::max(min_gas_limit, std::cmp::min(max_gas_limit, registered_gas_limit));
294
295 if best_gas_limit != header.gas_limit() {
296 return Err(ValidationApiError::GasLimitMismatch(GotExpected {
297 got: header.gas_limit(),
298 expected: best_gas_limit,
299 }))
300 }
301
302 Ok(())
303 }
304
305 fn ensure_payment(
310 &self,
311 block: &SealedBlock<<E::Primitives as NodePrimitives>::Block>,
312 output: &BlockExecutionOutput<<E::Primitives as NodePrimitives>::Receipt>,
313 message: &BidTrace,
314 ) -> Result<(), ValidationApiError> {
315 let (mut balance_before, balance_after) = if let Some(acc) =
316 output.state.state.get(&message.proposer_fee_recipient)
317 {
318 let balance_before = acc.original_info.as_ref().map(|i| i.balance).unwrap_or_default();
319 let balance_after = acc.info.as_ref().map(|i| i.balance).unwrap_or_default();
320
321 (balance_before, balance_after)
322 } else {
323 (U256::ZERO, U256::ZERO)
326 };
327
328 if let Some(withdrawals) = block.body().withdrawals() {
329 for withdrawal in withdrawals {
330 if withdrawal.address == message.proposer_fee_recipient {
331 balance_before += withdrawal.amount_wei();
332 }
333 }
334 }
335
336 if balance_after >= balance_before.saturating_add(message.value) {
337 return Ok(())
338 }
339
340 let (receipt, tx) = output
341 .receipts
342 .last()
343 .zip(block.body().transactions().last())
344 .ok_or(ValidationApiError::ProposerPayment)?;
345
346 if !receipt.status() {
347 return Err(ValidationApiError::ProposerPayment)
348 }
349
350 if tx.to() != Some(message.proposer_fee_recipient) {
351 return Err(ValidationApiError::ProposerPayment)
352 }
353
354 if tx.value() != message.value {
355 return Err(ValidationApiError::ProposerPayment)
356 }
357
358 if !tx.input().is_empty() {
359 return Err(ValidationApiError::ProposerPayment)
360 }
361
362 if let Some(block_base_fee) = block.header().base_fee_per_gas() &&
363 tx.effective_tip_per_gas(block_base_fee).unwrap_or_default() != 0
364 {
365 return Err(ValidationApiError::ProposerPayment)
366 }
367
368 Ok(())
369 }
370
371 pub fn validate_blobs_bundle(
373 &self,
374 blobs_bundle: BlobsBundleV1,
375 ) -> Result<Vec<B256>, ValidationApiError> {
376 let versioned_hashes = blobs_bundle.versioned_hashes();
377 let sidecar =
378 blobs_bundle.try_into_sidecar().map_err(|_| ValidationApiError::InvalidBlobsBundle)?;
379
380 sidecar.validate(&versioned_hashes, EnvKzgSettings::default().get())?;
381 Ok(versioned_hashes)
382 }
383
384 pub fn validate_blobs_bundle_v2(
386 &self,
387 blobs_bundle: BlobsBundleV2,
388 ) -> Result<Vec<B256>, ValidationApiError> {
389 let versioned_hashes = blobs_bundle.versioned_hashes();
390 let sidecar =
391 blobs_bundle.try_into_sidecar().map_err(|_| ValidationApiError::InvalidBlobsBundle)?;
392
393 sidecar.validate(&versioned_hashes, EnvKzgSettings::default().get())?;
394 Ok(versioned_hashes)
395 }
396
397 async fn validate_builder_submission_v3(
399 &self,
400 request: BuilderBlockValidationRequestV3,
401 ) -> Result<(), ValidationApiError> {
402 let block = self.payload_validator.ensure_well_formed_payload(ExecutionData {
403 payload: ExecutionPayload::V3(request.request.execution_payload),
404 sidecar: ExecutionPayloadSidecar::v3(CancunPayloadFields {
405 parent_beacon_block_root: request.parent_beacon_block_root,
406 versioned_hashes: self.validate_blobs_bundle(request.request.blobs_bundle)?,
407 }),
408 })?;
409
410 self.validate_message_against_block(
411 block,
412 request.request.message,
413 request.registered_gas_limit,
414 None,
415 )
416 .await
417 }
418
419 async fn validate_builder_submission_v4(
421 &self,
422 request: BuilderBlockValidationRequestV4,
423 ) -> Result<(), ValidationApiError> {
424 let block = self.payload_validator.ensure_well_formed_payload(ExecutionData {
425 payload: ExecutionPayload::V3(request.request.execution_payload),
426 sidecar: ExecutionPayloadSidecar::v4(
427 CancunPayloadFields {
428 parent_beacon_block_root: request.parent_beacon_block_root,
429 versioned_hashes: self.validate_blobs_bundle(request.request.blobs_bundle)?,
430 },
431 PraguePayloadFields {
432 requests: RequestsOrHash::Requests(
433 request.request.execution_requests.to_requests(),
434 ),
435 },
436 ),
437 })?;
438
439 self.validate_message_against_block(
440 block,
441 request.request.message,
442 request.registered_gas_limit,
443 None,
444 )
445 .await
446 }
447
448 async fn validate_builder_submission_v5(
450 &self,
451 request: BuilderBlockValidationRequestV5,
452 ) -> Result<(), ValidationApiError> {
453 let block = self.payload_validator.ensure_well_formed_payload(ExecutionData {
454 payload: ExecutionPayload::V3(request.request.execution_payload),
455 sidecar: ExecutionPayloadSidecar::v4(
456 CancunPayloadFields {
457 parent_beacon_block_root: request.parent_beacon_block_root,
458 versioned_hashes: self
459 .validate_blobs_bundle_v2(request.request.blobs_bundle)?,
460 },
461 PraguePayloadFields {
462 requests: RequestsOrHash::Requests(
463 request.request.execution_requests.to_requests(),
464 ),
465 },
466 ),
467 })?;
468
469 let chain_spec = self.provider.chain_spec();
471 if chain_spec.is_osaka_active_at_timestamp(block.timestamp()) &&
472 block.rlp_length() > MAX_RLP_BLOCK_SIZE
473 {
474 return Err(ValidationApiError::Consensus(ConsensusError::BlockTooLarge {
475 rlp_length: block.rlp_length(),
476 max_rlp_length: MAX_RLP_BLOCK_SIZE,
477 }));
478 }
479
480 self.validate_message_against_block(
481 block,
482 request.request.message,
483 request.registered_gas_limit,
484 None,
485 )
486 .await
487 }
488
489 async fn validate_builder_submission_v6(
491 &self,
492 request: BuilderBlockValidationRequestV6,
493 ) -> Result<(), ValidationApiError> {
494 let decoded_bal =
495 DecodedBal::from_rlp_bytes(request.request.execution_payload.block_access_list.clone())
496 .map_err(ValidationApiError::InvalidBlockAccessList)?;
497
498 let block = self.payload_validator.ensure_well_formed_payload(ExecutionData {
499 payload: ExecutionPayload::V4(request.request.execution_payload),
500 sidecar: ExecutionPayloadSidecar::v4(
501 CancunPayloadFields {
502 parent_beacon_block_root: request.parent_beacon_block_root,
503 versioned_hashes: self
504 .validate_blobs_bundle_v2(request.request.blobs_bundle)?,
505 },
506 PraguePayloadFields {
507 requests: RequestsOrHash::Requests(
508 request.request.execution_requests.to_requests(),
509 ),
510 },
511 ),
512 })?;
513
514 let chain_spec = self.provider.chain_spec();
515 if chain_spec.is_osaka_active_at_timestamp(block.timestamp()) &&
516 block.rlp_length() > MAX_RLP_BLOCK_SIZE
517 {
518 return Err(ValidationApiError::Consensus(ConsensusError::BlockTooLarge {
519 rlp_length: block.rlp_length(),
520 max_rlp_length: MAX_RLP_BLOCK_SIZE,
521 }));
522 }
523
524 self.validate_message_against_block(
525 block,
526 request.request.message,
527 request.registered_gas_limit,
528 Some(decoded_bal),
529 )
530 .await
531 }
532}
533
534#[async_trait]
535impl<Provider, E, T> BlockSubmissionValidationApiServer for ValidationApi<Provider, E, T>
536where
537 Provider: BlockReaderIdExt<Header = <E::Primitives as NodePrimitives>::BlockHeader>
538 + ChainSpecProvider<ChainSpec: EthereumHardforks>
539 + StateProviderFactory
540 + Clone
541 + 'static,
542 E: ConfigureEvm + 'static,
543 T: PayloadTypes<ExecutionData = ExecutionData>,
544{
545 async fn validate_builder_submission_v1(
546 &self,
547 _request: BuilderBlockValidationRequest,
548 ) -> RpcResult<()> {
549 warn!(target: "rpc::flashbots", "Method `flashbots_validateBuilderSubmissionV1` is not supported");
550 Err(internal_rpc_err("unimplemented"))
551 }
552
553 async fn validate_builder_submission_v2(
554 &self,
555 _request: BuilderBlockValidationRequestV2,
556 ) -> RpcResult<()> {
557 warn!(target: "rpc::flashbots", "Method `flashbots_validateBuilderSubmissionV2` is not supported");
558 Err(internal_rpc_err("unimplemented"))
559 }
560
561 async fn validate_builder_submission_v3(
563 &self,
564 request: BuilderBlockValidationRequestV3,
565 ) -> RpcResult<()> {
566 let this = self.clone();
567 let (tx, rx) = oneshot::channel();
568
569 self.task_spawner.spawn_blocking_task(async move {
570 let result = Self::validate_builder_submission_v3(&this, request)
571 .await
572 .map_err(ErrorObject::from);
573 let _ = tx.send(result);
574 });
575
576 rx.await.map_err(|_| internal_rpc_err("Internal blocking task error"))?
577 }
578
579 async fn validate_builder_submission_v4(
581 &self,
582 request: BuilderBlockValidationRequestV4,
583 ) -> RpcResult<()> {
584 let this = self.clone();
585 let (tx, rx) = oneshot::channel();
586
587 self.task_spawner.spawn_blocking_task(async move {
588 let result = Self::validate_builder_submission_v4(&this, request)
589 .await
590 .map_err(ErrorObject::from);
591 let _ = tx.send(result);
592 });
593
594 rx.await.map_err(|_| internal_rpc_err("Internal blocking task error"))?
595 }
596
597 async fn validate_builder_submission_v5(
599 &self,
600 request: BuilderBlockValidationRequestV5,
601 ) -> RpcResult<()> {
602 let this = self.clone();
603 let (tx, rx) = oneshot::channel();
604
605 self.task_spawner.spawn_blocking_task(async move {
606 let result = Self::validate_builder_submission_v5(&this, request)
607 .await
608 .map_err(ErrorObject::from);
609 let _ = tx.send(result);
610 });
611
612 rx.await.map_err(|_| internal_rpc_err("Internal blocking task error"))?
613 }
614
615 async fn validate_builder_submission_v6(
617 &self,
618 request: BuilderBlockValidationRequestV6,
619 ) -> RpcResult<()> {
620 let this = self.clone();
621 let (tx, rx) = oneshot::channel();
622
623 self.task_spawner.spawn_blocking_task(async move {
624 let result = Self::validate_builder_submission_v6(&this, request)
625 .await
626 .map_err(ErrorObject::from);
627 let _ = tx.send(result);
628 });
629
630 rx.await.map_err(|_| internal_rpc_err("Internal blocking task error"))?
631 }
632}
633
634pub struct ValidationApiInner<Provider, E: ConfigureEvm, T: PayloadTypes> {
635 provider: Provider,
637 consensus: Arc<dyn FullConsensus<E::Primitives>>,
639 payload_validator:
641 Arc<dyn PayloadValidator<T, Block = <E::Primitives as NodePrimitives>::Block>>,
642 evm_config: E,
644 disallow: AddressSet,
646 validation_window: u64,
648 cached_state: RwLock<(B256, CachedReads)>,
653 task_spawner: Runtime,
655 metrics: ValidationMetrics,
657}
658
659fn hash_disallow_list(disallow: &AddressSet) -> String {
664 let mut sorted: Vec<_> = disallow.iter().collect();
665 sorted.sort_unstable(); let mut hasher = Sha256::new();
668 for addr in sorted {
669 hasher.update(addr.as_slice());
670 }
671
672 format!("{:x}", hasher.finalize())
673}
674
675impl<Provider, E: ConfigureEvm, T: PayloadTypes> fmt::Debug for ValidationApiInner<Provider, E, T> {
676 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
677 f.debug_struct("ValidationApiInner").finish_non_exhaustive()
678 }
679}
680
681#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
683pub struct ValidationApiConfig {
684 pub disallow: AddressSet,
686 pub validation_window: u64,
688}
689
690impl ValidationApiConfig {
691 pub const DEFAULT_VALIDATION_WINDOW: u64 = 3;
693}
694
695impl Default for ValidationApiConfig {
696 fn default() -> Self {
697 Self { disallow: Default::default(), validation_window: Self::DEFAULT_VALIDATION_WINDOW }
698 }
699}
700
701#[derive(Debug, thiserror::Error)]
703pub enum ValidationApiError {
704 #[error("block gas limit mismatch: {_0}")]
705 GasLimitMismatch(GotExpected<u64>),
706 #[error("block gas used mismatch: {_0}")]
707 GasUsedMismatch(GotExpected<u64>),
708 #[error("block parent hash mismatch: {_0}")]
709 ParentHashMismatch(GotExpected<B256>),
710 #[error("block hash mismatch: {_0}")]
711 BlockHashMismatch(GotExpected<B256>),
712 #[error("missing latest block in database")]
713 MissingLatestBlock,
714 #[error("parent block not found")]
715 MissingParentBlock,
716 #[error("block is too old, outside validation window")]
717 BlockTooOld,
718 #[error("could not verify proposer payment")]
719 ProposerPayment,
720 #[error("invalid blobs bundle")]
721 InvalidBlobsBundle,
722 #[error("invalid block access list: {_0}")]
723 InvalidBlockAccessList(alloy_rlp::Error),
724 #[error("block accesses blacklisted address: {_0}")]
725 Blacklist(Address),
726 #[error(transparent)]
727 Blob(#[from] BlobTransactionValidationError),
728 #[error(transparent)]
729 Consensus(#[from] ConsensusError),
730 #[error(transparent)]
731 Provider(#[from] ProviderError),
732 #[error(transparent)]
733 Execution(#[from] BlockExecutionError),
734 #[error(transparent)]
735 Payload(#[from] NewPayloadError),
736}
737
738impl From<ValidationApiError> for ErrorObject<'static> {
739 fn from(error: ValidationApiError) -> Self {
740 match error {
741 ValidationApiError::GasLimitMismatch(_) |
742 ValidationApiError::GasUsedMismatch(_) |
743 ValidationApiError::ParentHashMismatch(_) |
744 ValidationApiError::BlockHashMismatch(_) |
745 ValidationApiError::Blacklist(_) |
746 ValidationApiError::ProposerPayment |
747 ValidationApiError::InvalidBlobsBundle |
748 ValidationApiError::InvalidBlockAccessList(_) |
749 ValidationApiError::Blob(_) => invalid_params_rpc_err(error.to_string()),
750
751 ValidationApiError::Consensus(
752 error @ (ConsensusError::BlockAccessListCostMoreThanGasLimit(_) |
753 ConsensusError::BlockAccessListHashMismatch(_)),
754 ) => invalid_params_rpc_err(error.to_string()),
755 ValidationApiError::MissingLatestBlock |
756 ValidationApiError::MissingParentBlock |
757 ValidationApiError::BlockTooOld |
758 ValidationApiError::Consensus(_) |
759 ValidationApiError::Provider(_) => internal_rpc_err(error.to_string()),
760 ValidationApiError::Execution(err) => match err {
761 error @ BlockExecutionError::Validation(_) => {
762 invalid_params_rpc_err(error.to_string())
763 }
764 error @ BlockExecutionError::Internal(_) => internal_rpc_err(error.to_string()),
765 },
766 ValidationApiError::Payload(err) => match err {
767 error @ NewPayloadError::Eth(_) => invalid_params_rpc_err(error.to_string()),
768 error @ NewPayloadError::Other(_) => internal_rpc_err(error.to_string()),
769 },
770 }
771 }
772}
773
774#[derive(Metrics)]
776#[metrics(scope = "builder.validation")]
777pub(crate) struct ValidationMetrics {
778 pub(crate) disallow_size: Gauge,
780}
781
782#[cfg(test)]
783mod tests {
784 use super::{hash_disallow_list, AddressSet};
785 use revm_primitives::Address;
786
787 #[test]
788 fn test_hash_disallow_list_deterministic() {
789 let mut addresses = AddressSet::default();
790 addresses.insert(Address::from([1u8; 20]));
791 addresses.insert(Address::from([2u8; 20]));
792
793 let hash1 = hash_disallow_list(&addresses);
794 let hash2 = hash_disallow_list(&addresses);
795
796 assert_eq!(hash1, hash2);
797 }
798
799 #[test]
800 fn test_hash_disallow_list_different_content() {
801 let mut addresses1 = AddressSet::default();
802 addresses1.insert(Address::from([1u8; 20]));
803
804 let mut addresses2 = AddressSet::default();
805 addresses2.insert(Address::from([2u8; 20]));
806
807 let hash1 = hash_disallow_list(&addresses1);
808 let hash2 = hash_disallow_list(&addresses2);
809
810 assert_ne!(hash1, hash2);
811 }
812
813 #[test]
814 fn test_hash_disallow_list_order_independent() {
815 let mut addresses1 = AddressSet::default();
816 addresses1.insert(Address::from([1u8; 20]));
817 addresses1.insert(Address::from([2u8; 20]));
818
819 let mut addresses2 = AddressSet::default();
820 addresses2.insert(Address::from([2u8; 20])); addresses2.insert(Address::from([1u8; 20]));
822
823 let hash1 = hash_disallow_list(&addresses1);
824 let hash2 = hash_disallow_list(&addresses2);
825
826 assert_eq!(hash1, hash2);
827 }
828
829 #[test]
830 fn test_disallow_list_hash_rbuilder_parity() {
832 let json = r#"["0x05E0b5B40B7b66098C2161A5EE11C5740A3A7C45","0x01e2919679362dFBC9ee1644Ba9C6da6D6245BB1","0x03893a7c7463AE47D46bc7f091665f1893656003","0x04DBA1194ee10112fE6C3207C0687DEf0e78baCf"]"#;
833 let blocklist: Vec<Address> = serde_json::from_str(json).unwrap();
834 let blocklist: AddressSet = blocklist.into_iter().collect();
835 let expected_hash = "ee14e9d115e182f61871a5a385ab2f32ecf434f3b17bdbacc71044810d89e608";
836 let hash = hash_disallow_list(&blocklist);
837 assert_eq!(expected_hash, hash);
838 }
839}