Skip to content

Commit

Permalink
Merge #1736: Sqlite - allow persisting anchor without tx
Browse files Browse the repository at this point in the history
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
  • Loading branch information
evanlinjin committed Nov 28, 2024
2 parents 721eb54 + 18f5f3f commit bf42562
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 1 deletion.
76 changes: 75 additions & 1 deletion crates/chain/src/rusqlite_impl.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Module for stuff
//! Support for persisting `bdk_chain` structures to SQLite using [`rusqlite`].
use crate::*;
use core::str::FromStr;
Expand Down Expand Up @@ -376,8 +376,15 @@ where
"REPLACE INTO {}(txid, block_height, block_hash, anchor) VALUES(:txid, :block_height, :block_hash, jsonb(:anchor))",
Self::ANCHORS_TABLE_NAME,
))?;
let mut statement_txid = db_tx.prepare_cached(&format!(
"INSERT OR IGNORE INTO {}(txid) VALUES(:txid)",
Self::TXS_TABLE_NAME,
))?;
for (anchor, txid) in &self.anchors {
let anchor_block = anchor.anchor_block();
statement_txid.execute(named_params! {
":txid": Impl(*txid)
})?;
statement.execute(named_params! {
":txid": Impl(*txid),
":block_height": anchor_block.height,
Expand Down Expand Up @@ -529,3 +536,70 @@ impl keychain_txout::ChangeSet {
Ok(())
}
}

#[cfg(test)]
mod test {
use super::*;

use bdk_testenv::{anyhow, hash};
use bitcoin::{absolute, transaction, TxIn, TxOut};

#[test]
fn can_persist_anchors_and_txs_independently() -> anyhow::Result<()> {
type ChangeSet = tx_graph::ChangeSet<BlockId>;
let mut conn = rusqlite::Connection::open_in_memory()?;

// init tables
{
let db_tx = conn.transaction()?;
ChangeSet::init_sqlite_tables(&db_tx)?;
db_tx.commit()?;
}

let tx = bitcoin::Transaction {
version: transaction::Version::TWO,
lock_time: absolute::LockTime::ZERO,
input: vec![TxIn::default()],
output: vec![TxOut::NULL],
};
let tx = Arc::new(tx);
let txid = tx.compute_txid();
let anchor = BlockId {
height: 21,
hash: hash!("anchor"),
};

// First persist the anchor
{
let changeset = ChangeSet {
anchors: [(anchor, txid)].into(),
..Default::default()
};
let db_tx = conn.transaction()?;
changeset.persist_to_sqlite(&db_tx)?;
db_tx.commit()?;
}

// Now persist the tx
{
let changeset = ChangeSet {
txs: [tx.clone()].into(),
..Default::default()
};
let db_tx = conn.transaction()?;
changeset.persist_to_sqlite(&db_tx)?;
db_tx.commit()?;
}

// Loading changeset from sqlite should succeed
{
let db_tx = conn.transaction()?;
let changeset = ChangeSet::from_sqlite(&db_tx)?;
db_tx.commit()?;
assert!(changeset.txs.contains(&tx));
assert!(changeset.anchors.contains(&(anchor, txid)));
}

Ok(())
}
}
31 changes: 31 additions & 0 deletions crates/chain/tests/test_tx_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1055,6 +1055,37 @@ fn transactions_inserted_into_tx_graph_are_not_canonical_until_they_have_an_anch
assert!(graph.txs_with_no_anchor_or_last_seen().next().is_none());
}

#[test]
fn insert_anchor_without_tx() {
let mut graph = TxGraph::<BlockId>::default();

let tx = new_tx(21);
let txid = tx.compute_txid();

let anchor = BlockId {
height: 100,
hash: hash!("A"),
};

// insert anchor with no corresponding tx
let mut changeset = graph.insert_anchor(txid, anchor);
assert!(changeset.anchors.contains(&(anchor, txid)));
// recover from changeset
let mut recovered = TxGraph::default();
recovered.apply_changeset(changeset.clone());
assert_eq!(recovered, graph);

// now insert tx
let tx = Arc::new(tx);
let graph_changeset = graph.insert_tx(tx.clone());
assert!(graph_changeset.txs.contains(&tx));
changeset.merge(graph_changeset);
// recover from changeset again
let mut recovered = TxGraph::default();
recovered.apply_changeset(changeset);
assert_eq!(recovered, graph);
}

#[test]
/// The `map_anchors` allow a caller to pass a function to reconstruct the [`TxGraph`] with any [`Anchor`],
/// even though the function is non-deterministic.
Expand Down

0 comments on commit bf42562

Please sign in to comment.