From 4f7fdb2ce4dfedb8e45b18ad9390917ad2d15eef Mon Sep 17 00:00:00 2001 From: ethan Date: Mon, 24 Aug 2026 17:01:56 +0800 Subject: [PATCH 1/2] fix pegout api --- node/src/bin/send_pegout.rs | 4 +- node/src/rpc_service/bitvm.rs | 3 +- node/src/rpc_service/handler/bitvm_handler.rs | 166 ++++++++++++++---- 3 files changed, 135 insertions(+), 38 deletions(-) diff --git a/node/src/bin/send_pegout.rs b/node/src/bin/send_pegout.rs index a3ae1298..9df6cea2 100644 --- a/node/src/bin/send_pegout.rs +++ b/node/src/bin/send_pegout.rs @@ -52,7 +52,7 @@ enum Commands { #[arg(long, default_value_t = false)] dry_run: bool, - /// Skip graphs whose instances are locked by another withdrawal + /// Skip candidates blocked by another withdrawal, including stale predecessors #[arg(long, default_value_t = false)] skip_locked: bool, }, @@ -70,7 +70,7 @@ enum Commands { #[arg(long, default_value_t = false)] dry_run: bool, - /// Skip graphs whose instances are locked by another withdrawal + /// Skip candidates blocked by another withdrawal, including stale predecessors #[arg(long, default_value_t = false)] skip_locked: bool, diff --git a/node/src/rpc_service/bitvm.rs b/node/src/rpc_service/bitvm.rs index 3be10219..c352ae99 100644 --- a/node/src/rpc_service/bitvm.rs +++ b/node/src/rpc_service/bitvm.rs @@ -111,7 +111,8 @@ pub struct PegoutRequest { pub graph_id: Option, #[serde(default)] pub dry_run: bool, - /// Skip graphs whose instances are temporarily locked by another withdrawal. + /// Skip candidate graphs, and their stale predecessors, when their instances are + /// locked by another withdrawal. #[serde(default)] pub skip_locked: bool, } diff --git a/node/src/rpc_service/handler/bitvm_handler.rs b/node/src/rpc_service/handler/bitvm_handler.rs index 1652b801..05719b3d 100644 --- a/node/src/rpc_service/handler/bitvm_handler.rs +++ b/node/src/rpc_service/handler/bitvm_handler.rs @@ -1790,8 +1790,8 @@ fn sats_to_token_amount(amount_sats: u64, token_decimals: u8) -> U256 { /// - `graph_id`: Optional preferred UUID of the graph to pegout. If it has /// already been claimed by another graph, the next eligible graph is used. /// - `dry_run`: If true, validate but skip the actual initWithdraw call (default: false) -/// - `skip_locked`: If true, skip an instance temporarily locked by another -/// withdrawal and search for another withdrawable graph (default: false). +/// - `skip_locked`: If true, skip a candidate whose instance is locked by another +/// withdrawal, including a stale locked predecessor (default: false). /// /// # Returns /// @@ -1917,6 +1917,9 @@ pub async fn pegout( "skipped obsolete graph while selecting pegout candidate" ); } + skipped_graph_ids.insert(graph.graph_id); + preferred_graph_id = None; + continue; } PeginStatus::Locked if !payload.skip_locked => { return error_response( @@ -1933,43 +1936,136 @@ pub async fn pegout( skipped_graph_id = %graph.graph_id, "skipped graph whose instance is locked by another withdrawal" ); + skipped_graph_ids.insert(graph.graph_id); + preferred_graph_id = None; + continue; } - _ => break (graph, pegin_data), + _ => {} } - skipped_graph_ids.insert(graph.graph_id); - preferred_graph_id = None; - }; - - let mut storage_process = app_state.local_db.acquire().await.api_error("PEGOUT_ERROR")?; - - // Check previous graph readiness - if graph.kickoff_index > 0 { - let pre_graphs = storage_process - .get_operator_graphs( - GraphQuery::default() - .with_operator_pubkey(graph.operator_pubkey.clone()) - .with_kickoff_index(graph.kickoff_index - 1), - ) - .await - .api_error("PEGOUT_ERROR")?; - if !pre_graphs.is_empty() - && [ - GraphStatus::OperatorDataPushed.to_string(), - GraphStatus::OperatorKickOff.to_string(), - GraphStatus::Challenge.to_string(), - ] - .contains(&pre_graphs[0].status) - { - return error_response( - "PEGOUT_ERROR".to_string(), - format!( - "graph {} not ready: previous graph {} still in status {}", - graph.graph_id, pre_graphs[0].graph_id, pre_graphs[0].status - ), - ); + if graph.kickoff_index > 0 { + let previous_graph = { + let mut storage_process = + app_state.local_db.acquire().await.api_error("PEGOUT_ERROR")?; + storage_process + .get_operator_graphs( + GraphQuery::default() + .with_operator_pubkey(graph.operator_pubkey.clone()) + .with_kickoff_index(graph.kickoff_index - 1), + ) + .await + .api_error("PEGOUT_ERROR")? + .into_iter() + .next() + }; + + if let Some(previous_graph) = previous_graph { + let previous_status = match GraphStatus::from_str(&previous_graph.status) { + Ok(status) => status, + Err(_) => { + return error_response( + "PEGOUT_ERROR".to_string(), + format!( + "graph {} has an unknown previous graph {} status {}", + graph.graph_id, previous_graph.graph_id, previous_graph.status + ), + ); + } + }; + match previous_status { + // A higher-index graph can only reach OperatorDataPushed after these + // graphs have become stale, so they must not block its withdrawal. + GraphStatus::OperatorPresigned | GraphStatus::CommitteePresigned => {} + GraphStatus::Obsoleted + | GraphStatus::Skipped + | GraphStatus::OperatorTake1 + | GraphStatus::OperatorTake2 + | GraphStatus::Disprove => {} + GraphStatus::OperatorDataPushed => { + let previous_pegin_data = app_state + .goat_client + .gateway_get_pegin_data(&previous_graph.instance_id) + .await + .api_error("PEGOUT_ERROR")?; + let previous_withdraw_data = app_state + .goat_client + .gateway_get_withdraw_data(&previous_graph.graph_id) + .await + .api_error("PEGOUT_ERROR")?; + match previous_pegin_data.status { + PeginStatus::Claimed => {} + PeginStatus::Locked + if previous_withdraw_data.status == WithdrawStatus::None + && payload.skip_locked => + { + info!( + instance_id = %graph.instance_id, + graph_id = %graph.graph_id, + previous_graph_id = %previous_graph.graph_id, + "previous graph is locked by another withdrawal; continuing pegout" + ); + } + PeginStatus::Locked + if previous_withdraw_data.status == WithdrawStatus::None => + { + return error_response( + "PEGOUT_IN_PROGRESS".to_string(), + format!( + "graph {} is blocked because previous graph {} is locked by another withdrawal; retry later or set skip_locked=true", + graph.graph_id, previous_graph.graph_id + ), + ); + } + PeginStatus::Withdrawable => { + return error_response( + "PEGOUT_ERROR".to_string(), + format!( + "graph {} not ready: previous graph {} is still withdrawable", + graph.graph_id, previous_graph.graph_id + ), + ); + } + previous_pegin_status => { + return error_response( + "PEGOUT_ERROR".to_string(), + format!( + "graph {} not ready: previous graph {} is in status {} with pegin status {:?} and withdraw status {:?}", + graph.graph_id, + previous_graph.graph_id, + previous_graph.status, + previous_pegin_status, + previous_withdraw_data.status + ), + ); + } + } + } + GraphStatus::PreKickoff + | GraphStatus::OperatorKickOff + | GraphStatus::Challenge => { + return error_response( + "PEGOUT_ERROR".to_string(), + format!( + "graph {} not ready: previous graph {} is in active pegout status {}", + graph.graph_id, previous_graph.graph_id, previous_graph.status + ), + ); + } + _ => { + return error_response( + "PEGOUT_ERROR".to_string(), + format!( + "graph {} not ready: previous graph {} has unexpected status {}", + graph.graph_id, previous_graph.graph_id, previous_graph.status + ), + ); + } + } + } } - } + + break (graph, pegin_data); + }; let amount = graph.amount; if amount <= 0 { From 246663fab37e0076996d0a5976767ef1e4dbd61e Mon Sep 17 00:00:00 2001 From: ethan Date: Wed, 26 Aug 2026 11:17:57 +0800 Subject: [PATCH 2/2] update bitvm-gc deps --- Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index df894457..66e18e4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5569,7 +5569,7 @@ dependencies = [ [[package]] name = "garbled-snark-verifier" version = "0.1.0" -source = "git+https://github.com/GOATNetwork/bitvm-gc?branch=feat%2Fgoat-bitvm3#64e6373162deda32b88289a87e05093124d36b04" +source = "git+https://github.com/GOATNetwork/bitvm-gc?branch=feat%2Fgoat-bitvm3#65b523681ac5dc8f1464602f0e38803b9055ccf7" dependencies = [ "aes", "ark-bn254", @@ -12195,7 +12195,7 @@ dependencies = [ [[package]] name = "soldering-host" version = "1.1.0" -source = "git+https://github.com/GOATNetwork/bitvm-gc?branch=feat%2Fgoat-bitvm3#64e6373162deda32b88289a87e05093124d36b04" +source = "git+https://github.com/GOATNetwork/bitvm-gc?branch=feat%2Fgoat-bitvm3#65b523681ac5dc8f1464602f0e38803b9055ccf7" dependencies = [ "ark-bn254", "ark-crypto-primitives", @@ -13896,7 +13896,7 @@ dependencies = [ [[package]] name = "verifiable-circuit-babe" version = "0.0.1" -source = "git+https://github.com/GOATNetwork/bitvm-gc?branch=feat%2Fgoat-bitvm3#64e6373162deda32b88289a87e05093124d36b04" +source = "git+https://github.com/GOATNetwork/bitvm-gc?branch=feat%2Fgoat-bitvm3#65b523681ac5dc8f1464602f0e38803b9055ccf7" dependencies = [ "aes", "ark-bn254",