Add non-segwit Firo support.

Rework tests to combine atomic and xmr test cases.
Modify btc interface to support P2WSH_nested_in_BIP16_P2SH
Add coin feature tests to test_btc_xmr.py
This commit is contained in:
tecnovert 2022-11-07 22:31:10 +02:00
parent c0c2c8b9bb
commit ca264db0d0
No known key found for this signature in database
GPG key ID: 8ED6D8750C4E3F93
21 changed files with 1400 additions and 411 deletions

View file

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

View file

@ -15,7 +15,6 @@ import traceback
import subprocess import subprocess
import basicswap.config as cfg import basicswap.config as cfg
import basicswap.contrib.segwit_addr as segwit_addr
from .rpc import ( from .rpc import (
callrpc, callrpc,
@ -112,12 +111,6 @@ class BaseApp:
return c return c
raise ValueError('Unknown coin: {}'.format(coin_name)) 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): def callrpc(self, method, params=[], wallet=None):
cc = self.coin_clients[Coins.PART] cc = self.coin_clients[Coins.PART]
return callrpc(cc['rpcport'], cc['rpcauth'], method, params, wallet, cc['rpchost']) return callrpc(cc['rpcport'], cc['rpcauth'], method, params, wallet, cc['rpchost'])

View file

@ -34,6 +34,7 @@ from .interface.nmc import NMCInterface
from .interface.xmr import XMRInterface from .interface.xmr import XMRInterface
from .interface.pivx import PIVXInterface from .interface.pivx import PIVXInterface
from .interface.dash import DASHInterface from .interface.dash import DASHInterface
from .interface.firo import FIROInterface
from .interface.passthrough_btc import PassthroughBTCInterface from .interface.passthrough_btc import PassthroughBTCInterface
from . import __version__ from . import __version__
@ -58,7 +59,6 @@ from .util.address import (
getKeyID, getKeyID,
decodeWif, decodeWif,
decodeAddress, decodeAddress,
encodeAddress,
pubkeyToAddress, pubkeyToAddress,
) )
from .chainparams import ( from .chainparams import (
@ -531,6 +531,8 @@ class BasicSwap(BaseApp):
return PIVXInterface(self.coin_clients[coin], self.chain, self) return PIVXInterface(self.coin_clients[coin], self.chain, self)
elif coin == Coins.DASH: elif coin == Coins.DASH:
return DASHInterface(self.coin_clients[coin], self.chain, self) return DASHInterface(self.coin_clients[coin], self.chain, self)
elif coin == Coins.FIRO:
return FIROInterface(self.coin_clients[coin], self.chain, self)
else: else:
raise ValueError('Unknown coin type') raise ValueError('Unknown coin type')
@ -549,7 +551,7 @@ class BasicSwap(BaseApp):
authcookiepath = os.path.join(self.getChainDatadirPath(coin), '.cookie') authcookiepath = os.path.join(self.getChainDatadirPath(coin), '.cookie')
pidfilename = cc['name'] pidfilename = cc['name']
if cc['name'] in ('bitcoin', 'litecoin', 'namecoin', 'dash'): if cc['name'] in ('bitcoin', 'litecoin', 'namecoin', 'dash', 'firo'):
pidfilename += 'd' pidfilename += 'd'
pidfilepath = os.path.join(self.getChainDatadirPath(coin), pidfilename + '.pid') pidfilepath = os.path.join(self.getChainDatadirPath(coin), pidfilename + '.pid')
@ -975,10 +977,8 @@ class BasicSwap(BaseApp):
raise ValueError('Invalid swap type for PART_ANON') 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: 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') raise ValueError('Invalid swap type for PART_BLIND')
if coin_from == Coins.PIVX and swap_type == SwapTypes.XMR_SWAP: if coin_from in (Coins.PIVX, Coins.DASH, Coins.FIRO, Coins.NMC) and swap_type == SwapTypes.XMR_SWAP:
raise ValueError('TODO: PIVX -> XMR') raise ValueError('TODO: {} -> XMR'.format(coin_from.name))
if coin_from == Coins.DASH and swap_type == SwapTypes.XMR_SWAP:
raise ValueError('TODO: DASH -> XMR')
def notify(self, event_type, event_data, session=None): 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') 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): def validateOfferLockValue(self, coin_from, coin_to, lock_type, lock_value):
coin_from_has_csv = self.coin_clients[coin_from]['use_csv'] coin_from_has_csv = self.coin_clients[coin_from]['use_csv']
coin_to_has_csv = self.coin_clients[coin_to]['use_csv'] coin_to_has_csv = self.coin_clients[coin_to]['use_csv']
@ -1609,18 +1608,6 @@ class BasicSwap(BaseApp):
self.mxDB.release() self.mxDB.release()
return self._contract_count 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): def getProofOfFunds(self, coin_type, amount_for, extra_commit_bytes):
ci = self.ci(coin_type) ci = self.ci(coin_type)
self.log.debug('getProofOfFunds %s %s', ci.coin_name(), ci.format_amount(amount_for)) 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': if self.coin_clients[coin_type]['connection_type'] != 'rpc':
return (None, None) return (None, None)
# TODO: Lock unspent and use same output/s to fund bid return ci.getProofOfFunds(amount_for, extra_commit_bytes)
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)
def saveBidInSession(self, bid_id, bid, session, xmr_swap=None, save_in_progress=None): def saveBidInSession(self, bid_id, bid, session, xmr_swap=None, save_in_progress=None):
session.add(bid) session.add(bid)
@ -2293,16 +2259,16 @@ class BasicSwap(BaseApp):
xmr_swap.kbsl_dleag = xmr_swap.pkbsl xmr_swap.kbsl_dleag = xmr_swap.pkbsl
# MSG2F # 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, bid.amount,
xmr_swap.pkal, xmr_swap.pkaf, xmr_swap.vkbv 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) 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) 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.a_lock_tx, xmr_swap.a_lock_tx_script,
xmr_swap.pkal, xmr_swap.pkaf, xmr_swap.pkal, xmr_swap.pkaf,
xmr_offer.lock_time_1, xmr_offer.lock_time_2, xmr_offer.lock_time_1, xmr_offer.lock_time_2,
@ -2316,7 +2282,7 @@ class BasicSwap(BaseApp):
ensure(v, 'Invalid coin A lock refund tx leader sig') ensure(v, 'Invalid coin A lock refund tx leader sig')
pkh_refund_to = ci_from.decodeAddress(self.getReceiveAddressForCoin(coin_from)) 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, xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script,
pkh_refund_to, pkh_refund_to,
xmr_offer.a_fee_rate, xmr_swap.vkbv xmr_offer.a_fee_rate, xmr_swap.vkbv
@ -2326,7 +2292,7 @@ class BasicSwap(BaseApp):
# Double check txns before sending # 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())) 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 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,
xmr_swap.a_lock_tx_script, xmr_swap.a_lock_tx_script,
bid.amount, bid.amount,
@ -2336,7 +2302,7 @@ class BasicSwap(BaseApp):
check_lock_tx_inputs, check_lock_tx_inputs,
xmr_swap.vkbv) 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_refund_tx,
xmr_swap.a_lock_tx, xmr_swap.a_lock_tx,
xmr_swap.a_lock_refund_tx_script, xmr_swap.a_lock_refund_tx_script,
@ -2351,7 +2317,7 @@ class BasicSwap(BaseApp):
xmr_offer.a_fee_rate, xmr_offer.a_fee_rate,
xmr_swap.vkbv) 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_spend_tx, xmr_swap.a_lock_refund_tx,
xmr_swap.a_lock_refund_tx_id, xmr_swap.a_lock_refund_tx_script, xmr_swap.a_lock_refund_tx_id, xmr_swap.a_lock_refund_tx_script,
xmr_swap.pkal, xmr_swap.pkal,
@ -2602,8 +2568,8 @@ class BasicSwap(BaseApp):
assert (addr_redeem_out is not None) assert (addr_redeem_out is not None)
if self.coin_clients[coin_type]['use_segwit']: if self.coin_clients[coin_type]['use_segwit']:
# Change to btc hrp # Change to part hrp
addr_redeem_out = self.encodeSegwit(Coins.PART, self.decodeSegwit(coin_type, addr_redeem_out)) addr_redeem_out = self.ci(Coins.PART).encodeSegwitAddress(ci.decodeSegwitAddress(addr_redeem_out))
else: else:
addr_redeem_out = replaceAddrPrefix(addr_redeem_out, Coins.PART, self.chain) addr_redeem_out = replaceAddrPrefix(addr_redeem_out, Coins.PART, self.chain)
self.log.debug('addr_redeem_out %s', addr_redeem_out) self.log.debug('addr_redeem_out %s', addr_redeem_out)
@ -2704,8 +2670,8 @@ class BasicSwap(BaseApp):
addr_refund_out = self.getReceiveAddressFromPool(coin_type, bid.bid_id, tx_type) addr_refund_out = self.getReceiveAddressFromPool(coin_type, bid.bid_id, tx_type)
ensure(addr_refund_out is not None, 'addr_refund_out is null') ensure(addr_refund_out is not None, 'addr_refund_out is null')
if self.coin_clients[coin_type]['use_segwit']: if self.coin_clients[coin_type]['use_segwit']:
# Change to btc hrp # Change to part hrp
addr_refund_out = self.encodeSegwit(Coins.PART, self.decodeSegwit(coin_type, addr_refund_out)) addr_refund_out = self.ci(Coins.PART).encodeSegwitAddress(ci.decodeSegwitAddress(addr_refund_out))
else: else:
addr_refund_out = replaceAddrPrefix(addr_refund_out, Coins.PART, self.chain) addr_refund_out = replaceAddrPrefix(addr_refund_out, Coins.PART, self.chain)
self.log.debug('addr_refund_out %s', addr_refund_out) self.log.debug('addr_refund_out %s', addr_refund_out)
@ -2861,7 +2827,6 @@ class BasicSwap(BaseApp):
# TODO: random offset into explorers, try blocks # TODO: random offset into explorers, try blocks
for exp in explorers: for exp in explorers:
# TODO: ExplorerBitAps use only gettransaction if assert_txid is set # TODO: ExplorerBitAps use only gettransaction if assert_txid is set
rv = exp.lookupUnspentByAddress(address) rv = exp.lookupUnspentByAddress(address)
@ -3033,11 +2998,11 @@ class BasicSwap(BaseApp):
# TODO: Timeout waiting for transactions # TODO: Timeout waiting for transactions
bid_changed = False bid_changed = False
a_lock_tx_dest = ci_from.getScriptDest(xmr_swap.a_lock_tx_script) if offer.coin_from == Coins.FIRO:
# Changed from ci_from.getOutput(bid.xmr_a_lock_tx.txid, a_lock_tx_dest, bid.amount, xmr_swap) 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:
p2wsh_addr = ci_from.encode_p2wsh(a_lock_tx_dest) 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, p2wsh_addr, bid.amount, bid.chain_a_height_start) 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: if lock_tx_chain_info is None:
return rv return rv
@ -3149,9 +3114,11 @@ class BasicSwap(BaseApp):
if TxTypes.XMR_SWAP_A_LOCK_REFUND in bid.txns: if TxTypes.XMR_SWAP_A_LOCK_REFUND in bid.txns:
refund_tx = bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND] refund_tx = bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND]
if refund_tx.block_time is None: if refund_tx.block_time is None:
a_lock_refund_tx_dest = ci_from.getScriptDest(xmr_swap.a_lock_refund_tx_script) if offer.coin_from == Coins.FIRO:
p2wsh_addr = ci_from.encode_p2wsh(a_lock_refund_tx_dest) lock_refund_tx_chain_info = ci_from.getLockTxHeightFiro(refund_tx.txid, xmr_swap.a_lock_refund_tx_script, 0, bid.chain_a_height_start)
lock_refund_tx_chain_info = ci_from.getLockTxHeight(refund_tx.txid, p2wsh_addr, 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: 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']) block_header = ci_from.getBlockHeaderFromHeight(lock_refund_tx_chain_info['height'])
@ -4015,18 +3982,7 @@ class BasicSwap(BaseApp):
if swap_type == SwapTypes.SELLER_FIRST: if swap_type == SwapTypes.SELLER_FIRST:
ensure(len(bid_data.pkhash_buyer) == 20, 'Bad pkhash_buyer length') ensure(len(bid_data.pkhash_buyer) == 20, 'Bad pkhash_buyer length')
# Verify proof of funds sum_unspent = ci_to.verifyProofOfFunds(bid_data.proof_address, bid_data.proof_signature, offer_id)
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)
self.log.debug('Proof of funds %s %s', bid_data.proof_address, self.ci(coin_to).format_amount(sum_unspent)) 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') ensure(sum_unspent >= amount_to, 'Proof of funds failed')
@ -4382,7 +4338,7 @@ class BasicSwap(BaseApp):
# TODO: check_lock_tx_inputs without txindex # TODO: check_lock_tx_inputs without txindex
check_a_lock_tx_inputs = False 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, xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script,
bid.amount, bid.amount,
xmr_swap.pkal, xmr_swap.pkaf, xmr_swap.pkal, xmr_swap.pkaf,
@ -4390,14 +4346,14 @@ class BasicSwap(BaseApp):
check_a_lock_tx_inputs, xmr_swap.vkbv) check_a_lock_tx_inputs, xmr_swap.vkbv)
a_lock_tx_dest = ci_from.getScriptDest(xmr_swap.a_lock_tx_script) 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_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.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_swap.pkal, xmr_swap.pkaf,
xmr_offer.lock_time_2, xmr_offer.lock_time_2,
bid.amount, xmr_offer.a_fee_rate, xmr_swap.vkbv) 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_spend_tx, xmr_swap.a_lock_refund_tx,
xmr_swap.a_lock_refund_tx_id, xmr_swap.a_lock_refund_tx_script, xmr_swap.a_lock_refund_tx_id, xmr_swap.a_lock_refund_tx_script,
xmr_swap.pkal, xmr_swap.pkal,
@ -4517,7 +4473,7 @@ class BasicSwap(BaseApp):
xmr_swap.kal_sig = ci_from.signCompact(kal, 'proof key owned for swap') xmr_swap.kal_sig = ci_from.signCompact(kal, 'proof key owned for swap')
# Create Script lock spend tx # 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.a_lock_tx, xmr_swap.a_lock_tx_script,
xmr_swap.dest_af, xmr_swap.dest_af,
xmr_offer.a_fee_rate, xmr_swap.vkbv) xmr_offer.a_fee_rate, xmr_swap.vkbv)
@ -4923,12 +4879,12 @@ class BasicSwap(BaseApp):
xmr_swap.a_lock_spend_tx_id = ci_from.getTxid(xmr_swap.a_lock_spend_tx) xmr_swap.a_lock_spend_tx_id = ci_from.getTxid(xmr_swap.a_lock_spend_tx)
xmr_swap.kal_sig = msg_data.kal_sig xmr_swap.kal_sig = msg_data.kal_sig
ci_from.verifyLockSpendTx( ci_from.verifySCLockSpendTx(
xmr_swap.a_lock_spend_tx, xmr_swap.a_lock_spend_tx,
xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script, xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script,
xmr_swap.dest_af, xmr_offer.a_fee_rate, xmr_swap.vkbv) 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_HAVE_SCRIPT_COIN_SPEND_TX)
bid.setState(BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX) bid.setState(BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX)
@ -5865,7 +5821,7 @@ class BasicSwap(BaseApp):
self.log.debug('Creating %s lock refund swipe tx', ci.coin_name()) self.log.debug('Creating %s lock refund swipe tx', ci.coin_name())
pkh_dest = ci.decodeAddress(self.getReceiveAddressForCoin(ci.coin_type())) 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, xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script,
pkh_dest, pkh_dest,
xmr_offer.a_fee_rate, xmr_swap.vkbv) xmr_offer.a_fee_rate, xmr_swap.vkbv)

