Merge branch 'firo' into GUI

2024-05-20_merge
tecnovert 2 years ago
commit eb8aa18224
  1. 2
      basicswap/__init__.py
  2. 7
      basicswap/base.py
  3. 149
      basicswap/basicswap.py
  4. 20
      basicswap/basicswap_util.py
  5. 40
      basicswap/chainparams.py
  6. 5
      basicswap/config.py
  7. 188
      basicswap/interface/btc.py
  8. 175
      basicswap/interface/firo.py
  9. 44
      basicswap/interface/part.py
  10. 5
      basicswap/js_server.py
  11. 7
      basicswap/protocols/atomic_swap_1.py
  12. 2
      basicswap/ui/page_wallet.py
  13. 10
      basicswap/ui/util.py
  14. 186
      bin/basicswap_prepare.py
  15. 8
      doc/api.md
  16. 16
      docker/production/compose-fragments/1_dash.yml
  17. 16
      docker/production/compose-fragments/1_firo.yml
  18. 16
      docker/production/compose-fragments/1_pivx.yml
  19. 27
      docker/production/dash/Dockerfile
  20. 11
      docker/production/dash/entrypoint.sh
  21. 18
      docker/production/example.env
  22. 25
      docker/production/firo/Dockerfile
  23. 11
      docker/production/firo/entrypoint.sh
  24. 14
      docker/production/notes.md
  25. 25
      docker/production/pivx/Dockerfile
  26. 11
      docker/production/pivx/entrypoint.sh
  27. 13
      pgp/keys/firo_reuben.pgp
  28. 0
      pgp/keys/monero_binaryfate.pgp
  29. 4
      tests/basicswap/common.py
  30. 45
      tests/basicswap/extended/test_dash.py
  31. 470
      tests/basicswap/extended/test_firo.py
  32. 37
      tests/basicswap/extended/test_nmc.py
  33. 45
      tests/basicswap/extended/test_pivx.py
  34. 5
      tests/basicswap/extended/test_xmr_persistent.py
  35. 248
      tests/basicswap/test_btc_xmr.py
  36. 281
      tests/basicswap/test_ltc_xmr.py
  37. 2
      tests/basicswap/test_other.py
  38. 15
      tests/basicswap/test_run.py
  39. 353
      tests/basicswap/test_xmr.py

@ -1,3 +1,3 @@
name = "basicswap"
__version__ = "0.11.44"
__version__ = "0.11.45"

