Skip to content

Commit 3f60642

Browse files
tomip01LeanSerrailitteriavilagaston9MegaRedHand
authored
feat(l2): based P2P (#2999)
**Motivation** This PR follows #2931 . We implement some basic functionality to communicate L2 based nodes via P2P. **Description** - Add new capability to the RLPx called `Based`. - Add new Message `NewBlock`. - Behaves similar to the message `Transactions`. - Every interval we look to the new blocks produced and send them to the peer. - Add this message to the allowed ones to be broadcasted via the P2P network. - When receiving this message we implemented a queue to be able to receive them in disorder. Once a continuos interval of blocks is in the queue we store them in order. - Add new message `BatchSealed` - Every interval we look in the `store_rollup` if a new batch has been sealed and then we send it to the peer. - Add this message to the allowed ones to be broadcasted via the P2P network. - This two new messages are signed by the lead sequencer who proposed the blocks and the batches. Every node must verify this signature correspond to the lead sequencer - Change `BlockFetcher` to not add a block received via the L1 if it already has been received via P2P, and vice versa. - Add a new `SequencingStatus`: `Syncing`. It is for nodes that are not up to date to the last committed batch. **How to test** Read the `Run Locally` section from `crates/l2/based/README.md` to run 3 nodes and register 2 of them as Sequencers. It is important that you assign different values in the nodes: - `--http.port <PORT>` - `--committer.l1-private-key <PRIVATE_KEY>` - `--proof-coordinator.port <PORT>` - `--p2p.port <P2P_PORT>` - `--discovery.port <PORT>` > [!TIP] > To enrich the review, I strongly suggest you read the documentation in `crates/l2/based/docs`. --------- Co-authored-by: Leandro Serra <leandro.serra@lambdaclass.com> Co-authored-by: ilitteri <ilitteri@fi.uba.ar> Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Co-authored-by: Avila Gastón <72628438+avilagaston9@users.noreply.github.com> Co-authored-by: Tomás Grüner <47506558+MegaRedHand@users.noreply.github.com> Co-authored-by: fkrause98 <fkrausear@gmail.com> Co-authored-by: Francisco Krause Arnim <56402156+fkrause98@users.noreply.github.com>
1 parent 4a3a5ae commit 3f60642

40 files changed

+1520
-128
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.

cmd/ethrex/ethrex.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,12 @@ async fn main() -> eyre::Result<()> {
116116

117117
cfg_if::cfg_if! {
118118
if #[cfg(feature = "dev")] {
119+
119120
use ethrex::initializers::init_dev_network;
120121

121122
init_dev_network(&opts, &store, tracker.clone()).await;
122123
} else {
123124
use ethrex::initializers::init_network;
124-
125125
if opts.p2p_enabled {
126126
init_network(
127127
&opts,
@@ -134,6 +134,7 @@ async fn main() -> eyre::Result<()> {
134134
store.clone(),
135135
tracker.clone(),
136136
blockchain.clone(),
137+
None
137138
)
138139
.await;
139140
} else {

cmd/ethrex/initializers.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use ethrex_p2p::{
99
kademlia::KademliaTable,
1010
network::{P2PContext, public_key_from_signing_key},
1111
peer_handler::PeerHandler,
12+
rlpx::l2::l2_connection::P2PBasedContext,
1213
sync_manager::SyncManager,
1314
types::{Node, NodeRecord},
1415
};
@@ -150,6 +151,7 @@ pub async fn init_network(
150151
store: Store,
151152
tracker: TaskTracker,
152153
blockchain: Arc<Blockchain>,
154+
based_context: Option<P2PBasedContext>,
153155
) {
154156
if opts.dev {
155157
error!("Binary wasn't built with The feature flag `dev` enabled.");
@@ -169,6 +171,7 @@ pub async fn init_network(
169171
store,
170172
blockchain,
171173
get_client_version(),
174+
based_context,
172175
);
173176

174177
context.set_fork_id().await.expect("Set fork id");

cmd/ethrex/l2/command.rs

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use ethrex_l2_common::l1_messages::get_l1_message_hash;
2121
use ethrex_l2_common::state_diff::StateDiff;
2222
use ethrex_l2_sdk::call_contract;
2323
use ethrex_p2p::network::peer_table;
24+
use ethrex_p2p::rlpx::l2::l2_connection::P2PBasedContext;
2425
use ethrex_rpc::{
2526
EthClient, clients::beacon::BeaconClient, types::block_identifier::BlockIdentifier,
2627
};
@@ -178,11 +179,17 @@ impl Command {
178179
l2::initializers::init_metrics(&opts.node_opts, tracker.clone());
179180
}
180181

182+
let l2_sequencer_cfg =
183+
SequencerConfig::try_from(opts.sequencer_opts).inspect_err(|err| {
184+
error!("{err}");
185+
})?;
186+
let cancellation_token = CancellationToken::new();
187+
181188
// TODO: This should be handled differently, the current problem
182189
// with using opts.node_opts.p2p_enabled is that with the removal
183190
// of the l2 feature flag, p2p_enabled is set to true by default
184191
// prioritizing the L1 UX.
185-
if opts.sequencer_opts.based {
192+
if l2_sequencer_cfg.based.enabled {
186193
init_network(
187194
&opts.node_opts,
188195
&network,
@@ -194,19 +201,23 @@ impl Command {
194201
store.clone(),
195202
tracker,
196203
blockchain.clone(),
204+
Some(P2PBasedContext {
205+
store_rollup: rollup_store.clone(),
206+
// TODO: The Web3Signer refactor introduced a limitation where the committer key cannot be accessed directly because the signer could be either Local or Remote.
207+
// The Signer enum cannot be used in the P2PBasedContext struct due to cyclic dependencies between the l2-rpc and p2p crates.
208+
// As a temporary solution, a dummy committer key is used until a proper mechanism to utilize the Signer enum is implemented.
209+
// This should be replaced with the Signer enum once the refactor is complete.
210+
committer_key: Arc::new(
211+
SecretKey::from_slice(&hex::decode("385c546456b6a603a1cfcaa9ec9494ba4832da08dd6bcf4de9a71e4a01b74924").expect("Invalid committer key"))
212+
.expect("Failed to create committer key"),
213+
),
214+
}),
197215
)
198216
.await;
199217
} else {
200218
info!("P2P is disabled");
201219
}
202220

203-
let l2_sequencer_cfg =
204-
SequencerConfig::try_from(opts.sequencer_opts).inspect_err(|err| {
205-
error!("{err}");
206-
})?;
207-
208-
let cancellation_token = CancellationToken::new();
209-
210221
let l2_sequencer = ethrex_l2::start_l2(
211222
store,
212223
rollup_store,

cmd/ethrex/l2/options.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ impl TryFrom<SequencerOptions> for SequencerConfig {
182182
validium: opts.validium,
183183
},
184184
based: BasedConfig {
185-
based: opts.based,
185+
enabled: opts.based,
186186
state_updater: StateUpdaterConfig {
187187
sequencer_registry: opts
188188
.based_opts

crates/blockchain/blockchain.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -772,11 +772,17 @@ impl Blockchain {
772772
}
773773

774774
/// Marks the node's chain as up to date with the current chain
775-
/// Once the initial sync has taken place, the node will be consireded as sync
775+
/// Once the initial sync has taken place, the node will be considered as sync
776776
pub fn set_synced(&self) {
777777
self.is_synced.store(true, Ordering::Relaxed);
778778
}
779779

780+
/// Marks the node's chain as not up to date with the current chain.
781+
/// This will be used when the node is one batch or more behind the current chain.
782+
pub fn set_not_synced(&self) {
783+
self.is_synced.store(false, Ordering::Relaxed);
784+
}
785+
780786
/// Returns whether the node's chain is up to date with the current chain
781787
/// This will be true if the initial sync has already taken place and does not reflect whether there is an ongoing sync process
782788
/// The node should accept incoming p2p transactions if this method returns true
@@ -807,8 +813,13 @@ impl Blockchain {
807813
})
808814
}
809815
Transaction::EIP7702Transaction(itx) => P2PTransaction::EIP7702Transaction(itx),
810-
Transaction::PrivilegedL2Transaction(itx) => {
811-
P2PTransaction::PrivilegedL2Transaction(itx)
816+
// Exclude privileged transactions as they are only created
817+
// by the lead sequencer. In the future, they might get gossiped
818+
// like the rest.
819+
Transaction::PrivilegedL2Transaction(_) => {
820+
return Err(StoreError::Custom(
821+
"Privileged Transactions are not supported in P2P".to_string(),
822+
));
812823
}
813824
};
814825

crates/common/types/batch.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::H256;
44

55
use super::BlobsBundle;
66

7-
#[derive(Clone, Serialize, Deserialize)]
7+
#[derive(Clone, Serialize, Deserialize, Debug, Default)]
88
pub struct Batch {
99
pub number: u64,
1010
pub first_block: u64,

crates/common/types/transaction.rs

Lines changed: 41 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::{cmp::min, fmt::Display};
22

33
use bytes::Bytes;
4-
use ethereum_types::{Address, H256, U256};
4+
use ethereum_types::{Address, H256, Signature, U256};
55
use keccak_hash::keccak;
66
pub use mempool::MempoolTransaction;
77
use secp256k1::{Message, ecdsa::RecoveryId};
@@ -904,7 +904,11 @@ impl Transaction {
904904
.encode_field(&0u8)
905905
.finish(),
906906
}
907-
recover_address(&tx.r, &tx.s, signature_y_parity, &Bytes::from(buf))
907+
let mut sig = [0u8; 65];
908+
sig[..32].copy_from_slice(&tx.r.to_big_endian());
909+
sig[32..64].copy_from_slice(&tx.s.to_big_endian());
910+
sig[64] = signature_y_parity as u8;
911+
recover_address_from_message(Signature::from_slice(&sig), &Bytes::from(buf))
908912
}
909913
Transaction::EIP2930Transaction(tx) => {
910914
let mut buf = vec![self.tx_type() as u8];
@@ -918,12 +922,11 @@ impl Transaction {
918922
.encode_field(&tx.data)
919923
.encode_field(&tx.access_list)
920924
.finish();
921-
recover_address(
922-
&tx.signature_r,
923-
&tx.signature_s,
924-
tx.signature_y_parity,
925-
&Bytes::from(buf),
926-
)
925+
let mut sig = [0u8; 65];
926+
sig[..32].copy_from_slice(&tx.signature_r.to_big_endian());
927+
sig[32..64].copy_from_slice(&tx.signature_s.to_big_endian());
928+
sig[64] = tx.signature_y_parity as u8;
929+
recover_address_from_message(Signature::from_slice(&sig), &Bytes::from(buf))
927930
}
928931
Transaction::EIP1559Transaction(tx) => {
929932
let mut buf = vec![self.tx_type() as u8];
@@ -938,12 +941,11 @@ impl Transaction {
938941
.encode_field(&tx.data)
939942
.encode_field(&tx.access_list)
940943
.finish();
941-
recover_address(
942-
&tx.signature_r,
943-
&tx.signature_s,
944-
tx.signature_y_parity,
945-
&Bytes::from(buf),
946-
)
944+
let mut sig = [0u8; 65];
945+
sig[..32].copy_from_slice(&tx.signature_r.to_big_endian());
946+
sig[32..64].copy_from_slice(&tx.signature_s.to_big_endian());
947+
sig[64] = tx.signature_y_parity as u8;
948+
recover_address_from_message(Signature::from_slice(&sig), &Bytes::from(buf))
947949
}
948950
Transaction::EIP4844Transaction(tx) => {
949951
let mut buf = vec![self.tx_type() as u8];
@@ -960,12 +962,11 @@ impl Transaction {
960962
.encode_field(&tx.max_fee_per_blob_gas)
961963
.encode_field(&tx.blob_versioned_hashes)
962964
.finish();
963-
recover_address(
964-
&tx.signature_r,
965-
&tx.signature_s,
966-
tx.signature_y_parity,
967-
&Bytes::from(buf),
968-
)
965+
let mut sig = [0u8; 65];
966+
sig[..32].copy_from_slice(&tx.signature_r.to_big_endian());
967+
sig[32..64].copy_from_slice(&tx.signature_s.to_big_endian());
968+
sig[64] = tx.signature_y_parity as u8;
969+
recover_address_from_message(Signature::from_slice(&sig), &Bytes::from(buf))
969970
}
970971
Transaction::EIP7702Transaction(tx) => {
971972
let mut buf = vec![self.tx_type() as u8];
@@ -981,12 +982,11 @@ impl Transaction {
981982
.encode_field(&tx.access_list)
982983
.encode_field(&tx.authorization_list)
983984
.finish();
984-
recover_address(
985-
&tx.signature_r,
986-
&tx.signature_s,
987-
tx.signature_y_parity,
988-
&Bytes::from(buf),
989-
)
985+
let mut sig = [0u8; 65];
986+
sig[..32].copy_from_slice(&tx.signature_r.to_big_endian());
987+
sig[32..64].copy_from_slice(&tx.signature_s.to_big_endian());
988+
sig[64] = tx.signature_y_parity as u8;
989+
recover_address_from_message(Signature::from_slice(&sig), &Bytes::from(buf))
990990
}
991991
Transaction::PrivilegedL2Transaction(tx) => Ok(tx.from),
992992
}
@@ -1188,25 +1188,27 @@ impl Transaction {
11881188
}
11891189
}
11901190

1191-
pub fn recover_address(
1192-
signature_r: &U256,
1193-
signature_s: &U256,
1194-
signature_y_parity: bool,
1191+
pub fn recover_address_from_message(
1192+
signature: Signature,
11951193
message: &Bytes,
11961194
) -> Result<Address, secp256k1::Error> {
1197-
// Create signature
1198-
let signature_bytes = [signature_r.to_big_endian(), signature_s.to_big_endian()].concat();
1199-
let signature = secp256k1::ecdsa::RecoverableSignature::from_compact(
1200-
&signature_bytes,
1201-
RecoveryId::from_i32(signature_y_parity as i32)?, // cannot fail
1202-
)?;
12031195
// Hash message
1204-
let msg_digest: [u8; 32] = Keccak256::new_with_prefix(message.as_ref())
1196+
let payload: [u8; 32] = Keccak256::new_with_prefix(message.as_ref())
12051197
.finalize()
12061198
.into();
1199+
recover_address(signature, H256::from_slice(&payload))
1200+
}
1201+
1202+
pub fn recover_address(signature: Signature, payload: H256) -> Result<Address, secp256k1::Error> {
1203+
// Create signature
1204+
let signature_bytes = signature.to_fixed_bytes();
1205+
let signature = secp256k1::ecdsa::RecoverableSignature::from_compact(
1206+
&signature_bytes[..64],
1207+
RecoveryId::from_i32(signature_bytes[64] as i32)?, // cannot fail
1208+
)?;
12071209
// Recover public key
1208-
let public =
1209-
secp256k1::SECP256K1.recover_ecdsa(&Message::from_digest(msg_digest), &signature)?;
1210+
let public = secp256k1::SECP256K1
1211+
.recover_ecdsa(&Message::from_digest(payload.to_fixed_bytes()), &signature)?;
12101212
// Hash public key to obtain address
12111213
let hash = Keccak256::new_with_prefix(&public.serialize_uncompressed()[1..]).finalize();
12121214
Ok(Address::from_slice(&hash[12..]))

crates/l2/based/README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,10 @@ cargo run --release --manifest-path ../../Cargo.toml --bin ethrex -- l2 init \
187187
--state-updater.sequencer-registry $ETHREX_DEPLOYER_SEQUENCER_REGISTRY_ADDRESS \
188188
--l1.on-chain-proposer-address $ETHREX_COMMITTER_ON_CHAIN_PROPOSER_ADDRESS \
189189
--l1.bridge-address $ETHREX_WATCHER_BRIDGE_ADDRESS \
190-
--based
190+
--based \
191+
--p2p.enabled \
192+
--p2p.port 30303 \
193+
--discovery.port 30303
191194
```
192195

193196
After running this command, the node will start syncing with the L1 and will be able to follow the lead Sequencer.
@@ -204,6 +207,12 @@ After running this command, the node will start syncing with the L1 and will be
204207
> - `--datadir`
205208
> - `--committer-l1-private-key`
206209
> - `--proof-coordinator-l1-private-key`
210+
> - `--p2p.port`
211+
> - `--discovery.port`
212+
>
213+
> Also, once a node has booted you can add it to the newer nodes for the P2P communication with:
214+
>
215+
> `--bootnodes <[ENODES]>`
207216
208217
### 3. Becoming a Sequencer
209218

crates/l2/based/block_fetcher.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ impl GenServer for BlockFetcher {
155155
handle: &GenServerHandle<Self>,
156156
mut state: Self::State,
157157
) -> CastResponse<Self> {
158-
if let SequencerStatus::Following = state.sequencer_state.status().await {
158+
if let SequencerStatus::Syncing = state.sequencer_state.status().await {
159159
let _ = fetch(&mut state).await.inspect_err(|err| {
160160
error!("Block Fetcher Error: {err}");
161161
});

0 commit comments

Comments
 (0)