db: an interface to query coins by their outpoints

This commit is contained in:
Antoine Poinsot 2022-08-31 12:02:59 +02:00
parent dab40527ae
commit 46320d5fc0
No known key found for this signature in database
GPG Key ID: E13FC145CD3F4304
3 changed files with 106 additions and 25 deletions

View File

@ -62,6 +62,39 @@ pub trait DatabaseConnection {
/// Mark a set of coins as being spent by a specified txid.
fn spend_coins<'a>(&mut self, outpoints: &[(bitcoin::OutPoint, bitcoin::Txid)]);
/// Get specific coins from the database.
fn coins_by_outpoints(
&mut self,
outpoints: &[bitcoin::OutPoint],
) -> HashMap<bitcoin::OutPoint, Coin>;
}
// FIXME: if possible, avoid reallocating.
fn db_coins_into_coins(db_coins: Vec<DbCoin>) -> HashMap<bitcoin::OutPoint, Coin> {
db_coins
.into_iter()
.map(|db_coin| {
let DbCoin {
outpoint,
block_height,
amount,
derivation_index,
spend_txid,
..
} = db_coin;
(
outpoint,
Coin {
outpoint,
block_height,
amount,
derivation_index,
spend_txid,
},
)
})
.collect()
}
impl DatabaseConnection for SqliteConn {
@ -93,30 +126,7 @@ impl DatabaseConnection for SqliteConn {
}
fn unspent_coins(&mut self) -> HashMap<bitcoin::OutPoint, Coin> {
// FIXME: if possible, avoid reallocating.
self.unspent_coins()
.into_iter()
.map(|db_coin| {
let DbCoin {
outpoint,
block_height,
amount,
derivation_index,
spend_txid,
..
} = db_coin;
(
outpoint,
Coin {
outpoint,
block_height,
amount,
derivation_index,
spend_txid,
},
)
})
.collect()
db_coins_into_coins(self.unspent_coins())
}
fn new_unspent_coins<'a>(&mut self, coins: &[Coin]) {
@ -138,6 +148,13 @@ impl DatabaseConnection for SqliteConn {
self.db_address(address)
.map(|db_addr| db_addr.derivation_index)
}
fn coins_by_outpoints(
&mut self,
outpoints: &[bitcoin::OutPoint],
) -> HashMap<bitcoin::OutPoint, Coin> {
db_coins_into_coins(self.db_coins(outpoints))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]

View File

@ -23,7 +23,7 @@ use crate::{
use std::{convert::TryInto, fmt, io, path};
use miniscript::bitcoin::{self, secp256k1};
use miniscript::bitcoin::{self, hashes::hex::ToHex, secp256k1};
const DB_VERSION: i64 = 0;
@ -330,6 +330,28 @@ impl SqliteConn {
.expect("Db must not fail")
.pop()
}
pub fn db_coins(&mut self, outpoints: &[bitcoin::OutPoint]) -> Vec<DbCoin> {
// SELECT * FROM coins WHERE (txid, vout) IN ((txidA, voutA), (txidB, voutB));
let mut query = "SELECT * FROM coins WHERE (txid, vout) IN (VALUES ".to_string();
for (i, outpoint) in outpoints.iter().enumerate() {
// NOTE: the txid is not stored as little-endian. Convert it to vec first.
query += &format!(
"(x'{}', {})",
&outpoint.txid.to_vec().to_hex(),
outpoint.vout
);
if i != outpoints.len() - 1 {
query += ", ";
}
}
query += ")";
db_query(&mut self.conn, &query, rusqlite::params![], |row| {
row.try_into()
})
.expect("Db must not fail")
}
}
#[cfg(test)]
@ -462,6 +484,11 @@ mod tests {
conn.new_unspent_coins(&[coin_a.clone()]); // On 1.48, arrays aren't IntoIterator
assert_eq!(conn.unspent_coins()[0].outpoint, coin_a.outpoint);
// We can query it by its outpoint
let coins = conn.db_coins(&[coin_a.outpoint]);
assert_eq!(coins.len(), 1);
assert_eq!(coins[0].outpoint, coin_a.outpoint);
// Add a second one, we'll get both.
let coin_b = Coin {
outpoint: bitcoin::OutPoint::from_str(
@ -482,6 +509,24 @@ mod tests {
assert!(outpoints.contains(&coin_a.outpoint));
assert!(outpoints.contains(&coin_b.outpoint));
// We can query both by their outpoints
let coins = conn.db_coins(&[coin_a.outpoint]);
assert_eq!(coins.len(), 1);
assert_eq!(coins[0].outpoint, coin_a.outpoint);
let coins = conn.db_coins(&[coin_b.outpoint]);
assert_eq!(coins.len(), 1);
assert_eq!(coins[0].outpoint, coin_b.outpoint);
let coins = conn.db_coins(&[coin_a.outpoint, coin_b.outpoint]);
assert_eq!(coins.len(), 2);
assert!(coins
.iter()
.find(|c| c.outpoint == coin_a.outpoint)
.is_some());
assert!(coins
.iter()
.find(|c| c.outpoint == coin_b.outpoint)
.is_some());
// Now if we confirm one, it'll be marked as such.
let height = 174500;
conn.confirm_coins(&[(coin_a.outpoint, height)]);
@ -501,6 +546,10 @@ mod tests {
.collect();
assert!(!outpoints.contains(&coin_a.outpoint));
assert!(outpoints.contains(&coin_b.outpoint));
// Both are still in DB
let coins = conn.db_coins(&[coin_a.outpoint, coin_b.outpoint]);
assert_eq!(coins.len(), 2);
}
fs::remove_dir_all(&tmp_dir).unwrap();

View File

@ -137,6 +137,21 @@ impl DatabaseConnection for DummyDbConn {
fn derivation_index_by_address(&mut self, _: &bitcoin::Address) -> Option<bip32::ChildNumber> {
None
}
fn coins_by_outpoints(
&mut self,
outpoints: &[bitcoin::OutPoint],
) -> HashMap<bitcoin::OutPoint, Coin> {
// Very inefficient but hey
self.db
.read()
.unwrap()
.coins
.clone()
.into_iter()
.filter(|(op, _)| outpoints.contains(&op))
.collect()
}
}
pub struct DummyMinisafe {