View file

@ -9,8 +9,8 @@ import struct
import hashlib import hashlib
from enum import IntEnum, auto from enum import IntEnum, auto
from .util.address import ( from .util.address import (
decodeAddress,
encodeAddress, encodeAddress,
decodeAddress,
) )
from .chainparams import ( from .chainparams import (
chainparams, chainparams,

View file

@ -30,6 +30,7 @@ class Coins(IntEnum):
# NDAU = 10 # NDAU = 10
PIVX = 11 PIVX = 11
DASH = 12 DASH = 12
FIRO = 13
chainparams = { chainparams = {
@ -287,6 +288,45 @@ chainparams = {
'min_amount': 1000, 'min_amount': 1000,
'max_amount': 100000 * COIN, '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 = {} ticker_map = {}

View file

@ -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) DASHD = os.getenv('DASHD', 'dashd' + bin_suffix)
DASH_CLI = os.getenv('DASH_CLI', 'dash-cli' + bin_suffix) DASH_CLI = os.getenv('DASH_CLI', 'dash-cli' + bin_suffix)
DASH_TX = os.getenv('DASH_TX', 'dash-tx' + 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)

View file

@ -320,7 +320,7 @@ class BTCInterface(CoinInterface):
def decodeAddress(self, address): def decodeAddress(self, address):
bech32_prefix = self.chainparams_network()['hrp'] 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 bytes(segwit_addr.decode(bech32_prefix, address)[1])
return decodeAddress(address)[1:] return decodeAddress(address)[1:]
@ -338,12 +338,22 @@ class BTCInterface(CoinInterface):
checksum = hashlib.sha256(hashlib.sha256(data).digest()).digest() checksum = hashlib.sha256(hashlib.sha256(data).digest()).digest()
return b58encode(data + checksum[0:4]) 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): def encode_p2wsh(self, script):
bech32_prefix = self.chainparams_network()['hrp'] bech32_prefix = self.chainparams_network()['hrp']
version = 0 version = 0
program = script[2:] # strip version and length program = script[2:] # strip version and length
return segwit_addr.encode(bech32_prefix, version, program) return segwit_addr.encode(bech32_prefix, version, program)
def encodeScriptDest(self, script):
return self.encode_p2wsh(script)
def encode_p2sh(self, script): def encode_p2sh(self, script):
return pubkeyToAddress(self.chainparams_network()['script_address'], script) return pubkeyToAddress(self.chainparams_network()['script_address'], script)
@ -375,6 +385,12 @@ class BTCInterface(CoinInterface):
def encodePubkey(self, pk): def encodePubkey(self, pk):
return pointToCPK(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): def decodePubkey(self, pke):
return CPKToPoint(pke) return CPKToPoint(pke)
@ -415,7 +431,7 @@ class BTCInterface(CoinInterface):
return CScript([2, Kal_enc, Kaf_enc, 2, CScriptOp(OP_CHECKMULTISIG)]) 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) script = self.genScriptLockTxScript(Kal, Kaf)
tx = CTransaction() tx = CTransaction()
tx.nVersion = self.txVersion() tx.nVersion = self.txVersion()
@ -423,7 +439,7 @@ class BTCInterface(CoinInterface):
return tx.serialize(), script 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) return self.fundTx(tx_bytes, feerate)
def extractScriptLockRefundScriptValues(self, script_bytes): def extractScriptLockRefundScriptValues(self, script_bytes):
@ -470,11 +486,11 @@ class BTCInterface(CoinInterface):
Kaf_enc, CScriptOp(OP_CHECKSIG), Kaf_enc, CScriptOp(OP_CHECKSIG),
CScriptOp(OP_ENDIF)]) 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 = CTransaction()
tx_lock = FromHex(tx_lock, tx_lock_bytes.hex()) 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) locked_n = findOutput(tx_lock, output_script)
ensure(locked_n is not None, 'Output not found in tx') ensure(locked_n is not None, 'Output not found in tx')
locked_coin = tx_lock.vout[locked_n].nValue locked_coin = tx_lock.vout[locked_n].nValue
@ -485,8 +501,10 @@ class BTCInterface(CoinInterface):
refund_script = self.genScriptLockRefundTxScript(Kal, Kaf, csv_val) refund_script = self.genScriptLockRefundTxScript(Kal, Kaf, csv_val)
tx = CTransaction() tx = CTransaction()
tx.nVersion = self.txVersion() tx.nVersion = self.txVersion()
tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n), nSequence=lock1_value)) tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n),
tx.vout.append(self.txoType()(locked_coin, CScript([OP_0, hashlib.sha256(refund_script).digest()]))) 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) dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock)
witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack) witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack)
@ -495,19 +513,19 @@ class BTCInterface(CoinInterface):
tx.vout[0].nValue = locked_coin - pay_fee tx.vout[0].nValue = locked_coin - pay_fee
tx.rehash() 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) i2h(tx.sha256), tx_fee_rate, vsize, pay_fee)
return tx.serialize(), refund_script, tx.vout[0].nValue 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 # 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 # 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 # 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) 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) locked_n = findOutput(tx_lock_refund, output_script)
ensure(locked_n is not None, 'Output not found in tx') ensure(locked_n is not None, 'Output not found in tx')
locked_coin = tx_lock_refund.vout[locked_n].nValue locked_coin = tx_lock_refund.vout[locked_n].nValue
@ -517,7 +535,9 @@ class BTCInterface(CoinInterface):
tx = CTransaction() tx = CTransaction()
tx.nVersion = self.txVersion() 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))) tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_refund_to)))
@ -528,18 +548,18 @@ class BTCInterface(CoinInterface):
tx.vout[0].nValue = locked_coin - pay_fee tx.vout[0].nValue = locked_coin - pay_fee
tx.rehash() 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) i2h(tx.sha256), tx_fee_rate, vsize, pay_fee)
return tx.serialize() 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 # lock refund swipe tx
# Sends the coinA locked coin to the follower # Sends the coinA locked coin to the follower
tx_lock_refund = self.loadTx(tx_lock_refund_bytes) 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) locked_n = findOutput(tx_lock_refund, output_script)
ensure(locked_n is not None, 'Output not found in tx') ensure(locked_n is not None, 'Output not found in tx')
locked_coin = tx_lock_refund.vout[locked_n].nValue locked_coin = tx_lock_refund.vout[locked_n].nValue
@ -551,7 +571,9 @@ class BTCInterface(CoinInterface):
tx = CTransaction() tx = CTransaction()
tx.nVersion = self.txVersion() 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))) tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest)))
@ -562,14 +584,14 @@ class BTCInterface(CoinInterface):
tx.vout[0].nValue = locked_coin - pay_fee tx.vout[0].nValue = locked_coin - pay_fee
tx.rehash() 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) i2h(tx.sha256), tx_fee_rate, vsize, pay_fee)
return tx.serialize() 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) 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) locked_n = findOutput(tx_lock, output_script)
ensure(locked_n is not None, 'Output not found in tx') ensure(locked_n is not None, 'Output not found in tx')
locked_coin = tx_lock.vout[locked_n].nValue locked_coin = tx_lock.vout[locked_n].nValue
@ -579,7 +601,8 @@ class BTCInterface(CoinInterface):
tx = CTransaction() tx = CTransaction()
tx.nVersion = self.txVersion() 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))) tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest)))
@ -590,12 +613,12 @@ class BTCInterface(CoinInterface):
tx.vout[0].nValue = locked_coin - pay_fee tx.vout[0].nValue = locked_coin - pay_fee
tx.rehash() 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) i2h(tx.sha256), tx_fee_rate, vsize, pay_fee)
return tx.serialize() return tx.serialize()
def verifyLockTx(self, tx_bytes, script_out, def verifySCLockTx(self, tx_bytes, script_out,
swap_value, swap_value,
Kal, Kaf, Kal, Kaf,
feerate, feerate,
@ -614,7 +637,7 @@ class BTCInterface(CoinInterface):
ensure(tx.nVersion == self.txVersion(), 'Bad version') ensure(tx.nVersion == self.txVersion(), 'Bad version')
ensure(tx.nLockTime == 0, 'Bad nLockTime') # TODO match txns created by cores 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) locked_n = findOutput(tx, script_pk)
ensure(locked_n is not None, 'Output not found in tx') ensure(locked_n is not None, 'Output not found in tx')
locked_coin = tx.vout[locked_n].nValue locked_coin = tx.vout[locked_n].nValue
@ -663,7 +686,7 @@ class BTCInterface(CoinInterface):
return txid, locked_n return txid, locked_n
def verifyLockRefundTx(self, tx_bytes, lock_tx_bytes, script_out, def verifySCLockRefundTx(self, tx_bytes, lock_tx_bytes, script_out,
prevout_id, prevout_n, prevout_seq, prevout_script, prevout_id, prevout_n, prevout_seq, prevout_script,
Kal, Kaf, csv_val_expect, swap_value, feerate, vkbv=None): Kal, Kaf, csv_val_expect, swap_value, feerate, vkbv=None):
# Verify: # Verify:
@ -680,12 +703,12 @@ class BTCInterface(CoinInterface):
ensure(len(tx.vin) == 1, 'tx doesn\'t have one input') ensure(len(tx.vin) == 1, 'tx doesn\'t have one input')
ensure(tx.vin[0].nSequence == prevout_seq, 'Bad input nSequence') 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(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') 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) locked_n = findOutput(tx, script_pk)
ensure(locked_n is not None, 'Output not found in tx') ensure(locked_n is not None, 'Output not found in tx')
locked_coin = tx.vout[locked_n].nValue locked_coin = tx.vout[locked_n].nValue
@ -712,7 +735,7 @@ class BTCInterface(CoinInterface):
return txid, locked_coin, locked_n return txid, locked_coin, locked_n
def verifyLockRefundSpendTx(self, tx_bytes, lock_refund_tx_bytes, def verifySCLockRefundSpendTx(self, tx_bytes, lock_refund_tx_bytes,
lock_refund_tx_id, prevout_script, lock_refund_tx_id, prevout_script,
Kal, Kal,
prevout_n, prevout_value, feerate, vkbv=None): prevout_n, prevout_value, feerate, vkbv=None):
@ -728,7 +751,7 @@ class BTCInterface(CoinInterface):
ensure(len(tx.vin) == 1, 'tx doesn\'t have one input') ensure(len(tx.vin) == 1, 'tx doesn\'t have one input')
ensure(tx.vin[0].nSequence == 0, 'Bad input nSequence') 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(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') ensure(len(tx.vout) == 1, 'tx doesn\'t have one output')
@ -756,7 +779,7 @@ class BTCInterface(CoinInterface):
return True return True
def verifyLockSpendTx(self, tx_bytes, def verifySCLockSpendTx(self, tx_bytes,
lock_tx_bytes, lock_tx_script, lock_tx_bytes, lock_tx_script,
a_pkhash_f, feerate, vkbv=None): a_pkhash_f, feerate, vkbv=None):
# Verify: # Verify:
@ -774,13 +797,13 @@ class BTCInterface(CoinInterface):
lock_tx = self.loadTx(lock_tx_bytes) lock_tx = self.loadTx(lock_tx_bytes)
lock_tx_id = self.getTxid(lock_tx) 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) locked_n = findOutput(lock_tx, output_script)
ensure(locked_n is not None, 'Output not found in tx') ensure(locked_n is not None, 'Output not found in tx')
locked_coin = lock_tx.vout[locked_n].nValue locked_coin = lock_tx.vout[locked_n].nValue
ensure(tx.vin[0].nSequence == 0, 'Bad input nSequence') 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(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') ensure(len(tx.vout) == 1, 'tx doesn\'t have one output')
@ -891,7 +914,7 @@ class BTCInterface(CoinInterface):
def getTxOutputPos(self, tx, script): def getTxOutputPos(self, tx, script):
if isinstance(tx, bytes): if isinstance(tx, bytes):
tx = self.loadTx(tx) tx = self.loadTx(tx)
script_pk = CScript([OP_0, hashlib.sha256(script).digest()]) script_pk = self.getScriptDest(script)
return findOutput(tx, script_pk) return findOutput(tx, script_pk)
def getPubkeyHash(self, K): def getPubkeyHash(self, K):
@ -900,6 +923,9 @@ class BTCInterface(CoinInterface):
def getScriptDest(self, script): def getScriptDest(self, script):
return CScript([OP_0, hashlib.sha256(script).digest()]) return CScript([OP_0, hashlib.sha256(script).digest()])
def getScriptScriptSig(self, script):
return bytes()
def getPkDest(self, K): def getPkDest(self, K):
return self.getScriptForPubkeyHash(self.getPubkeyHash(K)) return self.getScriptForPubkeyHash(self.getPubkeyHash(K))
@ -1003,11 +1029,22 @@ class BTCInterface(CoinInterface):
def spendBLockTx(self, chain_b_lock_txid, address_to, kbv, kbs, cb_swap_value, b_fee, restore_height): def spendBLockTx(self, chain_b_lock_txid, address_to, kbv, kbs, cb_swap_value, b_fee, restore_height):
raise ValueError('TODO') 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): def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index=False):
# Add watchonly address and rescan if required # Add watchonly address and rescan if required
addr_info = self.rpc_callback('getaddressinfo', [dest_address])
if not addr_info['iswatchonly']: if not self.isWatchOnlyAddress(dest_address):
ro = self.rpc_callback('importaddress', [dest_address, 'bid', False]) self.importWatchOnlyAddress(dest_address, 'bid')
self._log.info('Imported watch-only addr: {}'.format(dest_address)) self._log.info('Imported watch-only addr: {}'.format(dest_address))
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), rescan_from)) self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), rescan_from))
self.rpc_callback('rescanblockchain', [rescan_from]) self.rpc_callback('rescanblockchain', [rescan_from])
@ -1082,7 +1119,7 @@ class BTCInterface(CoinInterface):
privkey = PrivateKey(k) privkey = PrivateKey(k)
return privkey.sign_recoverable(message_hash, hasher=None)[:64] 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() message_hash = hashlib.sha256(bytes(message, 'utf-8')).digest()
pubkey = PublicKey(K) pubkey = PublicKey(K)
rv = pubkey.verify_compact(sig, message_hash, hasher=None) rv = pubkey.verify_compact(sig, message_hash, hasher=None)
@ -1090,7 +1127,7 @@ class BTCInterface(CoinInterface):
def verifyMessage(self, address, message, signature, message_magic=None) -> bool: def verifyMessage(self, address, message, signature, message_magic=None) -> bool:
if message_magic is None: 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_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() message_hash = hashlib.sha256(hashlib.sha256(message_bytes).digest()).digest()
@ -1185,9 +1222,61 @@ class BTCInterface(CoinInterface):
def getBlockWithTxns(self, block_hash): def getBlockWithTxns(self, block_hash):
return self.rpc_callback('getblock', [block_hash, 2]) 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(): def testBTCInterface():
print('testBTCInterface') print('TODO: testBTCInterface')
if __name__ == "__main__": if __name__ == "__main__":

