• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

bitcoindevkit / bdk / 10033071784

22 Jul 2024 01:26AM UTC coverage: 81.876% (-1.6%) from 83.434%
10033071784

push

github

notmandatory
Merge bitcoindevkit/bdk#1514: refactor(wallet)!: rework persistence, changeset, and construction

64eb57634 chore(wallet): Fix ChangeSet::merge (LLFourn)
8875c92ec chore(wallet): Fix descriptor mismatch error keychain (LLFourn)
2cf07d686 refactor(chain,wallet)!: move rusqlite things into it's own file (志宇)
93f9b83e2 chore(chain): rm unused `sqlite` types (志宇)
892b97d44 feat(chain,wallet)!: Change persist-traits to be "safer" (志宇)
3aed4cf17 test(wallet): ensure checks work when loading wallet (志宇)
af4ee0fa4 refactor(wallet)!: Make `bdk_wallet::ChangeSet` non-exhaustive (志宇)
22d02ed3d feat!: improve wallet building methods (志宇)
eb73f0659 refactor!: move `WalletChangeSet` to `bdk_wallet` and fix import paths (志宇)
6b4300195 feat!: Rework sqlite, changesets, persistence and wallet-construction (志宇)

Pull request description:

  Closes #1496
  Closes #1498
  Closes #1500

  ### Description

  Rework sqlite: Instead of only supported one schema (defined in `bdk_sqlite`), we have a schema per changeset type for more flexiblity.

  * rm `bdk_sqlite` crate (as we don't need `bdk_sqlite::Store` anymore).
  * add `sqlite` feature on `bdk_chain` which adds methods on each changeset type for initializing tables, loading the changeset and writing.

  Rework changesets: Some callers may want to use `KeychainTxOutIndex` where `K` may change per descriptor on every run. So we only want to persist the last revealed indices by `DescriptorId` (which uniquely-ish identifies the descriptor).

  * rm `keychain_added` field from `keychain_txout`'s changeset.
  * Add `keychain_added` to `CombinedChangeSet` (which is renamed to `WalletChangeSet`).

  Rework persistence: add... (continued)

817 of 1088 new or added lines in 12 files covered. (75.09%)

33 existing lines in 7 files now uncovered.

10946 of 13369 relevant lines covered (81.88%)

16192.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.13
/crates/wallet/src/wallet/changeset.rs
1
use bdk_chain::{
2
    indexed_tx_graph, keychain_txout, local_chain, tx_graph, ConfirmationBlockTime, Merge,
3
};
4
use miniscript::{Descriptor, DescriptorPublicKey};
5

6
type IndexedTxGraphChangeSet =
7
    indexed_tx_graph::ChangeSet<ConfirmationBlockTime, keychain_txout::ChangeSet>;
8

9
/// A changeset for [`Wallet`](crate::Wallet).
10
#[derive(Default, Debug, Clone, PartialEq, serde::Deserialize, serde::Serialize)]
88✔
11
#[non_exhaustive]
12
pub struct ChangeSet {
13
    /// Descriptor for recipient addresses.
14
    pub descriptor: Option<Descriptor<DescriptorPublicKey>>,
15
    /// Descriptor for change addresses.
16
    pub change_descriptor: Option<Descriptor<DescriptorPublicKey>>,
17
    /// Stores the network type of the transaction data.
18
    pub network: Option<bitcoin::Network>,
19
    /// Changes to the [`LocalChain`](local_chain::LocalChain).
20
    pub local_chain: local_chain::ChangeSet,
21
    /// Changes to [`TxGraph`](tx_graph::TxGraph).
22
    pub tx_graph: tx_graph::ChangeSet<ConfirmationBlockTime>,
23
    /// Changes to [`KeychainTxOutIndex`](keychain_txout::KeychainTxOutIndex).
24
    pub indexer: keychain_txout::ChangeSet,
25
}
26

27
impl Merge for ChangeSet {
28
    /// Merge another [`ChangeSet`] into itself.
29
    fn merge(&mut self, other: Self) {
17,202✔
30
        if other.descriptor.is_some() {
17,202✔
NEW
31
            debug_assert!(
×
NEW
32
                self.descriptor.is_none() || self.descriptor == other.descriptor,
×
NEW
33
                "descriptor must never change"
×
34
            );
NEW
35
            self.descriptor = other.descriptor;
×
36
        }
17,202✔
37
        if other.change_descriptor.is_some() {
17,202✔
NEW
38
            debug_assert!(
×
NEW
39
                self.change_descriptor.is_none()
×
NEW
40
                    || self.change_descriptor == other.change_descriptor,
×
NEW
41
                "change descriptor must never change"
×
42
            );
NEW
43
            self.change_descriptor = other.change_descriptor;
×
44
        }
17,202✔
45
        if other.network.is_some() {
17,202✔
NEW
46
            debug_assert!(
×
NEW
47
                self.network.is_none() || self.network == other.network,
×
NEW
48
                "network must never change"
×
49
            );
NEW
50
            self.network = other.network;
×
51
        }
17,202✔
52

53
        Merge::merge(&mut self.local_chain, other.local_chain);
17,202✔
54
        Merge::merge(&mut self.tx_graph, other.tx_graph);
17,202✔
55
        Merge::merge(&mut self.indexer, other.indexer);
17,202✔
56
    }
17,202✔
57

58
    fn is_empty(&self) -> bool {
2,662✔
59
        self.descriptor.is_none()
2,662✔
60
            && self.change_descriptor.is_none()
2,486✔
61
            && self.network.is_none()
2,486✔
62
            && self.local_chain.is_empty()
2,486✔
63
            && self.tx_graph.is_empty()
2,486✔
64
            && self.indexer.is_empty()
56✔
65
    }
2,662✔
66
}
67

68
#[cfg(feature = "rusqlite")]
69
impl ChangeSet {
70
    /// Schema name for wallet.
71
    pub const WALLET_SCHEMA_NAME: &'static str = "bdk_wallet";
72
    /// Name of table to store wallet descriptors and network.
73
    pub const WALLET_TABLE_NAME: &'static str = "bdk_wallet";
74

75
    /// Initialize sqlite tables for wallet schema & table.
76
    fn init_wallet_sqlite_tables(
72✔
77
        db_tx: &chain::rusqlite::Transaction,
72✔
78
    ) -> chain::rusqlite::Result<()> {
72✔
79
        let schema_v0: &[&str] = &[&format!(
72✔
80
            "CREATE TABLE {} ( \
72✔
81
                id INTEGER PRIMARY KEY NOT NULL CHECK (id = 0), \
72✔
82
                descriptor TEXT, \
72✔
83
                change_descriptor TEXT, \
72✔
84
                network TEXT \
72✔
85
                ) STRICT;",
72✔
86
            Self::WALLET_TABLE_NAME,
72✔
87
        )];
72✔
88
        crate::rusqlite_impl::migrate_schema(db_tx, Self::WALLET_SCHEMA_NAME, &[schema_v0])
72✔
89
    }
72✔
90

91
    /// Recover a [`ChangeSet`] from sqlite database.
92
    pub fn from_sqlite(db_tx: &chain::rusqlite::Transaction) -> chain::rusqlite::Result<Self> {
40✔
93
        Self::init_wallet_sqlite_tables(db_tx)?;
40✔
94
        use chain::rusqlite::OptionalExtension;
95
        use chain::Impl;
96
        use miniscript::{Descriptor, DescriptorPublicKey};
97

98
        let mut changeset = Self::default();
40✔
99

100
        let mut wallet_statement = db_tx.prepare(&format!(
40✔
101
            "SELECT descriptor, change_descriptor, network FROM {}",
40✔
102
            Self::WALLET_TABLE_NAME,
40✔
103
        ))?;
40✔
104
        let row = wallet_statement
40✔
105
            .query_row([], |row| {
40✔
106
                Ok((
40✔
107
                    row.get::<_, Impl<Descriptor<DescriptorPublicKey>>>("descriptor")?,
40✔
108
                    row.get::<_, Impl<Descriptor<DescriptorPublicKey>>>("change_descriptor")?,
40✔
109
                    row.get::<_, Impl<bitcoin::Network>>("network")?,
40✔
110
                ))
111
            })
40✔
112
            .optional()?;
40✔
113
        if let Some((Impl(desc), Impl(change_desc), Impl(network))) = row {
40✔
114
            changeset.descriptor = Some(desc);
40✔
115
            changeset.change_descriptor = Some(change_desc);
40✔
116
            changeset.network = Some(network);
40✔
117
        }
40✔
118

119
        changeset.local_chain = local_chain::ChangeSet::from_sqlite(db_tx)?;
40✔
120
        changeset.tx_graph = tx_graph::ChangeSet::<_>::from_sqlite(db_tx)?;
40✔
121
        changeset.indexer = keychain_txout::ChangeSet::from_sqlite(db_tx)?;
40✔
122

123
        Ok(changeset)
40✔
124
    }
40✔
125

126
    /// Persist [`ChangeSet`] to sqlite database.
127
    pub fn persist_to_sqlite(
32✔
128
        &self,
32✔
129
        db_tx: &chain::rusqlite::Transaction,
32✔
130
    ) -> chain::rusqlite::Result<()> {
32✔
131
        Self::init_wallet_sqlite_tables(db_tx)?;
32✔
132
        use chain::rusqlite::named_params;
133
        use chain::Impl;
134

135
        let mut descriptor_statement = db_tx.prepare_cached(&format!(
32✔
136
            "INSERT INTO {}(id, descriptor) VALUES(:id, :descriptor) ON CONFLICT(id) DO UPDATE SET descriptor=:descriptor",
32✔
137
            Self::WALLET_TABLE_NAME,
32✔
138
        ))?;
32✔
139
        if let Some(descriptor) = &self.descriptor {
32✔
140
            descriptor_statement.execute(named_params! {
24✔
141
                ":id": 0,
24✔
142
                ":descriptor": Impl(descriptor.clone()),
24✔
143
            })?;
24✔
144
        }
8✔
145

146
        let mut change_descriptor_statement = db_tx.prepare_cached(&format!(
32✔
147
            "INSERT INTO {}(id, change_descriptor) VALUES(:id, :change_descriptor) ON CONFLICT(id) DO UPDATE SET change_descriptor=:change_descriptor",
32✔
148
            Self::WALLET_TABLE_NAME,
32✔
149
        ))?;
32✔
150
        if let Some(change_descriptor) = &self.change_descriptor {
32✔
151
            change_descriptor_statement.execute(named_params! {
24✔
152
                ":id": 0,
24✔
153
                ":change_descriptor": Impl(change_descriptor.clone()),
24✔
154
            })?;
24✔
155
        }
8✔
156

157
        let mut network_statement = db_tx.prepare_cached(&format!(
32✔
158
            "INSERT INTO {}(id, network) VALUES(:id, :network) ON CONFLICT(id) DO UPDATE SET network=:network",
32✔
159
            Self::WALLET_TABLE_NAME,
32✔
160
        ))?;
32✔
161
        if let Some(network) = self.network {
32✔
162
            network_statement.execute(named_params! {
24✔
163
                ":id": 0,
24✔
164
                ":network": Impl(network),
24✔
165
            })?;
24✔
166
        }
8✔
167

168
        self.local_chain.persist_to_sqlite(db_tx)?;
32✔
169
        self.tx_graph.persist_to_sqlite(db_tx)?;
32✔
170
        self.indexer.persist_to_sqlite(db_tx)?;
32✔
171
        Ok(())
32✔
172
    }
32✔
173
}
174

