|
| 1 | +//! Rollkit custom consensus implementation that allows same timestamps across blocks. |
| 2 | +
|
| 3 | +use reth_chainspec::ChainSpec; |
| 4 | +use reth_consensus::{Consensus, ConsensusError, FullConsensus, HeaderValidator}; |
| 5 | +use reth_consensus_common::validation::validate_body_against_header; |
| 6 | +use reth_ethereum_consensus::EthBeaconConsensus; |
| 7 | +use reth_ethereum_primitives::{Block, BlockBody, EthPrimitives, Receipt}; |
| 8 | +use reth_execution_types::BlockExecutionResult; |
| 9 | +use reth_primitives::{GotExpected, GotExpectedBoxed, RecoveredBlock, SealedBlock, SealedHeader}; |
| 10 | +use std::sync::Arc; |
| 11 | + |
| 12 | +/// Rollkit consensus implementation that allows blocks with the same timestamp. |
| 13 | +/// |
| 14 | +/// This consensus implementation wraps the standard Ethereum beacon consensus |
| 15 | +/// but modifies the timestamp validation to allow multiple blocks to have the |
| 16 | +/// same timestamp, which is required for Rollkit's operation. |
| 17 | +#[derive(Debug, Clone)] |
| 18 | +pub struct RollkitConsensus { |
| 19 | + /// Inner Ethereum beacon consensus for standard validation |
| 20 | + inner: EthBeaconConsensus<ChainSpec>, |
| 21 | +} |
| 22 | + |
| 23 | +impl RollkitConsensus { |
| 24 | + /// Create a new Rollkit consensus instance |
| 25 | + pub const fn new(chain_spec: Arc<ChainSpec>) -> Self { |
| 26 | + let inner = EthBeaconConsensus::new(chain_spec); |
| 27 | + Self { inner } |
| 28 | + } |
| 29 | +} |
| 30 | + |
| 31 | +impl HeaderValidator for RollkitConsensus { |
| 32 | + fn validate_header(&self, header: &SealedHeader) -> Result<(), ConsensusError> { |
| 33 | + // Use inner consensus for basic header validation |
| 34 | + self.inner.validate_header(header) |
| 35 | + } |
| 36 | + |
| 37 | + fn validate_header_against_parent( |
| 38 | + &self, |
| 39 | + header: &SealedHeader, |
| 40 | + parent: &SealedHeader, |
| 41 | + ) -> Result<(), ConsensusError> { |
| 42 | + // Custom validation that allows same timestamps |
| 43 | + // This is the key difference from standard Ethereum consensus |
| 44 | + |
| 45 | + // First validate parent hash and number |
| 46 | + if header.parent_hash != parent.hash() { |
| 47 | + return Err(ConsensusError::ParentHashMismatch(GotExpectedBoxed( |
| 48 | + Box::new(GotExpected { |
| 49 | + got: header.parent_hash, |
| 50 | + expected: parent.hash(), |
| 51 | + }), |
| 52 | + ))); |
| 53 | + } |
| 54 | + |
| 55 | + if header.number != parent.number + 1 { |
| 56 | + return Err(ConsensusError::ParentBlockNumberMismatch { |
| 57 | + parent_block_number: parent.number, |
| 58 | + block_number: header.number, |
| 59 | + }); |
| 60 | + } |
| 61 | + |
| 62 | + // ROLLKIT MODIFICATION: Allow same timestamp |
| 63 | + // Standard Ethereum requires: header.timestamp > parent.timestamp |
| 64 | + // Rollkit allows: header.timestamp >= parent.timestamp |
| 65 | + if header.timestamp < parent.timestamp { |
| 66 | + return Err(ConsensusError::TimestampIsInPast { |
| 67 | + parent_timestamp: parent.timestamp, |
| 68 | + timestamp: header.timestamp, |
| 69 | + }); |
| 70 | + } |
| 71 | + // NOTE: We explicitly do NOT check for header.timestamp == parent.timestamp |
| 72 | + // as an error, which is the main change for Rollkit |
| 73 | + |
| 74 | + // For all other validations, delegate to the inner consensus |
| 75 | + // but skip it when timestamps are equal since the inner consensus |
| 76 | + // would reject this case |
| 77 | + if header.timestamp == parent.timestamp { |
| 78 | + // Timestamps are equal, which we explicitly allow for Rollkit |
| 79 | + // Skip the inner consensus validation that would reject this |
| 80 | + Ok(()) |
| 81 | + } else { |
| 82 | + // Timestamps are different, so we can safely delegate to inner consensus |
| 83 | + self.inner.validate_header_against_parent(header, parent) |
| 84 | + } |
| 85 | + } |
| 86 | +} |
| 87 | + |
| 88 | +impl Consensus<Block> for RollkitConsensus { |
| 89 | + type Error = ConsensusError; |
| 90 | + |
| 91 | + fn validate_body_against_header( |
| 92 | + &self, |
| 93 | + body: &BlockBody, |
| 94 | + header: &SealedHeader, |
| 95 | + ) -> Result<(), Self::Error> { |
| 96 | + validate_body_against_header(body, header.header()) |
| 97 | + } |
| 98 | + |
| 99 | + fn validate_block_pre_execution(&self, block: &SealedBlock) -> Result<(), Self::Error> { |
| 100 | + // Use inner consensus for pre-execution validation |
| 101 | + self.inner.validate_block_pre_execution(block) |
| 102 | + } |
| 103 | +} |
| 104 | + |
| 105 | +impl FullConsensus<EthPrimitives> for RollkitConsensus { |
| 106 | + fn validate_block_post_execution( |
| 107 | + &self, |
| 108 | + block: &RecoveredBlock<Block>, |
| 109 | + result: &BlockExecutionResult<Receipt>, |
| 110 | + ) -> Result<(), ConsensusError> { |
| 111 | + <EthBeaconConsensus<ChainSpec> as FullConsensus<EthPrimitives>>::validate_block_post_execution(&self.inner, block, result) |
| 112 | + } |
| 113 | +} |
| 114 | + |
| 115 | +/// Builder for `RollkitConsensus` |
| 116 | +#[derive(Debug, Default, Clone)] |
| 117 | +#[non_exhaustive] |
| 118 | +pub struct RollkitConsensusBuilder; |
| 119 | + |
| 120 | +impl RollkitConsensusBuilder { |
| 121 | + /// Create a new `RollkitConsensusBuilder` |
| 122 | + pub const fn new() -> Self { |
| 123 | + Self |
| 124 | + } |
| 125 | +} |
| 126 | + |
| 127 | +// Instead of implementing the generic ConsensusBuilder trait, |
| 128 | +// we'll use a direct approach that works with the node builder |
| 129 | +impl RollkitConsensusBuilder { |
| 130 | + /// Build the consensus implementation |
| 131 | + pub fn build(chain_spec: Arc<ChainSpec>) -> Arc<RollkitConsensus> { |
| 132 | + Arc::new(RollkitConsensus::new(chain_spec)) |
| 133 | + } |
| 134 | +} |
0 commit comments