mirror of
https://github.com/basicswap/basicswap.git
synced 2025-01-18 16:44:34 +00:00
Support xmr-protocol swaps to BTC and PART
This commit is contained in:
parent
1d0a3fbc12
commit
0e1cb6d03d
9 changed files with 349 additions and 205 deletions
|
@ -3227,11 +3227,11 @@ class BasicSwap(BaseApp):
|
|||
self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_SEEN, '', session)
|
||||
if bid.xmr_b_lock_tx is None:
|
||||
self.log.debug('Found {} lock tx in chain'.format(ci_to.coin_name()))
|
||||
b_lock_tx_id = bytes.fromhex(found_tx['txid'])
|
||||
xmr_swap.b_lock_tx_id = bytes.fromhex(found_tx['txid'])
|
||||
bid.xmr_b_lock_tx = SwapTx(
|
||||
bid_id=bid_id,
|
||||
tx_type=TxTypes.XMR_SWAP_B_LOCK,
|
||||
txid=b_lock_tx_id,
|
||||
txid=xmr_swap.b_lock_tx_id,
|
||||
chain_height=found_tx['height'],
|
||||
)
|
||||
bid_changed = True
|
||||
|
@ -3930,7 +3930,7 @@ class BasicSwap(BaseApp):
|
|||
raise ValueError('TODO')
|
||||
elif offer_data.swap_type == SwapTypes.XMR_SWAP:
|
||||
ensure(coin_from not in non_script_type_coins, 'Invalid coin from type')
|
||||
ensure(coin_to in non_script_type_coins, 'Invalid coin to type')
|
||||
ensure(ci_from.has_segwit(), 'Coin from must support segwit')
|
||||
ensure(len(offer_data.proof_address) == 0, 'Unexpected data')
|
||||
ensure(len(offer_data.proof_signature) == 0, 'Unexpected data')
|
||||
ensure(len(offer_data.pkhash_seller) == 0, 'Unexpected data')
|
||||
|
@ -4728,6 +4728,8 @@ class BasicSwap(BaseApp):
|
|||
try:
|
||||
b_lock_tx_id = ci_to.publishBLockTx(xmr_swap.pkbv, xmr_swap.pkbs, bid.amount_to, xmr_offer.b_fee_rate, unlock_time=unlock_time)
|
||||
except Exception as ex:
|
||||
if self.debug:
|
||||
self.log.error(traceback.format_exc())
|
||||
error_msg = 'publishBLockTx failed for bid {} with error {}'.format(bid_id.hex(), str(ex))
|
||||
num_retries = self.countBidEvents(bid, EventLogTypes.FAILED_TX_B_LOCK_PUBLISH, session)
|
||||
if num_retries > 0:
|
||||
|
@ -4751,6 +4753,7 @@ class BasicSwap(BaseApp):
|
|||
tx_type=TxTypes.XMR_SWAP_B_LOCK,
|
||||
txid=b_lock_tx_id,
|
||||
)
|
||||
xmr_swap.b_lock_tx_id = b_lock_tx_id
|
||||
bid.xmr_b_lock_tx.setState(TxStates.TX_NONE)
|
||||
self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_PUBLISHED, '', session)
|
||||
|
||||
|
@ -4867,8 +4870,11 @@ class BasicSwap(BaseApp):
|
|||
|
||||
if coin_to == Coins.XMR:
|
||||
address_to = self.getCachedMainWalletAddress(ci_to)
|
||||
else:
|
||||
elif coin_to == Coins.PART_BLIND:
|
||||
address_to = self.getCachedStealthAddressForCoin(coin_to)
|
||||
else:
|
||||
address_to = self.getReceiveAddressFromPool(coin_to, bid_id, TxTypes.XMR_SWAP_B_LOCK_SPEND)
|
||||
|
||||
txid = ci_to.spendBLockTx(xmr_swap.b_lock_tx_id, address_to, xmr_swap.vkbv, vkbs, bid.amount_to, xmr_offer.b_fee_rate, bid.chain_b_height_start)
|
||||
self.log.debug('Submitted lock B spend txn %s to %s chain for bid %s', txid.hex(), ci_to.coin_name(), bid_id.hex())
|
||||
self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_SPEND_TX_PUBLISHED, '', session)
|
||||
|
@ -4924,8 +4930,10 @@ class BasicSwap(BaseApp):
|
|||
try:
|
||||
if offer.coin_to == Coins.XMR:
|
||||
address_to = self.getCachedMainWalletAddress(ci_to)
|
||||
else:
|
||||
elif coin_to == Coins.PART_BLIND:
|
||||
address_to = self.getCachedStealthAddressForCoin(coin_to)
|
||||
else:
|
||||
address_to = self.getReceiveAddressFromPool(coin_to, bid_id, TxTypes.XMR_SWAP_B_LOCK_REFUND)
|
||||
txid = ci_to.spendBLockTx(xmr_swap.b_lock_tx_id, address_to, xmr_swap.vkbv, vkbs, bid.amount_to, xmr_offer.b_fee_rate, bid.chain_b_height_start)
|
||||
self.log.debug('Submitted lock B refund txn %s to %s chain for bid %s', txid.hex(), ci_to.coin_name(), bid_id.hex())
|
||||
self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_REFUND_TX_PUBLISHED, '', session)
|
||||
|
|
|
@ -122,6 +122,8 @@ class TxTypes(IntEnum):
|
|||
XMR_SWAP_A_LOCK_REFUND_SPEND = auto()
|
||||
XMR_SWAP_A_LOCK_REFUND_SWIPE = auto()
|
||||
XMR_SWAP_B_LOCK = auto()
|
||||
XMR_SWAP_B_LOCK_SPEND = auto()
|
||||
XMR_SWAP_B_LOCK_REFUND = auto()
|
||||
|
||||
ITX_PRE_FUNDED = auto()
|
||||
|
||||
|
|
|
@ -296,7 +296,7 @@ chainparams = {
|
|||
'blocks_target': 60 * 10,
|
||||
'decimal_places': 8,
|
||||
'has_csv': True,
|
||||
'has_segwit': True,
|
||||
'has_segwit': False,
|
||||
'mainnet': {
|
||||
'rpcport': 8888,
|
||||
'pubkey_address': 82,
|
||||
|
@ -353,20 +353,20 @@ class CoinInterface:
|
|||
self._unknown_wallet_seed = True
|
||||
self._restore_height = None
|
||||
|
||||
def make_int(self, amount_in, r=0):
|
||||
def make_int(self, amount_in: int, r: int = 0) -> int:
|
||||
return make_int(amount_in, self.exp(), r=r)
|
||||
|
||||
def format_amount(self, amount_in, conv_int=False, r=0):
|
||||
amount_int = make_int(amount_in, self.exp(), r=r) if conv_int else amount_in
|
||||
return format_amount(amount_int, self.exp())
|
||||
|
||||
def coin_name(self):
|
||||
def coin_name(self) -> str:
|
||||
coin_chainparams = chainparams[self.coin_type()]
|
||||
if coin_chainparams.get('use_ticker_as_name', False):
|
||||
return coin_chainparams['ticker']
|
||||
return coin_chainparams['name'].capitalize()
|
||||
|
||||
def ticker(self):
|
||||
def ticker(self) -> str:
|
||||
ticker = chainparams[self.coin_type()]['ticker']
|
||||
if self._network == 'testnet':
|
||||
ticker = 't' + ticker
|
||||
|
@ -405,6 +405,9 @@ class CoinInterface:
|
|||
def chainparams_network(self):
|
||||
return chainparams[self.coin_type()][self._network]
|
||||
|
||||
def has_segwit(self) -> bool:
|
||||
return chainparams[self.coin_type()].get('has_segwit', True)
|
||||
|
||||
def is_transient_error(self, ex):
|
||||
if isinstance(ex, TemporaryError):
|
||||
return True
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
import json
|
||||
import time
|
||||
import base64
|
||||
import hashlib
|
||||
import logging
|
||||
|
@ -16,7 +15,6 @@ from io import BytesIO
|
|||
from basicswap.contrib.test_framework import segwit_addr
|
||||
|
||||
from basicswap.util import (
|
||||
dumpj,
|
||||
ensure,
|
||||
make_int,
|
||||
b2h, i2b, b2i, i2h)
|
||||
|
@ -55,7 +53,8 @@ from basicswap.contrib.test_framework.messages import (
|
|||
CTxIn,
|
||||
CTxInWitness,
|
||||
CTxOut,
|
||||
FromHex)
|
||||
FromHex,
|
||||
uint256_from_str)
|
||||
|
||||
from basicswap.contrib.test_framework.script import (
|
||||
CScript, CScriptOp,
|
||||
|
@ -86,14 +85,14 @@ def ensure_op(v, err_string='Bad opcode'):
|
|||
ensure(v, err_string)
|
||||
|
||||
|
||||
def findOutput(tx, script_pk):
|
||||
def findOutput(tx, script_pk: bytes):
|
||||
for i in range(len(tx.vout)):
|
||||
if tx.vout[i].scriptPubKey == script_pk:
|
||||
return i
|
||||
return None
|
||||
|
||||
|
||||
def find_vout_for_address_from_txobj(tx_obj, addr):
|
||||
def find_vout_for_address_from_txobj(tx_obj, addr) -> int:
|
||||
"""
|
||||
Locate the vout index of the given transaction sending to the
|
||||
given address. Raises runtime error exception if not found.
|
||||
|
@ -139,7 +138,7 @@ class BTCInterface(CoinInterface):
|
|||
return 2
|
||||
|
||||
@staticmethod
|
||||
def getTxOutputValue(tx):
|
||||
def getTxOutputValue(tx) -> int:
|
||||
rv = 0
|
||||
for output in tx.vout:
|
||||
rv += output.nValue
|
||||
|
@ -158,7 +157,7 @@ class BTCInterface(CoinInterface):
|
|||
return CTxOut
|
||||
|
||||
@staticmethod
|
||||
def getExpectedSequence(lockType, lockVal):
|
||||
def getExpectedSequence(lockType: int, lockVal: int) -> int:
|
||||
assert (lockVal >= 1), 'Bad lockVal'
|
||||
if lockType == TxLockTypes.SEQUENCE_LOCK_BLOCKS:
|
||||
return lockVal
|
||||
|
@ -172,12 +171,16 @@ class BTCInterface(CoinInterface):
|
|||
raise ValueError('Unknown lock type')
|
||||
|
||||
@staticmethod
|
||||
def decodeSequence(lock_value):
|
||||
def decodeSequence(lock_value: int) -> int:
|
||||
# Return the raw value
|
||||
if lock_value & SEQUENCE_LOCKTIME_TYPE_FLAG:
|
||||
return (lock_value & SEQUENCE_LOCKTIME_MASK) << SEQUENCE_LOCKTIME_GRANULARITY
|
||||
return lock_value & SEQUENCE_LOCKTIME_MASK
|
||||
|
||||
@staticmethod
|
||||
def depth_spendable() -> int:
|
||||
return 0
|
||||
|
||||
def __init__(self, coin_settings, network, swap_client=None):
|
||||
super().__init__(network)
|
||||
self._rpc_host = coin_settings.get('rpchost', '127.0.0.1')
|
||||
|
@ -192,7 +195,8 @@ class BTCInterface(CoinInterface):
|
|||
self._log = self._sc.log if self._sc and self._sc.log else logging
|
||||
self._expect_seedid_hex = None
|
||||
|
||||
def using_segwit(self):
|
||||
def using_segwit(self) -> bool:
|
||||
# Using btc native segwit
|
||||
return self._use_segwit
|
||||
|
||||
def get_connection_type(self):
|
||||
|
@ -217,15 +221,12 @@ class BTCInterface(CoinInterface):
|
|||
def close_rpc(self, rpc_conn):
|
||||
rpc_conn.close()
|
||||
|
||||
def setConfTarget(self, new_conf_target):
|
||||
def setConfTarget(self, new_conf_target: int) -> None:
|
||||
ensure(new_conf_target >= 1 and new_conf_target < 33, 'Invalid conf_target value')
|
||||
self._conf_target = new_conf_target
|
||||
|
||||
def testDaemonRPC(self, with_wallet=True):
|
||||
if with_wallet:
|
||||
self.rpc_callback('getwalletinfo', [])
|
||||
else:
|
||||
self.rpc_callback('getblockchaininfo', [])
|
||||
def testDaemonRPC(self, with_wallet=True) -> None:
|
||||
self.rpc_callback('getwalletinfo' if with_wallet else 'getblockchaininfo')
|
||||
|
||||
def getDaemonVersion(self):
|
||||
return self.rpc_callback('getnetworkinfo')['version']
|
||||
|
@ -233,7 +234,7 @@ class BTCInterface(CoinInterface):
|
|||
def getBlockchainInfo(self):
|
||||
return self.rpc_callback('getblockchaininfo')
|
||||
|
||||
def getChainHeight(self):
|
||||
def getChainHeight(self) -> int:
|
||||
return self.rpc_callback('getblockcount')
|
||||
|
||||
def getMempoolTx(self, txid):
|
||||
|
@ -259,7 +260,7 @@ class BTCInterface(CoinInterface):
|
|||
last_block_header = prev_block_header
|
||||
raise ValueError(f'Block header not found at time: {time}')
|
||||
|
||||
def initialiseWallet(self, key_bytes):
|
||||
def initialiseWallet(self, key_bytes: bytes):
|
||||
key_wif = self.encodeKey(key_bytes)
|
||||
|
||||
self.rpc_callback('sethdseed', [True, key_wif])
|
||||
|
@ -270,10 +271,10 @@ class BTCInterface(CoinInterface):
|
|||
rv['locked'] = rv.get('unlocked_until', 1) <= 0
|
||||
return rv
|
||||
|
||||
def walletRestoreHeight(self):
|
||||
def walletRestoreHeight(self) -> int:
|
||||
return self._restore_height
|
||||
|
||||
def getWalletRestoreHeight(self):
|
||||
def getWalletRestoreHeight(self) -> int:
|
||||
start_time = self.rpc_callback('getwalletinfo')['keypoololdest']
|
||||
|
||||
blockchaininfo = self.rpc_callback('getblockchaininfo')
|
||||
|
@ -295,6 +296,7 @@ class BTCInterface(CoinInterface):
|
|||
block_hash = block_header['previousblockhash']
|
||||
finally:
|
||||
self.close_rpc(rpc_conn)
|
||||
raise ValueError('{} wallet restore height not found.'.format(self.coin_name()))
|
||||
|
||||
def getWalletSeedID(self) -> str:
|
||||
return self.rpc_callback('getwalletinfo')['hdseedid']
|
||||
|
@ -309,9 +311,11 @@ class BTCInterface(CoinInterface):
|
|||
args.append('bech32')
|
||||
return self.rpc_callback('getnewaddress', args)
|
||||
|
||||
def isAddressMine(self, address: str) -> bool:
|
||||
def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool:
|
||||
addr_info = self.rpc_callback('getaddressinfo', [address])
|
||||
return addr_info['ismine']
|
||||
if not or_watch_only:
|
||||
return addr_info['ismine']
|
||||
return addr_info['ismine'] or addr_info['iswatchonly']
|
||||
|
||||
def checkAddressMine(self, address: str) -> None:
|
||||
addr_info = self.rpc_callback('getaddressinfo', [address])
|
||||
|
@ -331,22 +335,22 @@ class BTCInterface(CoinInterface):
|
|||
except Exception:
|
||||
return self.rpc_callback('getnetworkinfo')['relayfee'], 'relayfee'
|
||||
|
||||
def isSegwitAddress(self, address):
|
||||
def isSegwitAddress(self, address: str) -> bool:
|
||||
return address.startswith(self.chainparams_network()['hrp'] + '1')
|
||||
|
||||
def decodeAddress(self, address):
|
||||
def decodeAddress(self, address: str) -> bytes:
|
||||
bech32_prefix = self.chainparams_network()['hrp']
|
||||
if len(bech32_prefix) > 0 and address.startswith(bech32_prefix + '1'):
|
||||
return bytes(segwit_addr.decode(bech32_prefix, address)[1])
|
||||
return decodeAddress(address)[1:]
|
||||
|
||||
def pubkey_to_segwit_address(self, pk):
|
||||
def pubkey_to_segwit_address(self, pk: bytes) -> str:
|
||||
bech32_prefix = self.chainparams_network()['hrp']
|
||||
version = 0
|
||||
pkh = hash160(pk)
|
||||
return segwit_addr.encode(bech32_prefix, version, pkh)
|
||||
|
||||
def pkh_to_address(self, pkh):
|
||||
def pkh_to_address(self, pkh: bytes) -> str:
|
||||
# pkh is hash160(pk)
|
||||
assert (len(pkh) == 20)
|
||||
prefix = self.chainparams_network()['pubkey_address']
|
||||
|
@ -354,26 +358,26 @@ class BTCInterface(CoinInterface):
|
|||
checksum = hashlib.sha256(hashlib.sha256(data).digest()).digest()
|
||||
return b58encode(data + checksum[0:4])
|
||||
|
||||
def sh_to_address(self, sh):
|
||||
def sh_to_address(self, sh: bytes) -> str:
|
||||
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: bytes) -> str:
|
||||
bech32_prefix = self.chainparams_network()['hrp']
|
||||
version = 0
|
||||
program = script[2:] # strip version and length
|
||||
return segwit_addr.encode(bech32_prefix, version, program)
|
||||
|
||||
def encodeScriptDest(self, script):
|
||||
def encodeScriptDest(self, script: bytes) -> str:
|
||||
return self.encode_p2wsh(script)
|
||||
|
||||
def encode_p2sh(self, script):
|
||||
def encode_p2sh(self, script: bytes) -> str:
|
||||
return pubkeyToAddress(self.chainparams_network()['script_address'], script)
|
||||
|
||||
def pubkey_to_address(self, pk):
|
||||
def pubkey_to_address(self, pk: bytes) -> str:
|
||||
assert (len(pk) == 33)
|
||||
return self.pkh_to_address(hash160(pk))
|
||||
|
||||
|
@ -383,31 +387,31 @@ class BTCInterface(CoinInterface):
|
|||
def getPubkey(self, privkey):
|
||||
return PublicKey.from_secret(privkey).format()
|
||||
|
||||
def getAddressHashFromKey(self, key):
|
||||
def getAddressHashFromKey(self, key) -> bytes:
|
||||
pk = self.getPubkey(key)
|
||||
return hash160(pk)
|
||||
|
||||
def getSeedHash(self, seed):
|
||||
return self.getAddressHashFromKey(seed)[::-1]
|
||||
|
||||
def verifyKey(self, k):
|
||||
def verifyKey(self, k: bytes) -> bool:
|
||||
i = b2i(k)
|
||||
return (i < ep.o and i > 0)
|
||||
|
||||
def verifyPubkey(self, pubkey_bytes):
|
||||
def verifyPubkey(self, pubkey_bytes: bytes) -> bool:
|
||||
return verify_secp256k1_point(pubkey_bytes)
|
||||
|
||||
def encodeKey(self, key_bytes):
|
||||
def encodeKey(self, key_bytes: bytes) -> str:
|
||||
wif_prefix = self.chainparams_network()['key_prefix']
|
||||
return toWIF(wif_prefix, key_bytes)
|
||||
|
||||
def encodePubkey(self, pk):
|
||||
def encodePubkey(self, pk: bytes) -> bytes:
|
||||
return pointToCPK(pk)
|
||||
|
||||
def encodeSegwitAddress(self, key_hash):
|
||||
def encodeSegwitAddress(self, key_hash: bytes) -> str:
|
||||
return segwit_addr.encode(self.chainparams_network()['hrp'], 0, key_hash)
|
||||
|
||||
def decodeSegwitAddress(self, addr):
|
||||
def decodeSegwitAddress(self, addr: str) -> bytes:
|
||||
return bytes(segwit_addr.decode(self.chainparams_network()['hrp'], addr)[1])
|
||||
|
||||
def decodePubkey(self, pke):
|
||||
|
@ -423,10 +427,10 @@ class BTCInterface(CoinInterface):
|
|||
def sumPubkeys(self, Ka, Kb):
|
||||
return PublicKey.combine_keys([PublicKey(Ka), PublicKey(Kb)]).format()
|
||||
|
||||
def getScriptForPubkeyHash(self, pkh):
|
||||
def getScriptForPubkeyHash(self, pkh: bytes) -> CScript:
|
||||
return CScript([OP_0, pkh])
|
||||
|
||||
def extractScriptLockScriptValues(self, script_bytes):
|
||||
def extractScriptLockScriptValues(self, script_bytes: bytes):
|
||||
script_len = len(script_bytes)
|
||||
ensure(script_len == 71, 'Bad script length')
|
||||
o = 0
|
||||
|
@ -444,7 +448,7 @@ class BTCInterface(CoinInterface):
|
|||
|
||||
return pk1, pk2
|
||||
|
||||
def createSCLockTx(self, value: int, script: bytearray, vkbv=None) -> bytes:
|
||||
def createSCLockTx(self, value: int, script: bytearray, vkbv: bytes = None) -> bytes:
|
||||
tx = CTransaction()
|
||||
tx.nVersion = self.txVersion()
|
||||
tx.vout.append(self.txoType()(value, self.getScriptDest(script)))
|
||||
|
@ -898,23 +902,28 @@ class BTCInterface(CoinInterface):
|
|||
inputs.append({'txid': i2h(pi.prevout.hash), 'vout': pi.prevout.n})
|
||||
self.rpc_callback('lockunspent', [True, inputs])
|
||||
|
||||
def signTxWithWallet(self, tx):
|
||||
def signTxWithWallet(self, tx: bytes) -> bytes:
|
||||
rv = self.rpc_callback('signrawtransactionwithwallet', [tx.hex()])
|
||||
return bytes.fromhex(rv['hex'])
|
||||
|
||||
def publishTx(self, tx):
|
||||
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
|
||||
key_wif = self.encodeKey(key)
|
||||
rv = self.rpc_callback('signrawtransactionwithkey', [tx.hex(), [key_wif, ]])
|
||||
return bytes.fromhex(rv['hex'])
|
||||
|
||||
def publishTx(self, tx: bytes):
|
||||
return self.rpc_callback('sendrawtransaction', [tx.hex()])
|
||||
|
||||
def encodeTx(self, tx):
|
||||
def encodeTx(self, tx) -> bytes:
|
||||
return tx.serialize()
|
||||
|
||||
def loadTx(self, tx_bytes) -> CTransaction:
|
||||
def loadTx(self, tx_bytes: bytes) -> CTransaction:
|
||||
# Load tx from bytes to internal representation
|
||||
tx = CTransaction()
|
||||
tx.deserialize(BytesIO(tx_bytes))
|
||||
return tx
|
||||
|
||||
def getTxid(self, tx):
|
||||
def getTxid(self, tx) -> bytes:
|
||||
if isinstance(tx, str):
|
||||
tx = bytes.fromhex(tx)
|
||||
if isinstance(tx, bytes):
|
||||
|
@ -928,16 +937,16 @@ class BTCInterface(CoinInterface):
|
|||
script_pk = self.getScriptDest(script)
|
||||
return findOutput(tx, script_pk)
|
||||
|
||||
def getPubkeyHash(self, K):
|
||||
return hash160(self.encodePubkey(K))
|
||||
def getPubkeyHash(self, K: bytes) -> bytes:
|
||||
return hash160(K)
|
||||
|
||||
def getScriptDest(self, script):
|
||||
return CScript([OP_0, hashlib.sha256(script).digest()])
|
||||
|
||||
def getScriptScriptSig(self, script):
|
||||
def getScriptScriptSig(self, script: bytes) -> bytes:
|
||||
return bytes()
|
||||
|
||||
def getPkDest(self, K):
|
||||
def getPkDest(self, K: bytes) -> bytearray:
|
||||
return self.getScriptForPubkeyHash(self.getPubkeyHash(K))
|
||||
|
||||
def scanTxOutset(self, dest):
|
||||
|
@ -980,26 +989,26 @@ class BTCInterface(CoinInterface):
|
|||
def createBLockTx(self, Kbs, output_amount):
|
||||
tx = CTransaction()
|
||||
tx.nVersion = self.txVersion()
|
||||
p2wpkh = self.getPkDest(Kbs)
|
||||
tx.vout.append(self.txoType()(output_amount, p2wpkh))
|
||||
p2wpkh_script_pk = self.getPkDest(Kbs)
|
||||
tx.vout.append(self.txoType()(output_amount, p2wpkh_script_pk))
|
||||
return tx.serialize()
|
||||
|
||||
def encodeSharedAddress(self, Kbv, Kbs):
|
||||
return self.pubkey_to_segwit_address(Kbs)
|
||||
|
||||
def publishBLockTx(self, Kbv, Kbs, output_amount, feerate, delay_for=10, unlock_time=0):
|
||||
def publishBLockTx(self, Kbv, Kbs, output_amount, feerate, delay_for: int = 10, unlock_time: int = 0) -> bytes:
|
||||
b_lock_tx = self.createBLockTx(Kbs, output_amount)
|
||||
|
||||
b_lock_tx = self.fundTx(b_lock_tx, feerate)
|
||||
b_lock_tx_id = self.getTxid(b_lock_tx)
|
||||
b_lock_tx = self.signTxWithWallet(b_lock_tx)
|
||||
|
||||
return self.publishTx(b_lock_tx)
|
||||
return bytes.fromhex(self.publishTx(b_lock_tx))
|
||||
|
||||
def recoverEncKey(self, esig, sig, K):
|
||||
return ecdsaotves_rec_enc_key(K, esig, sig[:-1]) # Strip sighash type
|
||||
|
||||
def getTxVSize(self, tx, add_bytes=0, add_witness_bytes=0):
|
||||
def getTxVSize(self, tx, add_bytes: int = 0, add_witness_bytes: int = 0) -> int:
|
||||
wsf = self.witnessScaleFactor()
|
||||
len_full = len(tx.serialize_with_witness()) + add_bytes + add_witness_bytes
|
||||
len_nwit = len(tx.serialize_without_witness()) + add_bytes
|
||||
|
@ -1007,10 +1016,13 @@ class BTCInterface(CoinInterface):
|
|||
return (weight + wsf - 1) // wsf
|
||||
|
||||
def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height, bid_sender):
|
||||
dest_address = self.pubkey_to_segwit_address(Kbs) if self.using_segwit() else self.pubkey_to_address(Kbs)
|
||||
return self.getLockTxHeight(None, dest_address, cb_swap_value, restore_height)
|
||||
|
||||
'''
|
||||
raw_dest = self.getPkDest(Kbs)
|
||||
|
||||
rv = self.scanTxOutset(raw_dest)
|
||||
print('scanTxOutset', dumpj(rv))
|
||||
|
||||
for utxo in rv['unspents']:
|
||||
if 'height' in utxo and utxo['height'] > 0 and rv['height'] - utxo['height'] > cb_block_confirmed:
|
||||
|
@ -1019,31 +1031,45 @@ class BTCInterface(CoinInterface):
|
|||
else:
|
||||
return {'txid': utxo['txid'], 'vout': utxo['vout'], 'amount': utxo['amount'], 'height': utxo['height']}
|
||||
return None
|
||||
'''
|
||||
|
||||
def waitForLockTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed):
|
||||
raw_dest = self.getPkDest(Kbs)
|
||||
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int) -> bytes:
|
||||
wtx = self.rpc_callback('gettransaction', [chain_b_lock_txid.hex(), ])
|
||||
lock_tx = self.loadTx(bytes.fromhex(wtx['hex']))
|
||||
|
||||
for i in range(20):
|
||||
time.sleep(1)
|
||||
rv = self.scanTxOutset(raw_dest)
|
||||
print('scanTxOutset', dumpj(rv))
|
||||
Kbs = self.getPubkey(kbs)
|
||||
script_pk = self.getPkDest(Kbs)
|
||||
locked_n = findOutput(lock_tx, script_pk)
|
||||
ensure(locked_n is not None, 'Output not found in tx')
|
||||
pkh_to = self.decodeAddress(address_to)
|
||||
|
||||
for utxo in rv['unspents']:
|
||||
if 'height' in utxo and utxo['height'] > 0 and rv['height'] - utxo['height'] > cb_block_confirmed:
|
||||
tx = CTransaction()
|
||||
tx.nVersion = self.txVersion()
|
||||
|
||||
if self.make_int(utxo['amount']) != cb_swap_value:
|
||||
self._log.warning('Found output to lock tx pubkey of incorrect value: %s', str(utxo['amount']))
|
||||
else:
|
||||
return True
|
||||
return False
|
||||
script_lock = self.getScriptForPubkeyHash(Kbs)
|
||||
chain_b_lock_txid_int = uint256_from_str(chain_b_lock_txid[::-1])
|
||||
|
||||
def spendBLockTx(self, chain_b_lock_txid, address_to, kbv, kbs, cb_swap_value, b_fee, restore_height):
|
||||
raise ValueError('TODO')
|
||||
tx.vin.append(CTxIn(COutPoint(chain_b_lock_txid_int, locked_n),
|
||||
nSequence=0,
|
||||
scriptSig=self.getScriptScriptSig(script_lock)))
|
||||
tx.vout.append(self.txoType()(cb_swap_value, self.getScriptForPubkeyHash(pkh_to)))
|
||||
|
||||
def importWatchOnlyAddress(self, address, label):
|
||||
witness_bytes = 109
|
||||
vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
|
||||
pay_fee = int(b_fee * vsize // 1000)
|
||||
tx.vout[0].nValue = cb_swap_value - pay_fee
|
||||
self._log.info('spendBLockTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.',
|
||||
chain_b_lock_txid.hex(), b_fee, vsize, pay_fee)
|
||||
|
||||
b_lock_spend_tx = tx.serialize()
|
||||
b_lock_spend_tx = self.signTxWithKey(b_lock_spend_tx, kbs)
|
||||
|
||||
return bytes.fromhex(self.publishTx(b_lock_spend_tx))
|
||||
|
||||
def importWatchOnlyAddress(self, address: str, label: str):
|
||||
self.rpc_callback('importaddress', [address, label, False])
|
||||
|
||||
def isWatchOnlyAddress(self, address):
|
||||
def isWatchOnlyAddress(self, address: str):
|
||||
addr_info = self.rpc_callback('getaddressinfo', [address])
|
||||
return addr_info['iswatchonly']
|
||||
|
||||
|
@ -1051,10 +1077,10 @@ class BTCInterface(CoinInterface):
|
|||
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: bool = False):
|
||||
# Add watchonly address and rescan if required
|
||||
|
||||
if not self.isWatchOnlyAddress(dest_address):
|
||||
if not self.isAddressMine(dest_address, or_watch_only=True):
|
||||
self.importWatchOnlyAddress(dest_address, 'bid')
|
||||
self._log.info('Imported watch-only addr: {}'.format(dest_address))
|
||||
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), rescan_from))
|
||||
|
@ -1263,7 +1289,6 @@ class BTCInterface(CoinInterface):
|
|||
|
||||
sign_for_addr = None
|
||||
for addr, value in unspent_addr.items():
|
||||
print('[rm]', value, amount_for)
|
||||
if value >= amount_for:
|
||||
sign_for_addr = addr
|
||||
break
|
||||
|
@ -1272,7 +1297,7 @@ class BTCInterface(CoinInterface):
|
|||
|
||||
self._log.debug('sign_for_addr %s', sign_for_addr)
|
||||
|
||||
if self._use_segwit: # TODO: Use isSegwitAddress when scantxoutset can use combo
|
||||
if self.using_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)
|
||||
|
@ -1286,7 +1311,7 @@ class BTCInterface(CoinInterface):
|
|||
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:
|
||||
if self.using_segwit():
|
||||
address = self.encodeSegwitAddress(decodeAddress(address)[1:])
|
||||
|
||||
return self.getUTXOBalance(address)
|
||||
|
@ -1334,6 +1359,19 @@ class BTCInterface(CoinInterface):
|
|||
def get_p2wsh_script_pubkey(self, script: bytearray) -> bytearray:
|
||||
return CScript([OP_0, hashlib.sha256(script).digest()])
|
||||
|
||||
def findTxnByHash(self, txid_hex: str):
|
||||
# Only works for wallet txns
|
||||
try:
|
||||
rv = self.rpc_callback('gettransaction', [txid_hex])
|
||||
except Exception as ex:
|
||||
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
|
||||
return None
|
||||
|
||||
if 'confirmations' in rv and rv['confirmations'] >= self.blocks_confirmed:
|
||||
return {'txid': txid_hex, 'amount': 0, 'height': rv['blockheight']}
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def testBTCInterface():
|
||||
print('TODO: testBTCInterface')
|
||||
|
|
|
@ -55,15 +55,17 @@ class FIROInterface(BTCInterface):
|
|||
addr_info = self.rpc_callback('validateaddress', [address])
|
||||
return addr_info['iswatchonly']
|
||||
|
||||
def isAddressMine(self, address):
|
||||
def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool:
|
||||
addr_info = self.rpc_callback('validateaddress', [address])
|
||||
return addr_info['ismine']
|
||||
if not or_watch_only:
|
||||
return addr_info['ismine']
|
||||
return addr_info['ismine'] or 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):
|
||||
if not self.isAddressMine(address, or_watch_only=True):
|
||||
# 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])
|
||||
|
@ -74,7 +76,7 @@ class FIROInterface(BTCInterface):
|
|||
# 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):
|
||||
if not self.isAddressMine(dest_address, or_watch_only=True):
|
||||
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))
|
||||
|
|
|
@ -638,7 +638,7 @@ class PARTInterfaceAnon(PARTInterface):
|
|||
def coin_name(self):
|
||||
return super().coin_name() + ' Anon'
|
||||
|
||||
def publishBLockTx(self, Kbv, Kbs, output_amount, feerate, delay_for=10, unlock_time=0):
|
||||
def publishBLockTx(self, Kbv, Kbs, output_amount, feerate, delay_for: int = 10, unlock_time: int = 0) -> bytes:
|
||||
sx_addr = self.formatStealthAddress(Kbv, Kbs)
|
||||
self._log.debug('sx_addr: {}'.format(sx_addr))
|
||||
|
||||
|
|
|
@ -260,7 +260,7 @@ class XMRInterface(CoinInterface):
|
|||
def encodeSharedAddress(self, Kbv, Kbs):
|
||||
return xmr_util.encode_address(Kbv, Kbs)
|
||||
|
||||
def publishBLockTx(self, Kbv, Kbs, output_amount, feerate, delay_for=10, unlock_time=0):
|
||||
def publishBLockTx(self, Kbv, Kbs, output_amount, feerate, delay_for: int = 10, unlock_time: int = 0) -> bytes:
|
||||
with self._mx_wallet:
|
||||
self.openWallet(self._wallet_filename)
|
||||
|
||||
|
@ -339,67 +339,6 @@ class XMRInterface(CoinInterface):
|
|||
rv = -1
|
||||
return rv
|
||||
|
||||
def waitForLockTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height):
|
||||
with self._mx_wallet:
|
||||
Kbv_enc = self.encodePubkey(self.pubkey(kbv))
|
||||
address_b58 = xmr_util.encode_address(Kbv_enc, self.encodePubkey(Kbs))
|
||||
|
||||
try:
|
||||
self.rpc_wallet_cb('close_wallet')
|
||||
except Exception as e:
|
||||
self._log.warning('close_wallet failed %s', str(e))
|
||||
|
||||
params = {
|
||||
'filename': address_b58,
|
||||
'address': address_b58,
|
||||
'viewkey': b2h(kbv[::-1]),
|
||||
'restore_height': restore_height,
|
||||
}
|
||||
self.createWallet(params)
|
||||
self.openWallet(address_b58)
|
||||
# For a while after opening the wallet rpc cmds return empty data
|
||||
|
||||
num_tries = 40
|
||||
for i in range(num_tries + 1):
|
||||
try:
|
||||
current_height = self.rpc_cb2('get_height')['height']
|
||||
print('current_height', current_height)
|
||||
except Exception as e:
|
||||
self._log.warning('rpc_cb failed %s', str(e))
|
||||
current_height = None # If the transfer is available it will be deep enough
|
||||
|
||||
# TODO: Make accepting current_height == None a user selectable option
|
||||
# Or look for all transfers and check height
|
||||
|
||||
params = {'transfer_type': 'available'}
|
||||
rv = self.rpc_wallet_cb('incoming_transfers', params)
|
||||
print('rv', rv)
|
||||
|
||||
if 'transfers' in rv:
|
||||
for transfer in rv['transfers']:
|
||||
if transfer['amount'] == cb_swap_value \
|
||||
and (current_height is None or current_height - transfer['block_height'] > cb_block_confirmed):
|
||||
return True
|
||||
|
||||
# TODO: Is it necessary to check the address?
|
||||
'''
|
||||
rv = self.rpc_wallet_cb('get_balance')
|
||||
print('get_balance', rv)
|
||||
|
||||
if 'per_subaddress' in rv:
|
||||
for sub_addr in rv['per_subaddress']:
|
||||
if sub_addr['address'] == address_b58:
|
||||
|
||||
'''
|
||||
|
||||
if i >= num_tries:
|
||||
raise ValueError('Balance not confirming on node')
|
||||
self._sc.delay_event.wait(1.0)
|
||||
if self._sc.delay_event.is_set():
|
||||
raise ValueError('Stopped')
|
||||
|
||||
return False
|
||||
|
||||
def findTxnByHash(self, txid):
|
||||
with self._mx_wallet:
|
||||
self.openWallet(self._wallet_filename)
|
||||
|
|
|
@ -28,6 +28,8 @@ from tests.basicswap.util import (
|
|||
from tests.basicswap.common import (
|
||||
wait_for_bid,
|
||||
wait_for_offer,
|
||||
wait_for_balance,
|
||||
wait_for_unspent,
|
||||
wait_for_none_active,
|
||||
BTC_BASE_RPC_PORT,
|
||||
)
|
||||
|
@ -52,8 +54,8 @@ logger = logging.getLogger()
|
|||
class BasicSwapTest(BaseTest):
|
||||
base_rpc_port = None
|
||||
|
||||
def getBalance(self, js_wallets):
|
||||
return float(js_wallets[self.test_coin_from.name]['balance']) + float(js_wallets[self.test_coin_from.name]['unconfirmed'])
|
||||
def getBalance(self, js_wallets, coin):
|
||||
return float(js_wallets[coin.name]['balance']) + float(js_wallets[coin.name]['unconfirmed'])
|
||||
|
||||
def callnoderpc(self, method, params=[], wallet=None, node_id=0):
|
||||
return callnoderpc(node_id, method, params, wallet, self.base_rpc_port)
|
||||
|
@ -252,22 +254,25 @@ class BasicSwapTest(BaseTest):
|
|||
self.callnoderpc('unloadwallet', [new_wallet_name])
|
||||
assert (addr == 'bcrt1qps7hnjd866e9ynxadgseprkc2l56m00dvwargr')
|
||||
|
||||
def test_01_full_swap(self):
|
||||
logging.info('---------- Test {} to XMR'.format(self.test_coin_from.name))
|
||||
def do_test_01_full_swap(self, coin_from, coin_to):
|
||||
logging.info('---------- Test {} to {}'.format(coin_from.name, coin_to.name))
|
||||
|
||||
swap_clients = self.swap_clients
|
||||
ci_from = swap_clients[0].ci(coin_from)
|
||||
ci_to = swap_clients[1].ci(coin_to)
|
||||
|
||||
js_0 = read_json_api(1800, 'wallets')
|
||||
node0_from_before = self.getBalance(js_0)
|
||||
node0_from_before = self.getBalance(js_0, coin_from)
|
||||
|
||||
js_1 = read_json_api(1801, 'wallets')
|
||||
node1_from_before = self.getBalance(js_1)
|
||||
node1_from_before = self.getBalance(js_1, coin_from)
|
||||
|
||||
js_0_xmr = read_json_api(1800, 'wallets/xmr')
|
||||
js_1_xmr = read_json_api(1801, 'wallets/xmr')
|
||||
js_0_to = read_json_api(1800, 'wallets/{}'.format(coin_to.name.lower()))
|
||||
js_1_to = read_json_api(1801, 'wallets/{}'.format(coin_to.name.lower()))
|
||||
|
||||
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)
|
||||
amt_swap = ci_from.make_int(random.uniform(0.1, 2.0), r=1)
|
||||
rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
|
||||
offer_id = swap_clients[0].postOffer(coin_from, coin_to, 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]
|
||||
|
@ -281,37 +286,55 @@ class BasicSwapTest(BaseTest):
|
|||
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))
|
||||
amount_from = float(ci_from.format_amount(amt_swap))
|
||||
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))
|
||||
node1_from_after = self.getBalance(js_1, coin_from)
|
||||
if coin_from is not Coins.PART: # TODO: staking
|
||||
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)
|
||||
node0_from_after = self.getBalance(js_0, coin_from)
|
||||
# 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')
|
||||
js_0_to_after = read_json_api(1800, 'wallets/{}'.format(coin_to.name.lower()))
|
||||
js_1_to_after = read_json_api(1801, 'wallets/{}'.format(coin_to.name.lower()))
|
||||
|
||||
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))
|
||||
amount_to_float = float(ci_to.format_amount(amount_to))
|
||||
node1_to_after = float(js_1_to_after['unconfirmed']) + float(js_1_to_after['balance'])
|
||||
node1_to_before = float(js_1_to['unconfirmed']) + float(js_1_to['balance'])
|
||||
if False: # TODO: set stakeaddress and xmr rewards to non wallet addresses
|
||||
assert (node1_to_after < node1_to_before - amount_to_float)
|
||||
|
||||
def test_01_full_swap(self):
|
||||
if not self.has_segwit:
|
||||
return
|
||||
self.do_test_01_full_swap(self.test_coin_from, Coins.XMR)
|
||||
|
||||
def test_01_full_swap_to_part(self):
|
||||
if not self.has_segwit:
|
||||
return
|
||||
self.do_test_01_full_swap(self.test_coin_from, Coins.PART)
|
||||
|
||||
def test_01_full_swap_from_part(self):
|
||||
self.do_test_01_full_swap(Coins.PART, self.test_coin_from)
|
||||
|
||||
def do_test_02_leader_recover_a_lock_tx(self, coin_from, coin_to):
|
||||
logging.info('---------- Test {} to {} leader recovers coin a lock tx'.format(coin_from.name, coin_to.name))
|
||||
|
||||
def test_02_leader_recover_a_lock_tx(self):
|
||||
logging.info('---------- Test {} to XMR leader recovers coin a lock tx'.format(self.test_coin_from.name))
|
||||
swap_clients = self.swap_clients
|
||||
ci_from = swap_clients[0].ci(coin_from)
|
||||
ci_to = swap_clients[0].ci(coin_to)
|
||||
|
||||
js_w0_before = read_json_api(1800, 'wallets')
|
||||
node0_from_before = self.getBalance(js_w0_before)
|
||||
node0_from_before = self.getBalance(js_w0_before, coin_from)
|
||||
|
||||
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)
|
||||
amt_swap = ci_from.make_int(random.uniform(0.1, 2.0), r=1)
|
||||
rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
|
||||
offer_id = swap_clients[0].postOffer(
|
||||
self.test_coin_from, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP,
|
||||
coin_from, coin_to, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP,
|
||||
lock_type=TxLockTypes.SEQUENCE_LOCK_BLOCKS, lock_value=32)
|
||||
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
||||
offer = swap_clients[1].getOffer(offer_id)
|
||||
|
@ -331,28 +354,43 @@ class BasicSwapTest(BaseTest):
|
|||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, [BidStates.BID_STALLED_FOR_TEST, BidStates.XMR_SWAP_FAILED], sent=True)
|
||||
|
||||
js_w0_after = read_json_api(1800, 'wallets')
|
||||
node0_from_after = self.getBalance(js_w0_after)
|
||||
node0_from_after = self.getBalance(js_w0_after, coin_from)
|
||||
|
||||
# TODO: Discard block rewards
|
||||
# assert (node0_from_before - node0_from_after < 0.02)
|
||||
|
||||
def test_03_follower_recover_a_lock_tx(self):
|
||||
logging.info('---------- Test {} to XMR follower recovers coin a lock tx'.format(self.test_coin_from.name))
|
||||
def test_02_leader_recover_a_lock_tx(self):
|
||||
if not self.has_segwit:
|
||||
return
|
||||
self.do_test_02_leader_recover_a_lock_tx(self.test_coin_from, Coins.XMR)
|
||||
|
||||
def test_02_leader_recover_a_lock_tx_to_part(self):
|
||||
if not self.has_segwit:
|
||||
return
|
||||
self.do_test_02_leader_recover_a_lock_tx(self.test_coin_from, Coins.PART)
|
||||
|
||||
def test_02_leader_recover_a_lock_tx_from_part(self):
|
||||
self.do_test_02_leader_recover_a_lock_tx(Coins.PART, self.test_coin_from)
|
||||
|
||||
def do_test_03_follower_recover_a_lock_tx(self, coin_from, coin_to):
|
||||
logging.info('---------- Test {} to {} follower recovers coin a lock tx'.format(coin_from.name, coin_to.name))
|
||||
|
||||
swap_clients = self.swap_clients
|
||||
ci_from = swap_clients[0].ci(coin_from)
|
||||
ci_to = swap_clients[0].ci(coin_to)
|
||||
|
||||
js_w0_before = read_json_api(1800, 'wallets')
|
||||
js_w1_before = read_json_api(1801, 'wallets')
|
||||
|
||||
amt_swap = make_int(random.uniform(0.1, 2.0), scale=8, r=1)
|
||||
rate_swap = make_int(random.uniform(0.2, 20.0), scale=12, r=1)
|
||||
amt_swap = ci_from.make_int(random.uniform(0.1, 2.0), r=1)
|
||||
rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
|
||||
offer_id = swap_clients[0].postOffer(
|
||||
self.test_coin_from, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP,
|
||||
coin_from, coin_to, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP,
|
||||
lock_type=TxLockTypes.SEQUENCE_LOCK_BLOCKS, lock_value=32)
|
||||
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
||||
offer = swap_clients[1].getOffer(offer_id)
|
||||
|
||||
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
|
||||
|
||||
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
|
||||
|
@ -368,8 +406,8 @@ class BasicSwapTest(BaseTest):
|
|||
|
||||
js_w1_after = read_json_api(1801, 'wallets')
|
||||
|
||||
node1_from_before = self.getBalance(js_w1_before)
|
||||
node1_from_after = self.getBalance(js_w1_after)
|
||||
node1_from_before = self.getBalance(js_w1_before, coin_from)
|
||||
node1_from_after = self.getBalance(js_w1_after, coin_from)
|
||||
amount_from = float(format_amount(amt_swap, 8))
|
||||
# TODO: Discard block rewards
|
||||
# assert (node1_from_after - node1_from_before > (amount_from - 0.02))
|
||||
|
@ -379,24 +417,38 @@ class BasicSwapTest(BaseTest):
|
|||
wait_for_none_active(test_delay_event, 1800)
|
||||
wait_for_none_active(test_delay_event, 1801)
|
||||
|
||||
def test_04_follower_recover_b_lock_tx(self):
|
||||
logging.info('---------- Test {} to XMR follower recovers coin b lock tx'.format(self.test_coin_from.name))
|
||||
def test_03_follower_recover_a_lock_tx(self):
|
||||
if not self.has_segwit:
|
||||
return
|
||||
self.do_test_03_follower_recover_a_lock_tx(self.test_coin_from, Coins.XMR)
|
||||
|
||||
def test_03_follower_recover_a_lock_tx_to_part(self):
|
||||
if not self.has_segwit:
|
||||
return
|
||||
self.do_test_03_follower_recover_a_lock_tx(self.test_coin_from, Coins.PART)
|
||||
|
||||
def test_03_follower_recover_a_lock_tx_from_part(self):
|
||||
self.do_test_03_follower_recover_a_lock_tx(Coins.PART, self.test_coin_from)
|
||||
|
||||
def do_test_04_follower_recover_b_lock_tx(self, coin_from, coin_to):
|
||||
logging.info('---------- Test {} to {} follower recovers coin b lock tx'.format(coin_from.name, coin_to.name))
|
||||
|
||||
swap_clients = self.swap_clients
|
||||
ci_from = swap_clients[0].ci(coin_from)
|
||||
ci_to = swap_clients[0].ci(coin_to)
|
||||
|
||||
js_w0_before = read_json_api(1800, 'wallets')
|
||||
js_w1_before = read_json_api(1801, 'wallets')
|
||||
|
||||
amt_swap = make_int(random.uniform(0.1, 2.0), scale=8, r=1)
|
||||
rate_swap = make_int(random.uniform(0.2, 20.0), scale=12, r=1)
|
||||
amt_swap = ci_from.make_int(random.uniform(0.1, 2.0), r=1)
|
||||
rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
|
||||
offer_id = swap_clients[0].postOffer(
|
||||
self.test_coin_from, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP,
|
||||
coin_from, coin_to, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP,
|
||||
lock_type=TxLockTypes.SEQUENCE_LOCK_BLOCKS, lock_value=32)
|
||||
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
||||
offer = swap_clients[1].getOffer(offer_id)
|
||||
|
||||
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
|
||||
|
||||
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
|
||||
|
@ -411,28 +463,127 @@ class BasicSwapTest(BaseTest):
|
|||
js_w0_after = read_json_api(1800, 'wallets')
|
||||
js_w1_after = read_json_api(1801, 'wallets')
|
||||
|
||||
node0_from_before = self.getBalance(js_w0_before)
|
||||
node0_from_after = self.getBalance(js_w0_after)
|
||||
|
||||
node0_from_before = self.getBalance(js_w0_before, coin_from)
|
||||
node0_from_after = self.getBalance(js_w0_after, coin_from)
|
||||
logging.info('End coin_from balance {}, diff {}'.format(node0_from_after, node0_from_after - node0_from_before))
|
||||
# TODO: Discard block rewards
|
||||
# assert (node0_from_before - node0_from_after < 0.02)
|
||||
|
||||
node1_xmr_before = self.getXmrBalance(js_w1_before)
|
||||
node1_xmr_after = self.getXmrBalance(js_w1_after)
|
||||
assert (node1_xmr_before - node1_xmr_after < 0.02)
|
||||
node1_coin_to_before = self.getBalance(js_w1_before, coin_to)
|
||||
node1_coin_to_after = self.getBalance(js_w1_after, coin_to)
|
||||
logging.info('End coin_to balance {}, diff {}'.format(node1_coin_to_after, node1_coin_to_after - node1_coin_to_before))
|
||||
assert (node1_coin_to_before - node1_coin_to_after < 0.02)
|
||||
|
||||
def test_04_follower_recover_b_lock_tx(self):
|
||||
if not self.has_segwit:
|
||||
return
|
||||
self.do_test_04_follower_recover_b_lock_tx(self.test_coin_from, Coins.XMR)
|
||||
|
||||
def test_04_follower_recover_b_lock_tx_to_part(self):
|
||||
if not self.has_segwit:
|
||||
return
|
||||
self.do_test_04_follower_recover_b_lock_tx(self.test_coin_from, Coins.PART)
|
||||
|
||||
def test_04_follower_recover_b_lock_tx_from_part(self):
|
||||
self.do_test_04_follower_recover_b_lock_tx(Coins.PART, self.test_coin_from)
|
||||
|
||||
def do_test_05_self_bid(self, coin_from, coin_to):
|
||||
logging.info('---------- Test {} to {} same client'.format(coin_from.name, coin_to.name))
|
||||
|
||||
def test_05_self_bid(self):
|
||||
logging.info('---------- Test {} to XMR same client'.format(self.test_coin_from.name))
|
||||
swap_clients = self.swap_clients
|
||||
ci_to = swap_clients[0].ci(coin_to)
|
||||
|
||||
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)
|
||||
rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
|
||||
|
||||
offer_id = swap_clients[1].postOffer(self.test_coin_from, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP, auto_accept_bids=True)
|
||||
offer_id = swap_clients[1].postOffer(coin_from, coin_to, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP, auto_accept_bids=True)
|
||||
bid_id = swap_clients[1].postXmrBid(offer_id, amt_swap)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, wait_for=180)
|
||||
|
||||
def test_05_self_bid(self):
|
||||
if not self.has_segwit:
|
||||
return
|
||||
self.do_test_05_self_bid(self.test_coin_from, Coins.XMR)
|
||||
|
||||
def test_05_self_bid_to_part(self):
|
||||
if not self.has_segwit:
|
||||
return
|
||||
self.do_test_05_self_bid(self.test_coin_from, Coins.PART)
|
||||
|
||||
def test_05_self_bid_from_part(self):
|
||||
self.do_test_05_self_bid(Coins.PART, self.test_coin_from)
|
||||
|
||||
def test_06_preselect_inputs(self):
|
||||
tla_from = self.test_coin_from.name
|
||||
logging.info('---------- Test {} Preselected inputs'.format(tla_from))
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
# Prepare balance
|
||||
js_w2 = read_json_api(1802, 'wallets')
|
||||
if float(js_w2[tla_from]['balance']) < 100.0:
|
||||
post_json = {
|
||||
'value': 100,
|
||||
'address': js_w2[tla_from]['deposit_address'],
|
||||
'subfee': False,
|
||||
}
|
||||
json_rv = read_json_api(1800, 'wallets/{}/withdraw'.format(tla_from.lower()), post_json)
|
||||
assert (len(json_rv['txid']) == 64)
|
||||
wait_for_balance(test_delay_event, 'http://127.0.0.1:1802/json/wallets/{}'.format(tla_from.lower()), 'balance', 100.0)
|
||||
|
||||
js_w2 = read_json_api(1802, 'wallets')
|
||||
assert (float(js_w2[tla_from]['balance']) >= 100.0)
|
||||
|
||||
js_w2 = read_json_api(1802, 'wallets')
|
||||
post_json = {
|
||||
'value': float(js_w2[tla_from]['balance']),
|
||||
'address': read_json_api(1802, 'wallets/{}/nextdepositaddr'.format(tla_from.lower())),
|
||||
'subfee': True,
|
||||
}
|
||||
json_rv = read_json_api(1802, 'wallets/{}/withdraw'.format(tla_from.lower()), post_json)
|
||||
wait_for_balance(test_delay_event, 'http://127.0.0.1:1802/json/wallets/{}'.format(tla_from.lower()), 'balance', 10.0)
|
||||
assert (len(json_rv['txid']) == 64)
|
||||
|
||||
# Create prefunded ITX
|
||||
ci = swap_clients[2].ci(self.test_coin_from)
|
||||
ci_to = swap_clients[2].ci(Coins.XMR)
|
||||
pi = swap_clients[2].pi(SwapTypes.XMR_SWAP)
|
||||
js_w2 = read_json_api(1802, 'wallets')
|
||||
swap_value = ci.make_int(js_w2[tla_from]['balance'])
|
||||
assert (swap_value > ci.make_int(95))
|
||||
|
||||
itx = pi.getFundedInitiateTxTemplate(ci, swap_value, True)
|
||||
itx_decoded = ci.describeTx(itx.hex())
|
||||
value_after_subfee = ci.make_int(itx_decoded['vout'][0]['value'])
|
||||
assert (value_after_subfee < swap_value)
|
||||
swap_value = value_after_subfee
|
||||
wait_for_unspent(test_delay_event, ci, swap_value)
|
||||
|
||||
extra_options = {'prefunded_itx': itx}
|
||||
rate_swap = ci_to.make_int(random.uniform(0.2, 20.0))
|
||||
offer_id = swap_clients[2].postOffer(self.test_coin_from, Coins.XMR, swap_value, rate_swap, swap_value, SwapTypes.XMR_SWAP, extra_options=extra_options)
|
||||
|
||||
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
||||
offer = swap_clients[1].getOffer(offer_id)
|
||||
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[2], bid_id, BidStates.BID_RECEIVED)
|
||||
swap_clients[2].acceptBid(bid_id)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[2], bid_id, BidStates.SWAP_COMPLETED, wait_for=120)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=120)
|
||||
|
||||
# Verify expected inputs were used
|
||||
bid, _, _, _, _ = swap_clients[2].getXmrBidAndOffer(bid_id)
|
||||
assert (bid.xmr_a_lock_tx)
|
||||
wtx = ci.rpc_callback('gettransaction', [bid.xmr_a_lock_tx.txid.hex(),])
|
||||
itx_after = ci.describeTx(wtx['hex'])
|
||||
assert (len(itx_after['vin']) == len(itx_decoded['vin']))
|
||||
for i, txin in enumerate(itx_decoded['vin']):
|
||||
txin_after = itx_after['vin'][i]
|
||||
assert (txin['txid'] == txin_after['txid'])
|
||||
assert (txin['vout'] == txin_after['vout'])
|
||||
|
||||
|
||||
class TestBTC(BasicSwapTest):
|
||||
__test__ = True
|
||||
|
|
|
@ -318,6 +318,7 @@ class BaseTest(unittest.TestCase):
|
|||
|
||||
start_ltc_nodes = False
|
||||
start_xmr_nodes = True
|
||||
has_segwit = True
|
||||
|
||||
xmr_addr = None
|
||||
btc_addr = None
|
||||
|
|
Loading…
Reference in a new issue