Skip to content

Commit 641b400

Browse files
committed
Merge bitcoindevkit#2195: fix(electrum): do not pick unindexed outputs for history lookup
2e3d52e fix(electrum): do not pick unindexed outputs for history lookup (Zoe Faltibà) Pull request description: ### Description `BdkElectrumClient::populate_with_txids` queries each transaction's confirmation status by calling `script_get_history` on the script of one of its outputs. It currently picks the first output unconditionally. This breaks for transactions which first output is an `OP_RETURN`, because Electrum servers don't index `OP_RETURN` scripts and will return an empty history. This is a real-world scenario: protocols like RGB place an `OP_RETURN` commitment as the first output of every transaction. ### Notes to the reviewers The fix selects the first output which script is not `OP_RETURN` or a `OP_FALSE OP_RETURN`. If a transaction has only `OP_RETURN`/`OP_FALSE OP_RETURN` outputs, we fall back to the script of any input's previous output to query history. The only case still skipped is a coinbase with all unindexed outputs, since coinbases have no parent to fall back on. ### Changelog notice ``` Fixed: - `BdkElectrumClient::sync` now correctly retrieves confirmation status for transactions which first output is an `OP_RETURN` or `OP_FALSE OP_RETURN` ``` ### Checklists #### All Submissions: * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md) ACKs for top commit: oleonardolima: ACK 2e3d52e evanlinjin: ACK 2e3d52e Tree-SHA512: d87a0580c0db4e86ee6fce84a82ac56ef70378b46405bb54ca33f1a2a643feba6fa769c0ba2a9e01c77cc6308c777ccc34f89d548e46a1bbf036e31d8c3ddcd0
2 parents 6a2c301 + 2e3d52e commit 641b400

1 file changed

Lines changed: 38 additions & 7 deletions

File tree

crates/electrum/src/bdk_electrum_client.rs

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
use bdk_core::{
2-
bitcoin::{block::Header, BlockHash, OutPoint, Transaction, Txid},
2+
bitcoin::{
3+
block::Header,
4+
opcodes::{all::OP_RETURN, OP_FALSE},
5+
BlockHash, OutPoint, Transaction, Txid,
6+
},
37
collections::{BTreeMap, HashMap, HashSet},
48
spk_client::{
59
FullScanRequest, FullScanResponse, SpkWithExpectedTxids, SyncRequest, SyncResponse,
@@ -443,12 +447,39 @@ impl<E: ElectrumApi> BdkElectrumClient<E> {
443447
for txid in txids {
444448
match self.fetch_tx(txid) {
445449
Ok(tx) => {
446-
let spk = tx
447-
.output
448-
.first()
449-
.map(|txo| &txo.script_pubkey)
450-
.expect("tx must have an output")
451-
.clone();
450+
// pick the first output Electrum will return history for
451+
let mut spk = tx.output.iter().find_map(|txo| {
452+
let script = &txo.script_pubkey;
453+
(!script.is_op_return()
454+
&& !script
455+
.as_bytes()
456+
.starts_with(&[OP_FALSE.to_u8(), OP_RETURN.to_u8()]))
457+
.then(|| script.clone())
458+
});
459+
460+
// fallback: if no output is indexable, use the spk of any input's
461+
// previous output, its history includes our tx since we spend from it
462+
if spk.is_none() && !tx.is_coinbase() {
463+
for txin in &tx.input {
464+
match self.fetch_tx(txin.previous_output.txid) {
465+
Ok(parent) => {
466+
if let Some(prev_out) =
467+
parent.output.get(txin.previous_output.vout as usize)
468+
{
469+
spk = Some(prev_out.script_pubkey.clone());
470+
break;
471+
}
472+
}
473+
Err(electrum_client::Error::Protocol(_)) => continue,
474+
Err(e) => return Err(e),
475+
}
476+
}
477+
}
478+
479+
let spk = match spk {
480+
Some(spk) => spk,
481+
None => continue,
482+
};
452483
txs.push((txid, tx));
453484
scripts.push(spk);
454485
}

0 commit comments

Comments
 (0)