Skip to content

Commit cb66f00

Browse files
committed
Merge #1966: fix(chain): persist first_seen
b27a019 fix(chain): persist `first_seen` (Wei Chen) Pull request description: Fixes #1965. ### Description Adds missing persistence for `first_seen`, which was not included in #1950. ### Changelog notice - Adds `first_seen` column to the `bdk_txs` table via schema v3 migration. - Updates `from_sqlite()` and `persist_to_sqlite()` to handle `first_seen`. - Updates the v0-to-v3 migration test to verify compatibility with older schemas. ### 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 +nightly fmt` and `cargo clippy` before committing #### New Features: * [x] I've added tests for the new feature * [x] I've added docs for the new feature #### Bugfixes: * [ ] 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: evanlinjin: ACK b27a019 Tree-SHA512: a8c4cd930e20f7bdf1a02fc3155b5df9f1627676fe10a2d77ea856e71e45f783bba1bb8cf4eceb8dba71c345e7985a9e091002966cec147871e6672c0e2ac5c4
2 parents ff7d703 + b27a019 commit cb66f00

File tree

1 file changed

+74
-5
lines changed

1 file changed

+74
-5
lines changed

crates/chain/src/rusqlite_impl.rs

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -272,12 +272,25 @@ impl tx_graph::ChangeSet<ConfirmationBlockTime> {
272272
)
273273
}
274274

275+
/// Get v3 of sqlite [tx_graph::ChangeSet] schema
276+
pub fn schema_v3() -> String {
277+
format!(
278+
"ALTER TABLE {} ADD COLUMN first_seen INTEGER",
279+
Self::TXS_TABLE_NAME,
280+
)
281+
}
282+
275283
/// Initialize sqlite tables.
276284
pub fn init_sqlite_tables(db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
277285
migrate_schema(
278286
db_tx,
279287
Self::SCHEMA_NAME,
280-
&[&Self::schema_v0(), &Self::schema_v1(), &Self::schema_v2()],
288+
&[
289+
&Self::schema_v0(),
290+
&Self::schema_v1(),
291+
&Self::schema_v2(),
292+
&Self::schema_v3(),
293+
],
281294
)
282295
}
283296

@@ -288,22 +301,26 @@ impl tx_graph::ChangeSet<ConfirmationBlockTime> {
288301
let mut changeset = Self::default();
289302

290303
let mut statement = db_tx.prepare(&format!(
291-
"SELECT txid, raw_tx, last_seen, last_evicted FROM {}",
304+
"SELECT txid, raw_tx, first_seen, last_seen, last_evicted FROM {}",
292305
Self::TXS_TABLE_NAME,
293306
))?;
294307
let row_iter = statement.query_map([], |row| {
295308
Ok((
296309
row.get::<_, Impl<bitcoin::Txid>>("txid")?,
297310
row.get::<_, Option<Impl<bitcoin::Transaction>>>("raw_tx")?,
311+
row.get::<_, Option<u64>>("first_seen")?,
298312
row.get::<_, Option<u64>>("last_seen")?,
299313
row.get::<_, Option<u64>>("last_evicted")?,
300314
))
301315
})?;
302316
for row in row_iter {
303-
let (Impl(txid), tx, last_seen, last_evicted) = row?;
317+
let (Impl(txid), tx, first_seen, last_seen, last_evicted) = row?;
304318
if let Some(Impl(tx)) = tx {
305319
changeset.txs.insert(Arc::new(tx));
306320
}
321+
if let Some(first_seen) = first_seen {
322+
changeset.first_seen.insert(txid, first_seen);
323+
}
307324
if let Some(last_seen) = last_seen {
308325
changeset.last_seen.insert(txid, last_seen);
309326
}
@@ -376,6 +393,18 @@ impl tx_graph::ChangeSet<ConfirmationBlockTime> {
376393
})?;
377394
}
378395

396+
let mut statement = db_tx.prepare_cached(&format!(
397+
"INSERT INTO {}(txid, first_seen) VALUES(:txid, :first_seen) ON CONFLICT(txid) DO UPDATE SET first_seen=:first_seen",
398+
Self::TXS_TABLE_NAME,
399+
))?;
400+
for (&txid, &first_seen) in &self.first_seen {
401+
let checked_time = first_seen.to_sql()?;
402+
statement.execute(named_params! {
403+
":txid": Impl(txid),
404+
":first_seen": Some(checked_time),
405+
})?;
406+
}
407+
379408
let mut statement = db_tx
380409
.prepare_cached(&format!(
381410
"INSERT INTO {}(txid, last_seen) VALUES(:txid, :last_seen) ON CONFLICT(txid) DO UPDATE SET last_seen=:last_seen",
@@ -653,7 +682,7 @@ mod test {
653682
}
654683

655684
#[test]
656-
fn v0_to_v2_schema_migration_is_backward_compatible() -> anyhow::Result<()> {
685+
fn v0_to_v3_schema_migration_is_backward_compatible() -> anyhow::Result<()> {
657686
type ChangeSet = tx_graph::ChangeSet<ConfirmationBlockTime>;
658687
let mut conn = rusqlite::Connection::open_in_memory()?;
659688

@@ -722,7 +751,7 @@ mod test {
722751
}
723752
}
724753

725-
// Apply v1 & v2 sqlite schema to tables with data
754+
// Apply v1, v2, v3 sqlite schema to tables with data
726755
{
727756
let db_tx = conn.transaction()?;
728757
migrate_schema(
@@ -732,6 +761,7 @@ mod test {
732761
&ChangeSet::schema_v0(),
733762
&ChangeSet::schema_v1(),
734763
&ChangeSet::schema_v2(),
764+
&ChangeSet::schema_v3(),
735765
],
736766
)?;
737767
db_tx.commit()?;
@@ -748,6 +778,45 @@ mod test {
748778
Ok(())
749779
}
750780

781+
#[test]
782+
fn can_persist_first_seen() -> anyhow::Result<()> {
783+
use bitcoin::hashes::Hash;
784+
785+
type ChangeSet = tx_graph::ChangeSet<ConfirmationBlockTime>;
786+
let mut conn = rusqlite::Connection::open_in_memory()?;
787+
788+
// Init tables
789+
{
790+
let db_tx = conn.transaction()?;
791+
ChangeSet::init_sqlite_tables(&db_tx)?;
792+
db_tx.commit()?;
793+
}
794+
795+
let txid = bitcoin::Txid::all_zeros();
796+
let first_seen = 100;
797+
798+
// Persist `first_seen`
799+
{
800+
let changeset = ChangeSet {
801+
first_seen: [(txid, first_seen)].into(),
802+
..Default::default()
803+
};
804+
let db_tx = conn.transaction()?;
805+
changeset.persist_to_sqlite(&db_tx)?;
806+
db_tx.commit()?;
807+
}
808+
809+
// Load from sqlite should succeed
810+
{
811+
let db_tx = conn.transaction()?;
812+
let changeset = ChangeSet::from_sqlite(&db_tx)?;
813+
db_tx.commit()?;
814+
assert_eq!(changeset.first_seen.get(&txid), Some(&first_seen));
815+
}
816+
817+
Ok(())
818+
}
819+
751820
#[test]
752821
fn can_persist_last_evicted() -> anyhow::Result<()> {
753822
use bitcoin::hashes::Hash;

0 commit comments

Comments
 (0)