172
basicswap/interface/firo.py Normal file
View file

@ -0,0 +1,172 @@
#!/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 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']

View file

@ -166,7 +166,7 @@ class PARTInterfaceBlind(PARTInterface):
ensure(v['result'] is True, 'verifycommitment failed') ensure(v['result'] is True, 'verifycommitment failed')
return output_n, blinded_info return output_n, blinded_info
def createScriptLockTx(self, value, Kal, Kaf, vkbv): def createSCLockTx(self, value, Kal, Kaf, vkbv):
script = self.genScriptLockTxScript(Kal, Kaf) script = self.genScriptLockTxScript(Kal, Kaf)
# Nonce is derived from vkbv, ephemeral_key isn't used # Nonce is derived from vkbv, ephemeral_key isn't used
@ -183,7 +183,7 @@ class PARTInterfaceBlind(PARTInterface):
tx_bytes = bytes.fromhex(rv['hex']) tx_bytes = bytes.fromhex(rv['hex'])
return tx_bytes, script return tx_bytes, script
def fundScriptLockTx(self, tx_bytes, feerate, vkbv): def fundSCLockTx(self, tx_bytes, feerate, vkbv):
feerate_str = self.format_amount(feerate) feerate_str = self.format_amount(feerate)
# TODO: unlock unspents if bid cancelled # TODO: unlock unspents if bid cancelled
@ -205,7 +205,7 @@ class PARTInterfaceBlind(PARTInterface):
rv = self.rpc_callback('fundrawtransactionfrom', ['blind', tx_hex, {}, outputs_info, options]) rv = self.rpc_callback('fundrawtransactionfrom', ['blind', tx_hex, {}, outputs_info, options])
return bytes.fromhex(rv['hex']) 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()]) lock_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_bytes.hex()])
assert (self.getTxid(tx_lock_bytes).hex() == lock_tx_obj['txid']) assert (self.getTxid(tx_lock_bytes).hex() == lock_tx_obj['txid'])
# Nonce is derived from vkbv, ephemeral_key isn't used # 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 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 # 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 # 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 # If the leader publishes the decrypted signature the leader's coinB spend privatekey will be revealed to the follower
@ -297,7 +297,7 @@ class PARTInterfaceBlind(PARTInterface):
return bytes.fromhex(lock_refund_spend_tx_hex) return bytes.fromhex(lock_refund_spend_tx_hex)
def verifyLockTx(self, tx_bytes, script_out, def verifySCLockTx(self, tx_bytes, script_out,
swap_value, swap_value,
Kal, Kaf, Kal, Kaf,
feerate, feerate,
@ -341,7 +341,7 @@ class PARTInterfaceBlind(PARTInterface):
return bytes.fromhex(lock_txid_hex), lock_output_n return bytes.fromhex(lock_txid_hex), lock_output_n
def verifyLockRefundTx(self, tx_bytes, lock_tx_bytes, script_out, def verifySCLockRefundTx(self, tx_bytes, lock_tx_bytes, script_out,
prevout_id, prevout_n, prevout_seq, prevout_script, prevout_id, prevout_n, prevout_seq, prevout_script,
Kal, Kaf, csv_val_expect, swap_value, feerate, vkbv): Kal, Kaf, csv_val_expect, swap_value, feerate, vkbv):
lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()]) lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()])
@ -399,7 +399,7 @@ class PARTInterfaceBlind(PARTInterface):
return bytes.fromhex(lock_refund_txid_hex), lock_refund_txo_value, lock_refund_output_n return bytes.fromhex(lock_refund_txid_hex), lock_refund_txo_value, lock_refund_output_n
def verifyLockRefundSpendTx(self, tx_bytes, lock_refund_tx_bytes, def verifySCLockRefundSpendTx(self, tx_bytes, lock_refund_tx_bytes,
lock_refund_tx_id, prevout_script, lock_refund_tx_id, prevout_script,
Kal, Kal,
prevout_n, prevout_value, feerate, vkbv): prevout_n, prevout_value, feerate, vkbv):
@ -460,7 +460,7 @@ class PARTInterfaceBlind(PARTInterface):
ensure(output_n is not None, 'Output not found in tx') ensure(output_n is not None, 'Output not found in tx')
return output_n 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_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_bytes.hex()])
lock_txid_hex = lock_tx_obj['txid'] lock_txid_hex = lock_tx_obj['txid']
@ -501,12 +501,12 @@ class PARTInterfaceBlind(PARTInterface):
vsize = lock_spend_tx_obj['vsize'] vsize = lock_spend_tx_obj['vsize']
pay_fee = make_int(lock_spend_tx_obj['vout'][0]['ct_fee']) pay_fee = make_int(lock_spend_tx_obj['vout'][0]['ct_fee'])
actual_tx_fee_rate = pay_fee * 1000 // vsize 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) lock_spend_tx_obj['txid'], actual_tx_fee_rate, vsize, pay_fee)
return bytes.fromhex(lock_spend_tx_hex) return bytes.fromhex(lock_spend_tx_hex)
def verifyLockSpendTx(self, tx_bytes, def verifySCLockSpendTx(self, tx_bytes,
lock_tx_bytes, lock_tx_script, lock_tx_bytes, lock_tx_script,
a_pk_f, feerate, vkbv): a_pk_f, feerate, vkbv):
lock_spend_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()]) lock_spend_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()])
@ -578,7 +578,7 @@ class PARTInterfaceBlind(PARTInterface):
return True 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 # lock refund swipe tx
# Sends the coinA locked coin to the follower # Sends the coinA locked coin to the follower
lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_refund_bytes.hex()]) lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_refund_bytes.hex()])

