Fix: Refund-path / Maturity checks.

This commit is contained in:
gerlofvanek
2026-05-29 18:36:05 +02:00
parent 5099b9ebaa
commit 221d962c12
2 changed files with 89 additions and 15 deletions
+34 -11
View File
@@ -5613,15 +5613,16 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
) )
# Check non-bip68 final # Check non-bip68 final
try: if not ci_from.useBackend():
txid = ci_from.publishTx(bid.initiate_txn_refund) try:
self.log.error( txid = ci_from.publishTx(bid.initiate_txn_refund)
f"Submit refund_txn unexpectedly worked {self.logIDT(bytes.fromhex(txid))}" self.log.error(
) f"Submit refund_txn unexpectedly worked {self.logIDT(bytes.fromhex(txid))}"
except Exception as ex: )
if ci_from.isTxNonFinalError(str(ex)) is False: except Exception as ex:
self.log.error(f"Submit refund_txn unexpected error: {ex}") if ci_from.isTxNonFinalError(str(ex)) is False:
raise ex self.log.error(f"Submit refund_txn unexpected error: {ex}")
raise ex
if txid is not None: if txid is not None:
msg_buf = BidAcceptMessage() msg_buf = BidAcceptMessage()
@@ -7686,7 +7687,16 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
self.saveBidInSession(bid_id, bid, cursor, xmr_swap) self.saveBidInSession(bid_id, bid, cursor, xmr_swap)
self.commitDB() self.commitDB()
if TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE not in bid.txns: if (
TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE not in bid.txns
and refund_tx.block_height is not None
and ci_from.isCsvLockMature(
offer.lock_type,
xmr_offer.lock_time_2,
refund_tx.block_height,
refund_tx.block_time,
)
):
try: try:
if self.haveDebugInd( if self.haveDebugInd(
bid.bid_id, bid.bid_id,
@@ -7782,6 +7792,13 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
if ( if (
len(xmr_swap.al_lock_refund_tx_sig) > 0 len(xmr_swap.al_lock_refund_tx_sig) > 0
and len(xmr_swap.af_lock_refund_tx_sig) > 0 and len(xmr_swap.af_lock_refund_tx_sig) > 0
and bid.xmr_a_lock_tx is not None
and ci_from.isCsvLockMature(
offer.lock_type,
xmr_offer.lock_time_1,
bid.xmr_a_lock_tx.block_height,
bid.xmr_a_lock_tx.block_time,
)
): ):
try: try:
@@ -8473,6 +8490,9 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
if ( if (
bid.getITxState() in (TxStates.TX_SENT, TxStates.TX_CONFIRMED) bid.getITxState() in (TxStates.TX_SENT, TxStates.TX_CONFIRMED)
and bid.initiate_txn_refund is not None and bid.initiate_txn_refund is not None
and ci_from.isAbsLockTimeMature(
ci_from.loadTx(bid.initiate_txn_refund).nLockTime
)
): ):
try: try:
txid = ci_from.publishTx(bid.initiate_txn_refund) txid = ci_from.publishTx(bid.initiate_txn_refund)
@@ -8496,6 +8516,9 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
if ( if (
bid.getPTxState() in (TxStates.TX_SENT, TxStates.TX_CONFIRMED) bid.getPTxState() in (TxStates.TX_SENT, TxStates.TX_CONFIRMED)
and bid.participate_txn_refund is not None and bid.participate_txn_refund is not None
and ci_to.isAbsLockTimeMature(
ci_to.loadTx(bid.participate_txn_refund).nLockTime
)
): ):
try: try:
txid = ci_to.publishTx(bid.participate_txn_refund) txid = ci_to.publishTx(bid.participate_txn_refund)
@@ -8511,7 +8534,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
) )
# State will update when spend is detected # State will update when spend is detected
except Exception as ex: except Exception as ex:
if ci_to.isTxNonFinalError(str(ex)): if ci_to.isTxNonFinalError(str(ex)) is False:
self.log.warning( self.log.warning(
f"Error trying to submit participate refund txn: {ex}" f"Error trying to submit participate refund txn: {ex}"
) )
+55 -4
View File
@@ -502,6 +502,57 @@ class BTCInterface(Secp256k1Interface):
return height return height
return self.rpc("getblockcount") return self.rpc("getblockcount")
def getChainMedianTime(self) -> int:
if self.useBackend():
height = self._backend.getBlockHeight()
times = []
for h in range(max(0, height - 10), height + 1):
header = self._getBlockHeaderFromHeightElectrum(h)
times.append(header["time"])
times.sort()
return times[len(times) // 2]
return self.rpc("getblockchaininfo")["mediantime"]
def isCsvLockMature(
self,
lock_type: int,
encoded_sequence: int,
parent_block_height: Optional[int],
parent_block_time: Optional[int],
chain_height: Optional[int] = None,
chain_mtp: Optional[int] = None,
) -> bool:
if parent_block_height is None or parent_block_height < 1:
return False
lock_value: int = self.decodeSequence(encoded_sequence)
if lock_type == TxLockTypes.SEQUENCE_LOCK_BLOCKS:
if chain_height is None:
chain_height = self.getChainHeight()
return chain_height + 1 >= parent_block_height + lock_value
if lock_type == TxLockTypes.SEQUENCE_LOCK_TIME:
if parent_block_time is None or parent_block_time < 1:
return False
if chain_mtp is None:
chain_mtp = self.getChainMedianTime()
return chain_mtp >= parent_block_time + lock_value
raise ValueError(f"Unknown lock type {lock_type}")
def isAbsLockTimeMature(
self,
nlocktime: int,
chain_height: Optional[int] = None,
chain_mtp: Optional[int] = None,
) -> bool:
if nlocktime == 0:
return True
if nlocktime < 500000000:
if chain_height is None:
chain_height = self.getChainHeight()
return chain_height + 1 >= nlocktime
if chain_mtp is None:
chain_mtp = self.getChainMedianTime()
return chain_mtp >= nlocktime
def getMempoolTx(self, txid): def getMempoolTx(self, txid):
if self._connection_type == "electrum": if self._connection_type == "electrum":
backend = self.getBackend() backend = self.getBackend()
@@ -4370,11 +4421,11 @@ class BTCInterface(Secp256k1Interface):
return "Transaction already in block chain" in err_str return "Transaction already in block chain" in err_str
def isTxNonFinalError(self, err_str: str) -> bool: def isTxNonFinalError(self, err_str: str) -> bool:
err_lower = err_str.lower()
return ( return (
"non-BIP68-final" in err_str "non-bip68-final" in err_lower
or "non-final" in err_str or "non-final" in err_lower
or "Missing inputs" in err_str or "locktime requirement not satisfied" in err_lower
or "bad-txns-inputs-missingorspent" in err_str
) )
def combine_non_segwit_prevouts(self): def combine_non_segwit_prevouts(self):