db: interface to upsert a Spend PSBT and query it by txid

I initially wanted to have a bridge table between the 'coins' and
'spend_transactions' table as we do in revaultd. But let's not optimize
to early, we'll see if/when we need it.
This commit is contained in:
Antoine Poinsot 2022-09-15 11:33:43 +02:00
parent 7d015bcf43
commit bafcadf398
No known key found for this signature in database
GPG Key ID: E13FC145CD3F4304
4 changed files with 105 additions and 5 deletions

View File

@ -13,7 +13,10 @@ use crate::{
use std::{collections::HashMap, sync};
use miniscript::bitcoin::{self, secp256k1, util::bip32};
use miniscript::bitcoin::{
self, secp256k1,
util::{bip32, psbt::PartiallySignedTransaction as Psbt},
};
pub trait DatabaseInterface: Send {
fn connection(&self) -> Box<dyn DatabaseConnection>;
@ -68,6 +71,11 @@ pub trait DatabaseConnection {
&mut self,
outpoints: &[bitcoin::OutPoint],
) -> HashMap<bitcoin::OutPoint, Coin>;
fn spend_tx(&mut self, txid: &bitcoin::Txid) -> Option<Psbt>;
/// Insert a new Spend transaction or replace an existing one.
fn store_spend(&mut self, psbt: &Psbt);
}
// FIXME: if possible, avoid reallocating.
@ -155,6 +163,14 @@ impl DatabaseConnection for SqliteConn {
) -> HashMap<bitcoin::OutPoint, Coin> {
db_coins_into_coins(self.db_coins(outpoints))
}
fn spend_tx(&mut self, txid: &bitcoin::Txid) -> Option<Psbt> {
self.db_spend(txid).map(|db_spend| db_spend.psbt)
}
fn store_spend(&mut self, psbt: &Psbt) {
self.store_spend(psbt)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]

View File

@ -13,7 +13,7 @@ use crate::{
bitcoin::BlockChainTip,
database::{
sqlite::{
schema::{DbAddress, DbCoin, DbTip, DbWallet},
schema::{DbAddress, DbCoin, DbSpendTransaction, DbTip, DbWallet},
utils::{create_fresh_db, db_exec, db_query, db_tx_query, LOOK_AHEAD_LIMIT},
},
Coin,
@ -23,7 +23,10 @@ use crate::{
use std::{convert::TryInto, fmt, io, path};
use miniscript::bitcoin::{self, hashes::hex::ToHex, secp256k1};
use miniscript::bitcoin::{
self, consensus::encode, hashes::hex::ToHex, secp256k1,
util::psbt::PartiallySignedTransaction as Psbt,
};
const DB_VERSION: i64 = 0;
@ -352,6 +355,33 @@ impl SqliteConn {
})
.expect("Db must not fail")
}
pub fn db_spend(&mut self, txid: &bitcoin::Txid) -> Option<DbSpendTransaction> {
db_query(
&mut self.conn,
"SELECT * FROM spend_transactions WHERE txid = ?1",
rusqlite::params![txid.to_vec()],
|row| row.try_into(),
)
.expect("Db must not fail")
.pop()
}
/// Insert a new Spend transaction or replace an existing one.
pub fn store_spend(&mut self, psbt: &Psbt) {
let txid = psbt.global.unsigned_tx.txid().to_vec();
let psbt = encode::serialize(psbt);
db_exec(&mut self.conn, |db_tx| {
db_tx.execute(
"INSERT into spend_transactions (psbt, txid) VALUES (?1, ?2) \
ON CONFLICT DO UPDATE SET psbt=excluded.psbt",
rusqlite::params![psbt, txid],
)?;
Ok(())
})
.expect("Db must not fail");
}
}
#[cfg(test)]

View File

@ -2,7 +2,11 @@ use crate::descriptors::InheritanceDescriptor;
use std::{convert::TryFrom, str::FromStr};
use miniscript::bitcoin::{self, consensus::encode, util::bip32};
use miniscript::bitcoin::{
self,
consensus::encode,
util::{bip32, psbt::PartiallySignedTransaction as Psbt},
};
pub const SCHEMA: &str = "\
CREATE TABLE version (
@ -49,6 +53,13 @@ CREATE TABLE addresses (
address TEXT NOT NULL UNIQUE,
derivation_index INTEGER NOT NULL UNIQUE
);
/* Transactions we created that spend some of our coins. */
CREATE TABLE spend_transactions (
id INTEGER PRIMARY KEY NOT NULL,
psbt BLOB UNIQUE NOT NULL,
txid BLOB UNIQUE NOT NULL
);
";
/// A row in the "tip" table.
@ -186,3 +197,28 @@ impl TryFrom<&rusqlite::Row<'_>> for DbAddress {
})
}
}
/// A row in the "spend_transactions" table
#[derive(Debug, Clone, PartialEq)]
pub struct DbSpendTransaction {
pub id: i64,
pub psbt: Psbt,
pub txid: bitcoin::Txid,
}
impl TryFrom<&rusqlite::Row<'_>> for DbSpendTransaction {
type Error = rusqlite::Error;
fn try_from(row: &rusqlite::Row) -> Result<Self, Self::Error> {
let id: i64 = row.get(0)?;
let psbt: Vec<u8> = row.get(1)?;
let psbt: Psbt = encode::deserialize(&psbt).expect("We only store valid PSBTs");
let txid: Vec<u8> = row.get(2)?;
let txid: bitcoin::Txid = encode::deserialize(&txid).expect("We only store valid txids");
assert_eq!(txid, psbt.global.unsigned_tx.txid());
Ok(DbSpendTransaction { id, psbt, txid })
}
}

View File

@ -8,7 +8,10 @@ use crate::{
use std::{collections::HashMap, env, fs, io, path, process, str::FromStr, sync, thread, time};
use miniscript::{
bitcoin::{self, secp256k1, util::bip32},
bitcoin::{
self, secp256k1,
util::{bip32, psbt::PartiallySignedTransaction as Psbt},
},
descriptor,
};
@ -58,6 +61,7 @@ pub struct DummyDb {
curr_index: bip32::ChildNumber,
curr_tip: Option<BlockChainTip>,
coins: HashMap<bitcoin::OutPoint, Coin>,
spend_txs: HashMap<bitcoin::Txid, Psbt>,
}
impl DummyDb {
@ -66,6 +70,7 @@ impl DummyDb {
curr_index: 0.into(),
curr_tip: None,
coins: HashMap::new(),
spend_txs: HashMap::new(),
}
}
}
@ -152,6 +157,19 @@ impl DatabaseConnection for DummyDbConn {
.filter(|(op, _)| outpoints.contains(&op))
.collect()
}
fn store_spend(&mut self, psbt: &Psbt) {
let txid = psbt.global.unsigned_tx.txid();
self.db
.write()
.unwrap()
.spend_txs
.insert(txid, psbt.clone());
}
fn spend_tx(&mut self, txid: &bitcoin::Txid) -> Option<Psbt> {
self.db.read().unwrap().spend_txs.get(txid).cloned()
}
}
pub struct DummyMinisafe {