database: interface and implementation for coins storage and update
This commit is contained in:
parent
b548451292
commit
05b3af1b5a
@ -5,12 +5,15 @@ pub mod sqlite;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
bitcoin::BlockChainTip,
|
bitcoin::BlockChainTip,
|
||||||
database::sqlite::{schema::DbTip, SqliteConn, SqliteDb},
|
database::sqlite::{
|
||||||
|
schema::{DbCoin, DbTip},
|
||||||
|
SqliteConn, SqliteDb,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::sync;
|
use std::{collections::HashMap, sync};
|
||||||
|
|
||||||
use miniscript::bitcoin::util::bip32;
|
use miniscript::bitcoin::{self, util::bip32};
|
||||||
|
|
||||||
pub trait DatabaseInterface: Send {
|
pub trait DatabaseInterface: Send {
|
||||||
fn connection(&self) -> Box<dyn DatabaseConnection>;
|
fn connection(&self) -> Box<dyn DatabaseConnection>;
|
||||||
@ -33,12 +36,27 @@ pub trait DatabaseConnection {
|
|||||||
/// Get the tip of the best chain we've seen.
|
/// Get the tip of the best chain we've seen.
|
||||||
fn chain_tip(&mut self) -> Option<BlockChainTip>;
|
fn chain_tip(&mut self) -> Option<BlockChainTip>;
|
||||||
|
|
||||||
|
/// The network we are operating on.
|
||||||
|
fn network(&mut self) -> bitcoin::Network;
|
||||||
|
|
||||||
/// Update our best chain seen.
|
/// Update our best chain seen.
|
||||||
fn update_tip(&mut self, tip: &BlockChainTip);
|
fn update_tip(&mut self, tip: &BlockChainTip);
|
||||||
|
|
||||||
fn derivation_index(&mut self) -> bip32::ChildNumber;
|
fn derivation_index(&mut self) -> bip32::ChildNumber;
|
||||||
|
|
||||||
fn update_derivation_index(&mut self, index: bip32::ChildNumber);
|
fn update_derivation_index(&mut self, index: bip32::ChildNumber);
|
||||||
|
|
||||||
|
/// Get all UTxOs.
|
||||||
|
fn unspent_coins(&mut self) -> HashMap<bitcoin::OutPoint, Coin>;
|
||||||
|
|
||||||
|
/// Store new UTxOs. Coins must not already be in database.
|
||||||
|
fn new_unspent_coins<'a>(&mut self, coins: &[Coin]);
|
||||||
|
|
||||||
|
/// Mark a set of coins as being confirmed at a specified height.
|
||||||
|
fn confirm_coins<'a>(&mut self, outpoints: &[(bitcoin::OutPoint, i32)]);
|
||||||
|
|
||||||
|
/// Mark a set of coins as being spent by a specified txid.
|
||||||
|
fn spend_coins<'a>(&mut self, outpoints: &[(bitcoin::OutPoint, bitcoin::Txid)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DatabaseConnection for SqliteConn {
|
impl DatabaseConnection for SqliteConn {
|
||||||
@ -53,6 +71,10 @@ impl DatabaseConnection for SqliteConn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn network(&mut self) -> bitcoin::Network {
|
||||||
|
self.db_tip().network
|
||||||
|
}
|
||||||
|
|
||||||
fn update_tip(&mut self, tip: &BlockChainTip) {
|
fn update_tip(&mut self, tip: &BlockChainTip) {
|
||||||
self.update_tip(&tip)
|
self.update_tip(&tip)
|
||||||
}
|
}
|
||||||
@ -64,4 +86,68 @@ impl DatabaseConnection for SqliteConn {
|
|||||||
fn update_derivation_index(&mut self, index: bip32::ChildNumber) {
|
fn update_derivation_index(&mut self, index: bip32::ChildNumber) {
|
||||||
self.update_derivation_index(index)
|
self.update_derivation_index(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_unspent_coins<'a>(&mut self, coins: &[Coin]) {
|
||||||
|
self.new_unspent_coins(coins)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn confirm_coins<'a>(&mut self, outpoints: &[(bitcoin::OutPoint, i32)]) {
|
||||||
|
self.confirm_coins(outpoints)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spend_coins<'a>(&mut self, outpoints: &[(bitcoin::OutPoint, bitcoin::Txid)]) {
|
||||||
|
self.spend_coins(outpoints)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Coin {
|
||||||
|
pub outpoint: bitcoin::OutPoint,
|
||||||
|
pub block_height: Option<i32>,
|
||||||
|
pub amount: bitcoin::Amount,
|
||||||
|
pub derivation_index: bip32::ChildNumber,
|
||||||
|
pub spend_txid: Option<bitcoin::Txid>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::hash::Hash for Coin {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, h: &mut H) {
|
||||||
|
self.outpoint.hash(h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Coin {
|
||||||
|
pub fn is_confirmed(&self) -> bool {
|
||||||
|
self.block_height.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_spent(&self) -> bool {
|
||||||
|
self.spend_txid.is_some()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,9 +11,12 @@ mod utils;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
bitcoin::BlockChainTip,
|
bitcoin::BlockChainTip,
|
||||||
database::sqlite::{
|
database::{
|
||||||
schema::{DbTip, DbWallet},
|
sqlite::{
|
||||||
utils::{create_fresh_db, db_exec, db_query},
|
schema::{DbCoin, DbTip, DbWallet},
|
||||||
|
utils::{create_fresh_db, db_exec, db_query},
|
||||||
|
},
|
||||||
|
Coin,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -141,6 +144,9 @@ impl SqliteDb {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We only support single wallet. The id of the wallet row is always 1.
|
||||||
|
const WALLET_ID: i64 = 1;
|
||||||
|
|
||||||
pub struct SqliteConn {
|
pub struct SqliteConn {
|
||||||
conn: rusqlite::Connection,
|
conn: rusqlite::Connection,
|
||||||
}
|
}
|
||||||
@ -214,13 +220,86 @@ impl SqliteConn {
|
|||||||
})
|
})
|
||||||
.expect("Database must be available")
|
.expect("Database must be available")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get all UTxOs.
|
||||||
|
pub fn unspent_coins(&mut self) -> Vec<DbCoin> {
|
||||||
|
db_query(
|
||||||
|
&mut self.conn,
|
||||||
|
"SELECT * FROM coins WHERE spend_txid is NULL",
|
||||||
|
rusqlite::params![],
|
||||||
|
|row| row.try_into(),
|
||||||
|
)
|
||||||
|
.expect("Db must not fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: don't take the whole coin, we don't need it.
|
||||||
|
/// Store new, unconfirmed and unspent, coins.
|
||||||
|
/// Will panic if given a coin that is already in DB.
|
||||||
|
pub fn new_unspent_coins<'a>(&mut self, coins: impl IntoIterator<Item = &'a Coin>) {
|
||||||
|
db_exec(&mut self.conn, |db_tx| {
|
||||||
|
for coin in coins {
|
||||||
|
let deriv_index: u32 = coin.derivation_index.into();
|
||||||
|
db_tx.execute(
|
||||||
|
"INSERT INTO coins (wallet_id, txid, vout, amount_sat, derivation_index) \
|
||||||
|
VALUES (?1, ?2, ?3, ?4, ?5)",
|
||||||
|
rusqlite::params![
|
||||||
|
WALLET_ID,
|
||||||
|
coin.outpoint.txid.to_vec(),
|
||||||
|
coin.outpoint.vout,
|
||||||
|
coin.amount.as_sat(),
|
||||||
|
deriv_index,
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.expect("Database must be available")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mark a set of coins as confirmed.
|
||||||
|
pub fn confirm_coins<'a>(
|
||||||
|
&mut self,
|
||||||
|
outpoints: impl IntoIterator<Item = &'a (bitcoin::OutPoint, i32)>,
|
||||||
|
) {
|
||||||
|
db_exec(&mut self.conn, |db_tx| {
|
||||||
|
for (outpoint, height) in outpoints {
|
||||||
|
db_tx.execute(
|
||||||
|
"UPDATE coins SET blockheight = ?1 WHERE txid = ?2 AND vout = ?3",
|
||||||
|
rusqlite::params![height, outpoint.txid.to_vec(), outpoint.vout,],
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.expect("Database must be available")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mark a set of coins as spent.
|
||||||
|
pub fn spend_coins<'a>(
|
||||||
|
&mut self,
|
||||||
|
outpoints: impl IntoIterator<Item = &'a (bitcoin::OutPoint, bitcoin::Txid)>,
|
||||||
|
) {
|
||||||
|
db_exec(&mut self.conn, |db_tx| {
|
||||||
|
for (outpoint, spend_txid) in outpoints {
|
||||||
|
db_tx.execute(
|
||||||
|
"UPDATE coins SET spend_txid = ?1 WHERE txid = ?2 AND vout = ?3",
|
||||||
|
rusqlite::params![spend_txid.to_vec(), outpoint.txid.to_vec(), outpoint.vout,],
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.expect("Database must be available")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::testutils::*;
|
use crate::testutils::*;
|
||||||
use std::{fs, path, str::FromStr};
|
use std::{collections::HashSet, fs, path, str::FromStr};
|
||||||
|
|
||||||
|
use bitcoin::hashes::Hash;
|
||||||
|
|
||||||
fn dummy_options() -> FreshDbOptions {
|
fn dummy_options() -> FreshDbOptions {
|
||||||
let desc_str = "wsh(andor(pk(03b506a1dbe57b4bf48c95e0c7d417b87dd3b4349d290d2e7e9ba72c912652d80a),older(10000),pk(0295e7f5d12a2061f1fd2286cefec592dff656a19f55f4f01305d6aa56630880ce)))";
|
let desc_str = "wsh(andor(pk(03b506a1dbe57b4bf48c95e0c7d417b87dd3b4349d290d2e7e9ba72c912652d80a),older(10000),pk(0295e7f5d12a2061f1fd2286cefec592dff656a19f55f4f01305d6aa56630880ce)))";
|
||||||
@ -231,6 +310,19 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn dummy_db() -> (path::PathBuf, FreshDbOptions, SqliteDb) {
|
||||||
|
let tmp_dir = tmp_dir();
|
||||||
|
fs::create_dir_all(&tmp_dir).unwrap();
|
||||||
|
|
||||||
|
let db_path: path::PathBuf = [tmp_dir.as_path(), path::Path::new("minisafed.sqlite3")]
|
||||||
|
.iter()
|
||||||
|
.collect();
|
||||||
|
let options = dummy_options();
|
||||||
|
let db = SqliteDb::new(db_path.clone(), Some(options.clone())).unwrap();
|
||||||
|
|
||||||
|
(tmp_dir, options, db)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn db_startup_sanity_checks() {
|
fn db_startup_sanity_checks() {
|
||||||
let tmp_dir = tmp_dir();
|
let tmp_dir = tmp_dir();
|
||||||
@ -274,14 +366,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn db_tip_update() {
|
fn db_tip_update() {
|
||||||
let tmp_dir = tmp_dir();
|
let (tmp_dir, options, db) = dummy_db();
|
||||||
fs::create_dir_all(&tmp_dir).unwrap();
|
|
||||||
|
|
||||||
let db_path: path::PathBuf = [tmp_dir.as_path(), path::Path::new("minisafed.sqlite3")]
|
|
||||||
.iter()
|
|
||||||
.collect();
|
|
||||||
let options = dummy_options();
|
|
||||||
let db = SqliteDb::new(db_path.clone(), Some(options.clone())).unwrap();
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut conn = db.connection().unwrap();
|
let mut conn = db.connection().unwrap();
|
||||||
@ -306,4 +391,72 @@ mod tests {
|
|||||||
|
|
||||||
fs::remove_dir_all(&tmp_dir).unwrap();
|
fs::remove_dir_all(&tmp_dir).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn db_coins_update() {
|
||||||
|
let (tmp_dir, _, db) = dummy_db();
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut conn = db.connection().unwrap();
|
||||||
|
|
||||||
|
// Necessarily empty at first.
|
||||||
|
assert!(conn.unspent_coins().is_empty());
|
||||||
|
|
||||||
|
// Add one, we'll get it.
|
||||||
|
let coin_a = Coin {
|
||||||
|
outpoint: bitcoin::OutPoint::from_str(
|
||||||
|
"6f0dc85a369b44458eba3a1f0ea5b5935d563afb6994f70f5b0094e05be1676c:1",
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
block_height: None,
|
||||||
|
amount: bitcoin::Amount::from_sat(98765),
|
||||||
|
derivation_index: bip32::ChildNumber::from_normal_idx(10).unwrap(),
|
||||||
|
spend_txid: None,
|
||||||
|
};
|
||||||
|
conn.new_unspent_coins(&[coin_a.clone()]); // On 1.48, arrays aren't IntoIterator
|
||||||
|
assert_eq!(conn.unspent_coins()[0].outpoint, coin_a.outpoint);
|
||||||
|
|
||||||
|
// Add a second one, we'll get both.
|
||||||
|
let coin_b = Coin {
|
||||||
|
outpoint: bitcoin::OutPoint::from_str(
|
||||||
|
"61db3e276b095e5b05f1849dd6bfffb4e7e5ec1c4a4210099b98fce01571936f:12",
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
block_height: None,
|
||||||
|
amount: bitcoin::Amount::from_sat(1111),
|
||||||
|
derivation_index: bip32::ChildNumber::from_normal_idx(103).unwrap(),
|
||||||
|
spend_txid: None,
|
||||||
|
};
|
||||||
|
conn.new_unspent_coins(&[coin_b.clone()]);
|
||||||
|
let outpoints: HashSet<bitcoin::OutPoint> = conn
|
||||||
|
.unspent_coins()
|
||||||
|
.into_iter()
|
||||||
|
.map(|c| c.outpoint)
|
||||||
|
.collect();
|
||||||
|
assert!(outpoints.contains(&coin_a.outpoint));
|
||||||
|
assert!(outpoints.contains(&coin_b.outpoint));
|
||||||
|
|
||||||
|
// Now if we confirm one, it'll be marked as such.
|
||||||
|
let height = 174500;
|
||||||
|
conn.confirm_coins(&[(coin_a.outpoint, height)]);
|
||||||
|
let coins = conn.unspent_coins();
|
||||||
|
assert_eq!(coins[0].block_height, Some(height));
|
||||||
|
assert!(coins[1].block_height.is_none());
|
||||||
|
|
||||||
|
// Now if we spend one, we'll only get the other one.
|
||||||
|
conn.spend_coins(&[(
|
||||||
|
coin_a.outpoint,
|
||||||
|
bitcoin::Txid::from_slice(&[0; 32][..]).unwrap(),
|
||||||
|
)]);
|
||||||
|
let outpoints: HashSet<bitcoin::OutPoint> = conn
|
||||||
|
.unspent_coins()
|
||||||
|
.into_iter()
|
||||||
|
.map(|c| c.outpoint)
|
||||||
|
.collect();
|
||||||
|
assert!(!outpoints.contains(&coin_a.outpoint));
|
||||||
|
assert!(outpoints.contains(&coin_b.outpoint));
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::remove_dir_all(&tmp_dir).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,6 +26,22 @@ CREATE TABLE wallets (
|
|||||||
main_descriptor TEXT NOT NULL,
|
main_descriptor TEXT NOT NULL,
|
||||||
deposit_derivation_index INTEGER NOT NULL
|
deposit_derivation_index INTEGER NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/* Our (U)TxOs. */
|
||||||
|
CREATE TABLE coins (
|
||||||
|
id INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
wallet_id INTEGER NOT NULL,
|
||||||
|
blockheight INTEGER,
|
||||||
|
txid BLOB NOT NULL,
|
||||||
|
vout INTEGER NOT NULL,
|
||||||
|
amount_sat INTEGER NOT NULL,
|
||||||
|
derivation_index INTEGER NOT NULL,
|
||||||
|
spend_txid BLOB,
|
||||||
|
UNIQUE (txid, vout),
|
||||||
|
FOREIGN KEY (wallet_id) REFERENCES wallets (id)
|
||||||
|
ON UPDATE RESTRICT
|
||||||
|
ON DELETE RESTRICT
|
||||||
|
);
|
||||||
";
|
";
|
||||||
|
|
||||||
/// A row in the "tip" table.
|
/// A row in the "tip" table.
|
||||||
@ -88,3 +104,54 @@ impl TryFrom<&rusqlite::Row<'_>> for DbWallet {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct DbCoin {
|
||||||
|
pub id: i64,
|
||||||
|
pub wallet_id: i64,
|
||||||
|
pub outpoint: bitcoin::OutPoint,
|
||||||
|
pub block_height: Option<i32>,
|
||||||
|
pub amount: bitcoin::Amount,
|
||||||
|
pub derivation_index: bip32::ChildNumber,
|
||||||
|
pub spend_txid: Option<bitcoin::Txid>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::hash::Hash for DbCoin {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, h: &mut H) {
|
||||||
|
self.outpoint.hash(h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&rusqlite::Row<'_>> for DbCoin {
|
||||||
|
type Error = rusqlite::Error;
|
||||||
|
|
||||||
|
fn try_from(row: &rusqlite::Row) -> Result<Self, Self::Error> {
|
||||||
|
let id = row.get(0)?;
|
||||||
|
let wallet_id = row.get(1)?;
|
||||||
|
|
||||||
|
let block_height = row.get(2)?;
|
||||||
|
let txid: Vec<u8> = row.get(3)?;
|
||||||
|
let txid: bitcoin::Txid = encode::deserialize(&txid).expect("We only store valid txids");
|
||||||
|
let vout = row.get(4)?;
|
||||||
|
let outpoint = bitcoin::OutPoint { txid, vout };
|
||||||
|
|
||||||
|
let amount = row.get(5)?;
|
||||||
|
let amount = bitcoin::Amount::from_sat(amount);
|
||||||
|
let der_idx: u32 = row.get(6)?;
|
||||||
|
let derivation_index = bip32::ChildNumber::from(der_idx);
|
||||||
|
|
||||||
|
let spend_txid: Option<Vec<u8>> = row.get(7)?;
|
||||||
|
let spend_txid =
|
||||||
|
spend_txid.map(|txid| encode::deserialize(&txid).expect("We only store valid txids"));
|
||||||
|
|
||||||
|
Ok(DbCoin {
|
||||||
|
id,
|
||||||
|
wallet_id,
|
||||||
|
outpoint,
|
||||||
|
block_height,
|
||||||
|
amount,
|
||||||
|
derivation_index,
|
||||||
|
spend_txid,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
bitcoin::{BitcoinInterface, BlockChainTip},
|
bitcoin::{BitcoinInterface, BlockChainTip},
|
||||||
config::{BitcoinConfig, Config},
|
config::{BitcoinConfig, Config},
|
||||||
database::{DatabaseConnection, DatabaseInterface},
|
database::{Coin, DatabaseConnection, DatabaseInterface},
|
||||||
DaemonHandle,
|
DaemonHandle,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::{env, fs, io, path, process, str::FromStr, sync, thread, time};
|
use std::{collections::HashMap, env, fs, io, path, process, str::FromStr, sync, thread, time};
|
||||||
|
|
||||||
use miniscript::{
|
use miniscript::{
|
||||||
bitcoin::{self, util::bip32},
|
bitcoin::{self, util::bip32},
|
||||||
@ -37,6 +37,7 @@ impl BitcoinInterface for DummyBitcoind {
|
|||||||
pub struct DummyDb {
|
pub struct DummyDb {
|
||||||
curr_index: bip32::ChildNumber,
|
curr_index: bip32::ChildNumber,
|
||||||
curr_tip: Option<BlockChainTip>,
|
curr_tip: Option<BlockChainTip>,
|
||||||
|
coins: HashMap<bitcoin::OutPoint, Coin>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DummyDb {
|
impl DummyDb {
|
||||||
@ -44,6 +45,7 @@ impl DummyDb {
|
|||||||
DummyDb {
|
DummyDb {
|
||||||
curr_index: 0.into(),
|
curr_index: 0.into(),
|
||||||
curr_tip: None,
|
curr_tip: None,
|
||||||
|
coins: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -59,6 +61,10 @@ pub struct DummyDbConn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DatabaseConnection for DummyDbConn {
|
impl DatabaseConnection for DummyDbConn {
|
||||||
|
fn network(&mut self) -> bitcoin::Network {
|
||||||
|
bitcoin::Network::Bitcoin
|
||||||
|
}
|
||||||
|
|
||||||
fn chain_tip(&mut self) -> Option<BlockChainTip> {
|
fn chain_tip(&mut self) -> Option<BlockChainTip> {
|
||||||
self.db.read().unwrap().curr_tip
|
self.db.read().unwrap().curr_tip
|
||||||
}
|
}
|
||||||
@ -74,6 +80,38 @@ impl DatabaseConnection for DummyDbConn {
|
|||||||
fn update_derivation_index(&mut self, index: bip32::ChildNumber) {
|
fn update_derivation_index(&mut self, index: bip32::ChildNumber) {
|
||||||
self.db.write().unwrap().curr_index = index;
|
self.db.write().unwrap().curr_index = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn unspent_coins(&mut self) -> HashMap<bitcoin::OutPoint, Coin> {
|
||||||
|
self.db.read().unwrap().coins.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_unspent_coins<'a>(&mut self, coins: &[Coin]) {
|
||||||
|
for coin in coins {
|
||||||
|
self.db
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.coins
|
||||||
|
.insert(coin.outpoint, coin.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn confirm_coins<'a>(&mut self, outpoints: &[(bitcoin::OutPoint, i32)]) {
|
||||||
|
for (op, height) in outpoints {
|
||||||
|
let mut db = self.db.write().unwrap();
|
||||||
|
let h = &mut db.coins.get_mut(op).unwrap().block_height;
|
||||||
|
assert!(h.is_none());
|
||||||
|
*h = Some(*height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spend_coins<'a>(&mut self, outpoints: &[(bitcoin::OutPoint, bitcoin::Txid)]) {
|
||||||
|
for (op, spend_txid) in outpoints {
|
||||||
|
let mut db = self.db.write().unwrap();
|
||||||
|
let spender = &mut db.coins.get_mut(op).unwrap().spend_txid;
|
||||||
|
assert!(spender.is_none());
|
||||||
|
*spender = Some(*spend_txid);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DummyMinisafe {
|
pub struct DummyMinisafe {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user