Skip to content

Commit c8ed4f4

Browse files
committed
bitcoind: import Miniscript descriptors instead of addresses
- Don't generate a set of addresses to be imported, import the ranged Miniscript descriptors directly. - Tell listsinceblock to list change, too (custom bitcoind patch). This is necessary because deposit addresses aren't part of bitcoind's address book anymore and we would otherwise fail to detect some deposits. - Use the 'wallet_desc' field in listsinceblock entries to track coins, instead of the former labels imported along with the addresses. This also cleans up the descriptor import routines.
1 parent 6abe197 commit c8ed4f4

File tree

2 files changed

+75
-161
lines changed

2 files changed

+75
-161
lines changed

src/bitcoind/interface.rs

Lines changed: 57 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,6 @@ use serde_json::Value as Json;
2424
// If bitcoind takes more than 3 minutes to answer one of our queries, fail.
2525
const RPC_SOCKET_TIMEOUT: u64 = 180;
2626

27-
// Labels used to tag utxos in the watchonly wallet
28-
const DEPOSIT_UTXOS_LABEL: &str = "revault-deposit";
29-
const UNVAULT_UTXOS_LABEL: &str = "revault-unvault";
30-
const CPFP_UTXOS_LABEL: &str = "revault-cpfp";
31-
3227
pub struct BitcoinD {
3328
node_client: Client,
3429
watchonly_client: Client,
@@ -350,32 +345,14 @@ impl BitcoinD {
350345
}
351346
}
352347

