bdk/crates/chain/tests/common/tx_template.rs
志宇 6b43001951
feat!: Rework sqlite, changesets, persistence and wallet-construction
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 back some safety and convenience when persisting
our types. Working with changeset directly (as we were doing before) can
be cumbersome.

* Intoduce `struct Persisted<T>` which wraps a type `T` which stores
  staged changes to it. This adds safety when creating and or loading
  `T` from db.
* `struct Persisted<T>` methods, `create`, `load` and `persist`, are
  avaliable if `trait PersistWith<Db>` is implemented for `T`. `Db`
  represents the database connection and `PersistWith` should be
  implemented per database-type.
* For async, we have `trait PersistedAsyncWith<Db>`.
* `Wallet` has impls of `PersistedWith<rusqlite::Connection>`,
  `PersistedWith<rusqlite::Transaction>` and
  `PersistedWith<bdk_file_store::Store>` by default.

Rework wallet-construction: Before, we had multiple methods for loading
and creating with different input-counts so it would be unwieldly to add
more parameters in the future. This also makes it difficult to impl
`PersistWith` (which has a single method for `load` that takes in
`PersistWith::LoadParams` and a single method for `create` that takes in
`PersistWith::CreateParams`).

* Introduce a builder pattern when constructing a `Wallet`. For loading
  from persistence or `ChangeSet`, we have `LoadParams`. For creating a
  new wallet, we have `CreateParams`.
2024-07-18 03:25:41 +00:00

138 lines
5.1 KiB
Rust

#![cfg(feature = "miniscript")]
use rand::distributions::{Alphanumeric, DistString};
use std::collections::HashMap;
use bdk_chain::{spk_txout::SpkTxOutIndex, tx_graph::TxGraph, Anchor};
use bitcoin::{
locktime::absolute::LockTime, secp256k1::Secp256k1, transaction, Amount, OutPoint, ScriptBuf,
Sequence, Transaction, TxIn, TxOut, Txid, Witness,
};
use miniscript::Descriptor;
/// Template for creating a transaction in `TxGraph`.
///
/// The incentive for transaction templates is to create a transaction history in a simple manner to
/// avoid having to explicitly hash previous transactions to form previous outpoints of later
/// transactions.
#[derive(Clone, Copy, Default)]
pub struct TxTemplate<'a, A> {
/// Uniquely identifies the transaction, before it can have a txid.
pub tx_name: &'a str,
pub inputs: &'a [TxInTemplate<'a>],
pub outputs: &'a [TxOutTemplate],
pub anchors: &'a [A],
pub last_seen: Option<u64>,
}
#[allow(dead_code)]
pub enum TxInTemplate<'a> {
/// This will give a random txid and vout.
Bogus,
/// This is used for coinbase transactions because they do not have previous outputs.
Coinbase,
/// Contains the `tx_name` and `vout` that we are spending. The rule is that we must only spend
/// from tx of a previous `TxTemplate`.
PrevTx(&'a str, usize),
}
pub struct TxOutTemplate {
pub value: u64,
pub spk_index: Option<u32>, // some = get spk from SpkTxOutIndex, none = random spk
}
#[allow(unused)]
impl TxOutTemplate {
pub fn new(value: u64, spk_index: Option<u32>) -> Self {
TxOutTemplate { value, spk_index }
}
}
#[allow(dead_code)]
pub fn init_graph<'a, A: Anchor + Clone + 'a>(
tx_templates: impl IntoIterator<Item = &'a TxTemplate<'a, A>>,
) -> (TxGraph<A>, SpkTxOutIndex<u32>, HashMap<&'a str, Txid>) {
let (descriptor, _) =
Descriptor::parse_descriptor(&Secp256k1::signing_only(), super::DESCRIPTORS[2]).unwrap();
let mut graph = TxGraph::<A>::default();
let mut spk_index = SpkTxOutIndex::default();
(0..10).for_each(|index| {
spk_index.insert_spk(
index,
descriptor
.at_derivation_index(index)
.unwrap()
.script_pubkey(),
);
});
let mut tx_ids = HashMap::<&'a str, Txid>::new();
for (bogus_txin_vout, tx_tmp) in tx_templates.into_iter().enumerate() {
let tx = Transaction {
version: transaction::Version::non_standard(0),
lock_time: LockTime::ZERO,
input: tx_tmp
.inputs
.iter()
.map(|input| match input {
TxInTemplate::Bogus => TxIn {
previous_output: OutPoint::new(
bitcoin::hashes::Hash::hash(
Alphanumeric
.sample_string(&mut rand::thread_rng(), 20)
.as_bytes(),
),
bogus_txin_vout as u32,
),
script_sig: ScriptBuf::new(),
sequence: Sequence::default(),
witness: Witness::new(),
},
TxInTemplate::Coinbase => TxIn {
previous_output: OutPoint::null(),
script_sig: ScriptBuf::new(),
sequence: Sequence::MAX,
witness: Witness::new(),
},
TxInTemplate::PrevTx(prev_name, prev_vout) => {
let prev_txid = tx_ids.get(prev_name).expect(
"txin template must spend from tx of template that comes before",
);
TxIn {
previous_output: OutPoint::new(*prev_txid, *prev_vout as _),
script_sig: ScriptBuf::new(),
sequence: Sequence::default(),
witness: Witness::new(),
}
}
})
.collect(),
output: tx_tmp
.outputs
.iter()
.map(|output| match &output.spk_index {
None => TxOut {
value: Amount::from_sat(output.value),
script_pubkey: ScriptBuf::new(),
},
Some(index) => TxOut {
value: Amount::from_sat(output.value),
script_pubkey: spk_index.spk_at_index(index).unwrap().to_owned(),
},
})
.collect(),
};
tx_ids.insert(tx_tmp.tx_name, tx.compute_txid());
spk_index.scan(&tx);
let _ = graph.insert_tx(tx.clone());
for anchor in tx_tmp.anchors.iter() {
let _ = graph.insert_anchor(tx.compute_txid(), anchor.clone());
}
let _ = graph.insert_seen_at(tx.compute_txid(), tx_tmp.last_seen.unwrap_or(0));
}
(graph, spk_index, tx_ids)
}