11use miden_node_db:: { DatabaseError , Db } ;
2- use miden_protocol:: block:: ProposedBlock ;
2+ use miden_node_utils:: tracing:: OpenTelemetrySpanExt ;
3+ use miden_protocol:: block:: { BlockHeader , BlockNumber , ProposedBlock } ;
34use miden_protocol:: crypto:: dsa:: ecdsa_k256_keccak:: Signature ;
45use miden_protocol:: errors:: ProposedBlockError ;
56use miden_protocol:: transaction:: { TransactionHeader , TransactionId } ;
6- use tracing:: { info_span , instrument} ;
7+ use tracing:: { Span , instrument} ;
78
8- use crate :: db:: find_unvalidated_transactions;
9+ use crate :: db:: { find_unvalidated_transactions, load_block_header } ;
910use crate :: { COMPONENT , ValidatorSigner } ;
1011
1112// BLOCK VALIDATION ERROR
@@ -21,19 +22,35 @@ pub enum BlockValidationError {
2122 BlockSigningFailed ( String ) ,
2223 #[ error( "failed to select transactions" ) ]
2324 DatabaseError ( #[ source] DatabaseError ) ,
25+ #[ error( "block number mismatch: expected {expected}, got {actual}" ) ]
26+ BlockNumberMismatch {
27+ expected : BlockNumber ,
28+ actual : BlockNumber ,
29+ } ,
30+ #[ error( "previous block commitment does not match chain tip" ) ]
31+ PrevBlockCommitmentMismatch ,
32+ #[ error( "no previous block header available for chain tip overwrite" ) ]
33+ NoPrevBlockHeader ,
2434}
2535
2636// BLOCK VALIDATION
2737// ================================================================================================
2838
29- /// Validates a block by checking that all transactions in the proposed block have been processed by
30- /// the validator in the past.
31- #[ instrument( target = COMPONENT , skip_all, err) ]
39+ /// Validates a proposed block by checking:
40+ /// 1. All transactions have been previously validated by this validator.
41+ /// 2. The block header can be successfully built from the proposed block.
42+ /// 3. The block is either: a. The valid next block in the chain (sequential block number, matching
43+ /// previous block commitment), or b. A replacement block at the same height as the current chain
44+ /// tip, validated against the previous block header.
45+ ///
46+ /// On success, returns the signature and the validated block header.
47+ #[ instrument( target = COMPONENT , skip_all, err, fields( tip. number = chain_tip. block_num( ) . as_u32( ) ) ) ]
3248pub async fn validate_block (
3349 proposed_block : ProposedBlock ,
3450 signer : & ValidatorSigner ,
3551 db : & Db ,
36- ) -> Result < Signature , BlockValidationError > {
52+ chain_tip : BlockHeader ,
53+ ) -> Result < ( Signature , BlockHeader ) , BlockValidationError > {
3754 // Search for any proposed transactions that have not previously been validated.
3855 let proposed_tx_ids =
3956 proposed_block. transactions ( ) . map ( TransactionHeader :: id) . collect :: < Vec < _ > > ( ) ;
@@ -50,15 +67,55 @@ pub async fn validate_block(
5067 }
5168
5269 // Build the block header.
53- let ( header , _) = proposed_block
70+ let ( proposed_header , _) = proposed_block
5471 . into_header_and_body ( )
5572 . map_err ( BlockValidationError :: BlockBuildingFailed ) ?;
5673
57- // Sign the header.
58- let signature = info_span ! ( "sign_block" )
59- . in_scope ( async move || signer. sign ( & header) . await )
60- . await
61- . map_err ( |err| BlockValidationError :: BlockSigningFailed ( err. to_string ( ) ) ) ?;
74+ let span = Span :: current ( ) ;
75+ span. set_attribute ( "block.number" , proposed_header. block_num ( ) . as_u32 ( ) ) ;
76+ span. set_attribute ( "block.commitment" , proposed_header. commitment ( ) ) ;
77+
78+ // If the proposed block has the same block number as the current chain tip, this is a
79+ // replacement block. Validate it against the previous block header.
80+ let prev = if proposed_header. block_num ( ) == chain_tip. block_num ( ) {
81+ let prev_block_num =
82+ chain_tip. block_num ( ) . parent ( ) . ok_or ( BlockValidationError :: NoPrevBlockHeader ) ?;
83+ db. query ( "load_block_header" , move |conn| load_block_header ( conn, prev_block_num) )
84+ . await
85+ . map_err ( BlockValidationError :: DatabaseError ) ?
86+ . ok_or ( BlockValidationError :: NoPrevBlockHeader ) ?
87+ } else {
88+ // Proposed block is a new block.
89+ // Block number must be sequential.
90+ let expected_block_num = chain_tip. block_num ( ) . child ( ) ;
91+ if proposed_header. block_num ( ) != expected_block_num {
92+ return Err ( BlockValidationError :: BlockNumberMismatch {
93+ expected : expected_block_num,
94+ actual : proposed_header. block_num ( ) ,
95+ } ) ;
96+ }
97+ // Current chain tip is the parent of the proposed block.
98+ chain_tip
99+ } ;
100+
101+ // The proposed block's parent must match the block that the Validator has determined is its
102+ // parent (either chain tip or parent of chain tip).
103+ if proposed_header. prev_block_commitment ( ) != prev. commitment ( ) {
104+ return Err ( BlockValidationError :: PrevBlockCommitmentMismatch ) ;
105+ }
62106
63- Ok ( signature)
107+ let signature = sign_header ( signer, & proposed_header) . await ?;
108+ Ok ( ( signature, proposed_header) )
109+ }
110+
111+ /// Signs a block header using the validator's signer.
112+ #[ instrument( target = COMPONENT , name = "sign_block" , skip_all, err, fields( block. number = header. block_num( ) . as_u32( ) ) ) ]
113+ async fn sign_header (
114+ signer : & ValidatorSigner ,
115+ header : & BlockHeader ,
116+ ) -> Result < Signature , BlockValidationError > {
117+ signer
118+ . sign ( header)
119+ . await
120+ . map_err ( |err| BlockValidationError :: BlockSigningFailed ( err. to_string ( ) ) )
64121}
0 commit comments