db: require the spend block height from the DB interface
Hence add a 'spend_block_height' field to the 'coin' column in the SQLite implementation. This also contains a couple cleanups, as well as a fix (we were still checking if the blockheight was > 1).
This commit is contained in:
parent
6038843d33
commit
972c8dac86
@ -51,7 +51,7 @@ pub trait BitcoinInterface: Send {
|
||||
fn spent_coins(
|
||||
&self,
|
||||
outpoints: &[(bitcoin::OutPoint, bitcoin::Txid)],
|
||||
) -> Vec<(bitcoin::OutPoint, bitcoin::Txid, u32)>;
|
||||
) -> Vec<(bitcoin::OutPoint, bitcoin::Txid, i32, u32)>;
|
||||
|
||||
/// Get the common ancestor between the Bitcoin backend's tip and the given tip.
|
||||
fn common_ancestor(&self, tip: &BlockChainTip) -> BlockChainTip;
|
||||
@ -155,7 +155,7 @@ impl BitcoinInterface for d::BitcoinD {
|
||||
fn spent_coins(
|
||||
&self,
|
||||
outpoints: &[(bitcoin::OutPoint, bitcoin::Txid)],
|
||||
) -> Vec<(bitcoin::OutPoint, bitcoin::Txid, u32)> {
|
||||
) -> Vec<(bitcoin::OutPoint, bitcoin::Txid, i32, u32)> {
|
||||
let mut spent = Vec::with_capacity(outpoints.len());
|
||||
|
||||
let mut cache: HashMap<bitcoin::Txid, Option<d::GetTxRes>> = HashMap::new();
|
||||
@ -174,10 +174,15 @@ impl BitcoinInterface for d::BitcoinD {
|
||||
let mut txs_to_cache: Vec<(bitcoin::Txid, Option<d::GetTxRes>)> = Vec::new();
|
||||
|
||||
if let Some(tx) = tx {
|
||||
if let Some(block_time) = tx.block_time {
|
||||
if let Some(block_height) = tx.block_height {
|
||||
// TODO: make both block time and height under the same Option.
|
||||
assert!(tx.block_height.is_some());
|
||||
spent.push((*op, *txid, block_time))
|
||||
spent.push((
|
||||
*op,
|
||||
*txid,
|
||||
block_height,
|
||||
tx.block_time.expect("Confirmed tx."),
|
||||
));
|
||||
} else if !tx.conflicting_txs.is_empty() {
|
||||
for txid in &tx.conflicting_txs {
|
||||
let tx: Option<&d::GetTxRes> = match cache.get(txid) {
|
||||
@ -190,13 +195,12 @@ impl BitcoinInterface for d::BitcoinD {
|
||||
};
|
||||
if let Some(tx) = tx {
|
||||
if let Some(block_height) = tx.block_height {
|
||||
if block_height > 1 {
|
||||
spent.push((
|
||||
*op,
|
||||
*txid,
|
||||
tx.block_time.expect("Spend is confirmed"),
|
||||
))
|
||||
}
|
||||
spent.push((
|
||||
*op,
|
||||
*txid,
|
||||
block_height,
|
||||
tx.block_time.expect("Spend is confirmed"),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -266,7 +270,7 @@ impl BitcoinInterface for sync::Arc<sync::Mutex<dyn BitcoinInterface + 'static>>
|
||||
fn spent_coins(
|
||||
&self,
|
||||
outpoints: &[(bitcoin::OutPoint, bitcoin::Txid)],
|
||||
) -> Vec<(bitcoin::OutPoint, bitcoin::Txid, u32)> {
|
||||
) -> Vec<(bitcoin::OutPoint, bitcoin::Txid, i32, u32)> {
|
||||
self.lock().unwrap().spent_coins(outpoints)
|
||||
}
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@ struct UpdatedCoins {
|
||||
pub received: Vec<Coin>,
|
||||
pub confirmed: Vec<(bitcoin::OutPoint, i32, u32)>,
|
||||
pub spending: Vec<(bitcoin::OutPoint, bitcoin::Txid)>,
|
||||
pub spent: Vec<(bitcoin::OutPoint, bitcoin::Txid, u32)>,
|
||||
pub spent: Vec<(bitcoin::OutPoint, bitcoin::Txid, i32, u32)>,
|
||||
}
|
||||
|
||||
// Update the state of our coins. There may be new unspent, and existing ones may become confirmed
|
||||
@ -43,7 +43,7 @@ fn update_coins(
|
||||
block_height: None,
|
||||
block_time: None,
|
||||
spend_txid: None,
|
||||
spend_block_time: None,
|
||||
spend_block: None,
|
||||
};
|
||||
received.push(coin);
|
||||
}
|
||||
|
||||
@ -572,7 +572,7 @@ mod tests {
|
||||
amount: bitcoin::Amount::from_sat(100_000),
|
||||
derivation_index: bip32::ChildNumber::from(13),
|
||||
spend_txid: None,
|
||||
spend_block_time: None,
|
||||
spend_block: None,
|
||||
}]);
|
||||
let res = control.create_spend(&[dummy_op], &destinations, 1).unwrap();
|
||||
let tx = res.psbt.global.unsigned_tx;
|
||||
@ -666,7 +666,7 @@ mod tests {
|
||||
amount: bitcoin::Amount::from_sat(100_000),
|
||||
derivation_index: bip32::ChildNumber::from(13),
|
||||
spend_txid: None,
|
||||
spend_block_time: None,
|
||||
spend_block: None,
|
||||
},
|
||||
Coin {
|
||||
outpoint: dummy_op_b,
|
||||
@ -675,7 +675,7 @@ mod tests {
|
||||
amount: bitcoin::Amount::from_sat(115_680),
|
||||
derivation_index: bip32::ChildNumber::from(34),
|
||||
spend_txid: None,
|
||||
spend_block_time: None,
|
||||
spend_block: None,
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ pub mod sqlite;
|
||||
use crate::{
|
||||
bitcoin::BlockChainTip,
|
||||
database::sqlite::{
|
||||
schema::{DbCoin, DbTip},
|
||||
schema::{DbCoin, DbSpendBlock, DbTip},
|
||||
SqliteConn, SqliteDb,
|
||||
},
|
||||
};
|
||||
@ -70,7 +70,7 @@ pub trait DatabaseConnection {
|
||||
fn spend_coins(&mut self, outpoints: &[(bitcoin::OutPoint, bitcoin::Txid)]);
|
||||
|
||||
/// Mark a set of coins as spent by a specified txid at a specified block time.
|
||||
fn confirm_spend(&mut self, outpoints: &[(bitcoin::OutPoint, bitcoin::Txid, u32)]);
|
||||
fn confirm_spend(&mut self, outpoints: &[(bitcoin::OutPoint, bitcoin::Txid, i32, u32)]);
|
||||
|
||||
/// Get specific coins from the database.
|
||||
fn coins_by_outpoints(
|
||||
@ -144,7 +144,7 @@ impl DatabaseConnection for SqliteConn {
|
||||
self.spend_coins(outpoints)
|
||||
}
|
||||
|
||||
fn confirm_spend<'a>(&mut self, outpoints: &[(bitcoin::OutPoint, bitcoin::Txid, u32)]) {
|
||||
fn confirm_spend<'a>(&mut self, outpoints: &[(bitcoin::OutPoint, bitcoin::Txid, i32, u32)]) {
|
||||
self.confirm_spend(outpoints)
|
||||
}
|
||||
|
||||
@ -186,6 +186,21 @@ impl DatabaseConnection for SqliteConn {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct SpendBlock {
|
||||
pub height: i32,
|
||||
pub time: u32,
|
||||
}
|
||||
|
||||
impl From<DbSpendBlock> for SpendBlock {
|
||||
fn from(b: DbSpendBlock) -> SpendBlock {
|
||||
SpendBlock {
|
||||
height: b.height,
|
||||
time: b.time,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Coin {
|
||||
pub outpoint: bitcoin::OutPoint,
|
||||
@ -194,7 +209,7 @@ pub struct Coin {
|
||||
pub amount: bitcoin::Amount,
|
||||
pub derivation_index: bip32::ChildNumber,
|
||||
pub spend_txid: Option<bitcoin::Txid>,
|
||||
pub spend_block_time: Option<u32>,
|
||||
pub spend_block: Option<SpendBlock>,
|
||||
}
|
||||
|
||||
impl std::convert::From<DbCoin> for Coin {
|
||||
@ -206,7 +221,7 @@ impl std::convert::From<DbCoin> for Coin {
|
||||
amount,
|
||||
derivation_index,
|
||||
spend_txid,
|
||||
spend_block_time,
|
||||
spend_block,
|
||||
..
|
||||
} = db_coin;
|
||||
Coin {
|
||||
@ -216,7 +231,7 @@ impl std::convert::From<DbCoin> for Coin {
|
||||
amount,
|
||||
derivation_index,
|
||||
spend_txid,
|
||||
spend_block_time,
|
||||
spend_block: spend_block.map(SpendBlock::from),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -334,17 +334,19 @@ impl SqliteConn {
|
||||
.expect("Database must be available")
|
||||
}
|
||||
|
||||
/// Mark a set of coins as spent.
|
||||
/// Mark the Spend transaction of a given set of coins as being confirmed at a given
|
||||
/// block.
|
||||
pub fn confirm_spend<'a>(
|
||||
&mut self,
|
||||
outpoints: impl IntoIterator<Item = &'a (bitcoin::OutPoint, bitcoin::Txid, u32)>,
|
||||
outpoints: impl IntoIterator<Item = &'a (bitcoin::OutPoint, bitcoin::Txid, i32, u32)>,
|
||||
) {
|
||||
db_exec(&mut self.conn, |db_tx| {
|
||||
for (outpoint, spend_txid, time) in outpoints {
|
||||
for (outpoint, spend_txid, height, time) in outpoints {
|
||||
db_tx.execute(
|
||||
"UPDATE coins SET spend_txid = ?1, spend_block_time = ?2 WHERE txid = ?3 AND vout = ?4",
|
||||
"UPDATE coins SET spend_txid = ?1, spend_block_height = ?2, spend_block_time = ?3 WHERE txid = ?4 AND vout = ?5",
|
||||
rusqlite::params![
|
||||
spend_txid.to_vec(),
|
||||
height,
|
||||
time,
|
||||
outpoint.txid.to_vec(),
|
||||
outpoint.vout,
|
||||
@ -566,7 +568,7 @@ mod tests {
|
||||
amount: bitcoin::Amount::from_sat(98765),
|
||||
derivation_index: bip32::ChildNumber::from_normal_idx(10).unwrap(),
|
||||
spend_txid: None,
|
||||
spend_block_time: None,
|
||||
spend_block: 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);
|
||||
@ -587,7 +589,7 @@ mod tests {
|
||||
amount: bitcoin::Amount::from_sat(1111),
|
||||
derivation_index: bip32::ChildNumber::from_normal_idx(103).unwrap(),
|
||||
spend_txid: None,
|
||||
spend_block_time: None,
|
||||
spend_block: None,
|
||||
};
|
||||
conn.new_unspent_coins(&[coin_b.clone()]);
|
||||
let outpoints: HashSet<bitcoin::OutPoint> = conn
|
||||
@ -641,10 +643,13 @@ mod tests {
|
||||
assert!(outpoints.contains(&coin_a.outpoint));
|
||||
|
||||
// Now if we confirm the spend.
|
||||
let height = 128_097;
|
||||
let time = 3_000_000;
|
||||
conn.confirm_spend(&[(
|
||||
coin_a.outpoint,
|
||||
bitcoin::Txid::from_slice(&[0; 32][..]).unwrap(),
|
||||
3,
|
||||
height,
|
||||
time,
|
||||
)]);
|
||||
// the coin is not in a spending state.
|
||||
let outpoints: HashSet<bitcoin::OutPoint> = conn
|
||||
@ -657,6 +662,12 @@ mod tests {
|
||||
// Both are still in DB
|
||||
let coins = conn.db_coins(&[coin_a.outpoint, coin_b.outpoint]);
|
||||
assert_eq!(coins.len(), 2);
|
||||
|
||||
// The confirmed one contains the right time and block height
|
||||
let coin = conn.db_coins(&[coin_a.outpoint]).pop().unwrap();
|
||||
assert!(coin.spend_block.is_some());
|
||||
assert_eq!(coin.spend_block.as_ref().unwrap().time, time);
|
||||
assert_eq!(coin.spend_block.unwrap().height, height);
|
||||
}
|
||||
|
||||
fs::remove_dir_all(&tmp_dir).unwrap();
|
||||
|
||||
@ -30,7 +30,11 @@ CREATE TABLE wallets (
|
||||
deposit_derivation_index INTEGER NOT NULL
|
||||
);
|
||||
|
||||
/* Our (U)TxOs. */
|
||||
/* Our (U)TxOs.
|
||||
*
|
||||
* The 'spend_block_height' and 'spend_block.time' are only present if the spending
|
||||
* transaction for this coin exists and was confirmed.
|
||||
*/
|
||||
CREATE TABLE coins (
|
||||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
wallet_id INTEGER NOT NULL,
|
||||
@ -41,7 +45,7 @@ CREATE TABLE coins (
|
||||
amount_sat INTEGER NOT NULL,
|
||||
derivation_index INTEGER NOT NULL,
|
||||
spend_txid BLOB,
|
||||
/* Time of the block containing the transaction spending the coin, NULL if not confirmed */
|
||||
spend_block_height INTEGER,
|
||||
spend_block_time INTEGER,
|
||||
UNIQUE (txid, vout),
|
||||
FOREIGN KEY (wallet_id) REFERENCES wallets (id)
|
||||
@ -126,6 +130,12 @@ impl TryFrom<&rusqlite::Row<'_>> for DbWallet {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct DbSpendBlock {
|
||||
pub height: i32,
|
||||
pub time: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct DbCoin {
|
||||
pub id: i64,
|
||||
@ -136,7 +146,7 @@ pub struct DbCoin {
|
||||
pub amount: bitcoin::Amount,
|
||||
pub derivation_index: bip32::ChildNumber,
|
||||
pub spend_txid: Option<bitcoin::Txid>,
|
||||
pub spend_block_time: Option<u32>,
|
||||
pub spend_block: Option<DbSpendBlock>,
|
||||
}
|
||||
|
||||
impl TryFrom<&rusqlite::Row<'_>> for DbCoin {
|
||||
@ -161,7 +171,13 @@ impl TryFrom<&rusqlite::Row<'_>> for DbCoin {
|
||||
let spend_txid: Option<Vec<u8>> = row.get(8)?;
|
||||
let spend_txid =
|
||||
spend_txid.map(|txid| encode::deserialize(&txid).expect("We only store valid txids"));
|
||||
let spend_block_time = row.get(9)?;
|
||||
let spend_height: Option<i32> = row.get(9)?;
|
||||
let spend_time: Option<u32> = row.get(10)?;
|
||||
assert_eq!(spend_height.is_none(), spend_time.is_none());
|
||||
let spend_block = spend_height.map(|height| DbSpendBlock {
|
||||
height,
|
||||
time: spend_time.expect("Must be there if height is"),
|
||||
});
|
||||
|
||||
Ok(DbCoin {
|
||||
id,
|
||||
@ -172,7 +188,7 @@ impl TryFrom<&rusqlite::Row<'_>> for DbCoin {
|
||||
amount,
|
||||
derivation_index,
|
||||
spend_txid,
|
||||
spend_block_time,
|
||||
spend_block,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
bitcoin::{BitcoinInterface, BlockChainTip, UTxO},
|
||||
config::{BitcoinConfig, Config},
|
||||
database::{Coin, DatabaseConnection, DatabaseInterface},
|
||||
database::{Coin, DatabaseConnection, DatabaseInterface, SpendBlock},
|
||||
DaemonHandle,
|
||||
};
|
||||
|
||||
@ -59,7 +59,7 @@ impl BitcoinInterface for DummyBitcoind {
|
||||
fn spent_coins(
|
||||
&self,
|
||||
_: &[(bitcoin::OutPoint, bitcoin::Txid)],
|
||||
) -> Vec<(bitcoin::OutPoint, bitcoin::Txid, u32)> {
|
||||
) -> Vec<(bitcoin::OutPoint, bitcoin::Txid, i32, u32)> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
@ -164,19 +164,22 @@ impl DatabaseConnection for DummyDbConn {
|
||||
let mut db = self.db.write().unwrap();
|
||||
let spent = &mut db.coins.get_mut(op).unwrap();
|
||||
assert!(spent.spend_txid.is_none());
|
||||
assert!(spent.spend_block_time.is_none());
|
||||
assert!(spent.spend_block.is_none());
|
||||
spent.spend_txid = Some(*spend_txid);
|
||||
}
|
||||
}
|
||||
|
||||
fn confirm_spend<'a>(&mut self, outpoints: &[(bitcoin::OutPoint, bitcoin::Txid, u32)]) {
|
||||
for (op, spend_txid, time) in outpoints {
|
||||
fn confirm_spend<'a>(&mut self, outpoints: &[(bitcoin::OutPoint, bitcoin::Txid, i32, u32)]) {
|
||||
for (op, spend_txid, height, time) in outpoints {
|
||||
let mut db = self.db.write().unwrap();
|
||||
let spent = &mut db.coins.get_mut(op).unwrap();
|
||||
assert!(spent.spend_txid.is_some());
|
||||
assert!(spent.spend_block_time.is_none());
|
||||
assert!(spent.spend_block.is_none());
|
||||
spent.spend_txid = Some(*spend_txid);
|
||||
spent.spend_block_time = Some(*time);
|
||||
spent.spend_block = Some(SpendBlock {
|
||||
height: *height,
|
||||
time: *time,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user