175
impl From<local_chain::ChangeSet> for ChangeSet {
176
    fn from(chain: local_chain::ChangeSet) -> Self {
3,260✔
177
        Self {
3,260✔
178
            local_chain: chain,
3,260✔
179
            ..Default::default()
3,260✔
180
        }
3,260✔
181
    }
3,260✔
182
}
183

184
impl From<IndexedTxGraphChangeSet> for ChangeSet {
185
    fn from(indexed_tx_graph: IndexedTxGraphChangeSet) -> Self {
5,772✔
186
        Self {
5,772✔
187
            tx_graph: indexed_tx_graph.tx_graph,
5,772✔
188
            indexer: indexed_tx_graph.indexer,
5,772✔
189
            ..Default::default()
5,772✔
190
        }
5,772✔
191
    }
5,772✔
192
}
193

194
impl From<tx_graph::ChangeSet<ConfirmationBlockTime>> for ChangeSet {
NEW
195
    fn from(tx_graph: tx_graph::ChangeSet<ConfirmationBlockTime>) -> Self {
×
NEW
196
        Self {
×
NEW
197
            tx_graph,
×
NEW
198
            ..Default::default()
×
NEW
199
        }
×
NEW
200
    }
×
201
}
202

203
impl From<keychain_txout::ChangeSet> for ChangeSet {
204
    fn from(indexer: keychain_txout::ChangeSet) -> Self {
3,294✔
205
        Self {
3,294✔
206
            indexer,
3,294✔
207
            ..Default::default()
3,294✔
208
        }
3,294✔
209
    }
3,294✔
210
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc