Skip to content

Commit bf42562

Browse files
committed
Merge #1736: Sqlite - allow persisting anchor without tx
18f5f3f test(sqlite): test persisting anchors and txs independently (valued mammal) 297ff34 test(chain): add test `insert_anchor_without_tx` (valued mammal) e69d10e doc(chain): document module `rusqlite_impl` (valued mammal) 8fa899b fix(chain): Sqlite - allow persisting anchor without tx (志宇) Pull request description: ### Description Previously, we may error when we insert an anchor where the txid being anchored has no corresponding tx. closes #1712 replaces #961 ### Notes to the reviewers <!-- In this section you can include notes directed to the reviewers, like explaining why some parts of the PR were done in a specific way --> ### Changelog notice <!-- Notice the release manager should include in the release tag message changelog --> <!-- See https://keepachangelog.com/en/1.0.0/ for examples --> ### 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 #### New Features: * [ ] I've added tests for the new feature * [ ] 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 18f5f3f ValuedMammal: ACK 18f5f3f Tree-SHA512: 7343eff857016f684cfb9f7af57f7be56ba36c70c36b8b4303159636f79ad5cc49a6f94354443323c9aa05c31ec7d43c22b038d9e41361596c3a346bd6a94b10
2 parents 721eb54 + 18f5f3f commit bf42562

File tree

2 files changed

+106
-1
lines changed

2 files changed

+106
-1
lines changed

crates/chain/src/rusqlite_impl.rs

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//! Module for stuff
1+
//! Support for persisting `bdk_chain` structures to SQLite using [`rusqlite`].
22
33
use crate::*;
44
use core::str::FromStr;
@@ -376,8 +376,15 @@ where
376376
"REPLACE INTO {}(txid, block_height, block_hash, anchor) VALUES(:txid, :block_height, :block_hash, jsonb(:anchor))",
377377
Self::ANCHORS_TABLE_NAME,
378378
))?;
379+
let mut statement_txid = db_tx.prepare_cached(&format!(
380+
"INSERT OR IGNORE INTO {}(txid) VALUES(:txid)",
381+
Self::TXS_TABLE_NAME,
382+
))?;
379383
for (anchor, txid) in &self.anchors {
380384
let anchor_block = anchor.anchor_block();
385+
statement_txid.execute(named_params! {
386+
":txid": Impl(*txid)
387+
})?;
381388
statement.execute(named_params! {
382389
":txid": Impl(*txid),
383390
":block_height": anchor_block.height,
@@ -529,3 +536,70 @@ impl keychain_txout::ChangeSet {
529536
Ok(())
530537
}
531538
}
539+
540+
#[cfg(test)]
541+
mod test {
542+
use super::*;
543+
544+
use bdk_testenv::{anyhow, hash};
545+
use bitcoin::{absolute, transaction, TxIn, TxOut};
546+
547+
#[test]
548+
fn can_persist_anchors_and_txs_independently() -> anyhow::Result<()> {
549+
type ChangeSet = tx_graph::ChangeSet<BlockId>;
550+
let mut conn = rusqlite::Connection::open_in_memory()?;
551+
552+
// init tables
553+
{
554+
let db_tx = conn.transaction()?;
555+
ChangeSet::init_sqlite_tables(&db_tx)?;
556+
db_tx.commit()?;
557+
}
558+
559+
let tx = bitcoin::Transaction {
560+
version: transaction::Version::TWO,
561+
lock_time: absolute::LockTime::ZERO,
562+
input: vec![TxIn::default()],
563+
output: vec![TxOut::NULL],
564+
};
565+
let tx = Arc::new(tx);
566+
let txid = tx.compute_txid();
567+
let anchor = BlockId {
568+
height: 21,
569+
hash: hash!("anchor"),
570+
};
571+
572+
// First persist the anchor
573+
{
574+
let changeset = ChangeSet {
575+
anchors: [(anchor, txid)].into(),
576+
..Default::default()
577+
};
578+
let db_tx = conn.transaction()?;
579+
changeset.persist_to_sqlite(&db_tx)?;
580+
db_tx.commit()?;
581+
}
582+
583+
// Now persist the tx
584+
{
585+
let changeset = ChangeSet {
586+
txs: [tx.clone()].into(),
587+
..Default::default()
588+
};
589+
let db_tx = conn.transaction()?;
590+
changeset.persist_to_sqlite(&db_tx)?;
591+
db_tx.commit()?;
592+
}
593+
594+
// Loading changeset from sqlite should succeed
595+
{
596+
let db_tx = conn.transaction()?;
597+
let changeset = ChangeSet::from_sqlite(&db_tx)?;
598+
db_tx.commit()?;
599+
assert!(changeset.txs.contains(&tx));
600+
assert!(changeset.anchors.contains(&(anchor, txid)));
601+
}
602+
603+
Ok(())
604+
}
605+
}

crates/chain/tests/test_tx_graph.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,6 +1055,37 @@ fn transactions_inserted_into_tx_graph_are_not_canonical_until_they_have_an_anch
10551055
assert!(graph.txs_with_no_anchor_or_last_seen().next().is_none());
10561056
}
10571057

1058+
#[test]
1059+
fn insert_anchor_without_tx() {
1060+
let mut graph = TxGraph::<BlockId>::default();
1061+
1062+
let tx = new_tx(21);
1063+
let txid = tx.compute_txid();
1064+
1065+
let anchor = BlockId {
1066+
height: 100,
1067+
hash: hash!("A"),
1068+
};
1069+
1070+
// insert anchor with no corresponding tx
1071+
let mut changeset = graph.insert_anchor(txid, anchor);
1072+
assert!(changeset.anchors.contains(&(anchor, txid)));
1073+
// recover from changeset
1074+
let mut recovered = TxGraph::default();
1075+
recovered.apply_changeset(changeset.clone());
1076+
assert_eq!(recovered, graph);
1077+
1078+
// now insert tx
1079+
let tx = Arc::new(tx);
1080+
let graph_changeset = graph.insert_tx(tx.clone());
1081+
assert!(graph_changeset.txs.contains(&tx));
1082+
changeset.merge(graph_changeset);
1083+
// recover from changeset again
1084+
let mut recovered = TxGraph::default();
1085+
recovered.apply_changeset(changeset);
1086+
assert_eq!(recovered, graph);
1087+
}
1088+
10581089
#[test]
10591090
/// The `map_anchors` allow a caller to pass a function to reconstruct the [`TxGraph`] with any [`Anchor`],
10601091
/// even though the function is non-deterministic.

0 commit comments

Comments
 (0)