@ -15,7 +15,6 @@ import traceback
import subprocess
import basicswap.config as cfg
import basicswap.contrib.segwit_addr as segwit_addr
from .rpc import (
callrpc,
@ -112,12 +111,6 @@ class BaseApp:
return c
raise ValueError('Unknown coin: {}'.format(coin_name))
def encodeSegwit(self, coin_type, raw):
return segwit_addr.encode(chainparams[coin_type][self.chain]['hrp'], 0, raw)
def decodeSegwit(self, coin_type, addr):
return bytes(segwit_addr.decode(chainparams[coin_type][self.chain]['hrp'], addr)[1])
def callrpc(self, method, params=[], wallet=None):
cc = self.coin_clients[Coins.PART]
return callrpc(cc['rpcport'], cc['rpcauth'], method, params, wallet, cc['rpchost'])

@ -34,6 +34,7 @@ from .interface.nmc import NMCInterface
from .interface.xmr import XMRInterface
from .interface.pivx import PIVXInterface
from .interface.dash import DASHInterface
from .interface.firo import FIROInterface
from .interface.passthrough_btc import PassthroughBTCInterface
from . import __version__
@ -58,7 +59,6 @@ from .util.address import (
getKeyID,
decodeWif,
decodeAddress,
encodeAddress,
pubkeyToAddress,
)
from .chainparams import (
@ -531,6 +531,8 @@ class BasicSwap(BaseApp):
return PIVXInterface(self.coin_clients[coin], self.chain, self)
elif coin == Coins.DASH:
return DASHInterface(self.coin_clients[coin], self.chain, self)
elif coin == Coins.FIRO:
return FIROInterface(self.coin_clients[coin], self.chain, self)
else:
raise ValueError('Unknown coin type')
@ -549,7 +551,7 @@ class BasicSwap(BaseApp):
authcookiepath = os.path.join(self.getChainDatadirPath(coin), '.cookie')
pidfilename = cc['name']
if cc['name'] in ('bitcoin', 'litecoin', 'namecoin', 'dash'):
if cc['name'] in ('bitcoin', 'litecoin', 'namecoin', 'dash', 'firo'):
pidfilename += 'd'
pidfilepath = os.path.join(self.getChainDatadirPath(coin), pidfilename + '.pid')
@ -725,7 +727,7 @@ class BasicSwap(BaseApp):
return
root_key = self.getWalletKey(coin_type, 1)
root_hash = ci.getAddressHashFromKey(root_key)[::-1]
root_hash = ci.getSeedHash(root_key)
try:
ci.initialiseWallet(root_key)
@ -975,10 +977,8 @@ class BasicSwap(BaseApp):
raise ValueError('Invalid swap type for PART_ANON')
if (coin_from == Coins.PART_BLIND or coin_to == Coins.PART_BLIND) and swap_type != SwapTypes.XMR_SWAP:
raise ValueError('Invalid swap type for PART_BLIND')
if coin_from == Coins.PIVX and swap_type == SwapTypes.XMR_SWAP:
raise ValueError('TODO: PIVX -> XMR')
if coin_from == Coins.DASH and swap_type == SwapTypes.XMR_SWAP:
raise ValueError('TODO: DASH -> XMR')
if coin_from in (Coins.PIVX, Coins.DASH, Coins.FIRO, Coins.NMC) and swap_type == SwapTypes.XMR_SWAP:
raise ValueError('TODO: {} -> XMR'.format(coin_from.name))
def notify(self, event_type, event_data, session=None):
@ -1053,7 +1053,6 @@ class BasicSwap(BaseApp):
ensure(amount_to < ci_to.max_amount(), 'To amount above max value for chain')
def validateOfferLockValue(self, coin_from, coin_to, lock_type, lock_value):
coin_from_has_csv = self.coin_clients[coin_from]['use_csv']
coin_to_has_csv = self.coin_clients[coin_to]['use_csv']
@ -1609,18 +1608,6 @@ class BasicSwap(BaseApp):
self.mxDB.release()
return self._contract_count
def getUnspentsByAddr(self, coin_type):
ci = self.ci(coin_type)
unspent_addr = dict()
unspent = self.callcoinrpc(coin_type, 'listunspent')
for u in unspent:
if u['spendable'] is not True:
continue
unspent_addr[u['address']] = unspent_addr.get(u['address'], 0) + ci.make_int(u['amount'], r=1)
return unspent_addr
def getProofOfFunds(self, coin_type, amount_for, extra_commit_bytes):
ci = self.ci(coin_type)
self.log.debug('getProofOfFunds %s %s', ci.coin_name(), ci.format_amount(amount_for))
@ -1628,28 +1615,7 @@ class BasicSwap(BaseApp):
if self.coin_clients[coin_type]['connection_type'] != 'rpc':
return (None, None)
# TODO: Lock unspent and use same output/s to fund bid
unspent_addr = self.getUnspentsByAddr(coin_type)
sign_for_addr = None
for addr, value in unspent_addr.items():
if value >= amount_for:
sign_for_addr = addr
break
ensure(sign_for_addr is not None, 'Could not find address with enough funds for proof')
self.log.debug('sign_for_addr %s', sign_for_addr)
if self.coin_clients[coin_type]['use_segwit']: # TODO: Use isSegwitAddress when scantxoutset can use combo
# 'Address does not refer to key' for non p2pkh
addrinfo = self.callcoinrpc(coin_type, 'getaddressinfo', [sign_for_addr])
pkh = addrinfo['scriptPubKey'][4:]
sign_for_addr = encodeAddress(bytes((chainparams[coin_type][self.chain]['pubkey_address'],)) + bytes.fromhex(pkh))
self.log.debug('sign_for_addr converted %s', sign_for_addr)
signature = self.callcoinrpc(coin_type, 'signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + extra_commit_bytes.hex()])
return (sign_for_addr, signature)
return ci.getProofOfFunds(amount_for, extra_commit_bytes)
def saveBidInSession(self, bid_id, bid, session, xmr_swap=None, save_in_progress=None):
session.add(bid)
@ -1859,6 +1825,15 @@ class BasicSwap(BaseApp):
session.remove()
self.mxDB.release()
def setTxBlockInfoFromHeight(self, ci, tx, height):
try:
tx.block_height = height
block_header = ci.getBlockHeaderFromHeight(height)
tx.block_hash = bytes.fromhex(block_header['hash'])
tx.block_time = block_header['time'] # Or median_time?
except Exception as e:
self.log.warning(f'setTxBlockInfoFromHeight failed {e}')
def loadBidTxns(self, bid, session):
bid.txns = {}
for stx in session.query(SwapTx).filter(sa.and_(SwapTx.bid_id == bid.bid_id)):
@ -2065,6 +2040,7 @@ class BasicSwap(BaseApp):
script=script,
)
bid.setITxState(TxStates.TX_SENT)
self.logEvent(Concepts.BID, bid.bid_id, EventLogTypes.ITX_PUBLISHED, '', None)
# Check non-bip68 final
try:
@ -2293,16 +2269,16 @@ class BasicSwap(BaseApp):
xmr_swap.kbsl_dleag = xmr_swap.pkbsl
# MSG2F
xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script = ci_from.createScriptLockTx(
xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script = ci_from.createSCLockTx(
bid.amount,
xmr_swap.pkal, xmr_swap.pkaf, xmr_swap.vkbv
)
xmr_swap.a_lock_tx = ci_from.fundScriptLockTx(xmr_swap.a_lock_tx, xmr_offer.a_fee_rate, xmr_swap.vkbv)
xmr_swap.a_lock_tx = ci_from.fundSCLockTx(xmr_swap.a_lock_tx, xmr_offer.a_fee_rate, xmr_swap.vkbv)
xmr_swap.a_lock_tx_id = ci_from.getTxid(xmr_swap.a_lock_tx)
a_lock_tx_dest = ci_from.getScriptDest(xmr_swap.a_lock_tx_script)
xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script, xmr_swap.a_swap_refund_value = ci_from.createScriptLockRefundTx(
xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script, xmr_swap.a_swap_refund_value = ci_from.createSCLockRefundTx(
xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script,
xmr_swap.pkal, xmr_swap.pkaf,
xmr_offer.lock_time_1, xmr_offer.lock_time_2,
@ -2316,7 +2292,7 @@ class BasicSwap(BaseApp):
ensure(v, 'Invalid coin A lock refund tx leader sig')
pkh_refund_to = ci_from.decodeAddress(self.getReceiveAddressForCoin(coin_from))
xmr_swap.a_lock_refund_spend_tx = ci_from.createScriptLockRefundSpendTx(
xmr_swap.a_lock_refund_spend_tx = ci_from.createSCLockRefundSpendTx(
xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script,
pkh_refund_to,
xmr_offer.a_fee_rate, xmr_swap.vkbv
@ -2326,7 +2302,7 @@ class BasicSwap(BaseApp):
# Double check txns before sending
self.log.debug('Bid: {} - Double checking chain A lock txns are valid before sending bid accept.'.format(bid_id.hex()))
check_lock_tx_inputs = False # TODO: check_lock_tx_inputs without txindex
_, xmr_swap.a_lock_tx_vout = ci_from.verifyLockTx(
_, xmr_swap.a_lock_tx_vout = ci_from.verifySCLockTx(
xmr_swap.a_lock_tx,
xmr_swap.a_lock_tx_script,
bid.amount,
@ -2336,7 +2312,7 @@ class BasicSwap(BaseApp):
check_lock_tx_inputs,
xmr_swap.vkbv)
_, _, lock_refund_vout = ci_from.verifyLockRefundTx(
_, _, lock_refund_vout = ci_from.verifySCLockRefundTx(
xmr_swap.a_lock_refund_tx,
xmr_swap.a_lock_tx,
xmr_swap.a_lock_refund_tx_script,
@ -2351,7 +2327,7 @@ class BasicSwap(BaseApp):
xmr_offer.a_fee_rate,
xmr_swap.vkbv)
ci_from.verifyLockRefundSpendTx(
ci_from.verifySCLockRefundSpendTx(
xmr_swap.a_lock_refund_spend_tx, xmr_swap.a_lock_refund_tx,
xmr_swap.a_lock_refund_tx_id, xmr_swap.a_lock_refund_tx_script,
xmr_swap.pkal,
@ -2602,8 +2578,8 @@ class BasicSwap(BaseApp):
assert (addr_redeem_out is not None)
if self.coin_clients[coin_type]['use_segwit']:
# Change to btc hrp
addr_redeem_out = self.encodeSegwit(Coins.PART, self.decodeSegwit(coin_type, addr_redeem_out))
# Change to part hrp
addr_redeem_out = self.ci(Coins.PART).encodeSegwitAddress(ci.decodeSegwitAddress(addr_redeem_out))
else:
addr_redeem_out = replaceAddrPrefix(addr_redeem_out, Coins.PART, self.chain)
self.log.debug('addr_redeem_out %s', addr_redeem_out)
@ -2704,8 +2680,8 @@ class BasicSwap(BaseApp):
addr_refund_out = self.getReceiveAddressFromPool(coin_type, bid.bid_id, tx_type)
ensure(addr_refund_out is not None, 'addr_refund_out is null')
if self.coin_clients[coin_type]['use_segwit']:
# Change to btc hrp
addr_refund_out = self.encodeSegwit(Coins.PART, self.decodeSegwit(coin_type, addr_refund_out))
# Change to part hrp
addr_refund_out = self.ci(Coins.PART).encodeSegwitAddress(ci.decodeSegwitAddress(addr_refund_out))
else:
addr_refund_out = replaceAddrPrefix(addr_refund_out, Coins.PART, self.chain)
self.log.debug('addr_refund_out %s', addr_refund_out)
@ -2783,6 +2759,7 @@ class BasicSwap(BaseApp):
txid = self.ci(coin_to).publishTx(bytes.fromhex(txn))
self.log.debug('Submitted participate txn %s to %s chain for bid %s', txid, chainparams[coin_to]['name'], bid_id.hex())
bid.setPTxState(TxStates.TX_SENT)
self.logEvent(Concepts.BID, bid.bid_id, EventLogTypes.PTX_PUBLISHED, '', None)
else:
bid.participate_tx = SwapTx(
bid_id=bid_id,
@ -2836,6 +2813,7 @@ class BasicSwap(BaseApp):
txn = self.createRedeemTxn(ci_to.coin_type(), bid)
txid = ci_to.publishTx(bytes.fromhex(txn))
self.log.debug('Submitted participate redeem txn %s to %s chain for bid %s', txid, ci_to.coin_name(), bid_id.hex())
self.logEvent(Concepts.BID, bid.bid_id, EventLogTypes.PTX_REDEEM_PUBLISHED, '', None)
# TX_REDEEMED will be set when spend is detected
# TODO: Wait for depth?
@ -2861,7 +2839,6 @@ class BasicSwap(BaseApp):
# TODO: random offset into explorers, try blocks
for exp in explorers:
# TODO: ExplorerBitAps use only gettransaction if assert_txid is set
rv = exp.lookupUnspentByAddress(address)
@ -3033,22 +3010,18 @@ class BasicSwap(BaseApp):
# TODO: Timeout waiting for transactions
bid_changed = False
a_lock_tx_dest = ci_from.getScriptDest(xmr_swap.a_lock_tx_script)
# Changed from ci_from.getOutput(bid.xmr_a_lock_tx.txid, a_lock_tx_dest, bid.amount, xmr_swap)
p2wsh_addr = ci_from.encode_p2wsh(a_lock_tx_dest)
lock_tx_chain_info = ci_from.getLockTxHeight(bid.xmr_a_lock_tx.txid, p2wsh_addr, bid.amount, bid.chain_a_height_start)
if offer.coin_from == Coins.FIRO:
lock_tx_chain_info = ci_from.getLockTxHeightFiro(bid.xmr_a_lock_tx.txid, xmr_swap.a_lock_tx_script, bid.amount, bid.chain_a_height_start)
else:
a_lock_tx_addr = ci_from.getSCLockScriptAddress(xmr_swap.a_lock_tx_script)
lock_tx_chain_info = ci_from.getLockTxHeight(bid.xmr_a_lock_tx.txid, a_lock_tx_addr, bid.amount, bid.chain_a_height_start)
if lock_tx_chain_info is None:
return rv
if not bid.xmr_a_lock_tx.chain_height and lock_tx_chain_info['height'] != 0:
self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_A_SEEN, '', session)
block_header = ci_from.getBlockHeaderFromHeight(lock_tx_chain_info['height'])
bid.xmr_a_lock_tx.block_hash = bytes.fromhex(block_header['hash'])
bid.xmr_a_lock_tx.block_height = block_header['height']
bid.xmr_a_lock_tx.block_time = block_header['time'] # Or median_time?
self.setTxBlockInfoFromHeight(ci_from, bid.xmr_a_lock_tx, lock_tx_chain_info['height'])
bid_changed = True
if bid.xmr_a_lock_tx.chain_height != lock_tx_chain_info['height'] and lock_tx_chain_info['height'] != 0:
@ -3149,15 +3122,14 @@ class BasicSwap(BaseApp):
if TxTypes.XMR_SWAP_A_LOCK_REFUND in bid.txns:
refund_tx = bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND]
if refund_tx.block_time is None:
a_lock_refund_tx_dest = ci_from.getScriptDest(xmr_swap.a_lock_refund_tx_script)
p2wsh_addr = ci_from.encode_p2wsh(a_lock_refund_tx_dest)
lock_refund_tx_chain_info = ci_from.getLockTxHeight(refund_tx.txid, p2wsh_addr, 0, bid.chain_a_height_start)
if offer.coin_from == Coins.FIRO:
lock_refund_tx_chain_info = ci_from.getLockTxHeightFiro(refund_tx.txid, xmr_swap.a_lock_refund_tx_script, 0, bid.chain_a_height_start)
else:
refund_tx_addr = ci_from.getSCLockScriptAddress(xmr_swap.a_lock_refund_tx_script)
lock_refund_tx_chain_info = ci_from.getLockTxHeight(refund_tx.txid, refund_tx_addr, 0, bid.chain_a_height_start)
if lock_refund_tx_chain_info is not None and lock_refund_tx_chain_info.get('height', 0) > 0:
block_header = ci_from.getBlockHeaderFromHeight(lock_refund_tx_chain_info['height'])
refund_tx.block_hash = bytes.fromhex(block_header['hash'])
refund_tx.block_height = block_header['height']
refund_tx.block_time = block_header['time'] # Or median_time?
self.setTxBlockInfoFromHeight(ci_from, refund_tx, lock_refund_tx_chain_info['height'])
self.saveBidInSession(bid_id, bid, session, xmr_swap)
session.commit()
@ -3200,6 +3172,7 @@ class BasicSwap(BaseApp):
index = None
tx_height = None
last_initiate_txn_conf = bid.initiate_tx.conf
ci_from = self.ci(coin_from)
if coin_from == Coins.PART: # Has txindex
try:
initiate_txn = self.callcoinrpc(coin_from, 'getrawtransaction', [initiate_txnid_hex, True])
@ -3223,7 +3196,6 @@ class BasicSwap(BaseApp):
else:
addr = p2sh
ci_from = self.ci(coin_from)
found = ci_from.getLockTxHeight(bytes.fromhex(initiate_txnid_hex), addr, bid.amount, bid.chain_a_height_start, find_index=True)
if found:
bid.initiate_tx.conf = found['depth']
@ -3240,6 +3212,8 @@ class BasicSwap(BaseApp):
bid.initiate_tx.vout = index
# Start checking for spends of initiate_txn before fully confirmed
bid.initiate_tx.chain_height = self.setLastHeightChecked(coin_from, tx_height)
self.setTxBlockInfoFromHeight(ci_from, bid.initiate_tx, tx_height)
self.addWatchedOutput(coin_from, bid_id, initiate_txnid_hex, bid.initiate_tx.vout, BidStates.SWAP_INITIATED)
if bid.getITxState() is None or bid.getITxState() < TxStates.TX_SENT:
bid.setITxState(TxStates.TX_SENT)
@ -3276,6 +3250,8 @@ class BasicSwap(BaseApp):
self.addParticipateTxn(bid_id, bid, coin_to, found['txid'], found['index'], found['height'])
bid.setPTxState(TxStates.TX_SENT)
save_bid = True
if found['height'] > 0 and bid.participate_tx.block_height is None:
self.setTxBlockInfoFromHeight(ci_to, bid.participate_tx, found['height'])
if bid.participate_tx.conf is not None:
self.log.debug('participate txid %s confirms %d', bid.participate_tx.txid.hex(), bid.participate_tx.conf)
@ -3318,6 +3294,7 @@ class BasicSwap(BaseApp):
try:
txid = ci_from.publishTx(bid.initiate_txn_refund)
self.log.debug('Submitted initiate refund txn %s to %s chain for bid %s', txid, chainparams[coin_from]['name'], bid_id.hex())
self.logEvent(Concepts.BID, bid.bid_id, EventLogTypes.ITX_REFUND_PUBLISHED, '', None)
# State will update when spend is detected
except Exception as ex:
if 'non-BIP68-final' not in str(ex) and 'non-final' not in str(ex):
@ -3328,6 +3305,7 @@ class BasicSwap(BaseApp):
try:
txid = ci_to.publishTx(bid.participate_txn_refund)
self.log.debug('Submitted participate refund txn %s to %s chain for bid %s', txid, chainparams[coin_to]['name'], bid_id.hex())
self.logEvent(Concepts.BID, bid.bid_id, EventLogTypes.PTX_REFUND_PUBLISHED, '', None)
# State will update when spend is detected
except Exception as ex:
if 'non-BIP68-final' not in str(ex) and 'non-final' not in str(ex):
@ -4015,18 +3993,7 @@ class BasicSwap(BaseApp):
if swap_type == SwapTypes.SELLER_FIRST:
ensure(len(bid_data.pkhash_buyer) == 20, 'Bad pkhash_buyer length')
# Verify proof of funds
bid_proof_address = replaceAddrPrefix(bid_data.proof_address, Coins.PART, self.chain)
mm = chainparams[coin_to]['message_magic']
passed = self.ci(Coins.PART).verifyMessage(bid_proof_address, bid_data.proof_address + '_swap_proof_' + offer_id.hex(), bid_data.proof_signature, mm)
ensure(passed is True, 'Proof of funds signature invalid')
if self.coin_clients[coin_to]['use_segwit']:
addr_search = self.encodeSegwit(coin_to, decodeAddress(bid_data.proof_address)[1:])
else:
addr_search = bid_data.proof_address
sum_unspent = self.getAddressBalance(coin_to, addr_search)
sum_unspent = ci_to.verifyProofOfFunds(bid_data.proof_address, bid_data.proof_signature, offer_id)
self.log.debug('Proof of funds %s %s', bid_data.proof_address, self.ci(coin_to).format_amount(sum_unspent))
ensure(sum_unspent >= amount_to, 'Proof of funds failed')
@ -4382,7 +4349,7 @@ class BasicSwap(BaseApp):
# TODO: check_lock_tx_inputs without txindex
check_a_lock_tx_inputs = False
xmr_swap.a_lock_tx_id, xmr_swap.a_lock_tx_vout = ci_from.verifyLockTx(
xmr_swap.a_lock_tx_id, xmr_swap.a_lock_tx_vout = ci_from.verifySCLockTx(
xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script,
bid.amount,
xmr_swap.pkal, xmr_swap.pkaf,
@ -4390,14 +4357,14 @@ class BasicSwap(BaseApp):
check_a_lock_tx_inputs, xmr_swap.vkbv)
a_lock_tx_dest = ci_from.getScriptDest(xmr_swap.a_lock_tx_script)
xmr_swap.a_lock_refund_tx_id, xmr_swap.a_swap_refund_value, lock_refund_vout = ci_from.verifyLockRefundTx(
xmr_swap.a_lock_refund_tx_id, xmr_swap.a_swap_refund_value, lock_refund_vout = ci_from.verifySCLockRefundTx(
xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_tx, xmr_swap.a_lock_refund_tx_script,
xmr_swap.a_lock_tx_id, xmr_swap.a_lock_tx_vout, xmr_offer.lock_time_1, xmr_swap.a_lock_tx_script,
xmr_swap.pkal, xmr_swap.pkaf,
xmr_offer.lock_time_2,
bid.amount, xmr_offer.a_fee_rate, xmr_swap.vkbv)
ci_from.verifyLockRefundSpendTx(
ci_from.verifySCLockRefundSpendTx(
xmr_swap.a_lock_refund_spend_tx, xmr_swap.a_lock_refund_tx,
xmr_swap.a_lock_refund_tx_id, xmr_swap.a_lock_refund_tx_script,
xmr_swap.pkal,
@ -4517,7 +4484,7 @@ class BasicSwap(BaseApp):
xmr_swap.kal_sig = ci_from.signCompact(kal, 'proof key owned for swap')
# Create Script lock spend tx
xmr_swap.a_lock_spend_tx = ci_from.createScriptLockSpendTx(
xmr_swap.a_lock_spend_tx = ci_from.createSCLockSpendTx(
xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script,
xmr_swap.dest_af,
xmr_offer.a_fee_rate, xmr_swap.vkbv)
@ -4923,12 +4890,12 @@ class BasicSwap(BaseApp):
xmr_swap.a_lock_spend_tx_id = ci_from.getTxid(xmr_swap.a_lock_spend_tx)
xmr_swap.kal_sig = msg_data.kal_sig
ci_from.verifyLockSpendTx(
ci_from.verifySCLockSpendTx(
xmr_swap.a_lock_spend_tx,
xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script,
xmr_swap.dest_af, xmr_offer.a_fee_rate, xmr_swap.vkbv)
ci_from.verifyCompact(xmr_swap.pkal, 'proof key owned for swap', xmr_swap.kal_sig)
ci_from.verifyCompactSig(xmr_swap.pkal, 'proof key owned for swap', xmr_swap.kal_sig)
bid.setState(BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX)
bid.setState(BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX)
@ -5865,7 +5832,7 @@ class BasicSwap(BaseApp):
self.log.debug('Creating %s lock refund swipe tx', ci.coin_name())
pkh_dest = ci.decodeAddress(self.getReceiveAddressForCoin(ci.coin_type()))
spend_tx = ci.createScriptLockRefundSpendToFTx(
spend_tx = ci.createSCLockRefundSpendToFTx(
xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script,
pkh_dest,
xmr_offer.a_fee_rate, xmr_swap.vkbv)

@ -9,8 +9,8 @@ import struct
import hashlib
from enum import IntEnum, auto
from .util.address import (
decodeAddress,
encodeAddress,
decodeAddress,
)
from .chainparams import (
chainparams,
@ -162,6 +162,12 @@ class EventLogTypes(IntEnum):
ERROR = auto()
AUTOMATION_CONSTRAINT = auto()
AUTOMATION_ACCEPTING_BID = auto()
ITX_PUBLISHED = auto()
ITX_REDEEM_PUBLISHED = auto()
ITX_REFUND_PUBLISHED = auto()
PTX_PUBLISHED = auto()
PTX_REDEEM_PUBLISHED = auto()
PTX_REFUND_PUBLISHED = auto()
class XmrSplitMsgTypes(IntEnum):
@ -357,6 +363,18 @@ def describeEventEntry(event_type, event_msg):
return 'Failed auto accepting'
if event_type == EventLogTypes.AUTOMATION_ACCEPTING_BID:
return 'Auto accepting'
if event_type == EventLogTypes.ITX_PUBLISHED:
return 'Initiate tx published'
if event_type == EventLogTypes.ITX_REDEEM_PUBLISHED:
return 'Initiate tx redeem tx published'
if event_type == EventLogTypes.ITX_REFUND_PUBLISHED:
return 'Initiate tx refund tx published'
if event_type == EventLogTypes.PTX_PUBLISHED:
return 'Participate tx published'
if event_type == EventLogTypes.PTX_REDEEM_PUBLISHED:
return 'Participate tx redeem tx published'
if event_type == EventLogTypes.PTX_REFUND_PUBLISHED:
return 'Participate tx refund tx published'
def getVoutByAddress(txjs, p2sh):

@ -30,6 +30,7 @@ class Coins(IntEnum):
# NDAU = 10
PIVX = 11
DASH = 12
FIRO = 13
chainparams = {
@ -287,6 +288,45 @@ chainparams = {
'min_amount': 1000,
'max_amount': 100000 * COIN,
}
},
Coins.FIRO: {
'name': 'firo',
'ticker': 'FIRO',
'message_magic': 'Zcoin Signed Message:\n',
'blocks_target': 60 * 10,
'decimal_places': 8,
'has_csv': True,
'has_segwit': True,
'mainnet': {
'rpcport': 8888,
'pubkey_address': 82,
'script_address': 7,
'key_prefix': 210,
'hrp': '',
'bip44': 136,
'min_amount': 1000,
'max_amount': 100000 * COIN,
},
'testnet': {
'rpcport': 18888,
'pubkey_address': 65,
'script_address': 178,
'key_prefix': 185,
'hrp': '',
'bip44': 1,
'min_amount': 1000,
'max_amount': 100000 * COIN,
},
'regtest': {
'rpcport': 28888,
'pubkey_address': 65,
'script_address': 178,
'key_prefix': 185,
'hrp': '',
'bip44': 1,
'min_amount': 1000,
'max_amount': 100000 * COIN,
}
}
}
ticker_map = {}

@ -46,3 +46,8 @@ DASH_BINDIR = os.path.expanduser(os.getenv('DASH_BINDIR', os.path.join(DEFAULT_T
DASHD = os.getenv('DASHD', 'dashd' + bin_suffix)
DASH_CLI = os.getenv('DASH_CLI', 'dash-cli' + bin_suffix)
DASH_TX = os.getenv('DASH_TX', 'dash-tx' + bin_suffix)
FIRO_BINDIR = os.path.expanduser(os.getenv('FIRO_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'firo')))
FIROD = os.getenv('FIROD', 'firod' + bin_suffix)
FIRO_CLI = os.getenv('FIRO_CLI', 'firo-cli' + bin_suffix)
FIRO_TX = os.getenv('FIRO_TX', 'firo-tx' + bin_suffix)

@ -320,7 +320,7 @@ class BTCInterface(CoinInterface):
def decodeAddress(self, address):
bech32_prefix = self.chainparams_network()['hrp']
if address.startswith(bech32_prefix + '1'):
if len(bech32_prefix) > 0 and address.startswith(bech32_prefix + '1'):
return bytes(segwit_addr.decode(bech32_prefix, address)[1])
return decodeAddress(address)[1:]
@ -338,12 +338,22 @@ class BTCInterface(CoinInterface):
checksum = hashlib.sha256(hashlib.sha256(data).digest()).digest()
return b58encode(data + checksum[0:4])
def sh_to_address(self, sh):
assert (len(sh) == 20)
prefix = self.chainparams_network()['script_address']
data = bytes((prefix,)) + sh
checksum = hashlib.sha256(hashlib.sha256(data).digest()).digest()
return b58encode(data + checksum[0:4])
def encode_p2wsh(self, script):
bech32_prefix = self.chainparams_network()['hrp']
version = 0
program = script[2:] # strip version and length
return segwit_addr.encode(bech32_prefix, version, program)
def encodeScriptDest(self, script):
return self.encode_p2wsh(script)
def encode_p2sh(self, script):
return pubkeyToAddress(self.chainparams_network()['script_address'], script)
@ -361,6 +371,9 @@ class BTCInterface(CoinInterface):
pk = self.getPubkey(key)
return hash160(pk)
def getSeedHash(self, seed):
return self.getAddressHashFromKey(seed)[::-1]
def verifyKey(self, k):
i = b2i(k)
return (i < ep.o and i > 0)
@ -375,6 +388,12 @@ class BTCInterface(CoinInterface):
def encodePubkey(self, pk):
return pointToCPK(pk)
def encodeSegwitAddress(self, key_hash):
return segwit_addr.encode(self.chainparams_network()['hrp'], 0, key_hash)
def decodeSegwitAddress(self, addr):
return bytes(segwit_addr.decode(self.chainparams_network()['hrp'], addr)[1])
def decodePubkey(self, pke):
return CPKToPoint(pke)
@ -415,7 +434,7 @@ class BTCInterface(CoinInterface):
return CScript([2, Kal_enc, Kaf_enc, 2, CScriptOp(OP_CHECKMULTISIG)])
def createScriptLockTx(self, value, Kal, Kaf, vkbv=None):
def createSCLockTx(self, value, Kal, Kaf, vkbv=None):
script = self.genScriptLockTxScript(Kal, Kaf)
tx = CTransaction()
tx.nVersion = self.txVersion()
@ -423,7 +442,7 @@ class BTCInterface(CoinInterface):
return tx.serialize(), script
def fundScriptLockTx(self, tx_bytes, feerate, vkbv=None):
def fundSCLockTx(self, tx_bytes, feerate, vkbv=None):
return self.fundTx(tx_bytes, feerate)
def extractScriptLockRefundScriptValues(self, script_bytes):
@ -470,11 +489,11 @@ class BTCInterface(CoinInterface):
Kaf_enc, CScriptOp(OP_CHECKSIG),
CScriptOp(OP_ENDIF)])
def createScriptLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate, vkbv=None):
def createSCLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate, vkbv=None):
tx_lock = CTransaction()
tx_lock = FromHex(tx_lock, tx_lock_bytes.hex())
output_script = CScript([OP_0, hashlib.sha256(script_lock).digest()])
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
@ -485,8 +504,10 @@ class BTCInterface(CoinInterface):
refund_script = self.genScriptLockRefundTxScript(Kal, Kaf, csv_val)
tx = CTransaction()
tx.nVersion = self.txVersion()
tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n), nSequence=lock1_value))
tx.vout.append(self.txoType()(locked_coin, CScript([OP_0, hashlib.sha256(refund_script).digest()])))
tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n),
nSequence=lock1_value,
scriptSig=self.getScriptScriptSig(script_lock)))
tx.vout.append(self.txoType()(locked_coin, self.getScriptDest(refund_script)))
dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock)
witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack)
@ -495,19 +516,19 @@ class BTCInterface(CoinInterface):
tx.vout[0].nValue = locked_coin - pay_fee
tx.rehash()
self._log.info('createScriptLockRefundTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
self._log.info('createSCLockRefundTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
i2h(tx.sha256), tx_fee_rate, vsize, pay_fee)
return tx.serialize(), refund_script, tx.vout[0].nValue
def createScriptLockRefundSpendTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_refund_to, tx_fee_rate, vkbv=None):
def createSCLockRefundSpendTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_refund_to, tx_fee_rate, vkbv=None):
# Returns the coinA locked coin to the leader
# The follower will sign the multisig path with a signature encumbered by the leader's coinB spend pubkey
# If the leader publishes the decrypted signature the leader's coinB spend privatekey will be revealed to the follower
tx_lock_refund = self.loadTx(tx_lock_refund_bytes)
output_script = CScript([OP_0, hashlib.sha256(script_lock_refund).digest()])
output_script = self.getScriptDest(script_lock_refund)
locked_n = findOutput(tx_lock_refund, output_script)
ensure(locked_n is not None, 'Output not found in tx')
locked_coin = tx_lock_refund.vout[locked_n].nValue
@ -517,7 +538,9 @@ class BTCInterface(CoinInterface):
tx = CTransaction()
tx.nVersion = self.txVersion()
tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n), nSequence=0))
tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n),
nSequence=0,
scriptSig=self.getScriptScriptSig(script_lock_refund)))
tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_refund_to)))
@ -528,18 +551,18 @@ class BTCInterface(CoinInterface):
tx.vout[0].nValue = locked_coin - pay_fee
tx.rehash()
self._log.info('createScriptLockRefundSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
self._log.info('createSCLockRefundSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
i2h(tx.sha256), tx_fee_rate, vsize, pay_fee)
return tx.serialize()
def createScriptLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv=None):
def createSCLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv=None):
# lock refund swipe tx
# Sends the coinA locked coin to the follower
tx_lock_refund = self.loadTx(tx_lock_refund_bytes)
output_script = CScript([OP_0, hashlib.sha256(script_lock_refund).digest()])
output_script = self.getScriptDest(script_lock_refund)
locked_n = findOutput(tx_lock_refund, output_script)
ensure(locked_n is not None, 'Output not found in tx')
locked_coin = tx_lock_refund.vout[locked_n].nValue
@ -551,7 +574,9 @@ class BTCInterface(CoinInterface):
tx = CTransaction()
tx.nVersion = self.txVersion()
tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n), nSequence=lock2_value))
tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n),
nSequence=lock2_value,
scriptSig=self.getScriptScriptSig(script_lock_refund)))
tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest)))
@ -562,14 +587,14 @@ class BTCInterface(CoinInterface):
tx.vout[0].nValue = locked_coin - pay_fee
tx.rehash()
self._log.info('createScriptLockRefundSpendToFTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
self._log.info('createSCLockRefundSpendToFTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
i2h(tx.sha256), tx_fee_rate, vsize, pay_fee)
return tx.serialize()
def createScriptLockSpendTx(self, tx_lock_bytes, script_lock, pkh_dest, tx_fee_rate, vkbv=None):
def createSCLockSpendTx(self, tx_lock_bytes, script_lock, pkh_dest, tx_fee_rate, vkbv=None):
tx_lock = self.loadTx(tx_lock_bytes)
output_script = CScript([OP_0, hashlib.sha256(script_lock).digest()])
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
@ -579,7 +604,8 @@ class BTCInterface(CoinInterface):
tx = CTransaction()
tx.nVersion = self.txVersion()
tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n)))
tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n),
scriptSig=self.getScriptScriptSig(script_lock)))
tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest)))
@ -590,16 +616,16 @@ class BTCInterface(CoinInterface):
tx.vout[0].nValue = locked_coin - pay_fee
tx.rehash()
self._log.info('createScriptLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
self._log.info('createSCLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
i2h(tx.sha256), tx_fee_rate, vsize, pay_fee)
return tx.serialize()
def verifyLockTx(self, tx_bytes, script_out,
swap_value,
Kal, Kaf,
feerate,
check_lock_tx_inputs, vkbv=None):
def verifySCLockTx(self, tx_bytes, script_out,
swap_value,
Kal, Kaf,
feerate,
check_lock_tx_inputs, vkbv=None):
# Verify:
#
@ -614,7 +640,7 @@ class BTCInterface(CoinInterface):
ensure(tx.nVersion == self.txVersion(), 'Bad version')
ensure(tx.nLockTime == 0, 'Bad nLockTime') # TODO match txns created by cores
script_pk = CScript([OP_0, hashlib.sha256(script_out).digest()])
script_pk = self.getScriptDest(script_out)
locked_n = findOutput(tx, script_pk)
ensure(locked_n is not None, 'Output not found in tx')
locked_coin = tx.vout[locked_n].nValue
@ -663,9 +689,9 @@ class BTCInterface(CoinInterface):
return txid, locked_n
def verifyLockRefundTx(self, tx_bytes, lock_tx_bytes, script_out,
prevout_id, prevout_n, prevout_seq, prevout_script,
Kal, Kaf, csv_val_expect, swap_value, feerate, vkbv=None):
def verifySCLockRefundTx(self, tx_bytes, lock_tx_bytes, script_out,
prevout_id, prevout_n, prevout_seq, prevout_script,
Kal, Kaf, csv_val_expect, swap_value, feerate, vkbv=None):
# Verify:
# Must have only one input with correct prevout and sequence
# Must have only one output to the p2wsh of the lock refund script
@ -680,12 +706,12 @@ class BTCInterface(CoinInterface):
ensure(len(tx.vin) == 1, 'tx doesn\'t have one input')
ensure(tx.vin[0].nSequence == prevout_seq, 'Bad input nSequence')
ensure(len(tx.vin[0].scriptSig) == 0, 'Input scriptsig not empty')
ensure(tx.vin[0].scriptSig == self.getScriptScriptSig(prevout_script), 'Input scriptsig mismatch')
ensure(tx.vin[0].prevout.hash == b2i(prevout_id) and tx.vin[0].prevout.n == prevout_n, 'Input prevout mismatch')
ensure(len(tx.vout) == 1, 'tx doesn\'t have one output')
script_pk = CScript([OP_0, hashlib.sha256(script_out).digest()])
script_pk = self.getScriptDest(script_out)
locked_n = findOutput(tx, script_pk)
ensure(locked_n is not None, 'Output not found in tx')
locked_coin = tx.vout[locked_n].nValue
@ -712,10 +738,10 @@ class BTCInterface(CoinInterface):
return txid, locked_coin, locked_n
def verifyLockRefundSpendTx(self, tx_bytes, lock_refund_tx_bytes,
lock_refund_tx_id, prevout_script,
Kal,
prevout_n, prevout_value, feerate, vkbv=None):
def verifySCLockRefundSpendTx(self, tx_bytes, lock_refund_tx_bytes,
lock_refund_tx_id, prevout_script,
Kal,
prevout_n, prevout_value, feerate, vkbv=None):
# Verify:
# Must have only one input with correct prevout (n is always 0) and sequence
# Must have only one output sending lock refund tx value - fee to leader's address, TODO: follower shouldn't need to verify destination addr
@ -728,7 +754,7 @@ class BTCInterface(CoinInterface):
ensure(len(tx.vin) == 1, 'tx doesn\'t have one input')
ensure(tx.vin[0].nSequence == 0, 'Bad input nSequence')
ensure(len(tx.vin[0].scriptSig) == 0, 'Input scriptsig not empty')
ensure(tx.vin[0].scriptSig == self.getScriptScriptSig(prevout_script), 'Input scriptsig mismatch')
ensure(tx.vin[0].prevout.hash == b2i(lock_refund_tx_id) and tx.vin[0].prevout.n == 0, 'Input prevout mismatch')
ensure(len(tx.vout) == 1, 'tx doesn\'t have one output')
@ -756,9 +782,9 @@ class BTCInterface(CoinInterface):
return True
def verifyLockSpendTx(self, tx_bytes,
lock_tx_bytes, lock_tx_script,
a_pkhash_f, feerate, vkbv=None):
def verifySCLockSpendTx(self, tx_bytes,
lock_tx_bytes, lock_tx_script,
a_pkhash_f, feerate, vkbv=None):
# Verify:
# Must have only one input with correct prevout (n is always 0) and sequence
# Must have only one output with destination and amount
@ -774,13 +800,13 @@ class BTCInterface(CoinInterface):
lock_tx = self.loadTx(lock_tx_bytes)
lock_tx_id = self.getTxid(lock_tx)
output_script = CScript([OP_0, hashlib.sha256(lock_tx_script).digest()])
output_script = self.getScriptDest(lock_tx_script)
locked_n = findOutput(lock_tx, output_script)
ensure(locked_n is not None, 'Output not found in tx')
locked_coin = lock_tx.vout[locked_n].nValue
ensure(tx.vin[0].nSequence == 0, 'Bad input nSequence')
ensure(len(tx.vin[0].scriptSig) == 0, 'Input scriptsig not empty')
ensure(tx.vin[0].scriptSig == self.getScriptScriptSig(lock_tx_script), 'Input scriptsig mismatch')
ensure(tx.vin[0].prevout.hash == b2i(lock_tx_id) and tx.vin[0].prevout.n == locked_n, 'Input prevout mismatch')
ensure(len(tx.vout) == 1, 'tx doesn\'t have one output')
@ -891,7 +917,7 @@ class BTCInterface(CoinInterface):
def getTxOutputPos(self, tx, script):
if isinstance(tx, bytes):
tx = self.loadTx(tx)
script_pk = CScript([OP_0, hashlib.sha256(script).digest()])
script_pk = self.getScriptDest(script)
return findOutput(tx, script_pk)
def getPubkeyHash(self, K):
@ -900,6 +926,9 @@ class BTCInterface(CoinInterface):
def getScriptDest(self, script):
return CScript([OP_0, hashlib.sha256(script).digest()])
def getScriptScriptSig(self, script):
return bytes()
def getPkDest(self, K):
return self.getScriptForPubkeyHash(self.getPubkeyHash(K))
@ -1003,11 +1032,22 @@ class BTCInterface(CoinInterface):
def spendBLockTx(self, chain_b_lock_txid, address_to, kbv, kbs, cb_swap_value, b_fee, restore_height):
raise ValueError('TODO')
def importWatchOnlyAddress(self, address, label):
self.rpc_callback('importaddress', [address, label, False])
def isWatchOnlyAddress(self, address):
addr_info = self.rpc_callback('getaddressinfo', [address])
return addr_info['iswatchonly']
def getSCLockScriptAddress(self, lock_script):
lock_tx_dest = self.getScriptDest(lock_script)
return self.encodeScriptDest(lock_tx_dest)
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index=False):
# Add watchonly address and rescan if required
addr_info = self.rpc_callback('getaddressinfo', [dest_address])
if not addr_info['iswatchonly']:
ro = self.rpc_callback('importaddress', [dest_address, 'bid', False])
if not self.isWatchOnlyAddress(dest_address):
self.importWatchOnlyAddress(dest_address, 'bid')
self._log.info('Imported watch-only addr: {}'.format(dest_address))
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), rescan_from))
self.rpc_callback('rescanblockchain', [rescan_from])
@ -1082,7 +1122,7 @@ class BTCInterface(CoinInterface):
privkey = PrivateKey(k)
return privkey.sign_recoverable(message_hash, hasher=None)[:64]
def verifyCompact(self, K, message, sig):
def verifyCompactSig(self, K, message, sig):
message_hash = hashlib.sha256(bytes(message, 'utf-8')).digest()
pubkey = PublicKey(K)
rv = pubkey.verify_compact(sig, message_hash, hasher=None)
@ -1090,7 +1130,7 @@ class BTCInterface(CoinInterface):
def verifyMessage(self, address, message, signature, message_magic=None) -> bool:
if message_magic is None:
message_magic = self.chainparams_network()['message_magic']
message_magic = self.chainparams()['message_magic']
message_bytes = SerialiseNumCompact(len(message_magic)) + bytes(message_magic, 'utf-8') + SerialiseNumCompact(len(message)) + bytes(message, 'utf-8')
message_hash = hashlib.sha256(hashlib.sha256(message_bytes).digest()).digest()
@ -1185,9 +1225,61 @@ class BTCInterface(CoinInterface):
def getBlockWithTxns(self, block_hash):
return self.rpc_callback('getblock', [block_hash, 2])
def getUnspentsByAddr(self):
unspent_addr = dict()
unspent = self.rpc_callback('listunspent')
for u in unspent:
if u['spendable'] is not True:
continue
unspent_addr[u['address']] = unspent_addr.get(u['address'], 0) + self.make_int(u['amount'], r=1)
return unspent_addr
def getUTXOBalance(self, address):
num_blocks = self.rpc_callback('getblockcount')
sum_unspent = 0
self._log.debug('[rm] scantxoutset start') # scantxoutset is slow
ro = self.rpc_callback('scantxoutset', ['start', ['addr({})'.format(address)]]) # TODO: Use combo(address) where possible
self._log.debug('[rm] scantxoutset end')
for o in ro['unspents']:
sum_unspent += self.make_int(o['amount'])
return sum_unspent
def getProofOfFunds(self, amount_for, extra_commit_bytes):
# TODO: Lock unspent and use same output/s to fund bid
unspent_addr = self.getUnspentsByAddr()
sign_for_addr = None
for addr, value in unspent_addr.items():
if value >= amount_for:
sign_for_addr = addr
break
ensure(sign_for_addr is not None, 'Could not find address with enough funds for proof')
self._log.debug('sign_for_addr %s', sign_for_addr)
if self._use_segwit: # TODO: Use isSegwitAddress when scantxoutset can use combo
# 'Address does not refer to key' for non p2pkh
pkh = self.decodeAddress(sign_for_addr)
sign_for_addr = self.pkh_to_address(pkh)
self._log.debug('sign_for_addr converted %s', sign_for_addr)
signature = self.rpc_callback('signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + extra_commit_bytes.hex()])
return (sign_for_addr, signature)
def verifyProofOfFunds(self, address, signature, extra_commit_bytes):
passed = self.verifyMessage(address, address + '_swap_proof_' + extra_commit_bytes.hex(), signature)
ensure(passed is True, 'Proof of funds signature invalid')
if self._use_segwit:
address = self.encodeSegwitAddress(decodeAddress(address)[1:])
return self.getUTXOBalance(address)
def testBTCInterface():
print('testBTCInterface')
print('TODO: testBTCInterface')
if __name__ == "__main__":

@ -0,0 +1,175 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2022 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import hashlib
from .btc import BTCInterface, find_vout_for_address_from_txobj
from basicswap.chainparams import Coins
from basicswap.util.address import (
decodeAddress,
)
from basicswap.contrib.test_framework.script import (
CScript,
OP_0,
OP_DUP,
OP_EQUAL,
OP_HASH160,
OP_CHECKSIG,
OP_EQUALVERIFY,
hash160,
)
from basicswap.contrib.test_framework.messages import (
CTransaction,
)
class FIROInterface(BTCInterface):
@staticmethod
def coin_type():
return Coins.FIRO
def initialiseWallet(self, key):
# load with -hdseed= parameter
pass
def getNewAddress(self, use_segwit, label='swap_receive'):
return self.rpc_callback('getnewaddress', [label])
# addr_plain = self.rpc_callback('getnewaddress', [label])
# return self.rpc_callback('addwitnessaddress', [addr_plain])
def decodeAddress(self, address):
return decodeAddress(address)[1:]
def encodeSegwitAddress(self, script):
raise ValueError('TODO')
def decodeSegwitAddress(self, addr):
raise ValueError('TODO')
def isWatchOnlyAddress(self, address):
addr_info = self.rpc_callback('validateaddress', [address])
return addr_info['iswatchonly']
def getSCLockScriptAddress(self, lock_script):
lock_tx_dest = self.getScriptDest(lock_script)
address = self.encodeScriptDest(lock_tx_dest)
if not self.isWatchOnlyAddress(address):
# Expects P2WSH nested in BIP16_P2SH
ro = self.rpc_callback('importaddress', [lock_tx_dest.hex(), 'bid lock', False, True])
addr_info = self.rpc_callback('validateaddress', [address])
return address
def getLockTxHeightFiro(self, txid, lock_script, bid_amount, rescan_from, find_index=False):
# Add watchonly address and rescan if required
lock_tx_dest = self.getScriptDest(lock_script)
dest_address = self.encodeScriptDest(lock_tx_dest)
if not self.isWatchOnlyAddress(dest_address):
self.rpc_callback('importaddress', [lock_tx_dest.hex(), 'bid lock', False, True])
self._log.info('Imported watch-only addr: {}'.format(dest_address))
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), rescan_from))
self.rpc_callback('rescanblockchain', [rescan_from])
return_txid = True if txid is None else False
if txid is None:
txns = self.rpc_callback('listunspent', [0, 9999999, [dest_address, ]])
for tx in txns:
if self.make_int(tx['amount']) == bid_amount:
txid = bytes.fromhex(tx['txid'])
break
if txid is None:
return None
try:
tx = self.rpc_callback('gettransaction', [txid.hex()])
block_height = 0
if 'blockhash' in tx:
block_header = self.rpc_callback('getblockheader', [tx['blockhash']])
block_height = block_header['height']
rv = {
'depth': 0 if 'confirmations' not in tx else tx['confirmations'],
'height': block_height}
except Exception as e:
self._log.debug('getLockTxHeight gettransaction failed: %s, %s', txid.hex(), str(e))
return None
if find_index:
tx_obj = self.rpc_callback('decoderawtransaction', [tx['hex']])
rv['index'] = find_vout_for_address_from_txobj(tx_obj, dest_address)
if return_txid:
rv['txid'] = txid.hex()
return rv
def createSCLockTx(self, value, Kal, Kaf, vkbv=None):
script = self.genScriptLockTxScript(Kal, Kaf)
tx = CTransaction()
tx.nVersion = self.txVersion()
tx.vout.append(self.txoType()(value, self.getScriptDest(script)))
return tx.serialize(), script
def fundSCLockTx(self, tx_bytes, feerate, vkbv=None):
return self.fundTx(tx_bytes, feerate)
def signTxWithWallet(self, tx):
rv = self.rpc_callback('signrawtransaction', [tx.hex()])
return bytes.fromhex(rv['hex'])
def createRawSignedTransaction(self, addr_to, amount):
txn = self.rpc_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
self._log.debug(f'Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}')
options = {
'lockUnspents': True,
'feeRate': fee_rate,
}
txn_funded = self.rpc_callback('fundrawtransaction', [txn, options])['hex']
txn_signed = self.rpc_callback('signrawtransaction', [txn_funded])['hex']
return txn_signed
def getScriptForPubkeyHash(self, pkh):
# Return P2WPKH nested in BIP16 P2SH
return CScript([OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG])
def getScriptDest(self, script):
# P2WSH nested in BIP16_P2SH
script_hash = hashlib.sha256(script).digest()
assert len(script_hash) == 32
script_hash_hash = hash160(script_hash)
assert len(script_hash_hash) == 20
return CScript([OP_HASH160, script_hash_hash, OP_EQUAL])
def getSeedHash(self, seed):
return hash160(seed)[::-1]
def encodeScriptDest(self, script):
# Extract hash from script
script_hash = script[2:-1]
return self.sh_to_address(script_hash)
def getScriptScriptSig(self, script):
return CScript([OP_0, hashlib.sha256(script).digest()])
def withdrawCoin(self, value, addr_to, subfee):
params = [addr_to, value, '', '', subfee]
return self.rpc_callback('sendtoaddress', params)
def getWalletSeedID(self):
return self.rpc_callback('getwalletinfo')['hdmasterkeyid']

