Skip to content

Commit 0fa60cf

Browse files
authored
fix: eth66 serve/persist signature (#312)
1 parent 3d400f7 commit 0fa60cf

File tree

8 files changed

+107
-23
lines changed

8 files changed

+107
-23
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/engine/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ reth-scroll-engine-primitives = { git = "https://github.com/scroll-tech/reth.git
3737
# rollup-node
3838
rollup-node-primitives.workspace = true
3939
rollup-node-providers.workspace = true
40+
rollup-node-signer.workspace = true
4041

4142
# scroll
4243
scroll-network.workspace = true

crates/engine/src/future/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use super::{payload::block_matches_attributes, EngineDriverError};
22
use crate::{api::*, ForkchoiceState};
33

4+
use alloy_primitives::bytes::Bytes;
45
use alloy_provider::Provider;
56
use alloy_rpc_types_engine::{
67
ExecutionData, ExecutionPayloadV1, ForkchoiceState as AlloyForkchoiceState, ForkchoiceUpdated,
@@ -13,6 +14,7 @@ use rollup_node_primitives::{
1314
BatchInfo, BlockInfo, ChainImport, L2BlockInfoWithL1Messages, MeteredFuture,
1415
ScrollPayloadAttributesWithBatchInfo, WithBlockNumber,
1516
};
17+
use rollup_node_signer::SignatureAsBytes;
1618
use scroll_alloy_hardforks::ScrollHardforks;
1719
use scroll_alloy_network::Scroll;
1820
use scroll_alloy_provider::ScrollEngineApi;
@@ -236,7 +238,7 @@ where
236238
Some(BlockImportOutcome::valid_block(
237239
peer_id,
238240
head,
239-
Into::<Vec<u8>>::into(signature).into(),
241+
Bytes::copy_from_slice(&signature.sig_as_bytes()),
240242
)),
241243
PayloadStatusEnum::Valid,
242244
)),

crates/network/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ reth-tokio-util.workspace = true
2525
reth-scroll-chainspec.workspace = true
2626
reth-scroll-node.workspace = true
2727
reth-scroll-primitives.workspace = true
28+
rollup-node-primitives.workspace = true
2829
scroll-alloy-hardforks.workspace = true
2930
scroll-wire.workspace = true
3031

crates/network/src/manager.rs

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use super::{
44
BlockImportOutcome, BlockValidation, NetworkHandleMessage, NetworkManagerEvent,
55
NewBlockWithPeer, ScrollNetworkHandle,
66
};
7-
use alloy_primitives::{FixedBytes, Signature, B256, U128};
7+
use alloy_primitives::{Address, FixedBytes, Signature, B256, U128};
88
use futures::{FutureExt, Stream, StreamExt};
99
use reth_chainspec::EthChainSpec;
1010
use reth_eth_wire_types::NewBlock as EthWireNewBlock;
@@ -17,6 +17,7 @@ use reth_scroll_node::ScrollNetworkPrimitives;
1717
use reth_scroll_primitives::ScrollBlock;
1818
use reth_storage_api::BlockNumReader as BlockNumReaderT;
1919
use reth_tokio_util::EventStream;
20+
use rollup_node_primitives::sig_encode_hash;
2021
use scroll_alloy_hardforks::ScrollHardforks;
2122
use scroll_wire::{
2223
NewBlock, ScrollWireConfig, ScrollWireEvent, ScrollWireManager, ScrollWireProtocolHandler,
@@ -59,6 +60,8 @@ pub struct ScrollNetworkManager<N, CS> {
5960
pub blocks_seen: LruCache<(B256, Signature)>,
6061
/// The constant value that must be added to the block number to get the total difficulty.
6162
td_constant: U128,
63+
/// The authorized signer for the network.
64+
authorized_signer: Option<Address>,
6265
}
6366

6467
impl<CS: ScrollHardforks + EthChainSpec + Send + Sync + 'static>
@@ -72,6 +75,7 @@ impl<CS: ScrollHardforks + EthChainSpec + Send + Sync + 'static>
7275
scroll_wire_config: ScrollWireConfig,
7376
eth_wire_listener: Option<EventStream<RethNewBlockWithPeer<ScrollBlock>>>,
7477
td_constant: U128,
78+
authorized_signer: Option<Address>,
7579
) -> Self {
7680
// Create the scroll-wire protocol handler.
7781
let (scroll_wire_handler, events) = ScrollWireProtocolHandler::new(scroll_wire_config);
@@ -105,6 +109,7 @@ impl<CS: ScrollHardforks + EthChainSpec + Send + Sync + 'static>
105109
blocks_seen,
106110
eth_wire_listener,
107111
td_constant,
112+
authorized_signer,
108113
}
109114
}
110115
}
@@ -124,6 +129,7 @@ impl<
124129
events: UnboundedReceiver<ScrollWireEvent>,
125130
eth_wire_listener: Option<EventStream<RethNewBlockWithPeer<ScrollBlock>>>,
126131
td_constant: U128,
132+
authorized_signer: Option<Address>,
127133
) -> Self {
128134
// Create the channel for sending messages to the network manager from the network handle.
129135
let (to_manager_tx, from_handle_rx) = mpsc::unbounded_channel();
@@ -143,6 +149,7 @@ impl<
143149
blocks_seen,
144150
eth_wire_listener,
145151
td_constant,
152+
authorized_signer,
146153
}
147154
}
148155

