Merge bitcoindevkit/bdk#1416: [chain] Change tx_last_seen to Option<u64>
af75817d4bref(tx_graph): Change last_seen to `HashMap<Txid, u64>` (valued mammal)6204d2c766feat(tx_graph): Add method `txs_with_no_anchor_or_last_seen` (valued mammal)496601b8b1test(tx_graph): Add test for `list_canonical_txs` (valued mammal)c4057297a9wallet: delete method `insert_anchor` (valued mammal)b34790c6b6ref(tx_graph)!: Rename `list_chain_txs` to `list_canonical_txs` (valued mammal)2ce4bb4dfctest(indexed_tx_graph): Add test_get_chain_position (valued mammal)36f58870cbtest(wallet): Add test_insert_tx_balance_and_utxos (valued mammal)bbc19c3536fix(tx_graph)!: Change tx_last_seen to `Option<u64>` (valued mammal)324eeb3eb4fix(wallet)!: Rework `Wallet::insert_tx` to no longer insert anchors (valued mammal) Pull request description: The PR changes the type of last_seen to `Option<u64>` for `txs` member of `TxGraph`. This fixes an issue where unbroadcast and otherwise non-canonical transactions were returned from methods `list_chain_txs` and `Wallet::transactions` because every new tx inserted had a last_seen of 0 making it appear unconfirmed. fixes #1446 fixes #1396 ### Notes to the reviewers ### Changelog notice Changed - Member `last_seen_unconfirmed` of `TxNode` is changed to `Option<u64>` - Renamed `TxGraph` method `list_chain_txs` to `list_canonical_txs` - Changed `Wallet::insert_tx` to take a single `tx: Transaction` as parameter Added - Add method `txs_with_no_anchor_or_last_seen` for `TxGraph` - Add method `unbroadcast_transactions` for `Wallet` ### Checklists #### All Submissions: * [x] I've signed all my commits * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md) * [x] I ran `cargo fmt` and `cargo clippy` before committing #### Bugfixes: * [x] This pull request breaks the existing API * [x] I've added tests to reproduce the issue which are now passing * [x] I'm linking the issue being fixed by this PR ACKs for top commit: notmandatory: Re ACKaf75817d4bTree-SHA512: e664b3b49e2f547873923f15dffbbc7fa032b6240e5b856b180e9e26123ca141864d10448912dc4a31bbb200c75bef4251a910a4330dac17ee6841b564612d13
This commit is contained in:
@@ -214,7 +214,7 @@ mod test {
|
||||
use core::str::FromStr;
|
||||
|
||||
use crate::std::string::ToString;
|
||||
use bdk_chain::{BlockId, ConfirmationTime};
|
||||
use bdk_chain::{BlockId, ConfirmationTimeHeightAnchor};
|
||||
use bitcoin::hashes::Hash;
|
||||
use bitcoin::{transaction, BlockHash, Network, Transaction};
|
||||
|
||||
@@ -222,6 +222,8 @@ mod test {
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
fn get_test_wallet(descriptor: &str, change_descriptor: &str, network: Network) -> Wallet {
|
||||
use crate::wallet::Update;
|
||||
use bdk_chain::TxGraph;
|
||||
let mut wallet = Wallet::new(descriptor, change_descriptor, network).unwrap();
|
||||
let transaction = Transaction {
|
||||
input: vec![],
|
||||
@@ -229,22 +231,27 @@ mod test {
|
||||
version: transaction::Version::non_standard(0),
|
||||
lock_time: bitcoin::absolute::LockTime::ZERO,
|
||||
};
|
||||
let txid = transaction.compute_txid();
|
||||
let block_id = BlockId {
|
||||
height: 5001,
|
||||
hash: BlockHash::all_zeros(),
|
||||
};
|
||||
wallet.insert_checkpoint(block_id).unwrap();
|
||||
wallet.insert_tx(transaction);
|
||||
let anchor = ConfirmationTimeHeightAnchor {
|
||||
confirmation_height: 5000,
|
||||
confirmation_time: 0,
|
||||
anchor_block: block_id,
|
||||
};
|
||||
let mut graph = TxGraph::default();
|
||||
let _ = graph.insert_anchor(txid, anchor);
|
||||
wallet
|
||||
.insert_checkpoint(BlockId {
|
||||
height: 5001,
|
||||
hash: BlockHash::all_zeros(),
|
||||
.apply_update(Update {
|
||||
graph,
|
||||
..Default::default()
|
||||
})
|
||||
.unwrap();
|
||||
wallet
|
||||
.insert_tx(
|
||||
transaction,
|
||||
ConfirmationTime::Confirmed {
|
||||
height: 5000,
|
||||
time: 0,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
wallet
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -27,7 +27,7 @@ use bdk_chain::{
|
||||
self, ApplyHeaderError, CannotConnectError, CheckPoint, CheckPointIter, LocalChain,
|
||||
},
|
||||
spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult},
|
||||
tx_graph::{CanonicalTx, TxGraph},
|
||||
tx_graph::{CanonicalTx, TxGraph, TxNode},
|
||||
Append, BlockId, ChainPosition, ConfirmationTime, ConfirmationTimeHeightAnchor, FullTxOut,
|
||||
Indexed, IndexedTxGraph,
|
||||
};
|
||||
@@ -290,35 +290,6 @@ impl fmt::Display for NewOrLoadError {
|
||||
#[cfg(feature = "std")]
|
||||
impl std::error::Error for NewOrLoadError {}
|
||||
|
||||
/// An error that may occur when inserting a transaction into [`Wallet`].
|
||||
#[derive(Debug)]
|
||||
pub enum InsertTxError {
|
||||
/// The error variant that occurs when the caller attempts to insert a transaction with a
|
||||
/// confirmation height that is greater than the internal chain tip.
|
||||
ConfirmationHeightCannotBeGreaterThanTip {
|
||||
/// The internal chain's tip height.
|
||||
tip_height: u32,
|
||||
/// The introduced transaction's confirmation height.
|
||||
tx_height: u32,
|
||||
},
|
||||
}
|
||||
|
||||
impl fmt::Display for InsertTxError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
InsertTxError::ConfirmationHeightCannotBeGreaterThanTip {
|
||||
tip_height,
|
||||
tx_height,
|
||||
} => {
|
||||
write!(f, "cannot insert tx with confirmation height ({}) higher than internal tip height ({})", tx_height, tip_height)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl std::error::Error for InsertTxError {}
|
||||
|
||||
/// An error that may occur when applying a block to [`Wallet`].
|
||||
#[derive(Debug)]
|
||||
pub enum ApplyBlockError {
|
||||
@@ -1085,63 +1056,21 @@ impl Wallet {
|
||||
/// Add a transaction to the wallet's internal view of the chain. This stages the change,
|
||||
/// you must persist it later.
|
||||
///
|
||||
/// Returns whether anything changed with the transaction insertion (e.g. `false` if the
|
||||
/// transaction was already inserted at the same position).
|
||||
/// This method inserts the given `tx` and returns whether anything changed after insertion,
|
||||
/// which will be false if the same transaction already exists in the wallet's transaction
|
||||
/// graph. Any changes are staged but not committed.
|
||||
///
|
||||
/// A `tx` can be rejected if `position` has a height greater than the [`latest_checkpoint`].
|
||||
/// Therefore you should use [`insert_checkpoint`] to insert new checkpoints before manually
|
||||
/// inserting new transactions.
|
||||
/// # Note
|
||||
///
|
||||
/// **WARNING**: If `position` is confirmed, we anchor the `tx` to the lowest checkpoint that
|
||||
/// is >= the `position`'s height. The caller is responsible for ensuring the `tx` exists in our
|
||||
/// local view of the best chain's history.
|
||||
///
|
||||
/// You must persist the changes resulting from one or more calls to this method if you need
|
||||
/// the inserted tx to be reloaded after closing the wallet.
|
||||
///
|
||||
/// [`commit`]: Self::commit
|
||||
/// [`latest_checkpoint`]: Self::latest_checkpoint
|
||||
/// [`insert_checkpoint`]: Self::insert_checkpoint
|
||||
pub fn insert_tx(
|
||||
&mut self,
|
||||
tx: Transaction,
|
||||
position: ConfirmationTime,
|
||||
) -> Result<bool, InsertTxError> {
|
||||
let (anchor, last_seen) = match position {
|
||||
ConfirmationTime::Confirmed { height, time } => {
|
||||
// anchor tx to checkpoint with lowest height that is >= position's height
|
||||
let anchor = self
|
||||
.chain
|
||||
.range(height..)
|
||||
.last()
|
||||
.ok_or(InsertTxError::ConfirmationHeightCannotBeGreaterThanTip {
|
||||
tip_height: self.chain.tip().height(),
|
||||
tx_height: height,
|
||||
})
|
||||
.map(|anchor_cp| ConfirmationTimeHeightAnchor {
|
||||
anchor_block: anchor_cp.block_id(),
|
||||
confirmation_height: height,
|
||||
confirmation_time: time,
|
||||
})?;
|
||||
|
||||
(Some(anchor), None)
|
||||
}
|
||||
ConfirmationTime::Unconfirmed { last_seen } => (None, Some(last_seen)),
|
||||
};
|
||||
|
||||
/// By default the inserted `tx` won't be considered "canonical" because it's not known
|
||||
/// whether the transaction exists in the best chain. To know whether it exists, the tx
|
||||
/// must be broadcast to the network and the wallet synced via a chain source.
|
||||
pub fn insert_tx(&mut self, tx: Transaction) -> bool {
|
||||
let mut changeset = ChangeSet::default();
|
||||
let txid = tx.compute_txid();
|
||||
changeset.append(self.indexed_graph.insert_tx(tx).into());
|
||||
if let Some(anchor) = anchor {
|
||||
changeset.append(self.indexed_graph.insert_anchor(txid, anchor).into());
|
||||
}
|
||||
if let Some(last_seen) = last_seen {
|
||||
changeset.append(self.indexed_graph.insert_seen_at(txid, last_seen).into());
|
||||
}
|
||||
|
||||
let changed = !changeset.is_empty();
|
||||
let ret = !changeset.is_empty();
|
||||
self.stage.append(changeset);
|
||||
Ok(changed)
|
||||
ret
|
||||
}
|
||||
|
||||
/// Iterate over the transactions in the wallet.
|
||||
@@ -1151,7 +1080,7 @@ impl Wallet {
|
||||
{
|
||||
self.indexed_graph
|
||||
.graph()
|
||||
.list_chain_txs(&self.chain, self.chain.tip().block_id())
|
||||
.list_canonical_txs(&self.chain, self.chain.tip().block_id())
|
||||
}
|
||||
|
||||
/// Return the balance, separated into available, trusted-pending, untrusted-pending and immature
|
||||
@@ -2326,6 +2255,14 @@ impl Wallet {
|
||||
self.indexed_graph.graph()
|
||||
}
|
||||
|
||||
/// Iterate over transactions in the wallet that are unseen and unanchored likely
|
||||
/// because they haven't been broadcast.
|
||||
pub fn unbroadcast_transactions(
|
||||
&self,
|
||||
) -> impl Iterator<Item = TxNode<'_, Arc<Transaction>, ConfirmationTimeHeightAnchor>> {
|
||||
self.tx_graph().txs_with_no_anchor_or_last_seen()
|
||||
}
|
||||
|
||||
/// Get a reference to the inner [`KeychainTxOutIndex`].
|
||||
pub fn spk_index(&self) -> &KeychainTxOutIndex<KeychainKind> {
|
||||
&self.indexed_graph.index
|
||||
@@ -2545,8 +2482,9 @@ macro_rules! floating_rate {
|
||||
macro_rules! doctest_wallet {
|
||||
() => {{
|
||||
use $crate::bitcoin::{BlockHash, Transaction, absolute, TxOut, Network, hashes::Hash};
|
||||
use $crate::chain::{ConfirmationTime, BlockId};
|
||||
use $crate::{KeychainKind, wallet::Wallet};
|
||||
use $crate::chain::{ConfirmationTimeHeightAnchor, BlockId, TxGraph};
|
||||
use $crate::wallet::{Update, Wallet};
|
||||
use $crate::KeychainKind;
|
||||
let descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/0/*)";
|
||||
let change_descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/1/*)";
|
||||
|
||||
@@ -2566,12 +2504,19 @@ macro_rules! doctest_wallet {
|
||||
script_pubkey: address.script_pubkey(),
|
||||
}],
|
||||
};
|
||||
let _ = wallet.insert_checkpoint(BlockId { height: 1_000, hash: BlockHash::all_zeros() });
|
||||
let _ = wallet.insert_tx(tx.clone(), ConfirmationTime::Confirmed {
|
||||
height: 500,
|
||||
time: 50_000
|
||||
});
|
||||
|
||||
let txid = tx.txid();
|
||||
let block = BlockId { height: 1_000, hash: BlockHash::all_zeros() };
|
||||
let _ = wallet.insert_checkpoint(block);
|
||||
let _ = wallet.insert_tx(tx);
|
||||
let anchor = ConfirmationTimeHeightAnchor {
|
||||
confirmation_height: 500,
|
||||
confirmation_time: 50_000,
|
||||
anchor_block: block,
|
||||
};
|
||||
let mut graph = TxGraph::default();
|
||||
let _ = graph.insert_anchor(txid, anchor);
|
||||
let update = Update { graph, ..Default::default() };
|
||||
wallet.apply_update(update).unwrap();
|
||||
wallet
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#![allow(unused)]
|
||||
|
||||
use bdk_chain::indexed_tx_graph::Indexer;
|
||||
use bdk_chain::{BlockId, ConfirmationTime};
|
||||
use bdk_chain::{BlockId, ConfirmationTime, ConfirmationTimeHeightAnchor, TxGraph};
|
||||
use bdk_wallet::wallet::Update;
|
||||
use bdk_wallet::{KeychainKind, LocalOutput, Wallet};
|
||||
use bitcoin::hashes::Hash;
|
||||
use bitcoin::{
|
||||
@@ -77,24 +78,26 @@ pub fn get_funded_wallet_with_change(descriptor: &str, change: &str) -> (Wallet,
|
||||
hash: BlockHash::all_zeros(),
|
||||
})
|
||||
.unwrap();
|
||||
wallet
|
||||
.insert_tx(
|
||||
tx0,
|
||||
ConfirmationTime::Confirmed {
|
||||
height: 1_000,
|
||||
time: 100,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
wallet
|
||||
.insert_tx(
|
||||
tx1.clone(),
|
||||
ConfirmationTime::Confirmed {
|
||||
height: 2_000,
|
||||
time: 200,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
wallet.insert_tx(tx0.clone());
|
||||
insert_anchor_from_conf(
|
||||
&mut wallet,
|
||||
tx0.compute_txid(),
|
||||
ConfirmationTime::Confirmed {
|
||||
height: 1_000,
|
||||
time: 100,
|
||||
},
|
||||
);
|
||||
|
||||
wallet.insert_tx(tx1.clone());
|
||||
insert_anchor_from_conf(
|
||||
&mut wallet,
|
||||
tx1.compute_txid(),
|
||||
ConfirmationTime::Confirmed {
|
||||
height: 2_000,
|
||||
time: 200,
|
||||
},
|
||||
);
|
||||
|
||||
(wallet, tx1.compute_txid())
|
||||
}
|
||||
@@ -192,3 +195,31 @@ pub fn feerate_unchecked(sat_vb: f64) -> FeeRate {
|
||||
let sat_kwu = (sat_vb * 250.0).ceil() as u64;
|
||||
FeeRate::from_sat_per_kwu(sat_kwu)
|
||||
}
|
||||
|
||||
/// Simulates confirming a tx with `txid` at the specified `position` by inserting an anchor
|
||||
/// at the lowest height in local chain that is greater or equal to `position`'s height,
|
||||
/// assuming the confirmation time matches `ConfirmationTime::Confirmed`.
|
||||
pub fn insert_anchor_from_conf(wallet: &mut Wallet, txid: Txid, position: ConfirmationTime) {
|
||||
if let ConfirmationTime::Confirmed { height, time } = position {
|
||||
// anchor tx to checkpoint with lowest height that is >= position's height
|
||||
let anchor = wallet
|
||||
.local_chain()
|
||||
.range(height..)
|
||||
.last()
|
||||
.map(|anchor_cp| ConfirmationTimeHeightAnchor {
|
||||
anchor_block: anchor_cp.block_id(),
|
||||
confirmation_height: height,
|
||||
confirmation_time: time,
|
||||
})
|
||||
.expect("confirmation height cannot be greater than tip");
|
||||
|
||||
let mut graph = TxGraph::default();
|
||||
let _ = graph.insert_anchor(txid, anchor);
|
||||
wallet
|
||||
.apply_update(Update {
|
||||
graph,
|
||||
..Default::default()
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,12 +51,19 @@ fn receive_output_to_address(
|
||||
}],
|
||||
};
|
||||
|
||||
wallet.insert_tx(tx.clone(), height).unwrap();
|
||||
let txid = tx.compute_txid();
|
||||
wallet.insert_tx(tx);
|
||||
|
||||
OutPoint {
|
||||
txid: tx.compute_txid(),
|
||||
vout: 0,
|
||||
match height {
|
||||
ConfirmationTime::Confirmed { .. } => {
|
||||
insert_anchor_from_conf(wallet, txid, height);
|
||||
}
|
||||
ConfirmationTime::Unconfirmed { last_seen } => {
|
||||
insert_seen_at(wallet, txid, last_seen);
|
||||
}
|
||||
}
|
||||
|
||||
OutPoint { txid, vout: 0 }
|
||||
}
|
||||
|
||||
fn receive_output_in_latest_block(wallet: &mut Wallet, value: u64) -> OutPoint {
|
||||
@@ -70,6 +77,18 @@ fn receive_output_in_latest_block(wallet: &mut Wallet, value: u64) -> OutPoint {
|
||||
receive_output(wallet, value, anchor)
|
||||
}
|
||||
|
||||
fn insert_seen_at(wallet: &mut Wallet, txid: Txid, seen_at: u64) {
|
||||
use bdk_wallet::wallet::Update;
|
||||
let mut graph = bdk_chain::TxGraph::default();
|
||||
let _ = graph.insert_seen_at(txid, seen_at);
|
||||
wallet
|
||||
.apply_update(Update {
|
||||
graph,
|
||||
..Default::default()
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// The satisfaction size of a P2WPKH is 112 WU =
|
||||
// 1 (elements in witness) + 1 (OP_PUSH) + 33 (pk) + 1 (OP_PUSH) + 72 (signature + sighash) + 1*4 (script len)
|
||||
// On the witness itself, we have to push once for the pk (33WU) and once for signature + sighash (72WU), for
|
||||
@@ -1188,12 +1207,16 @@ fn test_create_tx_add_utxo() {
|
||||
version: transaction::Version::non_standard(0),
|
||||
lock_time: absolute::LockTime::ZERO,
|
||||
};
|
||||
wallet
|
||||
.insert_tx(
|
||||
small_output_tx.clone(),
|
||||
ConfirmationTime::Unconfirmed { last_seen: 0 },
|
||||
)
|
||||
.unwrap();
|
||||
let txid = small_output_tx.compute_txid();
|
||||
wallet.insert_tx(small_output_tx);
|
||||
insert_anchor_from_conf(
|
||||
&mut wallet,
|
||||
txid,
|
||||
ConfirmationTime::Confirmed {
|
||||
height: 2000,
|
||||
time: 200,
|
||||
},
|
||||
);
|
||||
|
||||
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
|
||||
.unwrap()
|
||||
@@ -1201,10 +1224,7 @@ fn test_create_tx_add_utxo() {
|
||||
let mut builder = wallet.build_tx();
|
||||
builder
|
||||
.add_recipient(addr.script_pubkey(), Amount::from_sat(30_000))
|
||||
.add_utxo(OutPoint {
|
||||
txid: small_output_tx.compute_txid(),
|
||||
vout: 0,
|
||||
})
|
||||
.add_utxo(OutPoint { txid, vout: 0 })
|
||||
.unwrap();
|
||||
let psbt = builder.finish().unwrap();
|
||||
let sent_received =
|
||||
@@ -1237,13 +1257,16 @@ fn test_create_tx_manually_selected_insufficient() {
|
||||
version: transaction::Version::non_standard(0),
|
||||
lock_time: absolute::LockTime::ZERO,
|
||||
};
|
||||
|
||||
wallet
|
||||
.insert_tx(
|
||||
small_output_tx.clone(),
|
||||
ConfirmationTime::Unconfirmed { last_seen: 0 },
|
||||
)
|
||||
.unwrap();
|
||||
let txid = small_output_tx.compute_txid();
|
||||
wallet.insert_tx(small_output_tx.clone());
|
||||
insert_anchor_from_conf(
|
||||
&mut wallet,
|
||||
txid,
|
||||
ConfirmationTime::Confirmed {
|
||||
height: 2000,
|
||||
time: 200,
|
||||
},
|
||||
);
|
||||
|
||||
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
|
||||
.unwrap()
|
||||
@@ -1251,10 +1274,7 @@ fn test_create_tx_manually_selected_insufficient() {
|
||||
let mut builder = wallet.build_tx();
|
||||
builder
|
||||
.add_recipient(addr.script_pubkey(), Amount::from_sat(30_000))
|
||||
.add_utxo(OutPoint {
|
||||
txid: small_output_tx.compute_txid(),
|
||||
vout: 0,
|
||||
})
|
||||
.add_utxo(OutPoint { txid, vout: 0 })
|
||||
.unwrap()
|
||||
.manually_selected_only();
|
||||
builder.finish().unwrap();
|
||||
@@ -1289,9 +1309,9 @@ fn test_create_tx_policy_path_no_csv() {
|
||||
value: Amount::from_sat(50_000),
|
||||
}],
|
||||
};
|
||||
wallet
|
||||
.insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
|
||||
.unwrap();
|
||||
let txid = tx.compute_txid();
|
||||
wallet.insert_tx(tx);
|
||||
insert_seen_at(&mut wallet, txid, 0);
|
||||
|
||||
let external_policy = wallet.policies(KeychainKind::External).unwrap().unwrap();
|
||||
let root_id = external_policy.id;
|
||||
@@ -1659,9 +1679,8 @@ fn test_bump_fee_irreplaceable_tx() {
|
||||
|
||||
let tx = psbt.extract_tx().expect("failed to extract tx");
|
||||
let txid = tx.compute_txid();
|
||||
wallet
|
||||
.insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
|
||||
.unwrap();
|
||||
wallet.insert_tx(tx);
|
||||
insert_seen_at(&mut wallet, txid, 0);
|
||||
wallet.build_fee_bump(txid).unwrap().finish().unwrap();
|
||||
}
|
||||
|
||||
@@ -1677,15 +1696,15 @@ fn test_bump_fee_confirmed_tx() {
|
||||
let tx = psbt.extract_tx().expect("failed to extract tx");
|
||||
let txid = tx.compute_txid();
|
||||
|
||||
wallet
|
||||
.insert_tx(
|
||||
tx,
|
||||
ConfirmationTime::Confirmed {
|
||||
height: 42,
|
||||
time: 42_000,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
wallet.insert_tx(tx);
|
||||
insert_anchor_from_conf(
|
||||
&mut wallet,
|
||||
txid,
|
||||
ConfirmationTime::Confirmed {
|
||||
height: 42,
|
||||
time: 42_000,
|
||||
},
|
||||
);
|
||||
|
||||
wallet.build_fee_bump(txid).unwrap().finish().unwrap();
|
||||
}
|
||||
@@ -1704,9 +1723,8 @@ fn test_bump_fee_low_fee_rate() {
|
||||
let tx = psbt.extract_tx().expect("failed to extract tx");
|
||||
let txid = tx.compute_txid();
|
||||
|
||||
wallet
|
||||
.insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
|
||||
.unwrap();
|
||||
wallet.insert_tx(tx);
|
||||
insert_seen_at(&mut wallet, txid, 0);
|
||||
|
||||
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
||||
builder.fee_rate(FeeRate::BROADCAST_MIN);
|
||||
@@ -1737,9 +1755,8 @@ fn test_bump_fee_low_abs() {
|
||||
let tx = psbt.extract_tx().expect("failed to extract tx");
|
||||
let txid = tx.compute_txid();
|
||||
|
||||
wallet
|
||||
.insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
|
||||
.unwrap();
|
||||
wallet.insert_tx(tx);
|
||||
insert_seen_at(&mut wallet, txid, 0);
|
||||
|
||||
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
||||
builder.fee_absolute(Amount::from_sat(10));
|
||||
@@ -1759,9 +1776,8 @@ fn test_bump_fee_zero_abs() {
|
||||
|
||||
let tx = psbt.extract_tx().expect("failed to extract tx");
|
||||
let txid = tx.compute_txid();
|
||||
wallet
|
||||
.insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
|
||||
.unwrap();
|
||||
wallet.insert_tx(tx);
|
||||
insert_seen_at(&mut wallet, txid, 0);
|
||||
|
||||
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
||||
builder.fee_absolute(Amount::ZERO);
|
||||
@@ -1785,9 +1801,8 @@ fn test_bump_fee_reduce_change() {
|
||||
|
||||
let tx = psbt.extract_tx().expect("failed to extract tx");
|
||||
let txid = tx.compute_txid();
|
||||
wallet
|
||||
.insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
|
||||
.unwrap();
|
||||
wallet.insert_tx(tx);
|
||||
insert_seen_at(&mut wallet, txid, 0);
|
||||
|
||||
let feerate = FeeRate::from_sat_per_kwu(625); // 2.5 sat/vb
|
||||
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
||||
@@ -1883,9 +1898,8 @@ fn test_bump_fee_reduce_single_recipient() {
|
||||
let original_sent_received = wallet.sent_and_received(&tx);
|
||||
let original_fee = check_fee!(wallet, psbt);
|
||||
let txid = tx.compute_txid();
|
||||
wallet
|
||||
.insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
|
||||
.unwrap();
|
||||
wallet.insert_tx(tx);
|
||||
insert_seen_at(&mut wallet, txid, 0);
|
||||
|
||||
let feerate = FeeRate::from_sat_per_kwu(625); // 2.5 sat/vb
|
||||
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
||||
@@ -1931,9 +1945,8 @@ fn test_bump_fee_absolute_reduce_single_recipient() {
|
||||
let tx = psbt.extract_tx().expect("failed to extract tx");
|
||||
let original_sent_received = wallet.sent_and_received(&tx);
|
||||
let txid = tx.compute_txid();
|
||||
wallet
|
||||
.insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
|
||||
.unwrap();
|
||||
wallet.insert_tx(tx);
|
||||
insert_seen_at(&mut wallet, txid, 0);
|
||||
|
||||
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
||||
builder
|
||||
@@ -1976,15 +1989,18 @@ fn test_bump_fee_drain_wallet() {
|
||||
value: Amount::from_sat(25_000),
|
||||
}],
|
||||
};
|
||||
wallet
|
||||
.insert_tx(
|
||||
tx.clone(),
|
||||
ConfirmationTime::Confirmed {
|
||||
height: wallet.latest_checkpoint().height(),
|
||||
time: 42_000,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let txid = tx.compute_txid();
|
||||
let tip = wallet.latest_checkpoint().height();
|
||||
wallet.insert_tx(tx.clone());
|
||||
insert_anchor_from_conf(
|
||||
&mut wallet,
|
||||
txid,
|
||||
ConfirmationTime::Confirmed {
|
||||
height: tip,
|
||||
time: 42_000,
|
||||
},
|
||||
);
|
||||
|
||||
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
|
||||
.unwrap()
|
||||
.assume_checked();
|
||||
@@ -2004,9 +2020,8 @@ fn test_bump_fee_drain_wallet() {
|
||||
let original_sent_received = wallet.sent_and_received(&tx);
|
||||
|
||||
let txid = tx.compute_txid();
|
||||
wallet
|
||||
.insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
|
||||
.unwrap();
|
||||
wallet.insert_tx(tx);
|
||||
insert_seen_at(&mut wallet, txid, 0);
|
||||
assert_eq!(original_sent_received.0, Amount::from_sat(25_000));
|
||||
|
||||
// for the new feerate, it should be enough to reduce the output, but since we specify
|
||||
@@ -2041,18 +2056,17 @@ fn test_bump_fee_remove_output_manually_selected_only() {
|
||||
value: Amount::from_sat(25_000),
|
||||
}],
|
||||
};
|
||||
wallet
|
||||
.insert_tx(
|
||||
init_tx.clone(),
|
||||
wallet
|
||||
.transactions()
|
||||
.last()
|
||||
.unwrap()
|
||||
.chain_position
|
||||
.cloned()
|
||||
.into(),
|
||||
)
|
||||
.unwrap();
|
||||
let position: ConfirmationTime = wallet
|
||||
.transactions()
|
||||
.last()
|
||||
.unwrap()
|
||||
.chain_position
|
||||
.cloned()
|
||||
.into();
|
||||
|
||||
wallet.insert_tx(init_tx.clone());
|
||||
insert_anchor_from_conf(&mut wallet, init_tx.compute_txid(), position);
|
||||
|
||||
let outpoint = OutPoint {
|
||||
txid: init_tx.compute_txid(),
|
||||
vout: 0,
|
||||
@@ -2071,9 +2085,8 @@ fn test_bump_fee_remove_output_manually_selected_only() {
|
||||
let tx = psbt.extract_tx().expect("failed to extract tx");
|
||||
let original_sent_received = wallet.sent_and_received(&tx);
|
||||
let txid = tx.compute_txid();
|
||||
wallet
|
||||
.insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
|
||||
.unwrap();
|
||||
wallet.insert_tx(tx);
|
||||
insert_seen_at(&mut wallet, txid, 0);
|
||||
assert_eq!(original_sent_received.0, Amount::from_sat(25_000));
|
||||
|
||||
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
||||
@@ -2097,14 +2110,16 @@ fn test_bump_fee_add_input() {
|
||||
value: Amount::from_sat(25_000),
|
||||
}],
|
||||
};
|
||||
let pos = wallet
|
||||
let txid = init_tx.compute_txid();
|
||||
let pos: ConfirmationTime = wallet
|
||||
.transactions()
|
||||
.last()
|
||||
.unwrap()
|
||||
.chain_position
|
||||
.cloned()
|
||||
.into();
|
||||
wallet.insert_tx(init_tx, pos).unwrap();
|
||||
wallet.insert_tx(init_tx);
|
||||
insert_anchor_from_conf(&mut wallet, txid, pos);
|
||||
|
||||
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
|
||||
.unwrap()
|
||||
@@ -2117,9 +2132,8 @@ fn test_bump_fee_add_input() {
|
||||
let tx = psbt.extract_tx().expect("failed to extract tx");
|
||||
let original_details = wallet.sent_and_received(&tx);
|
||||
let txid = tx.compute_txid();
|
||||
wallet
|
||||
.insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
|
||||
.unwrap();
|
||||
wallet.insert_tx(tx);
|
||||
insert_seen_at(&mut wallet, txid, 0);
|
||||
|
||||
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
||||
builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(50));
|
||||
@@ -2174,9 +2188,8 @@ fn test_bump_fee_absolute_add_input() {
|
||||
let tx = psbt.extract_tx().expect("failed to extract tx");
|
||||
let original_sent_received = wallet.sent_and_received(&tx);
|
||||
let txid = tx.compute_txid();
|
||||
wallet
|
||||
.insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
|
||||
.unwrap();
|
||||
wallet.insert_tx(tx);
|
||||
insert_seen_at(&mut wallet, txid, 0);
|
||||
|
||||
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
||||
builder.fee_absolute(Amount::from_sat(6_000));
|
||||
@@ -2240,9 +2253,8 @@ fn test_bump_fee_no_change_add_input_and_change() {
|
||||
|
||||
let tx = psbt.extract_tx().expect("failed to extract tx");
|
||||
let txid = tx.compute_txid();
|
||||
wallet
|
||||
.insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
|
||||
.unwrap();
|
||||
wallet.insert_tx(tx);
|
||||
insert_seen_at(&mut wallet, txid, 0);
|
||||
|
||||
// Now bump the fees, the wallet should add an extra input and a change output, and leave
|
||||
// the original output untouched.
|
||||
@@ -2310,9 +2322,8 @@ fn test_bump_fee_add_input_change_dust() {
|
||||
assert_eq!(tx.input.len(), 1);
|
||||
assert_eq!(tx.output.len(), 2);
|
||||
let txid = tx.compute_txid();
|
||||
wallet
|
||||
.insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
|
||||
.unwrap();
|
||||
wallet.insert_tx(tx);
|
||||
insert_seen_at(&mut wallet, txid, 0);
|
||||
|
||||
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
||||
// We set a fee high enough that during rbf we are forced to add
|
||||
@@ -2381,9 +2392,8 @@ fn test_bump_fee_force_add_input() {
|
||||
for txin in &mut tx.input {
|
||||
txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
|
||||
}
|
||||
wallet
|
||||
.insert_tx(tx.clone(), ConfirmationTime::Unconfirmed { last_seen: 0 })
|
||||
.unwrap();
|
||||
wallet.insert_tx(tx.clone());
|
||||
insert_seen_at(&mut wallet, txid, 0);
|
||||
// the new fee_rate is low enough that just reducing the change would be fine, but we force
|
||||
// the addition of an extra input with `add_utxo()`
|
||||
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
||||
@@ -2448,9 +2458,8 @@ fn test_bump_fee_absolute_force_add_input() {
|
||||
for txin in &mut tx.input {
|
||||
txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
|
||||
}
|
||||
wallet
|
||||
.insert_tx(tx.clone(), ConfirmationTime::Unconfirmed { last_seen: 0 })
|
||||
.unwrap();
|
||||
wallet.insert_tx(tx.clone());
|
||||
insert_seen_at(&mut wallet, txid, 0);
|
||||
|
||||
// the new fee_rate is low enough that just reducing the change would be fine, but we force
|
||||
// the addition of an extra input with `add_utxo()`
|
||||
@@ -2527,9 +2536,8 @@ fn test_bump_fee_unconfirmed_inputs_only() {
|
||||
for txin in &mut tx.input {
|
||||
txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
|
||||
}
|
||||
wallet
|
||||
.insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
|
||||
.unwrap();
|
||||
wallet.insert_tx(tx);
|
||||
insert_seen_at(&mut wallet, txid, 0);
|
||||
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
||||
builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(25));
|
||||
builder.finish().unwrap();
|
||||
@@ -2560,9 +2568,8 @@ fn test_bump_fee_unconfirmed_input() {
|
||||
for txin in &mut tx.input {
|
||||
txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
|
||||
}
|
||||
wallet
|
||||
.insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
|
||||
.unwrap();
|
||||
wallet.insert_tx(tx);
|
||||
insert_seen_at(&mut wallet, txid, 0);
|
||||
|
||||
let mut builder = wallet.build_fee_bump(txid).unwrap();
|
||||
builder
|
||||
@@ -3755,15 +3762,16 @@ fn test_spend_coinbase() {
|
||||
value: Amount::from_sat(25_000),
|
||||
}],
|
||||
};
|
||||
wallet
|
||||
.insert_tx(
|
||||
coinbase_tx,
|
||||
ConfirmationTime::Confirmed {
|
||||
height: confirmation_height,
|
||||
time: 30_000,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let txid = coinbase_tx.compute_txid();
|
||||
wallet.insert_tx(coinbase_tx);
|
||||
insert_anchor_from_conf(
|
||||
&mut wallet,
|
||||
txid,
|
||||
ConfirmationTime::Confirmed {
|
||||
height: confirmation_height,
|
||||
time: 30_000,
|
||||
},
|
||||
);
|
||||
|
||||
let not_yet_mature_time = confirmation_height + COINBASE_MATURITY - 1;
|
||||
let maturity_time = confirmation_height + COINBASE_MATURITY;
|
||||
@@ -4092,3 +4100,45 @@ fn test_thread_safety() {
|
||||
fn thread_safe<T: Send + Sync>() {}
|
||||
thread_safe::<Wallet>(); // compiles only if true
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insert_tx_balance_and_utxos() {
|
||||
// creating many txs has no effect on the wallet's available utxos
|
||||
let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig_xprv());
|
||||
let addr = Address::from_str("bcrt1qc6fweuf4xjvz4x3gx3t9e0fh4hvqyu2qw4wvxm")
|
||||
.unwrap()
|
||||
.assume_checked();
|
||||
|
||||
let unspent: Vec<_> = wallet.list_unspent().collect();
|
||||
assert!(!unspent.is_empty());
|
||||
|
||||
let balance = wallet.balance().total();
|
||||
let fee = Amount::from_sat(143);
|
||||
let amt = balance - fee;
|
||||
|
||||
for _ in 0..3 {
|
||||
let mut builder = wallet.build_tx();
|
||||
builder.add_recipient(addr.script_pubkey(), amt);
|
||||
let mut psbt = builder.finish().unwrap();
|
||||
assert!(wallet.sign(&mut psbt, SignOptions::default()).unwrap());
|
||||
let tx = psbt.extract_tx().unwrap();
|
||||
let _ = wallet.insert_tx(tx);
|
||||
}
|
||||
assert_eq!(wallet.list_unspent().collect::<Vec<_>>(), unspent);
|
||||
assert_eq!(wallet.balance().confirmed, balance);
|
||||
|
||||
// manually setting a tx last_seen will consume the wallet's available utxos
|
||||
let addr = Address::from_str("bcrt1qfjg5lv3dvc9az8patec8fjddrs4aqtauadnagr")
|
||||
.unwrap()
|
||||
.assume_checked();
|
||||
let mut builder = wallet.build_tx();
|
||||
builder.add_recipient(addr.script_pubkey(), amt);
|
||||
let mut psbt = builder.finish().unwrap();
|
||||
assert!(wallet.sign(&mut psbt, SignOptions::default()).unwrap());
|
||||
let tx = psbt.extract_tx().unwrap();
|
||||
let txid = tx.compute_txid();
|
||||
let _ = wallet.insert_tx(tx);
|
||||
insert_seen_at(&mut wallet, txid, 2);
|
||||
assert!(wallet.list_unspent().next().is_none());
|
||||
assert_eq!(wallet.balance().total().to_sat(), 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user