353-
/// Constructs an `addr()` descriptor out of an address
354-
pub fn addr_descriptor(&self, address: &str) -> Result<String, BitcoindError> {
355-
let desc_wo_checksum = format!("addr({})", address);
356-
357-
Ok(self
358-
.make_watchonly_request(
359-
"getdescriptorinfo",
360-
&params!(Json::String(desc_wo_checksum)),
361-
)?
362-
.get("descriptor")
363-
.expect("No 'descriptor' in 'getdescriptorinfo'")
364-
.as_str()
365-
.expect("'descriptor' in 'getdescriptorinfo' isn't a string anymore")
366-
.to_string())
367-
}
368-
369-
fn bulk_import_descriptors(
348+
fn import_descriptors(
370349
&self,
371350
client: &Client,
372351
descriptors: Vec<String>,
373-
timestamp: u32,
374-
label: String,
375-
fresh_wallet: bool,
352+
timestamp: Option<u32>,
376353
active: bool,
377354
) -> Result<(), BitcoindError> {
378-
if !fresh_wallet {
355+
if timestamp.is_some() {
379356
log::debug!("Not a fresh wallet, rescan *may* take some time.");
380357
}
381358

@@ -388,13 +365,11 @@ impl BitcoinD {
388365
// will rescan the last few blocks for each of them.
389366
desc_map.insert(
390367
"timestamp".to_string(),
391-
if fresh_wallet {
392-
Json::String("now".to_string())
393-
} else {
394-
Json::Number(serde_json::Number::from(timestamp))
395-
},
368+
timestamp
369+
.map(serde_json::Number::from)
370+
.map(Json::Number)
371+
.unwrap_or_else(|| Json::String("now".to_string())),
396372
);
397-
desc_map.insert("label".to_string(), Json::String(label.clone()));
398373
desc_map.insert("active".to_string(), Json::Bool(active));
399374

400375
Json::Object(desc_map)
@@ -424,84 +399,26 @@ impl BitcoinD {
424399
)))
425400
}
426401

427-
pub fn startup_import_deposit_descriptors(
428-
&self,
429-
descriptors: Vec<String>,
430-
timestamp: u32,
431-
fresh_wallet: bool,
432-
) -> Result<(), BitcoindError> {
433-
self.bulk_import_descriptors(
434-
&self.watchonly_client,
435-
descriptors,
436-
timestamp,
437-
DEPOSIT_UTXOS_LABEL.to_string(),
438-
fresh_wallet,
439-
false,
440-
)
441-
}
442-
443-
pub fn startup_import_unvault_descriptors(
402+
/// Import the deposit and Unvault descriptors, when at startup.
403+
pub fn startup_import_descriptors(
444404
&self,
445-
descriptors: Vec<String>,
446-
timestamp: u32,
447-
fresh_wallet: bool,
405+
descriptors: [String; 2],
406+
timestamp: Option<u32>,
448407
) -> Result<(), BitcoindError> {
449-
self.bulk_import_descriptors(
408+
self.import_descriptors(
450409
&self.watchonly_client,
451-
descriptors,
410+
descriptors.to_vec(),
452411
timestamp,
453-
UNVAULT_UTXOS_LABEL.to_string(),
454-
fresh_wallet,
455412
false,
456413
)
457414
}
458415

459-
pub fn startup_import_cpfp_descriptor(
460-
&self,
461-
descriptor: String,
462-
timestamp: u32,
463-
fresh_wallet: bool,
464-
) -> Result<(), BitcoindError> {
465-
self.bulk_import_descriptors(
466-
&self.cpfp_client,
467-
vec![descriptor],
468-
timestamp,
469-
CPFP_UTXOS_LABEL.to_string(),
470-
fresh_wallet,
471-
true,
472-
)
473-
}
474-
475-
fn import_fresh_descriptor(
416+
pub fn import_cpfp_descriptor(
476417
&self,
477418
descriptor: String,
478-
label: String,
419+
timestamp: Option<u32>,
479420
) -> Result<(), BitcoindError> {
480-
let mut desc_map = serde_json::Map::with_capacity(3);
481-
desc_map.insert("desc".to_string(), Json::String(descriptor));
482-
desc_map.insert("timestamp".to_string(), Json::String("now".to_string()));
483-
desc_map.insert("label".to_string(), Json::String(label));
484-
485-
let res = self.make_watchonly_request(
486-
"importdescriptors",
487-
&params!(Json::Array(vec![Json::Object(desc_map,)])),
488-
)?;
489-
if res.get(0).map(|x| x.get("success")).flatten() == Some(&Json::Bool(true)) {
490-
return Ok(());
491-
}
492-
493-
Err(BitcoindError::Custom(format!(
494-
"In import_fresh descriptor, no success returned from 'importdescriptor': {:?}",
495-
res
496-
)))
497-
}
498-
499-
pub fn import_fresh_deposit_descriptor(&self, descriptor: String) -> Result<(), BitcoindError> {
500-
self.import_fresh_descriptor(descriptor, DEPOSIT_UTXOS_LABEL.to_string())
501-
}
502-
503-
pub fn import_fresh_unvault_descriptor(&self, descriptor: String) -> Result<(), BitcoindError> {
504-
self.import_fresh_descriptor(descriptor, UNVAULT_UTXOS_LABEL.to_string())
421+
self.import_descriptors(&self.cpfp_client, vec![descriptor], timestamp, true)
505422
}
506423

507424
pub fn list_unspent_cpfp(&self) -> Result<Vec<ListUnspentEntry>, BitcoindError> {
@@ -559,15 +476,21 @@ impl BitcoinD {
559476
fn list_since_block(
560477
&self,
561478
tip: &BlockchainTip,
562-
label: Option<&'static str>,
479+
descriptor: Option<String>,
563480
) -> Result<Vec<ListSinceBlockTransaction>, BitcoindError> {
564481
let req = if tip.height == 0 {
565482
self.make_request(&self.watchonly_client, "listsinceblock", &params!())?
566483
} else {
567484
self.make_request(
568485
&self.watchonly_client,
569486
"listsinceblock",
570-
&params!(Json::String(tip.hash.to_string())),
487+
&params!(
488+
Json::String(tip.hash.to_string()),
489+
Json::Number(1.into()),
490+
Json::Bool(true),
491+
Json::Bool(true),
492+
Json::Bool(true)
493+
),
571494
)?
572495
};
573496
Ok(req
@@ -578,20 +501,26 @@ impl BitcoinD {
578501
.iter()
579502
.filter_map(|t| {
580503
let t = ListSinceBlockTransaction::from(t);
581-
if label.or_else(|| t.label.as_deref()) == t.label.as_deref() {
582-
Some(t)
583-
} else {
584-
None
504+
match descriptor {
505+
None => Some(t),
506+
Some(ref desc) => {
507+
if t.wallet_descs.contains(desc) {
508+
Some(t)
509+
} else {
510+
None
511+
}
512+
}
585513
}
586514
})
587515
.collect())
588516
}
589517

590-
pub fn list_deposits_since_block(
518+
fn list_deposits_since_block(
591519
&self,
592520
tip: &BlockchainTip,
521+
deposit_desc: String,
593522
) -> Result<Vec<ListSinceBlockTransaction>, BitcoindError> {
594-
self.list_since_block(tip, Some(DEPOSIT_UTXOS_LABEL))
523+
self.list_since_block(tip, Some(deposit_desc))
595524
}
596525

597526
/// Repeatedly called by our main loop to stay in sync with bitcoind.
@@ -601,6 +530,7 @@ impl BitcoinD {
601530
deposits_utxos: &HashMap<OutPoint, UtxoInfo>,
602531
db_tip: &BlockchainTip,
603532
min_conf: u32,
533+
deposit_desc: String,
604534
) -> Result<DepositsState, BitcoindError> {
605535
let (mut new_unconf, mut new_conf, mut new_spent) =
606536
(HashMap::new(), HashMap::new(), HashMap::new());
@@ -645,7 +575,7 @@ impl BitcoinD {
645575
}
646576

647577
// Second, we scan for new ones.
648-
let utxos = self.list_deposits_since_block(db_tip)?;
578+
let utxos = self.list_deposits_since_block(db_tip, deposit_desc)?;
649579
for utxo in utxos {
650580
if utxo.is_receive && deposits_utxos.get(&utxo.outpoint).is_none() {
651581
new_unconf.insert(
@@ -850,9 +780,6 @@ impl BitcoinD {
850780
continue;
851781
}
852782

853-
// TODO: i think we can also filter out the entries *with* a "revault-somthing" label,
854-
// but we need to be sure.
855-
856783
let spending_txid = transaction
857784
.get("txid")
858785
.map(|t| t.as_str())
@@ -1169,7 +1096,7 @@ pub struct ListSinceBlockTransaction {
11691096
pub outpoint: OutPoint,
11701097
pub txo: TxOut,
11711098
pub is_receive: bool,
1172-
pub label: Option<String>,
1099+
pub wallet_descs: Vec<String>,
11731100
pub confirmations: i32,
11741101
pub blockheight: Option<u32>,
11751102
}
@@ -1225,19 +1152,31 @@ impl From<&Json> for ListSinceBlockTransaction {
12251152
.map(|a| a.as_u64())
12261153
.flatten()
12271154
.map(|b| b as u32);
1228-
let label = j
1229-
.get("label")
1230-
.map(|l| l.as_str())
1231-
.flatten()
1232-
.map(|l| l.to_string());
1155+
// FIXME: allocs
1156+
let wallet_descs = j
1157+
.get("wallet_descs")
1158+
.map(|l| {
1159+
l.as_array()
1160+
.expect(
1161+
"API break, 'listsinceblock' entry didn't contain a valid 'wallet_descs'.",
1162+
)
1163+
.iter()
1164+
.map(|desc| {
1165+
desc.as_str()
1166+
.expect("Invalid desc string in 'listsinceblock'.")
1167+
.to_string()
1168+
})
1169+
.collect()
1170+
})
1171+
.unwrap_or_else(|| Vec::new());
12331172

12341173
ListSinceBlockTransaction {
12351174
outpoint: OutPoint {
12361175
txid,
12371176
vout: vout as u32, // Bitcoin makes this safe
12381177
},
12391178
is_receive,
1240-
label,
1179+
wallet_descs,
12411180
txo: TxOut {
12421181
value,
12431182
script_pubkey,

0 commit comments

Comments
 (0)