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 BITCOIN_BINDIR="$BIN_DIR/bitcoin"
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
id: test_encrypted_xmr_reload
run: |
+70 -19
View File
@@ -5552,8 +5552,8 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
use_cursor = self.openDB(cursor)
bid, offer = self.getBidAndOffer(bid_id, use_cursor)
ensure(bid, "Bid not found")
ensure(offer, "Offer not found")
ensure(bid, f"Bid not found: {self.log.id(bid_id)}.")
ensure(offer, f"Offer not found: {self.log.id(bid.offer_id)}.")
# Ensure bid is still valid
now: int = self.getTime()
@@ -6828,8 +6828,8 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
try:
use_cursor = self.openDB(cursor)
bid, offer = self.getBidAndOffer(bid_id, use_cursor, with_txns=False)
ensure(bid, "Bid not found")
ensure(offer, "Offer not found")
ensure(bid, f"Bid not found: {self.log.id(bid_id)}.")
ensure(offer, f"Offer not found: {self.log.id(bid.offer_id)}.")
bid.setState(new_state)
self.deactivateBid(use_cursor, offer, bid)
@@ -7747,7 +7747,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
self.logBidEvent(
bid.bid_id,
EventLogTypes.DEBUG_TWEAK_APPLIED,
"ind {}".format(bid.debug_ind),
f"ind {bid.debug_ind}",
cursor,
)
self.commitDB()
@@ -7802,6 +7802,23 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
self.saveBidInSession(bid_id, bid, cursor, xmr_swap)
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 (
TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE not in bid.txns
and refund_tx.block_height is not None
@@ -7947,10 +7964,14 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
"",
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_id=bid_id,
tx_type=TxTypes.XMR_SWAP_A_LOCK_REFUND,
txid=bytes.fromhex(txid),
vout=refund_vout,
)
self.saveBidInSession(bid_id, bid, cursor, xmr_swap)
self.commitDB()
@@ -7962,10 +7983,14 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
)
txid = ci_from.getTxid(xmr_swap.a_lock_refund_tx)
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_id=bid_id,
tx_type=TxTypes.XMR_SWAP_A_LOCK_REFUND,
txid=txid,
vout=refund_vout,
)
self.saveBidInSession(bid_id, bid, cursor, xmr_swap)
self.commitDB()
@@ -8346,14 +8371,15 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
return rv
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):
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:
return False
txi_sequence: int = ci.getTxInSequence(refund_tx_bytes, 0)
return ci.isCsvLockMature(
offer.lock_type,
refund_tx.vin[0].nSequence,
txi_sequence,
parent_tx.block_height,
parent_tx.block_time,
)
@@ -8641,12 +8667,33 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
f"Error trying to submit initiate refund txn: {ex}"
)
if (
should_try_refund_ptx: bool = (
bid.getPTxState() in (TxStates.TX_SENT, TxStates.TX_CONFIRMED)
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:
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:
refund_vout: int = ci_from.getLockRefundVout(
bytes.fromhex(spend_txn_hex), xmr_swap.vkbv
)
bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND] = SwapTx(
bid_id=bid.bid_id,
tx_type=TxTypes.XMR_SWAP_A_LOCK_REFUND,
txid=xmr_swap.a_lock_refund_tx_id,
vout=refund_vout,
)
else:
self.setBidError(
@@ -9140,6 +9191,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
if was_received:
if self.isBchXmrSwap(offer):
# Mercy tx is sent separately
# Can't set XMR_SWAP_FAILED_SWIPED, as bid should continue looking for mercy tx
pass
else:
# Look for a mercy output
@@ -11218,7 +11270,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
refundExtraArgs = dict()
lockExtraArgs = dict()
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
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)}")
bid, offer = self.getBidAndOffer(bid_id, cursor)
ensure(bid, "Bid not found")
ensure(offer, "Offer not found")
ensure(bid, f"Bid not found: {self.log.id(bid_id)}.")
ensure(offer, f"Offer not found: {self.log.id(bid.offer_id)}.")
coin_from = Coins(offer.coin_from)
coin_to = Coins(offer.coin_to)
@@ -14082,9 +14134,9 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
walletinfo = ci.getWalletInfo()
rv = {
"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(
walletinfo["unconfirmed_balance"], conv_int=True
walletinfo["unconfirmed_balance"], conv_int=True, r=-1
),
"expected_seed": ci.knownWalletSeed(),
"encrypted": walletinfo["encrypted"],
@@ -14099,7 +14151,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
if "immature_balance" in walletinfo:
rv["immature"] = ci.format_amount(
walletinfo["immature_balance"], conv_int=True
walletinfo["immature_balance"], conv_int=True, r=-1
)
if "locked_utxos" in walletinfo:
@@ -15090,8 +15142,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
return
bid = self.getBid(bid_id)
if bid is None:
raise ValueError("Bid not found.")
ensure(bid, f"Bid not found: {self.log.id(bid_id)}.")
bid.debug_ind = debug_ind
+3 -1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2024 tecnovert
@@ -193,6 +192,9 @@ class AdaptorSigInterface:
def getScriptLockRefundSwipeTxDummyWitness(self, script: bytes) -> List[bytes]:
return [bytes(72), b"", bytes(len(script))]
def getLockRefundVout(self, lock_refund_tx_data: bytes, vbkv: bytes):
return 0
class Secp256k1Interface(CoinInterface, AdaptorSigInterface):
def __init__(self, **kwargs):
+29 -8
View File
@@ -147,7 +147,9 @@ class BCHInterface(BTCInterface):
if not self.isAddressMine(address, or_watch_only=True):
# 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
@@ -156,15 +158,23 @@ class BCHInterface(BTCInterface):
def createRawFundedTransaction(
self,
addr_to: str,
addr_to: str | bytes,
amount: int,
sub_fee: bool = False,
lock_unspents: bool = True,
feerate: int = None,
) -> str:
txn = self.rpc(
"createrawtransaction", [[], {addr_to: self.format_amount(amount)}]
)
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(
"createrawtransaction", [[], {addr_to: self.format_amount(amount)}]
)
if feerate:
fee_rate = self.format_amount(feerate)
@@ -228,6 +238,16 @@ class BCHInterface(BTCInterface):
)
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):
# Only works for wallet txns
try:
@@ -282,7 +302,7 @@ class BCHInterface(BTCInterface):
found_vout = try_vout
break
except Exception as e: # noqa: F841
# self._log.warning('gettxout {}'.format(e))
# self._log.warning(f"gettxout {e}")
return None
if found_vout is None:
@@ -295,7 +315,7 @@ class BCHInterface(BTCInterface):
# TODO: Better way?
if confirmations > 0:
block_height = self.getChainHeight() - confirmations
block_height = self.getChainHeight() - (confirmations - 1)
rv = {
"txid": txid.hex(),
@@ -516,6 +536,7 @@ class BCHInterface(BTCInterface):
tx_lock = self.loadTx(tx_lock_bytes)
output_script = self.getScriptDest(script_lock)
locked_n = findOutput(tx_lock, output_script)
ensure(locked_n is not None, "Output not found in tx")
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_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
tx_size = 275
dust_limit = 546
+121 -54
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2024 tecnovert
@@ -508,6 +507,14 @@ class BTCInterface(FeeValidator, Secp256k1Interface):
return height
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:
if self.useBackend():
import struct
@@ -601,7 +608,7 @@ class BTCInterface(FeeValidator, Secp256k1Interface):
block_hash = sha256(sha256(header_bytes))[::-1].hex()
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":
raise NotImplementedError(
"getBlockHeader by hash not available in electrum mode"
@@ -2785,6 +2792,47 @@ class BTCInterface(FeeValidator, Secp256k1Interface):
)
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(
self,
chain_b_lock_txid: bytes,
@@ -2798,48 +2846,14 @@ class BTCInterface(FeeValidator, Secp256k1Interface):
lock_tx_vout=None,
) -> bytes:
self._log.info(
"spendBLockTx: {} {}\n".format(
self._log.id(chain_b_lock_txid), lock_tx_vout
)
f"spendBLockTx: {self._log.id(chain_b_lock_txid)} {lock_tx_vout}\n"
)
Kbs = self.getPubkey(kbs)
script_pk = self.getPkDest(Kbs)
locked_n = None
actual_value = None
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
locked_n, actual_value = self.getBLockTxo(
chain_b_lock_txid, lock_tx_vout, script_pk
)
if (
locked_n is not None
@@ -2848,7 +2862,7 @@ class BTCInterface(FeeValidator, Secp256k1Interface):
):
self._log.warning(
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")
@@ -2938,13 +2952,9 @@ class BTCInterface(FeeValidator, Secp256k1Interface):
# Add watchonly address and rescan if required
if not self.isAddressMine(dest_address, or_watch_only=True):
self.importWatchOnlyAddress(dest_address, "bid")
self._log.info(f"Imported watch-only addr: {self._log.addr(dest_address)}")
self._log.info(
"Imported watch-only addr: {}".format(self._log.addr(dest_address))
)
self._log.info(
"Rescanning {} chain from height: {}".format(
self.coin_name(), rescan_from
)
f"Rescanning {self.coin_name()} chain from height: {rescan_from}"
)
self.rpc_wallet("rescanblockchain", [rescan_from])
@@ -3657,7 +3667,7 @@ class BTCInterface(FeeValidator, Secp256k1Interface):
continue
if "desc" in u:
desc = u["desc"]
if self.using_segwit:
if self.using_segwit():
if self.use_p2shp2wsh():
if not desc.startswith("sh(wpkh"):
continue
@@ -3818,7 +3828,7 @@ class BTCInterface(FeeValidator, Secp256k1Interface):
ensure(
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}")
@@ -4457,6 +4467,8 @@ class BTCInterface(FeeValidator, Secp256k1Interface):
return None
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
def isTxNonFinalError(self, err_str: str) -> bool:
@@ -4511,7 +4523,7 @@ class BTCInterface(FeeValidator, Secp256k1Interface):
self._log.id(bytes.fromhex(tx["txid"]))
)
)
self.publishTx(tx_signed)
self.publishTx(bytes.fromhex(tx_signed))
return tx["txid"]
@@ -4549,10 +4561,65 @@ class BTCInterface(FeeValidator, Secp256k1Interface):
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():
print("TODO: testBTCInterface")
try:
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__":
testBTCInterface()
def getTxOutInfo(
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 -*-
# Copyright (c) 2022-2024 tecnovert
+146 -27
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2024 tecnovert
@@ -13,7 +12,7 @@ import logging
import random
import traceback
from typing import List
from typing import List, Optional
from basicswap.basicswap_util import getVoutByScriptPubKey, TxLockTypes
from basicswap.chainparams import Coins
@@ -419,8 +418,10 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
return bci
def getBlockHeader(self, block_hash: str) -> dict:
return self.rpc("getblockheader", [block_hash])
def getWalletInfo(self):
rv = {}
rv = self.rpc_wallet("getinfo")
wi = self.rpc_wallet("walletinfo")
balances = self.rpc_wallet("getbalance")
@@ -595,7 +596,7 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
override_feerate = chain_client_settings.get("override_feerate", None)
if override_feerate:
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"
@@ -919,7 +920,7 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
found_vout = try_vout
break
except Exception as e: # noqa: F841
# self._log.warning('gettxout {}'.format(e))
# self._log.warning(f"gettxout {e})
return None
if found_vout is None:
@@ -932,7 +933,7 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
# TODO: Better way?
if confirmations > 0:
block_height = self.getChainHeight() - confirmations
block_height = self.getChainHeight() - (confirmations - 1)
rv = {
"txid": txid.hex(),
@@ -999,6 +1000,10 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
tx.vout.append(self.txoType()(output_value, script))
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):
inputs_valid: bool = True
validscripts: int = 0
@@ -1151,6 +1156,7 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock)
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
size += 1
pay_fee = round(tx_fee_rate * size / 1000)
tx.vout[0].value = locked_coin - pay_fee
@@ -1202,6 +1208,7 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock)
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
size += 1
pay_fee = round(tx_fee_rate * size / 1000)
tx.vout[0].value = locked_coin - pay_fee
@@ -1253,6 +1260,7 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
script_lock_refund
)
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
size += 1
pay_fee = round(tx_fee_rate * size / 1000)
tx.vout[0].value = locked_coin - pay_fee
@@ -1337,6 +1345,7 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
assert fee_paid > 0
size = len(tx.serialize()) + add_witness_bytes
size += 1
fee_rate_paid = fee_paid * 1000 // size
self._log.info(
@@ -1398,6 +1407,7 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
dummy_witness_stack = self.getScriptLockTxDummyWitness(lock_tx_script)
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
size += 1
fee_rate_paid = fee_paid * 1000 // size
self._log.info(
@@ -1470,6 +1480,7 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
dummy_witness_stack = self.getScriptLockTxDummyWitness(prevout_script)
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
size += 1
fee_rate_paid = fee_paid * 1000 // size
self._log.info(
@@ -1531,6 +1542,7 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
prevout_script
)
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
size += 1
fee_rate_paid = fee_paid * 1000 // size
self._log.info(
@@ -1781,32 +1793,41 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
spend_actual_balance: bool = False,
lock_tx_vout=None,
) -> 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)
script_pk = self.getPkDest(Kbs)
locked_n = None
actual_value = None
wtx = self.rpc_wallet(
"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].value
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)}"
try:
wtx = self.rpc_wallet(
"gettransaction",
[
chain_b_lock_txid.hex(),
],
)
for i, out in enumerate(lock_tx.vout):
self._log.debug(
f" vout[{i}]: value={out.value}, scriptPubKey={out.scriptPubKey.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].value
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.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 (
locked_n is not None
@@ -1815,7 +1836,7 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
):
self._log.warning(
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")
@@ -1851,14 +1872,14 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
try:
txout = self.rpc("gettxout", [txid_hex, 0, 0, True])
except Exception as e: # noqa: F841
# self._log.warning('gettxout {}'.format(e))
# self._log.warning(f"gettxout {e}"))
return None
confirmations: int = (
0 if "confirmations" not in txout else txout["confirmations"]
)
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 None
@@ -1873,3 +1894,101 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
def isTxNonFinalError(self, err_str: str) -> bool:
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 -*-
# Copyright (c) 2024 tecnovert
+5 -4
View File
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2024 tecnovert
# Copyright (c) 2024-2026 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -9,10 +10,10 @@ import traceback
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:
url = "http://{}@{}:{}/".format(auth, host, rpc_port)
x = Jsonrpc(url)
x = Jsonrpc(url, timeout=timeout if timeout else 10)
x.__handler = None
v = x.json_request(method, params)
x.close()
@@ -41,7 +42,7 @@ def make_rpc_func(port, auth, host="127.0.0.1"):
auth = auth
host = host
def rpc_func(method, params=None):
return callrpc(port, auth, method, params, host)
def rpc_func(method, params=None, timeout=None):
return callrpc(port, auth, method, params, host, timeout=timeout)
return rpc_func
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2024 The BasicSwap developers
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2024-2026 The Basicswap developers
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2022-2023 tecnovert
+1 -2
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2023 tecnovert
@@ -103,7 +102,7 @@ class LTCInterface(BTCInterface):
continue
if "desc" in u:
desc = u["desc"]
if self.using_segwit:
if self.using_segwit():
if self.use_p2shp2wsh():
if not desc.startswith("sh(wpkh"):
continue
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2023 tecnovert
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2022 tecnovert
+11 -1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2024 tecnovert
@@ -1300,6 +1299,17 @@ class PARTInterfaceBlind(PARTInterface):
"fundrawtransactionfrom", ["blind", tx_hex, {}, outputs_info, options]
)["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):
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2021 tecnovert
+10 -1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2022 tecnovert
@@ -171,3 +170,13 @@ class PIVXInterface(BTCInterface):
block_height = self.getBlockHeader(rv["blockhash"])["height"]
return {"txid": txid_hex, "amount": 0, "height": block_height}
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 -*-
# Copyright (c) 2024 The Basicswap developers
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2024 tecnovert
+22 -10
View File
@@ -50,11 +50,11 @@ def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key, cursor=None):
try:
use_cursor = self.openDB(cursor)
bid, xmr_swap = self.getXmrBidFromSession(use_cursor, bid_id)
ensure(bid, "Bid not found: {}.".format(bid_id.hex()))
ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex()))
ensure(bid, f"Bid not found: {self.log.id(bid_id)}.")
ensure(xmr_swap, f"Adaptor-sig swap not found: {self.log.id(bid_id)}.")
offer, xmr_offer = self.getXmrOfferFromSession(use_cursor, bid.offer_id)
ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex()))
ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex()))
ensure(offer, f"Offer not found: {self.log.id(bid.offer_id)}.")
ensure(xmr_offer, f"Adaptor-sig offer not found: {self.log.id(bid.offer_id)}.")
# The no-script coin is always the follower
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(
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()
txid = ci_follower.spendBLockTx(
xmr_swap.b_lock_tx_id,
@@ -114,7 +117,7 @@ def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key, cursor=None):
xmr_swap.vkbv,
vkbs,
amount,
xmr_offer.b_fee_rate,
chain_b_fee_rate,
bid.chain_b_height_start,
spend_actual_balance=True,
lock_tx_vout=lock_tx_vout,
@@ -209,7 +212,7 @@ class XmrSwapInterface(ProtocolInterface):
)
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):
return ci.genScriptLockTxScript(ci, Kal, Kaf, **kwargs)
@@ -221,7 +224,12 @@ class XmrSwapInterface(ProtocolInterface):
def getFundedInitiateTxTemplate(
self, ci, amount: int, sub_fee: bool, feerate: int = None
) -> bytes:
addr_to = self.getMockScriptAddr(ci)
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)
funded_tx = ci.createRawFundedTransaction(
addr_to, amount, sub_fee, lock_unspents=False, feerate=feerate
)
@@ -247,8 +255,12 @@ class XmrSwapInterface(ProtocolInterface):
return lock_vout
def promoteMockTx(self, ci, mock_tx: bytes, script: bytearray) -> bytearray:
mock_txo_script = self.getMockScriptScriptPubkey(ci)
real_txo_script = ci.getScriptDest(script)
if ci.coin_type() == Coins.BCH:
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
ctx = ci.loadTx(mock_tx, allow_witness=False)
+1
View File
@@ -22,6 +22,7 @@
- Fixed feerate from other chain displayed for reversed swaps.
- Added warning text for fee above 1.2 x local estimate.
- Added subfee bid option.
- Increase DCR fee estimate by 1 byte.
0.14.5
+7 -2
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2024 tecnovert
@@ -163,7 +162,7 @@ def prepare_balance(
post_json["type_to"] = type_to
json_rv = read_json_api(
port_take_from_node,
"wallets/{}/withdraw".format(coin_ticker.lower()),
f"wallets/{coin_ticker.lower()}/withdraw",
post_json,
)
assert len(json_rv["txid"]) == 64
@@ -236,11 +235,17 @@ def wait_for_bid(
)
if isinstance(state, (list, tuple)):
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
else:
continue
elif state is not None and state != bid[5]:
continue
swap_client.log.debug(
f"TEST: wait_for_bid found {bid_id.hex()}: Bid state {bid[5]}, target {state}."
)
return
else:
if i > 0 and i % 10 == 0:
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2024 tecnovert
+14 -11
View File
@@ -175,6 +175,7 @@ def prepareDir(datadir, nodeId, network_key, network_pubkey):
"datadir": node_dir,
"bindir": cfg.PARTICL_BINDIR,
"blocks_confirmed": 2, # Faster testing
"wallet_name": "bsx_wallet",
},
"dash": {
"connection_type": "rpc",
@@ -184,6 +185,7 @@ def prepareDir(datadir, nodeId, network_key, network_pubkey):
"bindir": DASH_BINDIR,
"use_csv": True,
"use_segwit": False,
"wallet_name": "bsx_wallet",
},
"bitcoin": {
"connection_type": "rpc",
@@ -192,6 +194,7 @@ def prepareDir(datadir, nodeId, network_key, network_pubkey):
"datadir": btcdatadir,
"bindir": cfg.BITCOIN_BINDIR,
"use_segwit": True,
"wallet_name": "bsx_wallet",
},
},
"check_progress_seconds": 2,
@@ -285,7 +288,7 @@ class Test(unittest.TestCase):
@classmethod
def setUpClass(cls):
super(Test, cls).setUpClass()
super().setUpClass()
k = PrivateKey()
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")
if len(dashRpc("listwallets")) < 1:
dashRpc("createwallet wbsx_wallet")
dashRpc("createwallet bsx_wallet")
sc.start()
waitForRPC(dashRpc, delay_event)
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")
dashRpc("generatetoaddress {} {}".format(num_blocks, cls.dash_addr))
dashRpc(f"generatetoaddress {num_blocks} {cls.dash_addr}")
ro = dashRpc("getblockchaininfo")
try:
@@ -431,8 +434,8 @@ class Test(unittest.TestCase):
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))
logging.info(f"Mining {num_blocks} Bitcoin blocks to {cls.btc_addr}")
btcRpc(f"generatetoaddress {num_blocks} {cls.btc_addr}")
ro = btcRpc("getblockchaininfo")
checkForks(ro)
@@ -449,7 +452,7 @@ class Test(unittest.TestCase):
# Wait for height, or sequencelock is thrown off by genesis blocktime
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):
particl_blocks = cls.swap_clients[0].callrpc("getblockcount")
print("particl_blocks", particl_blocks)
@@ -473,7 +476,7 @@ class Test(unittest.TestCase):
cls.swap_clients.clear()
cls.daemons.clear()
super(Test, cls).tearDownClass()
super().tearDownClass()
def test_02_part_dash(self):
logging.info("---------- Test PART to DASH")
@@ -683,9 +686,9 @@ class Test(unittest.TestCase):
offer_id = swap_clients[0].postOffer(
Coins.DASH,
Coins.BTC,
0.001 * COIN,
0.01 * COIN,
1.0 * COIN,
0.001 * COIN,
0.01 * COIN,
SwapTypes.SELLER_FIRST,
)
@@ -709,7 +712,7 @@ class Test(unittest.TestCase):
del swap_clients[0].getChainClientSettings(Coins.DASH)["override_feerate"]
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")
addr = dashRpc('getnewaddress "Withdrawal test"')
+13 -30
View File
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# 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
# 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
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):
logging.info(f"---------- Test {coin_from.name} to {coin_to.name}")
@@ -765,14 +747,14 @@ class Test(BaseTest):
@classmethod
def tearDownClass(cls):
logging.info("Finalising Decred Test")
super(Test, cls).tearDownClass()
super().tearDownClass()
stopDaemons(cls.dcr_daemons)
cls.dcr_daemons.clear()
@classmethod
def coins_loop(cls):
super(Test, cls).coins_loop()
super().coins_loop()
ci0 = cls.swap_clients[0].ci(cls.test_coin)
num_passed: int = 0
@@ -878,15 +860,16 @@ class Test(BaseTest):
"use_csv": True,
"use_segwit": True,
"blocks_confirmed": 1,
"min_relay_fee": 0.00001,
}
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.update(REQUIRED_SETTINGS)
ci = DCRInterface(coin_settings, "mainnet")
ci = DCRInterface(coin_settings, "mainnet", self.swap_clients[0])
k = ci.getNewRandomKey()
K = ci.getPubkey(k)
@@ -914,7 +897,7 @@ class Test(BaseTest):
assert hash160(masterpubkey_data) == seed_hash
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
ci0 = swap_clients[0].ci(self.test_coin)
@@ -972,7 +955,7 @@ class Test(BaseTest):
assert f_decoded["txid"] == ctx.TxHash().hex()
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
swap_clients = self.swap_clients
@@ -1047,7 +1030,7 @@ class Test(BaseTest):
assert len(sent_txid) == 64
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
ci0 = swap_clients[0].ci(self.test_coin)
@@ -1161,7 +1144,7 @@ class Test(BaseTest):
assert sent_spend_txid is not None
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
ci0 = swap_clients[0].ci(self.test_coin)
@@ -1261,7 +1244,7 @@ class Test(BaseTest):
assert found_txid is not None
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)
@@ -1373,7 +1356,7 @@ class Test(BaseTest):
assert amount_proved >= require_amount
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"):
jsw = read_json_api(1800, f"wallets/{coin}")
@@ -1412,7 +1395,7 @@ class Test(BaseTest):
assert jsw["locked"] is False
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
ci = swap_clients[0].ci(self.test_coin)
+4 -2
View File
@@ -179,6 +179,7 @@ class Test(TestFunctions):
@classmethod
def prepareExtraCoins(cls):
super().prepareExtraCoins()
if cls.restore_instance:
void_block_rewards_pubkey = cls.getRandomPubkey()
cls.doge_addr = (
@@ -232,7 +233,7 @@ class Test(TestFunctions):
@classmethod
def tearDownClass(cls):
logging.info("Finalising DOGE Test")
super(Test, cls).tearDownClass()
super().tearDownClass()
stopDaemons(cls.doge_daemons)
cls.doge_daemons.clear()
@@ -251,11 +252,12 @@ class Test(TestFunctions):
"use_segwit": False,
"blocks_confirmed": 1,
"min_relay_fee": 0.01, # RECOMMENDED_MIN_TX_FEE
"wallet_name": "bsx_wallet",
}
@classmethod
def coins_loop(cls):
super(Test, cls).coins_loop()
super().coins_loop()
if cls.pause_chain:
return
ci0 = cls.swap_clients[0].ci(cls.test_coin)
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- 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
# 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
export PYTHONPATH=$(pwd)
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,
UI_PORT,
)
from tests.basicswap.extended.test_scripts import (
wait_for_offers,
)
from tests.basicswap.util import (
read_json_api,
wait_for_offers,
)
logger = logging.getLogger()
@@ -50,11 +48,11 @@ def wait_for_bid(
bid = read_json_api(UI_PORT + node_id, f"bids/{bid_id}")
if "state" not in bid:
if "bid_state" not in bid:
continue
if state is None:
return
if bid["state"].lower() == state.lower():
if bid["bid_state"].lower() == state.lower():
return
raise ValueError("wait_for_bid failed")
@@ -101,8 +99,9 @@ def prepare_balance(
class DOGETest(BaseTestWithPrepare):
def test_a(self):
__test__ = True
def test_a(self):
amount_from = 10.0
offer_json = {
"coin_from": "btc",
@@ -114,10 +113,8 @@ class DOGETest(BaseTestWithPrepare):
"automation_strat_id": 1,
}
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)
wait_for_offers(self.delay_event, 1, 1, offer_id)
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("debugexclude=libevent\n")
fp.write("fallbackfee=0.01\n")
fp.write("fallbackfee=0.0002\n")
fp.write("acceptnonstdtxn=0\n")
"""
+1 -3
View File
@@ -29,6 +29,7 @@ import unittest
from tests.basicswap.util import (
read_json_api,
waitForServer,
UI_PORT,
)
logger = logging.getLogger()
@@ -37,9 +38,6 @@ if not len(logger.handlers):
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_DATADIR = os.getenv("ELECTRUM_DATADIR")
+165 -404
View File
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# 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
# 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 os
import random
import shutil
import signal
import sys
import threading
import time
import unittest
from coincurve.keys import PrivateKey
import basicswap.config as cfg
from basicswap.basicswap import (
BasicSwap,
Coins,
SwapTypes,
BidStates,
@@ -39,30 +31,27 @@ from basicswap.util import (
from basicswap.basicswap_util import (
TxLockTypes,
)
from basicswap.util.address import (
toWIF,
)
from tests.basicswap.util import (
read_json_api,
)
from tests.basicswap.common import (
callrpc_cli,
checkForks,
stopDaemons,
wait_for_bid,
wait_for_offer,
wait_for_balance,
wait_for_unspent,
wait_for_in_progress,
wait_for_bid_tx_state,
TEST_HTTP_HOST,
TEST_HTTP_PORT,
BASE_PORT,
BASE_RPC_PORT,
BASE_ZMQ_PORT,
PREFIX_SECRET_KEY_REGTEST,
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.prepare import downloadPIVXParams
@@ -72,11 +61,6 @@ if not len(logger.handlers):
logger.addHandler(logging.StreamHandler(sys.stdout))
NUM_NODES = 3
PIVX_NODE = 3
BTC_NODE = 4
delay_event = threading.Event()
stop_test = False
PIVX_BINDIR = os.path.expanduser(
os.getenv("PIVX_BINDIR", os.path.join(cfg.DEFAULT_TEST_BINDIR, "pivx"))
@@ -85,395 +69,173 @@ PIVXD = os.getenv("PIVXD", "pivxd" + 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)
def prepareOtherDir(datadir, nodeId, conf_file="pivx.conf"):
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, 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")
PIVX_BASE_PORT = 34832
PIVX_BASE_RPC_PORT = 35832
PIVX_BASE_ZMQ_PORT = 36832
def prepareDir(datadir, nodeId, network_key, network_pubkey):
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):
def pivxCli(cmd, node_id=0):
return callrpc_cli(
PIVX_BINDIR,
os.path.join(cfg.TEST_DATADIRS, str(PIVX_NODE)),
os.path.join(cfg.TEST_DATADIRS, "pivx_" + str(node_id)),
"regtest",
cmd,
PIVX_CLI,
)
def signal_handler(sig, frame):
global stop_test
os.write(sys.stdout.fileno(), f"Signal {sig} detected.\n".encode("utf-8"))
stop_test = True
delay_event.set()
def prepareDataDir(
datadir, node_id, conf_file, dir_prefix, base_p2p_port, base_rpc_port, num_nodes=3
):
node_dir = os.path.join(datadir, dir_prefix + str(node_id))
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")
def run_coins_loop(cls):
while not stop_test:
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
@classmethod
def setUpClass(cls):
super(Test, cls).setUpClass()
k = PrivateKey()
cls.network_key = toWIF(PREFIX_SECRET_KEY_REGTEST, k.secret)
cls.network_pubkey = k.public_key.format().hex()
if os.path.isdir(cfg.TEST_DATADIRS):
logging.info("Removing " + cfg.TEST_DATADIRS)
for name in os.listdir(cfg.TEST_DATADIRS):
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:
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(
os.path.join(cfg.TEST_DATADIRS, str(PIVX_NODE)), PIVX_BINDIR, PIVXD
salt = generate_salt(16)
fp.write(
"rpcauth={}:{}${}\n".format(
"test" + str(node_id),
salt,
password_to_hmac(salt, "test_pass" + str(node_id)),
)
)
logging.info("Started %s %d", PIVXD, cls.daemons[-1].handle.pid)
for i in range(NUM_NODES):
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)
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")
for i in range(NUM_NODES):
rpc = make_part_cli_rpc_func(i)
waitForRPC(rpc, delay_event)
if i == 0:
rpc(
"extkeyimportmaster",
[
"abandon baby cabbage dad eager fabric gadget habit ice kangaroo lab absorb"
],
)
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:
rpc("extkeyimportmaster", [rpc("mnemonic", ["new"])["master"]])
rpc(
"walletsettings",
[
"stakingoptions",
json.dumps(
{"stakecombinethreshold": 100, "stakesplitthreshold": 200}
).replace('"', '\\"'),
],
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
class Test(BaseTest):
__test__ = True
test_coin_from = Coins.PIVX
pivx_daemons = []
pivx_addr = None
start_ltc_nodes = False
start_xmr_nodes = False
@classmethod
def prepareExtraDataDir(cls, i):
extra_opts = []
if not cls.restore_instance:
prepareDataDir(
cfg.TEST_DATADIRS,
i,
"pivx.conf",
"pivx_",
base_p2p_port=PIVX_BASE_PORT,
base_rpc_port=PIVX_BASE_RPC_PORT,
)
rpc("reservebalance", ["false"])
basicswap_dir = os.path.join(
os.path.join(cfg.TEST_DATADIRS, str(i)), "basicswap"
cls.pivx_daemons.append(
startDaemon(
os.path.join(cfg.TEST_DATADIRS, "pivx_" + str(i)),
PIVX_BINDIR,
PIVXD,
opts=extra_opts,
)
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)
)
logging.info("Started %s %d", PIVXD, cls.pivx_daemons[-1].handle.pid)
waitForRPC(make_rpc_func(i, base_rpc_port=PIVX_BASE_RPC_PORT), delay_event)
@classmethod
def addPIDInfo(cls, sc, i):
sc.setDaemonPID(Coins.PIVX, cls.pivx_daemons[i].handle.pid)
@classmethod
def prepareExtraCoins(cls):
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)
)
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()
else:
num_blocks = 1352 # CHECKLOCKTIMEVERIFY soft-fork activates at (regtest) block height 1351.
logging.info(f"Mining {num_blocks} pivx blocks")
cls.pivx_addr = pivxCli("getnewaddress mining_addr")
pivxCli(f"generatetoaddress {num_blocks} {cls.pivx_addr}")
waitForRPC(pivxRpc, delay_event)
num_blocks = 1352 # CHECKLOCKTIMEVERIFY soft-fork activates at (regtest) block height 1351.
logging.info("Mining %d pivx blocks", num_blocks)
cls.pivx_addr = pivxRpc("getnewaddress mining_addr")
pivxRpc("generatetoaddress {} {}".format(num_blocks, cls.pivx_addr))
ro = pivxRpc("getblockchaininfo")
try:
assert ro["bip9_softforks"]["csv"]["status"] == "active"
except Exception:
logging.info("pivx: csv is not active")
try:
assert ro["bip9_softforks"]["segwit"]["status"] == "active"
except Exception:
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
ro = pivxCli("getblockchaininfo")
try:
assert ro["bip9_softforks"]["csv"]["status"] == "active"
except Exception:
logging.info("pivx: csv is not active")
try:
assert ro["bip9_softforks"]["segwit"]["status"] == "active"
except Exception:
logging.info("pivx: segwit is not active")
@classmethod
def tearDownClass(cls):
global stop_test
logging.info("Finalising")
stop_test = True
cls.update_thread.join()
cls.coins_update_thread.join()
for c in cls.swap_clients:
c.finalise()
logging.info("Finalising PIVX Test")
super().tearDownClass()
stopDaemons(cls.daemons)
cls.swap_clients.clear()
cls.daemons.clear()
stopDaemons(cls.pivx_daemons)
cls.pivx_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):
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_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(
delay_event,
@@ -508,7 +270,7 @@ class Test(unittest.TestCase):
bid_id,
BidStates.SWAP_COMPLETED,
sent=True,
wait_for=60,
wait_for=80,
)
js_0 = read_json_api(1800)
@@ -548,7 +310,7 @@ class Test(unittest.TestCase):
wait_for=60,
)
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)
@@ -580,7 +342,7 @@ class Test(unittest.TestCase):
wait_for_in_progress(delay_event, swap_clients[1], bid_id, sent=True)
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(
delay_event,
@@ -717,7 +479,7 @@ class Test(unittest.TestCase):
logging.info("---------- Test {} wallet".format(self.test_coin_from.name))
logging.info("Test withdrawal")
addr = pivxRpc('getnewaddress "Withdrawal test"')
addr = pivxCli('getnewaddress "Withdrawal test"')
wallets = read_json_api(TEST_HTTP_PORT + 0, "wallets")
assert float(wallets[self.test_coin_from.name]["balance"]) > 100
@@ -747,30 +509,30 @@ class Test(unittest.TestCase):
def test_09_v3_tx(self):
logging.info("---------- Test PIVX v3 txns")
generate_addr = pivxRpc('getnewaddress "generate test"')
pivx_addr = pivxRpc('getnewaddress "Sapling test"')
pivx_sapling_addr = pivxRpc('getnewshieldaddress "shield addr"')
generate_addr = pivxCli('getnewaddress "generate test"')
pivx_addr = pivxCli('getnewaddress "Sapling test"')
pivx_sapling_addr = pivxCli('getnewshieldaddress "shield addr"')
pivxRpc(f'sendtoaddress "{pivx_addr}" 6.0')
pivxRpc(f'generatetoaddress 1 "{generate_addr}"')
pivxCli(f'sendtoaddress "{pivx_addr}" 6.0')
pivxCli(f'generatetoaddress 1 "{generate_addr}"')
txid = pivxRpc(
txid = pivxCli(
'shieldsendmany "{}" "[{{\\"address\\": \\"{}\\", \\"amount\\": 1}}]"'.format(
pivx_addr, pivx_sapling_addr
)
)
rtx = pivxRpc(f'getrawtransaction "{txid}" true')
rtx = pivxCli(f'getrawtransaction "{txid}" true')
assert rtx["version"] == 3
block_hash = None
for i in range(15):
rtx = pivxRpc(f'getrawtransaction "{txid}" true')
rtx = pivxCli(f'getrawtransaction "{txid}" true')
if "blockhash" in rtx:
block_hash = rtx["blockhash"]
logging.info(f"Shielded tx confirmed in block {block_hash} after {i}s")
break
if i == 5:
pivxRpc(f'generatetoaddress 1 "{generate_addr}"')
pivxCli(f'generatetoaddress 1 "{generate_addr}"')
delay_event.wait(1)
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"])
assert value_after_subfee < swap_value
swap_value = value_after_subfee
wait_for_unspent(delay_event, ci_from, swap_value)
extra_options = {"prefunded_itx": itx}
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 -*-
# 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
# 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 (
read_json_api,
waitForServer,
wait_for_offers,
UI_PORT,
)
logger = logging.getLogger()
@@ -44,10 +46,6 @@ if not len(logger.handlers):
logger.addHandler(logging.StreamHandler(sys.stdout))
PORT_OFS = int(os.getenv("PORT_OFS", 1))
UI_PORT = 12700 + PORT_OFS
class HttpHandler(BaseHTTPRequestHandler):
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")
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:
logging.info(f"Waiting for {num_bids} bids on node {node_id}")
for i in range(20):
+4 -4
View File
@@ -5,9 +5,9 @@
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import time
import logging
import os
import time
from basicswap.basicswap import (
Coins,
@@ -120,14 +120,14 @@ class Test(BaseTest):
@classmethod
def tearDownClass(cls):
logging.info("Finalising Wownero Test")
super(Test, cls).tearDownClass()
super().tearDownClass()
stopDaemons(cls.wow_daemons)
cls.wow_daemons.clear()
@classmethod
def coins_loop(cls):
super(Test, cls).coins_loop()
super().coins_loop()
if cls.wow_addr is not None:
callrpc_xmr(
@@ -162,7 +162,7 @@ class Test(BaseTest):
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])
@@ -59,6 +59,8 @@ from tests.basicswap.util import (
make_boolean,
read_json_api,
waitForServer,
PORT_OFS,
UI_PORT,
)
from tests.basicswap.common_xmr import (
prepare_nodes,
@@ -73,9 +75,6 @@ import basicswap.bin.run as runSystem
test_path = os.path.expanduser(os.getenv("TEST_PATH", "/tmp/test_persistent"))
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))
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))
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (c) 2020 tecnovert
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (c) 2023 tecnovert
+17 -19
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- 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
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -167,6 +167,7 @@ class TestBCH(BasicSwapTest):
@classmethod
def prepareExtraCoins(cls):
super().prepareExtraCoins()
cls.bch_addr = callnoderpc(
0,
"getnewaddress",
@@ -197,11 +198,12 @@ class TestBCH(BasicSwapTest):
"datadir": os.path.join(datadir, "bch_" + str(node_id)),
"bindir": BITCOINCASH_BINDIR,
"use_segwit": False,
"wallet_name": "bsx_wallet",
}
@classmethod
def coins_loop(cls):
super(TestBCH, cls).coins_loop()
super().coins_loop()
ci0 = cls.swap_clients[0].ci(cls.test_coin)
try:
if cls.bch_addr is not None:
@@ -212,7 +214,7 @@ class TestBCH(BasicSwapTest):
@classmethod
def tearDownClass(cls):
logging.info("Finalising Bitcoincash Test")
super(TestBCH, cls).tearDownClass()
super().tearDownClass()
stopDaemons(cls.bch_daemons)
cls.bch_daemons.clear()
@@ -224,19 +226,15 @@ class TestBCH(BasicSwapTest):
return True
def test_001_nested_segwit(self):
logging.info(
"---------- Test {} p2sh nested segwit".format(self.test_coin.name)
)
logging.info(f"---------- Test {self.test_coin.name} p2sh nested segwit")
logging.info("Skipped")
def test_002_native_segwit(self):
logging.info(
"---------- Test {} p2sh native segwit".format(self.test_coin.name)
)
logging.info(f"---------- Test {self.test_coin.name} p2sh native segwit")
logging.info("Skipped")
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)
@@ -348,7 +346,7 @@ class TestBCH(BasicSwapTest):
assert len(tx_wallet["blockhash"]) == 64
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)
@@ -451,7 +449,7 @@ class TestBCH(BasicSwapTest):
assert len(tx_wallet["blockhash"]) == 64
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)
ci1 = self.swap_clients[1].ci(self.test_coin)
@@ -482,7 +480,7 @@ class TestBCH(BasicSwapTest):
super().test_006_getblock_verbosity()
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_wif = (
@@ -506,10 +504,10 @@ class TestBCH(BasicSwapTest):
super().test_009_scantxoutset()
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
ci = swap_clients[0].ci(Coins.BCH)
ci = swap_clients[0].ci(self.test_coin)
pi = swap_clients[0].pi(SwapTypes.XMR_SWAP)
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):
# 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)
@@ -717,7 +715,7 @@ class TestBCH(BasicSwapTest):
def test_011_p2sh32(self):
# 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)
@@ -806,7 +804,7 @@ class TestBCH(BasicSwapTest):
assert len(tx_wallet["blockhash"]) == 64
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")
def test_01_a_full_swap(self):
@@ -877,7 +875,7 @@ class TestBCH(BasicSwapTest):
def test_06_preselect_inputs(self):
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")
def test_07_expire_stuck_accepted(self):
+14 -16
View File
@@ -74,6 +74,7 @@ class TestFunctions(BaseTest):
@classmethod
def prepareExtraCoins(cls):
# Save sent messages so tests can count them
for sc in cls.swap_clients:
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:
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
# Bidder sends the bid
@@ -306,9 +307,7 @@ class TestFunctions(BaseTest):
self, coin_from: Coins, coin_to: Coins, lock_value: int = 32
) -> None:
logging.info(
"---------- Test {} to {} leader recovers coin a lock tx".format(
coin_from.name, coin_to.name
)
f"---------- Test {coin_from.name} to {coin_to.name} leader recovers coin a lock tx"
)
id_offerer: int = self.node_a_id
@@ -459,6 +458,12 @@ class TestFunctions(BaseTest):
if with_mercy
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(
test_delay_event,
swap_clients[id_leader],
@@ -492,12 +497,12 @@ class TestFunctions(BaseTest):
# Test manually redeeming the no-script lock tx
offerer_key = read_json_api(
1800 + id_offerer,
"bids/{}".format(bid_id.hex()),
f"bids/{bid_id.hex()}",
{"chainbkeysplit": True},
)["splitkey"]
data = {"spendchainblocktx": True, "remote_key": offerer_key}
redeemed_txid = read_json_api(
1800 + id_bidder, "bids/{}".format(bid_id.hex()), data
1800 + id_bidder, f"bids/{bid_id.hex()}", data
)["txid"]
assert len(redeemed_txid) == 64
@@ -505,9 +510,7 @@ class TestFunctions(BaseTest):
self, coin_from, coin_to, lock_value: int = 32
):
logging.info(
"---------- Test {} to {} follower recovers coin b lock tx".format(
coin_from.name, coin_to.name
)
f"---------- Test {coin_from.name} to {coin_to.name} follower recovers coin b lock tx"
)
id_offerer: int = self.node_a_id
@@ -920,7 +923,7 @@ class BasicSwapTest(TestFunctions):
@classmethod
def setUpClass(cls):
super(BasicSwapTest, cls).setUpClass()
super().setUpClass()
@classmethod
def addCoinSettings(cls, settings, datadir, node_id):
@@ -2480,11 +2483,6 @@ class BasicSwapTest(TestFunctions):
def test_09_expire_accepted_rev(self):
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):
coin_from, coin_to = (self.test_coin_from, Coins.XMR)
logging.info(
@@ -2822,7 +2820,7 @@ class TestBTC_PARTB(TestFunctions):
@classmethod
def setUpClass(cls):
super(TestBTC_PARTB, cls).setUpClass()
super().setUpClass()
if False:
for client in cls.swap_clients:
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:
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(
delay_event, node_port: int, bid_id: str, state=None, wait_for: int = 30
@@ -641,7 +645,7 @@ class Test(TestFunctions):
@classmethod
def setUpClass(cls):
cls.addElectrumxDaemon("bitcoin", 32793, 50001)
super(Test, cls).setUpClass()
super().setUpClass()
@classmethod
def modifyConfig(cls, test_path, i):
@@ -754,14 +758,6 @@ class Test(TestFunctions):
self.delay_event,
self.test_coin_b,
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_1,
True,
@@ -788,7 +784,7 @@ class Test(TestFunctions):
True,
)
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):
+6 -10
View File
@@ -48,15 +48,11 @@ class TestLTC(BasicSwapTest):
assert deploymentinfo["softforks"][feature_name]["active"] is True
def test_001_nested_segwit(self):
logging.info(
"---------- Test {} p2sh nested segwit".format(self.test_coin_from.name)
)
logging.info(f"---------- Test {self.test_coin_from.name} p2sh nested segwit")
logging.info("Skipped")
def test_002_native_segwit(self):
logging.info(
"---------- Test {} p2sh native segwit".format(self.test_coin_from.name)
)
logging.info(f"---------- Test {self.test_coin_from.name} p2sh native segwit")
ci = self.swap_clients[0].ci(self.test_coin_from)
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"]
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_wif = (
@@ -136,7 +132,7 @@ class TestLTC(BasicSwapTest):
assert addr == "rltc1qps7hnjd866e9ynxadgseprkc2l56m00djr82la"
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
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
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
ci0 = swap_clients[0].ci(self.test_coin_from)
@@ -327,7 +323,7 @@ class TestLTC(BasicSwapTest):
# TODO
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
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)
)
logging.info(
"Mining %d Litecoin blocks to %s", num_blocks, cls.ltc_addr
f"Mining {num_blocks} Litecoin blocks to {cls.ltc_addr}"
)
callnoderpc(
0,
@@ -942,6 +942,7 @@ class BaseTest(unittest.TestCase):
)
cls.coins_update_thread.start()
cls.prepareBalances()
except Exception:
traceback.print_exc()
cls.tearDownClass()
@@ -999,6 +1000,10 @@ class BaseTest(unittest.TestCase):
def prepareExtraCoins(cls):
pass
@classmethod
def prepareBalances(cls):
pass
@classmethod
def coins_loop(cls):
if cls.btc_addr is not None:
+18 -2
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (c) 2022-2024 tecnovert
@@ -7,9 +6,14 @@
# file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php.
import json
import logging
import os
import urllib
from urllib.request import urlopen
PORT_OFS = int(os.getenv("PORT_OFS", 1))
UI_PORT = 12700 + PORT_OFS
REQUIRED_SETTINGS = {
"blocks_confirmed": 1,
"conf_target": 1,
@@ -67,5 +71,17 @@ def waitForServer(delay_event, port, wait_for=40):
_ = read_json_api(port)
return
except Exception as e:
print("waitForServer, error:", str(e))
logging.error(f"waitForServer: {e}")
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")