@ -166,7 +166,7 @@ class PARTInterfaceBlind(PARTInterface):
ensure(v['result'] is True, 'verifycommitment failed')
return output_n, blinded_info
def createScriptLockTx(self, value, Kal, Kaf, vkbv):
def createSCLockTx(self, value, Kal, Kaf, vkbv):
script = self.genScriptLockTxScript(Kal, Kaf)
# Nonce is derived from vkbv, ephemeral_key isn't used
@ -183,7 +183,7 @@ class PARTInterfaceBlind(PARTInterface):
tx_bytes = bytes.fromhex(rv['hex'])
return tx_bytes, script
def fundScriptLockTx(self, tx_bytes, feerate, vkbv):
def fundSCLockTx(self, tx_bytes, feerate, vkbv):
feerate_str = self.format_amount(feerate)
# TODO: unlock unspents if bid cancelled
@ -205,7 +205,7 @@ class PARTInterfaceBlind(PARTInterface):
rv = self.rpc_callback('fundrawtransactionfrom', ['blind', tx_hex, {}, outputs_info, options])
return bytes.fromhex(rv['hex'])
def createScriptLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate, vkbv):
def createSCLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate, vkbv):
lock_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_bytes.hex()])
assert (self.getTxid(tx_lock_bytes).hex() == lock_tx_obj['txid'])
# Nonce is derived from vkbv, ephemeral_key isn't used
@ -252,7 +252,7 @@ class PARTInterfaceBlind(PARTInterface):
return bytes.fromhex(lock_refund_tx_hex), refund_script, refunded_value
def createScriptLockRefundSpendTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_refund_to, tx_fee_rate, vkbv):
def createSCLockRefundSpendTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_refund_to, tx_fee_rate, vkbv):
# Returns the coinA locked coin to the leader
# The follower will sign the multisig path with a signature encumbered by the leader's coinB spend pubkey
# If the leader publishes the decrypted signature the leader's coinB spend privatekey will be revealed to the follower
@ -297,11 +297,11 @@ class PARTInterfaceBlind(PARTInterface):
return bytes.fromhex(lock_refund_spend_tx_hex)
def verifyLockTx(self, tx_bytes, script_out,
swap_value,
Kal, Kaf,
feerate,
check_lock_tx_inputs, vkbv):
def verifySCLockTx(self, tx_bytes, script_out,
swap_value,
Kal, Kaf,
feerate,
check_lock_tx_inputs, vkbv):
lock_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()])
lock_txid_hex = lock_tx_obj['txid']
self._log.info('Verifying lock tx: {}.'.format(lock_txid_hex))
@ -341,9 +341,9 @@ class PARTInterfaceBlind(PARTInterface):
return bytes.fromhex(lock_txid_hex), lock_output_n
def verifyLockRefundTx(self, tx_bytes, lock_tx_bytes, script_out,
prevout_id, prevout_n, prevout_seq, prevout_script,
Kal, Kaf, csv_val_expect, swap_value, feerate, vkbv):
def verifySCLockRefundTx(self, tx_bytes, lock_tx_bytes, script_out,
prevout_id, prevout_n, prevout_seq, prevout_script,
Kal, Kaf, csv_val_expect, swap_value, feerate, vkbv):
lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()])
lock_refund_txid_hex = lock_refund_tx_obj['txid']
self._log.info('Verifying lock refund tx: {}.'.format(lock_refund_txid_hex))
@ -399,10 +399,10 @@ class PARTInterfaceBlind(PARTInterface):
return bytes.fromhex(lock_refund_txid_hex), lock_refund_txo_value, lock_refund_output_n
def verifyLockRefundSpendTx(self, tx_bytes, lock_refund_tx_bytes,
lock_refund_tx_id, prevout_script,
Kal,
prevout_n, prevout_value, feerate, vkbv):
def verifySCLockRefundSpendTx(self, tx_bytes, lock_refund_tx_bytes,
lock_refund_tx_id, prevout_script,
Kal,
prevout_n, prevout_value, feerate, vkbv):
lock_refund_spend_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()])
lock_refund_spend_txid_hex = lock_refund_spend_tx_obj['txid']
self._log.info('Verifying lock refund spend tx: {}.'.format(lock_refund_spend_txid_hex))
@ -460,7 +460,7 @@ class PARTInterfaceBlind(PARTInterface):
ensure(output_n is not None, 'Output not found in tx')
return output_n
def createScriptLockSpendTx(self, tx_lock_bytes, script_lock, pk_dest, tx_fee_rate, vkbv):
def createSCLockSpendTx(self, tx_lock_bytes, script_lock, pk_dest, tx_fee_rate, vkbv):
lock_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_bytes.hex()])
lock_txid_hex = lock_tx_obj['txid']
@ -501,14 +501,14 @@ class PARTInterfaceBlind(PARTInterface):
vsize = lock_spend_tx_obj['vsize']
pay_fee = make_int(lock_spend_tx_obj['vout'][0]['ct_fee'])
actual_tx_fee_rate = pay_fee * 1000 // vsize
self._log.info('createScriptLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
self._log.info('createSCLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
lock_spend_tx_obj['txid'], actual_tx_fee_rate, vsize, pay_fee)
return bytes.fromhex(lock_spend_tx_hex)
def verifyLockSpendTx(self, tx_bytes,
lock_tx_bytes, lock_tx_script,
a_pk_f, feerate, vkbv):
def verifySCLockSpendTx(self, tx_bytes,
lock_tx_bytes, lock_tx_script,
a_pk_f, feerate, vkbv):
lock_spend_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()])
lock_spend_txid_hex = lock_spend_tx_obj['txid']
self._log.info('Verifying lock spend tx: {}.'.format(lock_spend_txid_hex))
@ -578,7 +578,7 @@ class PARTInterfaceBlind(PARTInterface):
return True
def createScriptLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv):
def createSCLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv):
# lock refund swipe tx
# Sends the coinA locked coin to the follower
lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_refund_bytes.hex()])

@ -241,6 +241,7 @@ def js_bids(self, url_split, post_string, is_json):
bid_id = bytes.fromhex(url_split[3])
assert (len(bid_id) == 28)
show_txns = False
if post_string != '':
if is_json:
post_data = json.loads(post_string)
@ -252,6 +253,9 @@ def js_bids(self, url_split, post_string, is_json):
elif have_data_entry(post_data, 'debugind'):
swap_client.setBidDebugInd(bid_id, int(get_data_entry(post_data, 'debugind')))
if have_data_entry(post_data, 'show_extra'):
show_txns = True
bid, xmr_swap, offer, xmr_offer, events = swap_client.getXmrBidAndOffer(bid_id)
assert (bid), 'Unknown bid ID'
@ -267,7 +271,6 @@ def js_bids(self, url_split, post_string, is_json):
return bytes(json.dumps(old_states), 'UTF-8')
edit_bid = False
show_txns = False
data = describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, events, edit_bid, show_txns, for_api=True)
return bytes(json.dumps(data), 'UTF-8')

@ -4,12 +4,18 @@
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
from basicswap.db import (
Concepts,
)
from basicswap.util import (
SerialiseNum,
)
from basicswap.script import (
OpCodes,
)
from basicswap.basicswap_util import (
EventLogTypes,
)
INITIATE_TX_TIMEOUT = 40 * 60 # TODO: make variable per coin
ABS_LOCK_TIME_LEEWAY = 10 * 60
@ -59,3 +65,4 @@ def redeemITx(self, bid_id, session):
bid.initiate_tx.spend_txid = bytes.fromhex(txid)
self.log.debug('Submitted initiate redeem txn %s to %s chain for bid %s', txid, ci_from.coin_name(), bid_id.hex())
self.logEvent(Concepts.BID, bid_id, EventLogTypes.ITX_REDEEM_PUBLISHED, '', session)

@ -296,7 +296,7 @@ def page_wallet(self, url_split, post_string):
if show_utxo_groups:
utxo_groups = ''
unspent_by_addr = swap_client.getUnspentsByAddr(k)
unspent_by_addr = ci.getUnspentsByAddr()
sorted_unspent_by_addr = sorted(unspent_by_addr.items(), key=lambda x: x[1], reverse=True)
for kv in sorted_unspent_by_addr:

