Version: observed on aa6de36c (0.7.99), mainnet, Esplora over the unix socket. crates/rbitcoin-esplora/src/tx_json.rs is identical on origin/master 1aece752.
Related: #814 (mempool support), #632 (0.8 electrs drop-in). COMPAT.md describes the 0.8 goal as "drop-in mempool/electrs or Blockstream electrs HTTP". None of the fields below is listed there as a deliberate difference.
What happens
For every input, /tx/:txid (and every route that builds tx JSON: /block/:h/txs, /address/:a/txs, …) does the following:
- It always emits
"witness": [...], including "witness": [] for inputs that have no witness. electrs leaves the key out.
- It emits
inner_redeemscript_asm whenever the scriptSig's last push is non-empty, whatever the prevout type, by decoding that push as a script. For a P2PKH input this is the pubkey, and for a coinbase it is the coinbase data. electrs emits it only when the prevout is P2SH.
- It emits
inner_witnessscript_asm whenever the witness has 2 or more items and the last item does not start with 0x30, by decoding the last item as a script:
- P2WPKH and P2SH-P2WPKH: the pubkey is decoded as a script. electrs emits nothing.
- P2TR script path: the control block (last item) is decoded. mempool/electrs decodes the tapscript, which is the second-to-last item, or third-to-last when there is an annex. Blockstream electrs emits nothing for P2TR.
The output is garbage asm such as OP_PUSHBYTES_40 <push past end>, and it breaks byte-for-byte comparison with electrs. mempool's frontend shows these fields in the tx details view.
Evidence
Our node vs mempool.space, same txids. prevout is removed for brevity, and long strings are cut with … by jq.
P2PKH input: 6d2a0f0eb72eb8f87946b6f7344f70197ee0a5b78743b4c9f26c882301d8e944 vin 0.
$ curl -s --unix-socket /run/rbitcoin/esplora.sock http://x/tx/6d2a0f0e…e944 | jq -c '.vin[0] | del(.prevout)'
{"inner_redeemscript_asm":"OP_PUSHBYTES_3 f2fd4e OP_EQUAL OP_PUSHBYTES_40 <push past end>","is_coinbase":false,"scriptsig":"47304402…","scriptsig_asm":"OP_PUSHBYTES_71 304402…","sequence":4294967295,"txid":"c8c1a80b…","vout":4,"witness":[]}
$ curl -s https://mempool.space/api/tx/6d2a0f0e…e944 | jq -c '.vin[0] | keys'
["is_coinbase","prevout","scriptsig","scriptsig_asm","sequence","txid","vout"]
P2WPKH input: 1da61ed9c7323d42bc8355c165ab94688aa7d86ec23a49f8834b31736b299e58 vin 0.
rbitcoin: "inner_witnessscript_asm": "OP_PUSHBYTES_2 c3fb OP_RETURN_236 OP_RETURN_219 OP_RESERVED1 OP_PUSHBYTES_2 5cf6 OP_PUSHBYTES_40 <push past end>"
mempool.space: keys = ["is_coinbase","prevout","scriptsig","scriptsig_asm","sequence","txid","vout","witness"] (no inner_* key)
P2SH-P2WPKH input: 2d8e0faae34eb69c3fed808acb99071d988ec2dca0514ac659bf8d6fddbb11c3 vin 0. inner_redeemscript_asm (OP_0 OP_PUSHBYTES_20 29ee…) is correct, but rbitcoin also emits
"inner_witnessscript_asm": "OP_PUSHBYTES_3 3c643f OP_RETURN_194 OP_PUSHBYTES_6 e1f4d06adc46 … OP_PUSHBYTES_64 <push past end>"
Coinbase: 87ee4721c4d918ae883b20aff82b8a645ecad7ff526aa6e474939a0def642c83 (height 969500).
{"is_coinbase":true,"inner_redeemscript_asm":"OP_PUSHBYTES_52 <push past end>","witness":["0000…0000"]}
P2TR script path: the first inscription reveal, 6fb976ab49dcec017f1e201e84395983204ae1a7c2abf7ced0a85d692e442799, witness [sig, tapscript, control block].
rbitcoin: "inner_witnessscript_asm": "OP_RETURN_192 OP_PUSHBYTES_74 <push past end>" # control block c04a3c… decoded
mempool.space: "inner_witnessscript_asm": "OP_PUSHBYTES_32 4a3ca2cf…0cc5 OP_CHECKSIG OP_0 OP_IF OP_PUSHBYTES_3 6f7264 OP_PUSHBYTES_1 01 OP_PUSHBYTES_9 696d6167652f706e67 OP_0 OP_PUSHDATA2 8950…"
We compared 487 requests between a mempool instance backed by rbitcoin and one backed by Core+electrs. 82 of the 104 mismatches were Esplora-shape differences: these fields, plus the paging, --max-sh-creates and summary differences filed separately.
Source
crates/rbitcoin-esplora/src/tx_json.rs at 1aece752 (same lines at aa6de36):
:311-312: vin_obj["witness"] = json!(wit); runs unconditionally.
:314-319: both inner_* helpers are called without looking at the prevout script type, although the prevout is resolved a few lines later (prevout_json, :323).
:444-450: inner_redeemscript_asm decodes the last scriptSig push.
:457-470: inner_witnessscript_asm decodes the last witness item unless it starts with 0x30. It has no P2TR handling.
Reference:
- Blockstream electrs
new-index @ cb7c72f9, src/rest.rs:218-249: witness is Option with skip_serializing_if, and is None when the witness is empty.
- Blockstream electrs
src/util/script.rs:44-81 (get_innerscripts): redeem script only if prevout.is_p2sh(), witness script only if the prevout is P2WSH or the redeem script is P2WSH.
- mempool/electrs
mempool @ 9b14438a, src/util/script.rs:141-187: adds P2TR, taking the second-to-last witness item, or third-to-last when the last item starts with 0x50 (annex).
Suggested fix
Decide on the prevout's script_pubkey, which is already available as prev_spks/prevout_json:
witness: omit the key when tin.witness.is_empty(). Note that the pruned-path tests at tx_json.rs:951,958 already assert that the key is absent there.
inner_redeemscript_asm: only when the prevout is P2SH.
inner_witnessscript_asm: when the prevout is P2WSH, or P2SH whose redeem script is P2WSH: the last witness item. When the prevout is P2TR with at least 2 items after stripping the annex: the second-to-last item, as mempool/electrs does.
For a mempool-only input with an unknown prevout, electrs emits neither field.
Workaround
Clients have to normalise; our own backend strips these fields before re-serving them. Anything reading rbitcoin's Esplora directly (mempool's /api/tx passthrough, wallets, explorers) sees the extra fields.
Version: observed on
aa6de36c(0.7.99), mainnet, Esplora over the unix socket.crates/rbitcoin-esplora/src/tx_json.rsis identical onorigin/master1aece752.Related: #814 (mempool support), #632 (0.8 electrs drop-in). COMPAT.md describes the 0.8 goal as "drop-in mempool/electrs or Blockstream electrs HTTP". None of the fields below is listed there as a deliberate difference.
What happens
For every input,
/tx/:txid(and every route that builds tx JSON:/block/:h/txs,/address/:a/txs, …) does the following:"witness": [...], including"witness": []for inputs that have no witness. electrs leaves the key out.inner_redeemscript_asmwhenever the scriptSig's last push is non-empty, whatever the prevout type, by decoding that push as a script. For a P2PKH input this is the pubkey, and for a coinbase it is the coinbase data. electrs emits it only when the prevout is P2SH.inner_witnessscript_asmwhenever the witness has 2 or more items and the last item does not start with0x30, by decoding the last item as a script:The output is garbage asm such as
OP_PUSHBYTES_40 <push past end>, and it breaks byte-for-byte comparison with electrs. mempool's frontend shows these fields in the tx details view.Evidence
Our node vs mempool.space, same txids.
prevoutis removed for brevity, and long strings are cut with…by jq.P2PKH input:
6d2a0f0eb72eb8f87946b6f7344f70197ee0a5b78743b4c9f26c882301d8e944vin 0.P2WPKH input:
1da61ed9c7323d42bc8355c165ab94688aa7d86ec23a49f8834b31736b299e58vin 0.P2SH-P2WPKH input:
2d8e0faae34eb69c3fed808acb99071d988ec2dca0514ac659bf8d6fddbb11c3vin 0.inner_redeemscript_asm(OP_0 OP_PUSHBYTES_20 29ee…) is correct, but rbitcoin also emitsCoinbase:
87ee4721c4d918ae883b20aff82b8a645ecad7ff526aa6e474939a0def642c83(height 969500).P2TR script path: the first inscription reveal,
6fb976ab49dcec017f1e201e84395983204ae1a7c2abf7ced0a85d692e442799, witness[sig, tapscript, control block].We compared 487 requests between a mempool instance backed by rbitcoin and one backed by Core+electrs. 82 of the 104 mismatches were Esplora-shape differences: these fields, plus the paging,
--max-sh-createsand summary differences filed separately.Source
crates/rbitcoin-esplora/src/tx_json.rsat1aece752(same lines ataa6de36)::311-312:vin_obj["witness"] = json!(wit);runs unconditionally.:314-319: bothinner_*helpers are called without looking at the prevout script type, although the prevout is resolved a few lines later (prevout_json,:323).:444-450:inner_redeemscript_asmdecodes the last scriptSig push.:457-470:inner_witnessscript_asmdecodes the last witness item unless it starts with0x30. It has no P2TR handling.Reference:
new-index@cb7c72f9,src/rest.rs:218-249:witnessisOptionwithskip_serializing_if, and isNonewhen the witness is empty.src/util/script.rs:44-81(get_innerscripts): redeem script only ifprevout.is_p2sh(), witness script only if the prevout is P2WSH or the redeem script is P2WSH.mempool@9b14438a,src/util/script.rs:141-187: adds P2TR, taking the second-to-last witness item, or third-to-last when the last item starts with0x50(annex).Suggested fix
Decide on the prevout's
script_pubkey, which is already available asprev_spks/prevout_json:witness: omit the key whentin.witness.is_empty(). Note that the pruned-path tests attx_json.rs:951,958already assert that the key is absent there.inner_redeemscript_asm: only when the prevout is P2SH.inner_witnessscript_asm: when the prevout is P2WSH, or P2SH whose redeem script is P2WSH: the last witness item. When the prevout is P2TR with at least 2 items after stripping the annex: the second-to-last item, as mempool/electrs does.For a mempool-only input with an unknown prevout, electrs emits neither field.
Workaround
Clients have to normalise; our own backend strips these fields before re-serving them. Anything reading rbitcoin's Esplora directly (mempool's
/api/txpassthrough, wallets, explorers) sees the extra fields.