fix(wallet): categorize self-transfers to external addresses as trusted_pending#431
fix(wallet): categorize self-transfers to external addresses as trusted_pending#431Dmenec wants to merge 2 commits intobitcoindevkit:masterfrom
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #431 +/- ##
==========================================
+ Coverage 80.04% 80.16% +0.12%
==========================================
Files 24 24
Lines 5336 5369 +33
Branches 242 247 +5
==========================================
+ Hits 4271 4304 +33
+ Misses 987 986 -1
- Partials 78 79 +1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
ValuedMammal
left a comment
There was a problem hiding this comment.
Interesting approach. I'm not sure that I follow the transitive trust logic though - if Alice sends money to Bob, she might trust her own transaction but not Bob's transaction spending the output.
|
I went through the code and ran the tests, everything looks good and all the tests pass aea97d4 |
Yup, I think just verifying that all inputs of a transaction are owned by the wallet and the output belongs to it should be a better approach. [edit] Discussing with @qatkk we found there was still a problem with dependencies (addressed in 3b2af6b) It now propagates untrust by iterating over all transactions and marks owned outputs as untrusted if any of their inputs are external or inherit untrust from a previous transaction, then excludes them from the trusted set. This should solve the untrusted problem if there's some untrusted transacition in the middle. |
aea97d4 to
601a73a
Compare
…ed_pending Removes previous balance functionality. Now checks every outpoint owned by the wallet and searches for trusted transactions.
…pending Add tests to verify correct balance categorization for trusted/untrusted pending transactions. Covers: - owned inputs to internal output is trusted - external inputs to external output is untrusted - transitive trust through a chain of unconfirmed tx - external input to internal output is untrusted - trust does not propagate through foreign outputs - spending an unstrusted output is untrusted
601a73a to
3b2af6b
Compare
nymius
left a comment
There was a problem hiding this comment.
Although the code fixes the referenced issue, Isn't the code too much constrained by the TxGraph::balance signature?
The trust_predicate is missing key information that's available in the context of execution. What about bypassing the graph balance method and re implementing the logic directly in wallet?
| } | ||
| let is_untrusted = tx_node.tx.input.iter().any(|txin| { | ||
| !owned_outpoints.contains(&txin.previous_output) | ||
| || untrusted_outpoints.contains(&txin.previous_output) |
There was a problem hiding this comment.
I don't know if I'm missing an assumption over graph here, but if txin.previous_output is confirmed, wouldn't it mean that that output should be considered trusted?
This case:
#[test]
fn test_pay_to_internal_from_not_trusted_but_confirmed() {
let (mut wallet, _) = get_funded_wallet_wpkh();
// Build a tx whose input comes from an unknown (external) outpoint,
// but whose output goes to our change (internal) keychain address.
let external_txid = Txid::from_raw_hash(Hash::all_zeros());
let tx = Transaction {
input: vec![TxIn {
previous_output: OutPoint {
txid: external_txid,
vout: 0,
},
..Default::default()
}],
output: vec![TxOut {
value: Amount::from_sat(500),
script_pubkey: wallet
.next_unused_address(KeychainKind::Internal)
.address
.script_pubkey(),
}],
version: transaction::Version::ONE,
lock_time: absolute::LockTime::ZERO,
};
insert_tx(&mut wallet, tx);
let anchor = ConfirmationBlockTime {
block_id: wallet.latest_checkpoint().get(2000).unwrap().block_id(),
confirmation_time: 0,
};
// The transaction is confirmed
insert_anchor(&mut wallet, external_txid, anchor);
let balance = wallet.balance();
// The output is ours but the input is not owned, so it must be untrusted_pending.
assert!(balance.trusted_pending > Amount::ZERO);
assert_eq!(balance.untrusted_pending, Amount::ZERO);
}There was a problem hiding this comment.
Yup, I think there is another bug in there. Maybe another hashset of confirmed outpoints and checking if the hashet contains it on:
let is_untrusted = tx_node.tx.input.iter().any(|txin| {
!owned_outpoints.contains(&txin.previous_output)
|| untrusted_outpoints.contains(&txin.previous_output)
});
Would solve that?
There was a problem hiding this comment.
Sorry, did you intend to show something in the code? I'm not getting it.
Anyways, I wouldn't be using hash sets lightly here, not without testing against large cases.
There was a problem hiding this comment.
Yes, I think I lost context just with this snippet. The point was to add another condition there to check if it's confirmed or not, maybe by storing all the confirmed transactions in a separate hash set.
Agreeing with benchmarking it first.
There was a problem hiding this comment.
After discussing with @ValuedMammal, we conclude the definition of trust is: all inputs must be controlled by this wallet.
I don't know if it's already in the docs, but it would be worth adding it here.
In that case, my original comment is invalid, and you shouldn't make changes based on it.
There was a problem hiding this comment.
Regarding the definition of trust, are we deliberately not taking into account if a transaction is confirmed or not?
| let mut untrusted_outpoints: HashSet<OutPoint> = HashSet::new(); | ||
|
|
||
| let mut changed = true; | ||
| while changed { |
There was a problem hiding this comment.
I would change this for loop { ... } and break if conditions are met. changed doesn't hold any meaning by itself outside of this particular context.
We are still behind on this, are you willing to add bench marking test for this PR? |
Yes!
Probably it's the best option, maybe tempting to deprecate it in the future on |
Sure just to make it a bit more clear, the benchmarking would be only to report the overhead of the balance computation regarding the graph size and its topology, true? |
Yes, just benchmark the overhead of the balance computation in relation to the graph size. Adding topology without at least a simpler benchmark is an overkill for now. |
I'm on it. Meanwhile I wanted to propose a new approach to address the balance computation. Since the concept of |
Fixes #273.
Description
Unconfirmed transactions spending owned UTXOs to external addresses were being incorrectly categorized as
untrusted_pending. This should not happen since all inputs are controlled by the wallet.Notes to the reviewers
The
balancefunction iterates over all transactions in the graph and marks a transaction's owned outputs as trusted if all its inputs spend from owned outpoints.Checklists
All Submissions:
just pbefore pushingBugfixes: