Fix: Use witness-aware vsize calculation in _fundTxElectrum.

This commit is contained in:
gerlofvanek
2026-01-31 22:58:16 +01:00
parent a456a15b8d
commit d5a6d63e0b

View File

@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2024 tecnovert
# Copyright (c) 2024-2025 The Basicswap developers
# Copyright (c) 2024-2026 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -1160,7 +1160,7 @@ class BTCInterface(Secp256k1Interface):
def getScriptDummyWitness(self, script: bytes) -> List[bytes]:
if self.isScriptP2WPKH(script):
return [bytes(72), bytes(33)]
return self.getP2WPKHDummyWitness()
raise ValueError("Unknown script type")
def createSCLockRefundTx(
@@ -1866,20 +1866,35 @@ class BTCInterface(Secp256k1Interface):
funded_tx = CTransaction()
funded_tx.nVersion = self.txVersion()
dummy_witness_stack = []
for utxo in selected_utxos:
txid_bytes = bytes.fromhex(utxo["txid"])[::-1]
txid_int = int.from_bytes(txid_bytes, "little")
funded_tx.vin.append(
CTxIn(COutPoint(txid_int, utxo["vout"]), nSequence=0xFFFFFFFD)
)
dummy_witness_stack.append(self.getP2WPKHDummyWitness())
for out in parsed_tx.vout:
funded_tx.vout.append(out)
final_vsize = (
final_vsize_simple = (
10 + (len(funded_tx.vout) + 1) * 34 + len(selected_utxos) * input_vsize
)
final_fee = final_vsize * fee_per_vbyte
witness_bytes_len_est: int = self.getWitnessStackSerialisedLength(
dummy_witness_stack
)
final_vsize = self.getTxVSize(
funded_tx, add_witness_bytes=witness_bytes_len_est
)
self._log.warning(
f"[rm] inputs={len(dummy_witness_stack)}, witness_bytes_est={witness_bytes_len_est}, "
f"vsize_simple={final_vsize_simple}, vsize_witness={final_vsize}"
)
# Use feerate in sat/kB directly: fee = round(fee_rate * vsize / 1000)
feerate_satkb = feerate if isinstance(feerate, int) else int(feerate * 100000000)
final_fee = round(feerate_satkb * final_vsize / 1000)
min_relay_fee = 250
final_fee = max(final_fee, min_relay_fee)
@@ -1895,11 +1910,15 @@ class BTCInterface(Secp256k1Interface):
change_script = self.getScriptForPubkeyHash(pkh)
funded_tx.vout.append(self.txoType()(change, change_script))
else:
final_vsize = (
10 + len(funded_tx.vout) * 34 + len(selected_utxos) * input_vsize
# Recalculate vsize without change output using witness-aware method
final_vsize = self.getTxVSize(
funded_tx, add_witness_bytes=witness_bytes_len_est
)
self._log.warning(
f"[rm] no change: inputs={len(dummy_witness_stack)}, vsize_witness={final_vsize}"
)
min_relay_fee = 250
final_fee = max(final_vsize * fee_per_vbyte, min_relay_fee)
final_fee = max(round(feerate_satkb * final_vsize / 1000), min_relay_fee)
change = 0 # Goes to fees
for utxo in selected_utxos:
@@ -1923,10 +1942,10 @@ class BTCInterface(Secp256k1Interface):
f"fee={final_fee}, change={change}"
)
self._log.info_s(
"_fundTxElectrum tx amount, vsize, feerate: %ld, %ld, %ld",
"_fundTxElectrum tx amount, vsize, feerate(sat/kB): %ld, %ld, %ld",
total_output,
final_vsize,
fee_per_vbyte,
feerate_satkb,
)
return tx_serialized
@@ -3156,7 +3175,14 @@ class BTCInterface(Secp256k1Interface):
raise ValueError("Unimplemented")
def getWitnessStackSerialisedLength(self, witness_stack):
length = getCompactSizeLen(len(witness_stack))
length: int = 0
if len(witness_stack) > 0 and isinstance(witness_stack[0], list):
for input_stack in witness_stack:
length += getCompactSizeLen(len(input_stack))
for e in input_stack:
length += getWitnessElementLen(len(e))
else:
length += getCompactSizeLen(len(witness_stack))
for e in witness_stack:
length += getWitnessElementLen(len(e))