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:
parent
7d015bcf43
commit
bafcadf398
@ -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)]
|
||||
|
||||
@ -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)]
|
||||
|
||||
@ -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 })
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user