@ -356,6 +356,16 @@ def describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, bid_events, edit_b
if 'view_tx_hex' in data:
data['view_tx_desc'] = json.dumps(ci_from.describeTx(data['view_tx_hex']), indent=4)
else:
if offer.lock_type == TxLockTypes.SEQUENCE_LOCK_TIME:
if bid.initiate_tx and bid.initiate_tx.block_time is not None:
raw_sequence = ci_from.getExpectedSequence(offer.lock_type, offer.lock_value)
seconds_locked = ci_from.decodeSequence(raw_sequence)
data['itx_refund_tx_est_final'] = bid.initiate_tx.block_time + seconds_locked
if bid.participate_tx and bid.participate_tx.block_time is not None:
raw_sequence = ci_to.getExpectedSequence(offer.lock_type, offer.lock_value // 2)
seconds_locked = ci_to.decodeSequence(raw_sequence)
data['ptx_refund_tx_est_final'] = bid.participate_tx.block_time + seconds_locked
return data

@ -53,6 +53,9 @@ PIVX_VERSION_TAG = os.getenv('PIVX_VERSION_TAG', '_scantxoutset')
DASH_VERSION = os.getenv('DASH_VERSION', '18.1.0')
DASH_VERSION_TAG = os.getenv('DASH_VERSION_TAG', '')
FIRO_VERSION = os.getenv('FIRO_VERSION', '0.14.99.1')
FIRO_VERSION_TAG = os.getenv('FIRO_VERSION_TAG', '')
known_coins = {
'particl': (PARTICL_VERSION, PARTICL_VERSION_TAG, ('tecnovert',)),
@ -62,6 +65,8 @@ known_coins = {
'monero': (MONERO_VERSION, MONERO_VERSION_TAG, ('binaryfate',)),
'pivx': (PIVX_VERSION, PIVX_VERSION_TAG, ('tecnovert',)),
'dash': (DASH_VERSION, DASH_VERSION_TAG, ('pasta',)),
# 'firo': (FIRO_VERSION, FIRO_VERSION_TAG, ('reuben',)),
'firo': (FIRO_VERSION, FIRO_VERSION_TAG, ('tecnovert',)),
}
expected_key_ids = {
@ -73,6 +78,7 @@ expected_key_ids = {
'davidburkett38': ('3620E9D387E55666',),
'fuzzbawls': ('3BDCDA2D87A881D9',),
'pasta': ('52527BEDABE87984',),
'reuben': ('1290A1D0FA7EE109',),
}
if platform.system() == 'Darwin':
@ -137,6 +143,12 @@ DASH_ONION_PORT = int(os.getenv('DASH_ONION_PORT', 9999)) # nDefaultPort
DASH_RPC_USER = os.getenv('DASH_RPC_USER', '')
DASH_RPC_PWD = os.getenv('DASH_RPC_PWD', '')
FIRO_RPC_HOST = os.getenv('FIRO_RPC_HOST', '127.0.0.1')
FIRO_RPC_PORT = int(os.getenv('FIRO_RPC_PORT', 8888))
FIRO_ONION_PORT = int(os.getenv('FIRO_ONION_PORT', 8168)) # nDefaultPort
FIRO_RPC_USER = os.getenv('FIRO_RPC_USER', '')
FIRO_RPC_PWD = os.getenv('FIRO_RPC_PWD', '')
TOR_PROXY_HOST = os.getenv('TOR_PROXY_HOST', '127.0.0.1')
TOR_PROXY_PORT = int(os.getenv('TOR_PROXY_PORT', 9050))
TOR_CONTROL_PORT = int(os.getenv('TOR_CONTROL_PORT', 9051))
@ -147,6 +159,9 @@ TEST_ONION_LINK = toBool(os.getenv('TEST_ONION_LINK', 'false'))
BITCOIN_FASTSYNC_URL = os.getenv('BITCOIN_FASTSYNC_URL', 'http://utxosets.blob.core.windows.net/public/')
BITCOIN_FASTSYNC_FILE = os.getenv('BITCOIN_FASTSYNC_FILE', 'utxo-snapshot-bitcoin-mainnet-720179.tar')
# Set to false when running individual docker containers
START_DAEMONS = toBool(os.getenv('START_DAEMONS', 'true'))
use_tor_proxy = False
default_socket = socket.socket
@ -211,6 +226,19 @@ def downloadBytes(url):
popConnectionParameters()
def importPubkeyFromUrls(gpg, pubkeyurls):
for url in pubkeyurls:
try:
logger.info('Importing public key from url: ' + url)
rv = gpg.import_keys(downloadBytes(url))
break
except Exception as e:
logging.warning('Import from url failed: %s', str(e))
for key in rv.fingerprints:
gpg.trust_keys(key, 'TRUST_FULLY')
def testTorConnection():
test_url = 'https://check.torproject.org/'
logger.info('Testing TOR connection at: ' + test_url)
@ -278,8 +306,14 @@ def extractCore(coin, version_data, settings, bin_dir, release_path, extra_opts=
logger.info('extractCore %s v%s%s', coin, version, version_tag)
extract_core_overwrite = extra_opts.get('extract_core_overwrite', True)
if coin == 'monero':
bins = ['monerod', 'monero-wallet-rpc']
if coin in ('monero', 'firo'):
if coin == 'monero':
bins = ['monerod', 'monero-wallet-rpc']
elif coin == 'firo':
bins = [coin + 'd', coin + '-cli', coin + '-tx']
else:
raise ValueError('Unknown coin')
num_exist = 0
for b in bins:
out_path = os.path.join(bin_dir, b)
@ -412,12 +446,32 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
release_url = 'https://github.com/dashpay/dash/releases/download/v{}/{}'.format(version + version_tag, release_filename)
assert_filename = '{}-{}-{}-build.assert'.format(coin, os_name, major_version)
assert_url = 'https://raw.githubusercontent.com/dashpay/gitian.sigs/master/%s-%s/%s/%s' % (version + version_tag, os_dir_name, signing_key_name, assert_filename)
elif coin == 'firo':
'''
if BIN_ARCH == 'x86_64-linux-gnu':
arch_name = 'linux64'
file_ext = 'tar.gz'
elif BIN_ARCH == 'osx64':
arch_name = 'macos'
file_ext = 'dmg'
raise ValueError('TODO: Firo - Extract .dmg')
else:
raise ValueError('Firo: Unknown architecture')
release_filename = '{}-{}-{}{}.{}'.format('firo', version + version_tag, arch_name, filename_extra, file_ext)
# release_url = 'https://github.com/firoorg/firo/releases/download/v{}/{}'.format(version + version_tag, release_filename)
# assert_url = 'https://github.com/firoorg/firo/releases/download/v%s/SHA256SUMS' % (version + version_tag)
'''
if BIN_ARCH == 'x86_64-linux-gnu':
release_filename = 'firo-0.14.99.1-x86_64-linux-gnu.tar.gz'
elif BIN_ARCH == 'osx64':
release_filename = 'firo-0.14.99.1-osx-unsigned.tar.gz'
else:
raise ValueError('Firo: Unknown architecture')
release_url = 'https://github.com/tecnovert/particl-core/releases/download/v{}/{}'.format(version + version_tag, release_filename)
assert_url = 'https://github.com/tecnovert/particl-core/releases/download/v%s/SHA256SUMS.asc' % (version + version_tag)
else:
raise ValueError('Unknown coin')
assert_sig_filename = assert_filename + '.sig'
assert_sig_url = assert_url + ('.asc' if major_version >= 22 else '.sig')
release_path = os.path.join(bin_dir, release_filename)
if not os.path.exists(release_path):
downloadFile(release_url, release_path)
@ -428,10 +482,12 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
if not os.path.exists(assert_path):
downloadFile(assert_url, assert_path)
assert_sig_filename = '{}-{}-{}-build-{}.assert.sig'.format(coin, os_name, version, signing_key_name)
assert_sig_path = os.path.join(bin_dir, assert_sig_filename)
if not os.path.exists(assert_sig_path):
downloadFile(assert_sig_url, assert_sig_path)
if coin not in ('firo', ):
assert_sig_url = assert_url + ('.asc' if major_version >= 22 else '.sig')
assert_sig_filename = '{}-{}-{}-build-{}.assert.sig'.format(coin, os_name, version, signing_key_name)
assert_sig_path = os.path.join(bin_dir, assert_sig_filename)
if not os.path.exists(assert_sig_path):
downloadFile(assert_sig_url, assert_sig_path)
hasher = hashlib.sha256()
with open(release_path, 'rb') as fp:
@ -462,17 +518,28 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
for key in rv.fingerprints:
gpg.trust_keys(rv.fingerprints[0], 'TRUST_FULLY')
if coin in ('pivx', 'firo'):
pubkey_filename = '{}_{}.pgp'.format('particl', signing_key_name)
else:
pubkey_filename = '{}_{}.pgp'.format(coin, signing_key_name)
pubkeyurls = [
'https://raw.githubusercontent.com/tecnovert/basicswap/master/pgp/keys/' + pubkey_filename,
'https://gitlab.com/particl/basicswap/-/raw/master/pgp/keys/' + pubkey_filename,
]
if coin == 'dash':
pubkeyurls.append('https://raw.githubusercontent.com/dashpay/dash/master/contrib/gitian-keys/pasta.pgp')
if coin == 'monero':
pubkeyurls.append('https://raw.githubusercontent.com/monero-project/monero/master/utils/gpg_keys/binaryfate.asc')
if coin == 'firo':
pubkeyurls.append('https://firo.org/reuben.asc')
if coin in ('monero', 'firo'):
with open(assert_path, 'rb') as fp:
verified = gpg.verify_file(fp)
if not isValidSignature(verified) and verified.username is None:
logger.warning('Signature made by unknown key.')
pubkeyurl = 'https://raw.githubusercontent.com/monero-project/monero/master/utils/gpg_keys/binaryfate.asc'
logger.info('Importing public key from url: ' + pubkeyurl)
rv = gpg.import_keys(downloadBytes(pubkeyurl))
gpg.trust_keys(rv.fingerprints[0], 'TRUST_FULLY')
importPubkeyFromUrls(gpg, pubkeyurls)
with open(assert_path, 'rb') as fp:
verified = gpg.verify_file(fp)
else:
@ -481,28 +548,7 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
if not isValidSignature(verified) and verified.username is None:
logger.warning('Signature made by unknown key.')
if coin == 'pivx':
filename = '{}_{}.pgp'.format('particl', signing_key_name)
else:
filename = '{}_{}.pgp'.format(coin, signing_key_name)
pubkeyurls = [
'https://raw.githubusercontent.com/tecnovert/basicswap/master/pgp/keys/' + filename,
'https://gitlab.com/particl/basicswap/-/raw/master/pgp/keys/' + filename,
]
if coin == 'dash':
pubkeyurls.append('https://raw.githubusercontent.com/dashpay/dash/master/contrib/gitian-keys/pasta.pgp')
for url in pubkeyurls:
try:
logger.info('Importing public key from url: ' + url)
rv = gpg.import_keys(downloadBytes(url))
break
except Exception as e:
logging.warning('Import from url failed: %s', str(e))
for key in rv.fingerprints:
gpg.trust_keys(key, 'TRUST_FULLY')
importPubkeyFromUrls(gpg, pubkeyurls)
with open(assert_sig_path, 'rb') as fp:
verified = gpg.verify_file(fp, assert_path)
@ -600,12 +646,13 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
with open(core_conf_path, 'w') as fp:
if chain != 'mainnet':
fp.write(chain + '=1\n')
if chain == 'testnet':
fp.write('[test]\n\n')
if chain == 'regtest':
fp.write('[regtest]\n\n')
else:
logger.warning('Unknown chain %s', chain)
if coin != 'firo':
if chain == 'testnet':
fp.write('[test]\n\n')
elif chain == 'regtest':
fp.write('[regtest]\n\n')
else:
logger.warning('Unknown chain %s', chain)
if COINS_RPCBIND_IP != '127.0.0.1':
fp.write('rpcallowip=127.0.0.1\n')
@ -644,11 +691,11 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
elif coin == 'namecoin':
fp.write('prune=2000\n')
elif coin == 'pivx':
base_dir = extra_opts.get('data_dir', data_dir)
params_dir = os.path.join(base_dir, 'pivx-params')
params_dir = os.path.join(data_dir, 'pivx-params')
downloadPIVXParams(params_dir)
fp.write('prune=4000\n')
fp.write(f'paramsdir={params_dir}\n')
PIVX_PARAMSDIR = os.getenv('PIVX_PARAMSDIR', params_dir)
fp.write(f'paramsdir={PIVX_PARAMSDIR}\n')
if PIVX_RPC_USER != '':
fp.write('rpcauth={}:{}${}\n'.format(PIVX_RPC_USER, salt, password_to_hmac(salt, PIVX_RPC_PWD)))
elif coin == 'dash':
@ -656,6 +703,13 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
fp.write('fallbackfee=0.0002\n')
if DASH_RPC_USER != '':
fp.write('rpcauth={}:{}${}\n'.format(DASH_RPC_USER, salt, password_to_hmac(salt, DASH_RPC_PWD)))
elif coin == 'firo':
fp.write('prune=4000\n')
fp.write('fallbackfee=0.0002\n')
fp.write('txindex=0\n')
fp.write('usehd=1\n')
if FIRO_RPC_USER != '':
fp.write('rpcauth={}:{}${}\n'.format(FIRO_RPC_USER, salt, password_to_hmac(salt, FIRO_RPC_PWD)))
else:
logger.warning('Unknown coin %s', coin)
@ -878,16 +932,20 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings,
coin_settings = settings['chainclients'][coin_name]
c = swap_client.getCoinIdFromName(coin_name)
if c == Coins.XMR:
if coin_settings['manage_wallet_daemon']:
daemons.append(startXmrWalletDaemon(coin_settings['datadir'], coin_settings['bindir'], 'monero-wallet-rpc'))
else:
if coin_settings['manage_daemon']:
filename = coin_name + 'd' + ('.exe' if os.name == 'nt' else '')
coin_args = ['-nofindpeers', '-nostaking'] if c == Coins.PART else []
if START_DAEMONS:
if c == Coins.XMR:
if coin_settings['manage_wallet_daemon']:
daemons.append(startXmrWalletDaemon(coin_settings['datadir'], coin_settings['bindir'], 'monero-wallet-rpc'))
else:
if coin_settings['manage_daemon']:
filename = coin_name + 'd' + ('.exe' if os.name == 'nt' else '')
coin_args = ['-nofindpeers', '-nostaking'] if c == Coins.PART else []
if c == Coins.FIRO:
coin_args += ['-hdseed={}'.format(swap_client.getWalletKey(Coins.FIRO, 1).hex())]
daemons.append(startDaemon(coin_settings['datadir'], coin_settings['bindir'], filename, daemon_args + coin_args))
swap_client.setDaemonPID(c, daemons[-1].pid)
daemons.append(startDaemon(coin_settings['datadir'], coin_settings['bindir'], filename, daemon_args + coin_args))
swap_client.setDaemonPID(c, daemons[-1].pid)
swap_client.setCoinRunParams(c)
swap_client.createCoinInterface(c)
@ -1173,7 +1231,7 @@ def main():
},
'dash': {
'connection_type': 'rpc' if 'dash' in with_coins else 'none',
'manage_daemon': True if ('dash' in with_coins and PIVX_RPC_HOST == '127.0.0.1') else False,
'manage_daemon': True if ('dash' in with_coins and DASH_RPC_HOST == '127.0.0.1') else False,
'rpchost': DASH_RPC_HOST,
'rpcport': DASH_RPC_PORT + port_offset,
'onionport': DASH_ONION_PORT + port_offset,
@ -1185,6 +1243,21 @@ def main():
'conf_target': 2,
'core_version_group': 18,
'chain_lookups': 'local',
},
'firo': {
'connection_type': 'rpc' if 'firo' in with_coins else 'none',
'manage_daemon': True if ('firo' in with_coins and FIRO_RPC_HOST == '127.0.0.1') else False,
'rpchost': FIRO_RPC_HOST,
'rpcport': FIRO_RPC_PORT + port_offset,
'onionport': FIRO_ONION_PORT + port_offset,
'datadir': os.getenv('FIRO_DATA_DIR', os.path.join(data_dir, 'firo')),
'bindir': os.path.join(bin_dir, 'firo'),
'use_segwit': False,
'use_csv': True,
'blocks_confirmed': 1,
'conf_target': 2,
'core_version_group': 18,
'chain_lookups': 'local',
}
}
@ -1203,6 +1276,9 @@ def main():
if DASH_RPC_USER != '':
chainclients['dash']['rpcuser'] = DASH_RPC_USER
chainclients['dash']['rpcpassword'] = DASH_RPC_PWD
if FIRO_RPC_USER != '':
chainclients['firo']['rpcuser'] = FIRO_RPC_USER
chainclients['firo']['rpcpassword'] = FIRO_RPC_PWD
chainclients['monero']['walletsdir'] = os.getenv('XMR_WALLETS_DIR', chainclients['monero']['datadir'])

@ -0,0 +1,8 @@
Examples:
curl --header "Content-Type: application/json" \
--request POST \
--data '{"show_extra":true}' \
http://localhost:12701/json/bids/00000000636ab87a5c8950b66684e86b5ed3684f175c8d05a8f0bfb6

@ -0,0 +1,16 @@
dash_core:
image: i_dash
build:
context: dash
dockerfile: Dockerfile
container_name: dash_core
volumes:
- ${DATA_PATH}/dash:/data
expose:
- ${DASH_RPC_PORT}
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
restart: unless-stopped

@ -0,0 +1,16 @@
firo_core:
image: i_firo
build:
context: firo
dockerfile: Dockerfile
container_name: firo_core
volumes:
- ${DATA_PATH}/firo:/data
expose:
- ${FIRO_RPC_PORT}
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
restart: unless-stopped

@ -0,0 +1,16 @@
pivx_core:
image: i_pivx
build:
context: pivx
dockerfile: Dockerfile
container_name: pivx_core
volumes:
- ${DATA_PATH}/pivx:/data
expose:
- ${PIVX_RPC_PORT}
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
restart: unless-stopped

@ -0,0 +1,27 @@
# https://github.com/NicolasDorier/docker-bitcoin/blob/master/README.md
FROM i_swapclient as install_stage
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=dash --withoutcoins=particl && \
find /coin_bin -name *.tar.gz -delete
FROM debian:bullseye-slim
COPY --from=install_stage /coin_bin .
ENV DASH_DATA /data
RUN groupadd -r dash && useradd -r -m -g dash dash \
&& apt-get update \
&& apt-get install -qq --no-install-recommends gosu \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir "$DASH_DATA" \
&& chown -R dash:dash "$DASH_DATA" \
&& ln -sfn "$DASH_DATA" /home/dash/.dash \
&& chown -h dash:dash /home/dash/.dash
VOLUME /data
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
EXPOSE 9999 9998
CMD ["/dash/dashd", "--datadir=/data"]

@ -0,0 +1,11 @@
#!/bin/bash
set -e
if [[ "$1" == "dash-cli" || "$1" == "dash-tx" || "$1" == "dashd" || "$1" == "test_dash" ]]; then
mkdir -p "$DASH_DATA"
chown -h dash:dash /home/dash/.dash
exec gosu dash "$@"
else
exec "$@"
fi

@ -35,3 +35,21 @@ XMR_WALLET_RPC_HOST=monero_wallet
BASE_XMR_WALLET_PORT=29998
XMR_WALLET_RPC_USER=xmr_wallet_user
XMR_WALLET_RPC_PWD=xmr_wallet_pwd
PIVX_DATA_DIR=/data/pivx
PIVX_RPC_HOST=pivx_core
PIVX_RPC_PORT=51473
PIVX_RPC_USER=pivx_user
PIVX_RPC_PWD=pivx_pwd
DASH_DATA_DIR=/data/dash
DASH_RPC_HOST=dash_core
DASH_RPC_PORT=9998
DASH_RPC_USER=dash_user
DASH_RPC_PWD=dash_pwd
FIRO_DATA_DIR=/data/firo
FIRO_RPC_HOST=firo_core
FIRO_RPC_PORT=8888
FIRO_RPC_USER=firo_user
FIRO_RPC_PWD=firo_pwd

@ -0,0 +1,25 @@
FROM i_swapclient as install_stage
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=firo --withoutcoins=particl && \
find /coin_bin -name *.tar.gz -delete
FROM debian:bullseye-slim
COPY --from=install_stage /coin_bin .
ENV FIRO_DATA /data
RUN groupadd -r firo && useradd -r -m -g firo firo \
&& apt-get update \
&& apt-get install -qq --no-install-recommends gosu \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir "$FIRO_DATA" \
&& chown -R firo:firo "$FIRO_DATA" \
&& ln -sfn "$FIRO_DATA" /home/firo/.firo \
&& chown -h firo:firo /home/firo/.firo
VOLUME /data
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
EXPOSE 8168 8888
CMD ["/firo/firod", "--datadir=/data"]

@ -0,0 +1,11 @@
#!/bin/bash
set -e
if [[ "$1" == "firo-cli" || "$1" == "firo-tx" || "$1" == "firod" || "$1" == "test_firo" ]]; then
mkdir -p "$FIRO_DATA"
chown -h firo:firo /home/firo/.firo
exec gosu firo "$@"
else
exec "$@"
fi

@ -8,6 +8,16 @@ Copy and edit .env config:
cp example.env .env
Optionally set random RPC passwords:
for KEY in $(grep -o '^.*_RPC_PWD' .env)
do
echo "Replacing: $KEY"
NEW_PWD=$(cat /dev/random | base16 | head -c 48)
sed -i "s/${KEY}=.*$/${KEY}=${NEW_PWD}/g" .env
done
Set the latest Monero chain height, or the height your wallet must restore from:
echo "DEFAULT_XMR_RESTORE_HEIGHT=$(curl https://localmonero.co/blocks/api/get_stats | jq .height)" >> .env
@ -21,6 +31,9 @@ Create docker-compose config:
cat compose-fragments/1_bitcoin.yml >> docker-compose.yml
cat compose-fragments/1_litecoin.yml >> docker-compose.yml
cat compose-fragments/1_monero-wallet.yml >> docker-compose.yml
cat compose-fragments/1_pivx.yml >> docker-compose.yml
cat compose-fragments/1_dash.yml >> docker-compose.yml
cat compose-fragments/1_firo.yml >> docker-compose.yml
# Copy for prepare script config
cp docker-compose.yml docker-compose-prepare.yml
@ -28,6 +41,7 @@ Create docker-compose config:
# Add the Monero daemon if required (should not go in docker-compose-prepare.yml)
cat compose-fragments/8_monero-daemon.yml >> docker-compose.yml
# Add the swapclient
cat compose-fragments/8_swapclient.yml >> docker-compose.yml

@ -0,0 +1,25 @@
FROM i_swapclient as install_stage
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=pivx --withoutcoins=particl && \
find /coin_bin -name *.tar.gz -delete
FROM debian:bullseye-slim
COPY --from=install_stage /coin_bin .
ENV PIVX_DATA /data
RUN groupadd -r pivx && useradd -r -m -g pivx pivx \
&& apt-get update \
&& apt-get install -qq --no-install-recommends gosu \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir "$PIVX_DATA" \
&& chown -R pivx:pivx "$PIVX_DATA" \
&& ln -sfn "$PIVX_DATA" /home/pivx/.pivx \
&& chown -h pivx:pivx /home/pivx/.pivx
VOLUME /data
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
EXPOSE 51472 51473
CMD ["/pivx/pivxd", "--datadir=/data"]

@ -0,0 +1,11 @@
#!/bin/bash
set -e
if [[ "$1" == "pivx-cli" || "$1" == "pivx-tx" || "$1" == "pivxd" || "$1" == "test_pivx" ]]; then
mkdir -p "$PIVX_DATA"
chown -h pivx:pivx /home/pivx/.pivx
exec gosu pivx "$@"
else
exec "$@"
fi

@ -0,0 +1,13 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mDMEX7UxaxYJKwYBBAHaRw8BAQdAjb4i983N4ysLmcn6RyeTwctpB2EppSc7qJ6l
yb0pezm0IXJldWJlbkBmaXJvLm9yZyA8cmV1YmVuQGZpcm8ub3JnPoiPBBAWCgAg
BQJftTFrBgsJBwgDAgQVCAoCBBYCAQACGQECGwMCHgEAIQkQEpCh0Pp+4QkWIQQB
hkVNY+g9he+R3k4SkKHQ+n7hCaMKAP9pYkzGWBNRZyvLnUVob9mV+1rOQfNM0T8p
Pmj9rIl+fgEAw8ae8Suhotv9DawP90ehFNUNUwxKz4b2zJgzz5Y7ewO4OARftTFr
EgorBgEEAZdVAQUBAQdAi86WcrR9ArfA48pJ6hFiPilSfYLd7vZJ4UgeN/I6kB4D
AQgHiHgEGBYIAAkFAl+1MWsCGwwAIQkQEpCh0Pp+4QkWIQQBhkVNY+g9he+R3k4S
kKHQ+n7hCdfMAP49okBRnhH7n4VLmfdygZUDJyfMSPG4CBC+wL2igQMqBAEAjnAf
VjbDqrZ5bf6eBbSEblyyQBtKvlARiNUu1oNsogw=
=PWmb
-----END PGP PUBLIC KEY BLOCK-----

@ -308,6 +308,10 @@ def delay_for(delay_event, delay_for=60):
delay_event.wait(delay_for)
def make_boolean(s):
return s.lower() in ['1', 'true']
def make_rpc_func(node_id, base_rpc_port=BASE_RPC_PORT):
node_id = node_id
auth = 'test{0}:test_pass{0}'.format(node_id)

@ -382,11 +382,8 @@ class Test(unittest.TestCase):
offer_id = swap_clients[0].postOffer(Coins.PART, Coins.DASH, 100 * COIN, 0.1 * COIN, 100 * COIN, SwapTypes.SELLER_FIRST)
wait_for_offer(delay_event, swap_clients[1], offer_id)
offers = swap_clients[1].listOffers()
assert (len(offers) == 1)
for offer in offers:
if offer.offer_id == offer_id:
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
offer = swap_clients[1].getOffer(offer_id)
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
wait_for_bid(delay_event, swap_clients[0], bid_id)
@ -409,10 +406,8 @@ class Test(unittest.TestCase):
offer_id = swap_clients[1].postOffer(Coins.DASH, Coins.PART, 10 * COIN, 9.0 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST)
wait_for_offer(delay_event, swap_clients[0], offer_id)
offers = swap_clients[0].listOffers()
for offer in offers:
if offer.offer_id == offer_id:
bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
offer = swap_clients[0].getOffer(offer_id)
bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
wait_for_bid(delay_event, swap_clients[1], bid_id)
swap_clients[1].acceptBid(bid_id)
@ -434,10 +429,8 @@ class Test(unittest.TestCase):
offer_id = swap_clients[0].postOffer(Coins.DASH, Coins.BTC, 10 * COIN, 0.1 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST)
wait_for_offer(delay_event, swap_clients[1], offer_id)
offers = swap_clients[1].listOffers()
for offer in offers:
if offer.offer_id == offer_id:
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
offer = swap_clients[1].getOffer(offer_id)
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
wait_for_bid(delay_event, swap_clients[0], bid_id)
swap_clients[0].acceptBid(bid_id)
@ -464,10 +457,8 @@ class Test(unittest.TestCase):
TxLockTypes.SEQUENCE_LOCK_BLOCKS, 10)
wait_for_offer(delay_event, swap_clients[1], offer_id)
offers = swap_clients[1].listOffers()
for offer in offers:
if offer.offer_id == offer_id:
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
offer = swap_clients[1].getOffer(offer_id)
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
wait_for_bid(delay_event, swap_clients[0], bid_id)
swap_clients[1].abandonBid(bid_id)
@ -490,10 +481,8 @@ class Test(unittest.TestCase):
offer_id = swap_clients[0].postOffer(Coins.DASH, Coins.BTC, 10 * COIN, 10 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST)
wait_for_offer(delay_event, swap_clients[0], offer_id)
offers = swap_clients[0].listOffers()
for offer in offers:
if offer.offer_id == offer_id:
bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
offer = swap_clients[0].getOffer(offer_id)
bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
wait_for_bid(delay_event, swap_clients[0], bid_id)
swap_clients[0].acceptBid(bid_id)
@ -514,10 +503,8 @@ class Test(unittest.TestCase):
offer_id = swap_clients[0].postOffer(Coins.DASH, Coins.BTC, 0.001 * COIN, 1.0 * COIN, 0.001 * COIN, SwapTypes.SELLER_FIRST)
wait_for_offer(delay_event, swap_clients[0], offer_id)
offers = swap_clients[0].listOffers()
for offer in offers:
if offer.offer_id == offer_id:
bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
offer = swap_clients[0].getOffer(offer_id)
bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
wait_for_bid(delay_event, swap_clients[0], bid_id)
swap_clients[0].acceptBid(bid_id)
@ -528,13 +515,13 @@ class Test(unittest.TestCase):
def test_08_withdrawal(self):
logging.info('---------- Test DASH withdrawals')
pivx_addr = dashRpc('getnewaddress \"Withdrawal test\"')
wallets0 = read_json_api(TEST_HTTP_PORT + 0, 'wallets')
assert (float(wallets0['DASH']['balance']) > 100)
addr = dashRpc('getnewaddress \"Withdrawal test\"')
wallets = read_json_api(TEST_HTTP_PORT + 0, 'wallets')
assert (float(wallets['DASH']['balance']) > 100)
post_json = {
'value': 100,
'address': pivx_addr,
'address': addr,
'subfee': False,
}
json_rv = json.loads(post_json_req('http://127.0.0.1:{}/json/wallets/dash/withdraw'.format(TEST_HTTP_PORT + 0), post_json))

@ -0,0 +1,470 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (c) 2022 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import os
import json
import random
import logging
import unittest
import basicswap.config as cfg
from basicswap.basicswap import (
Coins,
TxStates,
SwapTypes,
BidStates,
)
from basicswap.basicswap_util import (
TxLockTypes,
)
from basicswap.util import (
COIN,
make_int,
format_amount,
)
from basicswap.rpc import (
callrpc_cli,
waitForRPC,
)
from tests.basicswap.common import (
stopDaemons,
wait_for_bid,
make_rpc_func,
read_json_api,
post_json_req,
TEST_HTTP_PORT,
wait_for_offer,
wait_for_in_progress,
wait_for_bid_tx_state,
)
from basicswap.contrib.test_framework.messages import (
FromHex,
CTransaction,
)
from bin.basicswap_run import startDaemon
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
from tests.basicswap.test_xmr import BaseTest, test_delay_event, callnoderpc
logger = logging.getLogger()
FIRO_BASE_PORT = 34832
FIRO_BASE_RPC_PORT = 35832
FIRO_BASE_ZMQ_PORT = 36832
def firoCli(cmd, node_id=0):
return callrpc_cli(cfg.FIRO_BINDIR, os.path.join(cfg.TEST_DATADIRS, 'firo_' + str(node_id)), 'regtest', cmd, cfg.FIRO_CLI)
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('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('dandelion=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')
# qa/rpc-tests/segwit.py
fp.write('prematurewitness=1\n')
fp.write('walletprematurewitness=1\n')
fp.write('blockversion=4\n')
fp.write('promiscuousmempoolflags=517\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
firo_daemons = []
firo_addr = None
test_coin_from = Coins.FIRO
test_atomic = True
test_xmr = False
# Particl node mnemonics are set in test/basicswap/mnemonics.py
firo_seeds = [
'd90b7ed1be614e1c172653aee1f3b6230f43b7fa99cf07fa984a17966ad81de7',
'6c81d6d74ba33a0db9e41518c2b6789fbe938e98018a4597dac661cfc5f2dfc1',
'c5de2be44834e7e47ad7dc8e35c6b77c79f17c6bb40d5509a00fc3dff384a865',
]
@classmethod
def setUpClass(cls):
cls.start_ltc_nodes = False
cls.start_xmr_nodes = False
super(Test, cls).setUpClass()
@classmethod
def prepareExtraDataDir(cls, i):
if not cls.restore_instance:
seed_hex = cls.firo_seeds[i]
extra_opts = [f'-hdseed={seed_hex}', ]
data_dir = prepareDataDir(cfg.TEST_DATADIRS, i, 'firo.conf', 'firo_', base_p2p_port=FIRO_BASE_PORT, base_rpc_port=FIRO_BASE_RPC_PORT)
if os.path.exists(os.path.join(cfg.FIRO_BINDIR, 'firo-wallet')):
callrpc_cli(cfg.FIRO_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'firo-wallet')
cls.firo_daemons.append(startDaemon(os.path.join(cfg.TEST_DATADIRS, 'firo_' + str(i)), cfg.FIRO_BINDIR, cfg.FIROD, opts=extra_opts))
logging.info('Started %s %d', cfg.FIROD, cls.part_daemons[-1].pid)
waitForRPC(make_rpc_func(i, base_rpc_port=FIRO_BASE_RPC_PORT))
@classmethod
def addPIDInfo(cls, sc, i):
sc.setDaemonPID(Coins.FIRO, cls.firo_daemons[i].pid)
@classmethod
def prepareExtraCoins(cls):
if cls.restore_instance:
void_block_rewards_pubkey = cls.getRandomPubkey()
cls.firo_addr = cls.swap_clients[0].ci(Coins.FIRO).pubkey_to_address(void_block_rewards_pubkey)
else:
num_blocks = 400
cls.firo_addr = callnoderpc(0, 'getnewaddress', ['mining_addr'], base_rpc_port=FIRO_BASE_RPC_PORT)
# cls.firo_addr = callnoderpc(0, 'addwitnessaddress', [cls.firo_addr], base_rpc_port=FIRO_BASE_RPC_PORT)
logging.info('Mining %d Firo blocks to %s', num_blocks, cls.firo_addr)
callnoderpc(0, 'generatetoaddress', [num_blocks, cls.firo_addr], base_rpc_port=FIRO_BASE_RPC_PORT)
firo_addr1 = callnoderpc(1, 'getnewaddress', ['initial addr'], base_rpc_port=FIRO_BASE_RPC_PORT)
# firo_addr1 = callnoderpc(1, 'addwitnessaddress', [firo_addr1], base_rpc_port=FIRO_BASE_RPC_PORT)
for i in range(5):
callnoderpc(0, 'sendtoaddress', [firo_addr1, 1000], base_rpc_port=FIRO_BASE_RPC_PORT)
# Set future block rewards to nowhere (a random address), so wallet amounts stay constant
void_block_rewards_pubkey = cls.getRandomPubkey()
cls.firo_addr = cls.swap_clients[0].ci(Coins.FIRO).pubkey_to_address(void_block_rewards_pubkey)
num_blocks = 100
logging.info('Mining %d Firo blocks to %s', num_blocks, cls.firo_addr)
callnoderpc(0, 'generatetoaddress', [num_blocks, cls.firo_addr], base_rpc_port=FIRO_BASE_RPC_PORT)
@classmethod
def tearDownClass(cls):
logging.info('Finalising FIRO Test')
super(Test, cls).tearDownClass()
stopDaemons(cls.firo_daemons)
@classmethod
def addCoinSettings(cls, settings, datadir, node_id):
settings['chainclients']['firo'] = {
'connection_type': 'rpc',
'manage_daemon': False,
'rpcport': FIRO_BASE_RPC_PORT + node_id,
'rpcuser': 'test' + str(node_id),
'rpcpassword': 'test_pass' + str(node_id),
'datadir': os.path.join(datadir, 'firo_' + str(node_id)),
'bindir': cfg.FIRO_BINDIR,
'use_csv': True,
'use_segwit': False,
}
@classmethod
def coins_loop(cls):
super(Test, cls).coins_loop()
callnoderpc(0, 'generatetoaddress', [1, cls.firo_addr], base_rpc_port=FIRO_BASE_RPC_PORT)
def getBalance(self, js_wallets):
return float(js_wallets[self.test_coin_from.name]['balance']) + float(js_wallets[self.test_coin_from.name]['unconfirmed'])
def getXmrBalance(self, js_wallets):
return float(js_wallets[Coins.XMR.name]['unconfirmed']) + float(js_wallets[Coins.XMR.name]['balance'])
def callnoderpc(self, method, params=[], wallet=None, node_id=0):
return callnoderpc(node_id, method, params, wallet, base_rpc_port=FIRO_BASE_RPC_PORT)
def test_001_firo(self):
logging.info('---------- Test {} segwit'.format(self.test_coin_from.name))
'''
Segwit is not currently enabled:
https://github.com/firoorg/firo/blob/master/src/validation.cpp#L4425
Txns spending segwit utxos don't get mined.
'''
swap_clients = self.swap_clients
addr_plain = firoCli('getnewaddress \"segwit test\"')
addr_witness = firoCli(f'addwitnessaddress {addr_plain}')
addr_witness_info = firoCli(f'validateaddress {addr_witness}')
txid = firoCli(f'sendtoaddress {addr_witness} 1.0')
assert len(txid) == 64
self.callnoderpc('generatetoaddress', [1, self.firo_addr])
'''
TODO: Add back when segwit is active
ro = self.callnoderpc('scantxoutset', ['start', ['addr({})'.format(addr_witness)]])
assert (len(ro['unspents']) == 1)
assert (ro['unspents'][0]['txid'] == txid)
'''
tx_wallet = firoCli(f'gettransaction {txid}')
tx_hex = tx_wallet['hex']
tx = firoCli(f'decoderawtransaction {tx_hex}')
prevout_n = -1
for txo in tx['vout']:
if addr_witness in txo['scriptPubKey']['addresses']:
prevout_n = txo['n']
break
assert prevout_n > -1
tx_funded = firoCli(f'createrawtransaction [{{\\"txid\\":\\"{txid}\\",\\"vout\\":{prevout_n}}}] {{\\"{addr_plain}\\":0.99}}')
tx_signed = firoCli(f'signrawtransaction {tx_funded}')['hex']
# Add scriptsig for txids to match
decoded_tx = CTransaction()
decoded_tx = FromHex(decoded_tx, tx_funded)
decoded_tx.vin[0].scriptSig = bytes.fromhex('16' + addr_witness_info['hex'])
txid_with_scriptsig = decoded_tx.rehash()
tx_funded_decoded = firoCli(f'decoderawtransaction {tx_funded}')
tx_signed_decoded = firoCli(f'decoderawtransaction {tx_signed}')
assert tx_funded_decoded['txid'] != tx_signed_decoded['txid']
assert txid_with_scriptsig == tx_signed_decoded['txid']
def test_007_hdwallet(self):
logging.info('---------- Test {} hdwallet'.format(self.test_coin_from.name))
swap_client = self.swap_clients[0]
# Run initialiseWallet to set 'main_wallet_seedid_'
swap_client.initialiseWallet(self.test_coin_from)
ci = swap_client.ci(self.test_coin_from)
assert ('490ba1e2c3894d5534c467141ee3cdf77292c362' == ci.getWalletSeedID())
assert swap_client.checkWalletSeed(self.test_coin_from) is True
def test_02_part_coin(self):
logging.info('---------- Test PART to {}'.format(self.test_coin_from.name))
if not self.test_atomic:
logging.warning('Skipping test')
return
swap_clients = self.swap_clients
offer_id = swap_clients[0].postOffer(Coins.PART, self.test_coin_from, 100 * COIN, 0.1 * COIN, 100 * COIN, SwapTypes.SELLER_FIRST)
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
offer = swap_clients[1].getOffer(offer_id)
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[0], bid_id)
swap_clients[0].acceptBid(bid_id)
wait_for_in_progress(test_delay_event, swap_clients[1], bid_id, sent=True)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60)
js_0 = read_json_api(1800)
js_1 = read_json_api(1801)
assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0)
def test_03_coin_part(self):
logging.info('---------- Test {} to PART'.format(self.test_coin_from.name))
swap_clients = self.swap_clients
offer_id = swap_clients[1].postOffer(self.test_coin_from, Coins.PART, 10 * COIN, 9.0 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST)
wait_for_offer(test_delay_event, swap_clients[0], offer_id)
offer = swap_clients[0].getOffer(offer_id)
bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[1], bid_id)
swap_clients[1].acceptBid(bid_id)
wait_for_in_progress(test_delay_event, swap_clients[0], bid_id, sent=True)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
js_0 = read_json_api(1800)
js_1 = read_json_api(1801)
assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0)
def test_04_coin_btc(self):
logging.info('---------- Test {} to BTC'.format(self.test_coin_from.name))
swap_clients = self.swap_clients
offer_id = swap_clients[0].postOffer(self.test_coin_from, Coins.BTC, 10 * COIN, 0.1 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST)
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
offer = swap_clients[1].getOffer(offer_id)
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[0], bid_id)
swap_clients[0].acceptBid(bid_id)
wait_for_in_progress(test_delay_event, swap_clients[1], bid_id, sent=True)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60)
js_0bid = read_json_api(1800, 'bids/{}'.format(bid_id.hex()))
js_0 = read_json_api(1800)
js_1 = read_json_api(1801)
assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0)
def test_05_refund(self):
# Seller submits initiate txn, buyer doesn't respond
logging.info('---------- Test refund, {} to BTC'.format(self.test_coin_from.name))
swap_clients = self.swap_clients
offer_id = swap_clients[0].postOffer(self.test_coin_from, Coins.BTC, 10 * COIN, 0.1 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST,
TxLockTypes.SEQUENCE_LOCK_BLOCKS, 10)
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
offer = swap_clients[1].getOffer(offer_id)
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[0], bid_id)
swap_clients[1].abandonBid(bid_id)
swap_clients[0].acceptBid(bid_id)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.BID_ABANDONED, sent=True, wait_for=60)
js_0 = read_json_api(1800)
js_1 = read_json_api(1801)
assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0)
def test_06_self_bid(self):
logging.info('---------- Test same client, BTC to {}'.format(self.test_coin_from.name))
swap_clients = self.swap_clients
js_0_before = read_json_api(1800)
offer_id = swap_clients[0].postOffer(self.test_coin_from, Coins.BTC, 10 * COIN, 10 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST)
wait_for_offer(test_delay_event, swap_clients[0], offer_id)
offer = swap_clients[0].getOffer(offer_id)
bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[0], bid_id)
swap_clients[0].acceptBid(bid_id)
wait_for_bid_tx_state(test_delay_event, swap_clients[0], bid_id, TxStates.TX_REDEEMED, TxStates.TX_REDEEMED, wait_for=60)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
js_0 = read_json_api(1800)
assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
assert (js_0['num_recv_bids'] == js_0_before['num_recv_bids'] + 1 and js_0['num_sent_bids'] == js_0_before['num_sent_bids'] + 1)
def test_07_error(self):
logging.info('---------- Test error, BTC to {}, set fee above bid value'.format(self.test_coin_from.name))
swap_clients = self.swap_clients
js_0_before = read_json_api(1800)
offer_id = swap_clients[0].postOffer(self.test_coin_from, Coins.BTC, 0.001 * COIN, 1.0 * COIN, 0.001 * COIN, SwapTypes.SELLER_FIRST)
wait_for_offer(test_delay_event, swap_clients[0], offer_id)
offer = swap_clients[0].getOffer(offer_id)
bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[0], bid_id)
swap_clients[0].acceptBid(bid_id)
swap_clients[0].getChainClientSettings(Coins.BTC)['override_feerate'] = 10.0
swap_clients[0].getChainClientSettings(Coins.FIRO)['override_feerate'] = 10.0
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_ERROR, wait_for=60)
def test_08_withdrawal(self):
logging.info('---------- Test {} withdrawals'.format(self.test_coin_from.name))
addr = self.callnoderpc('getnewaddress', ['Withdrawal test', ])
wallets = read_json_api(TEST_HTTP_PORT + 0, 'wallets')
assert (float(wallets[self.test_coin_from.name]['balance']) > 100)
post_json = {
'value': 100,
'address': addr,
'subfee': False,
}
json_rv = json.loads(post_json_req('http://127.0.0.1:{}/json/wallets/{}/withdraw'.format(TEST_HTTP_PORT + 0, self.test_coin_from.name.lower()), post_json))
assert (len(json_rv['txid']) == 64)
def test_101_full_swap(self):
logging.info('---------- Test {} to XMR'.format(self.test_coin_from.name))
if not self.test_xmr:
logging.warning('Skipping test')
return
swap_clients = self.swap_clients
js_0 = read_json_api(1800, 'wallets')
node0_from_before = self.getBalance(js_0)
js_1 = read_json_api(1801, 'wallets')
node1_from_before = self.getBalance(js_1)
js_0_xmr = read_json_api(1800, 'wallets/xmr')
js_1_xmr = read_json_api(1801, 'wallets/xmr')
amt_swap = make_int(random.uniform(0.1, 2.0), scale=8, r=1)
rate_swap = make_int(random.uniform(0.2, 20.0), scale=12, r=1)
offer_id = swap_clients[0].postOffer(self.test_coin_from, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP)
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
offer = swap_clients[0].getOffer(offer_id)
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
swap_clients[0].acceptXmrBid(bid_id)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=180)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True)
amount_from = float(format_amount(amt_swap, 8))
js_1 = read_json_api(1801, 'wallets')
node1_from_after = self.getBalance(js_1)
assert (node1_from_after > node1_from_before + (amount_from - 0.05))
js_0 = read_json_api(1800, 'wallets')
node0_from_after = self.getBalance(js_0)
# TODO: Discard block rewards
# assert (node0_from_after < node0_from_before - amount_from)
js_0_xmr_after = read_json_api(1800, 'wallets/xmr')
js_1_xmr_after = read_json_api(1801, 'wallets/xmr')
scale_from = 8
amount_to = int((amt_swap * rate_swap) // (10 ** scale_from))
amount_to_float = float(format_amount(amount_to, 12))
node1_xmr_after = float(js_1_xmr_after['unconfirmed']) + float(js_1_xmr_after['balance'])
node1_xmr_before = float(js_1_xmr['unconfirmed']) + float(js_1_xmr['balance'])
assert (node1_xmr_after > node1_xmr_before + (amount_to_float - 0.02))
if __name__ == '__main__':
unittest.main()

@ -381,11 +381,8 @@ class Test(unittest.TestCase):
offer_id = swap_clients[0].postOffer(Coins.PART, Coins.NMC, 100 * COIN, 0.1 * COIN, 100 * COIN, SwapTypes.SELLER_FIRST, TxLockTypes.ABS_LOCK_TIME)
wait_for_offer(delay_event, swap_clients[1], offer_id)
offers = swap_clients[1].listOffers()
assert (len(offers) == 1)
for offer in offers:
if offer.offer_id == offer_id:
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
offer = swap_clients[1].getOffer(offer_id)
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
wait_for_bid(delay_event, swap_clients[0], bid_id)
@ -408,10 +405,8 @@ class Test(unittest.TestCase):
offer_id = swap_clients[1].postOffer(Coins.NMC, Coins.PART, 10 * COIN, 9.0 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST, TxLockTypes.ABS_LOCK_TIME)
wait_for_offer(delay_event, swap_clients[0], offer_id)
offers = swap_clients[0].listOffers()
for offer in offers:
if offer.offer_id == offer_id:
bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
offer = swap_clients[0].getOffer(offer_id)
bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
wait_for_bid(delay_event, swap_clients[1], bid_id)
swap_clients[1].acceptBid(bid_id)
@ -433,10 +428,8 @@ class Test(unittest.TestCase):
offer_id = swap_clients[0].postOffer(Coins.NMC, Coins.BTC, 10 * COIN, 0.1 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST, TxLockTypes.ABS_LOCK_TIME)
wait_for_offer(delay_event, swap_clients[1], offer_id)
offers = swap_clients[1].listOffers()
for offer in offers:
if offer.offer_id == offer_id:
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
offer = swap_clients[1].getOffer(offer_id)
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
wait_for_bid(delay_event, swap_clients[0], bid_id)
swap_clients[0].acceptBid(bid_id)
@ -463,10 +456,8 @@ class Test(unittest.TestCase):
TxLockTypes.ABS_LOCK_BLOCKS, 10)
wait_for_offer(delay_event, swap_clients[1], offer_id)
offers = swap_clients[1].listOffers()
for offer in offers:
if offer.offer_id == offer_id:
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
offer = swap_clients[1].getOffer(offer_id)
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
wait_for_bid(delay_event, swap_clients[0], bid_id)
swap_clients[1].abandonBid(bid_id)
@ -489,10 +480,8 @@ class Test(unittest.TestCase):
offer_id = swap_clients[0].postOffer(Coins.NMC, Coins.BTC, 10 * COIN, 10 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST, TxLockTypes.ABS_LOCK_TIME)
wait_for_offer(delay_event, swap_clients[0], offer_id)
offers = swap_clients[0].listOffers()
for offer in offers:
if offer.offer_id == offer_id:
bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
offer = swap_clients[0].getOffer(offer_id)
bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
wait_for_bid(delay_event, swap_clients[0], bid_id)
swap_clients[0].acceptBid(bid_id)
@ -513,10 +502,8 @@ class Test(unittest.TestCase):
offer_id = swap_clients[0].postOffer(Coins.NMC, Coins.BTC, 0.001 * COIN, 1.0 * COIN, 0.001 * COIN, SwapTypes.SELLER_FIRST, TxLockTypes.ABS_LOCK_TIME)
wait_for_offer(delay_event, swap_clients[0], offer_id)
offers = swap_clients[0].listOffers()
for offer in offers:
if offer.offer_id == offer_id:
bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
offer = swap_clients[0].getOffer(offer_id)
bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
wait_for_bid(delay_event, swap_clients[0], bid_id)
swap_clients[0].acceptBid(bid_id)

