Skip to content

Commit 3dbd1b4

Browse files
committed
WIP
1 parent 2fceda8 commit 3dbd1b4

File tree

8 files changed

+166
-22
lines changed

8 files changed

+166
-22
lines changed

examples/example_wallet_electrum/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ edition = "2021"
66
[dependencies]
77
bdk_wallet = { path = "../../wallet", features = ["file_store"] }
88
#bdk_electrum = { version = "0.21" }
9-
bdk_electrum = { git = "https://github.com/bitcoindevkit/bdk", rev = "b70758652d1c819c88d92765905d09e26b951ee2" }
9+
bdk_electrum = { git = "https://github.com/evanlinjin/bdk", branch = "superimposed_canonicalization" }
1010
anyhow = "1"

examples/example_wallet_esplora_async/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ edition = "2021"
88
[dependencies]
99
bdk_wallet = { path = "../../wallet", features = ["rusqlite"] }
1010
#bdk_esplora = { version = "0.20", features = ["async-https", "tokio"] }
11-
bdk_esplora = { git = "https://github.com/bitcoindevkit/bdk", rev = "b70758652d1c819c88d92765905d09e26b951ee2", features = ["async-https", "tokio"] }
12-
bdk_testenv = { git = "https://github.com/bitcoindevkit/bdk", rev = "b70758652d1c819c88d92765905d09e26b951ee2" }
11+
bdk_esplora = { git = "https://github.com/evanlinjin/bdk", branch = "superimposed_canonicalization", features = ["async-https", "tokio"] }
12+
bdk_testenv = { git = "https://github.com/evanlinjin/bdk", branch = "superimposed_canonicalization" }
1313
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
1414
anyhow = "1"

examples/example_wallet_esplora_blocking/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ publish = false
99
[dependencies]
1010
bdk_wallet = { path = "../../wallet", features = ["file_store"] }
1111
#bdk_esplora = { version = "0.20", features = ["blocking"] }
12-
bdk_esplora = { git = "https://github.com/bitcoindevkit/bdk", rev = "b70758652d1c819c88d92765905d09e26b951ee2", features = ["blocking"] }
12+
bdk_esplora = { git = "https://github.com/evanlinjin/bdk", branch = "superimposed_canonicalization", features = ["blocking"] }
1313
anyhow = "1"

examples/example_wallet_rpc/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ edition = "2021"
88
[dependencies]
99
bdk_wallet = { path = "../../wallet", features = ["file_store"] }
1010
#bdk_bitcoind_rpc = { version = "0.18" }
11-
bdk_bitcoind_rpc = { git = "https://github.com/bitcoindevkit/bdk", rev = "b70758652d1c819c88d92765905d09e26b951ee2" }
11+
bdk_bitcoind_rpc = { git = "https://github.com/evanlinjin/bdk", branch = "superimposed_canonicalization" }
1212

1313
anyhow = "1"
1414
clap = { version = "4.5.17", features = ["derive", "env"] }

wallet/Cargo.toml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,14 @@ serde_json = { version = "^1.0" }
2828
bip39 = { version = "2.0", optional = true }
2929

3030
[dependencies.bdk_chain]
31-
git = "https://github.com/bitcoindevkit/bdk"
32-
rev = "b70758652d1c819c88d92765905d09e26b951ee2"
31+
git = "https://github.com/evanlinjin/bdk"
32+
branch = "superimposed_canonicalization"
3333
default-features = false
3434
features = ["miniscript", "serde"]
3535

3636
[dependencies.bdk_file_store]
37-
git = "https://github.com/bitcoindevkit/bdk"
38-
rev = "b70758652d1c819c88d92765905d09e26b951ee2"
37+
git = "https://github.com/evanlinjin/bdk"
38+
branch = "superimposed_canonicalization"
3939
optional = true
4040

4141
[features]
@@ -59,8 +59,8 @@ anyhow = "1"
5959
rand = "^0.8"
6060

6161
[dev-dependencies.bdk_chain]
62-
git = "https://github.com/bitcoindevkit/bdk"
63-
rev = "b70758652d1c819c88d92765905d09e26b951ee2"
62+
git = "https://github.com/evanlinjin/bdk"
63+
branch = "superimposed_canonicalization"
6464
features = ["rusqlite"]
6565

6666
[package.metadata.docs.rs]

wallet/src/wallet/changeset.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use bdk_chain::{
33
};
44
use miniscript::{Descriptor, DescriptorPublicKey};
55

6+
use super::unbroadcasted;
7+
68
type IndexedTxGraphChangeSet =
79
indexed_tx_graph::ChangeSet<ConfirmationBlockTime, keychain_txout::ChangeSet>;
810