View file

@ -296,7 +296,7 @@ def page_wallet(self, url_split, post_string):
if show_utxo_groups: if show_utxo_groups:
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) sorted_unspent_by_addr = sorted(unspent_by_addr.items(), key=lambda x: x[1], reverse=True)
for kv in sorted_unspent_by_addr: for kv in sorted_unspent_by_addr:

View file

@ -53,6 +53,9 @@ PIVX_VERSION_TAG = os.getenv('PIVX_VERSION_TAG', '_scantxoutset')
DASH_VERSION = os.getenv('DASH_VERSION', '18.1.0') DASH_VERSION = os.getenv('DASH_VERSION', '18.1.0')
DASH_VERSION_TAG = os.getenv('DASH_VERSION_TAG', '') DASH_VERSION_TAG = os.getenv('DASH_VERSION_TAG', '')
FIRO_VERSION = os.getenv('FIRO_VERSION', '0.14.11.1')
FIRO_VERSION_TAG = os.getenv('FIRO_VERSION_TAG', '')
known_coins = { known_coins = {
'particl': (PARTICL_VERSION, PARTICL_VERSION_TAG, ('tecnovert',)), 'particl': (PARTICL_VERSION, PARTICL_VERSION_TAG, ('tecnovert',)),
@ -62,6 +65,7 @@ known_coins = {
'monero': (MONERO_VERSION, MONERO_VERSION_TAG, ('binaryfate',)), 'monero': (MONERO_VERSION, MONERO_VERSION_TAG, ('binaryfate',)),
'pivx': (PIVX_VERSION, PIVX_VERSION_TAG, ('tecnovert',)), 'pivx': (PIVX_VERSION, PIVX_VERSION_TAG, ('tecnovert',)),
'dash': (DASH_VERSION, DASH_VERSION_TAG, ('pasta',)), 'dash': (DASH_VERSION, DASH_VERSION_TAG, ('pasta',)),
'firo': (FIRO_VERSION, FIRO_VERSION_TAG, ('reuben',)),
} }
expected_key_ids = { expected_key_ids = {
@ -73,6 +77,7 @@ expected_key_ids = {
'davidburkett38': ('3620E9D387E55666',), 'davidburkett38': ('3620E9D387E55666',),
'fuzzbawls': ('3BDCDA2D87A881D9',), 'fuzzbawls': ('3BDCDA2D87A881D9',),
'pasta': ('52527BEDABE87984',), 'pasta': ('52527BEDABE87984',),
'reuben': ('1290A1D0FA7EE109',),
} }
if platform.system() == 'Darwin': if platform.system() == 'Darwin':
@ -137,6 +142,12 @@ DASH_ONION_PORT = int(os.getenv('DASH_ONION_PORT', 9999)) # nDefaultPort
DASH_RPC_USER = os.getenv('DASH_RPC_USER', '') DASH_RPC_USER = os.getenv('DASH_RPC_USER', '')
DASH_RPC_PWD = os.getenv('DASH_RPC_PWD', '') 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_HOST = os.getenv('TOR_PROXY_HOST', '127.0.0.1')
TOR_PROXY_PORT = int(os.getenv('TOR_PROXY_PORT', 9050)) TOR_PROXY_PORT = int(os.getenv('TOR_PROXY_PORT', 9050))
TOR_CONTROL_PORT = int(os.getenv('TOR_CONTROL_PORT', 9051)) TOR_CONTROL_PORT = int(os.getenv('TOR_CONTROL_PORT', 9051))
@ -211,6 +222,19 @@ def downloadBytes(url):
popConnectionParameters() 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(): def testTorConnection():
test_url = 'https://check.torproject.org/' test_url = 'https://check.torproject.org/'
logger.info('Testing TOR connection at: ' + test_url) logger.info('Testing TOR connection at: ' + test_url)
@ -278,8 +302,14 @@ def extractCore(coin, version_data, settings, bin_dir, release_path, extra_opts=
logger.info('extractCore %s v%s%s', coin, version, version_tag) logger.info('extractCore %s v%s%s', coin, version, version_tag)
extract_core_overwrite = extra_opts.get('extract_core_overwrite', True) extract_core_overwrite = extra_opts.get('extract_core_overwrite', True)
if coin in ('monero', 'firo'):
if coin == 'monero': if coin == 'monero':
bins = ['monerod', 'monero-wallet-rpc'] bins = ['monerod', 'monero-wallet-rpc']
elif coin == 'firo':
bins = [coin + 'd', coin + '-cli', coin + '-tx']
else:
raise ValueError('Unknown coin')
num_exist = 0 num_exist = 0
for b in bins: for b in bins:
out_path = os.path.join(bin_dir, b) out_path = os.path.join(bin_dir, b)
@ -412,12 +442,23 @@ 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) 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_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) 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':
raise ValueError('TODO: scantxoutset release')
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)
else: else:
raise ValueError('Unknown coin') 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) release_path = os.path.join(bin_dir, release_filename)
if not os.path.exists(release_path): if not os.path.exists(release_path):
downloadFile(release_url, release_path) downloadFile(release_url, release_path)
@ -428,6 +469,8 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
if not os.path.exists(assert_path): if not os.path.exists(assert_path):
downloadFile(assert_url, assert_path) downloadFile(assert_url, assert_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_filename = '{}-{}-{}-build-{}.assert.sig'.format(coin, os_name, version, signing_key_name)
assert_sig_path = os.path.join(bin_dir, assert_sig_filename) assert_sig_path = os.path.join(bin_dir, assert_sig_filename)
if not os.path.exists(assert_sig_path): if not os.path.exists(assert_sig_path):
@ -462,17 +505,28 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
for key in rv.fingerprints: for key in rv.fingerprints:
gpg.trust_keys(rv.fingerprints[0], 'TRUST_FULLY') gpg.trust_keys(rv.fingerprints[0], 'TRUST_FULLY')
if coin == 'pivx':
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': 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: with open(assert_path, 'rb') as fp:
verified = gpg.verify_file(fp) verified = gpg.verify_file(fp)
if not isValidSignature(verified) and verified.username is None: if not isValidSignature(verified) and verified.username is None:
logger.warning('Signature made by unknown key.') logger.warning('Signature made by unknown key.')
importPubkeyFromUrls(gpg, pubkeyurls)
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')
with open(assert_path, 'rb') as fp: with open(assert_path, 'rb') as fp:
verified = gpg.verify_file(fp) verified = gpg.verify_file(fp)
else: else:
@ -481,28 +535,7 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
if not isValidSignature(verified) and verified.username is None: if not isValidSignature(verified) and verified.username is None:
logger.warning('Signature made by unknown key.') logger.warning('Signature made by unknown key.')
importPubkeyFromUrls(gpg, pubkeyurls)
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')
with open(assert_sig_path, 'rb') as fp: with open(assert_sig_path, 'rb') as fp:
verified = gpg.verify_file(fp, assert_path) verified = gpg.verify_file(fp, assert_path)
@ -600,9 +633,10 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
with open(core_conf_path, 'w') as fp: with open(core_conf_path, 'w') as fp:
if chain != 'mainnet': if chain != 'mainnet':
fp.write(chain + '=1\n') fp.write(chain + '=1\n')
if coin != 'firo':
if chain == 'testnet': if chain == 'testnet':
fp.write('[test]\n\n') fp.write('[test]\n\n')
if chain == 'regtest': elif chain == 'regtest':
fp.write('[regtest]\n\n') fp.write('[regtest]\n\n')
else: else:
logger.warning('Unknown chain %s', chain) logger.warning('Unknown chain %s', chain)
@ -656,6 +690,13 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
fp.write('fallbackfee=0.0002\n') fp.write('fallbackfee=0.0002\n')
if DASH_RPC_USER != '': if DASH_RPC_USER != '':
fp.write('rpcauth={}:{}${}\n'.format(DASH_RPC_USER, salt, password_to_hmac(salt, DASH_RPC_PWD))) 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: else:
logger.warning('Unknown coin %s', coin) logger.warning('Unknown coin %s', coin)
@ -886,6 +927,9 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings,
filename = coin_name + 'd' + ('.exe' if os.name == 'nt' else '') filename = coin_name + 'd' + ('.exe' if os.name == 'nt' else '')
coin_args = ['-nofindpeers', '-nostaking'] if c == Coins.PART 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)) daemons.append(startDaemon(coin_settings['datadir'], coin_settings['bindir'], filename, daemon_args + coin_args))
swap_client.setDaemonPID(c, daemons[-1].pid) swap_client.setDaemonPID(c, daemons[-1].pid)
swap_client.setCoinRunParams(c) swap_client.setCoinRunParams(c)
@ -1173,7 +1217,7 @@ def main():
}, },
'dash': { 'dash': {
'connection_type': 'rpc' if 'dash' in with_coins else 'none', '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, 'rpchost': DASH_RPC_HOST,
'rpcport': DASH_RPC_PORT + port_offset, 'rpcport': DASH_RPC_PORT + port_offset,
'onionport': DASH_ONION_PORT + port_offset, 'onionport': DASH_ONION_PORT + port_offset,
@ -1185,6 +1229,21 @@ def main():
'conf_target': 2, 'conf_target': 2,
'core_version_group': 18, 'core_version_group': 18,
'chain_lookups': 'local', '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 +1262,9 @@ def main():
if DASH_RPC_USER != '': if DASH_RPC_USER != '':
chainclients['dash']['rpcuser'] = DASH_RPC_USER chainclients['dash']['rpcuser'] = DASH_RPC_USER
chainclients['dash']['rpcpassword'] = DASH_RPC_PWD 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']) chainclients['monero']['walletsdir'] = os.getenv('XMR_WALLETS_DIR', chainclients['monero']['datadir'])

13
pgp/keys/firo_reuben.pgp Normal file
View file

@ -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-----

View file

@ -308,6 +308,10 @@ def delay_for(delay_event, delay_for=60):
delay_event.wait(delay_for) 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): def make_rpc_func(node_id, base_rpc_port=BASE_RPC_PORT):
node_id = node_id node_id = node_id
auth = 'test{0}:test_pass{0}'.format(node_id) auth = 'test{0}:test_pass{0}'.format(node_id)

View file

@ -528,13 +528,13 @@ class Test(unittest.TestCase):
def test_08_withdrawal(self): def test_08_withdrawal(self):
logging.info('---------- Test DASH withdrawals') logging.info('---------- Test DASH withdrawals')
pivx_addr = dashRpc('getnewaddress \"Withdrawal test\"') addr = dashRpc('getnewaddress \"Withdrawal test\"')
wallets0 = read_json_api(TEST_HTTP_PORT + 0, 'wallets') wallets = read_json_api(TEST_HTTP_PORT + 0, 'wallets')
assert (float(wallets0['DASH']['balance']) > 100) assert (float(wallets['DASH']['balance']) > 100)
post_json = { post_json = {
'value': 100, 'value': 100,
'address': pivx_addr, 'address': addr,
'subfee': False, '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)) json_rv = json.loads(post_json_req('http://127.0.0.1:{}/json/wallets/dash/withdraw'.format(TEST_HTTP_PORT + 0), post_json))

View file

@ -0,0 +1,465 @@
#!/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
@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:
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))
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_01_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_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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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))
if __name__ == '__main__':
unittest.main()