@ -392,11 +392,8 @@ class Test(unittest.TestCase):
offer_id = swap_clients[0].postOffer(Coins.PART, Coins.PIVX, 100 * COIN, 0.1 * COIN, 100 * COIN, SwapTypes.SELLER_FIRST, TxLockTypes.ABS_LOCK_TIME)
wait_for_offer(delay_event, swap_clients[1], offer_id)
offers = swap_clients[1].listOffers()
assert (len(offers) == 1)
for offer in offers:
if offer.offer_id == offer_id:
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
offer = swap_clients[1].getOffer(offer_id)
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
wait_for_bid(delay_event, swap_clients[0], bid_id)
@ -419,10 +416,8 @@ class Test(unittest.TestCase):
offer_id = swap_clients[1].postOffer(Coins.PIVX, Coins.PART, 10 * COIN, 9.0 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST, TxLockTypes.ABS_LOCK_TIME)
wait_for_offer(delay_event, swap_clients[0], offer_id)
offers = swap_clients[0].listOffers()
for offer in offers:
if offer.offer_id == offer_id:
bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
offer = swap_clients[0].getOffer(offer_id)
bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
wait_for_bid(delay_event, swap_clients[1], bid_id)
swap_clients[1].acceptBid(bid_id)
@ -444,10 +439,8 @@ class Test(unittest.TestCase):
offer_id = swap_clients[0].postOffer(Coins.PIVX, Coins.BTC, 10 * COIN, 0.1 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST, TxLockTypes.ABS_LOCK_TIME)
wait_for_offer(delay_event, swap_clients[1], offer_id)
offers = swap_clients[1].listOffers()
for offer in offers:
if offer.offer_id == offer_id:
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
offer = swap_clients[1].getOffer(offer_id)
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
wait_for_bid(delay_event, swap_clients[0], bid_id)
swap_clients[0].acceptBid(bid_id)
@ -474,10 +467,8 @@ class Test(unittest.TestCase):
TxLockTypes.ABS_LOCK_BLOCKS, 10)
wait_for_offer(delay_event, swap_clients[1], offer_id)
offers = swap_clients[1].listOffers()
for offer in offers:
if offer.offer_id == offer_id:
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
offer = swap_clients[1].getOffer(offer_id)
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
wait_for_bid(delay_event, swap_clients[0], bid_id)
swap_clients[1].abandonBid(bid_id)
@ -500,10 +491,8 @@ class Test(unittest.TestCase):
offer_id = swap_clients[0].postOffer(Coins.PIVX, Coins.BTC, 10 * COIN, 10 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST, TxLockTypes.ABS_LOCK_TIME)
wait_for_offer(delay_event, swap_clients[0], offer_id)
offers = swap_clients[0].listOffers()
for offer in offers:
if offer.offer_id == offer_id:
bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
offer = swap_clients[0].getOffer(offer_id)
bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
wait_for_bid(delay_event, swap_clients[0], bid_id)
swap_clients[0].acceptBid(bid_id)
@ -524,10 +513,8 @@ class Test(unittest.TestCase):
offer_id = swap_clients[0].postOffer(Coins.PIVX, Coins.BTC, 0.001 * COIN, 1.0 * COIN, 0.001 * COIN, SwapTypes.SELLER_FIRST, TxLockTypes.ABS_LOCK_TIME)
wait_for_offer(delay_event, swap_clients[0], offer_id)
offers = swap_clients[0].listOffers()
for offer in offers:
if offer.offer_id == offer_id:
bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
offer = swap_clients[0].getOffer(offer_id)
bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
wait_for_bid(delay_event, swap_clients[0], bid_id)
swap_clients[0].acceptBid(bid_id)
@ -538,13 +525,13 @@ class Test(unittest.TestCase):
def test_08_withdrawal(self):
logging.info('---------- Test PIVX withdrawals')
pivx_addr = pivxRpc('getnewaddress \"Withdrawal test\"')
wallets0 = read_json_api(TEST_HTTP_PORT + 0, 'wallets')
assert (float(wallets0['PIVX']['balance']) > 100)
addr = pivxRpc('getnewaddress \"Withdrawal test\"')
wallets = read_json_api(TEST_HTTP_PORT + 0, 'wallets')
assert (float(wallets['PIVX']['balance']) > 100)
post_json = {
'value': 100,
'address': pivx_addr,
'address': addr,
'subfee': False,
}
json_rv = json.loads(post_json_req('http://127.0.0.1:{}/json/wallets/pivx/withdraw'.format(TEST_HTTP_PORT + 0), post_json))

