Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple L2s #7

Merged
merged 8 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@
Block explorer: http://127.0.0.1:64003
```

```
chain_id: 167011
name: Gwyneth-2
rpc: http://127.0.0.1:32006
Currency: ETH
Block explorer: http://127.0.0.1:64005
```

```
chain_id: 160010
name: Gwyneth L1
Expand All @@ -35,6 +43,16 @@

Rabby/Brave wallet works, but some issues with nonces so you may have to manually input the correct nonce.

# How to add extra layer2s ?

In order to add extra layer 2 networks, you need to increase the the `NUM_L2_CHAINS` in the main function [here](https://github.com/taikoxyz/gwyneth/blob/5f6d3a6ffcf0ea359e4c52bbd94a251aef28b54c/bin/reth/src/main.rs#L15). (Later on it will be a configurational setting - no code !)

If you want infrastructure support too, namingly:

1. Exposing the jspn rpc port to the host machine (since everything is running in Docker with Kurtosis), you need to specify as a config param like [here](https://github.com/taikoxyz/gwyneth/blob/5f6d3a6ffcf0ea359e4c52bbd94a251aef28b54c/packages/protocol/scripts/confs/network_params.yaml#L6). (By default, if you dont specify this param, the first Layer2 port - which is 10110 - will be exposed to the host anyways. You only need to add this param if you are exposing more than 1 ports to the outter world.)

Check failure on line 52 in README.md

View workflow job for this annotation

GitHub Actions / codespell

outter ==> outer
2. Blockscout support: [Here](https://github.com/taikoxyz/gwyneth/blob/5f6d3a6ffcf0ea359e4c52bbd94a251aef28b54c/packages/protocol/scripts/confs/network_params.yaml#L16) you can see a pattern, how to shoot up blockscout service too. If you want 3 layer2 explorers, just use the service name `blockscout_l2_3`.


# reth

[![CI status](https://github.com/paradigmxyz/reth/workflows/unit/badge.svg)][gh-ci]
Expand Down
63 changes: 38 additions & 25 deletions bin/reth/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#![allow(missing_docs)]

// We use jemalloc for performance reasons.
#[cfg(all(feature = "jemalloc", unix))]
#[global_allocator]
Expand All @@ -12,44 +11,58 @@ use reth_node_builder::{NodeBuilder, NodeConfig, NodeHandle};
use reth_node_ethereum::EthereumNode;
use reth_tasks::TaskManager;

const BASE_CHAIN_ID: u64 = gwyneth::exex::BASE_CHAIN_ID; // Base chain ID for L2s
const NUM_L2_CHAINS: u64 = 2; // Number of L2 chains to create. Todo: Shall come from config */

fn main() -> eyre::Result<()> {
reth::cli::Cli::parse_args().run(|builder, _| async move {
let tasks = TaskManager::current();
let exec = tasks.executor();

let network_config = NetworkArgs {
discovery: DiscoveryArgs { disable_discovery: true, ..DiscoveryArgs::default() },
..NetworkArgs::default()
};

let chain_spec = ChainSpecBuilder::default()
.chain(gwyneth::exex::CHAIN_ID.into())
.genesis(
serde_json::from_str(include_str!(
"../../../crates/ethereum/node/tests/assets/genesis.json"
))
.unwrap(),
)
.cancun_activated()
.build();
let mut gwyneth_nodes = Vec::new();

for i in 0..NUM_L2_CHAINS {
let chain_id = BASE_CHAIN_ID + i; // Increment by 1 for each L2

let node_config = NodeConfig::test()
.with_chain(chain_spec.clone())
.with_network(network_config.clone())
.with_unused_ports()
.with_rpc(RpcServerArgs::default().with_unused_ports().with_static_l2_rpc_ip_and_port(chain_spec.chain.id()));
let chain_spec = ChainSpecBuilder::default()
.chain(chain_id.into())
.genesis(
serde_json::from_str(include_str!(
"../../../crates/ethereum/node/tests/assets/genesis.json"
))
.unwrap(),
)
.cancun_activated()
.build();

let NodeHandle { node: gwyneth_node, node_exit_future: _ } =
NodeBuilder::new(node_config.clone())
.gwyneth_node(exec.clone(), chain_spec.chain.id())
.node(GwynethNode::default())
.launch()
.await?;
let node_config = NodeConfig::test()
.with_chain(chain_spec.clone())
.with_network(network_config.clone())
.with_unused_ports()
.with_rpc(
RpcServerArgs::default()
.with_unused_ports()
.with_static_l2_rpc_ip_and_port(chain_id)
);

let NodeHandle { node: gwyneth_node, node_exit_future: _ } =
NodeBuilder::new(node_config.clone())
.gwyneth_node(exec.clone(), chain_spec.chain.id())
.node(GwynethNode::default())
.launch()
.await?;

gwyneth_nodes.push(gwyneth_node);
}

let handle = builder
.node(EthereumNode::default())
.install_exex("Rollup", move |ctx| async {
Ok(gwyneth::exex::Rollup::new(ctx, gwyneth_node).await?.start())
Ok(gwyneth::exex::Rollup::new(ctx, gwyneth_nodes).await?.start())
})
.launch()
.await?;
Expand All @@ -69,4 +82,4 @@ mod tests {
#[command(flatten)]
args: T,
}
}
}
14 changes: 10 additions & 4 deletions crates/gwyneth/src/engine_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ use reth_rpc_types::{
use std::{marker::PhantomData, net::Ipv4Addr};
use reth_rpc_builder::constants;

use crate::exex::BASE_CHAIN_ID;

/// Helper for engine api operations
pub struct EngineApiContext<E> {
pub canonical_stream: CanonStateNotificationStream,
Expand Down Expand Up @@ -119,12 +121,16 @@ pub trait RpcServerArgsExEx {
impl RpcServerArgsExEx for RpcServerArgs {
fn with_static_l2_rpc_ip_and_port(mut self, chain_id: u64) -> Self {
self.http = true;
// On the instance the program is running, we wanna have 10111 exposed as the (exex) L2's
// RPC port.
self.http_addr = Ipv4Addr::new(0, 0, 0, 0).into();
self.http_port = 10110u16;
self.ws_port = 10111u16;

// Calculate HTTP and WS ports based on chain_id
let port_offset = (chain_id - BASE_CHAIN_ID) as u16;
self.http_port = 10110 + (port_offset * 100);
self.ws_port = 10111 + (port_offset * 100);

// Set IPC path
self.ipcpath = format!("{}-{}", constants::DEFAULT_IPC_ENDPOINT, chain_id);

self
}
}
70 changes: 41 additions & 29 deletions crates/gwyneth/src/exex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use reth_transaction_pool::{
use RollupContract::{BlockProposed, RollupContractEvents};

const ROLLUP_CONTRACT_ADDRESS: Address = address!("9fCF7D13d10dEdF17d0f24C62f0cf4ED462f65b7");
pub const CHAIN_ID: u64 = 167010;
pub const BASE_CHAIN_ID: u64 = 167010;
const INITIAL_TIMESTAMP: u64 = 1710338135;

pub type GwynethFullNode = FullNode<
Expand Down Expand Up @@ -69,47 +69,44 @@ sol!(RollupContract, "TaikoL1.json");

pub struct Rollup<Node: reth_node_api::FullNodeComponents> {
ctx: ExExContext<Node>,
node: GwynethFullNode,
engine_api: EngineApiContext<GwynethEngineTypes>,
nodes: Vec<GwynethFullNode>,
engine_apis: Vec<EngineApiContext<GwynethEngineTypes>>,
}

impl<Node: reth_node_api::FullNodeComponents> Rollup<Node> {
pub async fn new(ctx: ExExContext<Node>, node: GwynethFullNode) -> eyre::Result<Self> {
let engine_api = EngineApiContext {
engine_api_client: node.auth_server_handle().http_client(),
canonical_stream: node.provider.canonical_state_stream(),
_marker: PhantomData::<GwynethEngineTypes>,
};
Ok(Self { ctx, node, /* payload_event_stream, */ engine_api })
pub async fn new(ctx: ExExContext<Node>, nodes: Vec<GwynethFullNode>) -> eyre::Result<Self> {
let mut engine_apis = Vec::new();
for node in &nodes {
let engine_api = EngineApiContext {
engine_api_client: node.auth_server_handle().http_client(),
canonical_stream: node.provider.canonical_state_stream(),
_marker: PhantomData::<GwynethEngineTypes>,
};
engine_apis.push(engine_api);
}
Ok(Self { ctx, nodes, /* payload_event_stream, */ engine_apis })
}

pub async fn start(mut self) -> eyre::Result<()> {
// Process all new chain state notifications
while let Some(notification) = self.ctx.notifications.recv().await {
if let Some(reverted_chain) = notification.reverted_chain() {
self.revert(&reverted_chain)?;
}

if let Some(committed_chain) = notification.committed_chain() {
self.commit(&committed_chain).await?;
for i in 0..self.nodes.len() {
self.commit(&committed_chain, i).await?;
}
self.ctx.events.send(ExExEvent::FinishedHeight(committed_chain.tip().number))?;
}
}

Ok(())
}

/// Process a new chain commit.
///
/// This function decodes all transactions to the rollup contract into events, executes the
/// corresponding actions and inserts the results into the database.
pub async fn commit(&mut self, chain: &Chain) -> eyre::Result<()> {
pub async fn commit(&mut self, chain: &Chain, node_idx: usize) -> eyre::Result<()> {
let events = decode_chain_into_rollup_events(chain);
for (block, _, event) in events {
// TODO: Don't emit ProposeBlock event but directely
// read the function call RollupContractCalls to extract Txs
// let _call = RollupContractCalls::abi_decode(tx.input(), true)?;

if let RollupContractEvents::BlockProposed(BlockProposed {
blockId: block_number,
meta,
Expand All @@ -120,6 +117,19 @@ impl<Node: reth_node_api::FullNodeComponents> Rollup<Node> {
let transactions: Vec<TransactionSigned> = decode_transactions(&meta.txList);
println!("transactions: {:?}", transactions);

let all_transactions: Vec<TransactionSigned> = decode_transactions(&meta.txList);
let node_chain_id = BASE_CHAIN_ID + (node_idx as u64);

let filtered_transactions: Vec<TransactionSigned> = all_transactions
.into_iter()
.filter(|tx| tx.chain_id() == Some(node_chain_id))
.collect();

if filtered_transactions.len() == 0 {
println!("no transactions for chain: {}", node_chain_id);
continue;
}

let attrs = GwynethPayloadAttributes {
inner: EthPayloadAttributes {
timestamp: block.timestamp,
Expand All @@ -128,7 +138,7 @@ impl<Node: reth_node_api::FullNodeComponents> Rollup<Node> {
withdrawals: Some(vec![]),
parent_beacon_block_root: Some(B256::ZERO),
},
transactions: Some(transactions.clone()),
transactions: Some(filtered_transactions.clone()),
gas_limit: None,
};

Expand All @@ -148,15 +158,16 @@ impl<Node: reth_node_api::FullNodeComponents> Rollup<Node> {
let payload_id = builder_attrs.inner.payload_id();
let parrent_beacon_block_root =
builder_attrs.inner.parent_beacon_block_root.unwrap();

// trigger new payload building draining the pool
self.node.payload_builder.new_payload(builder_attrs).await.unwrap();
self.nodes[node_idx].payload_builder.new_payload(builder_attrs).await.unwrap();

// wait for the payload builder to have finished building
let mut payload =
EthBuiltPayload::new(payload_id, SealedBlock::default(), U256::ZERO);
loop {
let result = self.node.payload_builder.best_payload(payload_id).await;
let result = self.nodes[node_idx].payload_builder.best_payload(payload_id).await;

// TODO: There seems to be no result when there's an empty tx list
if let Some(result) = result {
if let Ok(new_payload) = result {
payload = new_payload;
Expand All @@ -174,11 +185,12 @@ impl<Node: reth_node_api::FullNodeComponents> Rollup<Node> {
}
break;
}

// trigger resolve payload via engine api
self.engine_api.get_payload_v3_value(payload_id).await?;
self.engine_apis[node_idx].get_payload_v3_value(payload_id).await?;

// submit payload to engine api
let block_hash = self
.engine_api
let block_hash = self.engine_apis[node_idx]
.submit_payload(
payload.clone(),
parrent_beacon_block_root,
Expand All @@ -188,7 +200,7 @@ impl<Node: reth_node_api::FullNodeComponents> Rollup<Node> {
.await?;

// trigger forkchoice update via engine api to commit the block to the blockchain
self.engine_api.update_forkchoice(block_hash, block_hash).await?;
self.engine_apis[node_idx].update_forkchoice(block_hash, block_hash).await?;
}
}

Expand Down
4 changes: 3 additions & 1 deletion packages/protocol/scripts/confs/network_params.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@ participants:
el_image: taiko_reth
cl_type: lighthouse
cl_image: sigp/lighthouse:latest
el_extra_params: ["--num_of_l2s", "2"]
cl_extra_params: [--always-prepare-payload, --prepare-payload-lookahead, "12000"]
- el_type: reth
el_image: taiko_reth
cl_type: teku
cl_image: consensys/teku:latest
el_extra_params: ["--num_of_l2s", "2"]
network_params:
network_id: '160010'
additional_services:
- blockscout
- blockscout_l2_1
- blockscout_l2_2
port_publisher:
nat_exit_ip: KURTOSIS_IP_ADDR_PLACEHOLDER
el:
Expand Down
Loading