View file

@ -538,13 +538,13 @@ class Test(unittest.TestCase):
def test_08_withdrawal(self): def test_08_withdrawal(self):
logging.info('---------- Test PIVX withdrawals') logging.info('---------- Test PIVX withdrawals')
pivx_addr = pivxRpc('getnewaddress \"Withdrawal test\"') addr = pivxRpc('getnewaddress \"Withdrawal test\"')
wallets0 = read_json_api(TEST_HTTP_PORT + 0, 'wallets') wallets = read_json_api(TEST_HTTP_PORT + 0, 'wallets')
assert (float(wallets0['PIVX']['balance']) > 100) assert (float(wallets['PIVX']['balance']) > 100)
post_json = { post_json = {
'value': 100, 'value': 100,
'address': pivx_addr, 'address': addr,
'subfee': False, '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)) json_rv = json.loads(post_json_req('http://127.0.0.1:{}/json/wallets/pivx/withdraw'.format(TEST_HTTP_PORT + 0), post_json))

View file

@ -34,6 +34,7 @@ from basicswap.rpc import (
callrpc, callrpc,
) )
from tests.basicswap.common import ( from tests.basicswap.common import (
make_boolean,
read_json_api, read_json_api,
waitForServer, waitForServer,
BASE_RPC_PORT, BASE_RPC_PORT,
@ -46,10 +47,6 @@ from tests.basicswap.common_xmr import (
import bin.basicswap_run as runSystem 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')) test_path = os.path.expanduser(os.getenv('TEST_PATH', '/tmp/test_persistent'))
RESET_TEST = make_boolean(os.getenv('RESET_TEST', 'false')) RESET_TEST = make_boolean(os.getenv('RESET_TEST', 'false'))

View file

@ -27,39 +27,217 @@ from tests.basicswap.common import (
read_json_api, read_json_api,
wait_for_offer, wait_for_offer,
wait_for_none_active, wait_for_none_active,
BTC_BASE_RPC_PORT,
) )
from basicswap.contrib.test_framework.messages import (
from .test_xmr import BaseTest, test_delay_event 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() logger = logging.getLogger()
class Test(BaseTest): class Test(BaseTest):
__test__ = True __test__ = True
test_coin_from = Coins.BTC
@classmethod start_ltc_nodes = False
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()
def getBalance(self, js_wallets): def getBalance(self, js_wallets):
return float(js_wallets[self.test_coin_from.name]['balance']) + float(js_wallets[self.test_coin_from.name]['unconfirmed']) return float(js_wallets[self.test_coin_from.name]['balance']) + float(js_wallets[self.test_coin_from.name]['unconfirmed'])
def getXmrBalance(self, js_wallets): def callnoderpc(self, method, params=[], wallet=None, node_id=0):
return float(js_wallets[Coins.XMR.name]['unconfirmed']) + float(js_wallets[Coins.XMR.name]['balance']) return callnoderpc(node_id, method, params, wallet, base_rpc_port=BTC_BASE_RPC_PORT)
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.callnoderpc('generatetoaddress', [1, self.btc_addr])
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.callnoderpc('generatetoaddress', [1, self.btc_addr])
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.callnoderpc('generatetoaddress', [49, self.btc_addr])
txid = self.callnoderpc('sendrawtransaction', [tx_spend_hex, ])
self.callnoderpc('generatetoaddress', [1, self.btc_addr])
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.callnoderpc('generatetoaddress', [3, self.btc_addr])
txid = self.callnoderpc('sendrawtransaction', [tx_spend_hex, ])
self.callnoderpc('generatetoaddress', [1, self.btc_addr])
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_01_full_swap(self): 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 swap_clients = self.swap_clients
js_0 = read_json_api(1800, 'wallets') js_0 = read_json_api(1800, 'wallets')
@ -108,7 +286,7 @@ class Test(BaseTest):
assert (node1_xmr_after > node1_xmr_before + (amount_to_float - 0.02)) assert (node1_xmr_after > node1_xmr_before + (amount_to_float - 0.02))
def test_02_leader_recover_a_lock_tx(self): 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 swap_clients = self.swap_clients
js_w0_before = read_json_api(1800, 'wallets') js_w0_before = read_json_api(1800, 'wallets')
@ -143,7 +321,7 @@ class Test(BaseTest):
# assert (node0_from_before - node0_from_after < 0.02) # assert (node0_from_before - node0_from_after < 0.02)
def test_03_follower_recover_a_lock_tx(self): 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 swap_clients = self.swap_clients
js_w0_before = read_json_api(1800, 'wallets') js_w0_before = read_json_api(1800, 'wallets')
@ -186,7 +364,7 @@ class Test(BaseTest):
wait_for_none_active(test_delay_event, 1801) wait_for_none_active(test_delay_event, 1801)
def test_04_follower_recover_b_lock_tx(self): 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 swap_clients = self.swap_clients

View file

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

View file

@ -59,6 +59,7 @@ from basicswap.http_server import (
) )
from tests.basicswap.common import ( from tests.basicswap.common import (
prepareDataDir, prepareDataDir,
make_boolean,
make_rpc_func, make_rpc_func,
checkForks, checkForks,
stopDaemons, stopDaemons,
@ -80,8 +81,6 @@ from tests.basicswap.common import (
BTC_BASE_RPC_PORT, BTC_BASE_RPC_PORT,
LTC_BASE_PORT, LTC_BASE_PORT,
LTC_BASE_RPC_PORT, LTC_BASE_RPC_PORT,
PIVX_BASE_PORT,
PIVX_BASE_RPC_PORT,
PREFIX_SECRET_KEY_REGTEST, PREFIX_SECRET_KEY_REGTEST,
) )
from bin.basicswap_run import startDaemon, startXmrDaemon from bin.basicswap_run import startDaemon, startXmrDaemon
@ -93,7 +92,6 @@ NUM_NODES = 3
NUM_XMR_NODES = 3 NUM_XMR_NODES = 3
NUM_BTC_NODES = 3 NUM_BTC_NODES = 3
NUM_LTC_NODES = 3 NUM_LTC_NODES = 3
NUM_PIVX_NODES = 3
TEST_DIR = cfg.TEST_DATADIRS TEST_DIR = cfg.TEST_DATADIRS
XMR_BASE_P2P_PORT = 17792 XMR_BASE_P2P_PORT = 17792
@ -102,6 +100,7 @@ XMR_BASE_ZMQ_PORT = 22792
XMR_BASE_WALLET_RPC_PORT = 23792 XMR_BASE_WALLET_RPC_PORT = 23792
test_delay_event = threading.Event() test_delay_event = threading.Event()
RESET_TEST = make_boolean(os.getenv('RESET_TEST', 'true'))
def prepareXmrDataDir(datadir, node_id, conf_file): 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) 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)) basicswap_dir = os.path.join(datadir, 'basicswap_' + str(node_id))
if not os.path.exists(basicswap_dir): if not os.path.exists(basicswap_dir):
os.makedirs(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, 'use_segwit': True,
} }
if Coins.PIVX in with_coins: if cls:
settings['chainclients']['pivx'] = { cls.addCoinSettings(settings, datadir, node_id)
'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,
}
with open(settings_path, 'w') as fp: with open(settings_path, 'w') as fp:
json.dump(settings, fp, indent=4) 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) 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): def signal_handler(sig, frame):
logging.info('signal {} detected.'.format(sig)) logging.info('signal {} detected.'.format(sig))
test_delay_event.set() test_delay_event.set()
@ -298,14 +284,7 @@ def run_coins_loop(cls):
while not test_delay_event.is_set(): while not test_delay_event.is_set():
pause_event.wait() pause_event.wait()
try: try:
if cls.btc_addr is not None: cls.coins_loop()
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})
except Exception as e: except Exception as e:
logging.warning('run_coins_loop ' + str(e)) logging.warning('run_coins_loop ' + str(e))
test_delay_event.wait(1.0) test_delay_event.wait(1.0)
@ -320,34 +299,34 @@ def run_loop(cls):
class BaseTest(unittest.TestCase): class BaseTest(unittest.TestCase):
__test__ = False __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 getRandomPubkey(cls):
eckey = ECKey()
eckey.generate()
return eckey.get_pubkey().get_bytes()
@classmethod @classmethod
def setUpClass(cls): 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
random.seed(time.time()) 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.propagate = False
logger.handlers = [] logger.handlers = []
logger.setLevel(logging.INFO) # DEBUG shows many messages from requests.post logger.setLevel(logging.INFO) # DEBUG shows many messages from requests.post
@ -356,7 +335,12 @@ class BaseTest(unittest.TestCase):
stream_stdout.setFormatter(formatter) stream_stdout.setFormatter(formatter)
logger.addHandler(stream_stdout) 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): if os.path.isdir(TEST_DIR):
if RESET_TEST:
logging.info('Removing ' + TEST_DIR) logging.info('Removing ' + TEST_DIR)
for name in os.listdir(TEST_DIR): for name in os.listdir(TEST_DIR):
if name == 'pivx-params': if name == 'pivx-params':
@ -366,6 +350,9 @@ class BaseTest(unittest.TestCase):
shutil.rmtree(fullpath) shutil.rmtree(fullpath)
else: else:
os.remove(fullpath) os.remove(fullpath)
else:
logging.info('Restoring instance from ' + TEST_DIR)
cls.restore_instance = True
if not os.path.exists(TEST_DIR): if not os.path.exists(TEST_DIR):
os.makedirs(TEST_DIR) os.makedirs(TEST_DIR)
@ -373,13 +360,10 @@ class BaseTest(unittest.TestCase):
cls.stream_fp.setFormatter(formatter) cls.stream_fp.setFormatter(formatter)
logger.addHandler(cls.stream_fp) 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: try:
logging.info('Preparing coin nodes.') logging.info('Preparing coin nodes.')
for i in range(NUM_NODES): for i in range(NUM_NODES):
if not cls.restore_instance:
data_dir = prepareDataDir(TEST_DIR, i, 'particl.conf', 'part_') data_dir = prepareDataDir(TEST_DIR, i, 'particl.conf', 'part_')
if os.path.exists(os.path.join(cfg.PARTICL_BINDIR, 'particl-wallet')): 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') callrpc_cli(cfg.PARTICL_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'particl-wallet')
@ -387,6 +371,7 @@ class BaseTest(unittest.TestCase):
cls.part_daemons.append(startDaemon(os.path.join(TEST_DIR, 'part_' + str(i)), cfg.PARTICL_BINDIR, cfg.PARTICLD)) 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) logging.info('Started %s %d', cfg.PARTICLD, cls.part_daemons[-1].pid)
if not cls.restore_instance:
for i in range(NUM_NODES): for i in range(NUM_NODES):
# Load mnemonics after all nodes have started to avoid staking getting stuck in TryToSync # Load mnemonics after all nodes have started to avoid staking getting stuck in TryToSync
rpc = make_rpc_func(i) rpc = make_rpc_func(i)
@ -403,6 +388,7 @@ class BaseTest(unittest.TestCase):
rpc('walletsettings', ['stakingoptions', {'stakecombinethreshold': 100, 'stakesplitthreshold': 200}]) rpc('walletsettings', ['stakingoptions', {'stakecombinethreshold': 100, 'stakesplitthreshold': 200}])
for i in range(NUM_BTC_NODES): for i in range(NUM_BTC_NODES):
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) 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')): 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') callrpc_cli(cfg.BITCOIN_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'bitcoin-wallet')
@ -414,6 +400,7 @@ class BaseTest(unittest.TestCase):
if cls.start_ltc_nodes: if cls.start_ltc_nodes:
for i in range(NUM_LTC_NODES): for i in range(NUM_LTC_NODES):
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) 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')): 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') callrpc_cli(cfg.LITECOIN_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'litecoin-wallet')
@ -423,19 +410,9 @@ class BaseTest(unittest.TestCase):
waitForRPC(make_rpc_func(i, base_rpc_port=LTC_BASE_RPC_PORT)) 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: if cls.start_xmr_nodes:
for i in range(NUM_XMR_NODES): for i in range(NUM_XMR_NODES):
if not cls.restore_instance:
prepareXmrDataDir(TEST_DIR, i, 'monerod.conf') prepareXmrDataDir(TEST_DIR, i, 'monerod.conf')
cls.xmr_daemons.append(startXmrDaemon(os.path.join(TEST_DIR, 'xmr_' + str(i)), cfg.XMR_BINDIR, cfg.XMRD)) cls.xmr_daemons.append(startXmrDaemon(os.path.join(TEST_DIR, 'xmr_' + str(i)), cfg.XMR_BINDIR, cfg.XMRD))
@ -450,10 +427,16 @@ class BaseTest(unittest.TestCase):
waitForXMRWallet(i, cls.xmr_wallet_auth[i]) waitForXMRWallet(i, cls.xmr_wallet_auth[i])
if not cls.restore_instance:
cls.callxmrnodewallet(cls, i, 'create_wallet', {'filename': 'testwallet', 'language': 'English'}) cls.callxmrnodewallet(cls, i, 'create_wallet', {'filename': 'testwallet', 'language': 'English'})
cls.callxmrnodewallet(cls, i, 'open_wallet', {'filename': 'testwallet'}) 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.') logging.info('Preparing swap clients.')
if not cls.restore_instance:
eckey = ECKey() eckey = ECKey()
eckey.generate() eckey.generate()
cls.network_key = toWIF(PREFIX_SECRET_KEY_REGTEST, eckey.get_bytes()) cls.network_key = toWIF(PREFIX_SECRET_KEY_REGTEST, eckey.get_bytes())
@ -465,13 +448,15 @@ class BaseTest(unittest.TestCase):
start_nodes.add(Coins.LTC) start_nodes.add(Coins.LTC)
if cls.start_xmr_nodes: if cls.start_xmr_nodes:
start_nodes.add(Coins.XMR) start_nodes.add(Coins.XMR)
if cls.start_pivx_nodes: if not cls.restore_instance:
start_nodes.add(Coins.PIVX) prepare_swapclient_dir(TEST_DIR, i, cls.network_key, cls.network_pubkey, start_nodes, cls)
prepare_swapclient_dir(TEST_DIR, i, cls.network_key, cls.network_pubkey, start_nodes)
basicswap_dir = os.path.join(os.path.join(TEST_DIR, 'basicswap_' + str(i))) basicswap_dir = os.path.join(os.path.join(TEST_DIR, 'basicswap_' + str(i)))
settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME) settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME)
with open(settings_path) as fs: with open(settings_path) as fs:
settings = json.load(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') fp = open(os.path.join(basicswap_dir, 'basicswap.log'), 'w')
sc = BasicSwap(fp, basicswap_dir, settings, 'regtest', log_name='BasicSwap{}'.format(i)) sc = BasicSwap(fp, basicswap_dir, settings, 'regtest', log_name='BasicSwap{}'.format(i))
sc.setDaemonPID(Coins.BTC, cls.btc_daemons[i].pid) sc.setDaemonPID(Coins.BTC, cls.btc_daemons[i].pid)
@ -479,6 +464,7 @@ class BaseTest(unittest.TestCase):
if cls.start_ltc_nodes: if cls.start_ltc_nodes:
sc.setDaemonPID(Coins.LTC, cls.ltc_daemons[i].pid) sc.setDaemonPID(Coins.LTC, cls.ltc_daemons[i].pid)
cls.addPIDInfo(sc, i)
sc.start() sc.start()
if cls.start_xmr_nodes: if cls.start_xmr_nodes:
@ -490,17 +476,24 @@ class BaseTest(unittest.TestCase):
t = HttpThread(cls.swap_clients[i].fp, TEST_HTTP_HOST, TEST_HTTP_PORT + i, False, cls.swap_clients[i]) t = HttpThread(cls.swap_clients[i].fp, TEST_HTTP_HOST, TEST_HTTP_PORT + i, False, cls.swap_clients[i])
cls.http_threads.append(t) cls.http_threads.append(t)
t.start() t.start()
# Set future block rewards to nowhere (a random address), so wallet amounts stay constant # Set future block rewards to nowhere (a random address), so wallet amounts stay constant
eckey = ECKey() void_block_rewards_pubkey = cls.getRandomPubkey()
eckey.generate() if cls.restore_instance:
void_block_rewards_pubkey = eckey.get_pubkey().get_bytes() 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) cls.btc_addr = callnoderpc(0, 'getnewaddress', ['mining_addr', 'bech32'], base_rpc_port=BTC_BASE_RPC_PORT)
num_blocks = 400 # Mine enough to activate segwit num_blocks = 400 # Mine enough to activate segwit
logging.info('Mining %d Bitcoin blocks to %s', num_blocks, cls.btc_addr) 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) 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 # Switch addresses so wallet amounts stay constant
num_blocks = 100 num_blocks = 100
cls.btc_addr = cls.swap_clients[0].ci(Coins.BTC).pubkey_to_segwit_address(void_block_rewards_pubkey) cls.btc_addr = cls.swap_clients[0].ci(Coins.BTC).pubkey_to_segwit_address(void_block_rewards_pubkey)
@ -531,18 +524,6 @@ class BaseTest(unittest.TestCase):
checkForks(callnoderpc(0, 'getblockchaininfo', base_rpc_port=LTC_BASE_RPC_PORT)) checkForks(callnoderpc(0, 'getblockchaininfo', 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)
# 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 num_blocks = 100
if cls.start_xmr_nodes: if cls.start_xmr_nodes:
cls.xmr_addr = cls.callxmrnodewallet(cls, 1, 'get_address')['address'] cls.xmr_addr = cls.callxmrnodewallet(cls, 1, 'get_address')['address']
@ -559,6 +540,12 @@ class BaseTest(unittest.TestCase):
for i in range(6): for i in range(6):
callnoderpc(0, 'sendtypeto', ['part', 'anon', outputs]) 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.') logging.info('Starting update thread.')
signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGINT, signal_handler)
cls.update_thread = threading.Thread(target=run_loop, args=(cls,)) cls.update_thread = threading.Thread(target=run_loop, args=(cls,))
@ -599,13 +586,40 @@ class BaseTest(unittest.TestCase):
stopDaemons(cls.part_daemons) stopDaemons(cls.part_daemons)
stopDaemons(cls.btc_daemons) stopDaemons(cls.btc_daemons)
stopDaemons(cls.ltc_daemons) stopDaemons(cls.ltc_daemons)
stopDaemons(cls.pivx_daemons)
super(BaseTest, cls).tearDownClass() 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): 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) 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): class Test(BaseTest):
__test__ = True __test__ = True
@ -617,9 +631,9 @@ class Test(BaseTest):
logging.info('---------- Test PART to XMR') logging.info('---------- Test PART to XMR')
swap_clients = self.swap_clients swap_clients = self.swap_clients
start_xmr_amount = self.getXmrBalance(read_json_api(1800, 'wallets'))
js_1 = read_json_api(1801, 'wallets') js_1 = read_json_api(1801, 'wallets')
assert (make_int(js_1[Coins.XMR.name]['balance'], scale=12) > 0) assert (self.getXmrBalance(js_1) > 0.0)
assert (make_int(js_1[Coins.XMR.name]['unconfirmed'], scale=12) > 0)
offer_id = swap_clients[0].postOffer(Coins.PART, Coins.XMR, 100 * COIN, 0.11 * XMR_COIN, 100 * COIN, SwapTypes.XMR_SWAP) 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) wait_for_offer(test_delay_event, swap_clients[1], offer_id)
@ -640,8 +654,9 @@ class Test(BaseTest):
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True) wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True)
js_0_end = read_json_api(1800, 'wallets') js_0_end = read_json_api(1800, 'wallets')
end_xmr = float(js_0_end['XMR']['balance']) + float(js_0_end['XMR']['unconfirmed']) end_xmr_amount = self.getXmrBalance(js_0_end)
assert (end_xmr > 10.9 and end_xmr < 11.0) 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() bid_id_hex = bid_id.hex()
path = f'bids/{bid_id_hex}/states' path = f'bids/{bid_id_hex}/states'