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

L2 builder preparation #34

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions bin/reth/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ where
/// Injects a raw transaction into the node tx pool via RPC server
pub async fn inject_tx(&mut self, raw_tx: Bytes) -> EthResult<B256> {
let eth_api = self.inner.eth_api();
println!("Dani debug: Why not called? {:?}", raw_tx); // -> Not called most prob. because it is called during actual L2 execution. So the call to advance_block() in our main() will call it.
eth_api.send_raw_transaction(raw_tx).await
}

Expand Down
1 change: 1 addition & 0 deletions crates/e2e-test-utils/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ where
let mut chain = Vec::with_capacity(length as usize);
for i in 0..length {
let raw_tx = tx_generator(i).await;
println!("Dani debug: advance()'s inject_tx");
let tx_hash = self.rpc.inject_tx(raw_tx).await?;
let (payload, eth_attr) = self.advance_block(vec![], attributes_generator).await?;
let block_hash = payload.block().hash();
Expand Down
43 changes: 41 additions & 2 deletions crates/rpc/rpc-eth-api/src/helpers/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use std::{fmt, ops::Deref, sync::Arc};
use alloy_dyn_abi::TypedData;
use futures::Future;
use reth_primitives::{
Address, BlockId, Bytes, FromRecoveredPooledTransaction, IntoRecoveredTransaction, Receipt,
SealedBlockWithSenders, TransactionMeta, TransactionSigned, TxHash, TxKind, B256, U256,
Address, BlockId, Bytes, FromRecoveredPooledTransaction, IntoRecoveredTransaction, Receipt,Signature,
SealedBlockWithSenders, TransactionMeta, TransactionSigned, TxHash, TxKind, B256, U256, PooledTransactionsElementEcRecovered, PooledTransactionsElement, transaction::extract_chain_id,
};
use reth_provider::{BlockReaderIdExt, ReceiptProvider, TransactionsProvider};
use reth_rpc_eth_types::{
Expand All @@ -23,6 +23,7 @@ use reth_rpc_types::{
};
use reth_rpc_types_compat::transaction::from_recovered_with_block_context;
use reth_transaction_pool::{TransactionOrigin, TransactionPool};
use revm_primitives::{alloy_primitives::private::alloy_rlp::{self, Decodable}};

use super::EthSigner;

Expand Down Expand Up @@ -231,11 +232,30 @@ pub trait EthTransactions: LoadTransaction {
}
}

fn get_chain_id(tx: &PooledTransactionsElementEcRecovered) -> alloy_rlp::Result<Option<u64>> {
// Get the signature from the transaction
let signature = tx.signature();

// Get the v value from the signature
let v = signature.v(None); // We pass None as we don't know the chain_id yet

// Use the public extract_chain_id function
let (_, chain_id) = extract_chain_id(v)?;

Ok(chain_id)
}

/// Decodes and recovers the transaction and submits it to the pool.
///
/// Returns the hash of the transaction.
fn send_raw_transaction(&self, tx: Bytes) -> impl Future<Output = EthResult<B256>> + Send {
async move {

// It seems that both the 8545 RPC server (L1) and 10110 (L2 dedicated RPC) is routing into here BUT the send ether (sendATxnToL2Rpc.py) execution is happening on the correct chain.
// We (as builders) somehow gotta:
// 1. recognize the source (?) -> If L2 rpc/port then route towards the builder and not yet build L2 blocks
// 2. If block builder is full (or profitable, or some other sophisticated mechanism (?)) then flush towards TaikoL1 as calldata (or blob), then the 'BlockProposed' event is what will trigger the L2 execution.
println!("Dani debug: Raw txn bytes:{:?}", tx);
Comment on lines +240 to +245
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Brechtpd Could you please have a look on the above comments:

  1. Is it the correct understanding ? Is the comment valid ?
  2. Currently i'm trying to distinguish based on chain_id, tho i could not find a way to extract it from Signature Maybe it is not even the best approach to distinguish tho seems obvious ?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would guess ideally the node is configured differently in some way between L1 and L2. With L2 having a somewhat custom payload builder likely, and then instead of propegating the block through the network like done on L1, it would instead propose the block to the gwyneth smart contract (probably something related to the engine api, but haven't looked up the normal flow for this).

On the other hand, we can also just make it easy for now and just collect the tx here and propose the block to the smart contract when it's configured as an L2 node and then we can see later. Normally, the block building wil be done through something like rbuilder, so reth itself only really needs to be able to sync with a given tx list (and the internal payload builder would only be a fallback path).

// On optimism, transactions are forwarded directly to the sequencer to be included in
// blocks that it builds.
if let Some(client) = self.raw_tx_forwarder().as_ref() {
Expand All @@ -244,6 +264,23 @@ pub trait EthTransactions: LoadTransaction {
}

let recovered = recover_raw_transaction(tx)?;

match Self::get_chain_id(&recovered) {
Ok(maybe_chain_id) => {
if let Some(chain_id) = maybe_chain_id {
println!("Transaction is on network with chain ID: {}", chain_id);
// Perform your special logic here based on chain_id
} else {
println!("Transaction is pre-EIP-155 (no chain ID)");
// Handle pre-EIP-155 transactions (no chain ID)
}
},
Err(e) => {
eprintln!("Error decoding chain ID: {:?}", e);
// Handle RLP decoding error
}
}

let pool_transaction =
<Self::Pool as TransactionPool>::Transaction::from_recovered_pooled_transaction(
recovered,
Expand All @@ -252,6 +289,8 @@ pub trait EthTransactions: LoadTransaction {
// submit the transaction to the pool with a `Local` origin
let hash =
self.pool().add_transaction(TransactionOrigin::Local, pool_transaction).await?;

println!("Dani debug: TXN hash:{:?}", hash);

Ok(hash)
}
Expand Down
69 changes: 69 additions & 0 deletions packages/protocol/scripts/L2_txn_simulation/sendATxnToL2Rpc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from web3 import Web3
from eth_abi import encode
import argparse

RPC_URL_L2 = 'http://127.0.0.1:' # Anything is fine for now as long as we dont have the L2 network, but if we have we can automate nonce and gas settings
w3_taiko_l2 = Web3(Web3.HTTPProvider(RPC_URL_L2))

# Some pre-loaded ETH addresses from Kurtosis private network (NO secret, no harm to use for private testnets!)
sender_addresses = ['0x8943545177806ED17B9F23F0a21ee5948eCaa776']
sender_pks = ['bcdf20249abf0ed6d944c0288fad489e33f66b3960d9e6229c1cd214ed3bbe31']

receiver = '0xf93Ee4Cf8c6c40b329b0c0626F28333c132CF241' # This address also has pre-loaded ETH addresses

parser = argparse.ArgumentParser()

parser.add_argument("-p", "--port", help="port on localhost",
type=str, required=True)
# parser.add_argument("-c", "--chainid", help="l2 chainId",
# type=int, required=True)

transaction_list = []

if __name__ == "__main__":
args = parser.parse_args()
port = args.port
w3_taiko_l2 = Web3(Web3.HTTPProvider(RPC_URL_L2+port))
chainId = 167010

# Build the new tx list
idx = 0
for sender in sender_addresses:
# Build the tx
transaction = {
'chainId': chainId,
'from': sender,
'to': receiver,
'value': w3_taiko_l2.to_wei('1', 'ether'),
'nonce': w3_taiko_l2.eth.get_transaction_count(sender),
'gas': 200000,
'maxFeePerGas': 2000000000, # w3_taiko_l2.eth.gas_price or something
'maxPriorityFeePerGas': 1000000000,
}

# Debug prints of balance
# # Get the balance
# balance_wei = w3_taiko_l2.eth.get_balance(sender)

# # Convert balance from Wei to Ether
# balance_eth = w3_taiko_l2.from_wei(balance_wei, 'ether')
# print("Balance before:", balance_eth)

# 2. Sign tx with a private key
signed_txn = w3_taiko_l2.eth.account.sign_transaction(transaction, sender_pks[idx])

# print("RawTransaction:")
# print(signed_txn.rawTransaction)
print("RawTransaction.hex():")
print(signed_txn.rawTransaction.hex())

txn_hash = w3_taiko_l2.eth.send_raw_transaction(signed_txn.rawTransaction)
print("Txn hash:")
print(txn_hash.hex())

# # Get the balance
# balance_wei = w3_taiko_l2.eth.get_balance(sender)

# # Convert balance from Wei to Ether
# balance_eth = w3_taiko_l2.from_wei(balance_wei, 'ether')
# print("Balance after:", balance_eth)
Loading