bitcoind: filter received coins based on parent descriptors
Our bitcoind watchonly wallet could, maybe, have other descriptors that were imported. Sounds pretty unlikely since we use a dedicated wallet but hey. More importantly, we'll need to know the parent descriptor of the coin in order to recognize it as newly received or change.
This commit is contained in:
parent
ba4c1e0383
commit
7a18c583cb
@ -10,7 +10,7 @@ use jsonrpc::{
|
||||
client::Client,
|
||||
simple_http::{self, SimpleHttpTransport},
|
||||
};
|
||||
use miniscript::bitcoin;
|
||||
use miniscript::{bitcoin, descriptor};
|
||||
|
||||
use serde_json::Value as Json;
|
||||
|
||||
@ -532,8 +532,8 @@ impl BitcoinD {
|
||||
Json::String(block_hash.to_string()),
|
||||
Json::Number(1.into()), // Default for min_confirmations for the returned
|
||||
Json::Bool(true), // Whether to include watchonly
|
||||
Json::Bool(false), // Whether to include an array of txs that were removed in reorgs
|
||||
Json::Bool(true) // Whether to include UTxOs treated as change.
|
||||
Json::Bool(false), // Whether to include an array of txs that were removed in reorgs
|
||||
Json::Bool(true) // Whether to include UTxOs treated as change.
|
||||
),
|
||||
)
|
||||
.into()
|
||||
@ -714,6 +714,7 @@ pub struct LSBlockEntry {
|
||||
pub amount: bitcoin::Amount,
|
||||
pub block_height: Option<i32>,
|
||||
pub address: bitcoin::Address,
|
||||
pub parent_descs: Vec<descriptor::Descriptor<descriptor::DescriptorPublicKey>>,
|
||||
}
|
||||
|
||||
impl From<&Json> for LSBlockEntry {
|
||||
@ -745,12 +746,26 @@ impl From<&Json> for LSBlockEntry {
|
||||
.and_then(Json::as_str)
|
||||
.and_then(|s| bitcoin::Address::from_str(s).ok())
|
||||
.expect("bitcoind can't give a bad address");
|
||||
let parent_descs = json
|
||||
.get("parent_descs")
|
||||
.and_then(Json::as_array)
|
||||
.and_then(|descs| {
|
||||
descs
|
||||
.iter()
|
||||
.map(|desc| {
|
||||
desc.as_str()
|
||||
.and_then(|s| descriptor::Descriptor::<_>::from_str(s).ok())
|
||||
})
|
||||
.collect::<Option<Vec<_>>>()
|
||||
})
|
||||
.expect("bitcoind can't give invalid descriptors");
|
||||
|
||||
LSBlockEntry {
|
||||
outpoint,
|
||||
amount,
|
||||
block_height,
|
||||
address,
|
||||
parent_descs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,7 +4,10 @@
|
||||
pub mod d;
|
||||
pub mod poller;
|
||||
|
||||
use d::{BitcoindError, LSBlockEntry};
|
||||
use crate::{
|
||||
bitcoin::d::{BitcoindError, LSBlockEntry},
|
||||
descriptors,
|
||||
};
|
||||
|
||||
use std::{collections::HashMap, error, fmt, sync};
|
||||
|
||||
@ -56,7 +59,11 @@ pub trait BitcoinInterface: Send {
|
||||
fn is_in_chain(&self, tip: &BlockChainTip) -> bool;
|
||||
|
||||
/// Get coins received since the specified tip.
|
||||
fn received_coins(&self, tip: &BlockChainTip) -> Vec<UTxO>;
|
||||
fn received_coins(
|
||||
&self,
|
||||
tip: &BlockChainTip,
|
||||
desc: &descriptors::InheritanceDescriptor,
|
||||
) -> Vec<UTxO>;
|
||||
|
||||
/// Get all coins that were confirmed, and at what height and time.
|
||||
fn confirmed_coins(
|
||||
@ -106,25 +113,34 @@ impl BitcoinInterface for d::BitcoinD {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn received_coins(&self, tip: &BlockChainTip) -> Vec<UTxO> {
|
||||
fn received_coins(
|
||||
&self,
|
||||
tip: &BlockChainTip,
|
||||
desc: &descriptors::InheritanceDescriptor,
|
||||
) -> Vec<UTxO> {
|
||||
// TODO: don't assume only a single descriptor is loaded on the wo wallet
|
||||
let lsb_res = self.list_since_block(&tip.hash);
|
||||
|
||||
lsb_res
|
||||
.received_coins
|
||||
.into_iter()
|
||||
.map(|entry| {
|
||||
.filter_map(|entry| {
|
||||
let LSBlockEntry {
|
||||
outpoint,
|
||||
amount,
|
||||
block_height,
|
||||
address,
|
||||
parent_descs,
|
||||
} = entry;
|
||||
UTxO {
|
||||
outpoint,
|
||||
amount,
|
||||
block_height,
|
||||
address,
|
||||
if parent_descs.iter().any(|parent_desc| desc == parent_desc) {
|
||||
Some(UTxO {
|
||||
outpoint,
|
||||
amount,
|
||||
block_height,
|
||||
address,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
@ -287,8 +303,12 @@ impl BitcoinInterface for sync::Arc<sync::Mutex<dyn BitcoinInterface + 'static>>
|
||||
self.lock().unwrap().is_in_chain(tip)
|
||||
}
|
||||
|
||||
fn received_coins(&self, tip: &BlockChainTip) -> Vec<UTxO> {
|
||||
self.lock().unwrap().received_coins(tip)
|
||||
fn received_coins(
|
||||
&self,
|
||||
tip: &BlockChainTip,
|
||||
desc: &descriptors::InheritanceDescriptor,
|
||||
) -> Vec<UTxO> {
|
||||
self.lock().unwrap().received_coins(tip, desc)
|
||||
}
|
||||
|
||||
fn confirmed_coins(
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
use crate::{
|
||||
bitcoin::{BitcoinInterface, BlockChainTip, UTxO},
|
||||
database::{Coin, DatabaseConnection, DatabaseInterface},
|
||||
descriptors,
|
||||
};
|
||||
|
||||
use std::{
|
||||
@ -26,13 +27,14 @@ fn update_coins(
|
||||
bit: &impl BitcoinInterface,
|
||||
db_conn: &mut Box<dyn DatabaseConnection>,
|
||||
previous_tip: &BlockChainTip,
|
||||
desc: &descriptors::InheritanceDescriptor,
|
||||
) -> UpdatedCoins {
|
||||
let curr_coins = db_conn.coins();
|
||||
log::debug!("Current coins: {:?}", curr_coins);
|
||||
|
||||
// Start by fetching newly received coins.
|
||||
let mut received = Vec::new();
|
||||
for utxo in bit.received_coins(previous_tip) {
|
||||
for utxo in bit.received_coins(previous_tip, desc) {
|
||||
if let Some(derivation_index) = db_conn.derivation_index_by_address(&utxo.address) {
|
||||
if !curr_coins.contains_key(&utxo.outpoint) {
|
||||
let UTxO {
|
||||
@ -154,7 +156,11 @@ fn new_tip(bit: &impl BitcoinInterface, current_tip: &BlockChainTip) -> TipUpdat
|
||||
TipUpdate::Reorged(common_ancestor)
|
||||
}
|
||||
|
||||
fn updates(bit: &impl BitcoinInterface, db: &impl DatabaseInterface) {
|
||||
fn updates(
|
||||
bit: &impl BitcoinInterface,
|
||||
db: &impl DatabaseInterface,
|
||||
desc: &descriptors::InheritanceDescriptor,
|
||||
) {
|
||||
let mut db_conn = db.connection();
|
||||
|
||||
// Check if there was a new block before updating ourselves.
|
||||
@ -167,18 +173,18 @@ fn updates(bit: &impl BitcoinInterface, db: &impl DatabaseInterface) {
|
||||
// between our former chain and the new one, then restart fresh.
|
||||
db_conn.rollback_tip(&new_tip);
|
||||
log::info!("Tip was rolled back to '{}'.", new_tip);
|
||||
return updates(bit, db);
|
||||
return updates(bit, db, desc);
|
||||
}
|
||||
};
|
||||
|
||||
// Then check the state of our coins. Do it even if the tip did not change since last poll, as
|
||||
// we may have unconfirmed transactions.
|
||||
let updated_coins = update_coins(bit, &mut db_conn, ¤t_tip);
|
||||
let updated_coins = update_coins(bit, &mut db_conn, ¤t_tip, desc);
|
||||
|
||||
// If the tip changed while we were polling our Bitcoin interface, start over.
|
||||
if bit.chain_tip() != latest_tip {
|
||||
log::info!("Chain tip changed while we were updating our state. Starting over.");
|
||||
return updates(bit, db);
|
||||
return updates(bit, db, desc);
|
||||
}
|
||||
|
||||
// The chain tip did not change since we started our updates. Record them and the latest tip.
|
||||
@ -213,6 +219,7 @@ pub fn looper(
|
||||
db: sync::Arc<sync::Mutex<dyn DatabaseInterface>>,
|
||||
shutdown: sync::Arc<atomic::AtomicBool>,
|
||||
poll_interval: time::Duration,
|
||||
desc: descriptors::InheritanceDescriptor,
|
||||
) {
|
||||
let mut last_poll = None;
|
||||
let mut synced = false;
|
||||
@ -247,6 +254,6 @@ pub fn looper(
|
||||
}
|
||||
}
|
||||
|
||||
updates(&bit, &db);
|
||||
updates(&bit, &db, &desc);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ mod looper;
|
||||
use crate::{
|
||||
bitcoin::{poller::looper::looper, BitcoinInterface},
|
||||
database::DatabaseInterface,
|
||||
descriptors,
|
||||
};
|
||||
|
||||
use std::{
|
||||
@ -21,13 +22,14 @@ impl Poller {
|
||||
bit: sync::Arc<sync::Mutex<dyn BitcoinInterface>>,
|
||||
db: sync::Arc<sync::Mutex<dyn DatabaseInterface>>,
|
||||
poll_interval: time::Duration,
|
||||
desc: descriptors::InheritanceDescriptor,
|
||||
) -> Poller {
|
||||
let shutdown = sync::Arc::from(atomic::AtomicBool::from(false));
|
||||
let handle = thread::Builder::new()
|
||||
.name("Bitcoin poller".to_string())
|
||||
.spawn({
|
||||
let shutdown = shutdown.clone();
|
||||
move || looper(bit, db, shutdown, poll_interval)
|
||||
move || looper(bit, db, shutdown, poll_interval, desc)
|
||||
})
|
||||
.expect("Must not fail");
|
||||
|
||||
|
||||
@ -263,6 +263,12 @@ impl str::FromStr for InheritanceDescriptor {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<descriptor::Descriptor<descriptor::DescriptorPublicKey>> for InheritanceDescriptor {
|
||||
fn eq(&self, other: &descriptor::Descriptor<descriptor::DescriptorPublicKey>) -> bool {
|
||||
self.0.eq(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl InheritanceDescriptor {
|
||||
pub fn new(
|
||||
owner_key: descriptor::DescriptorPublicKey,
|
||||
|
||||
@ -317,6 +317,7 @@ impl DaemonHandle {
|
||||
bit.clone(),
|
||||
db.clone(),
|
||||
config.bitcoin_config.poll_interval_secs,
|
||||
config.main_descriptor.clone(),
|
||||
);
|
||||
|
||||
// Finally, set up the API.
|
||||
|
||||
@ -2,7 +2,7 @@ use crate::{
|
||||
bitcoin::{BitcoinError, BitcoinInterface, BlockChainTip, UTxO},
|
||||
config::{BitcoinConfig, Config},
|
||||
database::{Coin, DatabaseConnection, DatabaseInterface, SpendBlock},
|
||||
DaemonHandle,
|
||||
descriptors, DaemonHandle,
|
||||
};
|
||||
|
||||
use std::{collections::HashMap, env, fs, io, path, process, str::FromStr, sync, thread, time};
|
||||
@ -44,7 +44,11 @@ impl BitcoinInterface for DummyBitcoind {
|
||||
true
|
||||
}
|
||||
|
||||
fn received_coins(&self, _: &BlockChainTip) -> Vec<UTxO> {
|
||||
fn received_coins(
|
||||
&self,
|
||||
_: &BlockChainTip,
|
||||
_: &descriptors::InheritanceDescriptor,
|
||||
) -> Vec<UTxO> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user