@@ -169,13 +176,37 @@ impl<
169176
.filter_map(|(peer_id, blocks)| (!blocks.contains(&hash)).then_some(*peer_id))
170177
.collect();
171178

172-
let eth_wire_new_block = {
173-
let td = compute_td(self.td_constant, block.block.header.number);
174-
let mut eth_wire_block = block.block.clone();
175-
eth_wire_block.header.extra_data = block.signature.clone().into();
176-
EthWireNewBlock { block: eth_wire_block, td }
179+
// TODO: remove this once we deprecate l2geth.
180+
// Determine if we should announce via eth wire
181+
let should_announce_eth_wire = if let Some(authorized_signer) = self.authorized_signer {
182+
// Only announce if the block signature matches the authorized signer
183+
let sig_hash = sig_encode_hash(&block.block.header);
184+
if let Ok(signature) = Signature::from_raw(&block.signature) {
185+
if let Ok(recovered_signer) =
186+
reth_primitives_traits::crypto::secp256k1::recover_signer(&signature, sig_hash)
187+
{
188+
authorized_signer == recovered_signer
189+
} else {
190+
false
191+
}
192+
} else {
193+
false
194+
}
195+
} else {
196+
// If no authorized signer is set, always announce
197+
true
177198
};
178-
self.inner_network_handle().eth_wire_announce_block(eth_wire_new_block, hash);
199+
200+
// Announce via eth wire if allowed
201+
if should_announce_eth_wire {
202+
let eth_wire_new_block = {
203+
let td = compute_td(self.td_constant, block.block.header.number);
204+
let mut eth_wire_block = block.block.clone();
205+
eth_wire_block.header.extra_data = block.signature.clone().into();
206+
EthWireNewBlock { block: eth_wire_block, td }
207+
};
208+
self.inner_network_handle().eth_wire_announce_block(eth_wire_new_block, hash);
209+
}
179210

180211
// Announce block to the filtered set of peers
181212
for peer_id in peers {

crates/node/src/args.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,12 +246,17 @@ impl ScrollRollupNodeConfig {
246246
.network_args
247247
.enable_eth_scroll_wire_bridge
248248
.then_some(ctx.network.eth_wire_block_listener().await?);
249+
250+
// TODO: remove this once we deprecate l2geth.
251+
let authorized_signer = self.network_args.effective_signer(chain_spec.chain().named());
252+
249253
let scroll_network_manager = ScrollNetworkManager::from_parts(
250254
chain_spec.clone(),
251255
ctx.network.clone(),
252256
events,
253257
eth_wire_listener,
254258
td_constant(chain_spec.chain().named()),
259+
authorized_signer,
255260
);
256261

257262
// On startup we replay the latest batch of blocks from the database as such we set the safe

crates/node/src/builder/network.rs

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ use reth_network::{
1010
use reth_node_api::TxTy;
1111
use reth_node_builder::{components::NetworkBuilder, BuilderContext, FullNodeTypes};
1212
use reth_node_types::NodeTypes;
13-
use reth_primitives_traits::BlockHeader;
13+
use reth_primitives_traits::{BlockHeader, Header};
1414
use reth_scroll_chainspec::ScrollChainSpec;
1515
use reth_scroll_primitives::ScrollPrimitives;
1616
use reth_transaction_pool::{PoolTransaction, TransactionPool};
17+
use rollup_node_primitives::sig_encode_hash;
1718
use rollup_node_signer::SignatureAsBytes;
1819
use scroll_alloy_hardforks::ScrollHardforks;
1920
use scroll_db::{Database, DatabaseOperations};
@@ -165,13 +166,14 @@ impl<H: BlockHeader, ChainSpec: EthChainSpec + ScrollHardforks + Debug + Send +
165166
if !self.chain_spec.is_euclid_v2_active_at_timestamp(header.timestamp()) {
166167
return header;
167168
}
169+
168170
// TODO: remove this once we deprecated l2geth
169171
// Validate and process signature
170-
171172
if let Err(err) = self.validate_and_store_signature(&mut header, self.signer) {
172173
debug!(
173174
target: "scroll::network::response_header_transform",
174-
"Header signature persistence failed, header hash: {:?}, error: {}",
175+
"Header signature persistence failed, block number: {:?}, header hash: {:?}, error: {}",
176+
header.number(),
175177
header.hash_slow(), err
176178
);
177179
}
@@ -190,7 +192,7 @@ impl<ChainSpec: ScrollHardforks + Debug + Send + Sync> ScrollHeaderTransform<Cha
190192
let signature = parse_65b_signature(&signature_bytes)?;
191193

192194
// Recover and verify signer
193-
recover_and_verify_signer(&signature, header.hash_slow(), authorized_signer)?;
195+
recover_and_verify_signer(&signature, header, authorized_signer)?;
194196

195197
// Store signature in database
196198
persist_signature(self.db.clone(), header.hash_slow(), signature);
@@ -231,15 +233,17 @@ impl<H: BlockHeader, ChainSpec: EthChainSpec + ScrollHardforks + Debug + Send +
231233
}
232234

233235
// read the signature from the rollup node.
234-
let block_hash = header.hash_slow();
236+
let hash = header.hash_slow();
237+
235238
let signature = self
236239
.db
237-
.get_signature(block_hash)
240+
.get_signature(hash)
238241
.await
239242
.inspect_err(|e| {
240243
warn!(target: "scroll::network::request_header_transform",
241-
"Failed to get block signature from database, header hash: {:?}, error: {}",
242-
header.hash_slow(),
244+
"Failed to get block signature from database, block number: {:?}, header hash: {:?}, error: {}",
245+
header.number(),
246+
hash,
243247
HeaderTransformError::DatabaseError(e.to_string())
244248
)
245249
})
@@ -249,32 +253,42 @@ impl<H: BlockHeader, ChainSpec: EthChainSpec + ScrollHardforks + Debug + Send +
249253
// If we have a signature in the database and it matches configured signer then add it
250254
// to the extra data field
251255
if let Some(sig) = signature {
252-
if let Err(err) = recover_and_verify_signer(&sig, header.hash_slow(), self.signer) {
256+
if let Err(err) = recover_and_verify_signer(&sig, &header, self.signer) {
253257
warn!(
254258
target: "scroll::network::request_header_transform",
255-
"Found invalid signature(different from the hardcoded signer) for header hash: {:?}, sig: {:?}, error: {}",
256-
header.hash_slow(),
259+
"Found invalid signature(different from the hardcoded signer={:?}) for block number: {:?}, header hash: {:?}, sig: {:?}, error: {}",
260+
self.signer,
261+
header.number(),
262+
hash,
257263
sig.to_string(),
258264
err
259265
);
260266
} else {
261267
*header.extra_data_mut() = sig.sig_as_bytes().into();
262268
}
269+
} else {
270+
debug!(
271+
target: "scroll::network::request_header_transform",
272+
"No signature found in database for block number: {:?}, header hash: {:?}",
273+
header.number(),
274+
hash,
275+
);
263276
}
264277

265278
header
266279
}
267280
}
268281

269282
/// Recover signer from signature and verify authorization.
270-
fn recover_and_verify_signer(
283+
fn recover_and_verify_signer<H: BlockHeader>(
271284
signature: &Signature,
272-
hash: B256,
285+
header: &H,
273286
authorized_signer: Option<Address>,
274287
) -> Result<Address, HeaderTransformError> {
288+
let hash = sig_encode_hash(&header_to_alloy(header));
289+
275290
// Recover signer from signature
276-
let signer = signature
277-
.recover_address_from_prehash(&hash)
291+
let signer = reth_primitives_traits::crypto::secp256k1::recover_signer(signature, hash)
278292
.map_err(|_| HeaderTransformError::RecoveryFailed)?;
279293

280294
// Verify signer is authorized
@@ -310,3 +324,30 @@ fn persist_signature(db: Arc<Database>, hash: B256, signature: Signature) {
310324
}
311325
});
312326
}
327+
328+
/// Convert a generic `BlockHeader` to `alloy_consensus::Header`
329+
fn header_to_alloy<H: BlockHeader>(header: &H) -> Header {
330+
Header {
331+
parent_hash: header.parent_hash(),
332+
ommers_hash: header.ommers_hash(),
333+
beneficiary: header.beneficiary(),
334+
state_root: header.state_root(),
335+
transactions_root: header.transactions_root(),
336+
receipts_root: header.receipts_root(),
337+
logs_bloom: header.logs_bloom(),
338+
difficulty: header.difficulty(),
339+
number: header.number(),
340+
gas_limit: header.gas_limit(),
341+
gas_used: header.gas_used(),
342+
timestamp: header.timestamp(),
343+
extra_data: header.extra_data().clone(),
344+
mix_hash: header.mix_hash().unwrap_or_default(),
345+
nonce: header.nonce().unwrap_or_default(),
346+
base_fee_per_gas: header.base_fee_per_gas(),
347+
withdrawals_root: header.withdrawals_root(),
348+
blob_gas_used: header.blob_gas_used(),
349+
excess_blob_gas: header.excess_blob_gas(),
350+
parent_beacon_block_root: header.parent_beacon_block_root(),
351+
requests_hash: header.requests_hash(),
352+
}
353+
}

crates/node/tests/e2e.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,7 @@ async fn can_bridge_blocks() {
746746
scroll_wire_config,
747747
None,
748748
Default::default(),
749+
None,
749750
)
750751
.await;
751752
let scroll_network_handle = scroll_network.handle();

0 commit comments

Comments
 (0)