From 196dd96b068f484291f7b889dfd8a62fdfc92da5 Mon Sep 17 00:00:00 2001 From: Blake <0xblake.sg@gmail.com> Date: Wed, 26 Aug 2026 17:21:33 +0800 Subject: [PATCH 1/2] Add MIN_REQUIRED_VERIFIER env for nodes. --- .env.template | 6 +++++- deployment/README.md | 3 +++ .../regtest/bitvm-noded/committee_0/.env.committee_0 | 3 ++- .../regtest/bitvm-noded/committee_1/.env.committee_1 | 3 ++- .../regtest/bitvm-noded/operator_0/.env.operator_0 | 3 ++- .../regtest/bitvm-noded/verifier_0/.env.verifier_0 | 3 ++- .../bitvm-noded/watchtower_0/.env.watchtower_0 | 3 ++- .../bitvm-noded/committee_0/.env.committee_0 | 3 ++- .../bitvm-noded/committee_1/.env.committee_1 | 3 ++- .../testnet4/bitvm-noded/operator_0/.env.operator_0 | 3 ++- .../testnet4/bitvm-noded/verifier_0/.env.verifier_0 | 3 ++- .../bitvm-noded/watchtower_0/.env.watchtower_0 | 3 ++- node/src/env.rs | 12 ++++++++++++ node/src/main.rs | 2 ++ node/src/utils.rs | 4 ++-- 15 files changed, 44 insertions(+), 13 deletions(-) diff --git a/.env.template b/.env.template index 1c315f44..40784ec7 100644 --- a/.env.template +++ b/.env.template @@ -3,6 +3,10 @@ RUST_LOG=debug # required ACTOR=Verifier +# optional, positive integer; must be identical on all participating nodes. +# Applies to newly created graphs only. Existing finalized graphs keep their original verifier slots. +MIN_REQUIRED_VERIFIER=1 + # required GOAT_GATEWAY_CONTRACT_ADDRESS= @@ -25,4 +29,4 @@ GOAT_ADDRESS= PROTO_NAME=bitvmt3 # setup bootnodes, separate multiple nodes by comma ",". -BOOTNODES= \ No newline at end of file +BOOTNODES= diff --git a/deployment/README.md b/deployment/README.md index 7b00e497..2126dd23 100644 --- a/deployment/README.md +++ b/deployment/README.md @@ -58,6 +58,9 @@ Create a `.env` file and include necessary environment variables: | `GOAT_SWAP_EVENT_THE_GRAPH_URL` | The Graph URL for querying Swap events | Optional | | `BOOTNODES` | Multiaddr of bootnodes for P2P discovery | Optional | | `NODE_NAME` | The name of the node | Optional | +| `MIN_REQUIRED_VERIFIER` | Positive verifier-slot count for newly created graphs (default: `1`); must be identical on all participating nodes | Optional | + +`MIN_REQUIRED_VERIFIER` changes the transaction topology of newly created graphs. It does not add Verifier slots to existing finalized graphs, which continue to use the slots embedded when they were created. Configure the same value on every Operator, Verifier, Committee, Watchtower, and Relayer node before creating new graphs. ## Roles Deployment diff --git a/deployment/regtest/bitvm-noded/committee_0/.env.committee_0 b/deployment/regtest/bitvm-noded/committee_0/.env.committee_0 index c5a9643f..4a03d812 100644 --- a/deployment/regtest/bitvm-noded/committee_0/.env.committee_0 +++ b/deployment/regtest/bitvm-noded/committee_0/.env.committee_0 @@ -15,6 +15,7 @@ GOAT_CHAIN_URL=https://rpc.testnet3.goat.network ENABLE_UPDATE_SPV_CONTRACT=true # the graph config +MIN_REQUIRED_VERIFIER=1 GOAT_GATEWAY_CONTRACT_ADDRESS= GOAT_GATEWAY_EVENT_THE_GRAPH_URL= # block number to start filtering events from @@ -32,4 +33,4 @@ GENESIS_SEQUENCER_COMMIT_TXID= # libp2p config PEER_KEY= -PROTO_NAME=bitvm_regtest \ No newline at end of file +PROTO_NAME=bitvm_regtest diff --git a/deployment/regtest/bitvm-noded/committee_1/.env.committee_1 b/deployment/regtest/bitvm-noded/committee_1/.env.committee_1 index 32c9b4e9..f5d327d3 100644 --- a/deployment/regtest/bitvm-noded/committee_1/.env.committee_1 +++ b/deployment/regtest/bitvm-noded/committee_1/.env.committee_1 @@ -10,6 +10,7 @@ BTC_CHAIN_URL=http://127.0.0.1:3002 GOAT_CHAIN_URL=https://rpc.testnet3.goat.network # the graph config +MIN_REQUIRED_VERIFIER=1 GOAT_GATEWAY_CONTRACT_ADDRESS= GOAT_GATEWAY_EVENT_THE_GRAPH_URL= # block number to start filtering events from @@ -28,4 +29,4 @@ GENESIS_SEQUENCER_COMMIT_TXID= # libp2p config PEER_KEY= BOOTNODES=/ip4/127.0.0.1/tcp/8444/p2p/12D3KooWKmAQDFCibTTpKTi6Kwk9SmtLGDXhv5fEsc9gr1Vdcu4j -PROTO_NAME=bitvm_regtest \ No newline at end of file +PROTO_NAME=bitvm_regtest diff --git a/deployment/regtest/bitvm-noded/operator_0/.env.operator_0 b/deployment/regtest/bitvm-noded/operator_0/.env.operator_0 index 8d7796b5..d010f406 100644 --- a/deployment/regtest/bitvm-noded/operator_0/.env.operator_0 +++ b/deployment/regtest/bitvm-noded/operator_0/.env.operator_0 @@ -10,6 +10,7 @@ BTC_CHAIN_URL=http://127.0.0.1:3002 GOAT_CHAIN_URL=https://rpc.testnet3.goat.network # the graph config +MIN_REQUIRED_VERIFIER=1 GOAT_GATEWAY_CONTRACT_ADDRESS= GOAT_GATEWAY_EVENT_THE_GRAPH_URL= # block number to start filtering events from @@ -52,4 +53,4 @@ FGC_COMPACT_OUT_INDICES_PATH= SGC_COMPACT_GATES_PATH= SGC_COMPACT_OUT_INDICES_PATH= -ENABLE_BABE_SETUP_STATE_CLEANUP=true \ No newline at end of file +ENABLE_BABE_SETUP_STATE_CLEANUP=true diff --git a/deployment/regtest/bitvm-noded/verifier_0/.env.verifier_0 b/deployment/regtest/bitvm-noded/verifier_0/.env.verifier_0 index 79c14e82..86a05536 100644 --- a/deployment/regtest/bitvm-noded/verifier_0/.env.verifier_0 +++ b/deployment/regtest/bitvm-noded/verifier_0/.env.verifier_0 @@ -10,6 +10,7 @@ BTC_CHAIN_URL=http://127.0.0.1:3002 GOAT_CHAIN_URL=https://rpc.testnet3.goat.network # the graph config +MIN_REQUIRED_VERIFIER=1 GOAT_GATEWAY_CONTRACT_ADDRESS= GOAT_GATEWAY_EVENT_THE_GRAPH_URL= GOAT_GATEWAY_EVENT_FILTER_FROM= @@ -47,4 +48,4 @@ FGC_COMPACT_OUT_INDICES_PATH= SGC_COMPACT_GATES_PATH= SGC_COMPACT_OUT_INDICES_PATH= -ENABLE_BABE_SETUP_STATE_CLEANUP=true \ No newline at end of file +ENABLE_BABE_SETUP_STATE_CLEANUP=true diff --git a/deployment/regtest/bitvm-noded/watchtower_0/.env.watchtower_0 b/deployment/regtest/bitvm-noded/watchtower_0/.env.watchtower_0 index 182782f1..50ed6190 100644 --- a/deployment/regtest/bitvm-noded/watchtower_0/.env.watchtower_0 +++ b/deployment/regtest/bitvm-noded/watchtower_0/.env.watchtower_0 @@ -11,6 +11,7 @@ GOAT_CHAIN_URL=https://rpc.testnet3.goat.network GOAT_PROOF_BUILD_URL= # the graph config +MIN_REQUIRED_VERIFIER=1 GOAT_GATEWAY_CONTRACT_ADDRESS= GOAT_GATEWAY_EVENT_THE_GRAPH_URL= GOAT_GATEWAY_EVENT_FILTER_FROM= @@ -26,4 +27,4 @@ PEER_KEY= BOOTNODES=/ip4/127.0.0.1/tcp/8444/p2p/12D3KooWKmAQDFCibTTpKTi6Kwk9SmtLGDXhv5fEsc9gr1Vdcu4j PROTO_NAME=bitvm_regtest -GENESIS_SEQUENCER_COMMIT_TXID= \ No newline at end of file +GENESIS_SEQUENCER_COMMIT_TXID= diff --git a/deployment/testnet4/bitvm-noded/committee_0/.env.committee_0 b/deployment/testnet4/bitvm-noded/committee_0/.env.committee_0 index 5116cd7f..5524aa8e 100644 --- a/deployment/testnet4/bitvm-noded/committee_0/.env.committee_0 +++ b/deployment/testnet4/bitvm-noded/committee_0/.env.committee_0 @@ -15,6 +15,7 @@ GOAT_CHAIN_URL=https://rpc.testnet3.goat.network ENABLE_UPDATE_SPV_CONTRACT=true # the graph config +MIN_REQUIRED_VERIFIER=2 GOAT_GATEWAY_CONTRACT_ADDRESS=0x60a5b099eE011018DD064F9eBD50d381C37b5e8b GOAT_GATEWAY_EVENT_THE_GRAPH_URL=https://graph.goat.network/subgraphs/name/bitvmt4_gateway_0x60a5 GOAT_GATEWAY_EVENT_FILTER_FROM=15908000 @@ -30,4 +31,4 @@ GOAT_PRIVATE_KEY= PEER_KEY= PROTO_NAME=bitvmt4 -GENESIS_SEQUENCER_COMMIT_TXID=c2d816940391da550af23c7924f8d9ecd276cdf6a22a164fc44443f5eba6d4b5 \ No newline at end of file +GENESIS_SEQUENCER_COMMIT_TXID=c2d816940391da550af23c7924f8d9ecd276cdf6a22a164fc44443f5eba6d4b5 diff --git a/deployment/testnet4/bitvm-noded/committee_1/.env.committee_1 b/deployment/testnet4/bitvm-noded/committee_1/.env.committee_1 index 030f118b..773624e9 100644 --- a/deployment/testnet4/bitvm-noded/committee_1/.env.committee_1 +++ b/deployment/testnet4/bitvm-noded/committee_1/.env.committee_1 @@ -10,6 +10,7 @@ BTC_CHAIN_URL=https://mempool.space/testnet4/api GOAT_CHAIN_URL=https://rpc.testnet3.goat.network # the graph config +MIN_REQUIRED_VERIFIER=2 GOAT_GATEWAY_CONTRACT_ADDRESS=0x60a5b099eE011018DD064F9eBD50d381C37b5e8b GOAT_GATEWAY_EVENT_THE_GRAPH_URL=https://graph.goat.network/subgraphs/name/bitvmt4_gateway_0x60a5 GOAT_GATEWAY_EVENT_FILTER_FROM=15908000 @@ -26,4 +27,4 @@ PEER_KEY= BOOTNODES=/ip4/16.146.8.146/tcp/8445/p2p/12D3KooWCrPTAmhFdC5DBGgkxZvJi6iuSeiDWKRL87isrt4iMHXv PROTO_NAME=bitvmt4 -GENESIS_SEQUENCER_COMMIT_TXID=c2d816940391da550af23c7924f8d9ecd276cdf6a22a164fc44443f5eba6d4b5 \ No newline at end of file +GENESIS_SEQUENCER_COMMIT_TXID=c2d816940391da550af23c7924f8d9ecd276cdf6a22a164fc44443f5eba6d4b5 diff --git a/deployment/testnet4/bitvm-noded/operator_0/.env.operator_0 b/deployment/testnet4/bitvm-noded/operator_0/.env.operator_0 index e580e75c..d8c2fb75 100644 --- a/deployment/testnet4/bitvm-noded/operator_0/.env.operator_0 +++ b/deployment/testnet4/bitvm-noded/operator_0/.env.operator_0 @@ -11,6 +11,7 @@ GOAT_PROOF_BUILD_URL= GOAT_CHAIN_URL=https://rpc.testnet3.goat.network # the graph config +MIN_REQUIRED_VERIFIER=2 GOAT_GATEWAY_CONTRACT_ADDRESS=0x60a5b099eE011018DD064F9eBD50d381C37b5e8b GOAT_GATEWAY_EVENT_THE_GRAPH_URL=https://graph.goat.network/subgraphs/name/bitvmt4_gateway_0x60a5 GOAT_GATEWAY_EVENT_FILTER_FROM=15908000 @@ -47,4 +48,4 @@ FGC_COMPACT_OUT_INDICES_PATH= SGC_COMPACT_GATES_PATH= SGC_COMPACT_OUT_INDICES_PATH= -ENABLE_BABE_SETUP_STATE_CLEANUP=true \ No newline at end of file +ENABLE_BABE_SETUP_STATE_CLEANUP=true diff --git a/deployment/testnet4/bitvm-noded/verifier_0/.env.verifier_0 b/deployment/testnet4/bitvm-noded/verifier_0/.env.verifier_0 index c98d89bc..1c2d7628 100644 --- a/deployment/testnet4/bitvm-noded/verifier_0/.env.verifier_0 +++ b/deployment/testnet4/bitvm-noded/verifier_0/.env.verifier_0 @@ -10,6 +10,7 @@ BTC_CHAIN_URL=https://mempool.space/testnet4/api GOAT_CHAIN_URL=https://rpc.testnet3.goat.network # the graph config +MIN_REQUIRED_VERIFIER=2 GOAT_GATEWAY_CONTRACT_ADDRESS=0x60a5b099eE011018DD064F9eBD50d381C37b5e8b GOAT_GATEWAY_EVENT_THE_GRAPH_URL=https://graph.goat.network/subgraphs/name/bitvmt4_gateway_0x60a5 GOAT_GATEWAY_EVENT_FILTER_FROM=15908000 @@ -48,4 +49,4 @@ FGC_COMPACT_OUT_INDICES_PATH= SGC_COMPACT_GATES_PATH= SGC_COMPACT_OUT_INDICES_PATH= -ENABLE_BABE_SETUP_STATE_CLEANUP=true \ No newline at end of file +ENABLE_BABE_SETUP_STATE_CLEANUP=true diff --git a/deployment/testnet4/bitvm-noded/watchtower_0/.env.watchtower_0 b/deployment/testnet4/bitvm-noded/watchtower_0/.env.watchtower_0 index 2100c570..5dda8638 100644 --- a/deployment/testnet4/bitvm-noded/watchtower_0/.env.watchtower_0 +++ b/deployment/testnet4/bitvm-noded/watchtower_0/.env.watchtower_0 @@ -11,6 +11,7 @@ GOAT_CHAIN_URL=https://rpc.testnet3.goat.network GOAT_PROOF_BUILD_URL= # the graph config +MIN_REQUIRED_VERIFIER=2 GOAT_GATEWAY_CONTRACT_ADDRESS=0x60a5b099eE011018DD064F9eBD50d381C37b5e8b GOAT_GATEWAY_EVENT_THE_GRAPH_URL=https://graph.goat.network/subgraphs/name/bitvmt4_gateway_0x60a5 GOAT_GATEWAY_EVENT_FILTER_FROM=15908000 @@ -26,4 +27,4 @@ PEER_KEY= BOOTNODES=/ip4/16.146.8.146/tcp/8445/p2p/12D3KooWCrPTAmhFdC5DBGgkxZvJi6iuSeiDWKRL87isrt4iMHXv PROTO_NAME=bitvmt4 -GENESIS_SEQUENCER_COMMIT_TXID=c2d816940391da550af23c7924f8d9ecd276cdf6a22a164fc44443f5eba6d4b5 \ No newline at end of file +GENESIS_SEQUENCER_COMMIT_TXID=c2d816940391da550af23c7924f8d9ecd276cdf6a22a164fc44443f5eba6d4b5 diff --git a/node/src/env.rs b/node/src/env.rs index 35f3d062..97011bf2 100644 --- a/node/src/env.rs +++ b/node/src/env.rs @@ -60,6 +60,7 @@ pub const ENV_PROOF_SEVER_URL: &str = "PROOF_SEVER_URL"; pub const ENV_ACTOR: &str = "ACTOR"; pub const ENV_IPFS_ENDPOINT: &str = "IPFS_ENDPOINT"; pub const ENV_COMMITTEE_NUM: &str = "COMMITTEE_NUM"; +pub const ENV_MIN_REQUIRED_VERIFIER: &str = "MIN_REQUIRED_VERIFIER"; pub const ENV_EXTERNAL_SOCKET_ADDR: &str = "EXTERNAL_SOCKET_ADDR"; pub const COMMITTEE_INSTANCE_KEYS_DIR: &str = "cache/committee-instance-keys/"; pub const SCRIPT_CACHE_FILE_NAME: &str = "cache/partial_script.bin"; @@ -69,6 +70,7 @@ pub const DUST_AMOUNT: u64 = goat::transactions::base::DUST_AMOUNT; pub const MAX_CUSTOM_INPUTS: usize = 100; pub const DEFAULT_CONFIRMATION_TARGET: u16 = 1; +pub const DEFAULT_MIN_REQUIRED_VERIFIER: usize = 1; pub const ENV_BITCOIN_NETWORK: &str = "BITCOIN_NETWORK"; pub const ENV_GOAT_NETWORK: &str = "GOAT_NETWORK"; @@ -577,6 +579,16 @@ pub fn get_operator_proof_wait_secs() -> usize { .unwrap_or(DEFAULT_OPERATOR_PROOF_WAIT_SECS) } +pub fn get_min_required_verifier() -> anyhow::Result { + match std::env::var(ENV_MIN_REQUIRED_VERIFIER) { + Err(std::env::VarError::NotPresent) => Ok(DEFAULT_MIN_REQUIRED_VERIFIER), + Err(error) => Err(anyhow::anyhow!("failed to read {ENV_MIN_REQUIRED_VERIFIER}: {error}")), + Ok(value) => value.parse::().ok().filter(|value| *value > 0).ok_or_else(|| { + anyhow::anyhow!("{ENV_MIN_REQUIRED_VERIFIER} must be a positive integer, got {value:?}") + }), + } +} + pub fn get_soldering_proof_payload_store_path() -> anyhow::Result { let value = std::env::var(ENV_SOLDERING_PROOF_PAYLOAD_STORE_PATH) .map_err(|_| anyhow::anyhow!("{ENV_SOLDERING_PROOF_PAYLOAD_STORE_PATH} needs to be set"))?; diff --git a/node/src/main.rs b/node/src/main.rs index 5983787d..daffed12 100644 --- a/node/src/main.rs +++ b/node/src/main.rs @@ -131,6 +131,7 @@ async fn main() -> Result<(), Box> { .with_span_list(true) .with_env_filter(EnvFilter::from_default_env()) .try_init(); + let min_required_verifier = env::get_min_required_verifier()?; validate_soldering_proof_payload_store_config(&actor)?; let is_publisher = actor == Actor::Publisher || actor == Actor::All; @@ -202,6 +203,7 @@ async fn main() -> Result<(), Box> { bitcoin_network = ?get_network(), version = env!("CARGO_PKG_VERSION"), rpc_addr = %opt.rpc_addr, + min_required_verifier, "node initialization completed" ); diff --git a/node/src/utils.rs b/node/src/utils.rs index 511e86c7..6cd98ad2 100644 --- a/node/src/utils.rs +++ b/node/src/utils.rs @@ -194,8 +194,8 @@ pub mod todo_funcs { 1 } pub fn min_required_verifier() -> usize { - // todo!("get verifier num") - 1 + get_min_required_verifier() + .unwrap_or_else(|error| panic!("invalid verifier count configuration: {error}")) } /// Checks graph Watchtower membership, uniqueness, and supported count. From 37a64ab4f22e80b93c325dfcd0f22b5af7bf88e9 Mon Sep 17 00:00:00 2001 From: Blake <0xblake.sg@gmail.com> Date: Wed, 26 Aug 2026 18:35:34 +0800 Subject: [PATCH 2/2] remove todo_funcs module --- node/src/handle.rs | 75 ++-- node/src/utils.rs | 1016 +++++++++++++++++++++----------------------- 2 files changed, 528 insertions(+), 563 deletions(-) diff --git a/node/src/handle.rs b/node/src/handle.rs index 513e93f0..5a43fd30 100644 --- a/node/src/handle.rs +++ b/node/src/handle.rs @@ -771,11 +771,11 @@ fn freeze_operator_candidates(state: &mut OperatorBabeSetupState) -> Result<()> if state.frozen_verifier_pubkeys.is_some() { return Ok(()); } - if state.candidates.len() < todo_funcs::min_required_verifier() { + if state.candidates.len() < min_required_verifier() { bail!( "cannot freeze {} verifier candidates before reaching target {}", state.candidates.len(), - todo_funcs::min_required_verifier() + min_required_verifier() ); } let mut verifier_peer_ids = std::collections::HashSet::new(); @@ -784,7 +784,7 @@ fn freeze_operator_candidates(state: &mut OperatorBabeSetupState) -> Result<()> bail!("cannot freeze duplicate verifier peer id"); } } - state.candidates.truncate(todo_funcs::min_required_verifier()); + state.candidates.truncate(min_required_verifier()); state.candidates.sort_by_key(|candidate| candidate.verifier_pubkey.to_bytes()); for (verifier_index, candidate) in state.candidates.iter_mut().enumerate() { candidate.verifier_index = Some(verifier_index); @@ -1648,7 +1648,7 @@ async fn handle_gen_circuits_operator( } if operator_state.frozen_verifier_pubkeys.is_none() - && operator_state.candidates.len() < todo_funcs::min_required_verifier() + && operator_state.candidates.len() < min_required_verifier() { save_babe_setup_state(ctx.local_db, instance_id, graph_id, &state)?; @@ -2518,7 +2518,7 @@ async fn try_start_graph_committee_setup( ); return Ok(()); } - let validation = todo_funcs::validate_init_graph( + let validation = validate_init_graph( ctx.local_db, ctx.btc_client, ctx.goat_client, @@ -2744,8 +2744,7 @@ async fn handle_create_graph_committee( // 1. check graph data & operator stake without verifier params endorsements; those may arrive later. let validation = - todo_funcs::validate_init_graph_base(ctx.local_db, ctx.btc_client, ctx.goat_client, graph) - .await; + validate_init_graph_base(ctx.local_db, ctx.btc_client, ctx.goat_client, graph).await; ctx.metrics_state.record_graph_validation(validation.is_ok()); if let Err(e) = validation { if should_ignore_invalid_graph(&e, instance_id, graph_id, "CreateGraph", None) { @@ -3429,7 +3428,7 @@ async fn handle_graph_finalize_committee( // received from Operator // 1. check graph data - let validation = todo_funcs::validate_finalized_graph( + let validation = validate_finalized_graph( ctx.btc_client, ctx.goat_client, graph, @@ -3561,7 +3560,7 @@ async fn handle_graph_finalize_default( // received from Operator // 1. check graph data - let validation = todo_funcs::validate_finalized_graph( + let validation = validate_finalized_graph( ctx.btc_client, ctx.goat_client, graph, @@ -3926,7 +3925,7 @@ async fn handle_post_ready(ctx: &mut HandlerContext<'_>, instance_id: Uuid) -> R let pegin_tx = match ctx.btc_client.get_tx(&pegin_txid).await? { Some(tx) => tx, None => { - let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()); + let delay_secs = avg_block_time_secs(ctx.btc_client.network()); let message = GOATMessage::new( ctx.actor.clone(), GOATMessageContent::PostReady(PostReady { instance_id }), @@ -3952,7 +3951,7 @@ async fn handle_post_ready(ctx: &mut HandlerContext<'_>, instance_id: Uuid) -> R .map(|(_, es)| es) .collect::>(); if endorse_sigs.len() != committee_pubkeys.len() { - let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()); + let delay_secs = avg_block_time_secs(ctx.btc_client.network()); let message = GOATMessage::new( ctx.actor.clone(), GOATMessageContent::PostReady(PostReady { instance_id }), @@ -3975,7 +3974,7 @@ async fn handle_post_ready(ctx: &mut HandlerContext<'_>, instance_id: Uuid) -> R let pegin_height = match ctx.btc_client.get_tx_status(&pegin_txid).await?.block_height { Some(height) => height as u64, None => { - let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()); + let delay_secs = avg_block_time_secs(ctx.btc_client.network()); let message = GOATMessage::new( ctx.actor.clone(), GOATMessageContent::PostReady(PostReady { instance_id }), @@ -3997,7 +3996,7 @@ async fn handle_post_ready(ctx: &mut HandlerContext<'_>, instance_id: Uuid) -> R }; let goat_confirmed_height = ctx.goat_client.btc_spv_latest_height().await?; if goat_confirmed_height < pegin_height { - let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()) + let delay_secs = avg_block_time_secs(ctx.btc_client.network()) * (pegin_height - goat_confirmed_height); let message = GOATMessage::new( ctx.actor.clone(), @@ -4059,7 +4058,7 @@ async fn handle_post_ready(ctx: &mut HandlerContext<'_>, instance_id: Uuid) -> R .await?; } if missing_graph_endorsements { - let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()); + let delay_secs = avg_block_time_secs(ctx.btc_client.network()); let message = GOATMessage::new( ctx.actor.clone(), GOATMessageContent::PostReady(PostReady { instance_id }), @@ -4193,7 +4192,7 @@ async fn handle_kickoff_ready_operator( ctx.btc_client.network(), ¤t_graph.parameters.timelock_config, ) as u64 - * todo_funcs::avg_block_time_secs(ctx.btc_client.network()); + * avg_block_time_secs(ctx.btc_client.network()); let delay_secs = min_pegout_time_secs * nonce_interval; push_local_unhandled_messages_with_reason( ctx.local_db, @@ -4210,7 +4209,7 @@ async fn handle_kickoff_ready_operator( tracing::info!( "Operator {operator_pubkey} skipped obsoleted graph {current_instance_id}:{current_graph_id}" ); - let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()); // wait for 1 blocks + let delay_secs = avg_block_time_secs(ctx.btc_client.network()); // wait for 1 blocks push_local_unhandled_messages_with_reason( ctx.local_db, graph_id, @@ -4234,7 +4233,7 @@ async fn handle_kickoff_ready_operator( ctx.btc_client.network(), ¤t_graph.parameters.timelock_config, ) as u64 - * todo_funcs::avg_block_time_secs(ctx.btc_client.network()); + * avg_block_time_secs(ctx.btc_client.network()); let delay_secs = min_pegout_time_secs * nonce_interval; push_local_unhandled_messages_with_reason( ctx.local_db, @@ -4251,7 +4250,7 @@ async fn handle_kickoff_ready_operator( tracing::info!( "Operator {operator_pubkey} skipped non-posted graph {current_instance_id}:{current_graph_id}" ); - let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()); // wait for 1 blocks + let delay_secs = avg_block_time_secs(ctx.btc_client.network()); // wait for 1 blocks push_local_unhandled_messages_with_reason( ctx.local_db, graph_id, @@ -4310,7 +4309,7 @@ async fn handle_kickoff_sent_committee( let kickoff_height = match ctx.btc_client.get_tx_status(&kickoff_txid).await?.block_height { Some(height) => height as u64, None => { - let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()); + let delay_secs = avg_block_time_secs(ctx.btc_client.network()); let message = make_message(ctx, content); push_local_unhandled_messages_with_reason( ctx.local_db, @@ -4329,7 +4328,7 @@ async fn handle_kickoff_sent_committee( }; let goat_confirmed_btc_height = ctx.goat_client.btc_spv_latest_height().await?; if goat_confirmed_btc_height < kickoff_height { - let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()) + let delay_secs = avg_block_time_secs(ctx.btc_client.network()) * (kickoff_height - goat_confirmed_btc_height); let message = make_message(ctx, content); push_local_unhandled_messages_with_reason( @@ -5012,7 +5011,7 @@ async fn handle_operator_commit_pubin_ready_operator( let num_watchtowers = graph.parameters.watchtower_pubkeys.len(); let watchtower_timeout_txids: Vec = graph.watchtower_challenge_timeouts.iter().map(|tx| tx.tx().compute_txid()).collect(); - let wait_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()) as usize; + let wait_secs = avg_block_time_secs(ctx.btc_client.network()) as usize; let WatchtowerChallengeInfo { included_watchtowers: included_watchtowers_bits, resolved_branch_txids, @@ -5361,7 +5360,7 @@ async fn handle_assert_sent_verifier( { Ok(txins) => txins, Err(e) => { - let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()); + let delay_secs = avg_block_time_secs(ctx.btc_client.network()); let message = make_message(ctx, content); push_local_unhandled_messages_with_reason( ctx.local_db, @@ -5420,7 +5419,7 @@ async fn handle_assert_sent_verifier( "PubinDisprove invalid for {instance_id}:{graph_id}: operator pubin consistent, proceeding to ChallengeAssert" ), Err(e) => { - let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()); + let delay_secs = avg_block_time_secs(ctx.btc_client.network()); let message = make_message(ctx, content); push_local_unhandled_messages_with_reason( ctx.local_db, @@ -5600,7 +5599,7 @@ async fn handle_challenge_assert_sent_operator( validate_expected_challenge_assert_txid(&graph, verifier_index, challenge_assert_txid)?; let Some(challenge_assert_tx) = ctx.btc_client.get_tx(&challenge_assert_txid).await? else { - let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()); + let delay_secs = avg_block_time_secs(ctx.btc_client.network()); let message = make_message(ctx, content); push_local_unhandled_messages_with_reason( ctx.local_db, @@ -5771,7 +5770,7 @@ async fn handle_wrongly_challenge_timeout_verifier( return Ok(()); } - let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()); + let delay_secs = avg_block_time_secs(ctx.btc_client.network()); let message = make_message(ctx, content); if ctx.btc_client.get_tx(&challenge_assert_txid).await?.is_none() { push_local_unhandled_messages_with_reason( @@ -5820,8 +5819,8 @@ async fn handle_wrongly_challenge_timeout_verifier( ) as u64; let bitcoin_height = ctx.btc_client.get_height().await? as u64; if bitcoin_height < disprove_height { - let retry_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()) - * (disprove_height - bitcoin_height); + let retry_secs = + avg_block_time_secs(ctx.btc_client.network()) * (disprove_height - bitcoin_height); push_local_unhandled_messages_with_reason( ctx.local_db, graph_id, @@ -5922,7 +5921,7 @@ async fn handle_disprove_sent_committee( { Some(height) => height as u64, None => { - let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()); + let delay_secs = avg_block_time_secs(ctx.btc_client.network()); push_local_unhandled_messages_with_reason( ctx.local_db, graph_id, @@ -5940,7 +5939,7 @@ async fn handle_disprove_sent_committee( }; let goat_confirmed_height = ctx.goat_client.btc_spv_latest_height().await?; if goat_confirmed_height < challenge_finish_height { - let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()) + let delay_secs = avg_block_time_secs(ctx.btc_client.network()) * (challenge_finish_height - goat_confirmed_height); push_local_unhandled_messages_with_reason( ctx.local_db, @@ -6089,7 +6088,7 @@ async fn handle_take1_sent_committee( let withdraw_status = ctx.goat_client.gateway_get_withdraw_data(&graph_id).await?.status; if withdraw_status == WithdrawStatus::Initialized { // Kickoff not posted yet, wait for it - let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()) * 6; // wait for 6 blocks + let delay_secs = avg_block_time_secs(ctx.btc_client.network()) * 6; // wait for 6 blocks push_local_unhandled_messages_with_reason( ctx.local_db, graph_id, @@ -6113,7 +6112,7 @@ async fn handle_take1_sent_committee( let take1_height = match ctx.btc_client.get_tx_status(&take1_txid).await?.block_height { Some(height) => height as u64, None => { - let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()); // wait for 1 block + let delay_secs = avg_block_time_secs(ctx.btc_client.network()); // wait for 1 block push_local_unhandled_messages_with_reason( ctx.local_db, graph_id, @@ -6131,8 +6130,8 @@ async fn handle_take1_sent_committee( }; let goat_confirmed_height = ctx.goat_client.btc_spv_latest_height().await?; if goat_confirmed_height < take1_height { - let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()) - * (take1_height - goat_confirmed_height); + let delay_secs = + avg_block_time_secs(ctx.btc_client.network()) * (take1_height - goat_confirmed_height); push_local_unhandled_messages_with_reason( ctx.local_db, graph_id, @@ -6304,7 +6303,7 @@ async fn handle_take2_sent_committee( let withdraw_status = ctx.goat_client.gateway_get_withdraw_data(&graph_id).await?.status; if withdraw_status == WithdrawStatus::Initialized { // Kickoff not posted yet, wait for it - let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()) * 6; // wait for 6 blocks + let delay_secs = avg_block_time_secs(ctx.btc_client.network()) * 6; // wait for 6 blocks push_local_unhandled_messages_with_reason( ctx.local_db, graph_id, @@ -6328,7 +6327,7 @@ async fn handle_take2_sent_committee( let take2_height = match ctx.btc_client.get_tx_status(&take2_txid).await?.block_height { Some(height) => height as u64, None => { - let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()); // wait for 1 block + let delay_secs = avg_block_time_secs(ctx.btc_client.network()); // wait for 1 block push_local_unhandled_messages_with_reason( ctx.local_db, graph_id, @@ -6346,8 +6345,8 @@ async fn handle_take2_sent_committee( }; let goat_confirmed_height = ctx.goat_client.btc_spv_latest_height().await?; if goat_confirmed_height < take2_height { - let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()) - * (take2_height - goat_confirmed_height); + let delay_secs = + avg_block_time_secs(ctx.btc_client.network()) * (take2_height - goat_confirmed_height); push_local_unhandled_messages_with_reason( ctx.local_db, graph_id, @@ -6448,7 +6447,7 @@ async fn handle_sync_graph( "Failed to validate graph_id on GoatChain for SyncGraph {instance_id}:{graph_id}: {e}" ) })?; - let validation = todo_funcs::validate_graph_instance_parameters( + let validation = validate_graph_instance_parameters( ctx.btc_client, ctx.goat_client, &graph.parameters.instance_parameters, diff --git a/node/src/utils.rs b/node/src/utils.rs index 6cd98ad2..f9f5868a 100644 --- a/node/src/utils.rs +++ b/node/src/utils.rs @@ -33,7 +33,9 @@ use bitvm_lib::keys::{ CommitteeMasterKey, OperatorMasterKey, VerifierMasterKey, WatchtowerMasterKey, }; use bitvm_lib::operator::*; -use bitvm_lib::timelocks::{connector_f_timelock_blocks, default_timelock_config}; +use bitvm_lib::timelocks::{ + connector_f_timelock_blocks, default_timelock_config, estimated_block_interval_secs, +}; use bitvm_lib::types::{ BitvmGcCircuitData, BitvmGcGraph, BitvmGcGraphParameters, BitvmGcInstanceParameters, PrekickoffParameters, SimplifiedBitvmGcGraph, UserInfo, @@ -174,594 +176,559 @@ pub struct OperatorStatement { pub constant: [u8; 32], } -pub mod todo_funcs { - #![allow(dead_code, unreachable_code, unused_variables)] +// other operations +pub fn avg_block_time_secs(network: Network) -> u64 { + estimated_block_interval_secs(network) as u64 +} +pub fn min_required_operator() -> usize { + // todo!("get min required operator number") + 1 +} +pub fn min_required_watchtower() -> usize { + // todo!("get min required watchtower number") + 1 +} +pub fn min_required_verifier() -> usize { + get_min_required_verifier() + .unwrap_or_else(|error| panic!("invalid verifier count configuration: {error}")) +} - use super::*; - use bitvm_lib::timelocks::estimated_block_interval_secs; - use bitvm_lib::types::SimplifiedBitvmGcGraph; +/// Checks graph Watchtower membership, uniqueness, and supported count. +fn validate_watchtower_registry_selection( + selected: &[XOnlyPublicKey], + registered: &[XOnlyPublicKey], +) -> Result<()> { + use std::collections::HashSet; - // other operations - pub fn avg_block_time_secs(network: Network) -> u64 { - estimated_block_interval_secs(network) as u64 - } - pub fn min_required_operator() -> usize { - // todo!("get min required operator number") - 1 - } - pub fn min_required_watchtower() -> usize { - // todo!("get min required watchtower number") - 1 + if selected.len() < min_required_watchtower() { + bail!(SpecialError::InvalidGraph(format!( + "insufficient watchtowers: have {}, required {}", + selected.len(), + min_required_watchtower() + ))); } - pub fn min_required_verifier() -> usize { - get_min_required_verifier() - .unwrap_or_else(|error| panic!("invalid verifier count configuration: {error}")) + if selected.len() > 256 { + bail!(SpecialError::InvalidGraph(format!( + "too many watchtowers: {}, max 256", + selected.len() + ))); } - /// Checks graph Watchtower membership, uniqueness, and supported count. - pub(super) fn validate_watchtower_registry_selection( - selected: &[XOnlyPublicKey], - registered: &[XOnlyPublicKey], - ) -> Result<()> { - use std::collections::HashSet; - - if selected.len() < min_required_watchtower() { - bail!(SpecialError::InvalidGraph(format!( - "insufficient watchtowers: have {}, required {}", - selected.len(), - min_required_watchtower() - ))); + let mut seen = HashSet::new(); + for key in selected { + if !seen.insert(*key) { + bail!(SpecialError::InvalidGraph("duplicate watchtower pubkey in graph".to_string())); } - if selected.len() > 256 { - bail!(SpecialError::InvalidGraph(format!( - "too many watchtowers: {}, max 256", - selected.len() - ))); + if !registered.contains(key) { + bail!(SpecialError::InvalidGraph(format!("watchtower {} is not registered", key))); } - - let mut seen = HashSet::new(); - for key in selected { - if !seen.insert(*key) { - bail!(SpecialError::InvalidGraph( - "duplicate watchtower pubkey in graph".to_string() - )); - } - if !registered.contains(key) { - bail!(SpecialError::InvalidGraph(format!("watchtower {} is not registered", key))); - } - } - Ok(()) } + Ok(()) +} - /// Validates the graph's ordered watchtower selection and its bound Operator constant. - pub(super) fn validate_watchtower_selection( - selected: &[XOnlyPublicKey], - registered: &[XOnlyPublicKey], - graph_id: [u8; 16], - genesis_txid: [u8; 32], - challenge_init_txid: [u8; 32], - constant: [u8; 32], - ) -> Result<()> { - validate_watchtower_registry_selection(selected, registered)?; - let key_bytes = selected.iter().map(XOnlyPublicKey::serialize).collect::>(); - let expected = - hash_operator_constant(graph_id, genesis_txid, challenge_init_txid, &key_bytes); - if constant != expected { - bail!(SpecialError::InvalidGraph( - "operator constant mismatch for graph watchtower list".to_string() - )); - } - Ok(()) +/// Validates the graph's ordered watchtower selection and its bound Operator constant. +fn validate_watchtower_selection( + selected: &[XOnlyPublicKey], + registered: &[XOnlyPublicKey], + graph_id: [u8; 16], + genesis_txid: [u8; 32], + challenge_init_txid: [u8; 32], + constant: [u8; 32], +) -> Result<()> { + validate_watchtower_registry_selection(selected, registered)?; + let key_bytes = selected.iter().map(XOnlyPublicKey::serialize).collect::>(); + let expected = hash_operator_constant(graph_id, genesis_txid, challenge_init_txid, &key_bytes); + if constant != expected { + bail!(SpecialError::InvalidGraph( + "operator constant mismatch for graph watchtower list".to_string() + )); } + Ok(()) +} - pub async fn validate_graph_instance_parameters( - btc_client: &BTCClient, - goat_client: &GOATClient, - parameters: &BitvmGcInstanceParameters, - ) -> Result<()> { - let expected = super::read_instance_info_from_goat(goat_client, parameters.instance_id) - .await - .map_err(|e| { - SpecialError::InvalidGraph(format!( - "failed to load instance parameters from GoatChain: {e}" - )) - })?; - if parameters != &expected { - bail!(SpecialError::InvalidGraph( - "instance parameters mismatch with GoatChain peg-in data".to_string() - )); - } +pub async fn validate_graph_instance_parameters( + btc_client: &BTCClient, + goat_client: &GOATClient, + parameters: &BitvmGcInstanceParameters, +) -> Result<()> { + let expected = + read_instance_info_from_goat(goat_client, parameters.instance_id).await.map_err(|e| { + SpecialError::InvalidGraph(format!( + "failed to load instance parameters from GoatChain: {e}" + )) + })?; + if parameters != &expected { + bail!(SpecialError::InvalidGraph( + "instance parameters mismatch with GoatChain peg-in data".to_string() + )); + } - for input in &expected.user_info.inputs { - let funding_tx = btc_client.get_tx(&input.outpoint.txid).await.map_err(|e| { + for input in &expected.user_info.inputs { + let funding_tx = btc_client.get_tx(&input.outpoint.txid).await.map_err(|e| { + SpecialError::InvalidGraph(format!( + "failed to load peg-in funding transaction {}: {e}", + input.outpoint.txid + )) + })?; + let Some(funding_tx) = funding_tx else { + bail!(SpecialError::InvalidGraph(format!( + "peg-in funding transaction {} not found on Bitcoin", + input.outpoint.txid + ))); + }; + let funding_output = + funding_tx.output.get(input.outpoint.vout as usize).ok_or_else(|| { SpecialError::InvalidGraph(format!( - "failed to load peg-in funding transaction {}: {e}", - input.outpoint.txid + "peg-in funding outpoint {}:{} does not exist", + input.outpoint.txid, input.outpoint.vout )) })?; - let Some(funding_tx) = funding_tx else { - bail!(SpecialError::InvalidGraph(format!( - "peg-in funding transaction {} not found on Bitcoin", - input.outpoint.txid - ))); - }; - let funding_output = - funding_tx.output.get(input.outpoint.vout as usize).ok_or_else(|| { - SpecialError::InvalidGraph(format!( - "peg-in funding outpoint {}:{} does not exist", - input.outpoint.txid, input.outpoint.vout - )) - })?; - if funding_output.value != input.amount { - bail!(SpecialError::InvalidGraph(format!( - "peg-in funding amount mismatch for {}:{}: graph={}, bitcoin={}", - input.outpoint.txid, - input.outpoint.vout, - input.amount.to_sat(), - funding_output.value.to_sat() - ))); - } - } - - let pegin_deposit_txid = expected - .build_pegin_tx() - .map_err(|e| { - SpecialError::InvalidGraph(format!("failed to reconstruct peg-in deposit: {e}")) - })? - .0 - .tx() - .compute_txid(); - if btc_client - .get_tx(&pegin_deposit_txid) - .await - .map_err(|e| { - SpecialError::InvalidGraph(format!( - "failed to load peg-in deposit transaction {pegin_deposit_txid}: {e}" - )) - })? - .is_none() - { + if funding_output.value != input.amount { bail!(SpecialError::InvalidGraph(format!( - "peg-in deposit transaction {pegin_deposit_txid} not found on Bitcoin" + "peg-in funding amount mismatch for {}:{}: graph={}, bitcoin={}", + input.outpoint.txid, + input.outpoint.vout, + input.amount.to_sat(), + funding_output.value.to_sat() ))); } + } - Ok(()) + let pegin_deposit_txid = expected + .build_pegin_tx() + .map_err(|e| { + SpecialError::InvalidGraph(format!("failed to reconstruct peg-in deposit: {e}")) + })? + .0 + .tx() + .compute_txid(); + if btc_client + .get_tx(&pegin_deposit_txid) + .await + .map_err(|e| { + SpecialError::InvalidGraph(format!( + "failed to load peg-in deposit transaction {pegin_deposit_txid}: {e}" + )) + })? + .is_none() + { + bail!(SpecialError::InvalidGraph(format!( + "peg-in deposit transaction {pegin_deposit_txid} not found on Bitcoin" + ))); } - fn validate_graph_policy(parameters: &BitvmGcGraphParameters) -> Result<()> { - let verifier_num = parameters.gc_data.len(); - if verifier_num < min_required_verifier() { - bail!(SpecialError::InvalidGraph(format!( - "insufficient verifier GC slots: have {}, required {}", - verifier_num, - min_required_verifier() - ))); - } + Ok(()) +} - let mut verifier_pubkeys = std::collections::HashSet::new(); - for gc_data in ¶meters.gc_data { - if !verifier_pubkeys.insert(gc_data.verifier_pubkey) { - bail!(SpecialError::InvalidGraph(format!( - "duplicate verifier pubkey in GC data: {}", - gc_data.verifier_pubkey - ))); - } - if gc_data.final_msg_hashlocks.len() != BABE_M_CC { - bail!(SpecialError::InvalidGraph(format!( - "verifier {} has {} final message hashlocks, expected {}", - gc_data.verifier_pubkey, - gc_data.final_msg_hashlocks.len(), - BABE_M_CC - ))); - } - } +fn validate_graph_policy(parameters: &BitvmGcGraphParameters) -> Result<()> { + let verifier_num = parameters.gc_data.len(); + if verifier_num < min_required_verifier() { + bail!(SpecialError::InvalidGraph(format!( + "insufficient verifier GC slots: have {}, required {}", + verifier_num, + min_required_verifier() + ))); + } - let watchtower_num = parameters.watchtower_pubkeys.len(); - if watchtower_num < min_required_watchtower() { + let mut verifier_pubkeys = std::collections::HashSet::new(); + for gc_data in ¶meters.gc_data { + if !verifier_pubkeys.insert(gc_data.verifier_pubkey) { bail!(SpecialError::InvalidGraph(format!( - "insufficient watchtower slots: have {}, required {}", - watchtower_num, - min_required_watchtower() + "duplicate verifier pubkey in GC data: {}", + gc_data.verifier_pubkey ))); } - let mut watchtower_pubkeys = std::collections::HashSet::new(); - for pubkey in ¶meters.watchtower_pubkeys { - if !watchtower_pubkeys.insert(*pubkey) { - bail!(SpecialError::InvalidGraph(format!( - "duplicate watchtower pubkey in graph: {pubkey}" - ))); - } - } - let mut watchtower_hashlocks = std::collections::HashSet::new(); - for hashlock in ¶meters.watchtower_ack_hashlocks { - if !watchtower_hashlocks.insert(*hashlock) { - bail!(SpecialError::InvalidGraph( - "duplicate watchtower ack hashlock in graph".to_string() - )); - } + if gc_data.final_msg_hashlocks.len() != BABE_M_CC { + bail!(SpecialError::InvalidGraph(format!( + "verifier {} has {} final message hashlocks, expected {}", + gc_data.verifier_pubkey, + gc_data.final_msg_hashlocks.len(), + BABE_M_CC + ))); } - - Ok(()) } - async fn validate_prekickoff_continuity( - local_db: &LocalDB, - full_graph: &BitvmGcGraph, - ) -> Result<()> { - if full_graph.parameters.graph_nonce == 0 { - return Ok(()); - } - - let mut storage_processor = local_db.acquire().await?; - let previous_nonce = full_graph.parameters.graph_nonce - 1; - let graphs = storage_processor - .get_operator_graphs( - GraphQuery::default() - .with_operator_pubkey(full_graph.parameters.operator_pubkey.to_string()) - .with_kickoff_index(previous_nonce as i64) - .with_limit(2), - ) - .await?; - if let Some(previous_graph) = graphs.first() { - let simplified_graph = super::load_validated_graph_definition( - &mut storage_processor, - previous_graph.instance_id, - previous_graph.graph_id, - ) - .await? - .ok_or_else(|| { - SpecialError::InvalidGraph(format!( - "previous graph raw data is missing for graph nonce {previous_nonce}" - )) - })?; - let expected_cur_prekickoff = - BitvmGcGraph::from_simplified(&simplified_graph)?.next_prekickoff; - let expected_txid = expected_cur_prekickoff.finalize().compute_txid(); - let actual_txid = full_graph.cur_prekickoff.finalize().compute_txid(); - if actual_txid != expected_txid { - bail!(SpecialError::InvalidGraph(format!( - "cur prekickoff continuity mismatch: graph={actual_txid}, expected={expected_txid}" - ))); - } - Ok(()) - } else { + let watchtower_num = parameters.watchtower_pubkeys.len(); + if watchtower_num < min_required_watchtower() { + bail!(SpecialError::InvalidGraph(format!( + "insufficient watchtower slots: have {}, required {}", + watchtower_num, + min_required_watchtower() + ))); + } + let mut watchtower_pubkeys = std::collections::HashSet::new(); + for pubkey in ¶meters.watchtower_pubkeys { + if !watchtower_pubkeys.insert(*pubkey) { bail!(SpecialError::InvalidGraph(format!( - "previous graph is missing for graph nonce {previous_nonce}" + "duplicate watchtower pubkey in graph: {pubkey}" ))); } } - - pub async fn validate_init_graph_base( - local_db: &LocalDB, - btc_client: &BTCClient, - goat_client: &GOATClient, - graph: &SimplifiedBitvmGcGraph, - ) -> Result<()> { - if !graph.operator_pre_signed() { + let mut watchtower_hashlocks = std::collections::HashSet::new(); + for hashlock in ¶meters.watchtower_ack_hashlocks { + if !watchtower_hashlocks.insert(*hashlock) { bail!(SpecialError::InvalidGraph( - "graph is missing operator pre-signatures".to_string() + "duplicate watchtower ack hashlock in graph".to_string() )); } - // Basic structural and on-chain consistency checks for an incoming graph proposal. - // Return SpecialError::InvalidGraph on any validation failure. - // 1) Rebuild full graph (ensures signatures present if flags are set and tx graph is coherent) - let full_graph = BitvmGcGraph::from_simplified(graph) - .map_err(|e| SpecialError::InvalidGraph(format!("invalid graph structure: {e}")))?; - validate_graph_policy(&graph.parameters)?; - validate_prekickoff_continuity(local_db, &full_graph).await?; - verify_graph_operator_pre_signatures(&full_graph).map_err(|e| { - SpecialError::InvalidGraph(format!("invalid operator pre-signatures: {e}")) - })?; + } - // 2) Bind all instance and peg-in parameters to GoatChain and Bitcoin. - validate_graph_instance_parameters( - btc_client, - goat_client, - &graph.parameters.instance_parameters, + Ok(()) +} + +async fn validate_prekickoff_continuity( + local_db: &LocalDB, + full_graph: &BitvmGcGraph, +) -> Result<()> { + if full_graph.parameters.graph_nonce == 0 { + return Ok(()); + } + + let mut storage_processor = local_db.acquire().await?; + let previous_nonce = full_graph.parameters.graph_nonce - 1; + let graphs = storage_processor + .get_operator_graphs( + GraphQuery::default() + .with_operator_pubkey(full_graph.parameters.operator_pubkey.to_string()) + .with_kickoff_index(previous_nonce as i64) + .with_limit(2), ) .await?; - - // 3) Challenge amount and assert-commit count must match local constants - if graph.parameters.challenge_amount != super::todo_funcs::challenge_amount() { - bail!(SpecialError::InvalidGraph("unexpected challenge amount".to_string())); + if let Some(previous_graph) = graphs.first() { + let simplified_graph = load_validated_graph_definition( + &mut storage_processor, + previous_graph.instance_id, + previous_graph.graph_id, + ) + .await? + .ok_or_else(|| { + SpecialError::InvalidGraph(format!( + "previous graph raw data is missing for graph nonce {previous_nonce}" + )) + })?; + let expected_cur_prekickoff = + BitvmGcGraph::from_simplified(&simplified_graph)?.next_prekickoff; + let expected_txid = expected_cur_prekickoff.finalize().compute_txid(); + let actual_txid = full_graph.cur_prekickoff.finalize().compute_txid(); + if actual_txid != expected_txid { + bail!(SpecialError::InvalidGraph(format!( + "cur prekickoff continuity mismatch: graph={actual_txid}, expected={expected_txid}" + ))); } + Ok(()) + } else { + bail!(SpecialError::InvalidGraph(format!( + "previous graph is missing for graph nonce {previous_nonce}" + ))); + } +} - // 4) Watchtower config sanity: number of watchtowers should match number of hashlocks and registry size - let watchtowers_on_chain = - goat_client.committee_mana_get_watchtowers().await.map_err(|e| { - SpecialError::InvalidGraph(format!("failed to load watchtowers from chain: {e}")) - })?; +pub async fn validate_init_graph_base( + local_db: &LocalDB, + btc_client: &BTCClient, + goat_client: &GOATClient, + graph: &SimplifiedBitvmGcGraph, +) -> Result<()> { + if !graph.operator_pre_signed() { + bail!(SpecialError::InvalidGraph("graph is missing operator pre-signatures".to_string())); + } + // Basic structural and on-chain consistency checks for an incoming graph proposal. + // Return SpecialError::InvalidGraph on any validation failure. + // 1) Rebuild full graph (ensures signatures present if flags are set and tx graph is coherent) + let full_graph = BitvmGcGraph::from_simplified(graph) + .map_err(|e| SpecialError::InvalidGraph(format!("invalid graph structure: {e}")))?; + validate_graph_policy(&graph.parameters)?; + validate_prekickoff_continuity(local_db, &full_graph).await?; + verify_graph_operator_pre_signatures(&full_graph) + .map_err(|e| SpecialError::InvalidGraph(format!("invalid operator pre-signatures: {e}")))?; + + // 2) Bind all instance and peg-in parameters to GoatChain and Bitcoin. + validate_graph_instance_parameters( + btc_client, + goat_client, + &graph.parameters.instance_parameters, + ) + .await?; - validate_watchtower_selection( - &graph.parameters.watchtower_pubkeys, - &watchtowers_on_chain, - *graph.parameters.graph_id.as_bytes(), - get_genesis_sequencer_commit_id(), - full_graph.watchtower_challenge_init.tx().compute_txid().to_byte_array(), - graph.parameters.pubin_disprove_constant, - )?; + // 3) Challenge amount and assert-commit count must match local constants + if graph.parameters.challenge_amount != challenge_amount() { + bail!(SpecialError::InvalidGraph("unexpected challenge amount".to_string())); + } - // 5) Operator stake sanity: verify operator is registered and has enough locked stake - let op_pk_bytes = graph.parameters.operator_pubkey.to_bytes(); - let xonly: [u8; 32] = op_pk_bytes[1..33] - .try_into() - .map_err(|_| SpecialError::InvalidGraph("invalid operator pubkey".to_string()))?; - let operator_addr = - goat_client.stake_mana_pubkey_to_address(&xonly).await.map_err(|e| { - SpecialError::InvalidGraph(format!("failed to query operator address: {e}")) - })?; - if operator_addr == [0u8; 20] { - bail!(SpecialError::InvalidGraph("operator not registered".to_string())); + // 4) Watchtower config sanity: number of watchtowers should match number of hashlocks and registry size + let watchtowers_on_chain = goat_client.committee_mana_get_watchtowers().await.map_err(|e| { + SpecialError::InvalidGraph(format!("failed to load watchtowers from chain: {e}")) + })?; + + validate_watchtower_selection( + &graph.parameters.watchtower_pubkeys, + &watchtowers_on_chain, + *graph.parameters.graph_id.as_bytes(), + get_genesis_sequencer_commit_id(), + full_graph.watchtower_challenge_init.tx().compute_txid().to_byte_array(), + graph.parameters.pubin_disprove_constant, + )?; + + // 5) Operator stake sanity: verify operator is registered and has enough locked stake + let op_pk_bytes = graph.parameters.operator_pubkey.to_bytes(); + let xonly: [u8; 32] = op_pk_bytes[1..33] + .try_into() + .map_err(|_| SpecialError::InvalidGraph("invalid operator pubkey".to_string()))?; + let operator_addr = goat_client.stake_mana_pubkey_to_address(&xonly).await.map_err(|e| { + SpecialError::InvalidGraph(format!("failed to query operator address: {e}")) + })?; + if operator_addr == [0u8; 20] { + bail!(SpecialError::InvalidGraph("operator not registered".to_string())); + } + let min_stake_amount = goat_client.gateway_get_min_stake_amount().await.map_err(|e| { + SpecialError::InvalidGraph(format!("failed to query min stake amount: {e}")) + })?; + let locked_stake = goat_client.stake_mana_lock_stake_of(&operator_addr).await.map_err(|e| { + SpecialError::InvalidGraph(format!("failed to query operator locked stake: {e}")) + })?; + if locked_stake < min_stake_amount { + bail!(SpecialError::InvalidGraph(format!( + "insufficient operator stake: locked={locked_stake}, min={min_stake_amount}" + ))); + } + + Ok(()) +} + +pub fn validate_verifier_graph_params_endorsements( + graph: &SimplifiedBitvmGcGraph, + verifier_endorsements: &[(PublicKey, usize, SchnorrSignature)], +) -> Result<()> { + let expected_verifier_num = graph.parameters.gc_data.len(); + if verifier_endorsements.len() != expected_verifier_num { + bail!(SpecialError::InvalidGraph(format!( + "verifier params endorsement count {} does not match GC slot count {}", + verifier_endorsements.len(), + expected_verifier_num + ))); + } + + let mut seen_pubkeys = std::collections::HashSet::new(); + let mut seen_indices = std::collections::HashSet::new(); + for (verifier_pubkey, verifier_index, signature) in verifier_endorsements { + if *verifier_index >= expected_verifier_num { + bail!(SpecialError::InvalidGraph(format!( + "verifier params endorsement index {verifier_index} out of range" + ))); } - let min_stake_amount = goat_client.gateway_get_min_stake_amount().await.map_err(|e| { - SpecialError::InvalidGraph(format!("failed to query min stake amount: {e}")) - })?; - let locked_stake = - goat_client.stake_mana_lock_stake_of(&operator_addr).await.map_err(|e| { - SpecialError::InvalidGraph(format!("failed to query operator locked stake: {e}")) - })?; - if locked_stake < min_stake_amount { + let slot_pubkey = graph.parameters.gc_data[*verifier_index].verifier_pubkey; + if slot_pubkey != *verifier_pubkey { bail!(SpecialError::InvalidGraph(format!( - "insufficient operator stake: locked={locked_stake}, min={min_stake_amount}" + "verifier params endorsement slot mismatch at index {verifier_index}: signature pubkey={}, graph pubkey={slot_pubkey}", + verifier_pubkey ))); } - - Ok(()) - } - - pub fn validate_verifier_graph_params_endorsements( - graph: &SimplifiedBitvmGcGraph, - verifier_endorsements: &[(PublicKey, usize, SchnorrSignature)], - ) -> Result<()> { - let expected_verifier_num = graph.parameters.gc_data.len(); - if verifier_endorsements.len() != expected_verifier_num { + if !seen_pubkeys.insert(*verifier_pubkey) { bail!(SpecialError::InvalidGraph(format!( - "verifier params endorsement count {} does not match GC slot count {}", - verifier_endorsements.len(), - expected_verifier_num + "duplicate verifier params endorsement pubkey: {verifier_pubkey}" ))); } - - let mut seen_pubkeys = std::collections::HashSet::new(); - let mut seen_indices = std::collections::HashSet::new(); - for (verifier_pubkey, verifier_index, signature) in verifier_endorsements { - if *verifier_index >= expected_verifier_num { - bail!(SpecialError::InvalidGraph(format!( - "verifier params endorsement index {verifier_index} out of range" - ))); - } - let slot_pubkey = graph.parameters.gc_data[*verifier_index].verifier_pubkey; - if slot_pubkey != *verifier_pubkey { - bail!(SpecialError::InvalidGraph(format!( - "verifier params endorsement slot mismatch at index {verifier_index}: signature pubkey={}, graph pubkey={slot_pubkey}", - verifier_pubkey - ))); - } - if !seen_pubkeys.insert(*verifier_pubkey) { - bail!(SpecialError::InvalidGraph(format!( - "duplicate verifier params endorsement pubkey: {verifier_pubkey}" - ))); - } - if !seen_indices.insert(*verifier_index) { - bail!(SpecialError::InvalidGraph(format!( - "duplicate verifier params endorsement index: {verifier_index}" - ))); - } - let ok = - super::verify_verifier_graph_params_endorsement(verifier_pubkey, graph, signature)?; - if !ok { - bail!(SpecialError::InvalidGraph(format!( - "invalid verifier params endorsement signature for {verifier_pubkey}" - ))); - } + if !seen_indices.insert(*verifier_index) { + bail!(SpecialError::InvalidGraph(format!( + "duplicate verifier params endorsement index: {verifier_index}" + ))); } + let ok = verify_verifier_graph_params_endorsement(verifier_pubkey, graph, signature)?; + if !ok { + bail!(SpecialError::InvalidGraph(format!( + "invalid verifier params endorsement signature for {verifier_pubkey}" + ))); + } + } - Ok(()) + Ok(()) +} + +pub async fn validate_init_graph( + local_db: &LocalDB, + btc_client: &BTCClient, + goat_client: &GOATClient, + graph: &SimplifiedBitvmGcGraph, + verifier_endorsements: &[(PublicKey, usize, SchnorrSignature)], +) -> Result<()> { + validate_init_graph_base(local_db, btc_client, goat_client, graph).await?; + validate_verifier_graph_params_endorsements(graph, verifier_endorsements)?; + Ok(()) +} +pub async fn validate_finalized_graph( + btc_client: &BTCClient, + goat_client: &GOATClient, + graph: &SimplifiedBitvmGcGraph, + endorse_sigs: &[(PublicKey, EvmAddress, Vec)], + params_endorse_sigs: &[(PublicKey, EvmAddress, Vec)], +) -> Result<()> { + if !graph.operator_pre_signed() || !graph.committee_pre_signed() { + bail!(SpecialError::InvalidGraph( + "finalized graph is missing operator or committee pre-signatures".to_string() + )); + } + // 1) Rebuild full graph to ensure structure is coherent and txns derivable + let full_graph = BitvmGcGraph::from_simplified(graph) + .map_err(|e| SpecialError::InvalidGraph(format!("invalid graph structure: {e}")))?; + validate_graph_policy(&graph.parameters)?; + verify_graph_operator_pre_signatures(&full_graph) + .map_err(|e| SpecialError::InvalidGraph(format!("invalid operator pre-signatures: {e}")))?; + verify_graph_committee_pre_signatures(&full_graph).map_err(|e| { + SpecialError::InvalidGraph(format!("invalid committee pre-signatures: {e}")) + })?; + + // 2) Repeat the full instance and peg-in binding for nodes that did not see CreateGraph. + validate_graph_instance_parameters( + btc_client, + goat_client, + &graph.parameters.instance_parameters, + ) + .await?; + + let instance_id = graph.parameters.instance_parameters.instance_id; + if graph.parameters.challenge_amount != challenge_amount() { + bail!(SpecialError::InvalidGraph("unexpected challenge amount".to_string())); } - pub async fn validate_init_graph( - local_db: &LocalDB, - btc_client: &BTCClient, - goat_client: &GOATClient, - graph: &SimplifiedBitvmGcGraph, - verifier_endorsements: &[(PublicKey, usize, SchnorrSignature)], - ) -> Result<()> { - validate_init_graph_base(local_db, btc_client, goat_client, graph).await?; - validate_verifier_graph_params_endorsements(graph, verifier_endorsements)?; - Ok(()) + let watchtowers_on_chain = goat_client.committee_mana_get_watchtowers().await.map_err(|e| { + SpecialError::InvalidGraph(format!("failed to load watchtowers from chain: {e}")) + })?; + validate_watchtower_selection( + &graph.parameters.watchtower_pubkeys, + &watchtowers_on_chain, + *graph.parameters.graph_id.as_bytes(), + get_genesis_sequencer_commit_id(), + full_graph.watchtower_challenge_init.tx().compute_txid().to_byte_array(), + graph.parameters.pubin_disprove_constant, + )?; + + // 3) Validate endorsements: unique, from legitimate committee members, and signatures recover to the provided EVM address + use std::collections::HashSet; + let mut seen_committee: HashSet = HashSet::new(); + let mut seen_evm: HashSet = HashSet::new(); + let pegin_data = goat_client + .gateway_get_pegin_data(&instance_id) + .await + .map_err(|e| SpecialError::InvalidGraph(format!("failed to load instance data: {e}")))?; + if pegin_data.committee_pubkeys.len() != pegin_data.committee_addresses.len() { + bail!(SpecialError::InvalidGraph( + "on-chain committee pubkey and address counts differ".to_string() + )); } - pub async fn validate_finalized_graph( - btc_client: &BTCClient, - goat_client: &GOATClient, - graph: &SimplifiedBitvmGcGraph, - endorse_sigs: &[(PublicKey, EvmAddress, Vec)], - params_endorse_sigs: &[(PublicKey, EvmAddress, Vec)], - ) -> Result<()> { - if !graph.operator_pre_signed() || !graph.committee_pre_signed() { + if endorse_sigs.len() != pegin_data.committee_pubkeys.len() { + bail!(SpecialError::InvalidGraph(format!( + "endorsement count {} does not match instance committee count {}", + endorse_sigs.len(), + pegin_data.committee_pubkeys.len() + ))); + } + if params_endorse_sigs.len() != pegin_data.committee_pubkeys.len() { + bail!(SpecialError::InvalidGraph(format!( + "params endorsement count {} does not match instance committee count {}", + params_endorse_sigs.len(), + pegin_data.committee_pubkeys.len() + ))); + } + + for (pk, evm_addr, sig) in endorse_sigs.iter() { + // no duplicates + if !seen_committee.insert(*pk) { bail!(SpecialError::InvalidGraph( - "finalized graph is missing operator or committee pre-signatures".to_string() + "duplicate committee pubkey in endorsements".to_string() )); } - // 1) Rebuild full graph to ensure structure is coherent and txns derivable - let full_graph = BitvmGcGraph::from_simplified(graph) - .map_err(|e| SpecialError::InvalidGraph(format!("invalid graph structure: {e}")))?; - validate_graph_policy(&graph.parameters)?; - verify_graph_operator_pre_signatures(&full_graph).map_err(|e| { - SpecialError::InvalidGraph(format!("invalid operator pre-signatures: {e}")) - })?; - verify_graph_committee_pre_signatures(&full_graph).map_err(|e| { - SpecialError::InvalidGraph(format!("invalid committee pre-signatures: {e}")) - })?; - - // 2) Repeat the full instance and peg-in binding for nodes that did not see CreateGraph. - validate_graph_instance_parameters( - btc_client, - goat_client, - &graph.parameters.instance_parameters, - ) - .await?; + if !seen_evm.insert(*evm_addr) { + bail!(SpecialError::InvalidGraph("duplicate evm address in endorsements".to_string())); + } - let instance_id = graph.parameters.instance_parameters.instance_id; - if graph.parameters.challenge_amount != super::todo_funcs::challenge_amount() { - bail!(SpecialError::InvalidGraph("unexpected challenge amount".to_string())); + // map pubkey -> expected evm address from GoatChain + let mut found = false; + for i in 0..pegin_data.committee_pubkeys.len() { + let on_chain_pk = + PublicKey::from_slice(&pegin_data.committee_pubkeys[i]).map_err(|e| { + SpecialError::InvalidGraph(format!("invalid committee pubkey on-chain: {e}")) + })?; + if &on_chain_pk == pk { + found = true; + let expected_addr = pegin_data.committee_addresses[i]; + if &expected_addr != evm_addr { + bail!(SpecialError::InvalidGraph("committee evm address mismatch".to_string())); + } + break; + } + } + if !found { + bail!(SpecialError::InvalidGraph("endorser not in committee set".to_string())); } - let watchtowers_on_chain = - goat_client.committee_mana_get_watchtowers().await.map_err(|e| { - SpecialError::InvalidGraph(format!("failed to load watchtowers from chain: {e}")) - })?; - validate_watchtower_selection( - &graph.parameters.watchtower_pubkeys, - &watchtowers_on_chain, - *graph.parameters.graph_id.as_bytes(), - get_genesis_sequencer_commit_id(), - full_graph.watchtower_challenge_init.tx().compute_txid().to_byte_array(), - graph.parameters.pubin_disprove_constant, + // cryptographically verify the endorsement against the graph digest + let ok = verify_graph_endorsement(goat_client, evm_addr, &full_graph, sig).await.map_err( + |e| SpecialError::InvalidGraph(format!("failed to verify endorsement: {e}")), )?; + if !ok { + bail!(SpecialError::InvalidGraph("invalid endorsement signature".to_string())); + } + } - // 3) Validate endorsements: unique, from legitimate committee members, and signatures recover to the provided EVM address - use std::collections::HashSet; - let mut seen_committee: HashSet = HashSet::new(); - let mut seen_evm: HashSet = HashSet::new(); - let pegin_data = goat_client.gateway_get_pegin_data(&instance_id).await.map_err(|e| { - SpecialError::InvalidGraph(format!("failed to load instance data: {e}")) - })?; - if pegin_data.committee_pubkeys.len() != pegin_data.committee_addresses.len() { + let mut seen_committee: HashSet = HashSet::new(); + let mut seen_evm: HashSet = HashSet::new(); + for (pk, evm_addr, sig) in params_endorse_sigs.iter() { + if !seen_committee.insert(*pk) { bail!(SpecialError::InvalidGraph( - "on-chain committee pubkey and address counts differ".to_string() + "duplicate committee pubkey in params endorsements".to_string() )); } - if endorse_sigs.len() != pegin_data.committee_pubkeys.len() { - bail!(SpecialError::InvalidGraph(format!( - "endorsement count {} does not match instance committee count {}", - endorse_sigs.len(), - pegin_data.committee_pubkeys.len() - ))); - } - if params_endorse_sigs.len() != pegin_data.committee_pubkeys.len() { - bail!(SpecialError::InvalidGraph(format!( - "params endorsement count {} does not match instance committee count {}", - params_endorse_sigs.len(), - pegin_data.committee_pubkeys.len() - ))); + if !seen_evm.insert(*evm_addr) { + bail!(SpecialError::InvalidGraph( + "duplicate evm address in params endorsements".to_string() + )); } - for (pk, evm_addr, sig) in endorse_sigs.iter() { - // no duplicates - if !seen_committee.insert(*pk) { - bail!(SpecialError::InvalidGraph( - "duplicate committee pubkey in endorsements".to_string() - )); - } - if !seen_evm.insert(*evm_addr) { - bail!(SpecialError::InvalidGraph( - "duplicate evm address in endorsements".to_string() - )); - } - - // map pubkey -> expected evm address from GoatChain - let mut found = false; - for i in 0..pegin_data.committee_pubkeys.len() { - let on_chain_pk = - PublicKey::from_slice(&pegin_data.committee_pubkeys[i]).map_err(|e| { - SpecialError::InvalidGraph(format!( - "invalid committee pubkey on-chain: {e}" - )) - })?; - if &on_chain_pk == pk { - found = true; - let expected_addr = pegin_data.committee_addresses[i]; - if &expected_addr != evm_addr { - bail!(SpecialError::InvalidGraph( - "committee evm address mismatch".to_string() - )); - } - break; - } - } - if !found { - bail!(SpecialError::InvalidGraph("endorser not in committee set".to_string())); - } - - // cryptographically verify the endorsement against the graph digest - let ok = super::verify_graph_endorsement(goat_client, evm_addr, &full_graph, sig) - .await - .map_err(|e| { - SpecialError::InvalidGraph(format!("failed to verify endorsement: {e}")) + let mut found = false; + for i in 0..pegin_data.committee_pubkeys.len() { + let on_chain_pk = + PublicKey::from_slice(&pegin_data.committee_pubkeys[i]).map_err(|e| { + SpecialError::InvalidGraph(format!("invalid committee pubkey on-chain: {e}")) })?; - if !ok { - bail!(SpecialError::InvalidGraph("invalid endorsement signature".to_string())); - } - } - - let mut seen_committee: HashSet = HashSet::new(); - let mut seen_evm: HashSet = HashSet::new(); - for (pk, evm_addr, sig) in params_endorse_sigs.iter() { - if !seen_committee.insert(*pk) { - bail!(SpecialError::InvalidGraph( - "duplicate committee pubkey in params endorsements".to_string() - )); - } - if !seen_evm.insert(*evm_addr) { - bail!(SpecialError::InvalidGraph( - "duplicate evm address in params endorsements".to_string() - )); - } - - let mut found = false; - for i in 0..pegin_data.committee_pubkeys.len() { - let on_chain_pk = - PublicKey::from_slice(&pegin_data.committee_pubkeys[i]).map_err(|e| { - SpecialError::InvalidGraph(format!( - "invalid committee pubkey on-chain: {e}" - )) - })?; - if &on_chain_pk == pk { - found = true; - let expected_addr = pegin_data.committee_addresses[i]; - if &expected_addr != evm_addr { - bail!(SpecialError::InvalidGraph( - "committee evm address mismatch in params endorsements".to_string() - )); - } - break; + if &on_chain_pk == pk { + found = true; + let expected_addr = pegin_data.committee_addresses[i]; + if &expected_addr != evm_addr { + bail!(SpecialError::InvalidGraph( + "committee evm address mismatch in params endorsements".to_string() + )); } - } - if !found { - bail!(SpecialError::InvalidGraph( - "params endorser not in committee set".to_string() - )); - } - - let ok = super::verify_graph_params_endorsement(evm_addr, &full_graph, sig).map_err( - |e| SpecialError::InvalidGraph(format!("failed to verify params endorsement: {e}")), - )?; - if !ok { - bail!(SpecialError::InvalidGraph( - "invalid params endorsement signature".to_string() - )); + break; } } + if !found { + bail!(SpecialError::InvalidGraph("params endorser not in committee set".to_string())); + } - Ok(()) - } - pub fn prekickoff_replenishment_amount() -> Amount { - Amount::from_sat(500000) - } - pub fn min_prekickoff_input_amount() -> Amount { - Amount::from_sat(200000) - } - pub fn challenge_amount() -> Amount { - Amount::from_sat(20000) - } - pub fn prekickoff_fee_amount(replenish_fee_inputs_num: usize) -> Amount { - let tx_vbytes = PRE_KICKOFF_BASE_VBYTES - + (replenish_fee_inputs_num as u64 * CHEKSIG_P2WSH_INPUT_VBYTES); - Amount::from_sat(tx_vbytes) + let ok = verify_graph_params_endorsement(evm_addr, &full_graph, sig).map_err(|e| { + SpecialError::InvalidGraph(format!("failed to verify params endorsement: {e}")) + })?; + if !ok { + bail!(SpecialError::InvalidGraph("invalid params endorsement signature".to_string())); + } } -} + Ok(()) +} +pub fn prekickoff_replenishment_amount() -> Amount { + Amount::from_sat(500000) +} +pub fn min_prekickoff_input_amount() -> Amount { + Amount::from_sat(200000) +} +pub fn challenge_amount() -> Amount { + Amount::from_sat(20000) +} +pub fn prekickoff_fee_amount(replenish_fee_inputs_num: usize) -> Amount { + let tx_vbytes = + PRE_KICKOFF_BASE_VBYTES + (replenish_fee_inputs_num as u64 * CHEKSIG_P2WSH_INPUT_VBYTES); + Amount::from_sat(tx_vbytes) +} pub mod evm_swap_utils { use super::*; use crate::utils::evm_swap_utils::IEscrowManager::{EscrowData, IEscrowManagerCalls}; @@ -3292,7 +3259,7 @@ pub async fn build_genesis_prekickoff_tx( goat_client: &GOATClient, ) -> Result { let watchtower_num = goat_client.committee_mana_get_watchtowers().await?.len(); - let verifier_num = todo_funcs::min_required_verifier(); + let verifier_num = min_required_verifier(); let network = get_network(); let operator_master_key = OperatorMasterKey::new(get_bitvm_key()?); let node_keypair = operator_master_key.master_keypair(); @@ -3301,7 +3268,7 @@ pub async fn build_genesis_prekickoff_tx( let next_force_skip_connector = ForceSkipConnector::new(network, &operator_taproot_public_key); let next_kickoff_connector = KickoffConnector::new(network, &operator_taproot_public_key); let next_prekickoff_connector = PrekickoffConnector::new(network, &operator_taproot_public_key); - let init_amount = todo_funcs::prekickoff_replenishment_amount(); + let init_amount = prekickoff_replenishment_amount(); let cur_prekickoff_connector_input = Input { outpoint: fund_address( btc_client, @@ -3312,7 +3279,7 @@ pub async fn build_genesis_prekickoff_tx( .await?, amount: init_amount, }; - let fee_amount = todo_funcs::prekickoff_fee_amount(0); + let fee_amount = prekickoff_fee_amount(0); PrekickoffTransaction::new_for_validation( &cur_prekickoff_connector, &next_force_skip_connector, @@ -3338,17 +3305,17 @@ pub async fn build_prekickoff_params( .map_err(|e| anyhow!("failed to get pre-kickoff connector input: {e}"))? .amount; let (replenish_fee_inputs, replenish_fee_prev_outs, fee_amount) = if prekickoff_remaining_amount - >= todo_funcs::min_prekickoff_input_amount() + >= min_prekickoff_input_amount() { // no need to replenish funds - (vec![], vec![], todo_funcs::prekickoff_fee_amount(0)) + (vec![], vec![], prekickoff_fee_amount(0)) } else { let network = get_network(); let operator_master_key = OperatorMasterKey::new(get_bitvm_key()?); let master_keypair = operator_master_key.master_keypair(); let nonce_keypair = operator_master_key.keypair_for_nonce(graph_nonce); let nonce_address = node_p2wsh_address(network, &nonce_keypair.public_key().into()); - let replenishment_amount = todo_funcs::prekickoff_replenishment_amount(); + let replenishment_amount = prekickoff_replenishment_amount(); let mut replenish_fee_inputs: Vec = btc_client .get_address_utxo(nonce_address.clone()) .await? @@ -3374,7 +3341,7 @@ pub async fn build_prekickoff_params( .iter() .map(|i| TxOut { value: i.amount, script_pubkey: nonce_address.script_pubkey() }) .collect(); - let fee_amount = todo_funcs::prekickoff_fee_amount(replenish_fee_inputs.len()); + let fee_amount = prekickoff_fee_amount(replenish_fee_inputs.len()); (replenish_fee_inputs, replenish_fee_prev_outs, fee_amount) }; Ok(PrekickoffParameters { @@ -3413,14 +3380,14 @@ pub async fn build_graph_params( .to_byte_array() }) .collect(); - todo_funcs::validate_watchtower_registry_selection(&watchtower_pubkeys, &watchtower_pubkeys)?; + validate_watchtower_registry_selection(&watchtower_pubkeys, &watchtower_pubkeys)?; Ok(BitvmGcGraphParameters { instance_parameters, prekickoff_parameters, timelock_config: default_timelock_config(network), graph_id, graph_nonce, - challenge_amount: todo_funcs::challenge_amount(), + challenge_amount: challenge_amount(), operator_pubkey, operator_assert_wots_pubkey, operator_commit_pubin_wots_pubkey, @@ -5395,8 +5362,7 @@ pub async fn get_endorsed_graph_count(local_db: &LocalDB, instance_id: Uuid) -> } pub async fn has_required_presigned_graphs(local_db: &LocalDB, instance_id: Uuid) -> Result { - Ok(get_endorsed_graph_count(local_db, instance_id).await? - >= todo_funcs::min_required_operator()) + Ok(get_endorsed_graph_count(local_db, instance_id).await? >= min_required_operator()) } pub async fn try_transition_instance_to_presigned(