Skip to content

Commit

Permalink
taiko geth auth api (#224)
Browse files Browse the repository at this point in the history
* introducing jwt secret handling

* getting txs list from taiko-geth
  • Loading branch information
mskrzypkows authored Feb 21, 2025
1 parent f8f540e commit 5c30076
Show file tree
Hide file tree
Showing 10 changed files with 244 additions and 17 deletions.
2 changes: 2 additions & 0 deletions Node/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion Node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,18 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
alloy = { version = "0.2", features = ["full", "node-bindings", "rlp", "rpc-types-beacon"] }
alloy = { version = "0.2", features = [
"full",
"node-bindings",
"rlp",
"rpc-types-beacon",
] }
alloy-rlp = "0.3"
tokio = { version = "1.38", features = ["full"] }
tracing = "0.1.40"
tracing-subscriber = "0.3"
jsonrpsee = { version = "0.24", features = ["http-client", "server"] }
jsonwebtoken = "9.3"
serde = { version = "1.0.217", features = ["derive"] }
serde_json = "1.0"
lazy_static = "1.4"
Expand Down Expand Up @@ -39,6 +45,7 @@ tree_hash = "0.6.0"
tree_hash_derive = "0.6.0"
reth-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "5dd5555c5c7d8e43420e273e7005b8af63a847a5" }
blst = "0.3"
http = "1.2"

[dev-dependencies]
mockall_double = "0.3"
Expand Down
15 changes: 10 additions & 5 deletions Node/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,6 @@ async fn main() -> Result<(), Error> {
let p2p = p2p_network::AVSp2p::new(p2p_to_node_tx.clone(), node_to_p2p_rx);
p2p.start(config.p2p_network_config).await;
}
let taiko = Arc::new(taiko::Taiko::new(
&config.taiko_proposer_url,
&config.taiko_driver_url,
config.taiko_chain_id,
));