@ -34,6 +34,7 @@ from basicswap.rpc import (
callrpc,
)
from tests.basicswap.common import (
make_boolean,
read_json_api,
waitForServer,
BASE_RPC_PORT,
@ -46,10 +47,6 @@ from tests.basicswap.common_xmr import (
import bin.basicswap_run as runSystem
def make_boolean(s):
return s.lower() in ['1', 'true']
test_path = os.path.expanduser(os.getenv('TEST_PATH', '/tmp/test_persistent'))
RESET_TEST = make_boolean(os.getenv('RESET_TEST', 'false'))

@ -27,39 +27,230 @@ from tests.basicswap.common import (
read_json_api,
wait_for_offer,
wait_for_none_active,
BTC_BASE_RPC_PORT,
)
from .test_xmr import BaseTest, test_delay_event
from basicswap.contrib.test_framework.messages import (
ToHex,
FromHex,
CTxIn,
COutPoint,
CTransaction,
CTxInWitness,
)
from basicswap.contrib.test_framework.script import (
CScript,
OP_CHECKLOCKTIMEVERIFY,
OP_CHECKSEQUENCEVERIFY,
)
from .test_xmr import BaseTest, test_delay_event, callnoderpc
logger = logging.getLogger()
class Test(BaseTest):
__test__ = True
@classmethod
def setUpClass(cls):
if not hasattr(cls, 'test_coin_from'):
cls.test_coin_from = Coins.BTC
if not hasattr(cls, 'start_ltc_nodes'):
cls.start_ltc_nodes = False
if not hasattr(cls, 'start_pivx_nodes'):
cls.start_pivx_nodes = False
super(Test, cls).setUpClass()
@classmethod
def tearDownClass(cls):
logging.info('Finalising BTC Test')
super(Test, cls).tearDownClass()
class BasicSwapTest(BaseTest):
base_rpc_port = None
def getBalance(self, js_wallets):
return float(js_wallets[self.test_coin_from.name]['balance']) + float(js_wallets[self.test_coin_from.name]['unconfirmed'])
def getXmrBalance(self, js_wallets):
return float(js_wallets[Coins.XMR.name]['unconfirmed']) + float(js_wallets[Coins.XMR.name]['balance'])
def callnoderpc(self, method, params=[], wallet=None, node_id=0):
return callnoderpc(node_id, method, params, wallet, self.base_rpc_port)
def mineBlock(self, num_blocks=1):
self.callnoderpc('generatetoaddress', [num_blocks, self.btc_addr])
def test_001_nested_segwit(self):
logging.info('---------- Test {} p2sh nested segwit'.format(self.test_coin_from.name))
addr_p2sh_segwit = self.callnoderpc('getnewaddress', ['segwit test', 'p2sh-segwit'])
addr_info = self.callnoderpc('getaddressinfo', [addr_p2sh_segwit, ])
assert addr_info['script'] == 'witness_v0_keyhash'
txid = self.callnoderpc('sendtoaddress', [addr_p2sh_segwit, 1.0])
assert len(txid) == 64
self.mineBlock()
ro = self.callnoderpc('scantxoutset', ['start', ['addr({})'.format(addr_p2sh_segwit)]])
assert (len(ro['unspents']) == 1)
assert (ro['unspents'][0]['txid'] == txid)
tx_wallet = self.callnoderpc('gettransaction', [txid, ])['hex']
tx = self.callnoderpc('decoderawtransaction', [tx_wallet, ])
prevout_n = -1
for txo in tx['vout']:
if addr_p2sh_segwit == txo['scriptPubKey']['address']:
prevout_n = txo['n']
break
assert prevout_n > -1
tx_funded = self.callnoderpc('createrawtransaction', [[{'txid': txid, 'vout': prevout_n}], {addr_p2sh_segwit: 0.99}])
tx_signed = self.callnoderpc('signrawtransactionwithwallet', [tx_funded, ])['hex']
tx_funded_decoded = self.callnoderpc('decoderawtransaction', [tx_funded, ])
tx_signed_decoded = self.callnoderpc('decoderawtransaction', [tx_signed, ])
assert tx_funded_decoded['txid'] != tx_signed_decoded['txid']
# Add scriptsig for txids to match
addr_p2sh_segwit_info = self.callnoderpc('getaddressinfo', [addr_p2sh_segwit, ])
decoded_tx = FromHex(CTransaction(), tx_funded)
decoded_tx.vin[0].scriptSig = bytes.fromhex('16' + addr_p2sh_segwit_info['hex'])
txid_with_scriptsig = decoded_tx.rehash()
assert txid_with_scriptsig == tx_signed_decoded['txid']
def test_002_native_segwit(self):
logging.info('---------- Test {} p2sh native segwit'.format(self.test_coin_from.name))
addr_segwit = self.callnoderpc('getnewaddress', ['segwit test', 'bech32'])
addr_info = self.callnoderpc('getaddressinfo', [addr_segwit, ])
assert addr_info['iswitness'] is True
txid = self.callnoderpc('sendtoaddress', [addr_segwit, 1.0])
assert len(txid) == 64
tx_wallet = self.callnoderpc('gettransaction', [txid, ])['hex']
tx = self.callnoderpc('decoderawtransaction', [tx_wallet, ])
self.mineBlock()
ro = self.callnoderpc('scantxoutset', ['start', ['addr({})'.format(addr_segwit)]])
assert (len(ro['unspents']) == 1)
assert (ro['unspents'][0]['txid'] == txid)
prevout_n = -1
for txo in tx['vout']:
if addr_segwit == txo['scriptPubKey']['address']:
prevout_n = txo['n']
break
assert prevout_n > -1
tx_funded = self.callnoderpc('createrawtransaction', [[{'txid': txid, 'vout': prevout_n}], {addr_segwit: 0.99}])
tx_signed = self.callnoderpc('signrawtransactionwithwallet', [tx_funded, ])['hex']
tx_funded_decoded = self.callnoderpc('decoderawtransaction', [tx_funded, ])
tx_signed_decoded = self.callnoderpc('decoderawtransaction', [tx_signed, ])
assert tx_funded_decoded['txid'] == tx_signed_decoded['txid']
def test_003_cltv(self):
logging.info('---------- Test {} cltv'.format(self.test_coin_from.name))
ci = self.swap_clients[0].ci(self.test_coin_from)
chain_height = self.callnoderpc('getblockcount')
script = CScript([chain_height + 3, OP_CHECKLOCKTIMEVERIFY, ])
script_dest = ci.getScriptDest(script)
tx = CTransaction()
tx.nVersion = ci.txVersion()
tx.vout.append(ci.txoType()(ci.make_int(1.1), script_dest))
tx_hex = ToHex(tx)
tx_funded = self.callnoderpc('fundrawtransaction', [tx_hex])
utxo_pos = 0 if tx_funded['changepos'] == 1 else 1
tx_signed = self.callnoderpc('signrawtransactionwithwallet', [tx_funded['hex'], ])['hex']
txid = self.callnoderpc('sendrawtransaction', [tx_signed, ])
addr_out = self.callnoderpc('getnewaddress', ['csv test', 'bech32'])
pkh = ci.decodeSegwitAddress(addr_out)
script_out = ci.getScriptForPubkeyHash(pkh)
tx_spend = CTransaction()
tx_spend.nVersion = ci.txVersion()
tx_spend.nLockTime = chain_height + 3
tx_spend.vin.append(CTxIn(COutPoint(int(txid, 16), utxo_pos)))
tx_spend.vout.append(ci.txoType()(ci.make_int(1.0999), script_out))
tx_spend.wit.vtxinwit.append(CTxInWitness())
tx_spend.wit.vtxinwit[0].scriptWitness.stack = [script, ]
tx_spend_hex = ToHex(tx_spend)
try:
txid = self.callnoderpc('sendrawtransaction', [tx_spend_hex, ])
assert False, 'Should fail'
except Exception as e:
assert ('non-final' in str(e))
self.mineBlock(5)
txid = self.callnoderpc('sendrawtransaction', [tx_spend_hex, ])
self.mineBlock()
ro = self.callnoderpc('listreceivedbyaddress', [0, ])
sum_addr = 0
for entry in ro:
if entry['address'] == addr_out:
sum_addr += entry['amount']
assert (sum_addr == 1.0999)
def test_004_csv(self):
logging.info('---------- Test {} csv'.format(self.test_coin_from.name))
swap_clients = self.swap_clients
ci = self.swap_clients[0].ci(self.test_coin_from)
script = CScript([3, OP_CHECKSEQUENCEVERIFY, ])
script_dest = ci.getScriptDest(script)
tx = CTransaction()
tx.nVersion = ci.txVersion()
tx.vout.append(ci.txoType()(ci.make_int(1.1), script_dest))
tx_hex = ToHex(tx)
tx_funded = self.callnoderpc('fundrawtransaction', [tx_hex])
utxo_pos = 0 if tx_funded['changepos'] == 1 else 1
tx_signed = self.callnoderpc('signrawtransactionwithwallet', [tx_funded['hex'], ])['hex']
txid = self.callnoderpc('sendrawtransaction', [tx_signed, ])
addr_out = self.callnoderpc('getnewaddress', ['csv test', 'bech32'])
pkh = ci.decodeSegwitAddress(addr_out)
script_out = ci.getScriptForPubkeyHash(pkh)
tx_spend = CTransaction()
tx_spend.nVersion = ci.txVersion()
tx_spend.vin.append(CTxIn(COutPoint(int(txid, 16), utxo_pos),
nSequence=3))
tx_spend.vout.append(ci.txoType()(ci.make_int(1.0999), script_out))
tx_spend.wit.vtxinwit.append(CTxInWitness())
tx_spend.wit.vtxinwit[0].scriptWitness.stack = [script, ]
tx_spend_hex = ToHex(tx_spend)
try:
txid = self.callnoderpc('sendrawtransaction', [tx_spend_hex, ])
assert False, 'Should fail'
except Exception as e:
assert ('non-BIP68-final' in str(e))
self.mineBlock(3)
txid = self.callnoderpc('sendrawtransaction', [tx_spend_hex, ])
self.mineBlock(1)
ro = self.callnoderpc('listreceivedbyaddress', [0, ])
sum_addr = 0
for entry in ro:
if entry['address'] == addr_out:
sum_addr += entry['amount']
assert (sum_addr == 1.0999)
def test_005_watchonly(self):
logging.info('---------- Test {} watchonly'.format(self.test_coin_from.name))
addr = self.callnoderpc('getnewaddress', ['watchonly test', 'bech32'])
ro = self.callnoderpc('importaddress', [addr, '', False], node_id=1)
txid = self.callnoderpc('sendtoaddress', [addr, 1.0])
tx_hex = self.callnoderpc('getrawtransaction', [txid, ])
self.callnoderpc('sendrawtransaction', [tx_hex, ], node_id=1)
ro = self.callnoderpc('gettransaction', [txid, ], node_id=1)
assert (ro['txid'] == txid)
balances = self.callnoderpc('getbalances', node_id=1)
assert (balances['watchonly']['trusted'] + balances['watchonly']['untrusted_pending'] >= 1.0)
def test_006_getblock_verbosity(self):
logging.info('---------- Test {} getblock verbosity'.format(self.test_coin_from.name))
best_hash = self.callnoderpc('getbestblockhash')
block = self.callnoderpc('getblock', [best_hash, 2])
assert ('vin' in block['tx'][0])
def test_007_hdwallet(self):
logging.info('---------- Test {} hdwallet'.format(self.test_coin_from.name))
test_seed = '8e54a313e6df8918df6d758fafdbf127a115175fdd2238d0e908dd8093c9ac3b'
test_wif = self.swap_clients[0].ci(self.test_coin_from).encodeKey(bytes.fromhex(test_seed))
new_wallet_name = random.randbytes(10).hex()
self.callnoderpc('createwallet', [new_wallet_name])
self.callnoderpc('sethdseed', [True, test_wif], wallet=new_wallet_name)
addr = self.callnoderpc('getnewaddress', wallet=new_wallet_name)
self.callnoderpc('unloadwallet', [new_wallet_name])
assert (addr == 'bcrt1qps7hnjd866e9ynxadgseprkc2l56m00dvwargr')
def test_01_full_swap(self):
logging.info('---------- Test {} to XMR'.format(str(self.test_coin_from)))
logging.info('---------- Test {} to XMR'.format(self.test_coin_from.name))
swap_clients = self.swap_clients
js_0 = read_json_api(1800, 'wallets')
@ -108,7 +299,7 @@ class Test(BaseTest):
assert (node1_xmr_after > node1_xmr_before + (amount_to_float - 0.02))
def test_02_leader_recover_a_lock_tx(self):
logging.info('---------- Test {} to XMR leader recovers coin a lock tx'.format(str(self.test_coin_from)))
logging.info('---------- Test {} to XMR leader recovers coin a lock tx'.format(self.test_coin_from.name))
swap_clients = self.swap_clients
js_w0_before = read_json_api(1800, 'wallets')
@ -143,7 +334,7 @@ class Test(BaseTest):
# assert (node0_from_before - node0_from_after < 0.02)
def test_03_follower_recover_a_lock_tx(self):
logging.info('---------- Test {} to XMR follower recovers coin a lock tx'.format(str(self.test_coin_from)))
logging.info('---------- Test {} to XMR follower recovers coin a lock tx'.format(self.test_coin_from.name))
swap_clients = self.swap_clients
js_w0_before = read_json_api(1800, 'wallets')
@ -186,7 +377,7 @@ class Test(BaseTest):
wait_for_none_active(test_delay_event, 1801)
def test_04_follower_recover_b_lock_tx(self):
logging.info('---------- Test {} to XMR follower recovers coin b lock tx'.format(str(self.test_coin_from)))
logging.info('---------- Test {} to XMR follower recovers coin b lock tx'.format(self.test_coin_from.name))
swap_clients = self.swap_clients
@ -228,5 +419,12 @@ class Test(BaseTest):
assert (node1_xmr_before - node1_xmr_after < 0.02)
class TestBTC(BasicSwapTest):
__test__ = True
test_coin_from = Coins.BTC
start_ltc_nodes = False
base_rpc_port = BTC_BASE_RPC_PORT
if __name__ == '__main__':
unittest.main()

@ -13,237 +13,98 @@ from basicswap.basicswap import (
Coins,
SwapTypes,
BidStates,
DebugTypes,
)
from basicswap.basicswap_util import (
TxLockTypes,
)
from basicswap.util import (
make_int,
format_amount,
COIN,
)
from tests.basicswap.common import (
wait_for_bid,
read_json_api,
wait_for_offer,
wait_for_none_active,
wait_for_in_progress,
LTC_BASE_RPC_PORT,
)
'''
# Should work?
from .test_btc_xmr import Test, test_delay_event
from .test_btc_xmr import BasicSwapTest, test_delay_event
logger = logging.getLogger()
class TestLTC(Test):
class TestLTC(BasicSwapTest):
__test__ = True
@classmethod
def setUpClass(cls):
cls.test_coin_from = Coins.LTC
cls.start_ltc_nodes = True
super(TestLTC, cls).setUpClass()
@classmethod
def tearDownClass(cls):
logging.info('Finalising LTC Test')
super(TestLTC, cls).tearDownClass()
'''
from .test_xmr import BaseTest, test_delay_event
logger = logging.getLogger()
class Test(BaseTest):
__test__ = True
@classmethod
def setUpClass(cls):
cls.test_coin_from = Coins.LTC
cls.start_ltc_nodes = True
super(Test, cls).setUpClass()
@classmethod
def tearDownClass(cls):
logging.info('Finalising LTC Test')
super(Test, cls).tearDownClass()
def getBalance(self, js_wallets):
return float(js_wallets[self.test_coin_from.name]['balance']) + float(js_wallets[self.test_coin_from.name]['unconfirmed'])
def getXmrBalance(self, js_wallets):
return float(js_wallets[Coins.XMR.name]['unconfirmed']) + float(js_wallets[Coins.XMR.name]['balance'])
def test_01_full_swap(self):
logging.info('---------- Test {} to XMR'.format(str(self.test_coin_from)))
swap_clients = self.swap_clients
js_0 = read_json_api(1800, 'wallets')
node0_from_before = self.getBalance(js_0)
js_1 = read_json_api(1801, 'wallets')
node1_from_before = self.getBalance(js_1)
js_0_xmr = read_json_api(1800, 'wallets/xmr')
js_1_xmr = read_json_api(1801, 'wallets/xmr')
amt_swap = make_int(random.uniform(0.1, 2.0), scale=8, r=1)
rate_swap = make_int(random.uniform(0.2, 20.0), scale=12, r=1)
offer_id = swap_clients[0].postOffer(self.test_coin_from, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP)
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
offers = swap_clients[0].listOffers(filters={'offer_id': offer_id})
offer = offers[0]
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
swap_clients[0].acceptXmrBid(bid_id)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=180)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True)
amount_from = float(format_amount(amt_swap, 8))
js_1 = read_json_api(1801, 'wallets')
node1_from_after = self.getBalance(js_1)
assert (node1_from_after > node1_from_before + (amount_from - 0.05))
js_0 = read_json_api(1800, 'wallets')
node0_from_after = self.getBalance(js_0)
# TODO: Discard block rewards
# assert (node0_from_after < node0_from_before - amount_from)
js_0_xmr_after = read_json_api(1800, 'wallets/xmr')
js_1_xmr_after = read_json_api(1801, 'wallets/xmr')
scale_from = 8
amount_to = int((amt_swap * rate_swap) // (10 ** scale_from))
amount_to_float = float(format_amount(amount_to, 12))
node1_xmr_after = float(js_1_xmr_after['unconfirmed']) + float(js_1_xmr_after['balance'])
node1_xmr_before = float(js_1_xmr['unconfirmed']) + float(js_1_xmr['balance'])
assert (node1_xmr_after > node1_xmr_before + (amount_to_float - 0.02))
def test_02_leader_recover_a_lock_tx(self):
logging.info('---------- Test {} to XMR leader recovers coin a lock tx'.format(str(self.test_coin_from)))
test_coin_from = Coins.LTC
start_ltc_nodes = True
base_rpc_port = LTC_BASE_RPC_PORT
def mineBlock(self, num_blocks=1):
self.callnoderpc('generatetoaddress', [num_blocks, self.ltc_addr])
def test_001_nested_segwit(self):
logging.info('---------- Test {} p2sh nested segwit'.format(self.test_coin_from.name))
logging.info('Skipped')
def test_002_native_segwit(self):
logging.info('---------- Test {} p2sh native segwit'.format(self.test_coin_from.name))
addr_segwit = self.callnoderpc('getnewaddress', ['segwit test', 'bech32'])
addr_info = self.callnoderpc('getaddressinfo', [addr_segwit, ])
assert addr_info['iswitness'] is True
txid = self.callnoderpc('sendtoaddress', [addr_segwit, 1.0])
assert len(txid) == 64
tx_wallet = self.callnoderpc('gettransaction', [txid, ])['hex']
tx = self.callnoderpc('decoderawtransaction', [tx_wallet, ])
self.mineBlock()
ro = self.callnoderpc('scantxoutset', ['start', ['addr({})'.format(addr_segwit)]])
assert (len(ro['unspents']) == 1)
assert (ro['unspents'][0]['txid'] == txid)
prevout_n = -1
for txo in tx['vout']:
if addr_segwit in txo['scriptPubKey']['addresses']:
prevout_n = txo['n']
break
assert prevout_n > -1
tx_funded = self.callnoderpc('createrawtransaction', [[{'txid': txid, 'vout': prevout_n}], {addr_segwit: 0.99}])
tx_signed = self.callnoderpc('signrawtransactionwithwallet', [tx_funded, ])['hex']
tx_funded_decoded = self.callnoderpc('decoderawtransaction', [tx_funded, ])
tx_signed_decoded = self.callnoderpc('decoderawtransaction', [tx_signed, ])
assert tx_funded_decoded['txid'] == tx_signed_decoded['txid']
def test_007_hdwallet(self):
logging.info('---------- Test {} hdwallet'.format(self.test_coin_from.name))
test_seed = '8e54a313e6df8918df6d758fafdbf127a115175fdd2238d0e908dd8093c9ac3b'
test_wif = self.swap_clients[0].ci(self.test_coin_from).encodeKey(bytes.fromhex(test_seed))
new_wallet_name = random.randbytes(10).hex()
self.callnoderpc('createwallet', [new_wallet_name])
self.callnoderpc('sethdseed', [True, test_wif], wallet=new_wallet_name)
addr = self.callnoderpc('getnewaddress', wallet=new_wallet_name)
self.callnoderpc('unloadwallet', [new_wallet_name])
assert (addr == 'rltc1qps7hnjd866e9ynxadgseprkc2l56m00djr82la')
def test_20_btc_coin(self):
logging.info('---------- Test BTC to {}'.format(self.test_coin_from.name))
swap_clients = self.swap_clients
js_w0_before = read_json_api(1800, 'wallets')
node0_from_before = self.getBalance(js_w0_before)
offer_id = swap_clients[0].postOffer(Coins.BTC, self.test_coin_from, 100 * COIN, 0.1 * COIN, 100 * COIN, SwapTypes.SELLER_FIRST)
amt_swap = make_int(random.uniform(0.1, 2.0), scale=8, r=1)
rate_swap = make_int(random.uniform(0.2, 20.0), scale=12, r=1)
offer_id = swap_clients[0].postOffer(
self.test_coin_from, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP,
lock_type=TxLockTypes.SEQUENCE_LOCK_BLOCKS, lock_value=12)
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
offer = swap_clients[1].getOffer(offer_id)
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
assert (xmr_swap)
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.BID_STOP_AFTER_COIN_A_LOCK)
swap_clients[0].acceptXmrBid(bid_id)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=180)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, [BidStates.BID_STALLED_FOR_TEST, BidStates.XMR_SWAP_FAILED], sent=True)
js_w0_after = read_json_api(1800, 'wallets')
node0_from_after = self.getBalance(js_w0_after)
# TODO: Discard block rewards
# assert (node0_from_before - node0_from_after < 0.02)
def test_03_follower_recover_a_lock_tx(self):
logging.info('---------- Test {} to XMR follower recovers coin a lock tx'.format(str(self.test_coin_from)))
swap_clients = self.swap_clients
js_w0_before = read_json_api(1800, 'wallets')
js_w1_before = read_json_api(1801, 'wallets')
amt_swap = make_int(random.uniform(0.1, 2.0), scale=8, r=1)
rate_swap = make_int(random.uniform(0.2, 20.0), scale=12, r=1)
offer_id = swap_clients[0].postOffer(
self.test_coin_from, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP,
lock_type=TxLockTypes.SEQUENCE_LOCK_BLOCKS, lock_value=12)
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
offer = swap_clients[1].getOffer(offer_id)
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
assert (xmr_swap)
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.BID_STOP_AFTER_COIN_A_LOCK)
swap_clients[0].setBidDebugInd(bid_id, DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND)
swap_clients[0].acceptXmrBid(bid_id)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_STALLED_FOR_TEST, wait_for=180)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_SWIPED, wait_for=80, sent=True)
js_w1_after = read_json_api(1801, 'wallets')
node1_from_before = self.getBalance(js_w1_before)
node1_from_after = self.getBalance(js_w1_after)
amount_from = float(format_amount(amt_swap, 8))
# TODO: Discard block rewards
# assert (node1_from_after - node1_from_before > (amount_from - 0.02))
swap_clients[0].abandonBid(bid_id)
wait_for_none_active(test_delay_event, 1800)
wait_for_none_active(test_delay_event, 1801)
def test_04_follower_recover_b_lock_tx(self):
logging.info('---------- Test {} to XMR follower recovers coin b lock tx'.format(str(self.test_coin_from)))
swap_clients = self.swap_clients
js_w0_before = read_json_api(1800, 'wallets')
js_w1_before = read_json_api(1801, 'wallets')
amt_swap = make_int(random.uniform(0.1, 2.0), scale=8, r=1)
rate_swap = make_int(random.uniform(0.2, 20.0), scale=12, r=1)
offer_id = swap_clients[0].postOffer(
self.test_coin_from, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP,
lock_type=TxLockTypes.SEQUENCE_LOCK_BLOCKS, lock_value=28)
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
offer = swap_clients[1].getOffer(offer_id)
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
assert (xmr_swap)
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.CREATE_INVALID_COIN_B_LOCK)
swap_clients[0].acceptXmrBid(bid_id)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=180)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, sent=True)
js_w0_after = read_json_api(1800, 'wallets')
js_w1_after = read_json_api(1801, 'wallets')
node0_from_before = self.getBalance(js_w0_before)
node0_from_after = self.getBalance(js_w0_after)
wait_for_bid(test_delay_event, swap_clients[0], bid_id)
swap_clients[0].acceptBid(bid_id)
# TODO: Discard block rewards
# assert (node0_from_before - node0_from_after < 0.02)
wait_for_in_progress(test_delay_event, swap_clients[1], bid_id, sent=True)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60)
node1_xmr_before = self.getXmrBalance(js_w1_before)
node1_xmr_after = self.getXmrBalance(js_w1_after)
assert (node1_xmr_before - node1_xmr_after < 0.02)
js_0 = read_json_api(1800)
js_1 = read_json_api(1801)
assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0)
if __name__ == '__main__':

