From 0475e161d5fef168b36a5ea035edd17bfc024651 Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 9 Nov 2026 07:40:17 +0000 Subject: [PATCH] fix: post verifier assert txids and settle Disprove-X with its reward address --- crates/client/src/goat_chain/chain_adaptor.rs | 2 +- crates/client/src/goat_chain/goat_adaptor.rs | 14 +++-- .../gateway/abis/GatewayUpgradeable.json | 6 +- node/src/aggregates/graph_pegout_jobs.rs | 5 +- .../scheduled_tasks/event_watch/gateway.rs | 58 ++++++++++++++++++- node/src/utils/goat_chain.rs | 12 +++- proof-builder-rpc/src/api/auth.rs | 2 +- 7 files changed, 84 insertions(+), 15 deletions(-) diff --git a/crates/client/src/goat_chain/chain_adaptor.rs b/crates/client/src/goat_chain/chain_adaptor.rs index 986db6fb9..fb5cd471f 100644 --- a/crates/client/src/goat_chain/chain_adaptor.rs +++ b/crates/client/src/goat_chain/chain_adaptor.rs @@ -374,7 +374,7 @@ pub struct GraphData { pub take2_txid: [u8; 32], pub watchtower_challenge_init_txid: [u8; 32], pub prover_assert_txid: [u8; 32], - pub disprove_txids: Vec<[u8; 32]>, + pub verifier_assert_txids: Vec<[u8; 32]>, pub watchtower_challenge_timeout_txids: Vec<[u8; 32]>, pub operator_challenge_nack_txids: Vec<[u8; 32]>, pub operator_commit_timeout_txid: [u8; 32], diff --git a/crates/client/src/goat_chain/goat_adaptor.rs b/crates/client/src/goat_chain/goat_adaptor.rs index 6ffce2de7..76bc4c289 100644 --- a/crates/client/src/goat_chain/goat_adaptor.rs +++ b/crates/client/src/goat_chain/goat_adaptor.rs @@ -124,7 +124,7 @@ sol!( bytes32 take2Txid; bytes32 watchtowerChallengeInitTxid; bytes32 proverAssertTxid; - bytes32[] disproveTxids; + bytes32[] verifierAssertTxids; bytes32[] watchtowerChallengeTimeoutTxids; bytes32[] operatorChallengeNackTxids; bytes32 operatorCommitTimeoutTxid; @@ -876,8 +876,8 @@ impl From for IGateway::GraphData { &value.watchtower_challenge_init_txid, ), proverAssertTxid: FixedBytes::from_slice(&value.prover_assert_txid), - disproveTxids: value - .disprove_txids + verifierAssertTxids: value + .verifier_assert_txids .into_iter() .map(|txid| FixedBytes::from_slice(&txid)) .collect::>(), @@ -907,7 +907,11 @@ impl From for GraphData { take2_txid: value.take2Txid.0, watchtower_challenge_init_txid: value.watchtowerChallengeInitTxid.0, prover_assert_txid: value.proverAssertTxid.0, - disprove_txids: value.disproveTxids.into_iter().map(|txid| txid.into()).collect(), + verifier_assert_txids: value + .verifierAssertTxids + .into_iter() + .map(|txid| txid.into()) + .collect(), watchtower_challenge_timeout_txids: value .watchtowerChallengeTimeoutTxids .into_iter() @@ -1761,7 +1765,7 @@ mod digest_tests { take2_txid: word(5), watchtower_challenge_init_txid: word(6), prover_assert_txid: word(7), - disprove_txids: vec![word(0xd1), word(0xd2)], + verifier_assert_txids: vec![word(0xd1), word(0xd2)], watchtower_challenge_timeout_txids: vec![word(0xe1)], operator_challenge_nack_txids: vec![], operator_commit_timeout_txid: word(9), diff --git a/localnet/subgraph/gateway/abis/GatewayUpgradeable.json b/localnet/subgraph/gateway/abis/GatewayUpgradeable.json index 36e4de5a1..3bfe9a1e5 100644 --- a/localnet/subgraph/gateway/abis/GatewayUpgradeable.json +++ b/localnet/subgraph/gateway/abis/GatewayUpgradeable.json @@ -487,7 +487,7 @@ "internalType": "bytes32" }, { - "name": "disproveTxids", + "name": "verifierAssertTxids", "type": "bytes32[]", "internalType": "bytes32[]" }, @@ -693,7 +693,7 @@ "internalType": "bytes32" }, { - "name": "disproveTxids", + "name": "verifierAssertTxids", "type": "bytes32[]", "internalType": "bytes32[]" }, @@ -1174,7 +1174,7 @@ "internalType": "bytes32" }, { - "name": "disproveTxids", + "name": "verifierAssertTxids", "type": "bytes32[]", "internalType": "bytes32[]" }, diff --git a/node/src/aggregates/graph_pegout_jobs.rs b/node/src/aggregates/graph_pegout_jobs.rs index 5bfef22de..73c600551 100644 --- a/node/src/aggregates/graph_pegout_jobs.rs +++ b/node/src/aggregates/graph_pegout_jobs.rs @@ -1042,7 +1042,10 @@ async fn disprove( ) { return step; } - let disprove_tx = build_disprove_tx(graph, verifier_index, None)?; + // The Gateway pays the disprover named in output 0's OP_RETURN; it identifies + // the Disprove by its inputs, so the added output does not stop settlement. + let reward_address = crate::env::get_node_goat_address().map(|address| address.into_array()); + let disprove_tx = build_disprove_tx(graph, verifier_index, reward_address)?; let disprove_txid = disprove_tx.compute_txid(); let input_amount = graph.disproves[verifier_index].prev_outs().iter().map(|o| o.value).sum::(); diff --git a/node/src/scheduled_tasks/event_watch/gateway.rs b/node/src/scheduled_tasks/event_watch/gateway.rs index 4f528a027..4e6f0e9ad 100644 --- a/node/src/scheduled_tasks/event_watch/gateway.rs +++ b/node/src/scheduled_tasks/event_watch/gateway.rs @@ -426,7 +426,15 @@ async fn handle_withdraw_disproved_events<'a>( ) -> anyhow::Result<()> { for event in withdraw_disproved_events { let graph_id = Uuid::from_str(&strip_hex_prefix_owned(&event.graph_id))?; - let instance_id = Uuid::from_str(&strip_hex_prefix_owned(&event.instance_id))?; + let mut instance_id = Uuid::from_str(&strip_hex_prefix_owned(&event.instance_id))?; + // A graph disproved without initWithdraw has no withdraw record on a + // Gateway that does not record the instance at postGraphData, and its + // event carries a zero instance id: take the instance from the local graph. + if instance_id.is_nil() + && let Some(graph) = storage_processor.find_graph(&graph_id).await? + { + instance_id = graph.instance_id; + } if !graph_belongs_to_instance(storage_processor, instance_id, graph_id).await? { continue; } @@ -731,6 +739,54 @@ mod tests { } } + #[tokio::test] + async fn a_disprove_event_without_an_instance_id_is_matched_to_the_local_graph() { + let (db, _dir) = db().await; + let (instance_id, graph_id) = (Uuid::new_v4(), Uuid::new_v4()); + db.acquire() + .await + .unwrap() + .upsert_graph_definition(&store::Graph { + graph_id, + instance_id, + status: GraphStatus::OperatorPresigned.to_string(), + definition_hash: "definition".to_string(), + ..Default::default() + }) + .await + .unwrap(); + let address = format!("0x{}", "11".repeat(20)); + let event = WithdrawDisprovedEvent { + id: "1".to_string(), + transaction_hash: "0xbb".to_string(), + block_number: "7".to_string(), + instance_id: format!("0x{}", Uuid::nil().simple()), + graph_id: format!("0x{}", graph_id.simple()), + disprove_tx_type: 0, + tx_index: "0".to_string(), + challenge_start_txid: String::new(), + challenge_finish_txid: String::new(), + challenger_addr: address.clone(), + challenger_amount_sats: "0".to_string(), + disprover_addr: address, + disprover_amount_sats: "0".to_string(), + }; + let mut tx = db.start_transaction().await.unwrap(); + handle_withdraw_disproved_events(&mut tx, vec![event]).await.unwrap(); + tx.commit().await.unwrap(); + let mut storage = db.acquire().await.unwrap(); + let tx_type = GoatTxType::WithdrawDisproved.to_string(); + assert!( + storage + .find_graph_goat_tx_record(&instance_id, &graph_id, &tx_type) + .await + .unwrap() + .is_some() + ); + let graph = storage.find_graph(&graph_id).await.unwrap().unwrap(); + assert_eq!(graph.status, GraphStatus::Disprove.to_string()); + } + #[tokio::test] async fn a_posted_graph_unknown_locally_is_pulled_by_the_watching_roles() { let (db, _dir) = db().await; diff --git a/node/src/utils/goat_chain.rs b/node/src/utils/goat_chain.rs index 57ce963e4..53df05a7d 100644 --- a/node/src/utils/goat_chain.rs +++ b/node/src/utils/goat_chain.rs @@ -37,8 +37,14 @@ pub fn build_graph_data(graph: &BitvmGcGraph) -> Result { let watchtower_challenge_init_txid = graph.watchtower_challenge_init.finalize().compute_txid().to_byte_array(); let prover_assert_txid = graph.operator_assert.finalize().compute_txid().to_byte_array(); - let disprove_txids: Vec<[u8; 32]> = - graph.disproves.iter().map(|tx| tx.finalize().compute_txid().to_byte_array()).collect(); + // The Gateway identifies a Disprove by the outpoints it spends, output 0 of + // a verifier's ChallengeAssert among them: Disprove-X is pre-signed with + // SIGHASH_NONE, so its own txid changes with the disprover's reward output. + let verifier_assert_txids: Vec<[u8; 32]> = graph + .verifier_asserts + .iter() + .map(|tx| tx.finalize().compute_txid().to_byte_array()) + .collect(); let watchtower_challenge_timeout_txids: Vec<[u8; 32]> = graph .watchtower_challenge_timeouts .iter() @@ -61,7 +67,7 @@ pub fn build_graph_data(graph: &BitvmGcGraph) -> Result { take2_txid, watchtower_challenge_init_txid, prover_assert_txid, - disprove_txids, + verifier_assert_txids, watchtower_challenge_timeout_txids, operator_challenge_nack_txids, operator_commit_timeout_txid, diff --git a/proof-builder-rpc/src/api/auth.rs b/proof-builder-rpc/src/api/auth.rs index e0a9cde56..4ae0dc397 100644 --- a/proof-builder-rpc/src/api/auth.rs +++ b/proof-builder-rpc/src/api/auth.rs @@ -428,7 +428,7 @@ pub(crate) mod test_support { take2_txid: [0; 32], watchtower_challenge_init_txid: [0; 32], prover_assert_txid: [0; 32], - disprove_txids: vec![], + verifier_assert_txids: vec![], watchtower_challenge_timeout_txids: vec![], operator_challenge_nack_txids: vec![], operator_commit_timeout_txid: [0; 32],