db: an interface to query coins by their outpoints
This commit is contained in:
parent
dab40527ae
commit
46320d5fc0
@ -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)]
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user