@ -212,7 +212,7 @@ class Test(unittest.TestCase):
pk = ci.getPubkey(vk)
sig = ci.signCompact(vk, 'test signing message')
assert (len(sig) == 64)
ci.verifyCompact(pk, 'test signing message', sig)
ci.verifyCompactSig(pk, 'test signing message', sig)
def test_pubkey_to_address(self):
coin_settings = {'rpcport': 0, 'rpcauth': 'none'}

@ -154,11 +154,8 @@ class Test(BaseTest):
offer_id = swap_clients[0].postOffer(Coins.PART, Coins.LTC, 100 * COIN, 0.1 * COIN, 100 * COIN, SwapTypes.SELLER_FIRST)
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
offers = swap_clients[1].listOffers()
assert (len(offers) == 1)
for offer in offers:
if offer.offer_id == offer_id:
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
offer = swap_clients[1].getOffer(offer_id)
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[0], bid_id)
@ -275,7 +272,6 @@ class Test(BaseTest):
wait_for_offer(test_delay_event, swap_clients[0], offer_id)
offer = swap_clients[0].getOffer(offer_id)
offers = swap_clients[0].listOffers()
bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[0], bid_id)
@ -326,11 +322,8 @@ class Test(BaseTest):
offer_id = swap_clients[0].postOffer(Coins.PART, Coins.LTC, 100 * COIN, 0.1 * COIN, 100 * COIN, SwapTypes.SELLER_FIRST, auto_accept_bids=True)
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
offers = swap_clients[1].listOffers()
assert (len(offers) >= 1)
for offer in offers:
if offer.offer_id == offer_id:
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
offer = swap_clients[1].getOffer(offer_id)
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60)

