You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In wallets with slightly larger derivation gap, outputs spent at a higher derivation index are recognized inconsistently. After loading the wallet from sqlite, a spent output may be incorrectly reported as unspent leaving the wallet in unusable state. This appears to happen because KeychainTxOutIndex isn't consistently persisted/restored from the sqlite backend.
To Reproduce
Tricky to reproduce! Here's a test using the higher level Wallet which consists of two transactions:
tx1 creates some outputs with different gaps
tx2 spends an output with a larger gap.
Initially, wallet ignores the output with the larger gap, and it doesn't track its spend in tx2.
After calling Wallet::load, the wallet recognizes that output (it didn't previously). It's also incorrectly marked as unspent (because it didn't initially track the spend in tx2).
#[test]fntest_inconsistent_last_revealed_index_after_reload(){letmut conn = Connection::open_in_memory().expect("memory db");letmut wallet = Wallet::create("tr(tprv8ZgxMBicQKsPd3krDUsBAmtnRsK3rb8u5yi1zhQgMhF1tR8MW7xfE4rnrbbsrbPR52e7rKapu6ztw1jXveJSCGHEriUGZV7mCe88duLp5pj/86'/1'/0'/0/*)","tr(tprv8ZgxMBicQKsPd3krDUsBAmtnRsK3rb8u5yi1zhQgMhF1tR8MW7xfE4rnrbbsrbPR52e7rKapu6ztw1jXveJSCGHEriUGZV7mCe88duLp5pj/86'/1'/0'/1/*)",).network(Network::Regtest).create_wallet(&mut conn).expect("wallet");// Create some outputs with different gapslet funding_tx = Transaction{version: bitcoin::transaction::Version::TWO,lock_time:LockTime::from_consensus(1),input:vec![],output:vec![TxOut{
value:Amount::default(),
script_pubkey: wallet.peek_address(KeychainKind::External,1).script_pubkey(),},TxOut{
value:Amount::default(),
script_pubkey: wallet.peek_address(KeychainKind::External,45).script_pubkey(),},TxOut{
value:Amount::default(),
script_pubkey: wallet.peek_address(KeychainKind::External,6).script_pubkey(),},TxOut{
value:Amount::default(),
script_pubkey: wallet.peek_address(KeychainKind::External,20).script_pubkey(),},],};// Spend second outputlet spending_tx = Transaction{version: bitcoin::transaction::Version::TWO,lock_time:LockTime::from_consensus(1),input:vec![TxIn{
previous_output:OutPoint{
txid: funding_tx.compute_txid(),
vout:1,},
..Default::default()}],output:vec![TxOut::NULL],};let block = Block{header:Header{version:Default::default(),prev_blockhash:genesis_block(¶ms::REGTEST).block_hash(),merkle_root:TxMerkleNode::all_zeros(),time:0,bits:Default::default(),nonce:0,},txdata:vec![funding_tx, spending_tx],};
wallet.apply_block(&block,1).expect("apply block");
wallet.persist(&mut conn).expect("persist");let unspent_before = wallet.list_unspent().count();println!("last revealed index before: {:?}", wallet.spk_index().last_revealed_index(KeychainKind::External));// Reload walletlet wallet = Wallet::load().load_wallet(&mut conn).expect("wallet found").expect("load wallet");let unspent_after = wallet.list_unspent().count();println!("last revealed index after: {:?}", wallet.spk_index().last_revealed_index(KeychainKind::External));assert_eq!(
unspent_before,
unspent_after,"Unspent output count should be the same before and after reload");}
Output
last revealed index before: Some(20)
last revealed index after: Some(45)
Unspent output count should be the same before and after reload
Left: 3
Right: 4
Expected behavior
As the test shows, the wallet should at least consistently ignore it. If it's not tracking it initially, it shouldn't track it after reload.
The text was updated successfully, but these errors were encountered:
I haven't fully grokked where the issue might have originated, but I'm a little confused about the use of peek_address though I realize this is a slightly simplified example. I assume under normal operation you are revealing addresses before receiving a payment right?
I assume under normal operation you are revealing addresses before receiving a payment right?
I was thinking there may have been large gaps from handing out too many addresses but I just checked, and the largest gap in that wallet was only 3 addresses.
I haven't fully grokked where the issue might have originated
I suspect the logic assumes txs are included following the order of derivation index, but that’s not always true. Sorting outputs by chain position confirms this (derivation indexes are not sequential and have larger gaps confusing the wallet perhaps).
Interestingly, if you modify the test to reorder txouts by derivation index, the issue disappears.
Describe the bug
In wallets with slightly larger derivation gap, outputs spent at a higher derivation index are recognized inconsistently. After loading the wallet from sqlite, a spent output may be incorrectly reported as unspent leaving the wallet in unusable state. This appears to happen because
KeychainTxOutIndex
isn't consistently persisted/restored from the sqlite backend.To Reproduce
Tricky to reproduce! Here's a test using the higher level
Wallet
which consists of two transactions:tx1
creates some outputs with different gapstx2
spends an output with a larger gap.Wallet::load
, the wallet recognizes that output (it didn't previously). It's also incorrectly marked as unspent (because it didn't initially track the spend in tx2).Output
Expected behavior
As the test shows, the wallet should at least consistently ignore it. If it's not tracking it initially, it shouldn't track it after reload.
The text was updated successfully, but these errors were encountered: