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