@ -59,6 +59,7 @@ from basicswap.http_server import (
)
from tests.basicswap.common import (
prepareDataDir,
make_boolean,
make_rpc_func,
checkForks,
stopDaemons,
@ -80,8 +81,6 @@ from tests.basicswap.common import (
BTC_BASE_RPC_PORT,
LTC_BASE_PORT,
LTC_BASE_RPC_PORT,
PIVX_BASE_PORT,
PIVX_BASE_RPC_PORT,
PREFIX_SECRET_KEY_REGTEST,
)
from bin.basicswap_run import startDaemon, startXmrDaemon
@ -93,7 +92,6 @@ NUM_NODES = 3
NUM_XMR_NODES = 3
NUM_BTC_NODES = 3
NUM_LTC_NODES = 3
NUM_PIVX_NODES = 3
TEST_DIR = cfg.TEST_DATADIRS
XMR_BASE_P2P_PORT = 17792
@ -102,6 +100,7 @@ XMR_BASE_ZMQ_PORT = 22792
XMR_BASE_WALLET_RPC_PORT = 23792
test_delay_event = threading.Event()
RESET_TEST = make_boolean(os.getenv('RESET_TEST', 'true'))
def prepareXmrDataDir(datadir, node_id, conf_file):
@ -153,7 +152,7 @@ def startXmrWalletRPC(node_dir, bin_dir, wallet_bin, node_id, opts=[]):
return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=wallet_stdout, stderr=wallet_stderr, cwd=data_dir)
def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey, with_coins=set()):
def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey, with_coins=set(), cls=None):
basicswap_dir = os.path.join(datadir, 'basicswap_' + str(node_id))
if not os.path.exists(basicswap_dir):
os.makedirs(basicswap_dir)
@ -229,17 +228,8 @@ def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey, with_c
'use_segwit': True,
}
if Coins.PIVX in with_coins:
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': cfg.PIVX_BINDIR,
'use_segwit': False,
}
if cls:
cls.addCoinSettings(settings, datadir, node_id)
with open(settings_path, 'w') as fp:
json.dump(settings, fp, indent=4)
@ -253,10 +243,6 @@ def ltcCli(cmd, node_id=0):
return callrpc_cli(cfg.LITECOIN_BINDIR, os.path.join(TEST_DIR, 'ltc_' + str(node_id)), 'regtest', cmd, cfg.LITECOIN_CLI)
def pivxCli(cmd, node_id=0):
return callrpc_cli(cfg.PIVX_BINDIR, os.path.join(TEST_DIR, 'pivx_' + str(node_id)), 'regtest', cmd, cfg.PIVX_CLI)
def signal_handler(sig, frame):
logging.info('signal {} detected.'.format(sig))
test_delay_event.set()
@ -298,14 +284,7 @@ def run_coins_loop(cls):
while not test_delay_event.is_set():
pause_event.wait()
try:
if cls.btc_addr is not None:
btcCli('generatetoaddress 1 {}'.format(cls.btc_addr))
if cls.ltc_addr is not None:
ltcCli('generatetoaddress 1 {}'.format(cls.ltc_addr))
if cls.pivx_addr is not None:
pivxCli('generatetoaddress 1 {}'.format(cls.pivx_addr))
if cls.xmr_addr is not None:
callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': 1})
cls.coins_loop()
except Exception as e:
logging.warning('run_coins_loop ' + str(e))
test_delay_event.wait(1.0)
@ -320,34 +299,34 @@ def run_loop(cls):
class BaseTest(unittest.TestCase):
__test__ = False
update_thread = None
coins_update_thread = None
http_threads = []
swap_clients = []
part_daemons = []
btc_daemons = []
ltc_daemons = []
xmr_daemons = []
xmr_wallet_auth = []
restore_instance = False
start_ltc_nodes = False
start_xmr_nodes = True
xmr_addr = None
btc_addr = None
ltc_addr = None
@classmethod
def setUpClass(cls):
if not hasattr(cls, 'start_ltc_nodes'):
cls.start_ltc_nodes = False
if not hasattr(cls, 'start_pivx_nodes'):
cls.start_pivx_nodes = False
if not hasattr(cls, 'start_xmr_nodes'):
cls.start_xmr_nodes = True
def getRandomPubkey(cls):
eckey = ECKey()
eckey.generate()
return eckey.get_pubkey().get_bytes()
@classmethod
def setUpClass(cls):
random.seed(time.time())
cls.update_thread = None
cls.coins_update_thread = None
cls.http_threads = []
cls.swap_clients = []
cls.part_daemons = []
cls.btc_daemons = []
cls.ltc_daemons = []
cls.pivx_daemons = []
cls.xmr_daemons = []
cls.xmr_wallet_auth = []
cls.xmr_addr = None
cls.btc_addr = None
cls.ltc_addr = None
cls.pivx_addr = None
logger.propagate = False
logger.handlers = []
logger.setLevel(logging.INFO) # DEBUG shows many messages from requests.post
@ -356,16 +335,24 @@ class BaseTest(unittest.TestCase):
stream_stdout.setFormatter(formatter)
logger.addHandler(stream_stdout)
diagrams_dir = 'doc/protocols/sequence_diagrams'
cls.states_bidder = extract_states_from_xu_file(os.path.join(diagrams_dir, 'xmr.bidder.alt.xu'), 'B')
cls.states_offerer = extract_states_from_xu_file(os.path.join(diagrams_dir, 'xmr.offerer.alt.xu'), 'O')
if os.path.isdir(TEST_DIR):
logging.info('Removing ' + TEST_DIR)
for name in os.listdir(TEST_DIR):
if name == 'pivx-params':
continue
fullpath = os.path.join(TEST_DIR, name)
if os.path.isdir(fullpath):
shutil.rmtree(fullpath)
else:
os.remove(fullpath)
if RESET_TEST:
logging.info('Removing ' + TEST_DIR)
for name in os.listdir(TEST_DIR):
if name == 'pivx-params':
continue
fullpath = os.path.join(TEST_DIR, name)
if os.path.isdir(fullpath):
shutil.rmtree(fullpath)
else:
os.remove(fullpath)
else:
logging.info('Restoring instance from ' + TEST_DIR)
cls.restore_instance = True
if not os.path.exists(TEST_DIR):
os.makedirs(TEST_DIR)
@ -373,39 +360,38 @@ class BaseTest(unittest.TestCase):
cls.stream_fp.setFormatter(formatter)
logger.addHandler(cls.stream_fp)
diagrams_dir = 'doc/protocols/sequence_diagrams'
cls.states_bidder = extract_states_from_xu_file(os.path.join(diagrams_dir, 'xmr.bidder.alt.xu'), 'B')
cls.states_offerer = extract_states_from_xu_file(os.path.join(diagrams_dir, 'xmr.offerer.alt.xu'), 'O')
try:
logging.info('Preparing coin nodes.')
for i in range(NUM_NODES):
data_dir = prepareDataDir(TEST_DIR, i, 'particl.conf', 'part_')
if os.path.exists(os.path.join(cfg.PARTICL_BINDIR, 'particl-wallet')):
callrpc_cli(cfg.PARTICL_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'particl-wallet')
if not cls.restore_instance:
data_dir = prepareDataDir(TEST_DIR, i, 'particl.conf', 'part_')
if os.path.exists(os.path.join(cfg.PARTICL_BINDIR, 'particl-wallet')):
callrpc_cli(cfg.PARTICL_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'particl-wallet')
cls.part_daemons.append(startDaemon(os.path.join(TEST_DIR, 'part_' + str(i)), cfg.PARTICL_BINDIR, cfg.PARTICLD))
logging.info('Started %s %d', cfg.PARTICLD, cls.part_daemons[-1].pid)
for i in range(NUM_NODES):
# Load mnemonics after all nodes have started to avoid staking getting stuck in TryToSync
rpc = make_rpc_func(i)
waitForRPC(rpc)
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']])
# Lower output split threshold for more stakeable outputs
rpc('walletsettings', ['stakingoptions', {'stakecombinethreshold': 100, 'stakesplitthreshold': 200}])
if not cls.restore_instance:
for i in range(NUM_NODES):
# Load mnemonics after all nodes have started to avoid staking getting stuck in TryToSync
rpc = make_rpc_func(i)
waitForRPC(rpc)
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']])
# Lower output split threshold for more stakeable outputs
rpc('walletsettings', ['stakingoptions', {'stakecombinethreshold': 100, 'stakesplitthreshold': 200}])
for i in range(NUM_BTC_NODES):
data_dir = prepareDataDir(TEST_DIR, i, 'bitcoin.conf', 'btc_', base_p2p_port=BTC_BASE_PORT, base_rpc_port=BTC_BASE_RPC_PORT)
if os.path.exists(os.path.join(cfg.BITCOIN_BINDIR, 'bitcoin-wallet')):
callrpc_cli(cfg.BITCOIN_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'bitcoin-wallet')
if not cls.restore_instance:
data_dir = prepareDataDir(TEST_DIR, i, 'bitcoin.conf', 'btc_', base_p2p_port=BTC_BASE_PORT, base_rpc_port=BTC_BASE_RPC_PORT)
if os.path.exists(os.path.join(cfg.BITCOIN_BINDIR, 'bitcoin-wallet')):
callrpc_cli(cfg.BITCOIN_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'bitcoin-wallet')
cls.btc_daemons.append(startDaemon(os.path.join(TEST_DIR, 'btc_' + str(i)), cfg.BITCOIN_BINDIR, cfg.BITCOIND))
logging.info('Started %s %d', cfg.BITCOIND, cls.part_daemons[-1].pid)
@ -414,29 +400,20 @@ class BaseTest(unittest.TestCase):
if cls.start_ltc_nodes:
for i in range(NUM_LTC_NODES):
data_dir = prepareDataDir(TEST_DIR, i, 'litecoin.conf', 'ltc_', base_p2p_port=LTC_BASE_PORT, base_rpc_port=LTC_BASE_RPC_PORT)
if os.path.exists(os.path.join(cfg.LITECOIN_BINDIR, 'litecoin-wallet')):
callrpc_cli(cfg.LITECOIN_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'litecoin-wallet')
if not cls.restore_instance:
data_dir = prepareDataDir(TEST_DIR, i, 'litecoin.conf', 'ltc_', base_p2p_port=LTC_BASE_PORT, base_rpc_port=LTC_BASE_RPC_PORT)
if os.path.exists(os.path.join(cfg.LITECOIN_BINDIR, 'litecoin-wallet')):
callrpc_cli(cfg.LITECOIN_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'litecoin-wallet')
cls.ltc_daemons.append(startDaemon(os.path.join(TEST_DIR, 'ltc_' + str(i)), cfg.LITECOIN_BINDIR, cfg.LITECOIND))
logging.info('Started %s %d', cfg.LITECOIND, cls.part_daemons[-1].pid)
waitForRPC(make_rpc_func(i, base_rpc_port=LTC_BASE_RPC_PORT))
if cls.start_pivx_nodes:
for i in range(NUM_PIVX_NODES):
data_dir = prepareDataDir(TEST_DIR, i, 'pivx.conf', 'pivx_', base_p2p_port=PIVX_BASE_PORT, base_rpc_port=PIVX_BASE_RPC_PORT)
if os.path.exists(os.path.join(cfg.PIVX_BINDIR, 'pivx-wallet')):
callrpc_cli(cfg.PIVX_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'pivx-wallet')
cls.pivx_daemons.append(startDaemon(os.path.join(TEST_DIR, 'pivx_' + str(i)), cfg.PIVX_BINDIR, cfg.PIVXD))
logging.info('Started %s %d', cfg.PIVXD, cls.part_daemons[-1].pid)
waitForRPC(make_rpc_func(i, base_rpc_port=PIVX_BASE_RPC_PORT))
if cls.start_xmr_nodes:
for i in range(NUM_XMR_NODES):
prepareXmrDataDir(TEST_DIR, i, 'monerod.conf')
if not cls.restore_instance:
prepareXmrDataDir(TEST_DIR, i, 'monerod.conf')
cls.xmr_daemons.append(startXmrDaemon(os.path.join(TEST_DIR, 'xmr_' + str(i)), cfg.XMR_BINDIR, cfg.XMRD))
logging.info('Started %s %d', cfg.XMRD, cls.xmr_daemons[-1].pid)
@ -450,14 +427,20 @@ class BaseTest(unittest.TestCase):
waitForXMRWallet(i, cls.xmr_wallet_auth[i])
cls.callxmrnodewallet(cls, i, 'create_wallet', {'filename': 'testwallet', 'language': 'English'})
if not cls.restore_instance:
cls.callxmrnodewallet(cls, i, 'create_wallet', {'filename': 'testwallet', 'language': 'English'})
cls.callxmrnodewallet(cls, i, 'open_wallet', {'filename': 'testwallet'})
for i in range(NUM_NODES):
# Hook for descendant classes
cls.prepareExtraDataDir(i)
logging.info('Preparing swap clients.')
eckey = ECKey()
eckey.generate()
cls.network_key = toWIF(PREFIX_SECRET_KEY_REGTEST, eckey.get_bytes())
cls.network_pubkey = eckey.get_pubkey().get_bytes().hex()
if not cls.restore_instance:
eckey = ECKey()
eckey.generate()
cls.network_key = toWIF(PREFIX_SECRET_KEY_REGTEST, eckey.get_bytes())
cls.network_pubkey = eckey.get_pubkey().get_bytes().hex()
for i in range(NUM_NODES):
start_nodes = set()
@ -465,13 +448,15 @@ class BaseTest(unittest.TestCase):
start_nodes.add(Coins.LTC)
if cls.start_xmr_nodes:
start_nodes.add(Coins.XMR)
if cls.start_pivx_nodes:
start_nodes.add(Coins.PIVX)
prepare_swapclient_dir(TEST_DIR, i, cls.network_key, cls.network_pubkey, start_nodes)
if not cls.restore_instance:
prepare_swapclient_dir(TEST_DIR, i, cls.network_key, cls.network_pubkey, start_nodes, cls)
basicswap_dir = os.path.join(os.path.join(TEST_DIR, 'basicswap_' + str(i)))
settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME)
with open(settings_path) as fs:
settings = json.load(fs)
if cls.restore_instance and i == 1:
cls.network_key = settings['network_key']
cls.network_pubkey = settings['network_pubkey']
fp = open(os.path.join(basicswap_dir, 'basicswap.log'), 'w')
sc = BasicSwap(fp, basicswap_dir, settings, 'regtest', log_name='BasicSwap{}'.format(i))
sc.setDaemonPID(Coins.BTC, cls.btc_daemons[i].pid)
@ -479,6 +464,7 @@ class BaseTest(unittest.TestCase):
if cls.start_ltc_nodes:
sc.setDaemonPID(Coins.LTC, cls.ltc_daemons[i].pid)
cls.addPIDInfo(sc, i)
sc.start()
if cls.start_xmr_nodes:
@ -490,74 +476,79 @@ class BaseTest(unittest.TestCase):
t = HttpThread(cls.swap_clients[i].fp, TEST_HTTP_HOST, TEST_HTTP_PORT + i, False, cls.swap_clients[i])
cls.http_threads.append(t)
t.start()
# Set future block rewards to nowhere (a random address), so wallet amounts stay constant
eckey = ECKey()
eckey.generate()
void_block_rewards_pubkey = eckey.get_pubkey().get_bytes()
void_block_rewards_pubkey = cls.getRandomPubkey()
if cls.restore_instance:
cls.btc_addr = cls.swap_clients[0].ci(Coins.BTC).pubkey_to_segwit_address(void_block_rewards_pubkey)
if cls.start_ltc_nodes:
cls.ltc_addr = cls.swap_clients[0].ci(Coins.LTC).pubkey_to_address(void_block_rewards_pubkey)
if cls.start_xmr_nodes:
cls.xmr_addr = cls.callxmrnodewallet(cls, 1, 'get_address')['address']
else:
cls.btc_addr = callnoderpc(0, 'getnewaddress', ['mining_addr', 'bech32'], base_rpc_port=BTC_BASE_RPC_PORT)
num_blocks = 400 # Mine enough to activate segwit
logging.info('Mining %d Bitcoin blocks to %s', num_blocks, cls.btc_addr)
callnoderpc(0, 'generatetoaddress', [num_blocks, cls.btc_addr], base_rpc_port=BTC_BASE_RPC_PORT)
cls.btc_addr = callnoderpc(0, 'getnewaddress', ['mining_addr', 'bech32'], base_rpc_port=BTC_BASE_RPC_PORT)
num_blocks = 400 # Mine enough to activate segwit
logging.info('Mining %d Bitcoin blocks to %s', num_blocks, cls.btc_addr)
callnoderpc(0, 'generatetoaddress', [num_blocks, cls.btc_addr], base_rpc_port=BTC_BASE_RPC_PORT)
btc_addr1 = callnoderpc(1, 'getnewaddress', ['initial addr'], base_rpc_port=BTC_BASE_RPC_PORT)
for i in range(5):
callnoderpc(0, 'sendtoaddress', [btc_addr1, 100], base_rpc_port=BTC_BASE_RPC_PORT)
# Switch addresses so wallet amounts stay constant
num_blocks = 100
cls.btc_addr = cls.swap_clients[0].ci(Coins.BTC).pubkey_to_segwit_address(void_block_rewards_pubkey)
logging.info('Mining %d Bitcoin blocks to %s', num_blocks, cls.btc_addr)
callnoderpc(0, 'generatetoaddress', [num_blocks, cls.btc_addr], base_rpc_port=BTC_BASE_RPC_PORT)
# Switch addresses so wallet amounts stay constant
num_blocks = 100
cls.btc_addr = cls.swap_clients[0].ci(Coins.BTC).pubkey_to_segwit_address(void_block_rewards_pubkey)
logging.info('Mining %d Bitcoin blocks to %s', num_blocks, cls.btc_addr)
callnoderpc(0, 'generatetoaddress', [num_blocks, cls.btc_addr], base_rpc_port=BTC_BASE_RPC_PORT)
checkForks(callnoderpc(0, 'getblockchaininfo', base_rpc_port=BTC_BASE_RPC_PORT))
checkForks(callnoderpc(0, 'getblockchaininfo', base_rpc_port=BTC_BASE_RPC_PORT))
if cls.start_ltc_nodes:
num_blocks = 400
cls.ltc_addr = callnoderpc(0, 'getnewaddress', ['mining_addr', 'bech32'], base_rpc_port=LTC_BASE_RPC_PORT)
logging.info('Mining %d Litecoin blocks to %s', num_blocks, cls.ltc_addr)
callnoderpc(0, 'generatetoaddress', [num_blocks, cls.ltc_addr], base_rpc_port=LTC_BASE_RPC_PORT)
if cls.start_ltc_nodes:
num_blocks = 400
cls.ltc_addr = callnoderpc(0, 'getnewaddress', ['mining_addr', 'bech32'], base_rpc_port=LTC_BASE_RPC_PORT)
logging.info('Mining %d Litecoin blocks to %s', num_blocks, cls.ltc_addr)
callnoderpc(0, 'generatetoaddress', [num_blocks, cls.ltc_addr], base_rpc_port=LTC_BASE_RPC_PORT)
num_blocks = 31
cls.ltc_addr = cls.swap_clients[0].ci(Coins.LTC).pubkey_to_address(void_block_rewards_pubkey)
logging.info('Mining %d Litecoin blocks to %s', num_blocks, cls.ltc_addr)
callnoderpc(0, 'generatetoaddress', [num_blocks, cls.ltc_addr], base_rpc_port=LTC_BASE_RPC_PORT)
num_blocks = 31
cls.ltc_addr = cls.swap_clients[0].ci(Coins.LTC).pubkey_to_address(void_block_rewards_pubkey)
logging.info('Mining %d Litecoin blocks to %s', num_blocks, cls.ltc_addr)
callnoderpc(0, 'generatetoaddress', [num_blocks, cls.ltc_addr], base_rpc_port=LTC_BASE_RPC_PORT)
# https://github.com/litecoin-project/litecoin/issues/807
# Block 432 is when MWEB activates. It requires a peg-in. You'll need to generate an mweb address and send some coins to it. Then it will allow you to mine the next block.
mweb_addr = callnoderpc(2, 'getnewaddress', ['mweb_addr', 'mweb'], base_rpc_port=LTC_BASE_RPC_PORT)
callnoderpc(0, 'sendtoaddress', [mweb_addr, 1], base_rpc_port=LTC_BASE_RPC_PORT)
# https://github.com/litecoin-project/litecoin/issues/807
# Block 432 is when MWEB activates. It requires a peg-in. You'll need to generate an mweb address and send some coins to it. Then it will allow you to mine the next block.
mweb_addr = callnoderpc(2, 'getnewaddress', ['mweb_addr', 'mweb'], base_rpc_port=LTC_BASE_RPC_PORT)
callnoderpc(0, 'sendtoaddress', [mweb_addr, 1], base_rpc_port=LTC_BASE_RPC_PORT)
num_blocks = 69
cls.ltc_addr = cls.swap_clients[0].ci(Coins.LTC).pubkey_to_address(void_block_rewards_pubkey)
callnoderpc(0, 'generatetoaddress', [num_blocks, cls.ltc_addr], base_rpc_port=LTC_BASE_RPC_PORT)
ltc_addr1 = callnoderpc(1, 'getnewaddress', ['initial addr'], base_rpc_port=LTC_BASE_RPC_PORT)
for i in range(5):
callnoderpc(0, 'sendtoaddress', [ltc_addr1, 100], base_rpc_port=LTC_BASE_RPC_PORT)
checkForks(callnoderpc(0, 'getblockchaininfo', base_rpc_port=LTC_BASE_RPC_PORT))
num_blocks = 69
cls.ltc_addr = cls.swap_clients[0].ci(Coins.LTC).pubkey_to_address(void_block_rewards_pubkey)
callnoderpc(0, 'generatetoaddress', [num_blocks, cls.ltc_addr], base_rpc_port=LTC_BASE_RPC_PORT)
if cls.start_pivx_nodes:
num_blocks = 400
cls.pivx_addr = callnoderpc(0, 'getnewaddress', ['mining_addr'], base_rpc_port=PIVX_BASE_RPC_PORT)
logging.info('Mining %d PIVX blocks to %s', num_blocks, cls.pivx_addr)
callnoderpc(0, 'generatetoaddress', [num_blocks, cls.pivx_addr], base_rpc_port=PIVX_BASE_RPC_PORT)
checkForks(callnoderpc(0, 'getblockchaininfo', base_rpc_port=LTC_BASE_RPC_PORT))
# Switch addresses so wallet amounts stay constant
num_blocks = 100
cls.pivx_addr = cls.swap_clients[0].ci(Coins.PIVX).pubkey_to_address(void_block_rewards_pubkey)
logging.info('Mining %d PIVX blocks to %s', num_blocks, cls.pivx_addr)
callnoderpc(0, 'generatetoaddress', [num_blocks, cls.pivx_addr], base_rpc_port=PIVX_BASE_RPC_PORT)
num_blocks = 100
if cls.start_xmr_nodes:
cls.xmr_addr = cls.callxmrnodewallet(cls, 1, 'get_address')['address']
if callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'get_block_count')['count'] < num_blocks:
logging.info('Mining %d Monero blocks to %s.', num_blocks, cls.xmr_addr)
callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': num_blocks})
logging.info('XMR blocks: %d', callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'get_block_count')['count'])
logging.info('Adding anon outputs')
outputs = []
for i in range(8):
sx_addr = callnoderpc(1, 'getnewstealthaddress')
outputs.append({'address': sx_addr, 'amount': 0.5})
for i in range(6):
callnoderpc(0, 'sendtypeto', ['part', 'anon', outputs])
if cls.start_xmr_nodes:
cls.xmr_addr = cls.callxmrnodewallet(cls, 1, 'get_address')['address']
if callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'get_block_count')['count'] < num_blocks:
logging.info('Mining %d Monero blocks to %s.', num_blocks, cls.xmr_addr)
callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': num_blocks})
logging.info('XMR blocks: %d', callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'get_block_count')['count'])
logging.info('Adding anon outputs')
outputs = []
for i in range(8):
sx_addr = callnoderpc(1, 'getnewstealthaddress')
outputs.append({'address': sx_addr, 'amount': 0.5})
for i in range(6):
callnoderpc(0, 'sendtypeto', ['part', 'anon', outputs])
part_addr1 = callnoderpc(1, 'getnewaddress', ['initial addr'])
part_addr2 = callnoderpc(1, 'getnewaddress', ['initial addr 2'])
callnoderpc(0, 'sendtypeto', ['part', 'part', [{'address': part_addr1, 'amount': 100}, {'address': part_addr2, 'amount': 100}]])
cls.prepareExtraCoins()
logging.info('Starting update thread.')
signal.signal(signal.SIGINT, signal_handler)
@ -599,13 +590,40 @@ class BaseTest(unittest.TestCase):
stopDaemons(cls.part_daemons)
stopDaemons(cls.btc_daemons)
stopDaemons(cls.ltc_daemons)
stopDaemons(cls.pivx_daemons)
super(BaseTest, cls).tearDownClass()
@classmethod
def addCoinSettings(cls, settings, datadir, node_id):
pass
@classmethod
def prepareExtraDataDir(cls, i):
pass
@classmethod
def addPIDInfo(cls, sc, i):
pass
@classmethod
def prepareExtraCoins(cls):
pass
@classmethod
def coins_loop(cls):
if cls.btc_addr is not None:
btcCli('generatetoaddress 1 {}'.format(cls.btc_addr))
if cls.ltc_addr is not None:
ltcCli('generatetoaddress 1 {}'.format(cls.ltc_addr))
if cls.xmr_addr is not None:
callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': 1})
def callxmrnodewallet(self, node_id, method, params=None):
return callrpc_xmr(XMR_BASE_WALLET_RPC_PORT + node_id, self.xmr_wallet_auth[node_id], method, params)
def getXmrBalance(self, js_wallets):
return float(js_wallets[Coins.XMR.name]['unconfirmed']) + float(js_wallets[Coins.XMR.name]['balance'])
class Test(BaseTest):
__test__ = True
@ -617,9 +635,9 @@ class Test(BaseTest):
logging.info('---------- Test PART to XMR')
swap_clients = self.swap_clients
start_xmr_amount = self.getXmrBalance(read_json_api(1800, 'wallets'))
js_1 = read_json_api(1801, 'wallets')
assert (make_int(js_1[Coins.XMR.name]['balance'], scale=12) > 0)
assert (make_int(js_1[Coins.XMR.name]['unconfirmed'], scale=12) > 0)
assert (self.getXmrBalance(js_1) > 0.0)
offer_id = swap_clients[0].postOffer(Coins.PART, Coins.XMR, 100 * COIN, 0.11 * XMR_COIN, 100 * COIN, SwapTypes.XMR_SWAP)
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
@ -640,8 +658,9 @@ class Test(BaseTest):
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True)
js_0_end = read_json_api(1800, 'wallets')
end_xmr = float(js_0_end['XMR']['balance']) + float(js_0_end['XMR']['unconfirmed'])
assert (end_xmr > 10.9 and end_xmr < 11.0)
end_xmr_amount = self.getXmrBalance(js_0_end)
xmr_amount_diff = end_xmr_amount - start_xmr_amount
assert (xmr_amount_diff > 10.9 and xmr_amount_diff < 11.0)
bid_id_hex = bid_id.hex()
path = f'bids/{bid_id_hex}/states'

Loading…
Cancel
Save