let ethereum_l1 = Arc::new(ethereum_l1);
let mev_boost = mev_boost::MevBoost::new(
Expand All @@ -100,6 +95,16 @@ async fn main() -> Result<(), Error> {
.genesis_fork_version,
);

let jwt_secret_bytes = utils::file_operations::read_jwt_secret(&config.jwt_secret_file_path)?;
let taiko = Arc::new(taiko::Taiko::new(
&config.taiko_geth_url,
&config.taiko_driver_url,
config.taiko_chain_id,
config.rpc_client_timeout,
&jwt_secret_bytes,
ethereum_l1.execution_layer.get_preconfer_address(),
)?);

let block_proposed_event_checker =
BlockProposedEventReceiver::new(ethereum_l1.clone(), block_proposed_tx);
BlockProposedEventReceiver::start(block_proposed_event_checker);
Expand Down
33 changes: 33 additions & 0 deletions Node/src/taiko/l2_tx_lists.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,23 @@ pub fn decompose_pending_lists_json(json: Value) -> Result<RPCReplyL2TxLists, Er
Ok(rpc_reply)
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "PascalCase")]
struct PreBuiltTxList {
pub tx_list: Value,
estimated_gas_used: u64,
bytes_length: u64,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct PendingTxLists(Vec<PreBuiltTxList>);

pub fn decompose_pending_lists_json_from_geth(json: Value) -> Result<PendingTxLists, Error> {
let rpc_reply: PendingTxLists = serde_json::from_value(json)?;
Ok(rpc_reply)
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -73,4 +90,20 @@ mod tests {
assert_eq!(result.parent_meta_hash.len(), 32);
assert_eq!(result.parent_block_id, 1234);
}

#[test]
fn test_deserialize_pending_tx_lists() {
let json_data = serde_json::from_str(include_str!(
"../utils/tx_lists_test_response_from_geth.json"
))
.unwrap();
let pending_tx_lists = PendingTxLists::from(json_data);

println!("{:?}", pending_tx_lists);

assert_eq!(pending_tx_lists.0.len(), 1);
assert_eq!(pending_tx_lists.0[0].tx_list.as_array().unwrap().len(), 2);
assert_eq!(pending_tx_lists.0[0].estimated_gas_used, 42000);
assert_eq!(pending_tx_lists.0[0].bytes_length, 203);
}
}
65 changes: 57 additions & 8 deletions Node/src/taiko/mod.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,46 @@
use crate::utils::rpc_client::RpcClient;
#![allow(unused)] // TODO: remove this once using new rpc functions

use crate::utils::{rpc_client::RpcClient, types::*};
use anyhow::Error;
use serde_json::Value;
use std::time::Duration;
use tracing::debug;

pub mod l2_tx_lists;

pub struct Taiko {
rpc_proposer: RpcClient,
rpc_taiko_geth: RpcClient,
rpc_driver: RpcClient,
pub chain_id: u64,
preconfer_address: PreconferAddress,
}

impl Taiko {
pub fn new(proposer_url: &str, driver_url: &str, chain_id: u64) -> Self {
Self {
rpc_proposer: RpcClient::new(proposer_url),
pub fn new(
taiko_geth_url: &str,
driver_url: &str,
chain_id: u64,
rpc_client_timeout: Duration,
jwt_secret_bytes: &[u8],
preconfer_address: PreconferAddress,
) -> Result<Self, Error> {
Ok(Self {
rpc_taiko_geth: RpcClient::new_with_timeout_and_jwt(
taiko_geth_url,
rpc_client_timeout,
jwt_secret_bytes,
)?,
rpc_driver: RpcClient::new(driver_url),
chain_id,
}
preconfer_address,
})
}

// TODO: obsolete, remove this function
pub async fn get_pending_l2_tx_lists(&self) -> Result<l2_tx_lists::RPCReplyL2TxLists, Error> {
tracing::debug!("Getting L2 tx lists");
let result = l2_tx_lists::decompose_pending_lists_json(
self.rpc_proposer
self.rpc_taiko_geth
.call_method("RPC.GetL2TxLists", vec![])
.await
.map_err(|e| anyhow::anyhow!("Failed to get L2 tx lists: {}", e))?,
Expand All @@ -42,6 +59,30 @@ impl Taiko {
Ok(result)
}

pub async fn get_pending_l2_txs_from_taiko_geth(
&self,
) -> Result<l2_tx_lists::PendingTxLists, Error> {
let params = vec![
Value::String(format!("0x{}", hex::encode(self.preconfer_address))), // beneficiary address
Value::from(0x1dfd14000u64), // baseFee (8 gwei) - now as a number, not a string
Value::Number(30_000_000.into()), // blockMaxGasLimit
Value::Number(131_072.into()), // maxBytesPerTxList (128KB)
Value::Array(vec![]), // locals (empty array)
Value::Number(1.into()), // maxTransactionsLists
Value::Number(0.into()), // minTip
];

let result = self
.rpc_taiko_geth
.call_method("taikoAuth_txPoolContentWithMinTip", params)
.await
.map_err(|e| anyhow::anyhow!("Failed to get L2 tx lists: {}", e))?;

let tx_lists = l2_tx_lists::decompose_pending_lists_json_from_geth(result)
.map_err(|e| anyhow::anyhow!("Failed to decompose L2 tx lists: {}", e))?;
Ok(tx_lists)
}

fn print_number_of_received_txs(result: &l2_tx_lists::RPCReplyL2TxLists) {
if let Some(tx_lists) = result.tx_lists.as_array() {
let mut hashes = Vec::new();
Expand Down Expand Up @@ -141,7 +182,15 @@ mod test {
&format!("http://127.0.0.1:{}", port),
&format!("http://127.0.0.1:{}", port),
1,
);
Duration::from_secs(10),
&[
0xa6, 0xea, 0x92, 0x58, 0xca, 0x91, 0x2c, 0x59, 0x3b, 0x3e, 0x36, 0xee, 0x36, 0xc1,
0x7f, 0xe9, 0x74, 0x47, 0xf9, 0x20, 0xf5, 0xb3, 0x6a, 0x90, 0x74, 0x4d, 0x79, 0xd4,
0xf2, 0xd6, 0xae, 0x62,
],
PRECONFER_ADDRESS_ZERO,
)
.unwrap();
(rpc_server, taiko)
}
}
24 changes: 21 additions & 3 deletions Node/src/utils/config.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use p2p_network::generate_secp256k1;
use p2p_network::network::P2PNetworkConfig;
use std::time::Duration;
use tracing::{info, warn};

pub struct Config {
pub taiko_proposer_url: String,
pub taiko_geth_url: String,
pub taiko_driver_url: String,
pub avs_node_ecdsa_private_key: String,
pub mev_boost_url: String,
Expand All @@ -21,6 +22,8 @@ pub struct Config {
pub enable_p2p: bool,
pub enable_preconfirmation: bool,
pub always_push_lookahead: bool,
pub jwt_secret_file_path: String,
pub rpc_client_timeout: Duration,
}

#[derive(Debug)]
Expand Down Expand Up @@ -242,8 +245,17 @@ impl Config {
.parse::<bool>()
.expect("ALWAYS_PUSH_LOOKAHEAD must be a boolean");

let jwt_secret_file_path = std::env::var("JWT_SECRET_FILE_PATH")
.expect("JWT_SECRET_FILE_PATH env variable must be set");

let rpc_client_timeout = std::env::var("RPC_CLIENT_TIMEOUT_SEC")
.unwrap_or("10".to_string())
.parse::<u64>()
.expect("RPC_CLIENT_TIMEOUT_SEC must be a number");
let rpc_client_timeout = Duration::from_secs(rpc_client_timeout);

let config = Self {
taiko_proposer_url: std::env::var("TAIKO_PROPOSER_URL")
taiko_geth_url: std::env::var("TAIKO_GETH_URL")
.unwrap_or("http://127.0.0.1:1234".to_string()),
taiko_driver_url: std::env::var("TAIKO_DRIVER_URL")
.unwrap_or("http://127.0.0.1:1235".to_string()),
Expand All @@ -266,6 +278,8 @@ impl Config {
enable_p2p,
enable_preconfirmation,
always_push_lookahead,
jwt_secret_file_path,
rpc_client_timeout,
};

info!(
Expand All @@ -286,8 +300,10 @@ taiko chain id: {}
validator index: {}
enable p2p: {}
enable preconfirmation: {}
jwt secret file path: {}
rpc client timeout: {}
"#,
config.taiko_proposer_url,
config.taiko_geth_url,
config.taiko_driver_url,
config.mev_boost_url,
config.l1_ws_rpc_url,
Expand All @@ -302,6 +318,8 @@ enable preconfirmation: {}
config.validator_index,
config.enable_p2p,
config.enable_preconfirmation,
config.jwt_secret_file_path,
config.rpc_client_timeout.as_secs(),
);

config
Expand Down
12 changes: 12 additions & 0 deletions Node/src/utils/file_operations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use anyhow::Error;

pub fn read_jwt_secret(file_path: &str) -> Result<[u8; 32], Error> {
let secret = std::fs::read_to_string(file_path)
.map_err(|e| anyhow::anyhow!("Failed to read JWT secret from file: {}", e))?;
let secret_bytes = hex::decode(secret.strip_prefix("0x").unwrap())
.map_err(|e| anyhow::anyhow!("Failed to decode hex string: {}", e))?;
let secret_bytes: [u8; 32] = secret_bytes
.try_into()
.map_err(|e| anyhow::anyhow!("Failed to convert secret bytes to [u8; 32]: {:?}", e))?;
Ok(secret_bytes)
}
1 change: 1 addition & 0 deletions Node/src/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod bytes_tools;
pub mod config;
pub mod file_operations;
pub mod rpc_client;
pub mod rpc_server;
pub mod types;
60 changes: 60 additions & 0 deletions Node/src/utils/rpc_client.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@
use alloy::primitives::B256;
use anyhow::Error;
use http::{HeaderMap, HeaderValue};
use jsonrpsee::core::client::ClientT;
use jsonrpsee::http_client::{HttpClient, HttpClientBuilder};
use jsonwebtoken::{encode, Algorithm, EncodingKey, Header};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::time::Duration;

#[derive(Debug, Serialize, Deserialize)]
struct Claims {
iat: usize,
}

fn create_jwt_token(secret: &[u8]) -> Result<String, Box<dyn std::error::Error>> {
let claims = Claims {
iat: chrono::Utc::now().timestamp() as usize, // Current timestamp without adding duration
};

Ok(encode(
&Header::new(Algorithm::HS256),
&claims,
&EncodingKey::from_secret(secret),
)?)
}

pub struct RpcClient {
client: HttpClient,
}
Expand All @@ -21,6 +42,45 @@ impl RpcClient {
RpcClient { client }
}

/// Creates a new RpcClient with JWT authentication.
pub fn new_with_timeout_and_jwt(
url: &str,
timeout: Duration,
jwt_secret: &[u8],
) -> Result<Self, Error> {
if url.is_empty() {
return Err(anyhow::anyhow!("URL is empty"));
}

let jwt_secret_bytes: [u8; 32] = jwt_secret
.try_into()
.map_err(|e| anyhow::anyhow!("Invalid JWT secret: {e}"))?;
let jwt = B256::from_slice(&jwt_secret_bytes);

if jwt == B256::ZERO {
return Err(anyhow::anyhow!("JWT secret is illegal"));
}

let jwt_token = create_jwt_token(&jwt.0)
.map_err(|e| anyhow::anyhow!("Failed to create JWT token: {e}"))?;

let mut headers = HeaderMap::new();
headers.insert(
"authorization",
HeaderValue::from_str(&format!("Bearer {}", jwt_token)).map_err(|e| {
anyhow::anyhow!("Failed to create header value from jwt token: {e}")
})?,
);

let client = HttpClientBuilder::default()
.request_timeout(timeout)
.set_headers(headers)
.build(url)
.map_err(|e| anyhow::anyhow!("Failed to create HTTP client: {e}"))?;

Ok(Self { client })
}

pub async fn call_method(&self, method: &str, params: Vec<Value>) -> Result<Value, Error> {
self.client
.request(method, params)
Expand Down
Loading

0 comments on commit 5c30076

Please sign in to comment.