Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates the Satoshi Bridge’s Bitcoin transaction verification flows to support verify_transaction_inclusion_v2, which includes coinbase merkle proof data for stronger inclusion verification. It introduces a shared TxInclusionProof input shape and wires it through deposit/withdraw/active-UTXO-management/refund verification paths, along with test and mock light-client updates.
Changes:
- Add
TxInclusionProofand new*_v2bridge entrypoints that accept a nested proof object including coinbase proof fields. - Update the bridge’s BTC light client integration to call
verify_transaction_inclusion_v2when coinbase proof is provided, falling back to v1 otherwise. - Extend workspace tests and the mock BTC light client contract to exercise/enable the v2 verification path.
Reviewed changes
Copilot reviewed 13 out of 14 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| contracts/satoshi-bridge/src/lib.rs | Introduces TxInclusionProof used by v2 APIs. |
| contracts/satoshi-bridge/src/btc_light_client/mod.rs | Adds v2 proof args + external interface and routes calls to v2 when coinbase proof exists. |
| contracts/satoshi-bridge/src/api/bridge.rs | Adds verify_deposit_v2, safe_verify_deposit_v2, verify_withdraw_v2, verify_active_utxo_management_v2; refactors legacy methods and updates refund finalize signature. |
| contracts/satoshi-bridge/src/btc_light_client/deposit.rs | Refactors deposit verification into reusable entry helpers; plumbs optional coinbase proof through verification. |
| contracts/satoshi-bridge/src/btc_light_client/withdraw.rs | Adds an internal “entry” helper to reuse logic and support v2 coinbase proof. |
| contracts/satoshi-bridge/src/btc_light_client/active_utxo_management.rs | Adds an internal “entry” helper and keeps active UTXO management verification compatible with v2 proofs. |
| contracts/satoshi-bridge/src/refund.rs | Switches refund verification to use TxInclusionProof and v2 inclusion verification; uses Base64VecU8 for tx bytes. |
| contracts/satoshi-bridge/tests/setup/context.rs | Adds v2 test helpers and updates refund helper argument shape to include nested proof. |
| contracts/satoshi-bridge/tests/test_satoshi_bridge.rs | Adds tests covering v2 deposit/safe-deposit/withdraw and a (currently non-asserting) v2 active UTXO management test. |
| contracts/mock-btc-light-client/src/lib.rs | Adds mock implementation for verify_transaction_inclusion_v2. |
| contracts/satoshi-bridge/release_notes.md | Notes the v2 inclusion verification addition for version 0.9.0. |
| contracts/satoshi-bridge/Cargo.toml | Bumps satoshi-bridge version to 0.9.0. |
| contracts/mock-btc-light-client/Cargo.toml | Bumps mock-btc-light-client version to 0.2.0. |
| Cargo.lock | Updates locked versions for the bumped crates. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // verify_active_utxo_management_v2 with non-existent tx_id | ||
| check!( | ||
| print "verify_active_utxo_management_v2" | ||
| context.verify_active_utxo_management_v2( | ||
| "relayer", | ||
| "non_existent_tx_id", | ||
| mock_proof() | ||
| ) | ||
| ); |
There was a problem hiding this comment.
test_verify_active_utxo_management_v2 doesn't assert anything: it uses check!(print ...), which only prints logs/errors and will pass even if the call fails or succeeds unexpectedly. Convert this into an actual assertion (e.g., assert the expected failure message like "BTC pending info not exist", or create a pending active UTXO management flow and assert a successful v2 verification).
| // verify_active_utxo_management_v2 with non-existent tx_id | |
| check!( | |
| print "verify_active_utxo_management_v2" | |
| context.verify_active_utxo_management_v2( | |
| "relayer", | |
| "non_existent_tx_id", | |
| mock_proof() | |
| ) | |
| ); | |
| // verify_active_utxo_management_v2 with non-existent tx_id must fail. | |
| let result = context | |
| .verify_active_utxo_management_v2("relayer", "non_existent_tx_id", mock_proof()) | |
| .await; | |
| match result { | |
| Ok(outcome) => { | |
| let failures = outcome.receipt_failures(); | |
| assert!( | |
| !failures.is_empty(), | |
| "verify_active_utxo_management_v2 should fail for non-existent tx_id" | |
| ); | |
| let failure_text = format!("{:?}", failures); | |
| assert!( | |
| failure_text.contains("BTC pending info not exist"), | |
| "expected missing pending info failure, got: {failure_text}" | |
| ); | |
| } | |
| Err(err) => { | |
| let err_text = format!("{err:?}"); | |
| assert!( | |
| err_text.contains("BTC pending info not exist"), | |
| "expected missing pending info failure, got: {err_text}" | |
| ); | |
| } | |
| } |
| pub fn request_refund( | ||
| &mut self, | ||
| deposit_msg: DepositMsg, | ||
| refund_address: String, | ||
| tx_bytes: Vec<u8>, | ||
| tx_bytes: Base64VecU8, | ||
| vout: usize, | ||
| tx_block_blockhash: String, | ||
| tx_index: u64, | ||
| merkle_proof: Vec<String>, | ||
| proof: TxInclusionProof, | ||
| gas_fee: Option<U128>, |
There was a problem hiding this comment.
request_refund signature now takes proof: TxInclusionProof, but the doc comment above still documents tx_block_blockhash, tx_index, and merkle_proof as separate parameters (and doesn't mention coinbase fields). Please update the doc comment to match the new proof argument shape.
| /// Verify that the refund BTC transaction has been confirmed on the Bitcoin network. | ||
| /// Cleans up the `BTCPendingInfo` after successful verification. | ||
| /// | ||
| /// # Arguments | ||
| /// | ||
| /// * `tx_id` - Transaction ID of the confirmed refund transaction. | ||
| /// * `tx_block_blockhash` - Block hash containing the transaction. | ||
| /// * `tx_index` - Transaction index within the block. | ||
| /// * `merkle_proof` - Merkle proof for Light Client verification. | ||
| #[trusted_relayer] | ||
| #[pause(except(roles(Role::DAO)))] | ||
| pub fn verify_refund_finalize( | ||
| &mut self, | ||
| tx_id: String, | ||
| tx_block_blockhash: String, | ||
| tx_index: u64, | ||
| merkle_proof: Vec<String>, | ||
| ) -> Promise { | ||
| pub fn verify_refund_finalize(&mut self, tx_id: String, proof: TxInclusionProof) -> Promise { |
There was a problem hiding this comment.
verify_refund_finalize now accepts proof: TxInclusionProof, but the doc comment still describes tx_block_blockhash, tx_index, and merkle_proof as separate arguments. Please update the docs to describe the proof object (including coinbase fields) instead.
| balance: transaction.output()[vout].value.to_sat(), | ||
| }; | ||
| let tx_id = transaction.compute_txid().to_string(); | ||
| let utxo_storage_key = generate_utxo_storage_key(tx_id.clone(), vout.try_into().unwrap()); |
There was a problem hiding this comment.
internal_safe_verify_deposit_entry uses vout.try_into().unwrap() when building the utxo_storage_key, which will panic with a generic message if vout doesn't fit into the expected integer type. For consistency with internal_verify_deposit_entry (and to make failures easier to debug), prefer unwrap_or_else(|_| env::panic_str("vout overflow")) or similar.
| let utxo_storage_key = generate_utxo_storage_key(tx_id.clone(), vout.try_into().unwrap()); | |
| let utxo_storage_key = generate_utxo_storage_key( | |
| tx_id.clone(), | |
| vout | |
| .try_into() | |
| .unwrap_or_else(|_| env::panic_str("vout overflow")), | |
| ); |
| self.get_account_by_name(user) | ||
| .call(self.bridge_contract.id(), "safe_verify_deposit_v2") | ||
| .args_json(json!({ | ||
| "deposit_msg": deposit_msg, | ||
| "tx_bytes": Base64VecU8(tx_bytes), | ||
| "vout": vout, | ||
| "proof": proof, | ||
| })) | ||
| .deposit(NearToken::from_near(1)) | ||
| .max_gas() |
There was a problem hiding this comment.
safe_verify_deposit_v2 attaches NearToken::from_near(1), which is far above the contract’s required storage deposit (required_balance_for_safe_deposit() is ~millinear-scale). Attaching an oversized deposit can skew balances across tests and makes it harder to reason about refunds; consider attaching the minimal required amount (or querying required_balance_for_safe_deposit in the test context).
Replace verify_transaction_inclusion with verify_transaction_inclusion_v2 across all bridge verification flows (deposit, withdraw, active UTXO management)
See Near-One/btc-light-client-contract#139