From d6a3109932c8e1bd5d072a7c0357bdd8912c86ae Mon Sep 17 00:00:00 2001 From: Morty Date: Thu, 11 Sep 2025 13:57:06 +0800 Subject: [PATCH 01/13] fix: use correct function to recover signer --- crates/node/src/builder/network.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/node/src/builder/network.rs b/crates/node/src/builder/network.rs index af86bad8..ef706bc5 100644 --- a/crates/node/src/builder/network.rs +++ b/crates/node/src/builder/network.rs @@ -289,8 +289,7 @@ fn recover_and_verify_signer( authorized_signer: Option
, ) -> Result { // Recover signer from signature - let signer = signature - .recover_address_from_prehash(&hash) + let signer = reth_primitives_traits::crypto::secp256k1::recover_signer(signature, hash) .map_err(|_| HeaderTransformError::RecoveryFailed)?; // Verify signer is authorized From 8bbc5e283b9a656ea210010d8c3c3302eaebbd37 Mon Sep 17 00:00:00 2001 From: Morty Date: Thu, 11 Sep 2025 17:12:58 +0800 Subject: [PATCH 02/13] fix: use sig_encode_hash to calculate block hash --- crates/node/src/builder/network.rs | 25 +++++---- crates/node/tests/e2e.rs | 10 ++-- crates/primitives/src/signature.rs | 84 ++++++++++++++++++------------ crates/sequencer/tests/e2e.rs | 4 +- crates/signer/src/future.rs | 2 +- crates/signer/src/lib.rs | 2 +- 6 files changed, 74 insertions(+), 53 deletions(-) diff --git a/crates/node/src/builder/network.rs b/crates/node/src/builder/network.rs index ef706bc5..8c6a3059 100644 --- a/crates/node/src/builder/network.rs +++ b/crates/node/src/builder/network.rs @@ -14,6 +14,7 @@ use reth_primitives_traits::BlockHeader; use reth_scroll_chainspec::ScrollChainSpec; use reth_scroll_primitives::ScrollPrimitives; use reth_transaction_pool::{PoolTransaction, TransactionPool}; +use rollup_node_primitives::sig_encode_hash; use rollup_node_signer::SignatureAsBytes; use scroll_alloy_hardforks::ScrollHardforks; use scroll_db::{Database, DatabaseOperations}; @@ -187,8 +188,9 @@ impl ScrollHeaderTransform Result<(), HeaderTransformError> { let signature_bytes = std::mem::take(header.extra_data_mut()); let signature = parse_65b_signature(&signature_bytes)?; + let hash = sig_encode_hash(header); // Recover and verify signer - recover_and_verify_signer(&signature, header.hash_slow(), authorized_signer)?; + recover_and_verify_signer(&signature, hash, authorized_signer)?; // Store signature in database - persist_signature(self.db.clone(), header.hash_slow(), signature); + persist_signature(self.db.clone(), hash, signature); Ok(()) } @@ -247,15 +250,16 @@ impl eyre::Result<()> { assert_eq!(block0.hash_slow(), block_with_peer.block.hash_slow()); // Verify the signature is from the authorized signer - let hash = sig_encode_hash(&block_with_peer.block); + let hash = sig_encode_hash(&block_with_peer.block.header); let recovered = block_with_peer.signature.recover_address_from_prehash(&hash).unwrap(); assert_eq!(recovered, authorized_address, "Block should be signed by authorized signer"); } else { @@ -408,7 +408,7 @@ async fn can_penalize_peer_for_invalid_signature() -> eyre::Result<()> { block1.header.timestamp += 1; // Sign the block with the unauthorized signer - let block_hash = sig_encode_hash(&block1); + let block_hash = sig_encode_hash(&block1.header); let unauthorized_signature = unauthorized_signer.sign_hash(&block_hash).await.unwrap(); // Send the block with invalid signature from node0 to node1 @@ -420,7 +420,7 @@ async fn can_penalize_peer_for_invalid_signature() -> eyre::Result<()> { assert_eq!(block1.hash_slow(), block_with_peer.block.hash_slow()); // Verify the signature is from the unauthorized signer - let hash = sig_encode_hash(&block_with_peer.block); + let hash = sig_encode_hash(&block_with_peer.block.header); let recovered = block_with_peer.signature.recover_address_from_prehash(&hash).unwrap(); return recovered == unauthorized_signer.address(); } @@ -1712,7 +1712,7 @@ async fn signer_rotation() -> eyre::Result<()> { |event| { if let RollupManagerEvent::NewBlockReceived(block) = event { let signature = block.signature; - let hash = sig_encode_hash(&block.block); + let hash = sig_encode_hash(&block.block.header); // Verify that the block is signed by the first sequencer. let recovered_address = signature.recover_address_from_prehash(&hash).unwrap(); recovered_address == signer_1_address @@ -1760,7 +1760,7 @@ async fn signer_rotation() -> eyre::Result<()> { |event| { if let RollupManagerEvent::NewBlockReceived(block) = event { let signature = block.signature; - let hash = sig_encode_hash(&block.block); + let hash = sig_encode_hash(&block.block.header); let recovered_address = signature.recover_address_from_prehash(&hash).unwrap(); // Verify that the block is signed by the second sequencer. recovered_address == signer_2_address diff --git a/crates/primitives/src/signature.rs b/crates/primitives/src/signature.rs index 855a6fe6..411847c9 100644 --- a/crates/primitives/src/signature.rs +++ b/crates/primitives/src/signature.rs @@ -1,59 +1,75 @@ -use alloy_consensus::Header; use alloy_primitives::{ keccak256, private::{alloy_rlp, alloy_rlp::Encodable}, B256, U256, }; +use reth_primitives_traits::BlockHeader; use std::vec::Vec; /// Encode and hash the header for signature. The function is similar to `Header::encode` but skips /// the `extra_data` field. -pub fn sig_encode_hash(header: &Header) -> B256 { +pub fn sig_encode_hash(header: &H) -> B256 { let out = &mut Vec::new(); let list_header = alloy_rlp::Header { list: true, payload_length: sig_header_payload_length(header) }; list_header.encode(out); - header.parent_hash.encode(out); - header.ommers_hash.encode(out); - header.beneficiary.encode(out); - header.state_root.encode(out); - header.transactions_root.encode(out); - header.receipts_root.encode(out); - header.logs_bloom.encode(out); - header.difficulty.encode(out); - U256::from(header.number).encode(out); - U256::from(header.gas_limit).encode(out); - U256::from(header.gas_used).encode(out); - header.timestamp.encode(out); - header.mix_hash.encode(out); - header.nonce.encode(out); + header.parent_hash().encode(out); + header.ommers_hash().encode(out); + header.beneficiary().encode(out); + header.state_root().encode(out); + header.transactions_root().encode(out); + header.receipts_root().encode(out); + header.logs_bloom().encode(out); + header.difficulty().encode(out); + U256::from(header.number()).encode(out); + U256::from(header.gas_limit()).encode(out); + U256::from(header.gas_used()).encode(out); + header.timestamp().encode(out); + if let Some(mix_hash) = header.mix_hash() { + mix_hash.encode(out); + } else { + alloy_primitives::B256::ZERO.encode(out); + } + if let Some(nonce) = header.nonce() { + nonce.encode(out); + } else { + 0u64.encode(out); + } // Encode all the fork specific fields - if let Some(ref base_fee) = header.base_fee_per_gas { - U256::from(*base_fee).encode(out); + if let Some(base_fee) = header.base_fee_per_gas() { + U256::from(base_fee).encode(out); } keccak256(&out) } /// Returns the header payload length for signature. -fn sig_header_payload_length(header: &Header) -> usize { +fn sig_header_payload_length(header: &H) -> usize { let mut length = 0; - length += header.parent_hash.length(); - length += header.ommers_hash.length(); - length += header.beneficiary.length(); - length += header.state_root.length(); - length += header.transactions_root.length(); - length += header.receipts_root.length(); - length += header.logs_bloom.length(); - length += header.difficulty.length(); - length += U256::from(header.number).length(); - length += U256::from(header.gas_limit).length(); - length += U256::from(header.gas_used).length(); - length += header.timestamp.length(); - length += header.mix_hash.length(); - length += header.nonce.length(); + length += header.parent_hash().length(); + length += header.ommers_hash().length(); + length += header.beneficiary().length(); + length += header.state_root().length(); + length += header.transactions_root().length(); + length += header.receipts_root().length(); + length += header.logs_bloom().length(); + length += header.difficulty().length(); + length += U256::from(header.number()).length(); + length += U256::from(header.gas_limit()).length(); + length += U256::from(header.gas_used()).length(); + length += header.timestamp().length(); + if let Some(mix_hash) = header.mix_hash() { + length += mix_hash.length(); + } else { + length += alloy_primitives::B256::ZERO.length(); + } + if let Some(nonce) = header.nonce() { + length += nonce.length(); + } else { + length += 0u64.length(); + } - if let Some(base_fee) = header.base_fee_per_gas { + if let Some(base_fee) = header.base_fee_per_gas() { // Adding base fee length if it exists. length += U256::from(base_fee).length(); } diff --git a/crates/sequencer/tests/e2e.rs b/crates/sequencer/tests/e2e.rs index 87cbbe78..92887f58 100644 --- a/crates/sequencer/tests/e2e.rs +++ b/crates/sequencer/tests/e2e.rs @@ -558,7 +558,7 @@ async fn can_sequence_blocks_with_private_key_file() -> eyre::Result<()> { signature, })) = sequencer_events.next().await { - let hash = sig_encode_hash(&signed_block); + let hash = sig_encode_hash(&signed_block.header); let recovered_address = signature.recover_address_from_prehash(&hash)?; assert_eq!(recovered_address, expected_address); } else { @@ -652,7 +652,7 @@ async fn can_sequence_blocks_with_hex_key_file_without_prefix() -> eyre::Result< signature, }) = event { - let hash = sig_encode_hash(&signed_block); + let hash = sig_encode_hash(&signed_block.header); let recovered_address = signature.recover_address_from_prehash(&hash)?; assert_eq!(recovered_address, expected_address); break; diff --git a/crates/signer/src/future.rs b/crates/signer/src/future.rs index 9858b7a7..98b5419f 100644 --- a/crates/signer/src/future.rs +++ b/crates/signer/src/future.rs @@ -15,7 +15,7 @@ pub fn sign_block( Box::pin(async move { // TODO: Are we happy to sign the hash directly or do we want to use EIP-191 // (`signer.sign_message`)? - let hash = sig_encode_hash(&block); + let hash = sig_encode_hash(&block.header); let signature = signer.sign_hash(&hash).await?; Ok(SignerEvent::SignedBlock { block, signature }) }) diff --git a/crates/signer/src/lib.rs b/crates/signer/src/lib.rs index 7682a5bd..9c1f8056 100644 --- a/crates/signer/src/lib.rs +++ b/crates/signer/src/lib.rs @@ -152,7 +152,7 @@ mod tests { let (event_block, signature) = match event { SignerEvent::SignedBlock { block, signature } => (block, signature), }; - let hash = sig_encode_hash(&event_block); + let hash = sig_encode_hash(&event_block.header); let recovered_address = signature.recover_address_from_prehash(&hash).unwrap(); assert_eq!(event_block, block); From 2df374547cfe3635a652da79d58f5a9c15510a2d Mon Sep 17 00:00:00 2001 From: Morty Date: Thu, 11 Sep 2025 17:17:27 +0800 Subject: [PATCH 03/13] Revert "fix: use sig_encode_hash to calculate block hash" This reverts commit 8bbc5e283b9a656ea210010d8c3c3302eaebbd37. --- crates/node/src/builder/network.rs | 25 ++++----- crates/node/tests/e2e.rs | 10 ++-- crates/primitives/src/signature.rs | 84 ++++++++++++------------------ crates/sequencer/tests/e2e.rs | 4 +- crates/signer/src/future.rs | 2 +- crates/signer/src/lib.rs | 2 +- 6 files changed, 53 insertions(+), 74 deletions(-) diff --git a/crates/node/src/builder/network.rs b/crates/node/src/builder/network.rs index 8c6a3059..ef706bc5 100644 --- a/crates/node/src/builder/network.rs +++ b/crates/node/src/builder/network.rs @@ -14,7 +14,6 @@ use reth_primitives_traits::BlockHeader; use reth_scroll_chainspec::ScrollChainSpec; use reth_scroll_primitives::ScrollPrimitives; use reth_transaction_pool::{PoolTransaction, TransactionPool}; -use rollup_node_primitives::sig_encode_hash; use rollup_node_signer::SignatureAsBytes; use scroll_alloy_hardforks::ScrollHardforks; use scroll_db::{Database, DatabaseOperations}; @@ -188,9 +187,8 @@ impl ScrollHeaderTransform Result<(), HeaderTransformError> { let signature_bytes = std::mem::take(header.extra_data_mut()); let signature = parse_65b_signature(&signature_bytes)?; - let hash = sig_encode_hash(header); // Recover and verify signer - recover_and_verify_signer(&signature, hash, authorized_signer)?; + recover_and_verify_signer(&signature, header.hash_slow(), authorized_signer)?; // Store signature in database - persist_signature(self.db.clone(), hash, signature); + persist_signature(self.db.clone(), header.hash_slow(), signature); Ok(()) } @@ -250,16 +247,15 @@ impl eyre::Result<()> { assert_eq!(block0.hash_slow(), block_with_peer.block.hash_slow()); // Verify the signature is from the authorized signer - let hash = sig_encode_hash(&block_with_peer.block.header); + let hash = sig_encode_hash(&block_with_peer.block); let recovered = block_with_peer.signature.recover_address_from_prehash(&hash).unwrap(); assert_eq!(recovered, authorized_address, "Block should be signed by authorized signer"); } else { @@ -408,7 +408,7 @@ async fn can_penalize_peer_for_invalid_signature() -> eyre::Result<()> { block1.header.timestamp += 1; // Sign the block with the unauthorized signer - let block_hash = sig_encode_hash(&block1.header); + let block_hash = sig_encode_hash(&block1); let unauthorized_signature = unauthorized_signer.sign_hash(&block_hash).await.unwrap(); // Send the block with invalid signature from node0 to node1 @@ -420,7 +420,7 @@ async fn can_penalize_peer_for_invalid_signature() -> eyre::Result<()> { assert_eq!(block1.hash_slow(), block_with_peer.block.hash_slow()); // Verify the signature is from the unauthorized signer - let hash = sig_encode_hash(&block_with_peer.block.header); + let hash = sig_encode_hash(&block_with_peer.block); let recovered = block_with_peer.signature.recover_address_from_prehash(&hash).unwrap(); return recovered == unauthorized_signer.address(); } @@ -1712,7 +1712,7 @@ async fn signer_rotation() -> eyre::Result<()> { |event| { if let RollupManagerEvent::NewBlockReceived(block) = event { let signature = block.signature; - let hash = sig_encode_hash(&block.block.header); + let hash = sig_encode_hash(&block.block); // Verify that the block is signed by the first sequencer. let recovered_address = signature.recover_address_from_prehash(&hash).unwrap(); recovered_address == signer_1_address @@ -1760,7 +1760,7 @@ async fn signer_rotation() -> eyre::Result<()> { |event| { if let RollupManagerEvent::NewBlockReceived(block) = event { let signature = block.signature; - let hash = sig_encode_hash(&block.block.header); + let hash = sig_encode_hash(&block.block); let recovered_address = signature.recover_address_from_prehash(&hash).unwrap(); // Verify that the block is signed by the second sequencer. recovered_address == signer_2_address diff --git a/crates/primitives/src/signature.rs b/crates/primitives/src/signature.rs index 411847c9..855a6fe6 100644 --- a/crates/primitives/src/signature.rs +++ b/crates/primitives/src/signature.rs @@ -1,75 +1,59 @@ +use alloy_consensus::Header; use alloy_primitives::{ keccak256, private::{alloy_rlp, alloy_rlp::Encodable}, B256, U256, }; -use reth_primitives_traits::BlockHeader; use std::vec::Vec; /// Encode and hash the header for signature. The function is similar to `Header::encode` but skips /// the `extra_data` field. -pub fn sig_encode_hash(header: &H) -> B256 { +pub fn sig_encode_hash(header: &Header) -> B256 { let out = &mut Vec::new(); let list_header = alloy_rlp::Header { list: true, payload_length: sig_header_payload_length(header) }; list_header.encode(out); - header.parent_hash().encode(out); - header.ommers_hash().encode(out); - header.beneficiary().encode(out); - header.state_root().encode(out); - header.transactions_root().encode(out); - header.receipts_root().encode(out); - header.logs_bloom().encode(out); - header.difficulty().encode(out); - U256::from(header.number()).encode(out); - U256::from(header.gas_limit()).encode(out); - U256::from(header.gas_used()).encode(out); - header.timestamp().encode(out); - if let Some(mix_hash) = header.mix_hash() { - mix_hash.encode(out); - } else { - alloy_primitives::B256::ZERO.encode(out); - } - if let Some(nonce) = header.nonce() { - nonce.encode(out); - } else { - 0u64.encode(out); - } + header.parent_hash.encode(out); + header.ommers_hash.encode(out); + header.beneficiary.encode(out); + header.state_root.encode(out); + header.transactions_root.encode(out); + header.receipts_root.encode(out); + header.logs_bloom.encode(out); + header.difficulty.encode(out); + U256::from(header.number).encode(out); + U256::from(header.gas_limit).encode(out); + U256::from(header.gas_used).encode(out); + header.timestamp.encode(out); + header.mix_hash.encode(out); + header.nonce.encode(out); // Encode all the fork specific fields - if let Some(base_fee) = header.base_fee_per_gas() { - U256::from(base_fee).encode(out); + if let Some(ref base_fee) = header.base_fee_per_gas { + U256::from(*base_fee).encode(out); } keccak256(&out) } /// Returns the header payload length for signature. -fn sig_header_payload_length(header: &H) -> usize { +fn sig_header_payload_length(header: &Header) -> usize { let mut length = 0; - length += header.parent_hash().length(); - length += header.ommers_hash().length(); - length += header.beneficiary().length(); - length += header.state_root().length(); - length += header.transactions_root().length(); - length += header.receipts_root().length(); - length += header.logs_bloom().length(); - length += header.difficulty().length(); - length += U256::from(header.number()).length(); - length += U256::from(header.gas_limit()).length(); - length += U256::from(header.gas_used()).length(); - length += header.timestamp().length(); - if let Some(mix_hash) = header.mix_hash() { - length += mix_hash.length(); - } else { - length += alloy_primitives::B256::ZERO.length(); - } - if let Some(nonce) = header.nonce() { - length += nonce.length(); - } else { - length += 0u64.length(); - } + length += header.parent_hash.length(); + length += header.ommers_hash.length(); + length += header.beneficiary.length(); + length += header.state_root.length(); + length += header.transactions_root.length(); + length += header.receipts_root.length(); + length += header.logs_bloom.length(); + length += header.difficulty.length(); + length += U256::from(header.number).length(); + length += U256::from(header.gas_limit).length(); + length += U256::from(header.gas_used).length(); + length += header.timestamp.length(); + length += header.mix_hash.length(); + length += header.nonce.length(); - if let Some(base_fee) = header.base_fee_per_gas() { + if let Some(base_fee) = header.base_fee_per_gas { // Adding base fee length if it exists. length += U256::from(base_fee).length(); } diff --git a/crates/sequencer/tests/e2e.rs b/crates/sequencer/tests/e2e.rs index 92887f58..87cbbe78 100644 --- a/crates/sequencer/tests/e2e.rs +++ b/crates/sequencer/tests/e2e.rs @@ -558,7 +558,7 @@ async fn can_sequence_blocks_with_private_key_file() -> eyre::Result<()> { signature, })) = sequencer_events.next().await { - let hash = sig_encode_hash(&signed_block.header); + let hash = sig_encode_hash(&signed_block); let recovered_address = signature.recover_address_from_prehash(&hash)?; assert_eq!(recovered_address, expected_address); } else { @@ -652,7 +652,7 @@ async fn can_sequence_blocks_with_hex_key_file_without_prefix() -> eyre::Result< signature, }) = event { - let hash = sig_encode_hash(&signed_block.header); + let hash = sig_encode_hash(&signed_block); let recovered_address = signature.recover_address_from_prehash(&hash)?; assert_eq!(recovered_address, expected_address); break; diff --git a/crates/signer/src/future.rs b/crates/signer/src/future.rs index 98b5419f..9858b7a7 100644 --- a/crates/signer/src/future.rs +++ b/crates/signer/src/future.rs @@ -15,7 +15,7 @@ pub fn sign_block( Box::pin(async move { // TODO: Are we happy to sign the hash directly or do we want to use EIP-191 // (`signer.sign_message`)? - let hash = sig_encode_hash(&block.header); + let hash = sig_encode_hash(&block); let signature = signer.sign_hash(&hash).await?; Ok(SignerEvent::SignedBlock { block, signature }) }) diff --git a/crates/signer/src/lib.rs b/crates/signer/src/lib.rs index 9c1f8056..7682a5bd 100644 --- a/crates/signer/src/lib.rs +++ b/crates/signer/src/lib.rs @@ -152,7 +152,7 @@ mod tests { let (event_block, signature) = match event { SignerEvent::SignedBlock { block, signature } => (block, signature), }; - let hash = sig_encode_hash(&event_block.header); + let hash = sig_encode_hash(&event_block); let recovered_address = signature.recover_address_from_prehash(&hash).unwrap(); assert_eq!(event_block, block); From 31fbefbf85c38065587666926b2711adefd1c09a Mon Sep 17 00:00:00 2001 From: Morty Date: Thu, 11 Sep 2025 17:56:20 +0800 Subject: [PATCH 04/13] fix: use sig_encode_hash to calculate block hash --- crates/node/src/builder/network.rs | 54 ++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/crates/node/src/builder/network.rs b/crates/node/src/builder/network.rs index ef706bc5..585f4840 100644 --- a/crates/node/src/builder/network.rs +++ b/crates/node/src/builder/network.rs @@ -14,7 +14,9 @@ use reth_primitives_traits::BlockHeader; use reth_scroll_chainspec::ScrollChainSpec; use reth_scroll_primitives::ScrollPrimitives; use reth_transaction_pool::{PoolTransaction, TransactionPool}; +use rollup_node_primitives::sig_encode_hash; use rollup_node_signer::SignatureAsBytes; +use reth_primitives_traits::Header; use scroll_alloy_hardforks::ScrollHardforks; use scroll_db::{Database, DatabaseOperations}; use std::{fmt, fmt::Debug, path::PathBuf, sync::Arc}; @@ -187,8 +189,9 @@ impl ScrollHeaderTransform Result<(), HeaderTransformError> { let signature_bytes = std::mem::take(header.extra_data_mut()); let signature = parse_65b_signature(&signature_bytes)?; + let hash = sig_encode_hash(&header_to_alloy(header)); // Recover and verify signer - recover_and_verify_signer(&signature, header.hash_slow(), authorized_signer)?; + recover_and_verify_signer(&signature, hash, authorized_signer)?; // Store signature in database - persist_signature(self.db.clone(), header.hash_slow(), signature); + persist_signature(self.db.clone(), hash, signature); Ok(()) } @@ -247,15 +251,17 @@ impl, hash: B256, signature: Signature) { } }); } + +/// Convert a generic `BlockHeader` to `alloy_consensus::Header` +fn header_to_alloy(header: &H) -> Header { + Header { + parent_hash: header.parent_hash(), + ommers_hash: header.ommers_hash(), + beneficiary: header.beneficiary(), + state_root: header.state_root(), + transactions_root: header.transactions_root(), + receipts_root: header.receipts_root(), + logs_bloom: header.logs_bloom(), + difficulty: header.difficulty(), + number: header.number(), + gas_limit: header.gas_limit(), + gas_used: header.gas_used(), + timestamp: header.timestamp(), + extra_data: header.extra_data().clone(), + mix_hash: header.mix_hash().unwrap_or_default(), + nonce: header.nonce().unwrap_or_default(), + base_fee_per_gas: header.base_fee_per_gas(), + withdrawals_root: header.withdrawals_root(), + blob_gas_used: header.blob_gas_used(), + excess_blob_gas: header.excess_blob_gas(), + parent_beacon_block_root: header.parent_beacon_block_root(), + requests_hash: header.requests_hash(), + } +} From 20a00d13689969f5ce3f286bd5b1d8c2d3135d35 Mon Sep 17 00:00:00 2001 From: Morty Date: Thu, 11 Sep 2025 18:32:16 +0800 Subject: [PATCH 05/13] fix: ci --- crates/node/src/builder/network.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/node/src/builder/network.rs b/crates/node/src/builder/network.rs index 585f4840..327c2d4c 100644 --- a/crates/node/src/builder/network.rs +++ b/crates/node/src/builder/network.rs @@ -10,13 +10,12 @@ use reth_network::{ use reth_node_api::TxTy; use reth_node_builder::{components::NetworkBuilder, BuilderContext, FullNodeTypes}; use reth_node_types::NodeTypes; -use reth_primitives_traits::BlockHeader; +use reth_primitives_traits::{BlockHeader, Header}; use reth_scroll_chainspec::ScrollChainSpec; use reth_scroll_primitives::ScrollPrimitives; use reth_transaction_pool::{PoolTransaction, TransactionPool}; use rollup_node_primitives::sig_encode_hash; use rollup_node_signer::SignatureAsBytes; -use reth_primitives_traits::Header; use scroll_alloy_hardforks::ScrollHardforks; use scroll_db::{Database, DatabaseOperations}; use std::{fmt, fmt::Debug, path::PathBuf, sync::Arc}; From a15e0dd4122e96e018a4d6b1fc276a332ac6a728 Mon Sep 17 00:00:00 2001 From: Morty Date: Thu, 11 Sep 2025 18:34:25 +0800 Subject: [PATCH 06/13] rename value --- crates/node/src/builder/network.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/node/src/builder/network.rs b/crates/node/src/builder/network.rs index 327c2d4c..c7d6d24d 100644 --- a/crates/node/src/builder/network.rs +++ b/crates/node/src/builder/network.rs @@ -250,17 +250,17 @@ impl Date: Fri, 12 Sep 2025 09:07:18 +0800 Subject: [PATCH 07/13] fix using wrong hash when computing signature and loading signature from database in header transform --- crates/node/src/builder/network.rs | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/crates/node/src/builder/network.rs b/crates/node/src/builder/network.rs index c7d6d24d..e47c3445 100644 --- a/crates/node/src/builder/network.rs +++ b/crates/node/src/builder/network.rs @@ -206,13 +206,12 @@ impl ScrollHeaderTransform Result<(), HeaderTransformError> { let signature_bytes = std::mem::take(header.extra_data_mut()); let signature = parse_65b_signature(&signature_bytes)?; - let hash = sig_encode_hash(&header_to_alloy(header)); // Recover and verify signer - recover_and_verify_signer(&signature, hash, authorized_signer)?; + recover_and_verify_signer(&signature, header, authorized_signer)?; // Store signature in database - persist_signature(self.db.clone(), hash, signature); + persist_signature(self.db.clone(), header.hash_slow(), signature); Ok(()) } @@ -250,7 +249,7 @@ impl( signature: &Signature, - hash: B256, + header: &H, authorized_signer: Option
, ) -> Result { + let hash = sig_encode_hash(&header_to_alloy(header)); + // Recover signer from signature let signer = reth_primitives_traits::crypto::secp256k1::recover_signer(signature, hash) .map_err(|_| HeaderTransformError::RecoveryFailed)?; From 46cdc2c0119d66ddc7f5a4968cde6fccff73c2b1 Mon Sep 17 00:00:00 2001 From: Morty Date: Fri, 12 Sep 2025 13:24:18 +0800 Subject: [PATCH 08/13] feat: stop eth-wire block broadcast once we switch signer --- Cargo.lock | 1 + crates/network/Cargo.toml | 1 + crates/network/src/manager.rs | 45 +++++++++++++++++++++++++++++------ crates/node/src/args.rs | 5 ++++ crates/node/tests/e2e.rs | 1 + 5 files changed, 46 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 97c16531..7a124eb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11690,6 +11690,7 @@ dependencies = [ "reth-scroll-primitives", "reth-storage-api", "reth-tokio-util", + "rollup-node-primitives", "scroll-alloy-hardforks", "scroll-wire", "thiserror 2.0.16", diff --git a/crates/network/Cargo.toml b/crates/network/Cargo.toml index adc549fe..83537593 100644 --- a/crates/network/Cargo.toml +++ b/crates/network/Cargo.toml @@ -25,6 +25,7 @@ reth-tokio-util.workspace = true reth-scroll-chainspec.workspace = true reth-scroll-node.workspace = true reth-scroll-primitives.workspace = true +rollup-node-primitives.workspace = true scroll-alloy-hardforks.workspace = true scroll-wire.workspace = true diff --git a/crates/network/src/manager.rs b/crates/network/src/manager.rs index 4bc79e4a..9ad6835a 100644 --- a/crates/network/src/manager.rs +++ b/crates/network/src/manager.rs @@ -4,7 +4,7 @@ use super::{ BlockImportOutcome, BlockValidation, NetworkHandleMessage, NetworkManagerEvent, NewBlockWithPeer, ScrollNetworkHandle, }; -use alloy_primitives::{FixedBytes, Signature, B256, U128}; +use alloy_primitives::{Address, FixedBytes, Signature, B256, U128}; use futures::{FutureExt, Stream, StreamExt}; use reth_chainspec::EthChainSpec; use reth_eth_wire_types::NewBlock as EthWireNewBlock; @@ -17,6 +17,7 @@ use reth_scroll_node::ScrollNetworkPrimitives; use reth_scroll_primitives::ScrollBlock; use reth_storage_api::BlockNumReader as BlockNumReaderT; use reth_tokio_util::EventStream; +use rollup_node_primitives::sig_encode_hash; use scroll_alloy_hardforks::ScrollHardforks; use scroll_wire::{ NewBlock, ScrollWireConfig, ScrollWireEvent, ScrollWireManager, ScrollWireProtocolHandler, @@ -59,6 +60,8 @@ pub struct ScrollNetworkManager { pub blocks_seen: LruCache<(B256, Signature)>, /// The constant value that must be added to the block number to get the total difficulty. td_constant: U128, + /// The authorized signer for the network. + authorized_signer: Option
, } impl @@ -72,6 +75,7 @@ impl scroll_wire_config: ScrollWireConfig, eth_wire_listener: Option>>, td_constant: U128, + authorized_signer: Option
, ) -> Self { // Create the scroll-wire protocol handler. let (scroll_wire_handler, events) = ScrollWireProtocolHandler::new(scroll_wire_config); @@ -105,6 +109,7 @@ impl blocks_seen, eth_wire_listener, td_constant, + authorized_signer, } } } @@ -124,6 +129,7 @@ impl< events: UnboundedReceiver, eth_wire_listener: Option>>, td_constant: U128, + authorized_signer: Option
, ) -> Self { // Create the channel for sending messages to the network manager from the network handle. let (to_manager_tx, from_handle_rx) = mpsc::unbounded_channel(); @@ -143,6 +149,7 @@ impl< blocks_seen, eth_wire_listener, td_constant, + authorized_signer, } } @@ -169,13 +176,37 @@ impl< .filter_map(|(peer_id, blocks)| (!blocks.contains(&hash)).then_some(*peer_id)) .collect(); - let eth_wire_new_block = { - let td = compute_td(self.td_constant, block.block.header.number); - let mut eth_wire_block = block.block.clone(); - eth_wire_block.header.extra_data = block.signature.clone().into(); - EthWireNewBlock { block: eth_wire_block, td } + // TODO: remove this once we deprecate l2geth. + // Determine if we should announce via eth wire + let should_announce_eth_wire = if let Some(authorized_signer) = self.authorized_signer { + // Only announce if the block signature matches the authorized signer + let sig_hash = sig_encode_hash(&block.block.header); + if let Ok(signature) = Signature::from_raw(&block.signature) { + if let Ok(recovered_signer) = + reth_primitives_traits::crypto::secp256k1::recover_signer(&signature, sig_hash) + { + authorized_signer == recovered_signer + } else { + false + } + } else { + false + } + } else { + // If no authorized signer is set, always announce + true }; - self.inner_network_handle().eth_wire_announce_block(eth_wire_new_block, hash); + + // Announce via eth wire if allowed + if should_announce_eth_wire { + let eth_wire_new_block = { + let td = compute_td(self.td_constant, block.block.header.number); + let mut eth_wire_block = block.block.clone(); + eth_wire_block.header.extra_data = block.signature.clone().into(); + EthWireNewBlock { block: eth_wire_block, td } + }; + self.inner_network_handle().eth_wire_announce_block(eth_wire_new_block, hash); + } // Announce block to the filtered set of peers for peer_id in peers { diff --git a/crates/node/src/args.rs b/crates/node/src/args.rs index 01a1d3dd..827cad4a 100644 --- a/crates/node/src/args.rs +++ b/crates/node/src/args.rs @@ -233,12 +233,17 @@ impl ScrollRollupNodeConfig { .network_args .enable_eth_scroll_wire_bridge .then_some(ctx.network.eth_wire_block_listener().await?); + + // TODO: remove this once we deprecate l2geth. + let authorized_signer = self.network_args.effective_signer(chain_spec.chain().named()); + let scroll_network_manager = ScrollNetworkManager::from_parts( chain_spec.clone(), ctx.network.clone(), events, eth_wire_listener, td_constant(chain_spec.chain().named()), + authorized_signer, ); // On startup we replay the latest batch of blocks from the database as such we set the safe diff --git a/crates/node/tests/e2e.rs b/crates/node/tests/e2e.rs index 63986d7c..80fbd085 100644 --- a/crates/node/tests/e2e.rs +++ b/crates/node/tests/e2e.rs @@ -743,6 +743,7 @@ async fn can_bridge_blocks() { scroll_wire_config, None, Default::default(), + None, ) .await; let scroll_network_handle = scroll_network.handle(); From a532993866271060bc0b72a6226a8414ccebd583 Mon Sep 17 00:00:00 2001 From: Morty Date: Fri, 12 Sep 2025 14:24:24 +0800 Subject: [PATCH 09/13] fmt --- crates/node/src/builder/network.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/node/src/builder/network.rs b/crates/node/src/builder/network.rs index e47c3445..d86ffed2 100644 --- a/crates/node/src/builder/network.rs +++ b/crates/node/src/builder/network.rs @@ -182,9 +182,9 @@ impl Date: Fri, 12 Sep 2025 15:00:28 +0800 Subject: [PATCH 10/13] fmt --- crates/node/src/builder/network.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/node/src/builder/network.rs b/crates/node/src/builder/network.rs index d86ffed2..7c31efba 100644 --- a/crates/node/src/builder/network.rs +++ b/crates/node/src/builder/network.rs @@ -182,7 +182,7 @@ impl Date: Mon, 15 Sep 2025 04:26:05 +0800 Subject: [PATCH 11/13] fix: correctly encode signature when handling block import --- Cargo.lock | 1 + crates/engine/Cargo.toml | 1 + crates/engine/src/future/mod.rs | 4 +++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 7a124eb3..f30a75be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11627,6 +11627,7 @@ dependencies = [ "reth-testing-utils", "rollup-node-primitives", "rollup-node-providers", + "rollup-node-signer", "scroll-alloy-consensus", "scroll-alloy-hardforks", "scroll-alloy-network", diff --git a/crates/engine/Cargo.toml b/crates/engine/Cargo.toml index 80a15df6..318b885c 100644 --- a/crates/engine/Cargo.toml +++ b/crates/engine/Cargo.toml @@ -37,6 +37,7 @@ reth-scroll-engine-primitives = { git = "https://github.com/scroll-tech/reth.git # rollup-node rollup-node-primitives.workspace = true rollup-node-providers.workspace = true +rollup-node-signer.workspace = true # scroll scroll-network.workspace = true diff --git a/crates/engine/src/future/mod.rs b/crates/engine/src/future/mod.rs index c3825b29..699da685 100644 --- a/crates/engine/src/future/mod.rs +++ b/crates/engine/src/future/mod.rs @@ -6,6 +6,7 @@ use alloy_rpc_types_engine::{ ExecutionData, ExecutionPayloadV1, ForkchoiceState as AlloyForkchoiceState, ForkchoiceUpdated, PayloadStatusEnum, }; +use alloy_primitives::bytes::Bytes; use eyre::Result; use reth_scroll_engine_primitives::try_into_block; use reth_scroll_primitives::ScrollBlock; @@ -13,6 +14,7 @@ use rollup_node_primitives::{ BatchInfo, BlockInfo, ChainImport, L2BlockInfoWithL1Messages, MeteredFuture, ScrollPayloadAttributesWithBatchInfo, WithBlockNumber, }; +use rollup_node_signer::SignatureAsBytes; use scroll_alloy_hardforks::ScrollHardforks; use scroll_alloy_network::Scroll; use scroll_alloy_provider::ScrollEngineApi; @@ -236,7 +238,7 @@ where Some(BlockImportOutcome::valid_block( peer_id, head, - Into::>::into(signature).into(), + Bytes::copy_from_slice(&signature.sig_as_bytes()), )), PayloadStatusEnum::Valid, )), From c652fec5d401e709f5c94eb6c02b324ec85f220c Mon Sep 17 00:00:00 2001 From: Morty Date: Mon, 15 Sep 2025 14:46:09 +0800 Subject: [PATCH 12/13] cargo fmt --- crates/engine/src/future/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/engine/src/future/mod.rs b/crates/engine/src/future/mod.rs index 699da685..41ea27bf 100644 --- a/crates/engine/src/future/mod.rs +++ b/crates/engine/src/future/mod.rs @@ -1,12 +1,12 @@ use super::{payload::block_matches_attributes, EngineDriverError}; use crate::{api::*, ForkchoiceState}; +use alloy_primitives::bytes::Bytes; use alloy_provider::Provider; use alloy_rpc_types_engine::{ ExecutionData, ExecutionPayloadV1, ForkchoiceState as AlloyForkchoiceState, ForkchoiceUpdated, PayloadStatusEnum, }; -use alloy_primitives::bytes::Bytes; use eyre::Result; use reth_scroll_engine_primitives::try_into_block; use reth_scroll_primitives::ScrollBlock; From 8e7bd5d626c48cb6abd62c0533c98d4717f28df2 Mon Sep 17 00:00:00 2001 From: Morty Date: Mon, 15 Sep 2025 18:52:13 +0800 Subject: [PATCH 13/13] address comment --- crates/node/src/builder/network.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/node/src/builder/network.rs b/crates/node/src/builder/network.rs index a54c1b76..1ab94601 100644 --- a/crates/node/src/builder/network.rs +++ b/crates/node/src/builder/network.rs @@ -174,7 +174,7 @@ impl