database: allow to query coins by their spending status
This commit is contained in:
parent
b3337193a4
commit
54410cd9c4
@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
bitcoin::{BitcoinInterface, BlockChainTip, UTxO},
|
||||
database::{Coin, DatabaseConnection, DatabaseInterface},
|
||||
database::{Coin, CoinType, DatabaseConnection, DatabaseInterface},
|
||||
descriptors,
|
||||
};
|
||||
|
||||
@ -30,7 +30,7 @@ fn update_coins(
|
||||
descs: &[descriptors::InheritanceDescriptor],
|
||||
secp: &secp256k1::Secp256k1<secp256k1::VerifyOnly>,
|
||||
) -> UpdatedCoins {
|
||||
let curr_coins = db_conn.coins();
|
||||
let curr_coins = db_conn.coins(CoinType::All);
|
||||
log::debug!("Current coins: {:?}", curr_coins);
|
||||
|
||||
// Start by fetching newly received coins.
|
||||
|
||||
@ -6,7 +6,7 @@ mod utils;
|
||||
|
||||
use crate::{
|
||||
bitcoin::BitcoinInterface,
|
||||
database::{Coin, DatabaseInterface},
|
||||
database::{Coin, CoinType, DatabaseInterface},
|
||||
descriptors, DaemonControl, VERSION,
|
||||
};
|
||||
|
||||
@ -243,7 +243,7 @@ impl DaemonControl {
|
||||
pub fn list_coins(&self) -> ListCoinsResult {
|
||||
let mut db_conn = self.db.connection();
|
||||
let coins: Vec<ListCoinsEntry> = db_conn
|
||||
.coins()
|
||||
.coins(CoinType::All)
|
||||
// Can't use into_values as of Rust 1.48
|
||||
.into_iter()
|
||||
.map(|(_, coin)| {
|
||||
|
||||
@ -81,7 +81,7 @@ pub trait DatabaseConnection {
|
||||
) -> Option<(bip32::ChildNumber, bool)>;
|
||||
|
||||
/// Get all our coins, past or present, spent or not.
|
||||
fn coins(&mut self) -> HashMap<bitcoin::OutPoint, Coin>;
|
||||
fn coins(&mut self, coin_type: CoinType) -> HashMap<bitcoin::OutPoint, Coin>;
|
||||
|
||||
/// List coins that are being spent and whose spending transaction is still unconfirmed.
|
||||
fn list_spending_coins(&mut self) -> HashMap<bitcoin::OutPoint, Coin>;
|
||||
@ -178,8 +178,8 @@ impl DatabaseConnection for SqliteConn {
|
||||
self.complete_wallet_rescan()
|
||||
}
|
||||
|
||||
fn coins(&mut self) -> HashMap<bitcoin::OutPoint, Coin> {
|
||||
self.coins()
|
||||
fn coins(&mut self, coin_type: CoinType) -> HashMap<bitcoin::OutPoint, Coin> {
|
||||
self.coins(coin_type)
|
||||
.into_iter()
|
||||
.map(|db_coin| (db_coin.outpoint, db_coin.into()))
|
||||
.collect()
|
||||
@ -316,3 +316,10 @@ impl Coin {
|
||||
self.spend_txid.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum CoinType {
|
||||
All,
|
||||
Unspent,
|
||||
Spent,
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ use crate::{
|
||||
schema::{DbAddress, DbCoin, DbSpendTransaction, DbTip, DbWallet},
|
||||
utils::{create_fresh_db, db_exec, db_query, db_tx_query, LOOK_AHEAD_LIMIT},
|
||||
},
|
||||
Coin,
|
||||
Coin, CoinType,
|
||||
},
|
||||
descriptors::MultipathDescriptor,
|
||||
};
|
||||
@ -313,10 +313,14 @@ impl SqliteConn {
|
||||
}
|
||||
|
||||
/// Get all the coins from DB.
|
||||
pub fn coins(&mut self) -> Vec<DbCoin> {
|
||||
pub fn coins(&mut self, coin_type: CoinType) -> Vec<DbCoin> {
|
||||
db_query(
|
||||
&mut self.conn,
|
||||
"SELECT * FROM coins",
|
||||
match coin_type {
|
||||
CoinType::All => "SELECT * FROM coins",
|
||||
CoinType::Unspent => "SELECT * FROM coins WHERE spend_txid IS NULL",
|
||||
CoinType::Spent => "SELECT * FROM coins WHERE spend_txid IS NOT NULL",
|
||||
},
|
||||
rusqlite::params![],
|
||||
|row| row.try_into(),
|
||||
)
|
||||
@ -682,7 +686,7 @@ mod tests {
|
||||
let mut conn = db.connection().unwrap();
|
||||
|
||||
// Necessarily empty at first.
|
||||
assert!(conn.coins().is_empty());
|
||||
assert!(conn.coins(CoinType::All).is_empty());
|
||||
|
||||
// Add one, we'll get it.
|
||||
let coin_a = Coin {
|
||||
@ -699,13 +703,17 @@ mod tests {
|
||||
spend_block: None,
|
||||
};
|
||||
conn.new_unspent_coins(&[coin_a]);
|
||||
assert_eq!(conn.coins()[0].outpoint, coin_a.outpoint);
|
||||
assert_eq!(conn.coins(CoinType::All)[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);
|
||||
|
||||
// It is unspent.
|
||||
assert_eq!(conn.coins(CoinType::Unspent)[0].outpoint, coin_a.outpoint);
|
||||
assert!(conn.coins(CoinType::Spent).is_empty());
|
||||
|
||||
// Add a second one (this one is change), we'll get both.
|
||||
let coin_b = Coin {
|
||||
outpoint: bitcoin::OutPoint::from_str(
|
||||
@ -721,8 +729,11 @@ mod tests {
|
||||
spend_block: None,
|
||||
};
|
||||
conn.new_unspent_coins(&[coin_b]);
|
||||
let outpoints: HashSet<bitcoin::OutPoint> =
|
||||
conn.coins().into_iter().map(|c| c.outpoint).collect();
|
||||
let outpoints: HashSet<bitcoin::OutPoint> = conn
|
||||
.coins(CoinType::All)
|
||||
.into_iter()
|
||||
.map(|c| c.outpoint)
|
||||
.collect();
|
||||
assert!(outpoints.contains(&coin_a.outpoint));
|
||||
assert!(outpoints.contains(&coin_b.outpoint));
|
||||
|
||||
@ -738,11 +749,15 @@ mod tests {
|
||||
assert!(coins.iter().any(|c| c.outpoint == coin_a.outpoint));
|
||||
assert!(coins.iter().any(|c| c.outpoint == coin_b.outpoint));
|
||||
|
||||
// They are both unspent
|
||||
assert_eq!(conn.coins(CoinType::Unspent).len(), 2);
|
||||
assert!(conn.coins(CoinType::Spent).is_empty());
|
||||
|
||||
// Now if we confirm one, it'll be marked as such.
|
||||
let height = 174500;
|
||||
let time = 174500;
|
||||
conn.confirm_coins(&[(coin_a.outpoint, height, time)]);
|
||||
let coins = conn.coins();
|
||||
let coins = conn.coins(CoinType::All);
|
||||
assert_eq!(coins[0].block_height, Some(height));
|
||||
assert_eq!(coins[0].block_time, Some(time));
|
||||
assert!(coins[1].block_height.is_none());
|
||||
@ -753,14 +768,18 @@ mod tests {
|
||||
coin_a.outpoint,
|
||||
bitcoin::Txid::from_slice(&[0; 32][..]).unwrap(),
|
||||
)]);
|
||||
let coins_map: HashMap<bitcoin::OutPoint, DbCoin> =
|
||||
conn.coins().into_iter().map(|c| (c.outpoint, c)).collect();
|
||||
let coins_map: HashMap<bitcoin::OutPoint, DbCoin> = conn
|
||||
.coins(CoinType::All)
|
||||
.into_iter()
|
||||
.map(|c| (c.outpoint, c))
|
||||
.collect();
|
||||
assert!(coins_map
|
||||
.get(&coin_a.outpoint)
|
||||
.unwrap()
|
||||
.spend_txid
|
||||
.is_some());
|
||||
|
||||
// We will see it as 'spending'
|
||||
let outpoints: HashSet<bitcoin::OutPoint> = conn
|
||||
.list_spending_coins()
|
||||
.into_iter()
|
||||
@ -768,6 +787,10 @@ mod tests {
|
||||
.collect();
|
||||
assert!(outpoints.contains(&coin_a.outpoint));
|
||||
|
||||
// The first one is spent, not the second one.
|
||||
assert_eq!(conn.coins(CoinType::Spent)[0].outpoint, coin_a.outpoint);
|
||||
assert_eq!(conn.coins(CoinType::Unspent)[0].outpoint, coin_b.outpoint);
|
||||
|
||||
// Now if we confirm the spend.
|
||||
let height = 128_097;
|
||||
let time = 3_000_000;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
bitcoin::{BitcoinInterface, Block, BlockChainTip, UTxO},
|
||||
config::{BitcoinConfig, Config},
|
||||
database::{Coin, DatabaseConnection, DatabaseInterface, SpendBlock},
|
||||
database::{Coin, CoinType, DatabaseConnection, DatabaseInterface, SpendBlock},
|
||||
descriptors, DaemonHandle,
|
||||
};
|
||||
|
||||
@ -189,8 +189,19 @@ impl DatabaseConnection for DummyDatabase {
|
||||
self.db.write().unwrap().change_index = index;
|
||||
}
|
||||
|
||||
fn coins(&mut self) -> HashMap<bitcoin::OutPoint, Coin> {
|
||||
self.db.read().unwrap().coins.clone()
|
||||
fn coins(&mut self, coin_type: CoinType) -> HashMap<bitcoin::OutPoint, Coin> {
|
||||
let coins = self.db.read().unwrap().coins.clone();
|
||||
match coin_type {
|
||||
CoinType::All => coins,
|
||||
CoinType::Unspent => coins
|
||||
.into_iter()
|
||||
.filter(|(_, c)| c.spend_txid.is_none())
|
||||
.collect(),
|
||||
CoinType::Spent => coins
|
||||
.into_iter()
|
||||
.filter(|(_, c)| c.spend_txid.is_some())
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn list_spending_coins(&mut self) -> HashMap<bitcoin::OutPoint, Coin> {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user