From cecb3e839160d9d047bbfb4f67c5354d9bf91011 Mon Sep 17 00:00:00 2001 From: martines3000 Date: Mon, 7 Apr 2025 17:31:05 +0200 Subject: [PATCH 01/15] feat: challenger work in progress --- .gitignore | 1 + Cargo.lock | 81 ++++- Cargo.toml | 5 + bin/taiyi-challenger/Cargo.toml | 31 ++ bin/taiyi-challenger/src/main.rs | 298 ++++++++++++++++++ .../src/preconf_request_data.rs | 53 ++++ 6 files changed, 461 insertions(+), 8 deletions(-) create mode 100644 bin/taiyi-challenger/Cargo.toml create mode 100644 bin/taiyi-challenger/src/main.rs create mode 100644 bin/taiyi-challenger/src/preconf_request_data.rs diff --git a/.gitignore b/.gitignore index c9665775..e2e72cfe 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ crates/sp1-interactive-fraud-proof/*.bin # e2e 1-lighthouse-geth-0-63 el_cl_genesis_data +*.db \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 81229adb..344880dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2025,6 +2025,26 @@ dependencies = [ "serde", ] +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "bincode_derive", + "serde", + "unty", +] + +[[package]] +name = "bincode_derive" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" +dependencies = [ + "virtue", +] + [[package]] name = "bindgen" version = "0.70.1" @@ -6886,6 +6906,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" +[[package]] +name = "redb" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0a72cd7140de9fc3e318823b883abf819c20d478ec89ce880466dc2ef263c6" +dependencies = [ + "libc", +] + [[package]] name = "redox_syscall" version = "0.5.8" @@ -7917,7 +7946,7 @@ version = "4.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed3585aebe731e76b8f3549cccbaff34de3a58cbf22af074445a168941ef5d18" dependencies = [ - "bincode", + "bincode 1.3.3", "bytemuck", "clap", "elf", @@ -7956,7 +7985,7 @@ version = "4.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "430d6534e76cbd20e01b066c290bfce7663be5230a9c83cef49a5390c2af89e6" dependencies = [ - "bincode", + "bincode 1.3.3", "cbindgen", "cc", "cfg-if", @@ -8012,7 +8041,7 @@ version = "4.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a87155cf3b2ce1021150b02114b02f0f9c5f2e9304061b667e7de2beb22ab016" dependencies = [ - "bincode", + "bincode 1.3.3", "ctrlc", "prost", "serde", @@ -8061,7 +8090,7 @@ version = "4.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e4b2747ae411bca4ba0dd4112779246b101ac7e8f70afc1a33cf8ee003980d7" dependencies = [ - "bincode", + "bincode 1.3.3", "elliptic-curve 0.13.8", "serde", "sp1-primitives", @@ -8073,7 +8102,7 @@ version = "4.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7939f86891aa5995fa863abf296645a5ad4611d9598a8101389f85c624d41043" dependencies = [ - "bincode", + "bincode 1.3.3", "hex", "lazy_static", "num-bigint 0.4.6", @@ -8092,7 +8121,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3414cf7ef669ae417adfb0e78f871f9bb9c04bc208243645bcc8ca4437cd7c63" dependencies = [ "anyhow", - "bincode", + "bincode 1.3.3", "clap", "dirs", "downloader", @@ -8247,7 +8276,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc32b4b8833f849a7083f204261403711e27c9cfb958c95ad06af6666361bdd3" dependencies = [ "anyhow", - "bincode", + "bincode 1.3.3", "bindgen", "cc", "cfg-if", @@ -8279,7 +8308,7 @@ dependencies = [ "anyhow", "async-trait", "backoff", - "bincode", + "bincode 1.3.3", "cfg-if", "dirs", "futures", @@ -8854,6 +8883,30 @@ dependencies = [ "types", ] +[[package]] +name = "taiyi-challenger" +version = "0.1.4" +dependencies = [ + "alloy-contract", + "alloy-eips 0.12.6", + "alloy-primitives 0.8.25", + "alloy-provider 0.12.6", + "alloy-signer 0.12.6", + "alloy-signer-local 0.12.6", + "alloy-sol-types", + "bincode 2.0.1", + "clap", + "eyre", + "futures-util", + "redb", + "serde", + "serde_json", + "taiyi-primitives", + "tokio", + "tracing", + "tracing-subscriber", +] + [[package]] name = "taiyi-cli" version = "0.1.0" @@ -9918,6 +9971,12 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + [[package]] name = "url" version = "2.5.4" @@ -10002,6 +10061,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "virtue" +version = "0.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" + [[package]] name = "vsss-rs" version = "4.3.8" diff --git a/Cargo.toml b/Cargo.toml index 225d1614..2f01f61e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" members = [ "bin/taiyi", "bin/taiyi-boost", + "bin/taiyi-challenger", "bin/taiyi-cli", "bin/taiyi-fraud-proof-cli", "crates/cli", @@ -25,6 +26,7 @@ default-members = [ "bin/taiyi", "bin/taiyi-boost", "bin/taiyi-cli", + "bin/taiyi-challenger", "bin/taiyi-fraud-proof-cli", "crates/cli", "crates/underwriter", @@ -78,6 +80,7 @@ alloy-rpc-types = { version = "0.12", features = [ alloy-network = { version = "0.12", default-features = false } alloy-provider = { version = "0.12", features = [ "reqwest", + "ws", ], default-features = false } alloy-signer = { version = "0.12", default-features = false } alloy-signer-local = { version = "0.12", features = ["mnemonic"] } @@ -147,6 +150,8 @@ ssz_types = "0.10" http-body-util = "0.1.2" tower = "0.5.2" sha2 = "0.10.8" +redb = "2.4.0" +bincode = "2.0.1" [patch.crates-io] # SHA2 diff --git a/bin/taiyi-challenger/Cargo.toml b/bin/taiyi-challenger/Cargo.toml new file mode 100644 index 00000000..ab892728 --- /dev/null +++ b/bin/taiyi-challenger/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "taiyi-challenger" +version = { workspace = true } +edition = { workspace = true } + +[dependencies] +alloy-contract = { workspace = true } +alloy-eips = { workspace = true } +alloy-primitives = { workspace = true } +alloy-provider = { workspace = true } +alloy-signer = { workspace = true } +alloy-signer-local = { workspace = true } +alloy-sol-types = { workspace = true } + +tracing = { workspace = true } +tracing-subscriber = { workspace = true } +bincode = { workspace = true } + +clap = { workspace = true } +eyre = { workspace = true } +futures-util = { workspace = true } +redb = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +tokio = { workspace = true } + +taiyi-primitives = { workspace = true } + +[[bin]] +name = "taiyi-challenger" +path = "src/main.rs" diff --git a/bin/taiyi-challenger/src/main.rs b/bin/taiyi-challenger/src/main.rs new file mode 100644 index 00000000..348a686a --- /dev/null +++ b/bin/taiyi-challenger/src/main.rs @@ -0,0 +1,298 @@ +use std::{collections::HashSet, sync::Arc}; + +use alloy_eips::{merge::SLOT_DURATION_SECS, BlockNumberOrTag}; +use alloy_primitives::{hex, Address}; +use alloy_provider::{Provider, ProviderBuilder, WsConnect}; +use alloy_signer::k256::{self}; +use alloy_sol_types::sol; +use clap::Parser; +use futures_util::StreamExt; +use redb::{Database, TableDefinition}; +use taiyi_primitives::{PreconfRequestTypeA, PreconfRequestTypeB}; +use tracing::info; + +mod preconf_request_data; +use preconf_request_data::{Bincode, PreconfRequestData}; + +// TODO: Change to use correct types +const PRECONF_TABLE: TableDefinition>> = + TableDefinition::new("preconf"); +const CHALLENGE_TABLE: TableDefinition>> = + TableDefinition::new("challenge"); + +// TODO: Read from context ? +const GENESIS_TIMESTAMP: u64 = 1_655_733_600; + +pub fn get_slot_from_timestamp(timestamp: u64, genesis_timestamp: u64) -> u64 { + (timestamp - genesis_timestamp) / SLOT_DURATION_SECS +} + +#[derive(Parser, Clone)] +struct Opts { + /// execution_client_url + #[clap(long = "execution_client_url", default_value = "http://localhost:63970")] + execution_client_url: String, + /// execution_client_url + #[clap(long = "execution_client_ws_url", default_value = "ws://localhost:63971")] + execution_client_ws_url: String, + /// finalization_window + #[clap(long = "finalization_window", default_value = "32")] + finalization_window: u64, + + /// Private key to sign transactions + #[clap(long = "private_key")] + private_key: String, + /// Taiyi challenger contract address + #[clap(long = "taiyi_challenger_address")] + taiyi_challenger_address: Address, +} + +sol! { + #[sol(rpc)] + contract TaiyiInteractiveChallenger { + #[derive(Debug)] + struct PreconfRequestAType { + string[] txs; + string tipTx; + uint256 slot; + uint256 sequenceNum; + address signer; + } + + #[derive(Debug)] + struct BlockspaceAllocation { + uint256 gasLimit; + address sender; + address recipient; + uint256 deposit; + uint256 tip; + uint256 targetSlot; + uint256 blobCount; + } + + #[derive(Debug)] + struct PreconfRequestBType { + BlockspaceAllocation blockspaceAllocation; + bytes blockspaceAllocationSignature; + bytes underwriterSignedBlockspaceAllocation; + bytes rawTx; + bytes underwriterSignedRawTx; + } + + + #[derive(Debug)] + function createChallengeAType( + PreconfRequestAType calldata preconfRequestAType, + bytes calldata signature + ) + external + payable; + + #[derive(Debug)] + function createChallengeBType( + PreconfRequestBType calldata preconfRequestBType, + bytes calldata signature + ) + external + payable; + + #[derive(Debug)] + function resolveExpiredChallenge(bytes32 id) external; + } +} + +#[tokio::main] +async fn main() -> eyre::Result<()> { + // Read cli args + let opts = Opts::parse(); + let execution_client_ws_url = opts.execution_client_ws_url.clone(); + + // Initialize tracing + tracing_subscriber::fmt::init(); + + // 4. Initialize signer + let private_key_signer = alloy_signer_local::PrivateKeySigner::from_signing_key( + k256::ecdsa::SigningKey::from_slice(&hex::decode( + opts.private_key.strip_prefix("0x").unwrap_or(&opts.private_key), + )?)?, + ); + + info!("Signer address: {}", private_key_signer.address()); + + // let taiyi_escrow = + // TaiyiInteractiveChallenger::new(opts.taiyi_challenger_address, provider.clone()); + + // 5. Initialize contract + // 6. Initialize database for storing preconfirmations + let preconf_db = Database::create("preconf.db").unwrap_or_else(|e| { + eprintln!("Failed to create preconf database: {}", e); + std::process::exit(1); + }); + + let preconf_db = Arc::new(preconf_db); + + // 7. Initialize database for storing challenges + let challenge_db = Database::create("challenge.db").unwrap_or_else(|e| { + eprintln!("Failed to create challenge database: {}", e); + std::process::exit(1); + }); + + let challenge_db = Arc::new(challenge_db); + + // TODO: Preconfirmation ingestor + // 1. Read/Listen to preconfirmation streams for each provided underwriter address + // 2. For each preconfirmation store it in the kv db (key: slot/block_number, value: preconfirmation + other necessary data) + + let challenger_creator_handle = tokio::spawn(async move { + // Create a ws provider + let ws = WsConnect::new(opts.execution_client_ws_url.clone()); + let provider = ProviderBuilder::new().on_ws(ws).await.unwrap(); + + // Subscribe to block headers. + let subscription = provider.subscribe_blocks().await.unwrap(); + let mut stream = subscription.into_stream(); + + while let Some(header) = stream.next().await { + info!("Processing block {:?}", header.number); + let slot = get_slot_from_timestamp(header.timestamp, GENESIS_TIMESTAMP); + info!("Slot: {:?}", slot); + + // Check if preconfirmations exists for the slot + let read_tx = preconf_db.begin_read().unwrap(); + let table = read_tx.open_table(PRECONF_TABLE).unwrap(); + let preconfs = table.get(&slot); + + if preconfs.is_err() { + // Storage error + info!("Storage error for slot {}. Error: {:?}", slot, preconfs.err()); + continue; + } + + let preconfs = preconfs.unwrap(); + + if preconfs.is_none() { + // No preconfirmation found for the slot + continue; + } + + let preconfs = preconfs.unwrap().value(); + + info!("Found {} preconfirmations for slot {}", preconfs.len(), slot); + + let block = provider.get_block_by_number(BlockNumberOrTag::Number(header.number)).await; + + if block.is_err() { + // RPC error + info!("RPC error for block {}. Error: {:?}", header.number, block.err()); + continue; + } + + let block = block.unwrap().unwrap(); + let tx_hashes = block.transactions.hashes().collect::>(); + + // Calculate the challenge submission slot. We need to wait for the block to be finalized + // before we can open a challenge. + let challenge_submission_slot = slot + opts.finalization_window; + + // For each preconfirmation, check if the required txs are included in the block + for preconf in preconfs { + let preconf_type = preconf.preconf_type; + let preconf_request_signature = preconf.preconf_request_signature; + + if preconf_type == 0 { + // Type A + let preconf_request = + serde_json::from_str::(&preconf.preconf_request) + .unwrap(); + let mut open_challenge = false; + + // Check if all user txs are included in the block + if !preconf_request.preconf_tx.iter().all(|tx| tx_hashes.contains(tx.tx_hash())) + { + open_challenge = true; + } + + // Check if tip transaction is included in the block + if !tx_hashes.contains(preconf_request.tip_transaction.tx_hash()) { + open_challenge = true; + } + + if open_challenge { + // TODO: Create a challenge + } + } else { + // Type B + let preconf_request = + serde_json::from_str::(&preconf.preconf_request) + .unwrap(); + + // Check if all user txs are included in the block + if !tx_hashes.contains(preconf_request.transaction.unwrap().tx_hash()) { + // TODO: Create a challenge + } + } + } + + info!("Processed block {:?}", header.number); + } + }); + + let challenger_submitter_handle = tokio::spawn(async move { + // TODO: Challenger submitter + // 1. Read/Listen to latest block from provider + // 2. Check challenge db for challenges for the specific slot + // 3. Submit challenge to the network + + let ws = WsConnect::new(execution_client_ws_url); + let provider = ProviderBuilder::new().on_ws(ws).await.unwrap(); + let taiyi_challenger = + TaiyiInteractiveChallenger::new(opts.taiyi_challenger_address, provider.clone()); + + let subscription = provider.subscribe_blocks().await.unwrap(); + let mut stream = subscription.into_stream(); + + while let Some(header) = stream.next().await { + info!("Processing block {:?}", header.number); + let slot = get_slot_from_timestamp(header.timestamp, GENESIS_TIMESTAMP); + info!("Slot: {:?}", slot); + + // Check if challenges exists for the slot + let read_tx = challenge_db.begin_read().unwrap(); + let table = read_tx.open_table(CHALLENGE_TABLE).unwrap(); + let challenges = table.get(&slot); + + if challenges.is_err() { + // Storage error + info!("Storage error for slot {}. Error: {:?}", slot, challenges.err()); + continue; + } + + let challenges = challenges.unwrap(); + + if challenges.is_none() { + // No challenges found for the slot + info!("No challenges found for slot {}", slot); + continue; + } + + let challenges = challenges.unwrap().value(); + + // For each challenge, check if the challenge is expired + for challenge in challenges { + // TODO: Open challanges on-chan + if challenge.preconf_type == 0 { + // Type A + // taiyi_challenger.createChallengeAType().await; + } else { + // Type B + } + } + + info!("Processed block {:?}", header.number); + } + }); + + let _ = tokio::join!(challenger_creator_handle, challenger_submitter_handle); + + Ok(()) +} diff --git a/bin/taiyi-challenger/src/preconf_request_data.rs b/bin/taiyi-challenger/src/preconf_request_data.rs new file mode 100644 index 00000000..fbd187d3 --- /dev/null +++ b/bin/taiyi-challenger/src/preconf_request_data.rs @@ -0,0 +1,53 @@ +use std::{any::type_name, fmt::Debug}; + +use bincode::{decode_from_slice, encode_to_vec, Decode, Encode}; +use redb::{TypeName, Value}; + +#[derive(Debug, Clone, Decode, Encode, PartialEq)] +pub struct PreconfRequestData { + pub preconf_type: u8, // 0: Type A, 1: Type B + pub preconf_request: String, // Serde json string + pub preconf_request_signature: String, // Hex encoded signature +} + +/// Wrapper type to handle values using bincode serialization +#[derive(Debug)] +pub struct Bincode(pub T); + +impl Value for Bincode +where + T: Debug + Encode + Decode<()>, +{ + type SelfType<'a> + = T + where + Self: 'a; + + type AsBytes<'a> + = Vec + where + Self: 'a; + + fn fixed_width() -> Option { + None + } + + fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a> + where + Self: 'a, + { + decode_from_slice(data, bincode::config::standard()).unwrap().0 + } + + fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a> + where + Self: 'a, + Self: 'b, + { + encode_to_vec(value, bincode::config::standard()).unwrap() + } + + fn type_name() -> TypeName { + TypeName::new(&format!("Bincode<{}>", type_name::())) + } +} From 8fc9bcdbd85c9c1f540ce769d8b16857a213674e Mon Sep 17 00:00:00 2001 From: martines3000 Date: Tue, 8 Apr 2025 12:42:13 +0200 Subject: [PATCH 02/15] feat: handling multiple streams and processing --- bin/taiyi-challenger/Cargo.toml | 2 +- bin/taiyi-challenger/src/main.rs | 489 ++++++++++++++++++++----------- 2 files changed, 316 insertions(+), 175 deletions(-) diff --git a/bin/taiyi-challenger/Cargo.toml b/bin/taiyi-challenger/Cargo.toml index ab892728..55974204 100644 --- a/bin/taiyi-challenger/Cargo.toml +++ b/bin/taiyi-challenger/Cargo.toml @@ -11,10 +11,10 @@ alloy-provider = { workspace = true } alloy-signer = { workspace = true } alloy-signer-local = { workspace = true } alloy-sol-types = { workspace = true } +bincode = { workspace = true } tracing = { workspace = true } tracing-subscriber = { workspace = true } -bincode = { workspace = true } clap = { workspace = true } eyre = { workspace = true } diff --git a/bin/taiyi-challenger/src/main.rs b/bin/taiyi-challenger/src/main.rs index 348a686a..c970e4a1 100644 --- a/bin/taiyi-challenger/src/main.rs +++ b/bin/taiyi-challenger/src/main.rs @@ -1,27 +1,26 @@ use std::{collections::HashSet, sync::Arc}; -use alloy_eips::{merge::SLOT_DURATION_SECS, BlockNumberOrTag}; -use alloy_primitives::{hex, Address}; +use alloy_eips::{eip2718::Encodable2718, merge::SLOT_DURATION_SECS, BlockNumberOrTag}; +use alloy_primitives::{hex, Address, Bytes, U256}; use alloy_provider::{Provider, ProviderBuilder, WsConnect}; use alloy_signer::k256::{self}; use alloy_sol_types::sol; use clap::Parser; -use futures_util::StreamExt; +use futures_util::{future::join_all, StreamExt}; use redb::{Database, TableDefinition}; use taiyi_primitives::{PreconfRequestTypeA, PreconfRequestTypeB}; -use tracing::info; +use tracing::{info, level_filters::LevelFilter}; mod preconf_request_data; use preconf_request_data::{Bincode, PreconfRequestData}; -// TODO: Change to use correct types const PRECONF_TABLE: TableDefinition>> = TableDefinition::new("preconf"); const CHALLENGE_TABLE: TableDefinition>> = TableDefinition::new("challenge"); // TODO: Read from context ? -const GENESIS_TIMESTAMP: u64 = 1_655_733_600; +const GENESIS_TIMESTAMP: u64 = 1744102518; pub fn get_slot_from_timestamp(timestamp: u64, genesis_timestamp: u64) -> u64 { (timestamp - genesis_timestamp) / SLOT_DURATION_SECS @@ -29,16 +28,15 @@ pub fn get_slot_from_timestamp(timestamp: u64, genesis_timestamp: u64) -> u64 { #[derive(Parser, Clone)] struct Opts { - /// execution_client_url - #[clap(long = "execution_client_url", default_value = "http://localhost:63970")] - execution_client_url: String, - /// execution_client_url - #[clap(long = "execution_client_ws_url", default_value = "ws://localhost:63971")] + /// execution_client_ws_url + #[clap(long = "execution_client_ws_url", default_value = "ws://localhost:54488")] execution_client_ws_url: String, /// finalization_window #[clap(long = "finalization_window", default_value = "32")] finalization_window: u64, - + /// underwriter stream urls + #[clap(long = "underwriter_stream_urls", default_value = "1, 2, 3")] + underwriter_stream_urls: Vec, /// Private key to sign transactions #[clap(long = "private_key")] private_key: String, @@ -101,29 +99,287 @@ sol! { } } -#[tokio::main] -async fn main() -> eyre::Result<()> { - // Read cli args - let opts = Opts::parse(); - let execution_client_ws_url = opts.execution_client_ws_url.clone(); +async fn handle_underwriter_stream(preconf_db: Arc, opts: Arc) -> eyre::Result<()> { + // TODO: Implement underwriter stream ingestion + let write_tx = preconf_db.begin_write().unwrap(); + { + let mut table = write_tx.open_table(PRECONF_TABLE).unwrap(); + table.insert(&0, vec![]).unwrap(); + } + write_tx.commit().unwrap(); - // Initialize tracing - tracing_subscriber::fmt::init(); + Ok(()) +} + +async fn handle_challenge_creation( + preconf_db: Arc, + challenge_db: Arc, + opts: Arc, +) -> eyre::Result<()> { + // Create a ws provider + let ws = WsConnect::new(opts.execution_client_ws_url.clone()); + let provider = ProviderBuilder::new().on_ws(ws).await.unwrap(); + + // Subscribe to block headers. + let subscription = provider.subscribe_blocks().await.unwrap(); + let mut stream = subscription.into_stream(); + + while let Some(header) = stream.next().await { + info!("[Challenger Creator]: Processing block {:?}", header.number); + let slot = get_slot_from_timestamp(header.timestamp, GENESIS_TIMESTAMP); + info!("[Challenger Creator]: Slot: {:?}", slot); + + // Check if preconfirmations exists for the slot + let read_tx = preconf_db.begin_read().unwrap(); + let table = read_tx.open_table(PRECONF_TABLE).unwrap(); + let preconfs = table.get(&slot); + + if preconfs.is_err() { + // Storage error + info!( + "[Challenger Creator]: Storage error for slot {}. Error: {:?}", + slot, + preconfs.err() + ); + continue; + } + + let preconfs = preconfs.unwrap(); + + if preconfs.is_none() { + // No preconfirmation found for the slot + info!("[Challenger Creator]: No preconfirmations found for slot {}", slot); + continue; + } + + let preconfs = preconfs.unwrap().value(); + + info!("[Challenger Creator]: Found {} preconfirmations for slot {}", preconfs.len(), slot); + + let block = provider.get_block_by_number(BlockNumberOrTag::Number(header.number)).await; + + if block.is_err() { + // RPC error + info!( + "[Challenger Creator]: RPC error for block {}. Error: {:?}", + header.number, + block.err() + ); + continue; + } + + let block = block.unwrap().unwrap(); + let tx_hashes = block.transactions.hashes().collect::>(); + + // Calculate the challenge submission slot. We need to wait for the block to be finalized + // before we can open a challenge. + let challenge_submission_slot = slot + opts.finalization_window; - // 4. Initialize signer + // For each preconfirmation, check if the required txs are included in the block + for preconf in preconfs { + let preconf_type = preconf.preconf_type; + let preconf_request_signature = preconf.preconf_request_signature; + + if preconf_type == 0 { + // Type A + let preconf_request = + serde_json::from_str::(&preconf.preconf_request).unwrap(); + let mut open_challenge = false; + + // Check if all user txs are included in the block + if !preconf_request.preconf_tx.iter().all(|tx| tx_hashes.contains(tx.tx_hash())) { + open_challenge = true; + } + + // Check if tip transaction is included in the block + if !tx_hashes.contains(preconf_request.tip_transaction.tx_hash()) { + open_challenge = true; + } + + if open_challenge { + // TODO: Create a challenge + } + } else { + // Type B + let preconf_request = + serde_json::from_str::(&preconf.preconf_request).unwrap(); + + // Check if all user txs are included in the block + if !tx_hashes.contains(preconf_request.transaction.unwrap().tx_hash()) { + // TODO: Create a challenge + } + } + } + + info!("[Challenger Creator]: Processed block {:?}", header.number); + } + + Ok(()) +} + +async fn handle_challenge_submission( + preconf_db: Arc, + challenge_db: Arc, + opts: Arc, +) -> eyre::Result<()> { + // Initialize signer let private_key_signer = alloy_signer_local::PrivateKeySigner::from_signing_key( - k256::ecdsa::SigningKey::from_slice(&hex::decode( - opts.private_key.strip_prefix("0x").unwrap_or(&opts.private_key), - )?)?, + k256::ecdsa::SigningKey::from_slice( + &hex::decode(opts.private_key.strip_prefix("0x").unwrap_or(&opts.private_key)).unwrap(), + ) + .unwrap(), + ); + let signer_address = private_key_signer.address(); + + info!("[Challenger Submitter]: Signer address: {}", signer_address); + + let ws = WsConnect::new(opts.execution_client_ws_url.clone()); + + let provider = ProviderBuilder::new().wallet(private_key_signer).on_ws(ws).await.unwrap(); + info!( + "[Challenger Submitter]: Signer ETH balance: {}", + provider.get_balance(signer_address).await.unwrap() ); - info!("Signer address: {}", private_key_signer.address()); + let taiyi_challenger = + TaiyiInteractiveChallenger::new(opts.taiyi_challenger_address, provider.clone()); + + let subscription = provider.subscribe_blocks().await.unwrap(); + let mut stream = subscription.into_stream(); + + while let Some(header) = stream.next().await { + info!("[Challenger Submitter]: Processing block {:?}", header.number); + let slot = get_slot_from_timestamp(header.timestamp, GENESIS_TIMESTAMP); + info!("[Challenger Submitter]: Slot: {:?}", slot); + info!( + "[Challenger Submitter]: Signer ETH balance: {}", + provider.get_balance(signer_address).await.unwrap() + ); + + // Check if challenges exists for the slot + let read_tx = challenge_db.begin_read().unwrap(); + let table = read_tx.open_table(CHALLENGE_TABLE).unwrap(); + let challenges = table.get(&slot); + + if challenges.is_err() { + // Storage error + info!( + "[Challenger Submitter]: Storage error for slot {}. Error: {:?}", + slot, + challenges.err() + ); + continue; + } + + let challenges = challenges.unwrap(); - // let taiyi_escrow = - // TaiyiInteractiveChallenger::new(opts.taiyi_challenger_address, provider.clone()); + if challenges.is_none() { + // No challenges found for the slot + info!("[Challenger Submitter]: No challenges found for slot {}", slot); + continue; + } + + let challenges = challenges.unwrap().value(); + info!("[Challenger Submitter]: Found {} challenges for slot {}", challenges.len(), slot); + + // For each challenge, check if the challenge is expired + for challenge in challenges { + // TODO: Open challanges on-chan + if challenge.preconf_type == 0 { + // Type A + let preconf_request = + serde_json::from_str::(&challenge.preconf_request) + .unwrap(); + + let mut txs: Vec = Vec::new(); + + for tx in preconf_request.preconf_tx { + let mut tx_bytes = Vec::new(); + tx.encode_2718(&mut tx_bytes); + let hex_encoded_tx = format!("0x{}", hex::encode(&tx_bytes)); + txs.push(hex_encoded_tx); + } + + let mut tip_tx = Vec::new(); + preconf_request.tip_transaction.encode_2718(&mut tip_tx); + let tip_tx_raw = format!("0x{}", hex::encode(&tip_tx)); + + let preconf_request_a_type = TaiyiInteractiveChallenger::PreconfRequestAType { + txs, + tipTx: tip_tx_raw, + slot: U256::from(slot), + sequenceNum: U256::from(preconf_request.sequence_number.unwrap()), + signer: preconf_request.signer, + }; + + // TODO: Check types here (hex encoding or not...) + let signature_bytes = + Bytes::from(hex::decode(challenge.preconf_request_signature).unwrap()); + + // TODO: Should we watch/wait for the transaction here ? + // TODO: Maybe store this in a Vec<_> and wait for them separately ? + let _ = taiyi_challenger + .createChallengeAType(preconf_request_a_type, signature_bytes) + .send() + .await + .unwrap(); + } else { + // Type B + // TODO: Maybe I will already get the `TaiyiInteractiveChallenger::PreconfRequestBType` type here` + let preconf_request = + serde_json::from_str::(&challenge.preconf_request) + .unwrap(); + + let mut tx = Vec::new(); + preconf_request.transaction.unwrap().encode_2718(&mut tx); + let tx_raw = format!("0x{}", hex::encode(&tx)); + + let preconf_request_b_type = TaiyiInteractiveChallenger::PreconfRequestBType { + blockspaceAllocation: TaiyiInteractiveChallenger::BlockspaceAllocation { + gasLimit: U256::from(preconf_request.allocation.gas_limit), + sender: preconf_request.allocation.sender, + recipient: preconf_request.allocation.recipient, + deposit: U256::from(preconf_request.allocation.deposit), + tip: U256::from(preconf_request.allocation.tip), + targetSlot: U256::from(preconf_request.allocation.target_slot), + blobCount: U256::from(preconf_request.allocation.blob_count), + }, + blockspaceAllocationSignature: + // TODO: Maybe we need to hex encode this ? + preconf_request.alloc_sig.as_bytes().into() + , + rawTx: Bytes::from(tx_raw), + underwriterSignedBlockspaceAllocation: Bytes::from([]), + // TODO: Maybe we need to chage the `rawTx` type to String + underwriterSignedRawTx: Bytes::from([]), + }; + + let signature_bytes = + Bytes::from(hex::decode(challenge.preconf_request_signature).unwrap()); + + let _ = taiyi_challenger + .createChallengeBType(preconf_request_b_type, signature_bytes) + .send() + .await + .unwrap(); + } + } + + info!("[Challenger Submitter]: Processed block {:?}", header.number); + } + + Ok(()) +} + +#[tokio::main] +async fn main() -> eyre::Result<()> { + // Read cli args + let opts = Opts::parse(); + let opts = Arc::new(opts); + + // Initialize tracing + tracing_subscriber::fmt().with_max_level(LevelFilter::INFO).init(); - // 5. Initialize contract - // 6. Initialize database for storing preconfirmations let preconf_db = Database::create("preconf.db").unwrap_or_else(|e| { eprintln!("Failed to create preconf database: {}", e); std::process::exit(1); @@ -131,7 +387,6 @@ async fn main() -> eyre::Result<()> { let preconf_db = Arc::new(preconf_db); - // 7. Initialize database for storing challenges let challenge_db = Database::create("challenge.db").unwrap_or_else(|e| { eprintln!("Failed to create challenge database: {}", e); std::process::exit(1); @@ -139,160 +394,46 @@ async fn main() -> eyre::Result<()> { let challenge_db = Arc::new(challenge_db); - // TODO: Preconfirmation ingestor - // 1. Read/Listen to preconfirmation streams for each provided underwriter address - // 2. For each preconfirmation store it in the kv db (key: slot/block_number, value: preconfirmation + other necessary data) - - let challenger_creator_handle = tokio::spawn(async move { - // Create a ws provider - let ws = WsConnect::new(opts.execution_client_ws_url.clone()); - let provider = ProviderBuilder::new().on_ws(ws).await.unwrap(); - - // Subscribe to block headers. - let subscription = provider.subscribe_blocks().await.unwrap(); - let mut stream = subscription.into_stream(); - - while let Some(header) = stream.next().await { - info!("Processing block {:?}", header.number); - let slot = get_slot_from_timestamp(header.timestamp, GENESIS_TIMESTAMP); - info!("Slot: {:?}", slot); - - // Check if preconfirmations exists for the slot - let read_tx = preconf_db.begin_read().unwrap(); - let table = read_tx.open_table(PRECONF_TABLE).unwrap(); - let preconfs = table.get(&slot); - - if preconfs.is_err() { - // Storage error - info!("Storage error for slot {}. Error: {:?}", slot, preconfs.err()); - continue; - } - - let preconfs = preconfs.unwrap(); - - if preconfs.is_none() { - // No preconfirmation found for the slot - continue; - } - - let preconfs = preconfs.unwrap().value(); + // Create tables if they don't exist + info!("Creating tables..."); + let tx = preconf_db.begin_write().unwrap(); + tx.open_table(PRECONF_TABLE).unwrap(); + tx.commit().unwrap(); - info!("Found {} preconfirmations for slot {}", preconfs.len(), slot); - - let block = provider.get_block_by_number(BlockNumberOrTag::Number(header.number)).await; - - if block.is_err() { - // RPC error - info!("RPC error for block {}. Error: {:?}", header.number, block.err()); - continue; - } - - let block = block.unwrap().unwrap(); - let tx_hashes = block.transactions.hashes().collect::>(); - - // Calculate the challenge submission slot. We need to wait for the block to be finalized - // before we can open a challenge. - let challenge_submission_slot = slot + opts.finalization_window; - - // For each preconfirmation, check if the required txs are included in the block - for preconf in preconfs { - let preconf_type = preconf.preconf_type; - let preconf_request_signature = preconf.preconf_request_signature; - - if preconf_type == 0 { - // Type A - let preconf_request = - serde_json::from_str::(&preconf.preconf_request) - .unwrap(); - let mut open_challenge = false; - - // Check if all user txs are included in the block - if !preconf_request.preconf_tx.iter().all(|tx| tx_hashes.contains(tx.tx_hash())) - { - open_challenge = true; - } - - // Check if tip transaction is included in the block - if !tx_hashes.contains(preconf_request.tip_transaction.tx_hash()) { - open_challenge = true; - } - - if open_challenge { - // TODO: Create a challenge - } - } else { - // Type B - let preconf_request = - serde_json::from_str::(&preconf.preconf_request) - .unwrap(); - - // Check if all user txs are included in the block - if !tx_hashes.contains(preconf_request.transaction.unwrap().tx_hash()) { - // TODO: Create a challenge - } - } - } + let tx = challenge_db.begin_write().unwrap(); + tx.open_table(CHALLENGE_TABLE).unwrap(); + tx.commit().unwrap(); + info!("Tables created successfully"); - info!("Processed block {:?}", header.number); - } - }); + let mut handles = Vec::new(); - let challenger_submitter_handle = tokio::spawn(async move { - // TODO: Challenger submitter - // 1. Read/Listen to latest block from provider - // 2. Check challenge db for challenges for the specific slot - // 3. Submit challenge to the network - - let ws = WsConnect::new(execution_client_ws_url); - let provider = ProviderBuilder::new().on_ws(ws).await.unwrap(); - let taiyi_challenger = - TaiyiInteractiveChallenger::new(opts.taiyi_challenger_address, provider.clone()); - - let subscription = provider.subscribe_blocks().await.unwrap(); - let mut stream = subscription.into_stream(); - - while let Some(header) = stream.next().await { - info!("Processing block {:?}", header.number); - let slot = get_slot_from_timestamp(header.timestamp, GENESIS_TIMESTAMP); - info!("Slot: {:?}", slot); - - // Check if challenges exists for the slot - let read_tx = challenge_db.begin_read().unwrap(); - let table = read_tx.open_table(CHALLENGE_TABLE).unwrap(); - let challenges = table.get(&slot); - - if challenges.is_err() { - // Storage error - info!("Storage error for slot {}. Error: {:?}", slot, challenges.err()); - continue; - } + // Handles for ingesting underwriter streams + let underwriter_stream_urls = opts.underwriter_stream_urls.clone(); - let challenges = challenges.unwrap(); + for url in underwriter_stream_urls { + let handle = tokio::spawn(handle_underwriter_stream(preconf_db.clone(), opts.clone())); + handles.push(handle); + } - if challenges.is_none() { - // No challenges found for the slot - info!("No challenges found for slot {}", slot); - continue; - } + // Handle for creating challenges + let challenger_creator_handle = tokio::spawn(handle_challenge_creation( + preconf_db.clone(), + challenge_db.clone(), + opts.clone(), + )); - let challenges = challenges.unwrap().value(); + handles.push(challenger_creator_handle); - // For each challenge, check if the challenge is expired - for challenge in challenges { - // TODO: Open challanges on-chan - if challenge.preconf_type == 0 { - // Type A - // taiyi_challenger.createChallengeAType().await; - } else { - // Type B - } - } + // Handle for submitting challenges + let challenger_submitter_handle = tokio::spawn(handle_challenge_submission( + preconf_db.clone(), + challenge_db.clone(), + opts.clone(), + )); - info!("Processed block {:?}", header.number); - } - }); + handles.push(challenger_submitter_handle); - let _ = tokio::join!(challenger_creator_handle, challenger_submitter_handle); + let _ = join_all(handles).await; Ok(()) } From 4c543622d5924875b486d41dd7fb56d43a1cc695 Mon Sep 17 00:00:00 2001 From: martines3000 Date: Mon, 14 Apr 2025 17:28:40 +0200 Subject: [PATCH 03/15] feat: challenger implementation and run scripts --- Cargo.lock | 2 + bin/taiyi-challenger/Cargo.toml | 5 +- bin/taiyi-challenger/src/main.rs | 255 ++++++++++++++++----- scripts/devnet/start-devnet.sh | 2 +- scripts/devnet/start-taiyi-challenger.sh | 23 ++ scripts/devnet/start-taiyi-preconfer.sh | 20 +- scripts/devnet/submit-preconf-tx-type-a.sh | 14 ++ scripts/devnet/submit-preconf-tx-type-b.sh | 14 ++ scripts/devnet/submit-preconf-tx.sh | 7 - 9 files changed, 271 insertions(+), 71 deletions(-) create mode 100755 scripts/devnet/start-taiyi-challenger.sh mode change 100644 => 100755 scripts/devnet/start-taiyi-preconfer.sh create mode 100755 scripts/devnet/submit-preconf-tx-type-a.sh create mode 100755 scripts/devnet/submit-preconf-tx-type-b.sh delete mode 100644 scripts/devnet/submit-preconf-tx.sh diff --git a/Cargo.lock b/Cargo.lock index 7023b9d1..499eadc4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8922,6 +8922,8 @@ dependencies = [ "eyre", "futures-util", "redb", + "reqwest 0.12.9", + "reqwest-eventsource", "serde", "serde_json", "taiyi-primitives", diff --git a/bin/taiyi-challenger/Cargo.toml b/bin/taiyi-challenger/Cargo.toml index 55974204..c8fede11 100644 --- a/bin/taiyi-challenger/Cargo.toml +++ b/bin/taiyi-challenger/Cargo.toml @@ -20,11 +20,12 @@ clap = { workspace = true } eyre = { workspace = true } futures-util = { workspace = true } redb = { workspace = true } +reqwest = { workspace = true } +reqwest-eventsource = "0.6" serde = { workspace = true } serde_json = { workspace = true } -tokio = { workspace = true } - taiyi-primitives = { workspace = true } +tokio = { workspace = true } [[bin]] name = "taiyi-challenger" diff --git a/bin/taiyi-challenger/src/main.rs b/bin/taiyi-challenger/src/main.rs index c970e4a1..c67bffec 100644 --- a/bin/taiyi-challenger/src/main.rs +++ b/bin/taiyi-challenger/src/main.rs @@ -1,4 +1,4 @@ -use std::{collections::HashSet, sync::Arc}; +use std::{collections::HashSet, str::FromStr, sync::Arc}; use alloy_eips::{eip2718::Encodable2718, merge::SLOT_DURATION_SECS, BlockNumberOrTag}; use alloy_primitives::{hex, Address, Bytes, U256}; @@ -8,8 +8,12 @@ use alloy_sol_types::sol; use clap::Parser; use futures_util::{future::join_all, StreamExt}; use redb::{Database, TableDefinition}; -use taiyi_primitives::{PreconfRequestTypeA, PreconfRequestTypeB}; -use tracing::{info, level_filters::LevelFilter}; +use reqwest::Url; +use reqwest_eventsource::{Event, EventSource}; +use taiyi_primitives::{ + PreconfRequest, PreconfRequestTypeA, PreconfRequestTypeB, PreconfResponseData, +}; +use tracing::{debug, error, level_filters::LevelFilter}; mod preconf_request_data; use preconf_request_data::{Bincode, PreconfRequestData}; @@ -19,9 +23,6 @@ const PRECONF_TABLE: TableDefinition>> = const CHALLENGE_TABLE: TableDefinition>> = TableDefinition::new("challenge"); -// TODO: Read from context ? -const GENESIS_TIMESTAMP: u64 = 1744102518; - pub fn get_slot_from_timestamp(timestamp: u64, genesis_timestamp: u64) -> u64 { (timestamp - genesis_timestamp) / SLOT_DURATION_SECS } @@ -29,20 +30,26 @@ pub fn get_slot_from_timestamp(timestamp: u64, genesis_timestamp: u64) -> u64 { #[derive(Parser, Clone)] struct Opts { /// execution_client_ws_url - #[clap(long = "execution_client_ws_url", default_value = "ws://localhost:54488")] + #[clap(long = "execution-client-ws-url")] execution_client_ws_url: String, + /// beacon_url + #[clap(long = "beacon-url")] + beacon_url: String, /// finalization_window - #[clap(long = "finalization_window", default_value = "32")] + #[clap(long = "finalization-window")] finalization_window: u64, /// underwriter stream urls - #[clap(long = "underwriter_stream_urls", default_value = "1, 2, 3")] + #[clap(long = "underwriter-stream-urls")] underwriter_stream_urls: Vec, /// Private key to sign transactions - #[clap(long = "private_key")] + #[clap(long = "private-key")] private_key: String, /// Taiyi challenger contract address - #[clap(long = "taiyi_challenger_address")] + #[clap(long = "taiyi-challenger-address")] taiyi_challenger_address: Address, + /// Always open challenges + #[clap(long = "always-open-challenges", default_value = "false")] + always_open_challenges: bool, } sol! { @@ -99,14 +106,87 @@ sol! { } } -async fn handle_underwriter_stream(preconf_db: Arc, opts: Arc) -> eyre::Result<()> { - // TODO: Implement underwriter stream ingestion - let write_tx = preconf_db.begin_write().unwrap(); - { - let mut table = write_tx.open_table(PRECONF_TABLE).unwrap(); - table.insert(&0, vec![]).unwrap(); +async fn handle_underwriter_stream(preconf_db: Arc, url: Url) -> eyre::Result<()> { + let req = reqwest::Client::new().get(url); + + let mut event_source = EventSource::new(req).unwrap_or_else(|err| { + panic!("Failed to create EventSource: {:?}", err); + }); + + while let Some(event) = event_source.next().await { + match event { + Ok(Event::Message(message)) => { + let data = &message.data; + + let parsed_data = + serde_json::from_str::>(data) + .unwrap(); + + debug!("[Stream Ingestor]: Received {} preconfirmations", parsed_data.len()); + + for (preconf_request, preconf_response_data) in parsed_data.iter() { + let target_slot = preconf_request.target_slot(); + debug!( + "[Stream Ingestor]: Processing preconfirmation for slot {}", + target_slot + ); + + let preconf_request_data = PreconfRequestData { + preconf_type: match preconf_request { + PreconfRequest::TypeA(_) => 0, + PreconfRequest::TypeB(_) => 1, + }, + preconf_request: match preconf_request { + PreconfRequest::TypeA(preconf_request) => { + serde_json::to_string(preconf_request).unwrap() + } + PreconfRequest::TypeB(preconf_request) => { + serde_json::to_string(preconf_request).unwrap() + } + }, + preconf_request_signature: hex::encode( + preconf_response_data.commitment.unwrap().as_bytes(), + ), + }; + + let read_tx = preconf_db.begin_read().unwrap(); + let table = read_tx.open_table(PRECONF_TABLE).unwrap(); + let preconfs = table.get(&target_slot); + + if preconfs.is_err() { + // Storage error + error!( + "[Stream Ingestor]: Storage error for slot {}. Error: {:?}", + target_slot, + preconfs.err() + ); + continue; + } + + let preconfs = preconfs.unwrap(); + let mut preconfs = + if preconfs.is_none() { Vec::new() } else { preconfs.unwrap().value() }; + + preconfs.push(preconf_request_data); + + let write_tx = preconf_db.begin_write().unwrap(); + { + let mut table = write_tx.open_table(PRECONF_TABLE).unwrap(); + table.insert(&target_slot, preconfs).unwrap(); + } + write_tx.commit().unwrap(); + + debug!("[Stream Ingestor]: Stored preconfirmation for slot {}", target_slot); + } + } + Ok(Event::Open) => { + debug!("[Stream Ingestor]: SSE connection opened"); + } + Err(err) => { + error!("[Stream Ingestor]: Error receiving SSE event: {:?}", err); + } + } } - write_tx.commit().unwrap(); Ok(()) } @@ -115,9 +195,10 @@ async fn handle_challenge_creation( preconf_db: Arc, challenge_db: Arc, opts: Arc, + genesis_timestamp: u64, ) -> eyre::Result<()> { // Create a ws provider - let ws = WsConnect::new(opts.execution_client_ws_url.clone()); + let ws = WsConnect::new(&opts.execution_client_ws_url); let provider = ProviderBuilder::new().on_ws(ws).await.unwrap(); // Subscribe to block headers. @@ -125,9 +206,9 @@ async fn handle_challenge_creation( let mut stream = subscription.into_stream(); while let Some(header) = stream.next().await { - info!("[Challenger Creator]: Processing block {:?}", header.number); - let slot = get_slot_from_timestamp(header.timestamp, GENESIS_TIMESTAMP); - info!("[Challenger Creator]: Slot: {:?}", slot); + debug!("[Challenger Creator]: Processing block {:?}", header.number); + let slot = get_slot_from_timestamp(header.timestamp, genesis_timestamp); + debug!("[Challenger Creator]: Slot: {:?}", slot); // Check if preconfirmations exists for the slot let read_tx = preconf_db.begin_read().unwrap(); @@ -136,7 +217,7 @@ async fn handle_challenge_creation( if preconfs.is_err() { // Storage error - info!( + error!( "[Challenger Creator]: Storage error for slot {}. Error: {:?}", slot, preconfs.err() @@ -148,19 +229,19 @@ async fn handle_challenge_creation( if preconfs.is_none() { // No preconfirmation found for the slot - info!("[Challenger Creator]: No preconfirmations found for slot {}", slot); + debug!("[Challenger Creator]: No preconfirmations found for slot {}", slot); continue; } let preconfs = preconfs.unwrap().value(); - info!("[Challenger Creator]: Found {} preconfirmations for slot {}", preconfs.len(), slot); + debug!("[Challenger Creator]: Found {} preconfirmations for slot {}", preconfs.len(), slot); let block = provider.get_block_by_number(BlockNumberOrTag::Number(header.number)).await; if block.is_err() { // RPC error - info!( + error!( "[Challenger Creator]: RPC error for block {}. Error: {:?}", header.number, block.err() @@ -178,7 +259,6 @@ async fn handle_challenge_creation( // For each preconfirmation, check if the required txs are included in the block for preconf in preconfs { let preconf_type = preconf.preconf_type; - let preconf_request_signature = preconf.preconf_request_signature; if preconf_type == 0 { // Type A @@ -197,7 +277,37 @@ async fn handle_challenge_creation( } if open_challenge { - // TODO: Create a challenge + let read_tx = challenge_db.begin_read().unwrap(); + let table = read_tx.open_table(CHALLENGE_TABLE).unwrap(); + let challenges = table.get(&challenge_submission_slot); + + if challenges.is_err() { + // Storage error + error!( + "[Challenger Creator]: Storage error for slot {}. Error: {:?}", + challenge_submission_slot, + challenges.err() + ); + continue; + } + + let challenges = challenges.unwrap(); + let mut challenges = + if challenges.is_none() { Vec::new() } else { challenges.unwrap().value() }; + + challenges.push(preconf); + + let write_tx = challenge_db.begin_write().unwrap(); + { + let mut table = write_tx.open_table(CHALLENGE_TABLE).unwrap(); + table.insert(&challenge_submission_slot, challenges).unwrap(); + } + write_tx.commit().unwrap(); + + debug!( + "[Challenger Creator]: Stored challenge for slot {}", + challenge_submission_slot + ); } } else { // Type B @@ -206,21 +316,48 @@ async fn handle_challenge_creation( // Check if all user txs are included in the block if !tx_hashes.contains(preconf_request.transaction.unwrap().tx_hash()) { - // TODO: Create a challenge + let read_tx = challenge_db.begin_read().unwrap(); + let table = read_tx.open_table(CHALLENGE_TABLE).unwrap(); + let challenges = table.get(&slot); + + if challenges.is_err() { + // Storage error + error!( + "[Challenger Creator]: Storage error for slot {}. Error: {:?}", + slot, + challenges.err() + ); + continue; + } + + let challenges = challenges.unwrap(); + let mut challenges = + if challenges.is_none() { Vec::new() } else { challenges.unwrap().value() }; + + challenges.push(preconf); + + let write_tx = challenge_db.begin_write().unwrap(); + { + let mut table = write_tx.open_table(CHALLENGE_TABLE).unwrap(); + table.insert(&slot, challenges).unwrap(); + } + write_tx.commit().unwrap(); + + debug!("[Challenger Creator]: Stored challenge for slot {}", slot); } } } - info!("[Challenger Creator]: Processed block {:?}", header.number); + debug!("[Challenger Creator]: Processed block {:?}", header.number); } Ok(()) } async fn handle_challenge_submission( - preconf_db: Arc, challenge_db: Arc, opts: Arc, + genesis_timestamp: u64, ) -> eyre::Result<()> { // Initialize signer let private_key_signer = alloy_signer_local::PrivateKeySigner::from_signing_key( @@ -231,12 +368,12 @@ async fn handle_challenge_submission( ); let signer_address = private_key_signer.address(); - info!("[Challenger Submitter]: Signer address: {}", signer_address); - - let ws = WsConnect::new(opts.execution_client_ws_url.clone()); + debug!("[Challenger Submitter]: Signer address: {}", signer_address); + let ws = WsConnect::new(&opts.execution_client_ws_url); let provider = ProviderBuilder::new().wallet(private_key_signer).on_ws(ws).await.unwrap(); - info!( + + debug!( "[Challenger Submitter]: Signer ETH balance: {}", provider.get_balance(signer_address).await.unwrap() ); @@ -248,10 +385,10 @@ async fn handle_challenge_submission( let mut stream = subscription.into_stream(); while let Some(header) = stream.next().await { - info!("[Challenger Submitter]: Processing block {:?}", header.number); - let slot = get_slot_from_timestamp(header.timestamp, GENESIS_TIMESTAMP); - info!("[Challenger Submitter]: Slot: {:?}", slot); - info!( + debug!("[Challenger Submitter]: Processing block {:?}", header.number); + let slot = get_slot_from_timestamp(header.timestamp, genesis_timestamp); + debug!("[Challenger Submitter]: Slot: {:?}", slot); + debug!( "[Challenger Submitter]: Signer ETH balance: {}", provider.get_balance(signer_address).await.unwrap() ); @@ -263,7 +400,7 @@ async fn handle_challenge_submission( if challenges.is_err() { // Storage error - info!( + error!( "[Challenger Submitter]: Storage error for slot {}. Error: {:?}", slot, challenges.err() @@ -275,16 +412,15 @@ async fn handle_challenge_submission( if challenges.is_none() { // No challenges found for the slot - info!("[Challenger Submitter]: No challenges found for slot {}", slot); + debug!("[Challenger Submitter]: No challenges found for slot {}", slot); continue; } let challenges = challenges.unwrap().value(); - info!("[Challenger Submitter]: Found {} challenges for slot {}", challenges.len(), slot); + debug!("[Challenger Submitter]: Found {} challenges for slot {}", challenges.len(), slot); // For each challenge, check if the challenge is expired for challenge in challenges { - // TODO: Open challanges on-chan if challenge.preconf_type == 0 { // Type A let preconf_request = @@ -317,7 +453,6 @@ async fn handle_challenge_submission( Bytes::from(hex::decode(challenge.preconf_request_signature).unwrap()); // TODO: Should we watch/wait for the transaction here ? - // TODO: Maybe store this in a Vec<_> and wait for them separately ? let _ = taiyi_challenger .createChallengeAType(preconf_request_a_type, signature_bytes) .send() @@ -325,7 +460,6 @@ async fn handle_challenge_submission( .unwrap(); } else { // Type B - // TODO: Maybe I will already get the `TaiyiInteractiveChallenger::PreconfRequestBType` type here` let preconf_request = serde_json::from_str::(&challenge.preconf_request) .unwrap(); @@ -345,12 +479,12 @@ async fn handle_challenge_submission( blobCount: U256::from(preconf_request.allocation.blob_count), }, blockspaceAllocationSignature: - // TODO: Maybe we need to hex encode this ? + // TODO: Check types here (hex encoding or not...) preconf_request.alloc_sig.as_bytes().into() , rawTx: Bytes::from(tx_raw), + // TODO: Can we remove this two fields ? underwriterSignedBlockspaceAllocation: Bytes::from([]), - // TODO: Maybe we need to chage the `rawTx` type to String underwriterSignedRawTx: Bytes::from([]), }; @@ -365,7 +499,7 @@ async fn handle_challenge_submission( } } - info!("[Challenger Submitter]: Processed block {:?}", header.number); + debug!("[Challenger Submitter]: Processed block {:?}", header.number); } Ok(()) @@ -378,7 +512,7 @@ async fn main() -> eyre::Result<()> { let opts = Arc::new(opts); // Initialize tracing - tracing_subscriber::fmt().with_max_level(LevelFilter::INFO).init(); + tracing_subscriber::fmt().with_max_level(LevelFilter::DEBUG).init(); let preconf_db = Database::create("preconf.db").unwrap_or_else(|e| { eprintln!("Failed to create preconf database: {}", e); @@ -395,7 +529,7 @@ async fn main() -> eyre::Result<()> { let challenge_db = Arc::new(challenge_db); // Create tables if they don't exist - info!("Creating tables..."); + debug!("Creating tables..."); let tx = preconf_db.begin_write().unwrap(); tx.open_table(PRECONF_TABLE).unwrap(); tx.commit().unwrap(); @@ -403,15 +537,29 @@ async fn main() -> eyre::Result<()> { let tx = challenge_db.begin_write().unwrap(); tx.open_table(CHALLENGE_TABLE).unwrap(); tx.commit().unwrap(); - info!("Tables created successfully"); + debug!("Tables created successfully"); + + // Read genesis timestamp from Beacon API (/eth/v1/beacon/genesis) + let beacon_genesis_response = reqwest::Client::new() + .get(format!("{}/eth/v1/beacon/genesis", opts.beacon_url)) + .send() + .await + .unwrap(); + + let beacon_genesis_response = + beacon_genesis_response.json::().await.unwrap(); + let genesis_timestamp = + u64::from_str(&beacon_genesis_response["data"]["genesis_time"].as_str().unwrap()).unwrap(); let mut handles = Vec::new(); // Handles for ingesting underwriter streams let underwriter_stream_urls = opts.underwriter_stream_urls.clone(); + let underwriter_stream_urls = + underwriter_stream_urls.iter().map(|url| Url::parse(&url).unwrap()).collect::>(); for url in underwriter_stream_urls { - let handle = tokio::spawn(handle_underwriter_stream(preconf_db.clone(), opts.clone())); + let handle = tokio::spawn(handle_underwriter_stream(preconf_db.clone(), url)); handles.push(handle); } @@ -420,15 +568,16 @@ async fn main() -> eyre::Result<()> { preconf_db.clone(), challenge_db.clone(), opts.clone(), + genesis_timestamp, )); handles.push(challenger_creator_handle); // Handle for submitting challenges let challenger_submitter_handle = tokio::spawn(handle_challenge_submission( - preconf_db.clone(), challenge_db.clone(), opts.clone(), + genesis_timestamp, )); handles.push(challenger_submitter_handle); diff --git a/scripts/devnet/start-devnet.sh b/scripts/devnet/start-devnet.sh index e228a84b..a4513b6b 100644 --- a/scripts/devnet/start-devnet.sh +++ b/scripts/devnet/start-devnet.sh @@ -6,7 +6,7 @@ if [ -z "$TAIYI_BOOST_IMAGE" ]; then export TAIYI_BOOST_IMAGE="lubann/taiyi:latest" fi -sed -i "s|lubann/taiyi:latest|${TAIYI_BOOST_IMAGE}|g" scripts/devnet/luban.yml +# sed -i "s|lubann/taiyi:latest|${TAIYI_BOOST_IMAGE}|g" scripts/devnet/luban.yml cat scripts/devnet/luban.yml pushd $WORKING_DIR diff --git a/scripts/devnet/start-taiyi-challenger.sh b/scripts/devnet/start-taiyi-challenger.sh new file mode 100755 index 00000000..cc2aa948 --- /dev/null +++ b/scripts/devnet/start-taiyi-challenger.sh @@ -0,0 +1,23 @@ +set -xe + +source "$(dirname "$0")/config.sh" + +if kurtosis enclave inspect $ENCLAVE_NAME >/dev/null 2>&1; then + export EXECUTION_CLIENT_WS_URL="ws://`kurtosis port print luban el-1-geth-lighthouse ws`" + export BEACON_URL="`kurtosis port print luban cl-1-lighthouse-geth http`" +fi + +# TAIYI_DEPLOYMENT_FILE="contracts/script/output/devnet/taiyiAddresses.json" +# if [ -f "$TAIYI_DEPLOYMENT_FILE" ]; then +# export TAIYI_CHALLENGER_ADDRESS=$(jq -r '.taiyiAddresses.taiyiChallengerProxy' "$TAIYI_DEPLOYMENT_FILE") +# fi + +export TAIYI_CHALLENGER_ADDRESS=0x0000000000000000000000000000000000000000 + +cargo run --bin taiyi-challenger -- \ + --execution-client-ws-url $EXECUTION_CLIENT_WS_URL \ + --beacon-url $BEACON_URL \ + --finalization-window 32 \ + --underwriter-stream-urls http://127.0.0.1:5656/commitments/v0/commitment_stream \ + --private-key 0xbf3beef3bd999ba9f2451e06936f0423cd62b815c9233dd3bc90f7e02a1e8673 \ + --taiyi-challenger-address $TAIYI_CHALLENGER_ADDRESS diff --git a/scripts/devnet/start-taiyi-preconfer.sh b/scripts/devnet/start-taiyi-preconfer.sh old mode 100644 new mode 100755 index 4f61432c..b24be3b0 --- a/scripts/devnet/start-taiyi-preconfer.sh +++ b/scripts/devnet/start-taiyi-preconfer.sh @@ -2,13 +2,17 @@ set -xe source "$(dirname "$0")/config.sh" -export EXECUTION_URL="http://`kurtosis port print luban el-1-geth-lighthouse rpc`" -export BEACON_URL="`kurtosis port print luban cl-1-lighthouse-geth http`" -export HELIX_URL="http://`kurtosis port print luban helix-relay api`" +if kurtosis enclave inspect $ENCLAVE_NAME >/dev/null 2>&1; then + export EXECUTION_URL="http://`kurtosis port print luban el-1-geth-lighthouse rpc`" + export BEACON_URL="`kurtosis port print luban cl-1-lighthouse-geth http`" + export HELIX_URL="http://`kurtosis port print luban helix-relay api`" +fi + cargo run --bin taiyi underwriter \ - --bls_sk 4942d3308d3fbfbdb977c0bf4c09cb6990aec9fd5ce24709eaf23d96dba71148 \ - --ecdsa_sk 0xc5114526e042343c6d1899cad05e1c00ba588314de9b96929914ee0df18d46b2 \ + --bls-sk 4942d3308d3fbfbdb977c0bf4c09cb6990aec9fd5ce24709eaf23d96dba71148 \ + --ecdsa-sk 0xc5114526e042343c6d1899cad05e1c00ba588314de9b96929914ee0df18d46b2 \ --network $WORKING_DIR/el_cl_genesis_data \ - --execution_client_url $EXECUTION_URL \ - --beacon_client_url $BEACON_URL \ - --relay_url $HELIX_URL + --execution-rpc-url $EXECUTION_URL \ + --beacon-rpc-url $BEACON_URL \ + --relay-url $HELIX_URL \ + --taiyi-escrow-address $TAIYI_CORE_ADDRESS diff --git a/scripts/devnet/submit-preconf-tx-type-a.sh b/scripts/devnet/submit-preconf-tx-type-a.sh new file mode 100755 index 00000000..ad50eab1 --- /dev/null +++ b/scripts/devnet/submit-preconf-tx-type-a.sh @@ -0,0 +1,14 @@ +set -xe + +source "$(dirname "$0")/config.sh" + +if kurtosis enclave inspect $ENCLAVE_NAME >/dev/null 2>&1; then + export EXECUTION_CLIENT_URL="http://$(kurtosis port print luban el-1-geth-lighthouse rpc)" + export BEACON_CLIENT_URL="$(kurtosis port print luban cl-1-lighthouse-geth http)" +fi + +export UNDERWRITER_URL="http://127.0.0.1:5656" +export PRIVATE_KEY="bf3beef3bd999ba9f2451e06936f0423cd62b815c9233dd3bc90f7e02a1e8673" +export UNDERWRITER_ADDRESS=0xD8F3183DEF51A987222D845be228e0Bbb932C222 + +cargo run -p type_a \ No newline at end of file diff --git a/scripts/devnet/submit-preconf-tx-type-b.sh b/scripts/devnet/submit-preconf-tx-type-b.sh new file mode 100755 index 00000000..a3481b16 --- /dev/null +++ b/scripts/devnet/submit-preconf-tx-type-b.sh @@ -0,0 +1,14 @@ +set -xe + +source "$(dirname "$0")/config.sh" + +if kurtosis enclave inspect $ENCLAVE_NAME >/dev/null 2>&1; then + export EXECUTION_CLIENT_URL="http://$(kurtosis port print luban el-1-geth-lighthouse rpc)" + export BEACON_CLIENT_URL="$(kurtosis port print luban cl-1-lighthouse-geth http)" +fi + +export UNDERWRITER_URL="http://127.0.0.1:5656" +export PRIVATE_KEY="bf3beef3bd999ba9f2451e06936f0423cd62b815c9233dd3bc90f7e02a1e8673" +export UNDERWRITER_ADDRESS=0xD8F3183DEF51A987222D845be228e0Bbb932C222 + +cargo run -p type_b \ No newline at end of file diff --git a/scripts/devnet/submit-preconf-tx.sh b/scripts/devnet/submit-preconf-tx.sh deleted file mode 100644 index 3692af88..00000000 --- a/scripts/devnet/submit-preconf-tx.sh +++ /dev/null @@ -1,7 +0,0 @@ -set -xe - -source "$(dirname "$0")/config.sh" - -export PRIVATE_KEY="bf3beef3bd999ba9f2451e06936f0423cd62b815c9233dd3bc90f7e02a1e8673" -export TAIYI_UNDERWRITER_URL="http://127.0.0.1:5656" -cargo run --example submit-preconf-request From 04bcb33c864781cecbc06da805de9babcf9efa6d Mon Sep 17 00:00:00 2001 From: martines3000 Date: Mon, 14 Apr 2025 17:47:41 +0200 Subject: [PATCH 04/15] chore: update opts params --- Cargo.lock | 2 +- bin/taiyi-challenger/src/main.rs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1520b3f2..56073e5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8908,7 +8908,7 @@ dependencies = [ [[package]] name = "taiyi-challenger" -version = "0.1.4" +version = "0.1.5" dependencies = [ "alloy-contract", "alloy-eips 0.12.6", diff --git a/bin/taiyi-challenger/src/main.rs b/bin/taiyi-challenger/src/main.rs index c67bffec..0d595ade 100644 --- a/bin/taiyi-challenger/src/main.rs +++ b/bin/taiyi-challenger/src/main.rs @@ -276,7 +276,7 @@ async fn handle_challenge_creation( open_challenge = true; } - if open_challenge { + if open_challenge || opts.always_open_challenges { let read_tx = challenge_db.begin_read().unwrap(); let table = read_tx.open_table(CHALLENGE_TABLE).unwrap(); let challenges = table.get(&challenge_submission_slot); @@ -315,7 +315,9 @@ async fn handle_challenge_creation( serde_json::from_str::(&preconf.preconf_request).unwrap(); // Check if all user txs are included in the block - if !tx_hashes.contains(preconf_request.transaction.unwrap().tx_hash()) { + if !tx_hashes.contains(preconf_request.transaction.unwrap().tx_hash()) + || opts.always_open_challenges + { let read_tx = challenge_db.begin_read().unwrap(); let table = read_tx.open_table(CHALLENGE_TABLE).unwrap(); let challenges = table.get(&slot); From a2bb51fd10052c2915e0e2f68125fc87a0a95dce Mon Sep 17 00:00:00 2001 From: martines3000 Date: Tue, 6 May 2025 16:04:39 +0200 Subject: [PATCH 05/15] fix: resolve lint issues --- bin/taiyi-challenger/src/main.rs | 601 ++++++++++++++---- .../src/preconf_request_data.rs | 4 +- 2 files changed, 483 insertions(+), 122 deletions(-) diff --git a/bin/taiyi-challenger/src/main.rs b/bin/taiyi-challenger/src/main.rs index 0d595ade..9a9aaa20 100644 --- a/bin/taiyi-challenger/src/main.rs +++ b/bin/taiyi-challenger/src/main.rs @@ -110,7 +110,7 @@ async fn handle_underwriter_stream(preconf_db: Arc, url: Url) -> eyre: let req = reqwest::Client::new().get(url); let mut event_source = EventSource::new(req).unwrap_or_else(|err| { - panic!("Failed to create EventSource: {:?}", err); + panic!("Failed to create EventSource: {err:?}"); }); while let Some(event) = event_source.next().await { @@ -118,9 +118,16 @@ async fn handle_underwriter_stream(preconf_db: Arc, url: Url) -> eyre: Ok(Event::Message(message)) => { let data = &message.data; - let parsed_data = - serde_json::from_str::>(data) - .unwrap(); + let parsed_data = match serde_json::from_str::< + Vec<(PreconfRequest, PreconfResponseData)>, + >(data) + { + Ok(data) => data, + Err(e) => { + error!("[Stream Ingestor]: Failed to parse preconf data: {}", e); + continue; + } + }; debug!("[Stream Ingestor]: Received {} preconfirmations", parsed_data.len()); @@ -138,23 +145,51 @@ async fn handle_underwriter_stream(preconf_db: Arc, url: Url) -> eyre: }, preconf_request: match preconf_request { PreconfRequest::TypeA(preconf_request) => { - serde_json::to_string(preconf_request).unwrap() + match serde_json::to_string(&preconf_request) { + Ok(s) => s, + Err(e) => { + error!("[Stream Ingestor]: Failed to serialize preconf request: {}", e); + continue; + } + } } PreconfRequest::TypeB(preconf_request) => { - serde_json::to_string(preconf_request).unwrap() + match serde_json::to_string(&preconf_request) { + Ok(s) => s, + Err(e) => { + error!("[Stream Ingestor]: Failed to serialize preconf request: {}", e); + continue; + } + } + } + }, + preconf_request_signature: match &preconf_response_data.commitment { + Some(commitment) => hex::encode(commitment.as_bytes()), + None => { + error!("[Stream Ingestor]: Missing commitment in preconf response"); + continue; } }, - preconf_request_signature: hex::encode( - preconf_response_data.commitment.unwrap().as_bytes(), - ), }; - let read_tx = preconf_db.begin_read().unwrap(); - let table = read_tx.open_table(PRECONF_TABLE).unwrap(); - let preconfs = table.get(&target_slot); + let read_tx = match preconf_db.begin_read() { + Ok(tx) => tx, + Err(e) => { + error!("[Stream Ingestor]: Failed to begin read transaction: {}", e); + continue; + } + }; + + let table = match read_tx.open_table(PRECONF_TABLE) { + Ok(table) => table, + Err(e) => { + error!("[Stream Ingestor]: Failed to open preconf table: {}", e); + continue; + } + }; + let preconfs = table.get(&target_slot); if preconfs.is_err() { - // Storage error error!( "[Stream Ingestor]: Storage error for slot {}. Error: {:?}", target_slot, @@ -163,18 +198,36 @@ async fn handle_underwriter_stream(preconf_db: Arc, url: Url) -> eyre: continue; } - let preconfs = preconfs.unwrap(); - let mut preconfs = - if preconfs.is_none() { Vec::new() } else { preconfs.unwrap().value() }; + let preconfs_result = match preconfs { + Ok(result) => result, + Err(e) => { + error!("[Stream Ingestor]: Failed to get preconfs: {}", e); + continue; + } + }; + + let mut preconf_values = if let Some(values) = preconfs_result { + values.value() + } else { + Vec::new() + }; + + preconf_values.push(preconf_request_data); - preconfs.push(preconf_request_data); + let write_result = (|| -> Result<(), redb::Error> { + let write_tx = preconf_db.begin_write()?; + { + let mut table = write_tx.open_table(PRECONF_TABLE)?; + table.insert(&target_slot, preconf_values)?; + } + write_tx.commit()?; + Ok(()) + })(); - let write_tx = preconf_db.begin_write().unwrap(); - { - let mut table = write_tx.open_table(PRECONF_TABLE).unwrap(); - table.insert(&target_slot, preconfs).unwrap(); + if let Err(e) = write_result { + error!("[Stream Ingestor]: Failed to write preconf data: {}", e); + continue; } - write_tx.commit().unwrap(); debug!("[Stream Ingestor]: Stored preconfirmation for slot {}", target_slot); } @@ -199,10 +252,22 @@ async fn handle_challenge_creation( ) -> eyre::Result<()> { // Create a ws provider let ws = WsConnect::new(&opts.execution_client_ws_url); - let provider = ProviderBuilder::new().on_ws(ws).await.unwrap(); + let provider = match ProviderBuilder::new().on_ws(ws).await { + Ok(provider) => provider, + Err(e) => { + error!("[Challenger Creator]: Failed to create provider: {}", e); + return Err(eyre::eyre!("Failed to create provider: {}", e)); + } + }; // Subscribe to block headers. - let subscription = provider.subscribe_blocks().await.unwrap(); + let subscription = match provider.subscribe_blocks().await { + Ok(sub) => sub, + Err(e) => { + error!("[Challenger Creator]: Failed to subscribe to blocks: {}", e); + return Err(eyre::eyre!("Failed to subscribe to blocks: {}", e)); + } + }; let mut stream = subscription.into_stream(); while let Some(header) = stream.next().await { @@ -211,12 +276,25 @@ async fn handle_challenge_creation( debug!("[Challenger Creator]: Slot: {:?}", slot); // Check if preconfirmations exists for the slot - let read_tx = preconf_db.begin_read().unwrap(); - let table = read_tx.open_table(PRECONF_TABLE).unwrap(); + let read_tx = match preconf_db.begin_read() { + Ok(tx) => tx, + Err(e) => { + error!("[Challenger Creator]: Failed to begin read transaction: {}", e); + continue; + } + }; + + let table = match read_tx.open_table(PRECONF_TABLE) { + Ok(table) => table, + Err(e) => { + error!("[Challenger Creator]: Failed to open preconf table: {}", e); + continue; + } + }; + let preconfs = table.get(&slot); if preconfs.is_err() { - // Storage error error!( "[Challenger Creator]: Storage error for slot {}. Error: {:?}", slot, @@ -225,15 +303,27 @@ async fn handle_challenge_creation( continue; } - let preconfs = preconfs.unwrap(); + let preconfs_result = match preconfs { + Ok(result) => result, + Err(e) => { + error!("[Challenger Creator]: Failed to get preconfs: {}", e); + continue; + } + }; - if preconfs.is_none() { + if preconfs_result.is_none() { // No preconfirmation found for the slot debug!("[Challenger Creator]: No preconfirmations found for slot {}", slot); continue; } - let preconfs = preconfs.unwrap().value(); + let preconfs = match preconfs_result { + Some(values) => values.value(), + None => { + debug!("[Challenger Creator]: No preconfirmations found for slot {}", slot); + continue; + } + }; debug!("[Challenger Creator]: Found {} preconfirmations for slot {}", preconfs.len(), slot); @@ -249,7 +339,18 @@ async fn handle_challenge_creation( continue; } - let block = block.unwrap().unwrap(); + let block = match block { + Ok(Some(b)) => b, + Ok(None) => { + error!("[Challenger Creator]: Block {} not found", header.number); + continue; + } + Err(e) => { + error!("[Challenger Creator]: Failed to get block {}: {}", header.number, e); + continue; + } + }; + let tx_hashes = block.transactions.hashes().collect::>(); // Calculate the challenge submission slot. We need to wait for the block to be finalized @@ -263,7 +364,17 @@ async fn handle_challenge_creation( if preconf_type == 0 { // Type A let preconf_request = - serde_json::from_str::(&preconf.preconf_request).unwrap(); + match serde_json::from_str::(&preconf.preconf_request) { + Ok(req) => req, + Err(e) => { + error!( + "[Challenger Creator]: Failed to parse PreconfRequestTypeA: {}", + e + ); + continue; + } + }; + let mut open_challenge = false; // Check if all user txs are included in the block @@ -277,12 +388,25 @@ async fn handle_challenge_creation( } if open_challenge || opts.always_open_challenges { - let read_tx = challenge_db.begin_read().unwrap(); - let table = read_tx.open_table(CHALLENGE_TABLE).unwrap(); + let read_tx = match challenge_db.begin_read() { + Ok(tx) => tx, + Err(e) => { + error!("[Challenger Creator]: Failed to begin read transaction: {}", e); + continue; + } + }; + + let table = match read_tx.open_table(CHALLENGE_TABLE) { + Ok(table) => table, + Err(e) => { + error!("[Challenger Creator]: Failed to open challenge table: {}", e); + continue; + } + }; + let challenges = table.get(&challenge_submission_slot); if challenges.is_err() { - // Storage error error!( "[Challenger Creator]: Storage error for slot {}. Error: {:?}", challenge_submission_slot, @@ -291,18 +415,36 @@ async fn handle_challenge_creation( continue; } - let challenges = challenges.unwrap(); - let mut challenges = - if challenges.is_none() { Vec::new() } else { challenges.unwrap().value() }; + let challenges_result = match challenges { + Ok(result) => result, + Err(e) => { + error!("[Challenger Creator]: Failed to get challenges: {}", e); + continue; + } + }; + + let mut challenges_data = if let Some(values) = challenges_result { + values.value() + } else { + Vec::new() + }; + + challenges_data.push(preconf); - challenges.push(preconf); + let write_result = (|| -> Result<(), redb::Error> { + let write_tx = challenge_db.begin_write()?; + { + let mut table = write_tx.open_table(CHALLENGE_TABLE)?; + table.insert(&challenge_submission_slot, challenges_data)?; + } + write_tx.commit()?; + Ok(()) + })(); - let write_tx = challenge_db.begin_write().unwrap(); - { - let mut table = write_tx.open_table(CHALLENGE_TABLE).unwrap(); - table.insert(&challenge_submission_slot, challenges).unwrap(); + if let Err(e) = write_result { + error!("[Challenger Creator]: Failed to write challenge data: {}", e); + continue; } - write_tx.commit().unwrap(); debug!( "[Challenger Creator]: Stored challenge for slot {}", @@ -312,18 +454,46 @@ async fn handle_challenge_creation( } else { // Type B let preconf_request = - serde_json::from_str::(&preconf.preconf_request).unwrap(); + match serde_json::from_str::(&preconf.preconf_request) { + Ok(req) => req, + Err(e) => { + error!( + "[Challenger Creator]: Failed to parse PreconfRequestTypeB: {}", + e + ); + continue; + } + }; + + let transaction = match &preconf_request.transaction { + Some(tx) => tx, + None => { + error!("[Challenger Creator]: Missing transaction in PreconfRequestTypeB"); + continue; + } + }; // Check if all user txs are included in the block - if !tx_hashes.contains(preconf_request.transaction.unwrap().tx_hash()) - || opts.always_open_challenges - { - let read_tx = challenge_db.begin_read().unwrap(); - let table = read_tx.open_table(CHALLENGE_TABLE).unwrap(); + if !tx_hashes.contains(transaction.tx_hash()) || opts.always_open_challenges { + let read_tx = match challenge_db.begin_read() { + Ok(tx) => tx, + Err(e) => { + error!("[Challenger Creator]: Failed to begin read transaction: {}", e); + continue; + } + }; + + let table = match read_tx.open_table(CHALLENGE_TABLE) { + Ok(table) => table, + Err(e) => { + error!("[Challenger Creator]: Failed to open challenge table: {}", e); + continue; + } + }; + let challenges = table.get(&slot); if challenges.is_err() { - // Storage error error!( "[Challenger Creator]: Storage error for slot {}. Error: {:?}", slot, @@ -332,18 +502,36 @@ async fn handle_challenge_creation( continue; } - let challenges = challenges.unwrap(); - let mut challenges = - if challenges.is_none() { Vec::new() } else { challenges.unwrap().value() }; + let challenges_result = match challenges { + Ok(result) => result, + Err(e) => { + error!("[Challenger Creator]: Failed to get challenges: {}", e); + continue; + } + }; + + let mut challenges_data = if let Some(values) = challenges_result { + values.value() + } else { + Vec::new() + }; + + challenges_data.push(preconf); - challenges.push(preconf); + let write_result = (|| -> Result<(), redb::Error> { + let write_tx = challenge_db.begin_write()?; + { + let mut table = write_tx.open_table(CHALLENGE_TABLE)?; + table.insert(&slot, challenges_data)?; + } + write_tx.commit()?; + Ok(()) + })(); - let write_tx = challenge_db.begin_write().unwrap(); - { - let mut table = write_tx.open_table(CHALLENGE_TABLE).unwrap(); - table.insert(&slot, challenges).unwrap(); + if let Err(e) = write_result { + error!("[Challenger Creator]: Failed to write challenge data: {}", e); + continue; } - write_tx.commit().unwrap(); debug!("[Challenger Creator]: Stored challenge for slot {}", slot); } @@ -362,46 +550,86 @@ async fn handle_challenge_submission( genesis_timestamp: u64, ) -> eyre::Result<()> { // Initialize signer - let private_key_signer = alloy_signer_local::PrivateKeySigner::from_signing_key( - k256::ecdsa::SigningKey::from_slice( - &hex::decode(opts.private_key.strip_prefix("0x").unwrap_or(&opts.private_key)).unwrap(), - ) - .unwrap(), - ); + let private_key_bytes = + match hex::decode(opts.private_key.strip_prefix("0x").unwrap_or(&opts.private_key)) { + Ok(bytes) => bytes, + Err(e) => { + error!("[Challenger Submitter]: Failed to decode private key: {}", e); + return Err(eyre::eyre!("Failed to decode private key: {}", e)); + } + }; + + let signing_key = match k256::ecdsa::SigningKey::from_slice(&private_key_bytes) { + Ok(key) => key, + Err(e) => { + error!("[Challenger Submitter]: Failed to create signing key: {}", e); + return Err(eyre::eyre!("Failed to create signing key: {}", e)); + } + }; + + let private_key_signer = alloy_signer_local::PrivateKeySigner::from_signing_key(signing_key); let signer_address = private_key_signer.address(); debug!("[Challenger Submitter]: Signer address: {}", signer_address); let ws = WsConnect::new(&opts.execution_client_ws_url); - let provider = ProviderBuilder::new().wallet(private_key_signer).on_ws(ws).await.unwrap(); + let provider = match ProviderBuilder::new().wallet(private_key_signer).on_ws(ws).await { + Ok(p) => p, + Err(e) => { + error!("[Challenger Submitter]: Failed to create provider with wallet: {}", e); + return Err(eyre::eyre!("Failed to create provider with wallet: {}", e)); + } + }; - debug!( - "[Challenger Submitter]: Signer ETH balance: {}", - provider.get_balance(signer_address).await.unwrap() - ); + // Check balance to verify signer is working correctly + match provider.get_balance(signer_address).await { + Ok(balance) => { + debug!("[Challenger Submitter]: Signer ETH balance: {}", balance); + } + Err(e) => { + error!("[Challenger Submitter]: Failed to get signer balance: {}", e); + // Continue anyway, this is not critical + } + }; let taiyi_challenger = TaiyiInteractiveChallenger::new(opts.taiyi_challenger_address, provider.clone()); - let subscription = provider.subscribe_blocks().await.unwrap(); + let subscription = match provider.subscribe_blocks().await { + Ok(sub) => sub, + Err(e) => { + error!("[Challenger Submitter]: Failed to subscribe to blocks: {}", e); + return Err(eyre::eyre!("Failed to subscribe to blocks: {}", e)); + } + }; + let mut stream = subscription.into_stream(); while let Some(header) = stream.next().await { debug!("[Challenger Submitter]: Processing block {:?}", header.number); let slot = get_slot_from_timestamp(header.timestamp, genesis_timestamp); debug!("[Challenger Submitter]: Slot: {:?}", slot); - debug!( - "[Challenger Submitter]: Signer ETH balance: {}", - provider.get_balance(signer_address).await.unwrap() - ); // Check if challenges exists for the slot - let read_tx = challenge_db.begin_read().unwrap(); - let table = read_tx.open_table(CHALLENGE_TABLE).unwrap(); + let read_tx = match challenge_db.begin_read() { + Ok(tx) => tx, + Err(e) => { + error!("[Challenger Submitter]: Failed to begin read transaction: {}", e); + continue; + } + }; + + let table = match read_tx.open_table(CHALLENGE_TABLE) { + Ok(table) => table, + Err(e) => { + error!("[Challenger Submitter]: Failed to open challenge table: {}", e); + continue; + } + }; + let challenges = table.get(&slot); if challenges.is_err() { - // Storage error error!( "[Challenger Submitter]: Storage error for slot {}. Error: {:?}", slot, @@ -410,24 +638,49 @@ async fn handle_challenge_submission( continue; } - let challenges = challenges.unwrap(); + let challenges_result = match challenges { + Ok(result) => result, + Err(e) => { + error!("[Challenger Submitter]: Failed to get challenges: {}", e); + continue; + } + }; - if challenges.is_none() { + if challenges_result.is_none() { // No challenges found for the slot debug!("[Challenger Submitter]: No challenges found for slot {}", slot); continue; } - let challenges = challenges.unwrap().value(); - debug!("[Challenger Submitter]: Found {} challenges for slot {}", challenges.len(), slot); + let challenges_data = match challenges_result { + Some(values) => values.value(), + None => { + debug!("[Challenger Submitter]: No challenges found for slot {}", slot); + continue; + } + }; + + debug!( + "[Challenger Submitter]: Found {} challenges for slot {}", + challenges_data.len(), + slot + ); // For each challenge, check if the challenge is expired - for challenge in challenges { + for challenge in challenges_data { if challenge.preconf_type == 0 { // Type A let preconf_request = - serde_json::from_str::(&challenge.preconf_request) - .unwrap(); + match serde_json::from_str::(&challenge.preconf_request) { + Ok(req) => req, + Err(e) => { + error!( + "[Challenger Submitter]: Failed to parse PreconfRequestTypeA: {}", + e + ); + continue; + } + }; let mut txs: Vec = Vec::new(); @@ -442,33 +695,73 @@ async fn handle_challenge_submission( preconf_request.tip_transaction.encode_2718(&mut tip_tx); let tip_tx_raw = format!("0x{}", hex::encode(&tip_tx)); + let sequence_number = match preconf_request.sequence_number { + Some(num) => num, + None => { + error!("[Challenger Submitter]: Missing sequence number in PreconfRequestTypeA"); + continue; + } + }; + let preconf_request_a_type = TaiyiInteractiveChallenger::PreconfRequestAType { txs, tipTx: tip_tx_raw, slot: U256::from(slot), - sequenceNum: U256::from(preconf_request.sequence_number.unwrap()), + sequenceNum: U256::from(sequence_number), signer: preconf_request.signer, }; - // TODO: Check types here (hex encoding or not...) - let signature_bytes = - Bytes::from(hex::decode(challenge.preconf_request_signature).unwrap()); + // Decode signature + let signature_bytes = match hex::decode(&challenge.preconf_request_signature) { + Ok(bytes) => Bytes::from(bytes), + Err(e) => { + error!("[Challenger Submitter]: Failed to decode signature: {}", e); + continue; + } + }; - // TODO: Should we watch/wait for the transaction here ? - let _ = taiyi_challenger + // Submit challenge to the contract + debug!("[Challenger Submitter]: Submitting challenge type A for slot {}", slot); + match taiyi_challenger .createChallengeAType(preconf_request_a_type, signature_bytes) .send() .await - .unwrap(); + { + Ok(tx) => { + debug!("[Challenger Submitter]: Challenge type A submitted. TX: {:?}", tx); + } + Err(e) => { + error!("[Challenger Submitter]: Failed to create challenge type A: {}", e); + // Continue to next challenge, we may be able to submit others + } + } } else { // Type B let preconf_request = - serde_json::from_str::(&challenge.preconf_request) - .unwrap(); + match serde_json::from_str::(&challenge.preconf_request) { + Ok(req) => req, + Err(e) => { + error!( + "[Challenger Submitter]: Failed to parse PreconfRequestTypeB: {}", + e + ); + continue; + } + }; + + let transaction = match &preconf_request.transaction { + Some(tx) => tx, + None => { + error!( + "[Challenger Submitter]: Missing transaction in PreconfRequestTypeB" + ); + continue; + } + }; - let mut tx = Vec::new(); - preconf_request.transaction.unwrap().encode_2718(&mut tx); - let tx_raw = format!("0x{}", hex::encode(&tx)); + let mut tx_bytes = Vec::new(); + transaction.encode_2718(&mut tx_bytes); + let tx_raw = format!("0x{}", hex::encode(&tx_bytes)); let preconf_request_b_type = TaiyiInteractiveChallenger::PreconfRequestBType { blockspaceAllocation: TaiyiInteractiveChallenger::BlockspaceAllocation { @@ -480,24 +773,37 @@ async fn handle_challenge_submission( targetSlot: U256::from(preconf_request.allocation.target_slot), blobCount: U256::from(preconf_request.allocation.blob_count), }, - blockspaceAllocationSignature: - // TODO: Check types here (hex encoding or not...) - preconf_request.alloc_sig.as_bytes().into() - , + blockspaceAllocationSignature: preconf_request.alloc_sig.as_bytes().into(), rawTx: Bytes::from(tx_raw), // TODO: Can we remove this two fields ? underwriterSignedBlockspaceAllocation: Bytes::from([]), underwriterSignedRawTx: Bytes::from([]), }; - let signature_bytes = - Bytes::from(hex::decode(challenge.preconf_request_signature).unwrap()); + // Decode signature + let signature_bytes = match hex::decode(&challenge.preconf_request_signature) { + Ok(bytes) => Bytes::from(bytes), + Err(e) => { + error!("[Challenger Submitter]: Failed to decode signature: {}", e); + continue; + } + }; - let _ = taiyi_challenger + // Submit challenge to the contract + debug!("[Challenger Submitter]: Submitting challenge type B for slot {}", slot); + match taiyi_challenger .createChallengeBType(preconf_request_b_type, signature_bytes) .send() .await - .unwrap(); + { + Ok(tx) => { + debug!("[Challenger Submitter]: Challenge type B submitted. TX: {:?}", tx); + } + Err(e) => { + error!("[Challenger Submitter]: Failed to create challenge type B: {}", e); + // Continue to next challenge, we may be able to submit others + } + } } } @@ -532,33 +838,88 @@ async fn main() -> eyre::Result<()> { // Create tables if they don't exist debug!("Creating tables..."); - let tx = preconf_db.begin_write().unwrap(); - tx.open_table(PRECONF_TABLE).unwrap(); - tx.commit().unwrap(); - let tx = challenge_db.begin_write().unwrap(); - tx.open_table(CHALLENGE_TABLE).unwrap(); - tx.commit().unwrap(); + let create_preconf_table = || -> Result<(), redb::Error> { + let tx = preconf_db.begin_write()?; + tx.open_table(PRECONF_TABLE)?; + tx.commit()?; + Ok(()) + }; + + if let Err(e) = create_preconf_table() { + error!("Failed to create preconf table: {}", e); + return Err(eyre::eyre!("Failed to create preconf table: {}", e)); + } + + let create_challenge_table = || -> Result<(), redb::Error> { + let tx = challenge_db.begin_write()?; + tx.open_table(CHALLENGE_TABLE)?; + tx.commit()?; + Ok(()) + }; + + if let Err(e) = create_challenge_table() { + error!("Failed to create challenge table: {}", e); + return Err(eyre::eyre!("Failed to create challenge table: {}", e)); + } + debug!("Tables created successfully"); // Read genesis timestamp from Beacon API (/eth/v1/beacon/genesis) - let beacon_genesis_response = reqwest::Client::new() + let beacon_genesis_response = match reqwest::Client::new() .get(format!("{}/eth/v1/beacon/genesis", opts.beacon_url)) .send() .await - .unwrap(); + { + Ok(response) => response, + Err(e) => { + error!("Failed to get beacon genesis: {}", e); + return Err(eyre::eyre!("Failed to get beacon genesis: {}", e)); + } + }; - let beacon_genesis_response = - beacon_genesis_response.json::().await.unwrap(); - let genesis_timestamp = - u64::from_str(&beacon_genesis_response["data"]["genesis_time"].as_str().unwrap()).unwrap(); + let beacon_genesis_response = match beacon_genesis_response.json::().await { + Ok(value) => value, + Err(e) => { + error!("Failed to parse beacon genesis response: {}", e); + return Err(eyre::eyre!("Failed to parse beacon genesis response: {}", e)); + } + }; + + let genesis_time = + beacon_genesis_response["data"]["genesis_time"].as_str().ok_or_else(|| { + let err = "Failed to get genesis time from response"; + error!("{}", err); + eyre::eyre!("{}", err) + })?; + + let genesis_timestamp = match u64::from_str(genesis_time) { + Ok(timestamp) => timestamp, + Err(e) => { + error!("Failed to parse genesis time: {}", e); + return Err(eyre::eyre!("Failed to parse genesis time: {}", e)); + } + }; let mut handles = Vec::new(); // Handles for ingesting underwriter streams let underwriter_stream_urls = opts.underwriter_stream_urls.clone(); - let underwriter_stream_urls = - underwriter_stream_urls.iter().map(|url| Url::parse(&url).unwrap()).collect::>(); + let underwriter_stream_urls = underwriter_stream_urls + .iter() + .filter_map(|url| match Url::parse(url) { + Ok(parsed_url) => Some(parsed_url), + Err(e) => { + error!("Failed to parse URL '{}': {}", url, e); + None + } + }) + .collect::>(); + + if underwriter_stream_urls.is_empty() { + error!("No valid underwriter stream URLs provided"); + return Err(eyre::eyre!("No valid underwriter stream URLs provided")); + } for url in underwriter_stream_urls { let handle = tokio::spawn(handle_underwriter_stream(preconf_db.clone(), url)); diff --git a/bin/taiyi-challenger/src/preconf_request_data.rs b/bin/taiyi-challenger/src/preconf_request_data.rs index fbd187d3..c713184a 100644 --- a/bin/taiyi-challenger/src/preconf_request_data.rs +++ b/bin/taiyi-challenger/src/preconf_request_data.rs @@ -36,7 +36,7 @@ where where Self: 'a, { - decode_from_slice(data, bincode::config::standard()).unwrap().0 + decode_from_slice(data, bincode::config::standard()).expect("Failed to decode bincode").0 } fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a> @@ -44,7 +44,7 @@ where Self: 'a, Self: 'b, { - encode_to_vec(value, bincode::config::standard()).unwrap() + encode_to_vec(value, bincode::config::standard()).expect("Failed to encode bincode") } fn type_name() -> TypeName { From d5c938fcbbc7d1985b801cc37e5fcc2eb3236676 Mon Sep 17 00:00:00 2001 From: martines3000 Date: Tue, 6 May 2025 16:07:32 +0200 Subject: [PATCH 06/15] fix: remove unused dependency --- Cargo.lock | 1 - bin/taiyi-challenger/Cargo.toml | 1 - bin/taiyi-challenger/src/main.rs | 4 ++-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 56073e5b..4c4be92b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8924,7 +8924,6 @@ dependencies = [ "redb", "reqwest 0.12.9", "reqwest-eventsource", - "serde", "serde_json", "taiyi-primitives", "tokio", diff --git a/bin/taiyi-challenger/Cargo.toml b/bin/taiyi-challenger/Cargo.toml index c8fede11..83b99872 100644 --- a/bin/taiyi-challenger/Cargo.toml +++ b/bin/taiyi-challenger/Cargo.toml @@ -22,7 +22,6 @@ futures-util = { workspace = true } redb = { workspace = true } reqwest = { workspace = true } reqwest-eventsource = "0.6" -serde = { workspace = true } serde_json = { workspace = true } taiyi-primitives = { workspace = true } tokio = { workspace = true } diff --git a/bin/taiyi-challenger/src/main.rs b/bin/taiyi-challenger/src/main.rs index 9a9aaa20..e00ca529 100644 --- a/bin/taiyi-challenger/src/main.rs +++ b/bin/taiyi-challenger/src/main.rs @@ -823,14 +823,14 @@ async fn main() -> eyre::Result<()> { tracing_subscriber::fmt().with_max_level(LevelFilter::DEBUG).init(); let preconf_db = Database::create("preconf.db").unwrap_or_else(|e| { - eprintln!("Failed to create preconf database: {}", e); + eprintln!("Failed to create preconf database: {e}"); std::process::exit(1); }); let preconf_db = Arc::new(preconf_db); let challenge_db = Database::create("challenge.db").unwrap_or_else(|e| { - eprintln!("Failed to create challenge database: {}", e); + eprintln!("Failed to create challenge database: {e}"); std::process::exit(1); }); From 88b37316a602b3b0b16de40226a121dff4be4dfe Mon Sep 17 00:00:00 2001 From: martines3000 Date: Fri, 9 May 2025 09:31:27 +0200 Subject: [PATCH 07/15] fix: move functions out of `main.rs` --- .../src/handle_challenge_creation.rs | 314 +++++++ .../src/handle_challenge_submission.rs | 337 ++++++++ .../src/handle_underwriter_stream.rs | 149 ++++ bin/taiyi-challenger/src/main.rs | 793 +----------------- bin/taiyi-challenger/src/table_definitions.rs | 8 + 5 files changed, 822 insertions(+), 779 deletions(-) create mode 100644 bin/taiyi-challenger/src/handle_challenge_creation.rs create mode 100644 bin/taiyi-challenger/src/handle_challenge_submission.rs create mode 100644 bin/taiyi-challenger/src/handle_underwriter_stream.rs create mode 100644 bin/taiyi-challenger/src/table_definitions.rs diff --git a/bin/taiyi-challenger/src/handle_challenge_creation.rs b/bin/taiyi-challenger/src/handle_challenge_creation.rs new file mode 100644 index 00000000..ba53d0ad --- /dev/null +++ b/bin/taiyi-challenger/src/handle_challenge_creation.rs @@ -0,0 +1,314 @@ +use std::{collections::HashSet, sync::Arc}; + +use alloy_eips::BlockNumberOrTag; +use alloy_provider::{Provider, ProviderBuilder, WsConnect}; +use futures_util::StreamExt; +use redb::Database; +use taiyi_primitives::{PreconfRequestTypeA, PreconfRequestTypeB}; +use tracing::{debug, error}; + +use crate::{ + get_slot_from_timestamp, + table_definitions::{CHALLENGE_TABLE, PRECONF_TABLE}, + Opts, +}; + +pub async fn handle_challenge_creation( + preconf_db: Arc, + challenge_db: Arc, + opts: Arc, + genesis_timestamp: u64, +) -> eyre::Result<()> { + // Create a ws provider + let ws = WsConnect::new(&opts.execution_client_ws_url); + let provider = match ProviderBuilder::new().on_ws(ws).await { + Ok(provider) => provider, + Err(e) => { + error!("[Challenger Creator]: Failed to create provider: {}", e); + return Err(eyre::eyre!("Failed to create provider: {}", e)); + } + }; + + // Subscribe to block headers. + let subscription = match provider.subscribe_blocks().await { + Ok(sub) => sub, + Err(e) => { + error!("[Challenger Creator]: Failed to subscribe to blocks: {}", e); + return Err(eyre::eyre!("Failed to subscribe to blocks: {}", e)); + } + }; + let mut stream = subscription.into_stream(); + + while let Some(header) = stream.next().await { + debug!("[Challenger Creator]: Processing block {:?}", header.number); + let slot = get_slot_from_timestamp(header.timestamp, genesis_timestamp); + debug!("[Challenger Creator]: Slot: {:?}", slot); + + // Check if preconfirmations exists for the slot + let read_tx = match preconf_db.begin_read() { + Ok(tx) => tx, + Err(e) => { + error!("[Challenger Creator]: Failed to begin read transaction: {}", e); + continue; + } + }; + + let table = match read_tx.open_table(PRECONF_TABLE) { + Ok(table) => table, + Err(e) => { + error!("[Challenger Creator]: Failed to open preconf table: {}", e); + continue; + } + }; + + let preconfs = table.get(&slot); + + if preconfs.is_err() { + error!( + "[Challenger Creator]: Storage error for slot {}. Error: {:?}", + slot, + preconfs.err() + ); + continue; + } + + let preconfs_result = match preconfs { + Ok(result) => result, + Err(e) => { + error!("[Challenger Creator]: Failed to get preconfs: {}", e); + continue; + } + }; + + if preconfs_result.is_none() { + // No preconfirmation found for the slot + debug!("[Challenger Creator]: No preconfirmations found for slot {}", slot); + continue; + } + + let preconfs = match preconfs_result { + Some(values) => values.value(), + None => { + debug!("[Challenger Creator]: No preconfirmations found for slot {}", slot); + continue; + } + }; + + debug!("[Challenger Creator]: Found {} preconfirmations for slot {}", preconfs.len(), slot); + + let block = provider.get_block_by_number(BlockNumberOrTag::Number(header.number)).await; + + if block.is_err() { + // RPC error + error!( + "[Challenger Creator]: RPC error for block {}. Error: {:?}", + header.number, + block.err() + ); + continue; + } + + let block = match block { + Ok(Some(b)) => b, + Ok(None) => { + error!("[Challenger Creator]: Block {} not found", header.number); + continue; + } + Err(e) => { + error!("[Challenger Creator]: Failed to get block {}: {}", header.number, e); + continue; + } + }; + + let tx_hashes = block.transactions.hashes().collect::>(); + + // Calculate the challenge submission slot. We need to wait for the block to be finalized + // before we can open a challenge. + let challenge_submission_slot = slot + opts.finalization_window; + + // For each preconfirmation, check if the required txs are included in the block + for preconf in preconfs { + let preconf_type = preconf.preconf_type; + + if preconf_type == 0 { + // Type A + let preconf_request = + match serde_json::from_str::(&preconf.preconf_request) { + Ok(req) => req, + Err(e) => { + error!( + "[Challenger Creator]: Failed to parse PreconfRequestTypeA: {}", + e + ); + continue; + } + }; + + let mut open_challenge = false; + + // Check if all user txs are included in the block + if !preconf_request.preconf_tx.iter().all(|tx| tx_hashes.contains(tx.tx_hash())) { + open_challenge = true; + } + + // Check if tip transaction is included in the block + if !tx_hashes.contains(preconf_request.tip_transaction.tx_hash()) { + open_challenge = true; + } + + if open_challenge || opts.always_open_challenges { + let read_tx = match challenge_db.begin_read() { + Ok(tx) => tx, + Err(e) => { + error!("[Challenger Creator]: Failed to begin read transaction: {}", e); + continue; + } + }; + + let table = match read_tx.open_table(CHALLENGE_TABLE) { + Ok(table) => table, + Err(e) => { + error!("[Challenger Creator]: Failed to open challenge table: {}", e); + continue; + } + }; + + let challenges = table.get(&challenge_submission_slot); + + if challenges.is_err() { + error!( + "[Challenger Creator]: Storage error for slot {}. Error: {:?}", + challenge_submission_slot, + challenges.err() + ); + continue; + } + + let challenges_result = match challenges { + Ok(result) => result, + Err(e) => { + error!("[Challenger Creator]: Failed to get challenges: {}", e); + continue; + } + }; + + let mut challenges_data = if let Some(values) = challenges_result { + values.value() + } else { + Vec::new() + }; + + challenges_data.push(preconf); + + let write_result = (|| -> Result<(), redb::Error> { + let write_tx = challenge_db.begin_write()?; + { + let mut table = write_tx.open_table(CHALLENGE_TABLE)?; + table.insert(&challenge_submission_slot, challenges_data)?; + } + write_tx.commit()?; + Ok(()) + })(); + + if let Err(e) = write_result { + error!("[Challenger Creator]: Failed to write challenge data: {}", e); + continue; + } + + debug!( + "[Challenger Creator]: Stored challenge for slot {}", + challenge_submission_slot + ); + } + } else { + // Type B + let preconf_request = + match serde_json::from_str::(&preconf.preconf_request) { + Ok(req) => req, + Err(e) => { + error!( + "[Challenger Creator]: Failed to parse PreconfRequestTypeB: {}", + e + ); + continue; + } + }; + + let transaction = match &preconf_request.transaction { + Some(tx) => tx, + None => { + error!("[Challenger Creator]: Missing transaction in PreconfRequestTypeB"); + continue; + } + }; + + // Check if all user txs are included in the block + if !tx_hashes.contains(transaction.tx_hash()) || opts.always_open_challenges { + let read_tx = match challenge_db.begin_read() { + Ok(tx) => tx, + Err(e) => { + error!("[Challenger Creator]: Failed to begin read transaction: {}", e); + continue; + } + }; + + let table = match read_tx.open_table(CHALLENGE_TABLE) { + Ok(table) => table, + Err(e) => { + error!("[Challenger Creator]: Failed to open challenge table: {}", e); + continue; + } + }; + + let challenges = table.get(&slot); + + if challenges.is_err() { + error!( + "[Challenger Creator]: Storage error for slot {}. Error: {:?}", + slot, + challenges.err() + ); + continue; + } + + let challenges_result = match challenges { + Ok(result) => result, + Err(e) => { + error!("[Challenger Creator]: Failed to get challenges: {}", e); + continue; + } + }; + + let mut challenges_data = if let Some(values) = challenges_result { + values.value() + } else { + Vec::new() + }; + + challenges_data.push(preconf); + + let write_result = (|| -> Result<(), redb::Error> { + let write_tx = challenge_db.begin_write()?; + { + let mut table = write_tx.open_table(CHALLENGE_TABLE)?; + table.insert(&slot, challenges_data)?; + } + write_tx.commit()?; + Ok(()) + })(); + + if let Err(e) = write_result { + error!("[Challenger Creator]: Failed to write challenge data: {}", e); + continue; + } + + debug!("[Challenger Creator]: Stored challenge for slot {}", slot); + } + } + } + + debug!("[Challenger Creator]: Processed block {:?}", header.number); + } + + Ok(()) +} diff --git a/bin/taiyi-challenger/src/handle_challenge_submission.rs b/bin/taiyi-challenger/src/handle_challenge_submission.rs new file mode 100644 index 00000000..051d246a --- /dev/null +++ b/bin/taiyi-challenger/src/handle_challenge_submission.rs @@ -0,0 +1,337 @@ +use alloy_sol_types::sol; +use std::sync::Arc; + +use alloy_eips::eip2718::Encodable2718; +use alloy_primitives::{hex, Bytes, U256}; +use alloy_provider::{Provider, ProviderBuilder, WsConnect}; +use alloy_signer::k256::{self}; +use futures_util::StreamExt; +use redb::Database; +use taiyi_primitives::{PreconfRequestTypeA, PreconfRequestTypeB}; + +use tracing::{debug, error}; + +use crate::{get_slot_from_timestamp, table_definitions::CHALLENGE_TABLE, Opts}; + +sol! { + #[sol(rpc)] + contract TaiyiInteractiveChallenger { + #[derive(Debug)] + struct PreconfRequestAType { + string[] txs; + string tipTx; + uint256 slot; + uint256 sequenceNum; + address signer; + } + + #[derive(Debug)] + struct BlockspaceAllocation { + uint256 gasLimit; + address sender; + address recipient; + uint256 deposit; + uint256 tip; + uint256 targetSlot; + uint256 blobCount; + } + + #[derive(Debug)] + struct PreconfRequestBType { + BlockspaceAllocation blockspaceAllocation; + bytes blockspaceAllocationSignature; + bytes underwriterSignedBlockspaceAllocation; + bytes rawTx; + bytes underwriterSignedRawTx; + } + + + #[derive(Debug)] + function createChallengeAType( + PreconfRequestAType calldata preconfRequestAType, + bytes calldata signature + ) + external + payable; + + #[derive(Debug)] + function createChallengeBType( + PreconfRequestBType calldata preconfRequestBType, + bytes calldata signature + ) + external + payable; + + #[derive(Debug)] + function resolveExpiredChallenge(bytes32 id) external; + } +} + +pub async fn handle_challenge_submission( + challenge_db: Arc, + opts: Arc, + genesis_timestamp: u64, +) -> eyre::Result<()> { + // Initialize signer + let private_key_bytes = + match hex::decode(opts.private_key.strip_prefix("0x").unwrap_or(&opts.private_key)) { + Ok(bytes) => bytes, + Err(e) => { + error!("[Challenger Submitter]: Failed to decode private key: {}", e); + return Err(eyre::eyre!("Failed to decode private key: {}", e)); + } + }; + + let signing_key = match k256::ecdsa::SigningKey::from_slice(&private_key_bytes) { + Ok(key) => key, + Err(e) => { + error!("[Challenger Submitter]: Failed to create signing key: {}", e); + return Err(eyre::eyre!("Failed to create signing key: {}", e)); + } + }; + + let private_key_signer = alloy_signer_local::PrivateKeySigner::from_signing_key(signing_key); + let signer_address = private_key_signer.address(); + + debug!("[Challenger Submitter]: Signer address: {}", signer_address); + + let ws = WsConnect::new(&opts.execution_client_ws_url); + let provider = match ProviderBuilder::new().wallet(private_key_signer).on_ws(ws).await { + Ok(p) => p, + Err(e) => { + error!("[Challenger Submitter]: Failed to create provider with wallet: {}", e); + return Err(eyre::eyre!("Failed to create provider with wallet: {}", e)); + } + }; + + // Check balance to verify signer is working correctly + match provider.get_balance(signer_address).await { + Ok(balance) => { + debug!("[Challenger Submitter]: Signer ETH balance: {}", balance); + } + Err(e) => { + error!("[Challenger Submitter]: Failed to get signer balance: {}", e); + // Continue anyway, this is not critical + } + }; + + let taiyi_challenger = + TaiyiInteractiveChallenger::new(opts.taiyi_challenger_address, provider.clone()); + + let subscription = match provider.subscribe_blocks().await { + Ok(sub) => sub, + Err(e) => { + error!("[Challenger Submitter]: Failed to subscribe to blocks: {}", e); + return Err(eyre::eyre!("Failed to subscribe to blocks: {}", e)); + } + }; + + let mut stream = subscription.into_stream(); + + while let Some(header) = stream.next().await { + debug!("[Challenger Submitter]: Processing block {:?}", header.number); + let slot = get_slot_from_timestamp(header.timestamp, genesis_timestamp); + debug!("[Challenger Submitter]: Slot: {:?}", slot); + + // Check if challenges exists for the slot + let read_tx = match challenge_db.begin_read() { + Ok(tx) => tx, + Err(e) => { + error!("[Challenger Submitter]: Failed to begin read transaction: {}", e); + continue; + } + }; + + let table = match read_tx.open_table(CHALLENGE_TABLE) { + Ok(table) => table, + Err(e) => { + error!("[Challenger Submitter]: Failed to open challenge table: {}", e); + continue; + } + }; + + let challenges = table.get(&slot); + + if challenges.is_err() { + error!( + "[Challenger Submitter]: Storage error for slot {}. Error: {:?}", + slot, + challenges.err() + ); + continue; + } + + let challenges_result = match challenges { + Ok(result) => result, + Err(e) => { + error!("[Challenger Submitter]: Failed to get challenges: {}", e); + continue; + } + }; + + if challenges_result.is_none() { + // No challenges found for the slot + debug!("[Challenger Submitter]: No challenges found for slot {}", slot); + continue; + } + + let challenges_data = match challenges_result { + Some(values) => values.value(), + None => { + debug!("[Challenger Submitter]: No challenges found for slot {}", slot); + continue; + } + }; + + debug!( + "[Challenger Submitter]: Found {} challenges for slot {}", + challenges_data.len(), + slot + ); + + // For each challenge, check if the challenge is expired + for challenge in challenges_data { + if challenge.preconf_type == 0 { + // Type A + let preconf_request = + match serde_json::from_str::(&challenge.preconf_request) { + Ok(req) => req, + Err(e) => { + error!( + "[Challenger Submitter]: Failed to parse PreconfRequestTypeA: {}", + e + ); + continue; + } + }; + + let mut txs: Vec = Vec::new(); + + for tx in preconf_request.preconf_tx { + let mut tx_bytes = Vec::new(); + tx.encode_2718(&mut tx_bytes); + let hex_encoded_tx = format!("0x{}", hex::encode(&tx_bytes)); + txs.push(hex_encoded_tx); + } + + let mut tip_tx = Vec::new(); + preconf_request.tip_transaction.encode_2718(&mut tip_tx); + let tip_tx_raw = format!("0x{}", hex::encode(&tip_tx)); + + let sequence_number = match preconf_request.sequence_number { + Some(num) => num, + None => { + error!("[Challenger Submitter]: Missing sequence number in PreconfRequestTypeA"); + continue; + } + }; + + let preconf_request_a_type = TaiyiInteractiveChallenger::PreconfRequestAType { + txs, + tipTx: tip_tx_raw, + slot: U256::from(slot), + sequenceNum: U256::from(sequence_number), + signer: preconf_request.signer, + }; + + // Decode signature + let signature_bytes = match hex::decode(&challenge.preconf_request_signature) { + Ok(bytes) => Bytes::from(bytes), + Err(e) => { + error!("[Challenger Submitter]: Failed to decode signature: {}", e); + continue; + } + }; + + // Submit challenge to the contract + debug!("[Challenger Submitter]: Submitting challenge type A for slot {}", slot); + match taiyi_challenger + .createChallengeAType(preconf_request_a_type, signature_bytes) + .send() + .await + { + Ok(tx) => { + debug!("[Challenger Submitter]: Challenge type A submitted. TX: {:?}", tx); + } + Err(e) => { + error!("[Challenger Submitter]: Failed to create challenge type A: {}", e); + // Continue to next challenge, we may be able to submit others + } + } + } else { + // Type B + let preconf_request = + match serde_json::from_str::(&challenge.preconf_request) { + Ok(req) => req, + Err(e) => { + error!( + "[Challenger Submitter]: Failed to parse PreconfRequestTypeB: {}", + e + ); + continue; + } + }; + + let transaction = match &preconf_request.transaction { + Some(tx) => tx, + None => { + error!( + "[Challenger Submitter]: Missing transaction in PreconfRequestTypeB" + ); + continue; + } + }; + + let mut tx_bytes = Vec::new(); + transaction.encode_2718(&mut tx_bytes); + let tx_raw = format!("0x{}", hex::encode(&tx_bytes)); + + let preconf_request_b_type = TaiyiInteractiveChallenger::PreconfRequestBType { + blockspaceAllocation: TaiyiInteractiveChallenger::BlockspaceAllocation { + gasLimit: U256::from(preconf_request.allocation.gas_limit), + sender: preconf_request.allocation.sender, + recipient: preconf_request.allocation.recipient, + deposit: U256::from(preconf_request.allocation.deposit), + tip: U256::from(preconf_request.allocation.tip), + targetSlot: U256::from(preconf_request.allocation.target_slot), + blobCount: U256::from(preconf_request.allocation.blob_count), + }, + blockspaceAllocationSignature: preconf_request.alloc_sig.as_bytes().into(), + rawTx: Bytes::from(tx_raw), + // TODO: Can we remove this two fields ? + underwriterSignedBlockspaceAllocation: Bytes::from([]), + underwriterSignedRawTx: Bytes::from([]), + }; + + // Decode signature + let signature_bytes = match hex::decode(&challenge.preconf_request_signature) { + Ok(bytes) => Bytes::from(bytes), + Err(e) => { + error!("[Challenger Submitter]: Failed to decode signature: {}", e); + continue; + } + }; + + // Submit challenge to the contract + debug!("[Challenger Submitter]: Submitting challenge type B for slot {}", slot); + match taiyi_challenger + .createChallengeBType(preconf_request_b_type, signature_bytes) + .send() + .await + { + Ok(tx) => { + debug!("[Challenger Submitter]: Challenge type B submitted. TX: {:?}", tx); + } + Err(e) => { + error!("[Challenger Submitter]: Failed to create challenge type B: {}", e); + // Continue to next challenge, we may be able to submit others + } + } + } + } + + debug!("[Challenger Submitter]: Processed block {:?}", header.number); + } + + Ok(()) +} diff --git a/bin/taiyi-challenger/src/handle_underwriter_stream.rs b/bin/taiyi-challenger/src/handle_underwriter_stream.rs new file mode 100644 index 00000000..1f16b765 --- /dev/null +++ b/bin/taiyi-challenger/src/handle_underwriter_stream.rs @@ -0,0 +1,149 @@ +use std::sync::Arc; + +use alloy_primitives::hex; +use futures_util::StreamExt; +use redb::Database; +use reqwest::Url; +use reqwest_eventsource::{Event, EventSource}; +use taiyi_primitives::{PreconfRequest, PreconfResponseData}; +use tracing::{debug, error}; + +use crate::{preconf_request_data::PreconfRequestData, table_definitions::PRECONF_TABLE}; + +pub async fn handle_underwriter_stream(preconf_db: Arc, url: Url) -> eyre::Result<()> { + let req = reqwest::Client::new().get(url); + + let mut event_source = EventSource::new(req).unwrap_or_else(|err| { + panic!("Failed to create EventSource: {err:?}"); + }); + + while let Some(event) = event_source.next().await { + match event { + Ok(Event::Message(message)) => { + let data = &message.data; + + let parsed_data = match serde_json::from_str::< + Vec<(PreconfRequest, PreconfResponseData)>, + >(data) + { + Ok(data) => data, + Err(e) => { + error!("[Stream Ingestor]: Failed to parse preconf data: {}", e); + continue; + } + }; + + debug!("[Stream Ingestor]: Received {} preconfirmations", parsed_data.len()); + + for (preconf_request, preconf_response_data) in parsed_data.iter() { + let target_slot = preconf_request.target_slot(); + debug!( + "[Stream Ingestor]: Processing preconfirmation for slot {}", + target_slot + ); + + let preconf_request_data = PreconfRequestData { + preconf_type: match preconf_request { + PreconfRequest::TypeA(_) => 0, + PreconfRequest::TypeB(_) => 1, + }, + preconf_request: match preconf_request { + PreconfRequest::TypeA(preconf_request) => { + match serde_json::to_string(&preconf_request) { + Ok(s) => s, + Err(e) => { + error!("[Stream Ingestor]: Failed to serialize preconf request: {}", e); + continue; + } + } + } + PreconfRequest::TypeB(preconf_request) => { + match serde_json::to_string(&preconf_request) { + Ok(s) => s, + Err(e) => { + error!("[Stream Ingestor]: Failed to serialize preconf request: {}", e); + continue; + } + } + } + }, + preconf_request_signature: match &preconf_response_data.commitment { + Some(commitment) => hex::encode(commitment.as_bytes()), + None => { + error!("[Stream Ingestor]: Missing commitment in preconf response"); + continue; + } + }, + }; + + let read_tx = match preconf_db.begin_read() { + Ok(tx) => tx, + Err(e) => { + error!("[Stream Ingestor]: Failed to begin read transaction: {}", e); + continue; + } + }; + + let table = match read_tx.open_table(PRECONF_TABLE) { + Ok(table) => table, + Err(e) => { + error!("[Stream Ingestor]: Failed to open preconf table: {}", e); + continue; + } + }; + + let preconfs = table.get(&target_slot); + if preconfs.is_err() { + error!( + "[Stream Ingestor]: Storage error for slot {}. Error: {:?}", + target_slot, + preconfs.err() + ); + continue; + } + + let preconfs_result = match preconfs { + Ok(result) => result, + Err(e) => { + error!("[Stream Ingestor]: Failed to get preconfs: {}", e); + continue; + } + }; + + let mut preconf_values = if let Some(values) = preconfs_result { + values.value() + } else { + Vec::new() + }; + + preconf_values.push(preconf_request_data); + + let write_result = (|| -> Result<(), redb::Error> { + let write_tx = preconf_db.begin_write()?; + { + let mut table = write_tx.open_table(PRECONF_TABLE)?; + table.insert(&target_slot, preconf_values)?; + } + write_tx.commit()?; + Ok(()) + })(); + + if let Err(e) = write_result { + error!("[Stream Ingestor]: Failed to write preconf data: {}", e); + continue; + } + + debug!("[Stream Ingestor]: Stored preconfirmation for slot {}", target_slot); + } + } + Ok(Event::Open) => { + debug!("[Stream Ingestor]: SSE connection opened"); + } + Err(err) => { + error!("[Stream Ingestor]: Error receiving SSE event: {:?}", err); + } + } + } + + Ok(()) +} diff --git a/bin/taiyi-challenger/src/main.rs b/bin/taiyi-challenger/src/main.rs index e00ca529..45b22592 100644 --- a/bin/taiyi-challenger/src/main.rs +++ b/bin/taiyi-challenger/src/main.rs @@ -1,27 +1,23 @@ -use std::{collections::HashSet, str::FromStr, sync::Arc}; +use std::{str::FromStr, sync::Arc}; -use alloy_eips::{eip2718::Encodable2718, merge::SLOT_DURATION_SECS, BlockNumberOrTag}; -use alloy_primitives::{hex, Address, Bytes, U256}; -use alloy_provider::{Provider, ProviderBuilder, WsConnect}; -use alloy_signer::k256::{self}; -use alloy_sol_types::sol; +use alloy_eips::merge::SLOT_DURATION_SECS; +use alloy_primitives::Address; use clap::Parser; -use futures_util::{future::join_all, StreamExt}; -use redb::{Database, TableDefinition}; +use futures_util::future::join_all; +use handle_challenge_creation::handle_challenge_creation; +use handle_challenge_submission::handle_challenge_submission; +use handle_underwriter_stream::handle_underwriter_stream; +use redb::Database; use reqwest::Url; -use reqwest_eventsource::{Event, EventSource}; -use taiyi_primitives::{ - PreconfRequest, PreconfRequestTypeA, PreconfRequestTypeB, PreconfResponseData, -}; + +use table_definitions::{CHALLENGE_TABLE, PRECONF_TABLE}; use tracing::{debug, error, level_filters::LevelFilter}; +mod handle_challenge_creation; +mod handle_challenge_submission; +mod handle_underwriter_stream; mod preconf_request_data; -use preconf_request_data::{Bincode, PreconfRequestData}; - -const PRECONF_TABLE: TableDefinition>> = - TableDefinition::new("preconf"); -const CHALLENGE_TABLE: TableDefinition>> = - TableDefinition::new("challenge"); +mod table_definitions; pub fn get_slot_from_timestamp(timestamp: u64, genesis_timestamp: u64) -> u64 { (timestamp - genesis_timestamp) / SLOT_DURATION_SECS @@ -52,767 +48,6 @@ struct Opts { always_open_challenges: bool, } -sol! { - #[sol(rpc)] - contract TaiyiInteractiveChallenger { - #[derive(Debug)] - struct PreconfRequestAType { - string[] txs; - string tipTx; - uint256 slot; - uint256 sequenceNum; - address signer; - } - - #[derive(Debug)] - struct BlockspaceAllocation { - uint256 gasLimit; - address sender; - address recipient; - uint256 deposit; - uint256 tip; - uint256 targetSlot; - uint256 blobCount; - } - - #[derive(Debug)] - struct PreconfRequestBType { - BlockspaceAllocation blockspaceAllocation; - bytes blockspaceAllocationSignature; - bytes underwriterSignedBlockspaceAllocation; - bytes rawTx; - bytes underwriterSignedRawTx; - } - - - #[derive(Debug)] - function createChallengeAType( - PreconfRequestAType calldata preconfRequestAType, - bytes calldata signature - ) - external - payable; - - #[derive(Debug)] - function createChallengeBType( - PreconfRequestBType calldata preconfRequestBType, - bytes calldata signature - ) - external - payable; - - #[derive(Debug)] - function resolveExpiredChallenge(bytes32 id) external; - } -} - -async fn handle_underwriter_stream(preconf_db: Arc, url: Url) -> eyre::Result<()> { - let req = reqwest::Client::new().get(url); - - let mut event_source = EventSource::new(req).unwrap_or_else(|err| { - panic!("Failed to create EventSource: {err:?}"); - }); - - while let Some(event) = event_source.next().await { - match event { - Ok(Event::Message(message)) => { - let data = &message.data; - - let parsed_data = match serde_json::from_str::< - Vec<(PreconfRequest, PreconfResponseData)>, - >(data) - { - Ok(data) => data, - Err(e) => { - error!("[Stream Ingestor]: Failed to parse preconf data: {}", e); - continue; - } - }; - - debug!("[Stream Ingestor]: Received {} preconfirmations", parsed_data.len()); - - for (preconf_request, preconf_response_data) in parsed_data.iter() { - let target_slot = preconf_request.target_slot(); - debug!( - "[Stream Ingestor]: Processing preconfirmation for slot {}", - target_slot - ); - - let preconf_request_data = PreconfRequestData { - preconf_type: match preconf_request { - PreconfRequest::TypeA(_) => 0, - PreconfRequest::TypeB(_) => 1, - }, - preconf_request: match preconf_request { - PreconfRequest::TypeA(preconf_request) => { - match serde_json::to_string(&preconf_request) { - Ok(s) => s, - Err(e) => { - error!("[Stream Ingestor]: Failed to serialize preconf request: {}", e); - continue; - } - } - } - PreconfRequest::TypeB(preconf_request) => { - match serde_json::to_string(&preconf_request) { - Ok(s) => s, - Err(e) => { - error!("[Stream Ingestor]: Failed to serialize preconf request: {}", e); - continue; - } - } - } - }, - preconf_request_signature: match &preconf_response_data.commitment { - Some(commitment) => hex::encode(commitment.as_bytes()), - None => { - error!("[Stream Ingestor]: Missing commitment in preconf response"); - continue; - } - }, - }; - - let read_tx = match preconf_db.begin_read() { - Ok(tx) => tx, - Err(e) => { - error!("[Stream Ingestor]: Failed to begin read transaction: {}", e); - continue; - } - }; - - let table = match read_tx.open_table(PRECONF_TABLE) { - Ok(table) => table, - Err(e) => { - error!("[Stream Ingestor]: Failed to open preconf table: {}", e); - continue; - } - }; - - let preconfs = table.get(&target_slot); - if preconfs.is_err() { - error!( - "[Stream Ingestor]: Storage error for slot {}. Error: {:?}", - target_slot, - preconfs.err() - ); - continue; - } - - let preconfs_result = match preconfs { - Ok(result) => result, - Err(e) => { - error!("[Stream Ingestor]: Failed to get preconfs: {}", e); - continue; - } - }; - - let mut preconf_values = if let Some(values) = preconfs_result { - values.value() - } else { - Vec::new() - }; - - preconf_values.push(preconf_request_data); - - let write_result = (|| -> Result<(), redb::Error> { - let write_tx = preconf_db.begin_write()?; - { - let mut table = write_tx.open_table(PRECONF_TABLE)?; - table.insert(&target_slot, preconf_values)?; - } - write_tx.commit()?; - Ok(()) - })(); - - if let Err(e) = write_result { - error!("[Stream Ingestor]: Failed to write preconf data: {}", e); - continue; - } - - debug!("[Stream Ingestor]: Stored preconfirmation for slot {}", target_slot); - } - } - Ok(Event::Open) => { - debug!("[Stream Ingestor]: SSE connection opened"); - } - Err(err) => { - error!("[Stream Ingestor]: Error receiving SSE event: {:?}", err); - } - } - } - - Ok(()) -} - -async fn handle_challenge_creation( - preconf_db: Arc, - challenge_db: Arc, - opts: Arc, - genesis_timestamp: u64, -) -> eyre::Result<()> { - // Create a ws provider - let ws = WsConnect::new(&opts.execution_client_ws_url); - let provider = match ProviderBuilder::new().on_ws(ws).await { - Ok(provider) => provider, - Err(e) => { - error!("[Challenger Creator]: Failed to create provider: {}", e); - return Err(eyre::eyre!("Failed to create provider: {}", e)); - } - }; - - // Subscribe to block headers. - let subscription = match provider.subscribe_blocks().await { - Ok(sub) => sub, - Err(e) => { - error!("[Challenger Creator]: Failed to subscribe to blocks: {}", e); - return Err(eyre::eyre!("Failed to subscribe to blocks: {}", e)); - } - }; - let mut stream = subscription.into_stream(); - - while let Some(header) = stream.next().await { - debug!("[Challenger Creator]: Processing block {:?}", header.number); - let slot = get_slot_from_timestamp(header.timestamp, genesis_timestamp); - debug!("[Challenger Creator]: Slot: {:?}", slot); - - // Check if preconfirmations exists for the slot - let read_tx = match preconf_db.begin_read() { - Ok(tx) => tx, - Err(e) => { - error!("[Challenger Creator]: Failed to begin read transaction: {}", e); - continue; - } - }; - - let table = match read_tx.open_table(PRECONF_TABLE) { - Ok(table) => table, - Err(e) => { - error!("[Challenger Creator]: Failed to open preconf table: {}", e); - continue; - } - }; - - let preconfs = table.get(&slot); - - if preconfs.is_err() { - error!( - "[Challenger Creator]: Storage error for slot {}. Error: {:?}", - slot, - preconfs.err() - ); - continue; - } - - let preconfs_result = match preconfs { - Ok(result) => result, - Err(e) => { - error!("[Challenger Creator]: Failed to get preconfs: {}", e); - continue; - } - }; - - if preconfs_result.is_none() { - // No preconfirmation found for the slot - debug!("[Challenger Creator]: No preconfirmations found for slot {}", slot); - continue; - } - - let preconfs = match preconfs_result { - Some(values) => values.value(), - None => { - debug!("[Challenger Creator]: No preconfirmations found for slot {}", slot); - continue; - } - }; - - debug!("[Challenger Creator]: Found {} preconfirmations for slot {}", preconfs.len(), slot); - - let block = provider.get_block_by_number(BlockNumberOrTag::Number(header.number)).await; - - if block.is_err() { - // RPC error - error!( - "[Challenger Creator]: RPC error for block {}. Error: {:?}", - header.number, - block.err() - ); - continue; - } - - let block = match block { - Ok(Some(b)) => b, - Ok(None) => { - error!("[Challenger Creator]: Block {} not found", header.number); - continue; - } - Err(e) => { - error!("[Challenger Creator]: Failed to get block {}: {}", header.number, e); - continue; - } - }; - - let tx_hashes = block.transactions.hashes().collect::>(); - - // Calculate the challenge submission slot. We need to wait for the block to be finalized - // before we can open a challenge. - let challenge_submission_slot = slot + opts.finalization_window; - - // For each preconfirmation, check if the required txs are included in the block - for preconf in preconfs { - let preconf_type = preconf.preconf_type; - - if preconf_type == 0 { - // Type A - let preconf_request = - match serde_json::from_str::(&preconf.preconf_request) { - Ok(req) => req, - Err(e) => { - error!( - "[Challenger Creator]: Failed to parse PreconfRequestTypeA: {}", - e - ); - continue; - } - }; - - let mut open_challenge = false; - - // Check if all user txs are included in the block - if !preconf_request.preconf_tx.iter().all(|tx| tx_hashes.contains(tx.tx_hash())) { - open_challenge = true; - } - - // Check if tip transaction is included in the block - if !tx_hashes.contains(preconf_request.tip_transaction.tx_hash()) { - open_challenge = true; - } - - if open_challenge || opts.always_open_challenges { - let read_tx = match challenge_db.begin_read() { - Ok(tx) => tx, - Err(e) => { - error!("[Challenger Creator]: Failed to begin read transaction: {}", e); - continue; - } - }; - - let table = match read_tx.open_table(CHALLENGE_TABLE) { - Ok(table) => table, - Err(e) => { - error!("[Challenger Creator]: Failed to open challenge table: {}", e); - continue; - } - }; - - let challenges = table.get(&challenge_submission_slot); - - if challenges.is_err() { - error!( - "[Challenger Creator]: Storage error for slot {}. Error: {:?}", - challenge_submission_slot, - challenges.err() - ); - continue; - } - - let challenges_result = match challenges { - Ok(result) => result, - Err(e) => { - error!("[Challenger Creator]: Failed to get challenges: {}", e); - continue; - } - }; - - let mut challenges_data = if let Some(values) = challenges_result { - values.value() - } else { - Vec::new() - }; - - challenges_data.push(preconf); - - let write_result = (|| -> Result<(), redb::Error> { - let write_tx = challenge_db.begin_write()?; - { - let mut table = write_tx.open_table(CHALLENGE_TABLE)?; - table.insert(&challenge_submission_slot, challenges_data)?; - } - write_tx.commit()?; - Ok(()) - })(); - - if let Err(e) = write_result { - error!("[Challenger Creator]: Failed to write challenge data: {}", e); - continue; - } - - debug!( - "[Challenger Creator]: Stored challenge for slot {}", - challenge_submission_slot - ); - } - } else { - // Type B - let preconf_request = - match serde_json::from_str::(&preconf.preconf_request) { - Ok(req) => req, - Err(e) => { - error!( - "[Challenger Creator]: Failed to parse PreconfRequestTypeB: {}", - e - ); - continue; - } - }; - - let transaction = match &preconf_request.transaction { - Some(tx) => tx, - None => { - error!("[Challenger Creator]: Missing transaction in PreconfRequestTypeB"); - continue; - } - }; - - // Check if all user txs are included in the block - if !tx_hashes.contains(transaction.tx_hash()) || opts.always_open_challenges { - let read_tx = match challenge_db.begin_read() { - Ok(tx) => tx, - Err(e) => { - error!("[Challenger Creator]: Failed to begin read transaction: {}", e); - continue; - } - }; - - let table = match read_tx.open_table(CHALLENGE_TABLE) { - Ok(table) => table, - Err(e) => { - error!("[Challenger Creator]: Failed to open challenge table: {}", e); - continue; - } - }; - - let challenges = table.get(&slot); - - if challenges.is_err() { - error!( - "[Challenger Creator]: Storage error for slot {}. Error: {:?}", - slot, - challenges.err() - ); - continue; - } - - let challenges_result = match challenges { - Ok(result) => result, - Err(e) => { - error!("[Challenger Creator]: Failed to get challenges: {}", e); - continue; - } - }; - - let mut challenges_data = if let Some(values) = challenges_result { - values.value() - } else { - Vec::new() - }; - - challenges_data.push(preconf); - - let write_result = (|| -> Result<(), redb::Error> { - let write_tx = challenge_db.begin_write()?; - { - let mut table = write_tx.open_table(CHALLENGE_TABLE)?; - table.insert(&slot, challenges_data)?; - } - write_tx.commit()?; - Ok(()) - })(); - - if let Err(e) = write_result { - error!("[Challenger Creator]: Failed to write challenge data: {}", e); - continue; - } - - debug!("[Challenger Creator]: Stored challenge for slot {}", slot); - } - } - } - - debug!("[Challenger Creator]: Processed block {:?}", header.number); - } - - Ok(()) -} - -async fn handle_challenge_submission( - challenge_db: Arc, - opts: Arc, - genesis_timestamp: u64, -) -> eyre::Result<()> { - // Initialize signer - let private_key_bytes = - match hex::decode(opts.private_key.strip_prefix("0x").unwrap_or(&opts.private_key)) { - Ok(bytes) => bytes, - Err(e) => { - error!("[Challenger Submitter]: Failed to decode private key: {}", e); - return Err(eyre::eyre!("Failed to decode private key: {}", e)); - } - }; - - let signing_key = match k256::ecdsa::SigningKey::from_slice(&private_key_bytes) { - Ok(key) => key, - Err(e) => { - error!("[Challenger Submitter]: Failed to create signing key: {}", e); - return Err(eyre::eyre!("Failed to create signing key: {}", e)); - } - }; - - let private_key_signer = alloy_signer_local::PrivateKeySigner::from_signing_key(signing_key); - let signer_address = private_key_signer.address(); - - debug!("[Challenger Submitter]: Signer address: {}", signer_address); - - let ws = WsConnect::new(&opts.execution_client_ws_url); - let provider = match ProviderBuilder::new().wallet(private_key_signer).on_ws(ws).await { - Ok(p) => p, - Err(e) => { - error!("[Challenger Submitter]: Failed to create provider with wallet: {}", e); - return Err(eyre::eyre!("Failed to create provider with wallet: {}", e)); - } - }; - - // Check balance to verify signer is working correctly - match provider.get_balance(signer_address).await { - Ok(balance) => { - debug!("[Challenger Submitter]: Signer ETH balance: {}", balance); - } - Err(e) => { - error!("[Challenger Submitter]: Failed to get signer balance: {}", e); - // Continue anyway, this is not critical - } - }; - - let taiyi_challenger = - TaiyiInteractiveChallenger::new(opts.taiyi_challenger_address, provider.clone()); - - let subscription = match provider.subscribe_blocks().await { - Ok(sub) => sub, - Err(e) => { - error!("[Challenger Submitter]: Failed to subscribe to blocks: {}", e); - return Err(eyre::eyre!("Failed to subscribe to blocks: {}", e)); - } - }; - - let mut stream = subscription.into_stream(); - - while let Some(header) = stream.next().await { - debug!("[Challenger Submitter]: Processing block {:?}", header.number); - let slot = get_slot_from_timestamp(header.timestamp, genesis_timestamp); - debug!("[Challenger Submitter]: Slot: {:?}", slot); - - // Check if challenges exists for the slot - let read_tx = match challenge_db.begin_read() { - Ok(tx) => tx, - Err(e) => { - error!("[Challenger Submitter]: Failed to begin read transaction: {}", e); - continue; - } - }; - - let table = match read_tx.open_table(CHALLENGE_TABLE) { - Ok(table) => table, - Err(e) => { - error!("[Challenger Submitter]: Failed to open challenge table: {}", e); - continue; - } - }; - - let challenges = table.get(&slot); - - if challenges.is_err() { - error!( - "[Challenger Submitter]: Storage error for slot {}. Error: {:?}", - slot, - challenges.err() - ); - continue; - } - - let challenges_result = match challenges { - Ok(result) => result, - Err(e) => { - error!("[Challenger Submitter]: Failed to get challenges: {}", e); - continue; - } - }; - - if challenges_result.is_none() { - // No challenges found for the slot - debug!("[Challenger Submitter]: No challenges found for slot {}", slot); - continue; - } - - let challenges_data = match challenges_result { - Some(values) => values.value(), - None => { - debug!("[Challenger Submitter]: No challenges found for slot {}", slot); - continue; - } - }; - - debug!( - "[Challenger Submitter]: Found {} challenges for slot {}", - challenges_data.len(), - slot - ); - - // For each challenge, check if the challenge is expired - for challenge in challenges_data { - if challenge.preconf_type == 0 { - // Type A - let preconf_request = - match serde_json::from_str::(&challenge.preconf_request) { - Ok(req) => req, - Err(e) => { - error!( - "[Challenger Submitter]: Failed to parse PreconfRequestTypeA: {}", - e - ); - continue; - } - }; - - let mut txs: Vec = Vec::new(); - - for tx in preconf_request.preconf_tx { - let mut tx_bytes = Vec::new(); - tx.encode_2718(&mut tx_bytes); - let hex_encoded_tx = format!("0x{}", hex::encode(&tx_bytes)); - txs.push(hex_encoded_tx); - } - - let mut tip_tx = Vec::new(); - preconf_request.tip_transaction.encode_2718(&mut tip_tx); - let tip_tx_raw = format!("0x{}", hex::encode(&tip_tx)); - - let sequence_number = match preconf_request.sequence_number { - Some(num) => num, - None => { - error!("[Challenger Submitter]: Missing sequence number in PreconfRequestTypeA"); - continue; - } - }; - - let preconf_request_a_type = TaiyiInteractiveChallenger::PreconfRequestAType { - txs, - tipTx: tip_tx_raw, - slot: U256::from(slot), - sequenceNum: U256::from(sequence_number), - signer: preconf_request.signer, - }; - - // Decode signature - let signature_bytes = match hex::decode(&challenge.preconf_request_signature) { - Ok(bytes) => Bytes::from(bytes), - Err(e) => { - error!("[Challenger Submitter]: Failed to decode signature: {}", e); - continue; - } - }; - - // Submit challenge to the contract - debug!("[Challenger Submitter]: Submitting challenge type A for slot {}", slot); - match taiyi_challenger - .createChallengeAType(preconf_request_a_type, signature_bytes) - .send() - .await - { - Ok(tx) => { - debug!("[Challenger Submitter]: Challenge type A submitted. TX: {:?}", tx); - } - Err(e) => { - error!("[Challenger Submitter]: Failed to create challenge type A: {}", e); - // Continue to next challenge, we may be able to submit others - } - } - } else { - // Type B - let preconf_request = - match serde_json::from_str::(&challenge.preconf_request) { - Ok(req) => req, - Err(e) => { - error!( - "[Challenger Submitter]: Failed to parse PreconfRequestTypeB: {}", - e - ); - continue; - } - }; - - let transaction = match &preconf_request.transaction { - Some(tx) => tx, - None => { - error!( - "[Challenger Submitter]: Missing transaction in PreconfRequestTypeB" - ); - continue; - } - }; - - let mut tx_bytes = Vec::new(); - transaction.encode_2718(&mut tx_bytes); - let tx_raw = format!("0x{}", hex::encode(&tx_bytes)); - - let preconf_request_b_type = TaiyiInteractiveChallenger::PreconfRequestBType { - blockspaceAllocation: TaiyiInteractiveChallenger::BlockspaceAllocation { - gasLimit: U256::from(preconf_request.allocation.gas_limit), - sender: preconf_request.allocation.sender, - recipient: preconf_request.allocation.recipient, - deposit: U256::from(preconf_request.allocation.deposit), - tip: U256::from(preconf_request.allocation.tip), - targetSlot: U256::from(preconf_request.allocation.target_slot), - blobCount: U256::from(preconf_request.allocation.blob_count), - }, - blockspaceAllocationSignature: preconf_request.alloc_sig.as_bytes().into(), - rawTx: Bytes::from(tx_raw), - // TODO: Can we remove this two fields ? - underwriterSignedBlockspaceAllocation: Bytes::from([]), - underwriterSignedRawTx: Bytes::from([]), - }; - - // Decode signature - let signature_bytes = match hex::decode(&challenge.preconf_request_signature) { - Ok(bytes) => Bytes::from(bytes), - Err(e) => { - error!("[Challenger Submitter]: Failed to decode signature: {}", e); - continue; - } - }; - - // Submit challenge to the contract - debug!("[Challenger Submitter]: Submitting challenge type B for slot {}", slot); - match taiyi_challenger - .createChallengeBType(preconf_request_b_type, signature_bytes) - .send() - .await - { - Ok(tx) => { - debug!("[Challenger Submitter]: Challenge type B submitted. TX: {:?}", tx); - } - Err(e) => { - error!("[Challenger Submitter]: Failed to create challenge type B: {}", e); - // Continue to next challenge, we may be able to submit others - } - } - } - } - - debug!("[Challenger Submitter]: Processed block {:?}", header.number); - } - - Ok(()) -} - #[tokio::main] async fn main() -> eyre::Result<()> { // Read cli args diff --git a/bin/taiyi-challenger/src/table_definitions.rs b/bin/taiyi-challenger/src/table_definitions.rs new file mode 100644 index 00000000..4b9e26c1 --- /dev/null +++ b/bin/taiyi-challenger/src/table_definitions.rs @@ -0,0 +1,8 @@ +use redb::TableDefinition; + +use crate::preconf_request_data::{Bincode, PreconfRequestData}; + +pub const PRECONF_TABLE: TableDefinition>> = + TableDefinition::new("preconf"); +pub const CHALLENGE_TABLE: TableDefinition>> = + TableDefinition::new("challenge"); From 6dec768157bf19d9e56c7c1893565473c12a85ff Mon Sep 17 00:00:00 2001 From: martines3000 Date: Fri, 9 May 2025 09:38:58 +0200 Subject: [PATCH 08/15] fix: remove `beacon-url` from opts --- Cargo.lock | 1 + bin/taiyi-challenger/Cargo.toml | 1 + bin/taiyi-challenger/src/main.rs | 48 +++++------------------- scripts/devnet/start-taiyi-challenger.sh | 2 +- 4 files changed, 12 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4c4be92b..18c2e7e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8919,6 +8919,7 @@ dependencies = [ "alloy-sol-types", "bincode 2.0.1", "clap", + "ethereum-consensus", "eyre", "futures-util", "redb", diff --git a/bin/taiyi-challenger/Cargo.toml b/bin/taiyi-challenger/Cargo.toml index 83b99872..b5cd96ad 100644 --- a/bin/taiyi-challenger/Cargo.toml +++ b/bin/taiyi-challenger/Cargo.toml @@ -25,6 +25,7 @@ reqwest-eventsource = "0.6" serde_json = { workspace = true } taiyi-primitives = { workspace = true } tokio = { workspace = true } +ethereum-consensus = { workspace = true } [[bin]] name = "taiyi-challenger" diff --git a/bin/taiyi-challenger/src/main.rs b/bin/taiyi-challenger/src/main.rs index 45b22592..d028c21f 100644 --- a/bin/taiyi-challenger/src/main.rs +++ b/bin/taiyi-challenger/src/main.rs @@ -1,8 +1,9 @@ -use std::{str::FromStr, sync::Arc}; +use std::sync::Arc; use alloy_eips::merge::SLOT_DURATION_SECS; use alloy_primitives::Address; use clap::Parser; +use ethereum_consensus::{deneb::Context, networks::Network}; use futures_util::future::join_all; use handle_challenge_creation::handle_challenge_creation; use handle_challenge_submission::handle_challenge_submission; @@ -28,9 +29,9 @@ struct Opts { /// execution_client_ws_url #[clap(long = "execution-client-ws-url")] execution_client_ws_url: String, - /// beacon_url - #[clap(long = "beacon-url")] - beacon_url: String, + /// network + #[clap(long = "network")] + network: String, /// finalization_window #[clap(long = "finalization-window")] finalization_window: u64, @@ -100,41 +101,10 @@ async fn main() -> eyre::Result<()> { debug!("Tables created successfully"); - // Read genesis timestamp from Beacon API (/eth/v1/beacon/genesis) - let beacon_genesis_response = match reqwest::Client::new() - .get(format!("{}/eth/v1/beacon/genesis", opts.beacon_url)) - .send() - .await - { - Ok(response) => response, - Err(e) => { - error!("Failed to get beacon genesis: {}", e); - return Err(eyre::eyre!("Failed to get beacon genesis: {}", e)); - } - }; - - let beacon_genesis_response = match beacon_genesis_response.json::().await { - Ok(value) => value, - Err(e) => { - error!("Failed to parse beacon genesis response: {}", e); - return Err(eyre::eyre!("Failed to parse beacon genesis response: {}", e)); - } - }; - - let genesis_time = - beacon_genesis_response["data"]["genesis_time"].as_str().ok_or_else(|| { - let err = "Failed to get genesis time from response"; - error!("{}", err); - eyre::eyre!("{}", err) - })?; - - let genesis_timestamp = match u64::from_str(genesis_time) { - Ok(timestamp) => timestamp, - Err(e) => { - error!("Failed to parse genesis time: {}", e); - return Err(eyre::eyre!("Failed to parse genesis time: {}", e)); - } - }; + // Genesis timestamp + let network: Network = opts.network.clone().into(); + let context: Context = network.try_into()?; + let genesis_timestamp = context.genesis_time()?; let mut handles = Vec::new(); diff --git a/scripts/devnet/start-taiyi-challenger.sh b/scripts/devnet/start-taiyi-challenger.sh index cc2aa948..c5b8ca3a 100755 --- a/scripts/devnet/start-taiyi-challenger.sh +++ b/scripts/devnet/start-taiyi-challenger.sh @@ -16,7 +16,7 @@ export TAIYI_CHALLENGER_ADDRESS=0x0000000000000000000000000000000000000000 cargo run --bin taiyi-challenger -- \ --execution-client-ws-url $EXECUTION_CLIENT_WS_URL \ - --beacon-url $BEACON_URL \ + --network $NETWORK \ --finalization-window 32 \ --underwriter-stream-urls http://127.0.0.1:5656/commitments/v0/commitment_stream \ --private-key 0xbf3beef3bd999ba9f2451e06936f0423cd62b815c9233dd3bc90f7e02a1e8673 \ From 68d2e90b88c6db28f0ffa106d89700084964f6f6 Mon Sep 17 00:00:00 2001 From: martines3000 Date: Fri, 9 May 2025 09:39:32 +0200 Subject: [PATCH 09/15] chore: format and lint --- bin/taiyi-challenger/Cargo.toml | 2 +- bin/taiyi-challenger/src/handle_challenge_submission.rs | 3 +-- bin/taiyi-challenger/src/main.rs | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/bin/taiyi-challenger/Cargo.toml b/bin/taiyi-challenger/Cargo.toml index b5cd96ad..4e2c3fb5 100644 --- a/bin/taiyi-challenger/Cargo.toml +++ b/bin/taiyi-challenger/Cargo.toml @@ -17,6 +17,7 @@ tracing = { workspace = true } tracing-subscriber = { workspace = true } clap = { workspace = true } +ethereum-consensus = { workspace = true } eyre = { workspace = true } futures-util = { workspace = true } redb = { workspace = true } @@ -25,7 +26,6 @@ reqwest-eventsource = "0.6" serde_json = { workspace = true } taiyi-primitives = { workspace = true } tokio = { workspace = true } -ethereum-consensus = { workspace = true } [[bin]] name = "taiyi-challenger" diff --git a/bin/taiyi-challenger/src/handle_challenge_submission.rs b/bin/taiyi-challenger/src/handle_challenge_submission.rs index 051d246a..5abc8132 100644 --- a/bin/taiyi-challenger/src/handle_challenge_submission.rs +++ b/bin/taiyi-challenger/src/handle_challenge_submission.rs @@ -1,14 +1,13 @@ -use alloy_sol_types::sol; use std::sync::Arc; use alloy_eips::eip2718::Encodable2718; use alloy_primitives::{hex, Bytes, U256}; use alloy_provider::{Provider, ProviderBuilder, WsConnect}; use alloy_signer::k256::{self}; +use alloy_sol_types::sol; use futures_util::StreamExt; use redb::Database; use taiyi_primitives::{PreconfRequestTypeA, PreconfRequestTypeB}; - use tracing::{debug, error}; use crate::{get_slot_from_timestamp, table_definitions::CHALLENGE_TABLE, Opts}; diff --git a/bin/taiyi-challenger/src/main.rs b/bin/taiyi-challenger/src/main.rs index d028c21f..a1c856b1 100644 --- a/bin/taiyi-challenger/src/main.rs +++ b/bin/taiyi-challenger/src/main.rs @@ -10,7 +10,6 @@ use handle_challenge_submission::handle_challenge_submission; use handle_underwriter_stream::handle_underwriter_stream; use redb::Database; use reqwest::Url; - use table_definitions::{CHALLENGE_TABLE, PRECONF_TABLE}; use tracing::{debug, error, level_filters::LevelFilter}; From d5fb8e9294617828f60eff48a155dde07e2338fd Mon Sep 17 00:00:00 2001 From: martines3000 Date: Fri, 9 May 2025 14:57:04 +0200 Subject: [PATCH 10/15] chore: revert change --- scripts/devnet/start-devnet.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/devnet/start-devnet.sh b/scripts/devnet/start-devnet.sh index a4513b6b..e228a84b 100644 --- a/scripts/devnet/start-devnet.sh +++ b/scripts/devnet/start-devnet.sh @@ -6,7 +6,7 @@ if [ -z "$TAIYI_BOOST_IMAGE" ]; then export TAIYI_BOOST_IMAGE="lubann/taiyi:latest" fi -# sed -i "s|lubann/taiyi:latest|${TAIYI_BOOST_IMAGE}|g" scripts/devnet/luban.yml +sed -i "s|lubann/taiyi:latest|${TAIYI_BOOST_IMAGE}|g" scripts/devnet/luban.yml cat scripts/devnet/luban.yml pushd $WORKING_DIR From 92dfb0f9be7f05301f3d15817904699cb3aab05a Mon Sep 17 00:00:00 2001 From: martines3000 Date: Fri, 9 May 2025 15:11:56 +0200 Subject: [PATCH 11/15] fix: improvements based on pr comments --- .../src/handle_challenge_creation.rs | 138 +++++------------- .../src/handle_challenge_submission.rs | 75 +--------- .../src/handle_underwriter_stream.rs | 31 +--- bin/taiyi-challenger/src/main.rs | 26 ++-- 4 files changed, 62 insertions(+), 208 deletions(-) diff --git a/bin/taiyi-challenger/src/handle_challenge_creation.rs b/bin/taiyi-challenger/src/handle_challenge_creation.rs index ba53d0ad..85b05d50 100644 --- a/bin/taiyi-challenger/src/handle_challenge_creation.rs +++ b/bin/taiyi-challenger/src/handle_challenge_creation.rs @@ -30,13 +30,7 @@ pub async fn handle_challenge_creation( }; // Subscribe to block headers. - let subscription = match provider.subscribe_blocks().await { - Ok(sub) => sub, - Err(e) => { - error!("[Challenger Creator]: Failed to subscribe to blocks: {}", e); - return Err(eyre::eyre!("Failed to subscribe to blocks: {}", e)); - } - }; + let subscription = provider.subscribe_blocks().await?; let mut stream = subscription.into_stream(); while let Some(header) = stream.next().await { @@ -61,64 +55,32 @@ pub async fn handle_challenge_creation( } }; - let preconfs = table.get(&slot); - - if preconfs.is_err() { - error!( - "[Challenger Creator]: Storage error for slot {}. Error: {:?}", - slot, - preconfs.err() - ); - continue; - } - - let preconfs_result = match preconfs { - Ok(result) => result, - Err(e) => { - error!("[Challenger Creator]: Failed to get preconfs: {}", e); + let preconfs = match table.get(&slot) { + Ok(Some(val)) => val.value(), + Ok(None) => { + debug!("[Challenger Creator]: No preconfirmations found for slot {}", slot); continue; } - }; - - if preconfs_result.is_none() { - // No preconfirmation found for the slot - debug!("[Challenger Creator]: No preconfirmations found for slot {}", slot); - continue; - } - - let preconfs = match preconfs_result { - Some(values) => values.value(), - None => { - debug!("[Challenger Creator]: No preconfirmations found for slot {}", slot); + Err(e) => { + error!("[Challenger Creator]: Storage error for slot {}. Error: {:?}", slot, e); continue; } }; debug!("[Challenger Creator]: Found {} preconfirmations for slot {}", preconfs.len(), slot); - let block = provider.get_block_by_number(BlockNumberOrTag::Number(header.number)).await; - - if block.is_err() { - // RPC error - error!( - "[Challenger Creator]: RPC error for block {}. Error: {:?}", - header.number, - block.err() - ); - continue; - } - - let block = match block { - Ok(Some(b)) => b, - Ok(None) => { - error!("[Challenger Creator]: Block {} not found", header.number); - continue; - } - Err(e) => { - error!("[Challenger Creator]: Failed to get block {}: {}", header.number, e); - continue; - } - }; + let block = + match provider.get_block_by_number(BlockNumberOrTag::Number(header.number)).await { + Ok(Some(b)) => b, + Ok(None) => { + error!("[Challenger Creator]: Block {} not found", header.number); + continue; + } + Err(e) => { + error!("[Challenger Creator]: Failed to get block {}: {}", header.number, e); + continue; + } + }; let tx_hashes = block.transactions.hashes().collect::>(); @@ -146,13 +108,10 @@ pub async fn handle_challenge_creation( let mut open_challenge = false; - // Check if all user txs are included in the block - if !preconf_request.preconf_tx.iter().all(|tx| tx_hashes.contains(tx.tx_hash())) { - open_challenge = true; - } - - // Check if tip transaction is included in the block - if !tx_hashes.contains(preconf_request.tip_transaction.tx_hash()) { + // Check if all user txs are included in the block and if the tip transaction is included in the block + if !preconf_request.preconf_tx.iter().all(|tx| tx_hashes.contains(tx.tx_hash())) + || !tx_hashes.contains(preconf_request.tip_transaction.tx_hash()) + { open_challenge = true; } @@ -173,31 +132,18 @@ pub async fn handle_challenge_creation( } }; - let challenges = table.get(&challenge_submission_slot); - - if challenges.is_err() { - error!( - "[Challenger Creator]: Storage error for slot {}. Error: {:?}", - challenge_submission_slot, - challenges.err() - ); - continue; - } - - let challenges_result = match challenges { - Ok(result) => result, + let mut challenges_data = match table.get(&challenge_submission_slot) { + Ok(Some(val)) => val.value(), + Ok(None) => Vec::new(), Err(e) => { - error!("[Challenger Creator]: Failed to get challenges: {}", e); + error!( + "[Challenger Creator]: Storage error for slot {}: {}", + challenge_submission_slot, e + ); continue; } }; - let mut challenges_data = if let Some(values) = challenges_result { - values.value() - } else { - Vec::new() - }; - challenges_data.push(preconf); let write_result = (|| -> Result<(), redb::Error> { @@ -260,31 +206,15 @@ pub async fn handle_challenge_creation( } }; - let challenges = table.get(&slot); - - if challenges.is_err() { - error!( - "[Challenger Creator]: Storage error for slot {}. Error: {:?}", - slot, - challenges.err() - ); - continue; - } - - let challenges_result = match challenges { - Ok(result) => result, + let mut challenges_data = match table.get(&slot) { + Ok(Some(val)) => val.value(), + Ok(None) => Vec::new(), Err(e) => { - error!("[Challenger Creator]: Failed to get challenges: {}", e); + error!("[Challenger Creator]: Storage error for slot {}: {}", slot, e); continue; } }; - let mut challenges_data = if let Some(values) = challenges_result { - values.value() - } else { - Vec::new() - }; - challenges_data.push(preconf); let write_result = (|| -> Result<(), redb::Error> { diff --git a/bin/taiyi-challenger/src/handle_challenge_submission.rs b/bin/taiyi-challenger/src/handle_challenge_submission.rs index 5abc8132..54c7acd7 100644 --- a/bin/taiyi-challenger/src/handle_challenge_submission.rs +++ b/bin/taiyi-challenger/src/handle_challenge_submission.rs @@ -73,21 +73,9 @@ pub async fn handle_challenge_submission( ) -> eyre::Result<()> { // Initialize signer let private_key_bytes = - match hex::decode(opts.private_key.strip_prefix("0x").unwrap_or(&opts.private_key)) { - Ok(bytes) => bytes, - Err(e) => { - error!("[Challenger Submitter]: Failed to decode private key: {}", e); - return Err(eyre::eyre!("Failed to decode private key: {}", e)); - } - }; + hex::decode(opts.private_key.strip_prefix("0x").unwrap_or(&opts.private_key))?; - let signing_key = match k256::ecdsa::SigningKey::from_slice(&private_key_bytes) { - Ok(key) => key, - Err(e) => { - error!("[Challenger Submitter]: Failed to create signing key: {}", e); - return Err(eyre::eyre!("Failed to create signing key: {}", e)); - } - }; + let signing_key = k256::ecdsa::SigningKey::from_slice(&private_key_bytes)?; let private_key_signer = alloy_signer_local::PrivateKeySigner::from_signing_key(signing_key); let signer_address = private_key_signer.address(); @@ -95,35 +83,12 @@ pub async fn handle_challenge_submission( debug!("[Challenger Submitter]: Signer address: {}", signer_address); let ws = WsConnect::new(&opts.execution_client_ws_url); - let provider = match ProviderBuilder::new().wallet(private_key_signer).on_ws(ws).await { - Ok(p) => p, - Err(e) => { - error!("[Challenger Submitter]: Failed to create provider with wallet: {}", e); - return Err(eyre::eyre!("Failed to create provider with wallet: {}", e)); - } - }; - - // Check balance to verify signer is working correctly - match provider.get_balance(signer_address).await { - Ok(balance) => { - debug!("[Challenger Submitter]: Signer ETH balance: {}", balance); - } - Err(e) => { - error!("[Challenger Submitter]: Failed to get signer balance: {}", e); - // Continue anyway, this is not critical - } - }; + let provider = ProviderBuilder::new().wallet(private_key_signer).on_ws(ws).await?; let taiyi_challenger = TaiyiInteractiveChallenger::new(opts.taiyi_challenger_address, provider.clone()); - let subscription = match provider.subscribe_blocks().await { - Ok(sub) => sub, - Err(e) => { - error!("[Challenger Submitter]: Failed to subscribe to blocks: {}", e); - return Err(eyre::eyre!("Failed to subscribe to blocks: {}", e)); - } - }; + let subscription = provider.subscribe_blocks().await?; let mut stream = subscription.into_stream(); @@ -149,35 +114,11 @@ pub async fn handle_challenge_submission( } }; - let challenges = table.get(&slot); - - if challenges.is_err() { - error!( - "[Challenger Submitter]: Storage error for slot {}. Error: {:?}", - slot, - challenges.err() - ); - continue; - } - - let challenges_result = match challenges { - Ok(result) => result, + let challenges_data = match table.get(&slot) { + Ok(Some(val)) => val.value(), + Ok(None) => Vec::new(), Err(e) => { - error!("[Challenger Submitter]: Failed to get challenges: {}", e); - continue; - } - }; - - if challenges_result.is_none() { - // No challenges found for the slot - debug!("[Challenger Submitter]: No challenges found for slot {}", slot); - continue; - } - - let challenges_data = match challenges_result { - Some(values) => values.value(), - None => { - debug!("[Challenger Submitter]: No challenges found for slot {}", slot); + error!("[Challenger Submitter]: Storage error for slot {}: {}", slot, e); continue; } }; diff --git a/bin/taiyi-challenger/src/handle_underwriter_stream.rs b/bin/taiyi-challenger/src/handle_underwriter_stream.rs index 1f16b765..6236ebb6 100644 --- a/bin/taiyi-challenger/src/handle_underwriter_stream.rs +++ b/bin/taiyi-challenger/src/handle_underwriter_stream.rs @@ -13,10 +13,7 @@ use crate::{preconf_request_data::PreconfRequestData, table_definitions::PRECONF pub async fn handle_underwriter_stream(preconf_db: Arc, url: Url) -> eyre::Result<()> { let req = reqwest::Client::new().get(url); - let mut event_source = EventSource::new(req).unwrap_or_else(|err| { - panic!("Failed to create EventSource: {err:?}"); - }); - + let mut event_source = EventSource::new(req)?; while let Some(event) = event_source.next().await { match event { Ok(Event::Message(message)) => { @@ -92,30 +89,18 @@ pub async fn handle_underwriter_stream(preconf_db: Arc, url: Url) -> e } }; - let preconfs = table.get(&target_slot); - if preconfs.is_err() { - error!( - "[Stream Ingestor]: Storage error for slot {}. Error: {:?}", - target_slot, - preconfs.err() - ); - continue; - } - - let preconfs_result = match preconfs { - Ok(result) => result, + let mut preconf_values = match table.get(&target_slot) { + Ok(Some(val)) => val.value(), + Ok(None) => Vec::new(), Err(e) => { - error!("[Stream Ingestor]: Failed to get preconfs: {}", e); + error!( + "[Stream Ingestor]: Storage error for slot {}: {}", + target_slot, e + ); continue; } }; - let mut preconf_values = if let Some(values) = preconfs_result { - values.value() - } else { - Vec::new() - }; - preconf_values.push(preconf_request_data); let write_result = (|| -> Result<(), redb::Error> { diff --git a/bin/taiyi-challenger/src/main.rs b/bin/taiyi-challenger/src/main.rs index a1c856b1..bc927ac6 100644 --- a/bin/taiyi-challenger/src/main.rs +++ b/bin/taiyi-challenger/src/main.rs @@ -11,7 +11,7 @@ use handle_underwriter_stream::handle_underwriter_stream; use redb::Database; use reqwest::Url; use table_definitions::{CHALLENGE_TABLE, PRECONF_TABLE}; -use tracing::{debug, error, level_filters::LevelFilter}; +use tracing::{debug, error, info, level_filters::LevelFilter}; mod handle_challenge_creation; mod handle_challenge_submission; @@ -57,18 +57,10 @@ async fn main() -> eyre::Result<()> { // Initialize tracing tracing_subscriber::fmt().with_max_level(LevelFilter::DEBUG).init(); - let preconf_db = Database::create("preconf.db").unwrap_or_else(|e| { - eprintln!("Failed to create preconf database: {e}"); - std::process::exit(1); - }); - + let preconf_db = Database::create("preconf.db")?; let preconf_db = Arc::new(preconf_db); - let challenge_db = Database::create("challenge.db").unwrap_or_else(|e| { - eprintln!("Failed to create challenge database: {e}"); - std::process::exit(1); - }); - + let challenge_db = Database::create("challenge.db")?; let challenge_db = Arc::new(challenge_db); // Create tables if they don't exist @@ -108,8 +100,9 @@ async fn main() -> eyre::Result<()> { let mut handles = Vec::new(); // Handles for ingesting underwriter streams - let underwriter_stream_urls = opts.underwriter_stream_urls.clone(); - let underwriter_stream_urls = underwriter_stream_urls + let underwriter_stream_urls = opts + .underwriter_stream_urls + .clone() .iter() .filter_map(|url| match Url::parse(url) { Ok(parsed_url) => Some(parsed_url), @@ -149,7 +142,12 @@ async fn main() -> eyre::Result<()> { handles.push(challenger_submitter_handle); - let _ = join_all(handles).await; + tokio::select! { + _ = join_all(handles) => {}, + _ = tokio::signal::ctrl_c() => { + info!("Shutdown signal received."); + } + } Ok(()) } From 34199961db2db9de3ecc962ffedd0617070421e5 Mon Sep 17 00:00:00 2001 From: martines3000 Date: Fri, 9 May 2025 15:25:28 +0200 Subject: [PATCH 12/15] fix: extract duplicate logic to separate function --- .../src/handle_challenge_creation.rs | 114 ++++++------------ 1 file changed, 35 insertions(+), 79 deletions(-) diff --git a/bin/taiyi-challenger/src/handle_challenge_creation.rs b/bin/taiyi-challenger/src/handle_challenge_creation.rs index 85b05d50..923c4225 100644 --- a/bin/taiyi-challenger/src/handle_challenge_creation.rs +++ b/bin/taiyi-challenger/src/handle_challenge_creation.rs @@ -9,10 +9,41 @@ use tracing::{debug, error}; use crate::{ get_slot_from_timestamp, + preconf_request_data::PreconfRequestData, table_definitions::{CHALLENGE_TABLE, PRECONF_TABLE}, Opts, }; +/// Store a challenge in the database for a given slot +fn store_challenge( + challenge_db: &Database, + submission_slot: u64, + preconf: PreconfRequestData, +) -> Result<(), redb::Error> { + let read_tx = challenge_db.begin_read()?; + + let table = read_tx.open_table(CHALLENGE_TABLE)?; + + // Get existing challenges or create a new vec + let mut challenges_data = match table.get(&submission_slot)? { + Some(val) => val.value(), + None => Vec::new(), + }; + + // Add the new preconf + challenges_data.push(preconf); + + // Write the updated challenges + let write_tx = challenge_db.begin_write()?; + { + let mut table = write_tx.open_table(CHALLENGE_TABLE)?; + table.insert(&submission_slot, challenges_data)?; + } + write_tx.commit()?; + + Ok(()) +} + pub async fn handle_challenge_creation( preconf_db: Arc, challenge_db: Arc, @@ -116,47 +147,9 @@ pub async fn handle_challenge_creation( } if open_challenge || opts.always_open_challenges { - let read_tx = match challenge_db.begin_read() { - Ok(tx) => tx, - Err(e) => { - error!("[Challenger Creator]: Failed to begin read transaction: {}", e); - continue; - } - }; - - let table = match read_tx.open_table(CHALLENGE_TABLE) { - Ok(table) => table, - Err(e) => { - error!("[Challenger Creator]: Failed to open challenge table: {}", e); - continue; - } - }; - - let mut challenges_data = match table.get(&challenge_submission_slot) { - Ok(Some(val)) => val.value(), - Ok(None) => Vec::new(), - Err(e) => { - error!( - "[Challenger Creator]: Storage error for slot {}: {}", - challenge_submission_slot, e - ); - continue; - } - }; - - challenges_data.push(preconf); - - let write_result = (|| -> Result<(), redb::Error> { - let write_tx = challenge_db.begin_write()?; - { - let mut table = write_tx.open_table(CHALLENGE_TABLE)?; - table.insert(&challenge_submission_slot, challenges_data)?; - } - write_tx.commit()?; - Ok(()) - })(); - - if let Err(e) = write_result { + if let Err(e) = + store_challenge(&challenge_db, challenge_submission_slot, preconf) + { error!("[Challenger Creator]: Failed to write challenge data: {}", e); continue; } @@ -190,44 +183,7 @@ pub async fn handle_challenge_creation( // Check if all user txs are included in the block if !tx_hashes.contains(transaction.tx_hash()) || opts.always_open_challenges { - let read_tx = match challenge_db.begin_read() { - Ok(tx) => tx, - Err(e) => { - error!("[Challenger Creator]: Failed to begin read transaction: {}", e); - continue; - } - }; - - let table = match read_tx.open_table(CHALLENGE_TABLE) { - Ok(table) => table, - Err(e) => { - error!("[Challenger Creator]: Failed to open challenge table: {}", e); - continue; - } - }; - - let mut challenges_data = match table.get(&slot) { - Ok(Some(val)) => val.value(), - Ok(None) => Vec::new(), - Err(e) => { - error!("[Challenger Creator]: Storage error for slot {}: {}", slot, e); - continue; - } - }; - - challenges_data.push(preconf); - - let write_result = (|| -> Result<(), redb::Error> { - let write_tx = challenge_db.begin_write()?; - { - let mut table = write_tx.open_table(CHALLENGE_TABLE)?; - table.insert(&slot, challenges_data)?; - } - write_tx.commit()?; - Ok(()) - })(); - - if let Err(e) = write_result { + if let Err(e) = store_challenge(&challenge_db, slot, preconf) { error!("[Challenger Creator]: Failed to write challenge data: {}", e); continue; } From 64008b1efb665ce5ef38a18cb9ee8ca5550aae07 Mon Sep 17 00:00:00 2001 From: martines3000 Date: Wed, 14 May 2025 14:40:16 +0200 Subject: [PATCH 13/15] fix: add unit tests for bincode --- .../src/preconf_request_data.rs | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/bin/taiyi-challenger/src/preconf_request_data.rs b/bin/taiyi-challenger/src/preconf_request_data.rs index c713184a..674fd1e2 100644 --- a/bin/taiyi-challenger/src/preconf_request_data.rs +++ b/bin/taiyi-challenger/src/preconf_request_data.rs @@ -51,3 +51,125 @@ where TypeName::new(&format!("Bincode<{}>", type_name::())) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(Debug, Clone, PartialEq, Decode, Encode)] + struct TestStruct { + id: u64, + name: String, + data: Vec, + } + + #[test] + fn test_bincode_roundtrip() { + // Create test values of different types + let test_string = String::from("Hello, world!"); + let test_int = 42u64; + let test_struct = TestStruct { id: 1, name: "Test".to_string(), data: vec![1, 2, 3, 4, 5] }; + + // Test string roundtrip + let bytes = >::as_bytes(&test_string); + let decoded = >::from_bytes(&bytes); + assert_eq!(decoded, test_string); + + // Test integer roundtrip + let bytes = >::as_bytes(&test_int); + let decoded = >::from_bytes(&bytes); + assert_eq!(decoded, test_int); + + // Test struct roundtrip + let bytes = >::as_bytes(&test_struct); + let decoded = >::from_bytes(&bytes); + assert_eq!(decoded, test_struct); + } + + #[test] + fn test_bincode_type_name() { + assert_eq!( + >::type_name(), + TypeName::new(&format!("Bincode<{}>", type_name::())) + ); + assert_eq!( + >::type_name(), + TypeName::new(&format!("Bincode<{}>", type_name::())) + ); + assert_eq!( + >::type_name(), + TypeName::new(&format!("Bincode<{}>", type_name::())) + ); + } + + #[test] + fn test_bincode_fixed_width() { + assert_eq!(>::fixed_width(), None); + assert_eq!(>::fixed_width(), None); + assert_eq!(>::fixed_width(), None); + } + + #[test] + fn test_preconf_request_data_serialization() { + let original = PreconfRequestData { + preconf_type: 1, + preconf_request: r#"{"key": "value"}"#.to_string(), + preconf_request_signature: "abcdef1234567890".to_string(), + }; + + // Serialize and deserialize using Bincode + let bytes = >::as_bytes(&original); + let decoded = >::from_bytes(&bytes); + + assert_eq!(decoded, original); + } + + #[test] + fn test_bincode_with_vector_of_preconf_data() { + // Create a vector of PreconfRequestData + let data_vec = vec![ + PreconfRequestData { + preconf_type: 0, + preconf_request: r#"{"key": "value"}"#.to_string(), + preconf_request_signature: "signature1".to_string(), + }, + PreconfRequestData { + preconf_type: 1, + preconf_request: r#"{"key": "value"}"#.to_string(), + preconf_request_signature: "signature2".to_string(), + }, + PreconfRequestData { + preconf_type: 0, + preconf_request: r#"{"key": "value"}"#.to_string(), + preconf_request_signature: "signature3".to_string(), + }, + ]; + + // Test serialization and deserialization of the vector + let bytes = >>::as_bytes(&data_vec); + let decoded = >>::from_bytes(&bytes); + + assert_eq!(decoded.len(), data_vec.len()); + assert_eq!(decoded, data_vec); + } + + #[test] + fn test_bincode_with_empty_vector_of_preconf_data() { + // Test with an empty vector + let empty_vec: Vec = vec![]; + + let bytes = >>::as_bytes(&empty_vec); + let decoded = >>::from_bytes(&bytes); + + assert_eq!(decoded.len(), 0); + assert!(decoded.is_empty()); + } + + #[test] + #[should_panic(expected = "Failed to decode bincode")] + fn test_bincode_decode_invalid_data() { + // Test decoding invalid data + let invalid_data = "invalid data"; // Invalid data that can't be decoded + >::from_bytes(invalid_data.as_bytes()); // This should panic + } +} From 9618bdcd39a290a88eccdb40d9edd5dc3a59ee61 Mon Sep 17 00:00:00 2001 From: martines3000 Date: Wed, 14 May 2025 15:50:47 +0200 Subject: [PATCH 14/15] chore: add tests for redb usage --- bin/taiyi-challenger/src/table_definitions.rs | 214 ++++++++++++++++++ 1 file changed, 214 insertions(+) diff --git a/bin/taiyi-challenger/src/table_definitions.rs b/bin/taiyi-challenger/src/table_definitions.rs index 4b9e26c1..32dd7818 100644 --- a/bin/taiyi-challenger/src/table_definitions.rs +++ b/bin/taiyi-challenger/src/table_definitions.rs @@ -6,3 +6,217 @@ pub const PRECONF_TABLE: TableDefinition>> TableDefinition::new("preconf"); pub const CHALLENGE_TABLE: TableDefinition>> = TableDefinition::new("challenge"); + +#[cfg(test)] +mod tests { + use std::fs; + + use redb::Database; + + use super::*; + + // Helper function to create a test PreconfRequestData + fn create_test_data(preconf_type: u8, index: u64) -> PreconfRequestData { + PreconfRequestData { + preconf_type, + preconf_request: format!(r#"{{"id": {}}}"#, index), + preconf_request_signature: format!("signature{}", index), + } + } + + // Helper function to create a temporary database path + fn temp_db_path(test_name: &str) -> String { + format!("/tmp/taiyi_test_{}.db", test_name) + } + + // Helper function to cleanup database files + fn cleanup_db(path: &str) { + let _ = fs::remove_file(path); + } + + #[test] + fn test_preconf_table() { + let db_path = temp_db_path("preconf_table"); + cleanup_db(&db_path); // Ensure clean state + + // Create a database + let db = Database::create(&db_path).unwrap(); + + // Start a write transaction and insert data + let write_tx = db.begin_write().unwrap(); + { + let mut table = write_tx.open_table(PRECONF_TABLE).unwrap(); + + // Create test data + let slot = 42u64; + let data = vec![create_test_data(0, 1), create_test_data(1, 2)]; + + // Insert the data + table.insert(&slot, data.clone()).unwrap(); + } + + // Commit the transaction + write_tx.commit().unwrap(); + + // Start a read transaction and verify data + let read_tx = db.begin_read().unwrap(); + let table = read_tx.open_table(PRECONF_TABLE).unwrap(); + + let slot = 42u64; + let expected_data = vec![create_test_data(0, 1), create_test_data(1, 2)]; + + // Read the data + let read_data = table.get(&slot).unwrap().unwrap().value(); + + // Verify the data + assert_eq!(read_data.len(), expected_data.len()); + assert_eq!(read_data, expected_data); + + // Clean up + drop(db); + cleanup_db(&db_path); + } + + #[test] + fn test_challenge_table() { + let db_path = temp_db_path("challenge_table"); + cleanup_db(&db_path); // Ensure clean state + + // Create a database + let db = Database::create(&db_path).unwrap(); + + let slot = 100u64; + let data = vec![create_test_data(0, 3)]; + + // Write transaction + let write_tx = db.begin_write().unwrap(); + { + // Open the CHALLENGE_TABLE + let mut table = write_tx.open_table(CHALLENGE_TABLE).unwrap(); + + // Insert the data + table.insert(&slot, data.clone()).unwrap(); + } + + // Commit the transaction + write_tx.commit().unwrap(); + + // Read transaction + let read_tx = db.begin_read().unwrap(); + let table = read_tx.open_table(CHALLENGE_TABLE).unwrap(); + + // Read the data + let read_data = table.get(&slot).unwrap().unwrap().value(); + + // Verify the data + assert_eq!(read_data, data); + + // Clean up + drop(db); + cleanup_db(&db_path); + } + + #[test] + fn test_multiple_slots() { + let db_path = temp_db_path("multiple_slots"); + cleanup_db(&db_path); // Ensure clean state + + // Create a database + let db = Database::create(&db_path).unwrap(); + + // Write transaction + let write_tx = db.begin_write().unwrap(); + { + // Open both tables + let mut preconf_table = write_tx.open_table(PRECONF_TABLE).unwrap(); + let mut challenge_table = write_tx.open_table(CHALLENGE_TABLE).unwrap(); + + // Create and insert test data for multiple slots + for slot in 1..5 { + let preconf_data = + vec![create_test_data(0, slot * 10), create_test_data(1, slot * 10 + 1)]; + + let challenge_data = vec![create_test_data(1, slot * 100)]; + + preconf_table.insert(&slot, preconf_data).unwrap(); + challenge_table.insert(&slot, challenge_data).unwrap(); + } + } + + // Commit the transaction + write_tx.commit().unwrap(); + + // Read transaction + let read_tx = db.begin_read().unwrap(); + let preconf_table = read_tx.open_table(PRECONF_TABLE).unwrap(); + let challenge_table = read_tx.open_table(CHALLENGE_TABLE).unwrap(); + + // Verify the data for each slot + for slot in 1..5 { + let preconf_data = preconf_table.get(&slot).unwrap().unwrap().value(); + let challenge_data = challenge_table.get(&slot).unwrap().unwrap().value(); + + assert_eq!(preconf_data.len(), 2); + assert_eq!(preconf_data[0].preconf_type, 0); + assert_eq!(preconf_data[1].preconf_type, 1); + + assert_eq!(challenge_data.len(), 1); + assert_eq!(challenge_data[0].preconf_type, 1); + } + + // Clean up + drop(db); + cleanup_db(&db_path); + } + + #[test] + fn test_update_existing_data() { + let db_path = temp_db_path("update_existing"); + cleanup_db(&db_path); // Ensure clean state + + // Create a database + let db = Database::create(&db_path).unwrap(); + + let slot = 200u64; + let initial_data = vec![create_test_data(0, 1)]; + + // First write transaction - insert initial data + let write_tx = db.begin_write().unwrap(); + { + let mut table = write_tx.open_table(PRECONF_TABLE).unwrap(); + table.insert(&slot, initial_data.clone()).unwrap(); + } + write_tx.commit().unwrap(); + + // Read the data and create updated data + let updated_data = { + let read_tx = db.begin_read().unwrap(); + let table = read_tx.open_table(PRECONF_TABLE).unwrap(); + let mut data = table.get(&slot).unwrap().unwrap().value(); + + // Append new data + data.push(create_test_data(1, 2)); + data + }; + + // Second write transaction - update with new data + let write_tx = db.begin_write().unwrap(); + { + let mut table = write_tx.open_table(PRECONF_TABLE).unwrap(); + table.insert(&slot, updated_data.clone()).unwrap(); + } + write_tx.commit().unwrap(); + + // Verify the updated data + let read_tx = db.begin_read().unwrap(); + let table = read_tx.open_table(PRECONF_TABLE).unwrap(); + let read_data = table.get(&slot).unwrap().unwrap().value(); + + assert_eq!(read_data.len(), 2); + assert_eq!(read_data, updated_data); + + // Clean up + drop(db); + cleanup_db(&db_path); + } +} From 6f05a0d4966d809b30d7b3bc4efe7407aed6f7c5 Mon Sep 17 00:00:00 2001 From: martines3000 Date: Wed, 14 May 2025 16:04:31 +0200 Subject: [PATCH 15/15] fix: resolve `Cargo.lock` conflicts --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 18c2e7e7..3afab834 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6915,9 +6915,9 @@ checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" [[package]] name = "redb" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0a72cd7140de9fc3e318823b883abf819c20d478ec89ce880466dc2ef263c6" +checksum = "34bc6763177194266fc3773e2b2bb3693f7b02fdf461e285aa33202e3164b74e" dependencies = [ "libc", ]