Merge pull request #489 from tecnovert/csv_check_fix

fix: get refund tx block info for CSV check
This commit is contained in:
tecnovert
2026-06-06 21:53:08 +00:00
committed by GitHub
42 changed files with 708 additions and 686 deletions
+1 -1
View File
@@ -92,7 +92,7 @@ jobs:
export PARTICL_BINDIR="$BIN_DIR/particl" export PARTICL_BINDIR="$BIN_DIR/particl"
export BITCOIN_BINDIR="$BIN_DIR/bitcoin" export BITCOIN_BINDIR="$BIN_DIR/bitcoin"
export XMR_BINDIR="$BIN_DIR/monero" export XMR_BINDIR="$BIN_DIR/monero"
pytest tests/basicswap/test_btc_xmr.py::TestBTC -k "test_003_api or test_02_a_leader_recover_a_lock_tx or test_11_fee_validation" pytest tests/basicswap/test_btc_xmr.py::TestBTC -k "test_003_api or test_02_a_leader_recover_a_lock_tx or test_03_a_follower_recover_a_lock_tx or test_11_fee_validation"
- name: Run test_encrypted_xmr_reload - name: Run test_encrypted_xmr_reload
id: test_encrypted_xmr_reload id: test_encrypted_xmr_reload
run: | run: |
+70 -19
View File
@@ -5552,8 +5552,8 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
use_cursor = self.openDB(cursor) use_cursor = self.openDB(cursor)
bid, offer = self.getBidAndOffer(bid_id, use_cursor) bid, offer = self.getBidAndOffer(bid_id, use_cursor)
ensure(bid, "Bid not found") ensure(bid, f"Bid not found: {self.log.id(bid_id)}.")
ensure(offer, "Offer not found") ensure(offer, f"Offer not found: {self.log.id(bid.offer_id)}.")
# Ensure bid is still valid # Ensure bid is still valid
now: int = self.getTime() now: int = self.getTime()
@@ -6828,8 +6828,8 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
try: try:
use_cursor = self.openDB(cursor) use_cursor = self.openDB(cursor)
bid, offer = self.getBidAndOffer(bid_id, use_cursor, with_txns=False) bid, offer = self.getBidAndOffer(bid_id, use_cursor, with_txns=False)
ensure(bid, "Bid not found") ensure(bid, f"Bid not found: {self.log.id(bid_id)}.")
ensure(offer, "Offer not found") ensure(offer, f"Offer not found: {self.log.id(bid.offer_id)}.")
bid.setState(new_state) bid.setState(new_state)
self.deactivateBid(use_cursor, offer, bid) self.deactivateBid(use_cursor, offer, bid)
@@ -7747,7 +7747,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
self.logBidEvent( self.logBidEvent(
bid.bid_id, bid.bid_id,
EventLogTypes.DEBUG_TWEAK_APPLIED, EventLogTypes.DEBUG_TWEAK_APPLIED,
"ind {}".format(bid.debug_ind), f"ind {bid.debug_ind}",
cursor, cursor,
) )
self.commitDB() self.commitDB()
@@ -7802,6 +7802,23 @@ 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 refund_tx.block_height is None:
self.log.debug(
f"A_LOCK_REFUND tx: {self.logIDT(refund_tx.txid)} block height not known, bid: {self.log.id(bid_id)}"
)
refund_tx_info = ci_from.getTxOutInfo(
refund_tx.txid, refund_tx.vout
)
if refund_tx_info:
refund_tx.block_hash = refund_tx_info["block_hash"]
refund_tx.block_height = refund_tx_info["block_height"]
refund_tx.block_time = refund_tx_info["block_time"]
self.log.debug(
f"Found A_LOCK_REFUND tx block height: {refund_tx.block_height}, time: {refund_tx.block_time}"
)
self.add(refund_tx, cursor, upsert=True)
self.commitDB()
if ( if (
TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE not in bid.txns TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE not in bid.txns
and refund_tx.block_height is not None and refund_tx.block_height is not None
@@ -7947,10 +7964,14 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
"", "",
cursor, cursor,
) )
refund_vout: int = ci_from.getLockRefundVout(
xmr_swap.a_lock_refund_tx, xmr_swap.vkbv
)
bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND] = SwapTx( bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND] = SwapTx(
bid_id=bid_id, bid_id=bid_id,
tx_type=TxTypes.XMR_SWAP_A_LOCK_REFUND, tx_type=TxTypes.XMR_SWAP_A_LOCK_REFUND,
txid=bytes.fromhex(txid), txid=bytes.fromhex(txid),
vout=refund_vout,
) )
self.saveBidInSession(bid_id, bid, cursor, xmr_swap) self.saveBidInSession(bid_id, bid, cursor, xmr_swap)
self.commitDB() self.commitDB()
@@ -7962,10 +7983,14 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
) )
txid = ci_from.getTxid(xmr_swap.a_lock_refund_tx) txid = ci_from.getTxid(xmr_swap.a_lock_refund_tx)
if TxTypes.XMR_SWAP_A_LOCK_REFUND not in bid.txns: if TxTypes.XMR_SWAP_A_LOCK_REFUND not in bid.txns:
refund_vout: int = ci_from.getLockRefundVout(
xmr_swap.a_lock_refund_tx, xmr_swap.vkbv
)
bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND] = SwapTx( bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND] = SwapTx(
bid_id=bid_id, bid_id=bid_id,
tx_type=TxTypes.XMR_SWAP_A_LOCK_REFUND, tx_type=TxTypes.XMR_SWAP_A_LOCK_REFUND,
txid=txid, txid=txid,
vout=refund_vout,
) )
self.saveBidInSession(bid_id, bid, cursor, xmr_swap) self.saveBidInSession(bid_id, bid, cursor, xmr_swap)
self.commitDB() self.commitDB()
@@ -8346,14 +8371,15 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
return rv return rv
def _isScriptRefundMature(self, ci, offer, refund_tx_bytes, parent_tx) -> bool: def _isScriptRefundMature(self, ci, offer, refund_tx_bytes, parent_tx) -> bool:
refund_tx = ci.loadTx(refund_tx_bytes)
if offer.lock_type in (TxLockTypes.ABS_LOCK_BLOCKS, TxLockTypes.ABS_LOCK_TIME): if offer.lock_type in (TxLockTypes.ABS_LOCK_BLOCKS, TxLockTypes.ABS_LOCK_TIME):
return ci.isAbsLockTimeMature(refund_tx.nLockTime) tx_locktime: int = ci.getTxLocktime(refund_tx_bytes)
return ci.isAbsLockTimeMature(tx_locktime)
if parent_tx is None or parent_tx.block_height is None: if parent_tx is None or parent_tx.block_height is None:
return False return False
txi_sequence: int = ci.getTxInSequence(refund_tx_bytes, 0)
return ci.isCsvLockMature( return ci.isCsvLockMature(
offer.lock_type, offer.lock_type,
refund_tx.vin[0].nSequence, txi_sequence,
parent_tx.block_height, parent_tx.block_height,
parent_tx.block_time, parent_tx.block_time,
) )
@@ -8641,12 +8667,33 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
f"Error trying to submit initiate refund txn: {ex}" f"Error trying to submit initiate refund txn: {ex}"
) )
if ( should_try_refund_ptx: bool = (
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 self._isScriptRefundMature(
ci_to, offer, bid.participate_txn_refund, bid.participate_tx
) )
if (
should_try_refund_ptx
and bid.participate_tx is not None
and bid.participate_tx.block_height is None
):
self.log.debug(
f"PTX: {self.logIDT(bid.participate_tx.txid)} block height not known, bid: {self.log.id(bid_id)}"
)
# An invalid ptx, won't be confirmed, check block height here
ptx_info = ci_to.getTxOutInfo(
bid.participate_tx.txid, bid.participate_tx.vout
)
if ptx_info:
bid.participate_tx.block_hash = ptx_info["block_hash"]
bid.participate_tx.block_height = ptx_info["block_height"]
bid.participate_tx.block_time = ptx_info["block_time"]
self.log.debug(
f"Found PTX block height: {bid.participate_tx.block_height}, time: {bid.participate_tx.block_time}"
)
self.saveBid(bid_id, bid)
if should_try_refund_ptx and self._isScriptRefundMature(
ci_to, offer, bid.participate_txn_refund, bid.participate_tx
): ):
try: try:
txid = ci_to.publishTx(bid.participate_txn_refund) txid = ci_to.publishTx(bid.participate_txn_refund)
@@ -9017,10 +9064,14 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
) )
if TxTypes.XMR_SWAP_A_LOCK_REFUND not in bid.txns: if TxTypes.XMR_SWAP_A_LOCK_REFUND not in bid.txns:
refund_vout: int = ci_from.getLockRefundVout(
bytes.fromhex(spend_txn_hex), xmr_swap.vkbv
)
bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND] = SwapTx( bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND] = SwapTx(
bid_id=bid.bid_id, bid_id=bid.bid_id,
tx_type=TxTypes.XMR_SWAP_A_LOCK_REFUND, tx_type=TxTypes.XMR_SWAP_A_LOCK_REFUND,
txid=xmr_swap.a_lock_refund_tx_id, txid=xmr_swap.a_lock_refund_tx_id,
vout=refund_vout,
) )
else: else:
self.setBidError( self.setBidError(
@@ -9140,6 +9191,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
if was_received: if was_received:
if self.isBchXmrSwap(offer): if self.isBchXmrSwap(offer):
# Mercy tx is sent separately # Mercy tx is sent separately
# Can't set XMR_SWAP_FAILED_SWIPED, as bid should continue looking for mercy tx
pass pass
else: else:
# Look for a mercy output # Look for a mercy output
@@ -11218,7 +11270,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
refundExtraArgs = dict() refundExtraArgs = dict()
lockExtraArgs = dict() lockExtraArgs = dict()
if self.isBchXmrSwap(offer): if self.isBchXmrSwap(offer):
# perform check that both lock and refund transactions have their outs pointing to correct follower address # Perform check that both lock and refund transactions have their outs pointing to correct follower address
# and prepare extra args for validation # and prepare extra args for validation
bch_ci = self.ci(Coins.BCH) bch_ci = self.ci(Coins.BCH)
@@ -12975,8 +13027,8 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
self.log.info(f"Route established for bid {self.log.id(bid_id)}") self.log.info(f"Route established for bid {self.log.id(bid_id)}")
bid, offer = self.getBidAndOffer(bid_id, cursor) bid, offer = self.getBidAndOffer(bid_id, cursor)
ensure(bid, "Bid not found") ensure(bid, f"Bid not found: {self.log.id(bid_id)}.")
ensure(offer, "Offer not found") ensure(offer, f"Offer not found: {self.log.id(bid.offer_id)}.")
coin_from = Coins(offer.coin_from) coin_from = Coins(offer.coin_from)
coin_to = Coins(offer.coin_to) coin_to = Coins(offer.coin_to)
@@ -14082,9 +14134,9 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
walletinfo = ci.getWalletInfo() walletinfo = ci.getWalletInfo()
rv = { rv = {
"deposit_address": self.getCachedAddressForCoin(coin), "deposit_address": self.getCachedAddressForCoin(coin),
"balance": ci.format_amount(walletinfo["balance"], conv_int=True), "balance": ci.format_amount(walletinfo["balance"], conv_int=True, r=-1),
"unconfirmed": ci.format_amount( "unconfirmed": ci.format_amount(
walletinfo["unconfirmed_balance"], conv_int=True walletinfo["unconfirmed_balance"], conv_int=True, r=-1
), ),
"expected_seed": ci.knownWalletSeed(), "expected_seed": ci.knownWalletSeed(),
"encrypted": walletinfo["encrypted"], "encrypted": walletinfo["encrypted"],
@@ -14099,7 +14151,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
if "immature_balance" in walletinfo: if "immature_balance" in walletinfo:
rv["immature"] = ci.format_amount( rv["immature"] = ci.format_amount(
walletinfo["immature_balance"], conv_int=True walletinfo["immature_balance"], conv_int=True, r=-1
) )
if "locked_utxos" in walletinfo: if "locked_utxos" in walletinfo:
@@ -15090,8 +15142,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
return return
bid = self.getBid(bid_id) bid = self.getBid(bid_id)
if bid is None: ensure(bid, f"Bid not found: {self.log.id(bid_id)}.")
raise ValueError("Bid not found.")
bid.debug_ind = debug_ind bid.debug_ind = debug_ind
+3 -1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2024 tecnovert # Copyright (c) 2024 tecnovert
@@ -193,6 +192,9 @@ class AdaptorSigInterface:
def getScriptLockRefundSwipeTxDummyWitness(self, script: bytes) -> List[bytes]: def getScriptLockRefundSwipeTxDummyWitness(self, script: bytes) -> List[bytes]:
return [bytes(72), b"", bytes(len(script))] return [bytes(72), b"", bytes(len(script))]
def getLockRefundVout(self, lock_refund_tx_data: bytes, vbkv: bytes):
return 0
class Secp256k1Interface(CoinInterface, AdaptorSigInterface): class Secp256k1Interface(CoinInterface, AdaptorSigInterface):
def __init__(self, **kwargs): def __init__(self, **kwargs):
+26 -5
View File
@@ -147,7 +147,9 @@ class BCHInterface(BTCInterface):
if not self.isAddressMine(address, or_watch_only=True): if not self.isAddressMine(address, or_watch_only=True):
# Expects P2WSH nested in BIP16_P2SH # Expects P2WSH nested in BIP16_P2SH
self.rpc("importaddress", [lock_tx_dest.hex(), "bid lock", False, True]) self.rpc_wallet(
"importaddress", [lock_tx_dest.hex(), "bid lock", False, True]
)
return address return address
@@ -156,12 +158,20 @@ class BCHInterface(BTCInterface):
def createRawFundedTransaction( def createRawFundedTransaction(
self, self,
addr_to: str, addr_to: str | bytes,
amount: int, amount: int,
sub_fee: bool = False, sub_fee: bool = False,
lock_unspents: bool = True, lock_unspents: bool = True,
feerate: int = None, feerate: int = None,
) -> str: ) -> str:
if isinstance(addr_to, bytes):
# addr_to is script_pubkey
tx = CTransaction()
tx.nVersion = self.txVersion()
tx.vout.append(self.txoType()(amount, addr_to))
txn = tx.serialize_without_witness().hex()
else:
txn = self.rpc( txn = self.rpc(
"createrawtransaction", [[], {addr_to: self.format_amount(amount)}] "createrawtransaction", [[], {addr_to: self.format_amount(amount)}]
) )
@@ -228,6 +238,16 @@ class BCHInterface(BTCInterface):
) )
return pay_fee return pay_fee
def getBLockTxo(
self,
chain_b_lock_txid: bytes,
lock_tx_vout: int,
script_pk: bytes,
) -> (int, int):
txout = self.rpc("gettxout", [chain_b_lock_txid.hex(), lock_tx_vout, True])
actual_value = self.make_int(txout["value"])
return lock_tx_vout, actual_value
def findTxnByHash(self, txid_hex: str): def findTxnByHash(self, txid_hex: str):
# Only works for wallet txns # Only works for wallet txns
try: try:
@@ -282,7 +302,7 @@ class BCHInterface(BTCInterface):
found_vout = try_vout found_vout = try_vout
break break
except Exception as e: # noqa: F841 except Exception as e: # noqa: F841
# self._log.warning('gettxout {}'.format(e)) # self._log.warning(f"gettxout {e}")
return None return None
if found_vout is None: if found_vout is None:
@@ -295,7 +315,7 @@ class BCHInterface(BTCInterface):
# TODO: Better way? # TODO: Better way?
if confirmations > 0: if confirmations > 0:
block_height = self.getChainHeight() - confirmations block_height = self.getChainHeight() - (confirmations - 1)
rv = { rv = {
"txid": txid.hex(), "txid": txid.hex(),
@@ -516,6 +536,7 @@ class BCHInterface(BTCInterface):
tx_lock = self.loadTx(tx_lock_bytes) tx_lock = self.loadTx(tx_lock_bytes)
output_script = self.getScriptDest(script_lock) output_script = self.getScriptDest(script_lock)
locked_n = findOutput(tx_lock, output_script) locked_n = findOutput(tx_lock, output_script)
ensure(locked_n is not None, "Output not found in tx") ensure(locked_n is not None, "Output not found in tx")
locked_coin = tx_lock.vout[locked_n].nValue locked_coin = tx_lock.vout[locked_n].nValue
@@ -1134,7 +1155,7 @@ class BCHInterface(BTCInterface):
refund_output_value = refund_swipe_tx.vout[0].nValue refund_output_value = refund_swipe_tx.vout[0].nValue
refund_output_script = refund_swipe_tx.vout[0].scriptPubKey refund_output_script = refund_swipe_tx.vout[0].scriptPubKey
# mercy transaction size consisting of one input of freshly received funds, # Mercy transaction size consisting of one input of freshly received funds,
# one op_return with mercy information, a dust output to the leader and change back to the follower # one op_return with mercy information, a dust output to the leader and change back to the follower
tx_size = 275 tx_size = 275
dust_limit = 546 dust_limit = 546
+120 -53
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2020-2024 tecnovert # Copyright (c) 2020-2024 tecnovert
@@ -508,6 +507,14 @@ class BTCInterface(FeeValidator, Secp256k1Interface):
return height return height
return self.rpc("getblockcount") return self.rpc("getblockcount")
def getTxLocktime(self, tx_data: bytes) -> int:
tx_obj = self.loadTx(tx_data)
return tx_obj.nLockTime
def getTxInSequence(self, tx_data: bytes, vout: int) -> int:
tx_obj = self.loadTx(tx_data)
return tx_obj.vin[vout].nSequence
def getChainMedianTime(self) -> int: def getChainMedianTime(self) -> int:
if self.useBackend(): if self.useBackend():
import struct import struct
@@ -601,7 +608,7 @@ class BTCInterface(FeeValidator, Secp256k1Interface):
block_hash = sha256(sha256(header_bytes))[::-1].hex() block_hash = sha256(sha256(header_bytes))[::-1].hex()
return {"height": height, "hash": block_hash, "time": block_time} return {"height": height, "hash": block_hash, "time": block_time}
def getBlockHeader(self, block_hash): def getBlockHeader(self, block_hash: str) -> dict:
if self._connection_type == "electrum": if self._connection_type == "electrum":
raise NotImplementedError( raise NotImplementedError(
"getBlockHeader by hash not available in electrum mode" "getBlockHeader by hash not available in electrum mode"
@@ -2785,6 +2792,47 @@ class BTCInterface(FeeValidator, Secp256k1Interface):
) )
return pay_fee return pay_fee
def getBLockTxo(
self,
chain_b_lock_txid: bytes,
lock_tx_vout: int,
script_pk: bytes,
) -> (int, int):
if self.useBackend():
backend = self.getBackend()
tx_hex = backend.getTransactionRaw(chain_b_lock_txid.hex())
if tx_hex:
lock_tx = self.loadTx(bytes.fromhex(tx_hex))
locked_n = findOutput(lock_tx, script_pk)
if locked_n is not None:
actual_value = lock_tx.vout[locked_n].nValue
else:
self._log.error(
f"spendBLockTx: Output not found in tx {self._log.id(chain_b_lock_txid)}, "
f"script_pk={script_pk.hex()}, num_outputs={len(lock_tx.vout)}"
)
for i, out in enumerate(lock_tx.vout):
self._log.debug(
f" vout[{i}]: value={out.nValue}, scriptPubKey={out.scriptPubKey.hex()}"
)
else:
self._log.warning(
f"spendBLockTx: Failed to fetch tx {self._log.id(chain_b_lock_txid)} from backend"
)
locked_n = lock_tx_vout
return locked_n, actual_value
wtx = self.rpc_wallet_watch(
"gettransaction",
[
chain_b_lock_txid.hex(),
],
)
lock_tx = self.loadTx(bytes.fromhex(wtx["hex"]))
locked_n = findOutput(lock_tx, script_pk)
if locked_n is not None:
actual_value = lock_tx.vout[locked_n].nValue
return locked_n, actual_value
def spendBLockTx( def spendBLockTx(
self, self,
chain_b_lock_txid: bytes, chain_b_lock_txid: bytes,
@@ -2798,48 +2846,14 @@ class BTCInterface(FeeValidator, Secp256k1Interface):
lock_tx_vout=None, lock_tx_vout=None,
) -> bytes: ) -> bytes:
self._log.info( self._log.info(
"spendBLockTx: {} {}\n".format( f"spendBLockTx: {self._log.id(chain_b_lock_txid)} {lock_tx_vout}\n"
self._log.id(chain_b_lock_txid), lock_tx_vout
)
) )
Kbs = self.getPubkey(kbs) Kbs = self.getPubkey(kbs)
script_pk = self.getPkDest(Kbs) script_pk = self.getPkDest(Kbs)
locked_n = None locked_n, actual_value = self.getBLockTxo(
actual_value = None chain_b_lock_txid, lock_tx_vout, script_pk
if self.useBackend():
backend = self.getBackend()
tx_hex = backend.getTransactionRaw(chain_b_lock_txid.hex())
if tx_hex:
lock_tx = self.loadTx(bytes.fromhex(tx_hex))
locked_n = findOutput(lock_tx, script_pk)
if locked_n is not None:
actual_value = lock_tx.vout[locked_n].nValue
else:
self._log.error(
f"spendBLockTx: Output not found in tx {chain_b_lock_txid.hex()}, "
f"script_pk={script_pk.hex()}, num_outputs={len(lock_tx.vout)}"
) )
for i, out in enumerate(lock_tx.vout):
self._log.debug(
f" vout[{i}]: value={out.nValue}, scriptPubKey={out.scriptPubKey.hex()}"
)
else:
self._log.warning(
f"spendBLockTx: Failed to fetch tx {chain_b_lock_txid.hex()} from backend"
)
locked_n = lock_tx_vout
else:
wtx = self.rpc_wallet_watch(
"gettransaction",
[
chain_b_lock_txid.hex(),
],
)
lock_tx = self.loadTx(bytes.fromhex(wtx["hex"]))
locked_n = findOutput(lock_tx, script_pk)
if locked_n is not None:
actual_value = lock_tx.vout[locked_n].nValue
if ( if (
locked_n is not None locked_n is not None
@@ -2848,7 +2862,7 @@ class BTCInterface(FeeValidator, Secp256k1Interface):
): ):
self._log.warning( self._log.warning(
f"spendBLockTx: Stored vout {lock_tx_vout} differs from actual vout {locked_n} " f"spendBLockTx: Stored vout {lock_tx_vout} differs from actual vout {locked_n} "
f"for tx {chain_b_lock_txid.hex()}" f"for tx {self._log.id(chain_b_lock_txid)}"
) )
ensure(locked_n is not None, "Output not found in tx") ensure(locked_n is not None, "Output not found in tx")
@@ -2938,13 +2952,9 @@ class BTCInterface(FeeValidator, Secp256k1Interface):
# Add watchonly address and rescan if required # Add watchonly address and rescan if required
if not self.isAddressMine(dest_address, or_watch_only=True): if not self.isAddressMine(dest_address, or_watch_only=True):
self.importWatchOnlyAddress(dest_address, "bid") self.importWatchOnlyAddress(dest_address, "bid")
self._log.info(f"Imported watch-only addr: {self._log.addr(dest_address)}")
self._log.info( self._log.info(
"Imported watch-only addr: {}".format(self._log.addr(dest_address)) f"Rescanning {self.coin_name()} chain from height: {rescan_from}"
)
self._log.info(
"Rescanning {} chain from height: {}".format(
self.coin_name(), rescan_from
)
) )
self.rpc_wallet("rescanblockchain", [rescan_from]) self.rpc_wallet("rescanblockchain", [rescan_from])
@@ -3657,7 +3667,7 @@ class BTCInterface(FeeValidator, Secp256k1Interface):
continue continue
if "desc" in u: if "desc" in u:
desc = u["desc"] desc = u["desc"]
if self.using_segwit: if self.using_segwit():
if self.use_p2shp2wsh(): if self.use_p2shp2wsh():
if not desc.startswith("sh(wpkh"): if not desc.startswith("sh(wpkh"):
continue continue
@@ -3818,7 +3828,7 @@ class BTCInterface(FeeValidator, Secp256k1Interface):
ensure( ensure(
sign_for_addr is not None, sign_for_addr is not None,
"Could not find address with enough funds for proof", f"Could not find {self.ticker()} address with enough funds for proof",
) )
self._log.debug(f"sign_for_addr {sign_for_addr}") self._log.debug(f"sign_for_addr {sign_for_addr}")
@@ -4457,6 +4467,8 @@ class BTCInterface(FeeValidator, Secp256k1Interface):
return None return None
def isTxExistsError(self, err_str: str) -> bool: def isTxExistsError(self, err_str: str) -> bool:
if self._connection_type == "electrum":
return "Transaction outputs already in utxo set" in err_str
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:
@@ -4511,7 +4523,7 @@ class BTCInterface(FeeValidator, Secp256k1Interface):
self._log.id(bytes.fromhex(tx["txid"])) self._log.id(bytes.fromhex(tx["txid"]))
) )
) )
self.publishTx(tx_signed) self.publishTx(bytes.fromhex(tx_signed))
return tx["txid"] return tx["txid"]
@@ -4549,10 +4561,65 @@ class BTCInterface(FeeValidator, Secp256k1Interface):
return bytes.fromhex(txi_txid_hex), fee_rate return bytes.fromhex(txi_txid_hex), fee_rate
def _getTxOutInfoElectrum(self, txid: bytes, n: int, include_mempool: bool = False):
backend = self.getBackend()
if not backend:
return None
def testBTCInterface(): try:
print("TODO: testBTCInterface") tx_info = backend.getTransaction(txid.hex())
if "blockhash" not in tx_info:
return None
confirmations: int = (
0 if "confirmations" not in tx_info else tx_info["confirmations"]
)
if confirmations < 1:
return None
chain_tip_height = self.getChainHeight()
block_height: int = chain_tip_height - (confirmations - 1)
block_hash: bytes = bytes.fromhex(tx_info["blockhash"])
return {
"block_hash": block_hash,
"block_height": block_height,
"block_time": tx_info["blocktime"],
}
except Exception as e:
self._log.debug(f"_findTxnByHashElectrum failed: {e}")
return None
if __name__ == "__main__": def getTxOutInfo(
testBTCInterface() self, txid: bytes, n: int, include_mempool: bool = False
) -> dict():
if self._connection_type == "electrum":
return self._getTxOutInfoElectrum(txid, n, include_mempool)
try:
txout = self.rpc("gettxout", [txid.hex(), n, include_mempool])
confirmations: int = (
0 if "confirmations" not in txout else txout["confirmations"]
)
if confirmations < 1:
return None
chain_tip_height: int = 0
if "bestblock" in txout:
bestheader_info = self.getBlockHeader(txout["bestblock"])
chain_tip_height = bestheader_info["height"]
else:
chain_tip_height = self.getChainHeight()
if confirmations == 1:
header_info = bestheader_info
else:
block_height: int = chain_tip_height - (confirmations - 1)
header_info = self.getBlockHeaderFromHeight(block_height)
block_hash: bytes = bytes.fromhex(header_info["hash"])
return {
"block_hash": block_hash,
"block_height": header_info["height"],
"block_time": header_info["time"],
}
except Exception as e: # noqa: F841
# self._log.warning(f"gettxout {e}")
return None
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2022-2024 tecnovert # Copyright (c) 2022-2024 tecnovert
+130 -11
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2024 tecnovert # Copyright (c) 2024 tecnovert
@@ -13,7 +12,7 @@ import logging
import random import random
import traceback import traceback
from typing import List from typing import List, Optional
from basicswap.basicswap_util import getVoutByScriptPubKey, TxLockTypes from basicswap.basicswap_util import getVoutByScriptPubKey, TxLockTypes
from basicswap.chainparams import Coins from basicswap.chainparams import Coins
@@ -419,8 +418,10 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
return bci return bci
def getBlockHeader(self, block_hash: str) -> dict:
return self.rpc("getblockheader", [block_hash])
def getWalletInfo(self): def getWalletInfo(self):
rv = {}
rv = self.rpc_wallet("getinfo") rv = self.rpc_wallet("getinfo")
wi = self.rpc_wallet("walletinfo") wi = self.rpc_wallet("walletinfo")
balances = self.rpc_wallet("getbalance") balances = self.rpc_wallet("getbalance")
@@ -595,7 +596,7 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
override_feerate = chain_client_settings.get("override_feerate", None) override_feerate = chain_client_settings.get("override_feerate", None)
if override_feerate: if override_feerate:
self._log.debug( self._log.debug(
"Fee rate override used for %s: %f", self.coin_name(), override_feerate f"Fee rate override used for {self.coin_name()}: {override_feerate}"
) )
return override_feerate, "override_feerate" return override_feerate, "override_feerate"
@@ -919,7 +920,7 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
found_vout = try_vout found_vout = try_vout
break break
except Exception as e: # noqa: F841 except Exception as e: # noqa: F841
# self._log.warning('gettxout {}'.format(e)) # self._log.warning(f"gettxout {e})
return None return None
if found_vout is None: if found_vout is None:
@@ -932,7 +933,7 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
# TODO: Better way? # TODO: Better way?
if confirmations > 0: if confirmations > 0:
block_height = self.getChainHeight() - confirmations block_height = self.getChainHeight() - (confirmations - 1)
rv = { rv = {
"txid": txid.hex(), "txid": txid.hex(),
@@ -999,6 +1000,10 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
tx.vout.append(self.txoType()(output_value, script)) tx.vout.append(self.txoType()(output_value, script))
return tx.serialize().hex() return tx.serialize().hex()
def ensureFunds(self, amount: int) -> None:
if self.getSpendableBalance() < amount:
raise ValueError("Balance too low")
def verifyRawTransaction(self, tx_hex: str, prevouts): def verifyRawTransaction(self, tx_hex: str, prevouts):
inputs_valid: bool = True inputs_valid: bool = True
validscripts: int = 0 validscripts: int = 0
@@ -1151,6 +1156,7 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock) dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock)
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack)) size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
size += 1
pay_fee = round(tx_fee_rate * size / 1000) pay_fee = round(tx_fee_rate * size / 1000)
tx.vout[0].value = locked_coin - pay_fee tx.vout[0].value = locked_coin - pay_fee
@@ -1202,6 +1208,7 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock) dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock)
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack)) size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
size += 1
pay_fee = round(tx_fee_rate * size / 1000) pay_fee = round(tx_fee_rate * size / 1000)
tx.vout[0].value = locked_coin - pay_fee tx.vout[0].value = locked_coin - pay_fee
@@ -1253,6 +1260,7 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
script_lock_refund script_lock_refund
) )
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack)) size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
size += 1
pay_fee = round(tx_fee_rate * size / 1000) pay_fee = round(tx_fee_rate * size / 1000)
tx.vout[0].value = locked_coin - pay_fee tx.vout[0].value = locked_coin - pay_fee
@@ -1337,6 +1345,7 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
assert fee_paid > 0 assert fee_paid > 0
size = len(tx.serialize()) + add_witness_bytes size = len(tx.serialize()) + add_witness_bytes
size += 1
fee_rate_paid = fee_paid * 1000 // size fee_rate_paid = fee_paid * 1000 // size
self._log.info( self._log.info(
@@ -1398,6 +1407,7 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
dummy_witness_stack = self.getScriptLockTxDummyWitness(lock_tx_script) dummy_witness_stack = self.getScriptLockTxDummyWitness(lock_tx_script)
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack)) size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
size += 1
fee_rate_paid = fee_paid * 1000 // size fee_rate_paid = fee_paid * 1000 // size
self._log.info( self._log.info(
@@ -1470,6 +1480,7 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
dummy_witness_stack = self.getScriptLockTxDummyWitness(prevout_script) dummy_witness_stack = self.getScriptLockTxDummyWitness(prevout_script)
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack)) size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
size += 1
fee_rate_paid = fee_paid * 1000 // size fee_rate_paid = fee_paid * 1000 // size
self._log.info( self._log.info(
@@ -1531,6 +1542,7 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
prevout_script prevout_script
) )
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack)) size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
size += 1
fee_rate_paid = fee_paid * 1000 // size fee_rate_paid = fee_paid * 1000 // size
self._log.info( self._log.info(
@@ -1781,13 +1793,16 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
spend_actual_balance: bool = False, spend_actual_balance: bool = False,
lock_tx_vout=None, lock_tx_vout=None,
) -> bytes: ) -> bytes:
self._log.info("spendBLockTx %s:\n", chain_b_lock_txid.hex()) self._log.info(
f"spendBLockTx: {self._log.id(chain_b_lock_txid)} {lock_tx_vout}\n"
)
Kbs = self.getPubkey(kbs) Kbs = self.getPubkey(kbs)
script_pk = self.getPkDest(Kbs) script_pk = self.getPkDest(Kbs)
locked_n = None locked_n = None
actual_value = None actual_value = None
try:
wtx = self.rpc_wallet( wtx = self.rpc_wallet(
"gettransaction", "gettransaction",
[ [
@@ -1800,13 +1815,19 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
actual_value = lock_tx.vout[locked_n].value actual_value = lock_tx.vout[locked_n].value
else: else:
self._log.error( self._log.error(
f"spendBLockTx: Output not found in tx {chain_b_lock_txid.hex()}, " f"spendBLockTx: Output not found in tx {self._log.id(chain_b_lock_txid)}, "
f"script_pk={script_pk.hex()}, num_outputs={len(lock_tx.vout)}" f"script_pk={script_pk.hex()}, num_outputs={len(lock_tx.vout)}"
) )
for i, out in enumerate(lock_tx.vout): for i, out in enumerate(lock_tx.vout):
self._log.debug( self._log.debug(
f" vout[{i}]: value={out.value}, scriptPubKey={out.scriptPubKey.hex()}" f" vout[{i}]: value={out.value}, scriptPubKey={out.scriptPubKey.hex()}"
) )
except Exception as e: # noqa: F841
txout = self.rpc(
"gettxout", [chain_b_lock_txid.hex(), lock_tx_vout, 0, True]
)
actual_value = self.make_int(txout["value"])
locked_n = lock_tx_vout
if ( if (
locked_n is not None locked_n is not None
@@ -1815,7 +1836,7 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
): ):
self._log.warning( self._log.warning(
f"spendBLockTx: Stored vout {lock_tx_vout} differs from actual vout {locked_n} " f"spendBLockTx: Stored vout {lock_tx_vout} differs from actual vout {locked_n} "
f"for tx {chain_b_lock_txid.hex()}" f"for tx {self._log.id(chain_b_lock_txid)}"
) )
ensure(locked_n is not None, "Output not found in tx") ensure(locked_n is not None, "Output not found in tx")
@@ -1851,14 +1872,14 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
try: try:
txout = self.rpc("gettxout", [txid_hex, 0, 0, True]) txout = self.rpc("gettxout", [txid_hex, 0, 0, True])
except Exception as e: # noqa: F841 except Exception as e: # noqa: F841
# self._log.warning('gettxout {}'.format(e)) # self._log.warning(f"gettxout {e}"))
return None return None
confirmations: int = ( confirmations: int = (
0 if "confirmations" not in txout else txout["confirmations"] 0 if "confirmations" not in txout else txout["confirmations"]
) )
if confirmations >= self.blocks_confirmed: if confirmations >= self.blocks_confirmed:
block_height = self.getChainHeight() - confirmations # TODO: Better way? block_height = self.getChainHeight() - (confirmations - 1)
return {"txid": txid_hex, "amount": 0, "height": block_height} return {"txid": txid_hex, "amount": 0, "height": block_height}
return None return None
@@ -1873,3 +1894,101 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
def isTxNonFinalError(self, err_str: str) -> bool: def isTxNonFinalError(self, err_str: str) -> bool:
return "locks on inputs not met" in err_str return "locks on inputs not met" in err_str
def getChainMedianTime(self) -> int:
bestblockhash = self.rpc("getbestblockhash")
bestblockheader = self.rpc(
"getblockheader",
[
bestblockhash,
],
)
return bestblockheader["mediantime"]
def getTxLocktime(self, tx_data: bytes) -> int:
tx_obj = self.loadTx(tx_data)
return tx_obj.locktime
def getTxInSequence(self, tx_data: bytes, vout: int) -> int:
tx_obj = self.loadTx(tx_data)
return tx_obj.vin[vout].sequence
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 getTxOutInfo(
self, txid: bytes, n: int, include_mempool: bool = False
) -> dict():
try:
txout = self.rpc("gettxout", [txid.hex(), n, 0, include_mempool])
confirmations: int = (
0 if "confirmations" not in txout else txout["confirmations"]
)
if confirmations < 1:
return None
chain_tip_height: int = 0
if "bestblock" in txout:
bestheader_info = self.getBlockHeader(txout["bestblock"])
chain_tip_height = bestheader_info["height"]
else:
chain_tip_height = self.getChainHeight()
if confirmations == 1:
header_info = bestheader_info
else:
block_height: int = chain_tip_height - (confirmations - 1)
header_info = self.getBlockHeaderFromHeight(block_height)
block_hash: bytes = bytes.fromhex(header_info["hash"])
return {
"block_hash": block_hash,
"block_height": header_info["height"],
"block_time": header_info["time"],
}
except Exception as e: # noqa: F841
# self._log.warning(f"gettxout {e}")
return None
def is_transient_error(self, ex) -> bool:
str_error: str = str(ex).lower()
if "no information for transaction" in str_error:
return True
return super().is_transient_error(ex)
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2024 tecnovert # Copyright (c) 2024 tecnovert
+5 -4
View File
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2024 tecnovert # Copyright (c) 2024 tecnovert
# Copyright (c) 2024-2026 The Basicswap developers
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -9,10 +10,10 @@ import traceback
from basicswap.rpc import Jsonrpc from basicswap.rpc import Jsonrpc
def callrpc(rpc_port, auth, method, params=[], host="127.0.0.1"): def callrpc(rpc_port, auth, method, params=[], host="127.0.0.1", timeout=None):
try: try:
url = "http://{}@{}:{}/".format(auth, host, rpc_port) url = "http://{}@{}:{}/".format(auth, host, rpc_port)
x = Jsonrpc(url) x = Jsonrpc(url, timeout=timeout if timeout else 10)
x.__handler = None x.__handler = None
v = x.json_request(method, params) v = x.json_request(method, params)
x.close() x.close()
@@ -41,7 +42,7 @@ def make_rpc_func(port, auth, host="127.0.0.1"):
auth = auth auth = auth
host = host host = host
def rpc_func(method, params=None): def rpc_func(method, params=None, timeout=None):
return callrpc(port, auth, method, params, host) return callrpc(port, auth, method, params, host, timeout=timeout)
return rpc_func return rpc_func
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2024 The BasicSwap developers # Copyright (c) 2024 The BasicSwap developers
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2024-2026 The Basicswap developers # Copyright (c) 2024-2026 The Basicswap developers
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2022-2023 tecnovert # Copyright (c) 2022-2023 tecnovert
+1 -2
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2020-2023 tecnovert # Copyright (c) 2020-2023 tecnovert
@@ -103,7 +102,7 @@ class LTCInterface(BTCInterface):
continue continue
if "desc" in u: if "desc" in u:
desc = u["desc"] desc = u["desc"]
if self.using_segwit: if self.using_segwit():
if self.use_p2shp2wsh(): if self.use_p2shp2wsh():
if not desc.startswith("sh(wpkh"): if not desc.startswith("sh(wpkh"):
continue continue
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2023 tecnovert # Copyright (c) 2023 tecnovert
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2020-2022 tecnovert # Copyright (c) 2020-2022 tecnovert
+11 -1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2020-2024 tecnovert # Copyright (c) 2020-2024 tecnovert
@@ -1300,6 +1299,17 @@ class PARTInterfaceBlind(PARTInterface):
"fundrawtransactionfrom", ["blind", tx_hex, {}, outputs_info, options] "fundrawtransactionfrom", ["blind", tx_hex, {}, outputs_info, options]
)["hex"] )["hex"]
def getLockRefundVout(self, lock_refund_tx_data: bytes, vkbv: bytes):
lock_refund_tx_obj = self.rpc(
"decoderawtransaction", [lock_refund_tx_data.hex()]
)
# Nonce is derived from vkbv
nonce = self.getScriptLockRefundTxNonce(vkbv)
# Find the output of the lock refund tx to spend
spend_n, input_blinded_info = self.findOutputByNonce(lock_refund_tx_obj, nonce)
return spend_n
class PARTInterfaceAnon(PARTInterface): class PARTInterfaceAnon(PARTInterface):
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2021 tecnovert # Copyright (c) 2021 tecnovert
+10 -1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2022 tecnovert # Copyright (c) 2022 tecnovert
@@ -171,3 +170,13 @@ class PIVXInterface(BTCInterface):
block_height = self.getBlockHeader(rv["blockhash"])["height"] block_height = self.getBlockHeader(rv["blockhash"])["height"]
return {"txid": txid_hex, "amount": 0, "height": block_height} return {"txid": txid_hex, "amount": 0, "height": block_height}
return None return None
def getChainMedianTime(self) -> int:
bestblockhash = self.rpc("getbestblockhash")
bestblockheader = self.rpc(
"getblockheader",
[
bestblockhash,
],
)
return bestblockheader["mediantime"]
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2024 The Basicswap developers # Copyright (c) 2024 The Basicswap developers
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2020-2024 tecnovert # Copyright (c) 2020-2024 tecnovert
+21 -9
View File
@@ -50,11 +50,11 @@ def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key, cursor=None):
try: try:
use_cursor = self.openDB(cursor) use_cursor = self.openDB(cursor)
bid, xmr_swap = self.getXmrBidFromSession(use_cursor, bid_id) bid, xmr_swap = self.getXmrBidFromSession(use_cursor, bid_id)
ensure(bid, "Bid not found: {}.".format(bid_id.hex())) ensure(bid, f"Bid not found: {self.log.id(bid_id)}.")
ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) ensure(xmr_swap, f"Adaptor-sig swap not found: {self.log.id(bid_id)}.")
offer, xmr_offer = self.getXmrOfferFromSession(use_cursor, bid.offer_id) offer, xmr_offer = self.getXmrOfferFromSession(use_cursor, bid.offer_id)
ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) ensure(offer, f"Offer not found: {self.log.id(bid.offer_id)}.")
ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) ensure(xmr_offer, f"Adaptor-sig offer not found: {self.log.id(bid.offer_id)}.")
# The no-script coin is always the follower # The no-script coin is always the follower
reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to) reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to)
@@ -106,7 +106,10 @@ def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key, cursor=None):
address_to = self.getReceiveAddressFromPool( address_to = self.getReceiveAddressFromPool(
base_coin_to, bid_id, TxTypes.XMR_SWAP_B_LOCK_SPEND, use_cursor base_coin_to, bid_id, TxTypes.XMR_SWAP_B_LOCK_SPEND, use_cursor
) )
amount = bid.amount_to amount: int = bid.amount_to
chain_b_fee_rate: int = (
xmr_offer.a_fee_rate if reverse_bid else xmr_offer.b_fee_rate
)
lock_tx_vout = bid.getLockTXBVout() lock_tx_vout = bid.getLockTXBVout()
txid = ci_follower.spendBLockTx( txid = ci_follower.spendBLockTx(
xmr_swap.b_lock_tx_id, xmr_swap.b_lock_tx_id,
@@ -114,7 +117,7 @@ def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key, cursor=None):
xmr_swap.vkbv, xmr_swap.vkbv,
vkbs, vkbs,
amount, amount,
xmr_offer.b_fee_rate, chain_b_fee_rate,
bid.chain_b_height_start, bid.chain_b_height_start,
spend_actual_balance=True, spend_actual_balance=True,
lock_tx_vout=lock_tx_vout, lock_tx_vout=lock_tx_vout,
@@ -209,7 +212,7 @@ class XmrSwapInterface(ProtocolInterface):
) )
def genScriptLockTxScript(self, ci, Kal: bytes, Kaf: bytes, **kwargs) -> CScript: def genScriptLockTxScript(self, ci, Kal: bytes, Kaf: bytes, **kwargs) -> CScript:
# fallthrough to ci if genScriptLockTxScript is implemented there # Fallthrough to ci if genScriptLockTxScript is implemented there
if hasattr(ci, "genScriptLockTxScript") and callable(ci.genScriptLockTxScript): if hasattr(ci, "genScriptLockTxScript") and callable(ci.genScriptLockTxScript):
return ci.genScriptLockTxScript(ci, Kal, Kaf, **kwargs) return ci.genScriptLockTxScript(ci, Kal, Kaf, **kwargs)
@@ -221,6 +224,11 @@ class XmrSwapInterface(ProtocolInterface):
def getFundedInitiateTxTemplate( def getFundedInitiateTxTemplate(
self, ci, amount: int, sub_fee: bool, feerate: int = None self, ci, amount: int, sub_fee: bool, feerate: int = None
) -> bytes: ) -> bytes:
if ci.coin_type() == Coins.BCH:
# Workaround, BCH getScriptDest() uses OP_HASH256
script: bytes = self.getMockScript()
addr_to: bytes = ci.getScriptDest(script)
else:
addr_to = self.getMockScriptAddr(ci) addr_to = self.getMockScriptAddr(ci)
funded_tx = ci.createRawFundedTransaction( funded_tx = ci.createRawFundedTransaction(
addr_to, amount, sub_fee, lock_unspents=False, feerate=feerate addr_to, amount, sub_fee, lock_unspents=False, feerate=feerate
@@ -247,8 +255,12 @@ class XmrSwapInterface(ProtocolInterface):
return lock_vout return lock_vout
def promoteMockTx(self, ci, mock_tx: bytes, script: bytearray) -> bytearray: def promoteMockTx(self, ci, mock_tx: bytes, script: bytearray) -> bytearray:
mock_txo_script = self.getMockScriptScriptPubkey(ci) if ci.coin_type() == Coins.BCH:
real_txo_script = ci.getScriptDest(script) mock_script: bytes = self.getMockScript()
mock_txo_script: bytes = ci.getScriptDest(mock_script)
else:
mock_txo_script: bytes = self.getMockScriptScriptPubkey(ci)
real_txo_script: bytes = ci.getScriptDest(script)
found: int = 0 found: int = 0
ctx = ci.loadTx(mock_tx, allow_witness=False) ctx = ci.loadTx(mock_tx, allow_witness=False)
+1
View File
@@ -22,6 +22,7 @@
- Fixed feerate from other chain displayed for reversed swaps. - Fixed feerate from other chain displayed for reversed swaps.
- Added warning text for fee above 1.2 x local estimate. - Added warning text for fee above 1.2 x local estimate.
- Added subfee bid option. - Added subfee bid option.
- Increase DCR fee estimate by 1 byte.
0.14.5 0.14.5
+7 -2
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2020-2024 tecnovert # Copyright (c) 2020-2024 tecnovert
@@ -163,7 +162,7 @@ def prepare_balance(
post_json["type_to"] = type_to post_json["type_to"] = type_to
json_rv = read_json_api( json_rv = read_json_api(
port_take_from_node, port_take_from_node,
"wallets/{}/withdraw".format(coin_ticker.lower()), f"wallets/{coin_ticker.lower()}/withdraw",
post_json, post_json,
) )
assert len(json_rv["txid"]) == 64 assert len(json_rv["txid"]) == 64
@@ -236,11 +235,17 @@ def wait_for_bid(
) )
if isinstance(state, (list, tuple)): if isinstance(state, (list, tuple)):
if bid[5] in state: if bid[5] in state:
swap_client.log.debug(
f"TEST: wait_for_bid found {bid_id.hex()}: Bid state {bid[5]}, target {state}."
)
return return
else: else:
continue continue
elif state is not None and state != bid[5]: elif state is not None and state != bid[5]:
continue continue
swap_client.log.debug(
f"TEST: wait_for_bid found {bid_id.hex()}: Bid state {bid[5]}, target {state}."
)
return return
else: else:
if i > 0 and i % 10 == 0: if i > 0 and i % 10 == 0:
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2020-2024 tecnovert # Copyright (c) 2020-2024 tecnovert
+14 -11
View File
@@ -175,6 +175,7 @@ def prepareDir(datadir, nodeId, network_key, network_pubkey):
"datadir": node_dir, "datadir": node_dir,
"bindir": cfg.PARTICL_BINDIR, "bindir": cfg.PARTICL_BINDIR,
"blocks_confirmed": 2, # Faster testing "blocks_confirmed": 2, # Faster testing
"wallet_name": "bsx_wallet",
}, },
"dash": { "dash": {
"connection_type": "rpc", "connection_type": "rpc",
@@ -184,6 +185,7 @@ def prepareDir(datadir, nodeId, network_key, network_pubkey):
"bindir": DASH_BINDIR, "bindir": DASH_BINDIR,
"use_csv": True, "use_csv": True,
"use_segwit": False, "use_segwit": False,
"wallet_name": "bsx_wallet",
}, },
"bitcoin": { "bitcoin": {
"connection_type": "rpc", "connection_type": "rpc",
@@ -192,6 +194,7 @@ def prepareDir(datadir, nodeId, network_key, network_pubkey):
"datadir": btcdatadir, "datadir": btcdatadir,
"bindir": cfg.BITCOIN_BINDIR, "bindir": cfg.BITCOIN_BINDIR,
"use_segwit": True, "use_segwit": True,
"wallet_name": "bsx_wallet",
}, },
}, },
"check_progress_seconds": 2, "check_progress_seconds": 2,
@@ -285,7 +288,7 @@ class Test(unittest.TestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
super(Test, cls).setUpClass() super().setUpClass()
k = PrivateKey() k = PrivateKey()
cls.network_key = toWIF(PREFIX_SECRET_KEY_REGTEST, k.secret) cls.network_key = toWIF(PREFIX_SECRET_KEY_REGTEST, k.secret)
@@ -409,15 +412,15 @@ class Test(unittest.TestCase):
waitForRPC(dashRpc, delay_event, rpc_command="getblockchaininfo") waitForRPC(dashRpc, delay_event, rpc_command="getblockchaininfo")
if len(dashRpc("listwallets")) < 1: if len(dashRpc("listwallets")) < 1:
dashRpc("createwallet wbsx_wallet") dashRpc("createwallet bsx_wallet")
sc.start() sc.start()
waitForRPC(dashRpc, delay_event) waitForRPC(dashRpc, delay_event)
num_blocks = 500 num_blocks = 500
logging.info("Mining %d dash blocks", num_blocks) logging.info(f"Mining {num_blocks} dash blocks")
cls.dash_addr = dashRpc("getnewaddress mining_addr") cls.dash_addr = dashRpc("getnewaddress mining_addr")
dashRpc("generatetoaddress {} {}".format(num_blocks, cls.dash_addr)) dashRpc(f"generatetoaddress {num_blocks} {cls.dash_addr}")
ro = dashRpc("getblockchaininfo") ro = dashRpc("getblockchaininfo")
try: try:
@@ -431,8 +434,8 @@ class Test(unittest.TestCase):
waitForRPC(btcRpc, delay_event) waitForRPC(btcRpc, delay_event)
cls.btc_addr = btcRpc("getnewaddress mining_addr bech32") cls.btc_addr = btcRpc("getnewaddress mining_addr bech32")
logging.info("Mining %d Bitcoin blocks to %s", num_blocks, cls.btc_addr) logging.info(f"Mining {num_blocks} Bitcoin blocks to {cls.btc_addr}")
btcRpc("generatetoaddress {} {}".format(num_blocks, cls.btc_addr)) btcRpc(f"generatetoaddress {num_blocks} {cls.btc_addr}")
ro = btcRpc("getblockchaininfo") ro = btcRpc("getblockchaininfo")
checkForks(ro) checkForks(ro)
@@ -449,7 +452,7 @@ class Test(unittest.TestCase):
# Wait for height, or sequencelock is thrown off by genesis blocktime # Wait for height, or sequencelock is thrown off by genesis blocktime
num_blocks = 3 num_blocks = 3
logging.info("Waiting for Particl chain height %d", num_blocks) logging.info(f"Waiting for Particl chain height {num_blocks}")
for i in range(60): for i in range(60):
particl_blocks = cls.swap_clients[0].callrpc("getblockcount") particl_blocks = cls.swap_clients[0].callrpc("getblockcount")
print("particl_blocks", particl_blocks) print("particl_blocks", particl_blocks)
@@ -473,7 +476,7 @@ class Test(unittest.TestCase):
cls.swap_clients.clear() cls.swap_clients.clear()
cls.daemons.clear() cls.daemons.clear()
super(Test, cls).tearDownClass() super().tearDownClass()
def test_02_part_dash(self): def test_02_part_dash(self):
logging.info("---------- Test PART to DASH") logging.info("---------- Test PART to DASH")
@@ -683,9 +686,9 @@ class Test(unittest.TestCase):
offer_id = swap_clients[0].postOffer( offer_id = swap_clients[0].postOffer(
Coins.DASH, Coins.DASH,
Coins.BTC, Coins.BTC,
0.001 * COIN, 0.01 * COIN,
1.0 * COIN, 1.0 * COIN,
0.001 * COIN, 0.01 * COIN,
SwapTypes.SELLER_FIRST, SwapTypes.SELLER_FIRST,
) )
@@ -709,7 +712,7 @@ class Test(unittest.TestCase):
del swap_clients[0].getChainClientSettings(Coins.DASH)["override_feerate"] del swap_clients[0].getChainClientSettings(Coins.DASH)["override_feerate"]
def test_08_wallet(self): def test_08_wallet(self):
logging.info("---------- Test {} wallet".format(self.test_coin_from.name)) logging.info(f"---------- Test {self.test_coin_from.name} wallet")
logging.info("Test withdrawal") logging.info("Test withdrawal")
addr = dashRpc('getnewaddress "Withdrawal test"') addr = dashRpc('getnewaddress "Withdrawal test"')
+13 -30
View File
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2024 tecnovert # Copyright (c) 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 # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -75,24 +75,6 @@ def make_rpc_func(node_id, base_rpc_port):
return rpc_func return rpc_func
def wait_for_dcr_height(http_port, num_blocks=3):
logging.info("Waiting for DCR chain height %d", num_blocks)
for i in range(60):
if test_delay_event.is_set():
raise ValueError("Test stopped.")
try:
wallet = read_json_api(http_port, "wallets/dcr")
decred_blocks = wallet["blocks"]
print("decred_blocks", decred_blocks)
if decred_blocks >= num_blocks:
return
except Exception as e:
print("Error reading wallets", str(e))
test_delay_event.wait(1)
raise ValueError(f"wait_for_decred_blocks failed http_port: {http_port}")
def run_test_success_path(self, coin_from: Coins, coin_to: Coins): def run_test_success_path(self, coin_from: Coins, coin_to: Coins):
logging.info(f"---------- Test {coin_from.name} to {coin_to.name}") logging.info(f"---------- Test {coin_from.name} to {coin_to.name}")
@@ -765,14 +747,14 @@ class Test(BaseTest):
@classmethod @classmethod
def tearDownClass(cls): def tearDownClass(cls):
logging.info("Finalising Decred Test") logging.info("Finalising Decred Test")
super(Test, cls).tearDownClass() super().tearDownClass()
stopDaemons(cls.dcr_daemons) stopDaemons(cls.dcr_daemons)
cls.dcr_daemons.clear() cls.dcr_daemons.clear()
@classmethod @classmethod
def coins_loop(cls): def coins_loop(cls):
super(Test, cls).coins_loop() super().coins_loop()
ci0 = cls.swap_clients[0].ci(cls.test_coin) ci0 = cls.swap_clients[0].ci(cls.test_coin)
num_passed: int = 0 num_passed: int = 0
@@ -878,15 +860,16 @@ class Test(BaseTest):
"use_csv": True, "use_csv": True,
"use_segwit": True, "use_segwit": True,
"blocks_confirmed": 1, "blocks_confirmed": 1,
"min_relay_fee": 0.00001,
} }
def test_0001_decred_address(self): def test_0001_decred_address(self):
logging.info("---------- Test {}".format(self.test_coin.name)) logging.info(f"---------- Test {self.test_coin.name}")
coin_settings = {"rpcport": 0, "rpcauth": "none"} coin_settings = {"rpcport": 0, "rpcauth": "none"}
coin_settings.update(REQUIRED_SETTINGS) coin_settings.update(REQUIRED_SETTINGS)
ci = DCRInterface(coin_settings, "mainnet") ci = DCRInterface(coin_settings, "mainnet", self.swap_clients[0])
k = ci.getNewRandomKey() k = ci.getNewRandomKey()
K = ci.getPubkey(k) K = ci.getPubkey(k)
@@ -914,7 +897,7 @@ class Test(BaseTest):
assert hash160(masterpubkey_data) == seed_hash assert hash160(masterpubkey_data) == seed_hash
def test_001_segwit(self): def test_001_segwit(self):
logging.info("---------- Test {} segwit".format(self.test_coin.name)) logging.info(f"---------- Test {self.test_coin.name} segwit")
swap_clients = self.swap_clients swap_clients = self.swap_clients
ci0 = swap_clients[0].ci(self.test_coin) ci0 = swap_clients[0].ci(self.test_coin)
@@ -972,7 +955,7 @@ class Test(BaseTest):
assert f_decoded["txid"] == ctx.TxHash().hex() assert f_decoded["txid"] == ctx.TxHash().hex()
def test_003_signature_hash(self): def test_003_signature_hash(self):
logging.info("---------- Test {} signature_hash".format(self.test_coin.name)) logging.info(f"---------- Test {self.test_coin.name} signature_hash")
# Test that signing a transaction manually produces the same result when signed with the wallet # Test that signing a transaction manually produces the same result when signed with the wallet
swap_clients = self.swap_clients swap_clients = self.swap_clients
@@ -1047,7 +1030,7 @@ class Test(BaseTest):
assert len(sent_txid) == 64 assert len(sent_txid) == 64
def test_004_csv(self): def test_004_csv(self):
logging.info("---------- Test {} csv".format(self.test_coin.name)) logging.info(f"---------- Test {self.test_coin.name} csv")
swap_clients = self.swap_clients swap_clients = self.swap_clients
ci0 = swap_clients[0].ci(self.test_coin) ci0 = swap_clients[0].ci(self.test_coin)
@@ -1161,7 +1144,7 @@ class Test(BaseTest):
assert sent_spend_txid is not None assert sent_spend_txid is not None
def test_005_watchonly(self): def test_005_watchonly(self):
logging.info("---------- Test {} watchonly".format(self.test_coin.name)) logging.info(f"---------- Test {self.test_coin.name} watchonly")
swap_clients = self.swap_clients swap_clients = self.swap_clients
ci0 = swap_clients[0].ci(self.test_coin) ci0 = swap_clients[0].ci(self.test_coin)
@@ -1261,7 +1244,7 @@ class Test(BaseTest):
assert found_txid is not None assert found_txid is not None
def test_008_gettxout(self): def test_008_gettxout(self):
logging.info("---------- Test {} gettxout".format(self.test_coin.name)) logging.info(f"---------- Test {self.test_coin.name} gettxout")
ci0 = self.swap_clients[0].ci(self.test_coin) ci0 = self.swap_clients[0].ci(self.test_coin)
@@ -1373,7 +1356,7 @@ class Test(BaseTest):
assert amount_proved >= require_amount assert amount_proved >= require_amount
def test_009_wallet_encryption(self): def test_009_wallet_encryption(self):
logging.info("---------- Test {} wallet encryption".format(self.test_coin.name)) logging.info(f"---------- Test {self.test_coin.name} wallet encryption")
for coin in ("part", "dcr", "xmr"): for coin in ("part", "dcr", "xmr"):
jsw = read_json_api(1800, f"wallets/{coin}") jsw = read_json_api(1800, f"wallets/{coin}")
@@ -1412,7 +1395,7 @@ class Test(BaseTest):
assert jsw["locked"] is False assert jsw["locked"] is False
def test_010_txn_size(self): def test_010_txn_size(self):
logging.info("---------- Test {} txn size".format(self.test_coin.name)) logging.info(f"---------- Test {self.test_coin.name} txn size")
swap_clients = self.swap_clients swap_clients = self.swap_clients
ci = swap_clients[0].ci(self.test_coin) ci = swap_clients[0].ci(self.test_coin)
+4 -2
View File
@@ -179,6 +179,7 @@ class Test(TestFunctions):
@classmethod @classmethod
def prepareExtraCoins(cls): def prepareExtraCoins(cls):
super().prepareExtraCoins()
if cls.restore_instance: if cls.restore_instance:
void_block_rewards_pubkey = cls.getRandomPubkey() void_block_rewards_pubkey = cls.getRandomPubkey()
cls.doge_addr = ( cls.doge_addr = (
@@ -232,7 +233,7 @@ class Test(TestFunctions):
@classmethod @classmethod
def tearDownClass(cls): def tearDownClass(cls):
logging.info("Finalising DOGE Test") logging.info("Finalising DOGE Test")
super(Test, cls).tearDownClass() super().tearDownClass()
stopDaemons(cls.doge_daemons) stopDaemons(cls.doge_daemons)
cls.doge_daemons.clear() cls.doge_daemons.clear()
@@ -251,11 +252,12 @@ class Test(TestFunctions):
"use_segwit": False, "use_segwit": False,
"blocks_confirmed": 1, "blocks_confirmed": 1,
"min_relay_fee": 0.01, # RECOMMENDED_MIN_TX_FEE "min_relay_fee": 0.01, # RECOMMENDED_MIN_TX_FEE
"wallet_name": "bsx_wallet",
} }
@classmethod @classmethod
def coins_loop(cls): def coins_loop(cls):
super(Test, cls).coins_loop() super().coins_loop()
if cls.pause_chain: if cls.pause_chain:
return return
ci0 = cls.swap_clients[0].ci(cls.test_coin) ci0 = cls.swap_clients[0].ci(cls.test_coin)
@@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2024 The Basicswap developers # Copyright (c) 2024-2026 The Basicswap developers
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -12,7 +12,7 @@ mkdir -p ${TEST_PATH}/bin
cp -r ~/tmp/basicswap_bin/* ${TEST_PATH}/bin cp -r ~/tmp/basicswap_bin/* ${TEST_PATH}/bin
export PYTHONPATH=$(pwd) export PYTHONPATH=$(pwd)
export TEST_COINS_LIST='bitcoin,dogecoin' export TEST_COINS_LIST='bitcoin,dogecoin'
python tests/basicswap/extended/test_doge.py python tests/basicswap/extended/test_doge_with_prepare.py
""" """
@@ -27,11 +27,9 @@ from tests.basicswap.extended.test_xmr_persistent import (
BaseTestWithPrepare, BaseTestWithPrepare,
UI_PORT, UI_PORT,
) )
from tests.basicswap.extended.test_scripts import (
wait_for_offers,
)
from tests.basicswap.util import ( from tests.basicswap.util import (
read_json_api, read_json_api,
wait_for_offers,
) )
logger = logging.getLogger() logger = logging.getLogger()
@@ -50,11 +48,11 @@ def wait_for_bid(
bid = read_json_api(UI_PORT + node_id, f"bids/{bid_id}") bid = read_json_api(UI_PORT + node_id, f"bids/{bid_id}")
if "state" not in bid: if "bid_state" not in bid:
continue continue
if state is None: if state is None:
return return
if bid["state"].lower() == state.lower(): if bid["bid_state"].lower() == state.lower():
return return
raise ValueError("wait_for_bid failed") raise ValueError("wait_for_bid failed")
@@ -101,8 +99,9 @@ def prepare_balance(
class DOGETest(BaseTestWithPrepare): class DOGETest(BaseTestWithPrepare):
def test_a(self): __test__ = True
def test_a(self):
amount_from = 10.0 amount_from = 10.0
offer_json = { offer_json = {
"coin_from": "btc", "coin_from": "btc",
@@ -114,10 +113,8 @@ class DOGETest(BaseTestWithPrepare):
"automation_strat_id": 1, "automation_strat_id": 1,
} }
offer_id = read_json_api(UI_PORT + 0, "offers/new", offer_json)["offer_id"] offer_id = read_json_api(UI_PORT + 0, "offers/new", offer_json)["offer_id"]
logging.debug(f"offer_id {offer_id}")
prepare_balance(self.delay_event, 1, 0, "DOGE", 1000.0) prepare_balance(self.delay_event, 1, 0, "DOGE", 1000.0)
wait_for_offers(self.delay_event, 1, 1, offer_id) wait_for_offers(self.delay_event, 1, 1, offer_id)
post_json = {"offer_id": offer_id, "amount_from": amount_from} post_json = {"offer_id": offer_id, "amount_from": amount_from}
+1 -1
View File
@@ -104,7 +104,7 @@ def prepareDataDir(
fp.write("debug=1\n") fp.write("debug=1\n")
fp.write("debugexclude=libevent\n") fp.write("debugexclude=libevent\n")
fp.write("fallbackfee=0.01\n") fp.write("fallbackfee=0.0002\n")
fp.write("acceptnonstdtxn=0\n") fp.write("acceptnonstdtxn=0\n")
""" """
+1 -3
View File
@@ -29,6 +29,7 @@ import unittest
from tests.basicswap.util import ( from tests.basicswap.util import (
read_json_api, read_json_api,
waitForServer, waitForServer,
UI_PORT,
) )
logger = logging.getLogger() logger = logging.getLogger()
@@ -37,9 +38,6 @@ if not len(logger.handlers):
logger.addHandler(logging.StreamHandler(sys.stdout)) logger.addHandler(logging.StreamHandler(sys.stdout))
PORT_OFS = int(os.getenv("PORT_OFS", 1))
UI_PORT = 12700 + PORT_OFS
ELECTRUM_PATH = os.getenv("ELECTRUM_PATH") ELECTRUM_PATH = os.getenv("ELECTRUM_PATH")
ELECTRUM_DATADIR = os.getenv("ELECTRUM_DATADIR") ELECTRUM_DATADIR = os.getenv("ELECTRUM_DATADIR")
+151 -390
View File
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2022-2023 tecnovert # Copyright (c) 2022-2023 tecnovert
# Copyright (c) 2024-2025 The Basicswap developers # Copyright (c) 2024-2026 The Basicswap developers
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -11,22 +11,14 @@ basicswap]$ python tests/basicswap/extended/test_pivx.py
""" """
import json
import logging import logging
import os import os
import random import random
import shutil
import signal
import sys import sys
import threading
import time
import unittest import unittest
from coincurve.keys import PrivateKey
import basicswap.config as cfg import basicswap.config as cfg
from basicswap.basicswap import ( from basicswap.basicswap import (
BasicSwap,
Coins, Coins,
SwapTypes, SwapTypes,
BidStates, BidStates,
@@ -39,30 +31,27 @@ from basicswap.util import (
from basicswap.basicswap_util import ( from basicswap.basicswap_util import (
TxLockTypes, TxLockTypes,
) )
from basicswap.util.address import (
toWIF,
)
from tests.basicswap.util import ( from tests.basicswap.util import (
read_json_api, read_json_api,
) )
from tests.basicswap.common import ( from tests.basicswap.common import (
callrpc_cli, callrpc_cli,
checkForks,
stopDaemons, stopDaemons,
wait_for_bid, wait_for_bid,
wait_for_offer, wait_for_offer,
wait_for_balance, wait_for_balance,
wait_for_unspent,
wait_for_in_progress, wait_for_in_progress,
wait_for_bid_tx_state, wait_for_bid_tx_state,
TEST_HTTP_HOST,
TEST_HTTP_PORT, TEST_HTTP_PORT,
BASE_PORT,
BASE_RPC_PORT,
BASE_ZMQ_PORT,
PREFIX_SECRET_KEY_REGTEST,
waitForRPC, waitForRPC,
make_rpc_func,
) )
from tests.basicswap.test_xmr import (
BaseTest,
test_delay_event as delay_event,
callnoderpc,
)
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
from basicswap.bin.run import startDaemon from basicswap.bin.run import startDaemon
from basicswap.bin.prepare import downloadPIVXParams from basicswap.bin.prepare import downloadPIVXParams
@@ -72,11 +61,6 @@ if not len(logger.handlers):
logger.addHandler(logging.StreamHandler(sys.stdout)) logger.addHandler(logging.StreamHandler(sys.stdout))
NUM_NODES = 3 NUM_NODES = 3
PIVX_NODE = 3
BTC_NODE = 4
delay_event = threading.Event()
stop_test = False
PIVX_BINDIR = os.path.expanduser( PIVX_BINDIR = os.path.expanduser(
os.getenv("PIVX_BINDIR", os.path.join(cfg.DEFAULT_TEST_BINDIR, "pivx")) os.getenv("PIVX_BINDIR", os.path.join(cfg.DEFAULT_TEST_BINDIR, "pivx"))
@@ -85,345 +69,123 @@ PIVXD = os.getenv("PIVXD", "pivxd" + cfg.bin_suffix)
PIVX_CLI = os.getenv("PIVX_CLI", "pivx-cli" + cfg.bin_suffix) PIVX_CLI = os.getenv("PIVX_CLI", "pivx-cli" + cfg.bin_suffix)
PIVX_TX = os.getenv("PIVX_TX", "pivx-tx" + cfg.bin_suffix) PIVX_TX = os.getenv("PIVX_TX", "pivx-tx" + cfg.bin_suffix)
PIVX_BASE_PORT = 34832
def prepareOtherDir(datadir, nodeId, conf_file="pivx.conf"): PIVX_BASE_RPC_PORT = 35832
node_dir = os.path.join(datadir, str(nodeId)) PIVX_BASE_ZMQ_PORT = 36832
if not os.path.exists(node_dir):
os.makedirs(node_dir)
filePath = os.path.join(node_dir, conf_file)
with open(filePath, "w+") as fp:
fp.write("regtest=1\n")
fp.write("[regtest]\n")
fp.write("port=" + str(BASE_PORT + nodeId) + "\n")
fp.write("rpcport=" + str(BASE_RPC_PORT + nodeId) + "\n")
fp.write("daemon=0\n")
fp.write("printtoconsole=0\n")
fp.write("server=1\n")
fp.write("discover=0\n")
fp.write("listenonion=0\n")
fp.write("bind=127.0.0.1\n")
fp.write("findpeers=0\n")
fp.write("debug=1\n")
fp.write("debugexclude=libevent\n")
fp.write("fallbackfee=0.01\n")
fp.write("acceptnonstdtxn=0\n")
if conf_file == "pivx.conf":
params_dir = os.path.join(datadir, "pivx-params")
downloadPIVXParams(params_dir)
fp.write(f"paramsdir={params_dir}\n")
if conf_file == "bitcoin.conf":
fp.write("wallet=bsx_wallet\n")
def prepareDir(datadir, nodeId, network_key, network_pubkey): def pivxCli(cmd, node_id=0):
node_dir = os.path.join(datadir, str(nodeId))
if not os.path.exists(node_dir):
os.makedirs(node_dir)
filePath = os.path.join(node_dir, "particl.conf")
with open(filePath, "w+") as fp:
fp.write("regtest=1\n")
fp.write("[regtest]\n")
fp.write("port=" + str(BASE_PORT + nodeId) + "\n")
fp.write("rpcport=" + str(BASE_RPC_PORT + nodeId) + "\n")
fp.write("daemon=0\n")
fp.write("printtoconsole=0\n")
fp.write("server=1\n")
fp.write("discover=0\n")
fp.write("listenonion=0\n")
fp.write("bind=127.0.0.1\n")
fp.write("findpeers=0\n")
fp.write("debug=1\n")
fp.write("debugexclude=libevent\n")
fp.write("zmqpubsmsg=tcp://127.0.0.1:" + str(BASE_ZMQ_PORT + nodeId) + "\n")
fp.write("wallet=bsx_wallet\n")
fp.write("fallbackfee=0.01\n")
fp.write("acceptnonstdtxn=0\n")
fp.write("minstakeinterval=5\n")
fp.write("smsgsregtestadjust=0\n")
for i in range(0, NUM_NODES):
if nodeId == i:
continue
fp.write("addnode=127.0.0.1:%d\n" % (BASE_PORT + i))
if nodeId < 2:
fp.write("spentindex=1\n")
fp.write("txindex=1\n")
basicswap_dir = os.path.join(datadir, str(nodeId), "basicswap")
if not os.path.exists(basicswap_dir):
os.makedirs(basicswap_dir)
pivxdatadir = os.path.join(datadir, str(PIVX_NODE))
btcdatadir = os.path.join(datadir, str(BTC_NODE))
settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME)
settings = {
"debug": True,
"zmqhost": "tcp://127.0.0.1",
"zmqport": BASE_ZMQ_PORT + nodeId,
"htmlhost": TEST_HTTP_HOST,
"htmlport": TEST_HTTP_PORT + nodeId,
"network_key": network_key,
"network_pubkey": network_pubkey,
"chainclients": {
"particl": {
"connection_type": "rpc",
"manage_daemon": False,
"rpcport": BASE_RPC_PORT + nodeId,
"datadir": node_dir,
"bindir": cfg.PARTICL_BINDIR,
"blocks_confirmed": 2, # Faster testing
"wallet_name": "bsx_wallet",
},
"pivx": {
"connection_type": "rpc",
"manage_daemon": False,
"rpcport": BASE_RPC_PORT + PIVX_NODE,
"datadir": pivxdatadir,
"bindir": PIVX_BINDIR,
"use_csv": False,
"use_segwit": False,
"wallet_name": "",
},
"bitcoin": {
"connection_type": "rpc",
"manage_daemon": False,
"rpcport": BASE_RPC_PORT + BTC_NODE,
"datadir": btcdatadir,
"bindir": cfg.BITCOIN_BINDIR,
"use_segwit": True,
"wallet_name": "bsx_wallet",
},
},
"check_progress_seconds": 2,
"check_watched_seconds": 4,
"check_expired_seconds": 60,
"check_events_seconds": 1,
"check_xmr_swaps_seconds": 1,
"min_delay_event": 1,
"max_delay_event": 3,
"min_delay_event_short": 1,
"max_delay_event_short": 3,
"min_delay_retry": 2,
"max_delay_retry": 10,
"restrict_unknown_seed_wallets": False,
"check_updates": False,
}
with open(settings_path, "w") as fp:
json.dump(settings, fp, indent=4)
def partRpc(cmd, node_id=0):
return callrpc_cli(
cfg.PARTICL_BINDIR,
os.path.join(cfg.TEST_DATADIRS, str(node_id)),
"regtest",
cmd,
cfg.PARTICL_CLI,
)
def btcRpc(cmd):
return callrpc_cli(
cfg.BITCOIN_BINDIR,
os.path.join(cfg.TEST_DATADIRS, str(BTC_NODE)),
"regtest",
cmd,
cfg.BITCOIN_CLI,
)
def pivxRpc(cmd):
return callrpc_cli( return callrpc_cli(
PIVX_BINDIR, PIVX_BINDIR,
os.path.join(cfg.TEST_DATADIRS, str(PIVX_NODE)), os.path.join(cfg.TEST_DATADIRS, "pivx_" + str(node_id)),
"regtest", "regtest",
cmd, cmd,
PIVX_CLI, PIVX_CLI,
) )
def signal_handler(sig, frame): def prepareDataDir(
global stop_test datadir, node_id, conf_file, dir_prefix, base_p2p_port, base_rpc_port, num_nodes=3
os.write(sys.stdout.fileno(), f"Signal {sig} detected.\n".encode("utf-8")) ):
stop_test = True node_dir = os.path.join(datadir, dir_prefix + str(node_id))
delay_event.set() if not os.path.exists(node_dir):
os.makedirs(node_dir)
cfg_file_path = os.path.join(node_dir, conf_file)
if os.path.exists(cfg_file_path):
return
with open(cfg_file_path, "w+") as fp:
fp.write("regtest=1\n")
fp.write("[regtest]\n")
fp.write("port=" + str(base_p2p_port + node_id) + "\n")
fp.write("rpcport=" + str(base_rpc_port + node_id) + "\n")
salt = generate_salt(16)
fp.write(
"rpcauth={}:{}${}\n".format(
"test" + str(node_id),
salt,
password_to_hmac(salt, "test_pass" + str(node_id)),
)
)
fp.write("daemon=0\n")
fp.write("printtoconsole=0\n")
fp.write("server=1\n")
fp.write("discover=0\n")
fp.write("listenonion=0\n")
fp.write("bind=127.0.0.1\n")
fp.write("findpeers=0\n")
fp.write("debug=1\n")
fp.write("debugexclude=libevent\n")
fp.write("fallbackfee=0.01\n")
fp.write("acceptnonstdtxn=0\n")
params_dir = os.path.join(datadir, "pivx-params")
downloadPIVXParams(params_dir)
fp.write(f"paramsdir={params_dir}\n")
for i in range(0, num_nodes):
if node_id == i:
continue
fp.write("addnode=127.0.0.1:{}\n".format(base_p2p_port + i))
return node_dir
def run_coins_loop(cls): class Test(BaseTest):
while not stop_test: __test__ = True
try:
pivxRpc("generatetoaddress 1 {}".format(cls.pivx_addr))
btcRpc("generatetoaddress 1 {}".format(cls.btc_addr))
except Exception as e:
logging.warning("run_coins_loop " + str(e))
time.sleep(1.0)
def run_loop(self):
while not stop_test:
for c in self.swap_clients:
c.update()
time.sleep(1)
def make_part_cli_rpc_func(node_id):
node_id = node_id
def rpc_func(method, params=None, wallet=None):
cmd = method
if params:
for p in params:
cmd += ' "' + p + '"'
return partRpc(cmd, node_id)
return rpc_func
class Test(unittest.TestCase):
test_coin_from = Coins.PIVX test_coin_from = Coins.PIVX
pivx_daemons = []
pivx_addr = None
start_ltc_nodes = False
start_xmr_nodes = False
@classmethod @classmethod
def setUpClass(cls): def prepareExtraDataDir(cls, i):
super(Test, cls).setUpClass() extra_opts = []
if not cls.restore_instance:
k = PrivateKey() prepareDataDir(
cls.network_key = toWIF(PREFIX_SECRET_KEY_REGTEST, k.secret) cfg.TEST_DATADIRS,
cls.network_pubkey = k.public_key.format().hex() i,
"pivx.conf",
if os.path.isdir(cfg.TEST_DATADIRS): "pivx_",
logging.info("Removing " + cfg.TEST_DATADIRS) base_p2p_port=PIVX_BASE_PORT,
for name in os.listdir(cfg.TEST_DATADIRS): base_rpc_port=PIVX_BASE_RPC_PORT,
if name == "pivx-params":
continue
fullpath = os.path.join(cfg.TEST_DATADIRS, name)
if os.path.isdir(fullpath):
shutil.rmtree(fullpath)
else:
os.remove(fullpath)
for i in range(NUM_NODES):
prepareDir(cfg.TEST_DATADIRS, i, cls.network_key, cls.network_pubkey)
prepareOtherDir(cfg.TEST_DATADIRS, PIVX_NODE)
prepareOtherDir(cfg.TEST_DATADIRS, BTC_NODE, "bitcoin.conf")
cls.daemons = []
cls.swap_clients = []
btc_data_dir = os.path.join(cfg.TEST_DATADIRS, str(BTC_NODE))
if os.path.exists(os.path.join(cfg.BITCOIN_BINDIR, "bitcoin-wallet")):
try:
callrpc_cli(
cfg.BITCOIN_BINDIR,
btc_data_dir,
"regtest",
"-wallet=bsx_wallet -legacy create",
"bitcoin-wallet",
) )
except Exception: cls.pivx_daemons.append(
callrpc_cli(
cfg.BITCOIN_BINDIR,
btc_data_dir,
"regtest",
"-wallet=bsx_wallet create",
"bitcoin-wallet",
)
cls.daemons.append(startDaemon(btc_data_dir, cfg.BITCOIN_BINDIR, cfg.BITCOIND))
logging.info("Started %s %d", cfg.BITCOIND, cls.daemons[-1].handle.pid)
cls.daemons.append(
startDaemon( startDaemon(
os.path.join(cfg.TEST_DATADIRS, str(PIVX_NODE)), PIVX_BINDIR, PIVXD os.path.join(cfg.TEST_DATADIRS, "pivx_" + str(i)),
PIVX_BINDIR,
PIVXD,
opts=extra_opts,
) )
) )
logging.info("Started %s %d", PIVXD, cls.daemons[-1].handle.pid) logging.info("Started %s %d", PIVXD, cls.pivx_daemons[-1].handle.pid)
for i in range(NUM_NODES): waitForRPC(make_rpc_func(i, base_rpc_port=PIVX_BASE_RPC_PORT), delay_event)
data_dir = os.path.join(cfg.TEST_DATADIRS, str(i))
if os.path.exists(os.path.join(cfg.PARTICL_BINDIR, "particl-wallet")):
try:
callrpc_cli(
cfg.PARTICL_BINDIR,
data_dir,
"regtest",
"-wallet=bsx_wallet -legacy create",
"particl-wallet",
)
except Exception:
callrpc_cli(
cfg.PARTICL_BINDIR,
data_dir,
"regtest",
"-wallet=bsx_wallet create",
"particl-wallet",
)
cls.daemons.append(startDaemon(data_dir, cfg.PARTICL_BINDIR, cfg.PARTICLD))
logging.info("Started %s %d", cfg.PARTICLD, cls.daemons[-1].handle.pid)
for i in range(NUM_NODES): @classmethod
rpc = make_part_cli_rpc_func(i) def addPIDInfo(cls, sc, i):
waitForRPC(rpc, delay_event) sc.setDaemonPID(Coins.PIVX, cls.pivx_daemons[i].handle.pid)
if i == 0:
rpc( @classmethod
"extkeyimportmaster", def prepareExtraCoins(cls):
[
"abandon baby cabbage dad eager fabric gadget habit ice kangaroo lab absorb" if cls.restore_instance:
], void_block_rewards_pubkey = cls.getRandomPubkey()
cls.pivx_addr = (
cls.swap_clients[0]
.ci(Coins.PIVX)
.pubkey_to_address(void_block_rewards_pubkey)
) )
elif i == 1:
rpc(
"extkeyimportmaster",
[
"pact mammal barrel matrix local final lecture chunk wasp survey bid various book strong spread fall ozone daring like topple door fatigue limb olympic",
"",
"true",
],
)
rpc("getnewextaddress", ["lblExtTest"])
rpc("rescanblockchain")
else: else:
rpc("extkeyimportmaster", [rpc("mnemonic", ["new"])["master"]])
rpc(
"walletsettings",
[
"stakingoptions",
json.dumps(
{"stakecombinethreshold": 100, "stakesplitthreshold": 200}
).replace('"', '\\"'),
],
)
rpc("reservebalance", ["false"])
basicswap_dir = os.path.join(
os.path.join(cfg.TEST_DATADIRS, str(i)), "basicswap"
)
settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME)
with open(settings_path) as fs:
settings = json.load(fs)
sc = BasicSwap(
basicswap_dir, settings, "regtest", log_name="BasicSwap{}".format(i)
)
cls.swap_clients.append(sc)
sc.setDaemonPID(Coins.BTC, cls.daemons[0].handle.pid)
sc.setDaemonPID(Coins.PIVX, cls.daemons[1].handle.pid)
sc.setDaemonPID(Coins.PART, cls.daemons[2 + i].handle.pid)
sc.start()
waitForRPC(pivxRpc, delay_event)
num_blocks = 1352 # CHECKLOCKTIMEVERIFY soft-fork activates at (regtest) block height 1351. num_blocks = 1352 # CHECKLOCKTIMEVERIFY soft-fork activates at (regtest) block height 1351.
logging.info("Mining %d pivx blocks", num_blocks) logging.info(f"Mining {num_blocks} pivx blocks")
cls.pivx_addr = pivxRpc("getnewaddress mining_addr") cls.pivx_addr = pivxCli("getnewaddress mining_addr")
pivxRpc("generatetoaddress {} {}".format(num_blocks, cls.pivx_addr)) pivxCli(f"generatetoaddress {num_blocks} {cls.pivx_addr}")
ro = pivxRpc("getblockchaininfo") ro = pivxCli("getblockchaininfo")
try: try:
assert ro["bip9_softforks"]["csv"]["status"] == "active" assert ro["bip9_softforks"]["csv"]["status"] == "active"
except Exception: except Exception:
@@ -433,47 +195,47 @@ class Test(unittest.TestCase):
except Exception: except Exception:
logging.info("pivx: segwit is not active") logging.info("pivx: segwit is not active")
waitForRPC(btcRpc, delay_event)
cls.btc_addr = btcRpc("getnewaddress mining_addr bech32")
logging.info("Mining %d Bitcoin blocks to %s", num_blocks, cls.btc_addr)
btcRpc("generatetoaddress {} {}".format(num_blocks, cls.btc_addr))
ro = btcRpc("getblockchaininfo")
checkForks(ro)
signal.signal(signal.SIGINT, signal_handler)
cls.update_thread = threading.Thread(target=run_loop, args=(cls,))
cls.update_thread.start()
cls.coins_update_thread = threading.Thread(target=run_coins_loop, args=(cls,))
cls.coins_update_thread.start()
# Wait for height, or sequencelock is thrown off by genesis blocktime
num_blocks = 3
logging.info("Waiting for Particl chain height %d", num_blocks)
for i in range(60):
particl_blocks = cls.swap_clients[0].callrpc("getblockcount")
print("particl_blocks", particl_blocks)
if particl_blocks >= num_blocks:
break
delay_event.wait(1)
assert particl_blocks >= num_blocks
@classmethod @classmethod
def tearDownClass(cls): def tearDownClass(cls):
global stop_test logging.info("Finalising PIVX Test")
logging.info("Finalising") super().tearDownClass()
stop_test = True
cls.update_thread.join()
cls.coins_update_thread.join()
for c in cls.swap_clients:
c.finalise()
stopDaemons(cls.daemons) stopDaemons(cls.pivx_daemons)
cls.swap_clients.clear() cls.pivx_daemons.clear()
cls.daemons.clear()
super(Test, cls).tearDownClass() @classmethod
def addCoinSettings(cls, settings, datadir, node_id):
settings["chainclients"]["pivx"] = {
"connection_type": "rpc",
"manage_daemon": False,
"rpcport": PIVX_BASE_RPC_PORT + node_id,
"rpcuser": "test" + str(node_id),
"rpcpassword": "test_pass" + str(node_id),
"datadir": os.path.join(datadir, "pivx_" + str(node_id)),
"bindir": PIVX_BINDIR,
"use_csv": False,
"use_segwit": False,
"wallet_name": "",
}
@classmethod
def coins_loop(cls):
super().coins_loop()
callnoderpc(
0, "generatetoaddress", [1, cls.pivx_addr], base_rpc_port=PIVX_BASE_RPC_PORT
)
@classmethod
def prepareBalances(cls):
super().prepareBalances()
cls.prepare_balance(
cls,
Coins.PIVX,
10000.0,
1801,
1800,
)
def test_02_part_pivx(self): def test_02_part_pivx(self):
logging.info("---------- Test PART to PIVX") logging.info("---------- Test PART to PIVX")
@@ -500,7 +262,7 @@ class Test(unittest.TestCase):
wait_for_in_progress(delay_event, swap_clients[1], bid_id, sent=True) wait_for_in_progress(delay_event, swap_clients[1], bid_id, sent=True)
wait_for_bid( wait_for_bid(
delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60 delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=80
) )
wait_for_bid( wait_for_bid(
delay_event, delay_event,
@@ -508,7 +270,7 @@ class Test(unittest.TestCase):
bid_id, bid_id,
BidStates.SWAP_COMPLETED, BidStates.SWAP_COMPLETED,
sent=True, sent=True,
wait_for=60, wait_for=80,
) )
js_0 = read_json_api(1800) js_0 = read_json_api(1800)
@@ -548,7 +310,7 @@ class Test(unittest.TestCase):
wait_for=60, wait_for=60,
) )
wait_for_bid( wait_for_bid(
delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, wait_for=60 delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, wait_for=80
) )
js_0 = read_json_api(1800) js_0 = read_json_api(1800)
@@ -580,7 +342,7 @@ class Test(unittest.TestCase):
wait_for_in_progress(delay_event, swap_clients[1], bid_id, sent=True) wait_for_in_progress(delay_event, swap_clients[1], bid_id, sent=True)
wait_for_bid( wait_for_bid(
delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60 delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=80
) )
wait_for_bid( wait_for_bid(
delay_event, delay_event,
@@ -717,7 +479,7 @@ class Test(unittest.TestCase):
logging.info("---------- Test {} wallet".format(self.test_coin_from.name)) logging.info("---------- Test {} wallet".format(self.test_coin_from.name))
logging.info("Test withdrawal") logging.info("Test withdrawal")
addr = pivxRpc('getnewaddress "Withdrawal test"') addr = pivxCli('getnewaddress "Withdrawal test"')
wallets = read_json_api(TEST_HTTP_PORT + 0, "wallets") wallets = read_json_api(TEST_HTTP_PORT + 0, "wallets")
assert float(wallets[self.test_coin_from.name]["balance"]) > 100 assert float(wallets[self.test_coin_from.name]["balance"]) > 100
@@ -747,30 +509,30 @@ class Test(unittest.TestCase):
def test_09_v3_tx(self): def test_09_v3_tx(self):
logging.info("---------- Test PIVX v3 txns") logging.info("---------- Test PIVX v3 txns")
generate_addr = pivxRpc('getnewaddress "generate test"') generate_addr = pivxCli('getnewaddress "generate test"')
pivx_addr = pivxRpc('getnewaddress "Sapling test"') pivx_addr = pivxCli('getnewaddress "Sapling test"')
pivx_sapling_addr = pivxRpc('getnewshieldaddress "shield addr"') pivx_sapling_addr = pivxCli('getnewshieldaddress "shield addr"')
pivxRpc(f'sendtoaddress "{pivx_addr}" 6.0') pivxCli(f'sendtoaddress "{pivx_addr}" 6.0')
pivxRpc(f'generatetoaddress 1 "{generate_addr}"') pivxCli(f'generatetoaddress 1 "{generate_addr}"')
txid = pivxRpc( txid = pivxCli(
'shieldsendmany "{}" "[{{\\"address\\": \\"{}\\", \\"amount\\": 1}}]"'.format( 'shieldsendmany "{}" "[{{\\"address\\": \\"{}\\", \\"amount\\": 1}}]"'.format(
pivx_addr, pivx_sapling_addr pivx_addr, pivx_sapling_addr
) )
) )
rtx = pivxRpc(f'getrawtransaction "{txid}" true') rtx = pivxCli(f'getrawtransaction "{txid}" true')
assert rtx["version"] == 3 assert rtx["version"] == 3
block_hash = None block_hash = None
for i in range(15): for i in range(15):
rtx = pivxRpc(f'getrawtransaction "{txid}" true') rtx = pivxCli(f'getrawtransaction "{txid}" true')
if "blockhash" in rtx: if "blockhash" in rtx:
block_hash = rtx["blockhash"] block_hash = rtx["blockhash"]
logging.info(f"Shielded tx confirmed in block {block_hash} after {i}s") logging.info(f"Shielded tx confirmed in block {block_hash} after {i}s")
break break
if i == 5: if i == 5:
pivxRpc(f'generatetoaddress 1 "{generate_addr}"') pivxCli(f'generatetoaddress 1 "{generate_addr}"')
delay_event.wait(1) delay_event.wait(1)
assert block_hash is not None, "Shielded tx was not confirmed" assert block_hash is not None, "Shielded tx was not confirmed"
@@ -860,7 +622,6 @@ class Test(unittest.TestCase):
value_after_subfee = ci_from.make_int(itx_decoded["vout"][n]["value"]) value_after_subfee = ci_from.make_int(itx_decoded["vout"][n]["value"])
assert value_after_subfee < swap_value assert value_after_subfee < swap_value
swap_value = value_after_subfee swap_value = value_after_subfee
wait_for_unspent(delay_event, ci_from, swap_value)
extra_options = {"prefunded_itx": itx} extra_options = {"prefunded_itx": itx}
rate_swap = ci_to.make_int(random.uniform(0.2, 10.0), r=1) rate_swap = ci_to.make_int(random.uniform(0.2, 10.0), r=1)
+3 -17
View File
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2023-2024 tecnovert # Copyright (c) 2023-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 # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -36,6 +36,8 @@ from tests.basicswap.common import (
from tests.basicswap.util import ( from tests.basicswap.util import (
read_json_api, read_json_api,
waitForServer, waitForServer,
wait_for_offers,
UI_PORT,
) )
logger = logging.getLogger() logger = logging.getLogger()
@@ -44,10 +46,6 @@ if not len(logger.handlers):
logger.addHandler(logging.StreamHandler(sys.stdout)) logger.addHandler(logging.StreamHandler(sys.stdout))
PORT_OFS = int(os.getenv("PORT_OFS", 1))
UI_PORT = 12700 + PORT_OFS
class HttpHandler(BaseHTTPRequestHandler): class HttpHandler(BaseHTTPRequestHandler):
def js_response(self, url_split, post_string, is_json): def js_response(self, url_split, post_string, is_json):
@@ -131,18 +129,6 @@ def clear_offers(delay_event, node_id) -> None:
raise ValueError("clear_offers failed") raise ValueError("clear_offers failed")
def wait_for_offers(delay_event, node_id, num_offers, offer_id=None) -> None:
logging.info(f"Waiting for {num_offers} offers on node {node_id}")
for i in range(20):
delay_event.wait(1)
offers = read_json_api(
UI_PORT + node_id, "offers" if offer_id is None else f"offers/{offer_id}"
)
if len(offers) >= num_offers:
return
raise ValueError("wait_for_offers failed")
def wait_for_bids(delay_event, node_id, num_bids, offer_id=None) -> None: def wait_for_bids(delay_event, node_id, num_bids, offer_id=None) -> None:
logging.info(f"Waiting for {num_bids} bids on node {node_id}") logging.info(f"Waiting for {num_bids} bids on node {node_id}")
for i in range(20): for i in range(20):
+4 -4
View File
@@ -5,9 +5,9 @@
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import time
import logging import logging
import os import os
import time
from basicswap.basicswap import ( from basicswap.basicswap import (
Coins, Coins,
@@ -120,14 +120,14 @@ class Test(BaseTest):
@classmethod @classmethod
def tearDownClass(cls): def tearDownClass(cls):
logging.info("Finalising Wownero Test") logging.info("Finalising Wownero Test")
super(Test, cls).tearDownClass() super().tearDownClass()
stopDaemons(cls.wow_daemons) stopDaemons(cls.wow_daemons)
cls.wow_daemons.clear() cls.wow_daemons.clear()
@classmethod @classmethod
def coins_loop(cls): def coins_loop(cls):
super(Test, cls).coins_loop() super().coins_loop()
if cls.wow_addr is not None: if cls.wow_addr is not None:
callrpc_xmr( callrpc_xmr(
@@ -162,7 +162,7 @@ class Test(BaseTest):
startXmrWalletDaemon(node_dir, WOW_BINDIR, WOW_WALLET_RPC, opts=opts) startXmrWalletDaemon(node_dir, WOW_BINDIR, WOW_WALLET_RPC, opts=opts)
) )
cls.wow_wallet_auth.append(("test{0}".format(i), "test_pass{0}".format(i))) cls.wow_wallet_auth.append((f"test{i}", f"test_pass{i}"))
waitForWOWNode(i, auth=cls.wow_wallet_auth[i]) waitForWOWNode(i, auth=cls.wow_wallet_auth[i])
@@ -59,6 +59,8 @@ from tests.basicswap.util import (
make_boolean, make_boolean,
read_json_api, read_json_api,
waitForServer, waitForServer,
PORT_OFS,
UI_PORT,
) )
from tests.basicswap.common_xmr import ( from tests.basicswap.common_xmr import (
prepare_nodes, prepare_nodes,
@@ -73,9 +75,6 @@ import basicswap.bin.run as runSystem
test_path = os.path.expanduser(os.getenv("TEST_PATH", "/tmp/test_persistent")) test_path = os.path.expanduser(os.getenv("TEST_PATH", "/tmp/test_persistent"))
RESET_TEST = make_boolean(os.getenv("RESET_TEST", "true")) RESET_TEST = make_boolean(os.getenv("RESET_TEST", "true"))
PORT_OFS = int(os.getenv("PORT_OFS", 1))
UI_PORT = 12700 + PORT_OFS
PARTICL_RPC_PORT_BASE = int(os.getenv("PARTICL_RPC_PORT_BASE", BASE_RPC_PORT)) PARTICL_RPC_PORT_BASE = int(os.getenv("PARTICL_RPC_PORT_BASE", BASE_RPC_PORT))
BITCOIN_RPC_PORT_BASE = int(os.getenv("BITCOIN_RPC_PORT_BASE", BTC_BASE_RPC_PORT)) BITCOIN_RPC_PORT_BASE = int(os.getenv("BITCOIN_RPC_PORT_BASE", BTC_BASE_RPC_PORT))
LITECOIN_RPC_PORT_BASE = int(os.getenv("LITECOIN_RPC_PORT_BASE", LTC_BASE_RPC_PORT)) LITECOIN_RPC_PORT_BASE = int(os.getenv("LITECOIN_RPC_PORT_BASE", LTC_BASE_RPC_PORT))
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2020 tecnovert # Copyright (c) 2020 tecnovert
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2023 tecnovert # Copyright (c) 2023 tecnovert
+17 -19
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2024 The Basicswap developers # Copyright (c) 2024-2026 The Basicswap developers
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -167,6 +167,7 @@ class TestBCH(BasicSwapTest):
@classmethod @classmethod
def prepareExtraCoins(cls): def prepareExtraCoins(cls):
super().prepareExtraCoins()
cls.bch_addr = callnoderpc( cls.bch_addr = callnoderpc(
0, 0,
"getnewaddress", "getnewaddress",
@@ -197,11 +198,12 @@ class TestBCH(BasicSwapTest):
"datadir": os.path.join(datadir, "bch_" + str(node_id)), "datadir": os.path.join(datadir, "bch_" + str(node_id)),
"bindir": BITCOINCASH_BINDIR, "bindir": BITCOINCASH_BINDIR,
"use_segwit": False, "use_segwit": False,
"wallet_name": "bsx_wallet",
} }
@classmethod @classmethod
def coins_loop(cls): def coins_loop(cls):
super(TestBCH, cls).coins_loop() super().coins_loop()
ci0 = cls.swap_clients[0].ci(cls.test_coin) ci0 = cls.swap_clients[0].ci(cls.test_coin)
try: try:
if cls.bch_addr is not None: if cls.bch_addr is not None:
@@ -212,7 +214,7 @@ class TestBCH(BasicSwapTest):
@classmethod @classmethod
def tearDownClass(cls): def tearDownClass(cls):
logging.info("Finalising Bitcoincash Test") logging.info("Finalising Bitcoincash Test")
super(TestBCH, cls).tearDownClass() super().tearDownClass()
stopDaemons(cls.bch_daemons) stopDaemons(cls.bch_daemons)
cls.bch_daemons.clear() cls.bch_daemons.clear()
@@ -224,19 +226,15 @@ class TestBCH(BasicSwapTest):
return True return True
def test_001_nested_segwit(self): def test_001_nested_segwit(self):
logging.info( logging.info(f"---------- Test {self.test_coin.name} p2sh nested segwit")
"---------- Test {} p2sh nested segwit".format(self.test_coin.name)
)
logging.info("Skipped") logging.info("Skipped")
def test_002_native_segwit(self): def test_002_native_segwit(self):
logging.info( logging.info(f"---------- Test {self.test_coin.name} p2sh native segwit")
"---------- Test {} p2sh native segwit".format(self.test_coin.name)
)
logging.info("Skipped") logging.info("Skipped")
def test_003_cltv(self): def test_003_cltv(self):
logging.info("---------- Test {} cltv".format(self.test_coin.name)) logging.info(f"---------- Test {self.test_coin.name} cltv")
ci = self.swap_clients[0].ci(self.test_coin) ci = self.swap_clients[0].ci(self.test_coin)
@@ -348,7 +346,7 @@ class TestBCH(BasicSwapTest):
assert len(tx_wallet["blockhash"]) == 64 assert len(tx_wallet["blockhash"]) == 64
def test_004_csv(self): def test_004_csv(self):
logging.info("---------- Test {} csv".format(self.test_coin.name)) logging.info(f"---------- Test {self.test_coin.name} csv")
ci = self.swap_clients[0].ci(self.test_coin) ci = self.swap_clients[0].ci(self.test_coin)
@@ -451,7 +449,7 @@ class TestBCH(BasicSwapTest):
assert len(tx_wallet["blockhash"]) == 64 assert len(tx_wallet["blockhash"]) == 64
def test_005_watchonly(self): def test_005_watchonly(self):
logging.info("---------- Test {} watchonly".format(self.test_coin.name)) logging.info(f"---------- Test {self.test_coin.name} watchonly")
ci = self.swap_clients[0].ci(self.test_coin) ci = self.swap_clients[0].ci(self.test_coin)
ci1 = self.swap_clients[1].ci(self.test_coin) ci1 = self.swap_clients[1].ci(self.test_coin)
@@ -482,7 +480,7 @@ class TestBCH(BasicSwapTest):
super().test_006_getblock_verbosity() super().test_006_getblock_verbosity()
def test_007_hdwallet(self): def test_007_hdwallet(self):
logging.info("---------- Test {} hdwallet".format(self.test_coin.name)) logging.info(f"---------- Test {self.test_coin.name} hdwallet")
test_seed = "8e54a313e6df8918df6d758fafdbf127a115175fdd2238d0e908dd8093c9ac3b" test_seed = "8e54a313e6df8918df6d758fafdbf127a115175fdd2238d0e908dd8093c9ac3b"
test_wif = ( test_wif = (
@@ -506,10 +504,10 @@ class TestBCH(BasicSwapTest):
super().test_009_scantxoutset() super().test_009_scantxoutset()
def test_010_txn_size(self): def test_010_txn_size(self):
logging.info("---------- Test {} txn_size".format(Coins.BCH)) logging.info(f"---------- Test {self.test_coin.name} txn_size")
swap_clients = self.swap_clients swap_clients = self.swap_clients
ci = swap_clients[0].ci(Coins.BCH) ci = swap_clients[0].ci(self.test_coin)
pi = swap_clients[0].pi(SwapTypes.XMR_SWAP) pi = swap_clients[0].pi(SwapTypes.XMR_SWAP)
amount: int = ci.make_int(random.uniform(0.1, 2.0), r=1) amount: int = ci.make_int(random.uniform(0.1, 2.0), r=1)
@@ -627,7 +625,7 @@ class TestBCH(BasicSwapTest):
def test_011_p2sh(self): def test_011_p2sh(self):
# Not used in bsx for native-segwit coins # Not used in bsx for native-segwit coins
logging.info("---------- Test {} p2sh".format(self.test_coin.name)) logging.info(f"---------- Test {self.test_coin.name} p2sh")
ci = self.swap_clients[0].ci(self.test_coin) ci = self.swap_clients[0].ci(self.test_coin)
@@ -717,7 +715,7 @@ class TestBCH(BasicSwapTest):
def test_011_p2sh32(self): def test_011_p2sh32(self):
# Not used in bsx for native-segwit coins # Not used in bsx for native-segwit coins
logging.info("---------- Test {} p2sh32".format(self.test_coin.name)) logging.info(f"---------- Test {self.test_coin.name} p2sh32")
ci = self.swap_clients[0].ci(self.test_coin) ci = self.swap_clients[0].ci(self.test_coin)
@@ -806,7 +804,7 @@ class TestBCH(BasicSwapTest):
assert len(tx_wallet["blockhash"]) == 64 assert len(tx_wallet["blockhash"]) == 64
def test_012_p2sh_p2wsh(self): def test_012_p2sh_p2wsh(self):
logging.info("---------- Test {} p2sh-p2wsh".format(self.test_coin.name)) logging.info(f"---------- Test {self.test_coin.name} p2sh-p2wsh")
logging.info("Skipped") logging.info("Skipped")
def test_01_a_full_swap(self): def test_01_a_full_swap(self):
@@ -877,7 +875,7 @@ class TestBCH(BasicSwapTest):
def test_06_preselect_inputs(self): def test_06_preselect_inputs(self):
tla_from = self.test_coin.name tla_from = self.test_coin.name
logging.info("---------- Test {} Preselected inputs".format(tla_from)) logging.info(f"---------- Test {tla_from} Preselected inputs")
logging.info("Skipped") logging.info("Skipped")
def test_07_expire_stuck_accepted(self): def test_07_expire_stuck_accepted(self):
+14 -16
View File
@@ -74,6 +74,7 @@ class TestFunctions(BaseTest):
@classmethod @classmethod
def prepareExtraCoins(cls): def prepareExtraCoins(cls):
# Save sent messages so tests can count them
for sc in cls.swap_clients: for sc in cls.swap_clients:
sc._smsg_add_to_outbox = True sc._smsg_add_to_outbox = True
@@ -113,7 +114,7 @@ class TestFunctions(BaseTest):
) )
def do_test_01_full_swap(self, coin_from: Coins, coin_to: Coins) -> None: def do_test_01_full_swap(self, coin_from: Coins, coin_to: Coins) -> None:
logging.info("---------- Test {} to {}".format(coin_from.name, coin_to.name)) logging.info(f"---------- Test {coin_from.name} to {coin_to.name}")
# Offerer sends the offer # Offerer sends the offer
# Bidder sends the bid # Bidder sends the bid
@@ -306,9 +307,7 @@ class TestFunctions(BaseTest):
self, coin_from: Coins, coin_to: Coins, lock_value: int = 32 self, coin_from: Coins, coin_to: Coins, lock_value: int = 32
) -> None: ) -> None:
logging.info( logging.info(
"---------- Test {} to {} leader recovers coin a lock tx".format( f"---------- Test {coin_from.name} to {coin_to.name} leader recovers coin a lock tx"
coin_from.name, coin_to.name
)
) )
id_offerer: int = self.node_a_id id_offerer: int = self.node_a_id
@@ -459,6 +458,12 @@ class TestFunctions(BaseTest):
if with_mercy if with_mercy
else (BidStates.BID_STALLED_FOR_TEST, BidStates.XMR_SWAP_FAILED_SWIPED) else (BidStates.BID_STALLED_FOR_TEST, BidStates.XMR_SWAP_FAILED_SWIPED)
) )
chain_a_coin = coin_to if reverse_bid else coin_from
if with_mercy is False and chain_a_coin == Coins.BCH:
# When using BCH, can't set XMR_SWAP_FAILED_SWIPED as should wait for mercy tx
expect_state = expect_state + (BidStates.XMR_SWAP_SCRIPT_TX_PREREFUND,)
wait_for_bid( wait_for_bid(
test_delay_event, test_delay_event,
swap_clients[id_leader], swap_clients[id_leader],
@@ -492,12 +497,12 @@ class TestFunctions(BaseTest):
# Test manually redeeming the no-script lock tx # Test manually redeeming the no-script lock tx
offerer_key = read_json_api( offerer_key = read_json_api(
1800 + id_offerer, 1800 + id_offerer,
"bids/{}".format(bid_id.hex()), f"bids/{bid_id.hex()}",
{"chainbkeysplit": True}, {"chainbkeysplit": True},
)["splitkey"] )["splitkey"]
data = {"spendchainblocktx": True, "remote_key": offerer_key} data = {"spendchainblocktx": True, "remote_key": offerer_key}
redeemed_txid = read_json_api( redeemed_txid = read_json_api(
1800 + id_bidder, "bids/{}".format(bid_id.hex()), data 1800 + id_bidder, f"bids/{bid_id.hex()}", data
)["txid"] )["txid"]
assert len(redeemed_txid) == 64 assert len(redeemed_txid) == 64
@@ -505,9 +510,7 @@ class TestFunctions(BaseTest):
self, coin_from, coin_to, lock_value: int = 32 self, coin_from, coin_to, lock_value: int = 32
): ):
logging.info( logging.info(
"---------- Test {} to {} follower recovers coin b lock tx".format( f"---------- Test {coin_from.name} to {coin_to.name} follower recovers coin b lock tx"
coin_from.name, coin_to.name
)
) )
id_offerer: int = self.node_a_id id_offerer: int = self.node_a_id
@@ -920,7 +923,7 @@ class BasicSwapTest(TestFunctions):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
super(BasicSwapTest, cls).setUpClass() super().setUpClass()
@classmethod @classmethod
def addCoinSettings(cls, settings, datadir, node_id): def addCoinSettings(cls, settings, datadir, node_id):
@@ -2480,11 +2483,6 @@ class BasicSwapTest(TestFunctions):
def test_09_expire_accepted_rev(self): def test_09_expire_accepted_rev(self):
self.do_test_09_expire_accepted(Coins.XMR, self.test_coin_from) self.do_test_09_expire_accepted(Coins.XMR, self.test_coin_from)
def test_10_presigned_txns(self):
raise RuntimeError(
"TODO"
) # Build without xmr first for quicker test iterations
def test_11_fee_validation(self): def test_11_fee_validation(self):
coin_from, coin_to = (self.test_coin_from, Coins.XMR) coin_from, coin_to = (self.test_coin_from, Coins.XMR)
logging.info( logging.info(
@@ -2822,7 +2820,7 @@ class TestBTC_PARTB(TestFunctions):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
super(TestBTC_PARTB, cls).setUpClass() super().setUpClass()
if False: if False:
for client in cls.swap_clients: for client in cls.swap_clients:
client.log.safe_logs = True client.log.safe_logs = True
+6 -10
View File
@@ -115,6 +115,10 @@ def modify_config(test_path, i):
with open(config_path, "w") as fp: with open(config_path, "w") as fp:
json.dump(settings, fp, indent=4) json.dump(settings, fp, indent=4)
btc_config_path = os.path.join(test_path, f"client{i}", "bitcoin", "bitcoin.conf")
with open(btc_config_path, "a") as fp:
fp.write("minrelaytxfee=0.00001\n")
def wait_for_bid_state( def wait_for_bid_state(
delay_event, node_port: int, bid_id: str, state=None, wait_for: int = 30 delay_event, node_port: int, bid_id: str, state=None, wait_for: int = 30
@@ -641,7 +645,7 @@ class Test(TestFunctions):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
cls.addElectrumxDaemon("bitcoin", 32793, 50001) cls.addElectrumxDaemon("bitcoin", 32793, 50001)
super(Test, cls).setUpClass() super().setUpClass()
@classmethod @classmethod
def modifyConfig(cls, test_path, i): def modifyConfig(cls, test_path, i):
@@ -754,14 +758,6 @@ class Test(TestFunctions):
self.delay_event, self.delay_event,
self.test_coin_b, self.test_coin_b,
100, 100,
self.port_node_1,
self.port_node_0,
True,
)
prepare_balance(
self.delay_event,
self.test_coin_xmr,
100,
self.port_node_0, self.port_node_0,
self.port_node_1, self.port_node_1,
True, True,
@@ -788,7 +784,7 @@ class Test(TestFunctions):
True, True,
) )
self.do_test_03_follower_recover_a_lock_tx( self.do_test_03_follower_recover_a_lock_tx(
self.test_coin_b, self.test_coin_xmr, self.port_node_1, self.port_node_0 self.test_coin_b, self.test_coin_xmr, self.port_node_0, self.port_node_1
) )
def test_03_b_follower_recover_a_lock_tx_reverse(self): def test_03_b_follower_recover_a_lock_tx_reverse(self):
+6 -10
View File
@@ -48,15 +48,11 @@ class TestLTC(BasicSwapTest):
assert deploymentinfo["softforks"][feature_name]["active"] is True assert deploymentinfo["softforks"][feature_name]["active"] is True
def test_001_nested_segwit(self): def test_001_nested_segwit(self):
logging.info( logging.info(f"---------- Test {self.test_coin_from.name} p2sh nested segwit")
"---------- Test {} p2sh nested segwit".format(self.test_coin_from.name)
)
logging.info("Skipped") logging.info("Skipped")
def test_002_native_segwit(self): def test_002_native_segwit(self):
logging.info( logging.info(f"---------- Test {self.test_coin_from.name} p2sh native segwit")
"---------- Test {} p2sh native segwit".format(self.test_coin_from.name)
)
ci = self.swap_clients[0].ci(self.test_coin_from) ci = self.swap_clients[0].ci(self.test_coin_from)
addr_segwit = ci.rpc_wallet("getnewaddress", ["segwit test", "bech32"]) addr_segwit = ci.rpc_wallet("getnewaddress", ["segwit test", "bech32"])
@@ -120,7 +116,7 @@ class TestLTC(BasicSwapTest):
assert tx_funded_decoded["txid"] == tx_signed_decoded["txid"] assert tx_funded_decoded["txid"] == tx_signed_decoded["txid"]
def test_007_hdwallet(self): def test_007_hdwallet(self):
logging.info("---------- Test {} hdwallet".format(self.test_coin_from.name)) logging.info(f"---------- Test {self.test_coin_from.name} hdwallet")
test_seed = "8e54a313e6df8918df6d758fafdbf127a115175fdd2238d0e908dd8093c9ac3b" test_seed = "8e54a313e6df8918df6d758fafdbf127a115175fdd2238d0e908dd8093c9ac3b"
test_wif = ( test_wif = (
@@ -136,7 +132,7 @@ class TestLTC(BasicSwapTest):
assert addr == "rltc1qps7hnjd866e9ynxadgseprkc2l56m00djr82la" assert addr == "rltc1qps7hnjd866e9ynxadgseprkc2l56m00djr82la"
def test_20_btc_coin(self): def test_20_btc_coin(self):
logging.info("---------- Test BTC to {}".format(self.test_coin_from.name)) logging.info(f"---------- Test BTC to {self.test_coin_from.name}")
swap_clients = self.swap_clients swap_clients = self.swap_clients
offer_id = swap_clients[0].postOffer( offer_id = swap_clients[0].postOffer(
@@ -178,7 +174,7 @@ class TestLTC(BasicSwapTest):
assert js_1["num_swapping"] == 0 and js_1["num_watched_outputs"] == 0 assert js_1["num_swapping"] == 0 and js_1["num_watched_outputs"] == 0
def test_21_mweb(self): def test_21_mweb(self):
logging.info("---------- Test MWEB {}".format(self.test_coin_from.name)) logging.info(f"---------- Test MWEB {self.test_coin_from.name}")
swap_clients = self.swap_clients swap_clients = self.swap_clients
ci0 = swap_clients[0].ci(self.test_coin_from) ci0 = swap_clients[0].ci(self.test_coin_from)
@@ -327,7 +323,7 @@ class TestLTC(BasicSwapTest):
# TODO # TODO
def test_22_mweb_balance(self): def test_22_mweb_balance(self):
logging.info("---------- Test MWEB balance {}".format(self.test_coin_from.name)) logging.info(f"---------- Test MWEB balance {self.test_coin_from.name}")
swap_clients = self.swap_clients swap_clients = self.swap_clients
ci_mweb = swap_clients[0].ci(Coins.LTC_MWEB) ci_mweb = swap_clients[0].ci(Coins.LTC_MWEB)
+6 -1
View File
@@ -815,7 +815,7 @@ class BaseTest(unittest.TestCase):
.pubkey_to_address(void_block_rewards_pubkey) .pubkey_to_address(void_block_rewards_pubkey)
) )
logging.info( logging.info(
"Mining %d Litecoin blocks to %s", num_blocks, cls.ltc_addr f"Mining {num_blocks} Litecoin blocks to {cls.ltc_addr}"
) )
callnoderpc( callnoderpc(
0, 0,
@@ -942,6 +942,7 @@ class BaseTest(unittest.TestCase):
) )
cls.coins_update_thread.start() cls.coins_update_thread.start()
cls.prepareBalances()
except Exception: except Exception:
traceback.print_exc() traceback.print_exc()
cls.tearDownClass() cls.tearDownClass()
@@ -999,6 +1000,10 @@ class BaseTest(unittest.TestCase):
def prepareExtraCoins(cls): def prepareExtraCoins(cls):
pass pass
@classmethod
def prepareBalances(cls):
pass
@classmethod @classmethod
def coins_loop(cls): def coins_loop(cls):
if cls.btc_addr is not None: if cls.btc_addr is not None:
+18 -2
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2022-2024 tecnovert # Copyright (c) 2022-2024 tecnovert
@@ -7,9 +6,14 @@
# file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php. # file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php.
import json import json
import logging
import os
import urllib import urllib
from urllib.request import urlopen from urllib.request import urlopen
PORT_OFS = int(os.getenv("PORT_OFS", 1))
UI_PORT = 12700 + PORT_OFS
REQUIRED_SETTINGS = { REQUIRED_SETTINGS = {
"blocks_confirmed": 1, "blocks_confirmed": 1,
"conf_target": 1, "conf_target": 1,
@@ -67,5 +71,17 @@ def waitForServer(delay_event, port, wait_for=40):
_ = read_json_api(port) _ = read_json_api(port)
return return
except Exception as e: except Exception as e:
print("waitForServer, error:", str(e)) logging.error(f"waitForServer: {e}")
raise ValueError("waitForServer failed") raise ValueError("waitForServer failed")
def wait_for_offers(delay_event, node_id, num_offers, offer_id=None) -> None:
logging.info(f"Waiting for {num_offers} offers on node {node_id}")
for i in range(20):
delay_event.wait(1)
offers = read_json_api(
UI_PORT + node_id, "offers" if offer_id is None else f"offers/{offer_id}"
)
if len(offers) >= num_offers:
return
raise ValueError("wait_for_offers failed")