@@ -21,6 +23,8 @@ pub struct ChangeSet {
2123
pub tx_graph: tx_graph::ChangeSet<ConfirmationBlockTime>,
2224
/// Changes to [`KeychainTxOutIndex`](keychain_txout::KeychainTxOutIndex).
2325
pub indexer: keychain_txout::ChangeSet,
26+
/// Changes to [`Unbroadcasted`](crate::unbroadcasted::Unbroadcasted).
27+
pub unbroadcasted: unbroadcasted::ChangeSet,
2428
}
2529

2630
impl Merge for ChangeSet {

wallet/src/wallet/mod.rs

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use alloc::{
1919
sync::Arc,
2020
vec::Vec,
2121
};
22+
use chain::CanonicalizationParams;
2223
use core::{cmp::Ordering, fmt, mem, ops::Deref};
2324

2425
use bdk_chain::{
@@ -57,6 +58,7 @@ mod params;
5758
mod persisted;
5859
pub mod signer;
5960
pub mod tx_builder;
61+
pub mod unbroadcasted;
6062
pub(crate) mod utils;
6163

6264
use crate::collections::{BTreeMap, HashMap, HashSet};
@@ -80,6 +82,7 @@ pub use bdk_chain::Balance;
8082
pub use changeset::ChangeSet;
8183
pub use params::*;
8284
pub use persisted::*;
85+
pub use unbroadcasted::Unbroadcasted;
8386
pub use utils::IsDust;
8487

8588
/// A Bitcoin wallet
@@ -105,6 +108,7 @@ pub struct Wallet {
105108
change_signers: Arc<SignersContainer>,
106109
chain: LocalChain,
107110
indexed_graph: IndexedTxGraph<ConfirmationBlockTime, KeychainTxOutIndex<KeychainKind>>,
111+
unbroadcasted: Unbroadcasted,
108112
stage: ChangeSet,
109113
network: Network,
110114
secp: SecpCtx,
@@ -405,13 +409,15 @@ impl Wallet {
405409
let change_descriptor = index.get_descriptor(KeychainKind::Internal).cloned();
406410
let indexed_graph = IndexedTxGraph::new(index);
407411
let indexed_graph_changeset = indexed_graph.initial_changeset();
412+
let unbroadcasted = Unbroadcasted::default();
408413

409414
let stage = ChangeSet {
410415
descriptor,
411416
change_descriptor,
412417
local_chain: chain_changeset,
413418
tx_graph: indexed_graph_changeset.tx_graph,
414419
indexer: indexed_graph_changeset.indexer,
420+
unbroadcasted: Default::default(),
415421
network: Some(network),
416422
};
417423

@@ -421,6 +427,7 @@ impl Wallet {
421427
network,
422428
chain,
423429
indexed_graph,
430+
unbroadcasted,
424431
stage,
425432
secp,
426433
})
@@ -605,13 +612,16 @@ impl Wallet {
605612
indexed_graph.apply_changeset(changeset.indexer.into());
606613
indexed_graph.apply_changeset(changeset.tx_graph.into());
607614

615+
let unbroadcasted = Unbroadcasted::from_changeset(changeset.unbroadcasted);
616+
608617
let stage = ChangeSet::default();
609618

610619
Ok(Some(Wallet {
611620
signers,
612621
change_signers,
613622
chain,
614623
indexed_graph,
624+
unbroadcasted,
615625
stage,
616626
network,
617627
secp,
@@ -809,13 +819,31 @@ impl Wallet {
809819
self.indexed_graph.index.index_of_spk(spk).cloned()
810820
}
811821

822+
/// Modifies canonicalization by assuming that all unbroadcasted transactions are canonical.
823+
pub fn include_unbroadcasted_canonicalization_params(&self) -> CanonicalizationParams {
824+
CanonicalizationParams {
825+
assume_canonical: self.unbroadcasted.txids().collect(),
826+
}
827+
}
828+
829+
/// TODO: Return this in the order in which it needs to be broadcasted.
830+
pub fn unbroadcasted(&self) -> impl Iterator<Item = (Txid, Arc<Transaction>)> + '_ {
831+
self.unbroadcasted
832+
.txids()
833+
.filter_map(|txid| self.tx_graph().get_tx(txid).map(|tx| (txid, tx)))
834+
}
835+
812836
/// Return the list of unspent outputs of this wallet
813-
pub fn list_unspent(&self) -> impl Iterator<Item = LocalOutput> + '_ {
837+
pub fn list_unspent(
838+
&self,
839+
params: CanonicalizationParams,
840+
) -> impl Iterator<Item = LocalOutput> + '_ {
814841
self.indexed_graph
815842
.graph()
816843
.filter_chain_unspents(
817844
&self.chain,
818845
self.chain.tip().block_id(),
846+
params,
819847
self.indexed_graph.index.outpoints().iter().cloned(),
820848
)
821849
.map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo))
@@ -824,12 +852,16 @@ impl Wallet {
824852
/// List all relevant outputs (includes both spent and unspent, confirmed and unconfirmed).
825853
///
826854
/// To list only unspent outputs (UTXOs), use [`Wallet::list_unspent`] instead.
827-
pub fn list_output(&self) -> impl Iterator<Item = LocalOutput> + '_ {
855+
pub fn list_output(
856+
&self,
857+
params: CanonicalizationParams,
858+
) -> impl Iterator<Item = LocalOutput> + '_ {
828859
self.indexed_graph
829860
.graph()
830861
.filter_chain_txouts(
831862
&self.chain,
832863
self.chain.tip().block_id(),
864+
params,
833865
self.indexed_graph.index.outpoints().iter().cloned(),
834866
)
835867
.map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo))
@@ -876,13 +908,14 @@ impl Wallet {
876908

877909
/// Returns the utxo owned by this wallet corresponding to `outpoint` if it exists in the
878910
/// wallet's database.
879-
pub fn get_utxo(&self, op: OutPoint) -> Option<LocalOutput> {
911+
pub fn get_utxo(&self, params: CanonicalizationParams, op: OutPoint) -> Option<LocalOutput> {
880912
let ((keychain, index), _) = self.indexed_graph.index.txout(op)?;
881913
self.indexed_graph
882914
.graph()
883915
.filter_chain_unspents(
884916
&self.chain,
885917
self.chain.tip().block_id(),
918+
params,
886919
core::iter::once(((), op)),
887920
)
888921
.map(|(_, full_txo)| new_local_utxo(keychain, index, full_txo))
@@ -1055,10 +1088,10 @@ impl Wallet {
10551088
/// ```
10561089
///
10571090
/// [`Anchor`]: bdk_chain::Anchor
1058-
pub fn get_tx(&self, txid: Txid) -> Option<WalletTx> {
1091+
pub fn get_tx(&self, params: CanonicalizationParams, txid: Txid) -> Option<WalletTx> {
10591092
let graph = self.indexed_graph.graph();
10601093
graph
1061-
.list_canonical_txs(&self.chain, self.chain.tip().block_id())
1094+
.list_canonical_txs(&self.chain, self.chain.tip().block_id(), params)
10621095
.find(|tx| tx.tx_node.txid == txid)
10631096
}
10641097

@@ -1073,11 +1106,14 @@ impl Wallet {
10731106
///
10741107
/// To iterate over all canonical transactions, including those that are irrelevant, use
10751108
/// [`TxGraph::list_canonical_txs`].
1076-
pub fn transactions(&self) -> impl Iterator<Item = WalletTx> + '_ {
1109+
pub fn transactions(
1110+
&self,
1111+
params: CanonicalizationParams,
1112+
) -> impl Iterator<Item = WalletTx> + '_ {
10771113
let tx_graph = self.indexed_graph.graph();
10781114
let tx_index = &self.indexed_graph.index;
10791115
tx_graph
1080-
.list_canonical_txs(&self.chain, self.chain.tip().block_id())
1116+
.list_canonical_txs(&self.chain, self.chain.tip().block_id(), params)
10811117
.filter(|c_tx| tx_index.is_tx_relevant(&c_tx.tx_node.tx))
10821118
}
10831119

@@ -1097,21 +1133,26 @@ impl Wallet {
10971133
/// wallet.transactions_sort_by(|tx1, tx2| tx2.chain_position.cmp(&tx1.chain_position));
10981134
/// # Ok::<(), anyhow::Error>(())
10991135
/// ```
1100-
pub fn transactions_sort_by<F>(&self, compare: F) -> Vec<WalletTx>
1136+
pub fn transactions_sort_by<F>(
1137+
&self,
1138+
params: CanonicalizationParams,
1139+
compare: F,
1140+
) -> Vec<WalletTx>
11011141
where
11021142
F: FnMut(&WalletTx, &WalletTx) -> Ordering,
11031143
{
1104-
let mut txs: Vec<WalletTx> = self.transactions().collect();
1144+
let mut txs: Vec<WalletTx> = self.transactions(params).collect();
11051145
txs.sort_unstable_by(compare);
11061146
txs
11071147
}
11081148

11091149
/// Return the balance, separated into available, trusted-pending, untrusted-pending and immature
11101150
/// values.
1111-
pub fn balance(&self) -> Balance {
1151+
pub fn balance(&self, params: CanonicalizationParams) -> Balance {
11121152
self.indexed_graph.graph().balance(
11131153
&self.chain,
11141154
self.chain.tip().block_id(),
1155+
params,
11151156
self.indexed_graph.index.outpoints().iter().cloned(),
11161157
|&(k, _), _| k == KeychainKind::Internal,
11171158
)
@@ -1590,7 +1631,7 @@ impl Wallet {
15901631
let txout_index = &self.indexed_graph.index;
15911632
let chain_tip = self.chain.tip().block_id();
15921633
let chain_positions = graph
1593-
.list_canonical_txs(&self.chain, chain_tip)
1634+
.list_canonical_txs(&self.chain, chain_tip, CanonicalizationParams::default())
15941635
.map(|canon_tx| (canon_tx.tx_node.txid, canon_tx.chain_position))
15951636
.collect::<HashMap<Txid, _>>();
15961637

wallet/src/wallet/unbroadcasted.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
use crate::collections::BTreeMap;
2+
3+
use crate::collections::HashSet;
4+
5+
use crate::collections::hash_map;
6+
use crate::collections::HashMap;
7+
8+
use bitcoin::Txid;
9+
use chain::CanonicalizationParams;
10+
use chain::Merge;
11+
12+
/// An ordered unbroadcasted list.
13+
///
14+
/// It is ordered in case of RBF txs.
15+
#[derive(Debug, Clone, Default)]
16+
pub struct Unbroadcasted {
17+
txs: HashMap<Txid, u64>,
18+
order: HashSet<(u64, Txid)>,
19+
next_seq: u64,
20+
}
21+
22+
#[must_use]
23+
#[derive(Debug, Clone, Default, PartialEq, serde::Deserialize, serde::Serialize)]
24+
pub struct ChangeSet {
25+
/// Add or remove?
26+
pub txs: BTreeMap<(u64, Txid), bool>,
27+
}
28+
29+
impl Merge for ChangeSet {
30+
fn merge(&mut self, other: Self) {
31+
self.txs.merge(other.txs);
32+
}
33+
34+
fn is_empty(&self) -> bool {
35+
self.txs.is_empty()
36+
}
37+
}
38+
39+
impl Unbroadcasted {
40+
pub fn from_changeset(changeset: ChangeSet) -> Self {
41+
let mut out = Unbroadcasted::default();
42+
out.apply_changeset(changeset);
43+
out
44+
}
45+
46+
pub fn apply_changeset(&mut self, changeset: ChangeSet) {
47+
for ((_, txid), is_add) in changeset.txs {
48+
if is_add {
49+
let _ = self.insert(txid);
50+
} else {
51+
let _ = self.remove(txid);
52+
}
53+
}
54+
}
55+
56+
/// Reinserting will bump the tx's seq to `next_seq`.
57+
pub fn insert(&mut self, txid: Txid) -> ChangeSet {
58+
let seq = self.next_seq;
59+
self.next_seq += 1;
60+
61+
match self.txs.entry(txid) {
62+
hash_map::Entry::Occupied(mut entry) => {
63+
// remove stuff
64+
let entry_seq = entry.get_mut();
65+
self.order.remove(&(*entry_seq, txid));
66+
self.order.insert((seq, txid));
67+
*entry_seq = seq;
68+
}
69+
hash_map::Entry::Vacant(entry) => {
70+
entry.insert(seq);
71+
self.order.insert((seq, txid));
72+
}
73+
}
74+
75+
let mut changeset = ChangeSet::default();
76+
changeset.txs.insert((seq, txid), true);
77+
changeset
78+
}
79+
80+
pub fn remove(&mut self, txid: Txid) -> ChangeSet {
81+
let mut changeset = ChangeSet::default();
82+
if let Some(seq) = self.txs.remove(&txid) {
83+
self.order.remove(&(seq, txid));
84+
85+
let seq = self.next_seq;
86+
self.next_seq += 1;
87+
88+
changeset.txs.insert((seq, txid), false);
89+
}
90+
changeset
91+
}
92+
93+
/// Txids ordered by precedence.
94+
///
95+
/// Transactions with greater precedence will appear later in this list.
96+
pub fn txids(&self) -> impl ExactSizeIterator<Item = Txid> + '_ {
97+
self.order.iter().map(|&(_, txid)| txid)
98+
}
99+
}

0 commit comments

Comments
 (0)