mirror of
https://github.com/basicswap/basicswap.git
synced 2024-12-23 03:49:25 +00:00
NMC and CLTV, abs lock values still to be verified
This commit is contained in:
parent
222fbb492f
commit
307b8ab8bf
6 changed files with 638 additions and 45 deletions
|
@ -126,6 +126,9 @@ class TxTypes(IntEnum): # For PooledAddress
|
|||
|
||||
SEQUENCE_LOCK_BLOCKS = 1
|
||||
SEQUENCE_LOCK_TIME = 2
|
||||
ABS_LOCK_BLOCKS = 3
|
||||
ABS_LOCK_TIME = 4
|
||||
|
||||
SEQUENCE_LOCKTIME_GRANULARITY = 9 # 512 seconds
|
||||
SEQUENCE_LOCKTIME_TYPE_FLAG = (1 << 22)
|
||||
SEQUENCE_LOCKTIME_MASK = 0x0000ffff
|
||||
|
@ -183,6 +186,10 @@ def getLockName(lock_type):
|
|||
return 'Sequence lock, blocks'
|
||||
if lock_type == SEQUENCE_LOCK_TIME:
|
||||
return 'Sequence lock, time'
|
||||
if lock_type == ABS_LOCK_BLOCKS:
|
||||
return 'blocks'
|
||||
if lock_type == ABS_LOCK_TIME:
|
||||
return 'time'
|
||||
|
||||
|
||||
def getExpectedSequence(lockType, lockVal, coin_type):
|
||||
|
@ -206,7 +213,7 @@ def decodeSequence(lock_value):
|
|||
return lock_value & SEQUENCE_LOCKTIME_MASK
|
||||
|
||||
|
||||
def buildContractScriptCSV(sequence, secret_hash, pkh_redeem, pkh_refund):
|
||||
def buildContractScript(lock_val, secret_hash, pkh_redeem, pkh_refund, op_lock=OpCodes.OP_CHECKSEQUENCEVERIFY):
|
||||
script = bytearray([
|
||||
OpCodes.OP_IF,
|
||||
OpCodes.OP_SIZE,
|
||||
|
@ -222,9 +229,9 @@ def buildContractScriptCSV(sequence, secret_hash, pkh_redeem, pkh_refund):
|
|||
0x14]) \
|
||||
+ pkh_redeem \
|
||||
+ bytearray([OpCodes.OP_ELSE, ]) \
|
||||
+ SerialiseNum(sequence) \
|
||||
+ SerialiseNum(lock_val) \
|
||||
+ bytearray([
|
||||
OpCodes.OP_CHECKSEQUENCEVERIFY,
|
||||
op_lock,
|
||||
OpCodes.OP_DROP,
|
||||
OpCodes.OP_DUP,
|
||||
OpCodes.OP_HASH160,
|
||||
|
@ -590,6 +597,7 @@ class BasicSwap():
|
|||
'watched_outputs': [],
|
||||
'last_height_checked': last_height_checked,
|
||||
'use_segwit': use_segwit,
|
||||
'use_csv': chain_client_settings.get('use_csv', True),
|
||||
}
|
||||
|
||||
def start(self):
|
||||
|
@ -710,8 +718,16 @@ class BasicSwap():
|
|||
def validateOfferLockValue(self, coin_from, coin_to, lock_type, lock_value):
|
||||
if lock_type == OfferMessage.SEQUENCE_LOCK_TIME:
|
||||
assert(lock_value >= 2 * 60 * 60 and lock_value <= 96 * 60 * 60), 'Invalid lock_value time'
|
||||
assert(self.coin_clients[coin_from]['use_csv'] and self.coin_clients[coin_to]['use_csv']), 'Both coins need CSV activated.'
|
||||
elif lock_type == OfferMessage.SEQUENCE_LOCK_BLOCKS:
|
||||
assert(lock_value >= 5 and lock_value <= 1000), 'Invalid lock_value blocks'
|
||||
assert(self.coin_clients[coin_from]['use_csv'] and self.coin_clients[coin_to]['use_csv']), 'Both coins need CSV activated.'
|
||||
elif lock_type == ABS_LOCK_TIME:
|
||||
# TODO: range?
|
||||
assert(lock_value >= 4 * 60 * 60 and lock_value <= 96 * 60 * 60), 'Invalid lock_value time'
|
||||
elif lock_type == ABS_LOCK_BLOCKS:
|
||||
# TODO: range?
|
||||
assert(lock_value >= 10 and lock_value <= 1000), 'Invalid lock_value blocks'
|
||||
else:
|
||||
raise ValueError('Unknown locktype')
|
||||
|
||||
|
@ -872,7 +888,7 @@ class BasicSwap():
|
|||
def getReceiveAddressForCoin(self, coin_type):
|
||||
if coin_type == Coins.PART:
|
||||
new_addr = self.callcoinrpc(Coins.PART, 'getnewaddress')
|
||||
elif coin_type == Coins.LTC or coin_type == Coins.BTC:
|
||||
elif coin_type == Coins.LTC or coin_type == Coins.BTC or coin_type == Coins.NMC:
|
||||
args = []
|
||||
if self.coin_clients[coin_type]['use_segwit']:
|
||||
args = ['swap_receive', 'bech32']
|
||||
|
@ -1123,15 +1139,22 @@ class BasicSwap():
|
|||
coin_from = Coins(offer.coin_from)
|
||||
bid_date = dt.datetime.fromtimestamp(bid.created_at).date()
|
||||
|
||||
# TODO: Use CLTV for coins without CSV
|
||||
sequence = getExpectedSequence(offer.lock_type, offer.lock_value, coin_from)
|
||||
secret = self.getContractSecret(bid_date, bid.contract_count)
|
||||
secret_hash = hashlib.sha256(secret).digest()
|
||||
|
||||
pubkey_refund = self.getContractPubkey(bid_date, bid.contract_count)
|
||||
pkhash_refund = getKeyID(pubkey_refund)
|
||||
|
||||
script = buildContractScriptCSV(sequence, secret_hash, bid.pkhash_buyer, pkhash_refund)
|
||||
if offer.lock_type < ABS_LOCK_BLOCKS:
|
||||
sequence = getExpectedSequence(offer.lock_type, offer.lock_value, coin_from)
|
||||
script = buildContractScript(sequence, secret_hash, bid.pkhash_buyer, pkhash_refund)
|
||||
else:
|
||||
if offer.lock_type == ABS_LOCK_BLOCKS:
|
||||
lock_value = self.callcoinrpc(coin_from, 'getblockchaininfo')['blocks'] + offer.lock_value
|
||||
else:
|
||||
lock_value = int(time.time()) + offer.lock_value
|
||||
logging.debug('initiate %s lock_value %d %d', coin_from, offer.lock_value, lock_value)
|
||||
script = buildContractScript(lock_value, secret_hash, bid.pkhash_buyer, pkhash_refund, OpCodes.OP_CHECKLOCKTIMEVERIFY)
|
||||
|
||||
p2sh = self.callcoinrpc(Coins.PART, 'decodescript', [script.hex()])['p2sh']
|
||||
|
||||
|
@ -1141,7 +1164,7 @@ class BasicSwap():
|
|||
txn = self.createInitiateTxn(coin_from, bid_id, bid)
|
||||
|
||||
# Store the signed refund txn in case wallet is locked when refund is possible
|
||||
refund_txn = self.createRefundTxn(coin_from, txn, bid, script)
|
||||
refund_txn = self.createRefundTxn(coin_from, txn, offer, bid, script)
|
||||
bid.initiate_txn_refund = bytes.fromhex(refund_txn)
|
||||
|
||||
txid = self.submitTxn(coin_from, txn)
|
||||
|
@ -1153,7 +1176,7 @@ class BasicSwap():
|
|||
txid = self.submitTxn(coin_from, bid.initiate_txn_refund.hex())
|
||||
self.log.error('Submit refund_txn unexpectedly worked: ' + txid)
|
||||
except Exception as e:
|
||||
if 'non-BIP68-final' not in str(e):
|
||||
if 'non-BIP68-final' not in str(e) and 'non-final' not in str(e):
|
||||
self.log.error('Submit refund_txn unexpected error' + str(e))
|
||||
|
||||
if txid is not None:
|
||||
|
@ -1269,14 +1292,22 @@ class BasicSwap():
|
|||
|
||||
bid_date = dt.datetime.fromtimestamp(bid.created_at).date()
|
||||
|
||||
# Participate txn is locked for half the time of the initiate txn
|
||||
lock_value = decodeSequence(offer.lock_value) // 2
|
||||
sequence = getExpectedSequence(offer.lock_type, lock_value, coin_to)
|
||||
|
||||
secret_hash = extractScriptSecretHash(bid.initiate_script)
|
||||
pkhash_seller = bid.pkhash_seller
|
||||
pkhash_buyer_refund = bid.pkhash_buyer
|
||||
bid.participate_script = buildContractScriptCSV(sequence, secret_hash, pkhash_seller, pkhash_buyer_refund)
|
||||
|
||||
# Participate txn is locked for half the time of the initiate txn
|
||||
lock_value = offer.lock_value // 2
|
||||
if offer.lock_type < ABS_LOCK_BLOCKS:
|
||||
sequence = getExpectedSequence(offer.lock_type, lock_value, coin_to)
|
||||
bid.participate_script = buildContractScript(sequence, secret_hash, pkhash_seller, pkhash_buyer_refund)
|
||||
else:
|
||||
if offer.lock_type == ABS_LOCK_BLOCKS:
|
||||
contract_lock_value = self.callcoinrpc(coin_to, 'getblockchaininfo')['blocks'] + lock_value
|
||||
else:
|
||||
contract_lock_value = int(time.time()) + lock_value
|
||||
logging.debug('participate %s lock_value %d %d', coin_to, lock_value, contract_lock_value)
|
||||
bid.participate_script = buildContractScript(contract_lock_value, secret_hash, pkhash_seller, pkhash_buyer_refund, OpCodes.OP_CHECKLOCKTIMEVERIFY)
|
||||
|
||||
def createParticipateTxn(self, bid_id, bid, offer):
|
||||
self.log.debug('createParticipateTxn')
|
||||
|
@ -1302,7 +1333,7 @@ class BasicSwap():
|
|||
|
||||
txn_signed = self.callcoinrpc(coin_to, 'signrawtransactionwithwallet', [txn_funded])['hex']
|
||||
|
||||
refund_txn = self.createRefundTxn(coin_to, txn_signed, bid, bid.participate_script, tx_type=TxTypes.PTX_REFUND)
|
||||
refund_txn = self.createRefundTxn(coin_to, txn_signed, offer, bid, bid.participate_script, tx_type=TxTypes.PTX_REFUND)
|
||||
bid.participate_txn_refund = bytes.fromhex(refund_txn)
|
||||
|
||||
chain_height = self.callcoinrpc(coin_to, 'getblockchaininfo')['blocks']
|
||||
|
@ -1433,7 +1464,7 @@ class BasicSwap():
|
|||
|
||||
return redeem_txn
|
||||
|
||||
def createRefundTxn(self, coin_type, txn, bid, txn_script, addr_refund_out=None, tx_type=TxTypes.ITX_REFUND):
|
||||
def createRefundTxn(self, coin_type, txn, offer, bid, txn_script, addr_refund_out=None, tx_type=TxTypes.ITX_REFUND):
|
||||
self.log.debug('createRefundTxn')
|
||||
if self.coin_clients[coin_type]['connection_type'] != 'rpc':
|
||||
return None
|
||||
|
@ -1459,7 +1490,11 @@ class BasicSwap():
|
|||
'redeemScript': txn_script.hex(),
|
||||
'amount': prev_amount}
|
||||
|
||||
sequence = DeserialiseNum(txn_script, 64)
|
||||
lock_value = DeserialiseNum(txn_script, 64)
|
||||
if offer.lock_type < ABS_LOCK_BLOCKS:
|
||||
sequence = lock_value
|
||||
else:
|
||||
sequence = 1
|
||||
prevout_s = ' in={}:{}:{}'.format(txjs['txid'], vout, sequence)
|
||||
|
||||
fee_rate = self.getFeeRateForCoin(coin_type)
|
||||
|
@ -1488,6 +1523,9 @@ class BasicSwap():
|
|||
else:
|
||||
refund_txn = self.calltx('-btcmode -create nversion=2' + prevout_s + output_to)
|
||||
|
||||
if offer.lock_type == ABS_LOCK_BLOCKS or offer.lock_type == ABS_LOCK_TIME:
|
||||
refund_txn = self.calltx('{} locktime={}'.format(refund_txn, lock_value))
|
||||
|
||||
options = {}
|
||||
if self.coin_clients[coin_type]['use_segwit']:
|
||||
options['force_segwit'] = True
|
||||
|
@ -1691,7 +1729,7 @@ class BasicSwap():
|
|||
self.initiateTxnConfirmed(bid_id, bid, offer)
|
||||
save_bid = True
|
||||
|
||||
# Bid times out if buyer doesn't see tx in chain within
|
||||
# Bid times out if buyer doesn't see tx in chain within INITIATE_TX_TIMEOUT seconds
|
||||
if bid.state_time + INITIATE_TX_TIMEOUT < int(time.time()):
|
||||
self.log.info('Swap timed out waiting for initiate tx for bid %s', bid_id.hex())
|
||||
bid.setState(BidStates.SWAP_TIMEDOUT)
|
||||
|
@ -1757,7 +1795,7 @@ class BasicSwap():
|
|||
self.log.debug('Submitted initiate refund txn %s to %s chain for bid %s', txid, chainparams[coin_from]['name'], bid_id.hex())
|
||||
# State will update when spend is detected
|
||||
except Exception as e:
|
||||
if 'non-BIP68-final (code 64)' not in str(e):
|
||||
if 'non-BIP68-final (code 64)' not in str(e) and 'non-final' not in str(e):
|
||||
self.log.warning('Error trying to submit initiate refund txn: %s', str(e))
|
||||
if (bid.participate_txn_state == TxStates.TX_SENT or bid.participate_txn_state == TxStates.TX_CONFIRMED) \
|
||||
and bid.participate_txn_refund is not None:
|
||||
|
@ -1766,7 +1804,7 @@ class BasicSwap():
|
|||
self.log.debug('Submitted participate refund txn %s to %s chain for bid %s', txid, chainparams[coin_to]['name'], bid_id.hex())
|
||||
# State will update when spend is detected
|
||||
except Exception as e:
|
||||
if 'non-BIP68-final (code 64)' not in str(e):
|
||||
if 'non-BIP68-final (code 64)' not in str(e) and 'non-final' not in str(e):
|
||||
self.log.warning('Error trying to submit participate refund txn: %s', str(e))
|
||||
return False # Bid is still active
|
||||
|
||||
|
@ -2090,19 +2128,11 @@ class BasicSwap():
|
|||
|
||||
self.log.debug('for bid %s', bid_accept_data.bid_msg_id.hex())
|
||||
|
||||
decoded_script = self.callcoinrpc(Coins.PART, 'decodescript', [bid_accept_data.contract_script.hex()])
|
||||
|
||||
# TODO: Verify script without decoding?
|
||||
prog = re.compile('OP_IF OP_SIZE 32 OP_EQUALVERIFY OP_SHA256 (\w+) OP_EQUALVERIFY OP_DUP OP_HASH160 (\w+) OP_ELSE (\d+) OP_CHECKSEQUENCEVERIFY OP_DROP OP_DUP OP_HASH160 (\w+) OP_ENDIF OP_EQUALVERIFY OP_CHECKSIG')
|
||||
rr = prog.match(decoded_script['asm'])
|
||||
if not rr:
|
||||
raise ValueError('Bad script')
|
||||
scriptvalues = rr.groups()
|
||||
|
||||
bid_id = bid_accept_data.bid_msg_id
|
||||
bid, offer = self.getBidAndOffer(bid_id)
|
||||
assert(bid is not None and bid.was_sent is True), 'Unknown bidid'
|
||||
assert(offer), 'Offer not found ' + bid.offer_id.hex()
|
||||
coin_from = Coins(offer.coin_from)
|
||||
|
||||
# assert(bid.expire_at > now), 'Bid expired' # How much time over to accept
|
||||
|
||||
|
@ -2112,12 +2142,26 @@ class BasicSwap():
|
|||
return
|
||||
raise ValueError('Wrong bid state: {}'.format(str(BidStates(bid.state))))
|
||||
|
||||
coin_from = Coins(offer.coin_from)
|
||||
expect_sequence = getExpectedSequence(offer.lock_type, offer.lock_value, coin_from)
|
||||
use_csv = True if offer.lock_type < ABS_LOCK_BLOCKS else False
|
||||
|
||||
# TODO: Verify script without decoding?
|
||||
decoded_script = self.callcoinrpc(Coins.PART, 'decodescript', [bid_accept_data.contract_script.hex()])
|
||||
lock_check_op = 'OP_CHECKSEQUENCEVERIFY' if use_csv else 'OP_CHECKLOCKTIMEVERIFY'
|
||||
prog = re.compile('OP_IF OP_SIZE 32 OP_EQUALVERIFY OP_SHA256 (\w+) OP_EQUALVERIFY OP_DUP OP_HASH160 (\w+) OP_ELSE (\d+) {} OP_DROP OP_DUP OP_HASH160 (\w+) OP_ENDIF OP_EQUALVERIFY OP_CHECKSIG'.format(lock_check_op))
|
||||
rr = prog.match(decoded_script['asm'])
|
||||
if not rr:
|
||||
raise ValueError('Bad script')
|
||||
scriptvalues = rr.groups()
|
||||
|
||||
assert(len(scriptvalues[0]) == 64), 'Bad secret_hash length'
|
||||
assert(bytes.fromhex(scriptvalues[1]) == bid.pkhash_buyer), 'pkhash_buyer mismatch'
|
||||
assert(int(scriptvalues[2]) == expect_sequence), 'sequence mismatch'
|
||||
|
||||
if use_csv:
|
||||
expect_sequence = getExpectedSequence(offer.lock_type, offer.lock_value, coin_from)
|
||||
assert(int(scriptvalues[2]) == expect_sequence), 'sequence mismatch'
|
||||
else:
|
||||
self.log.warning('TODO: validate absolute lock values')
|
||||
|
||||
assert(len(scriptvalues[3]) == 40), 'pkhash_refund bad length'
|
||||
|
||||
assert(bid.accept_msg_id is None), 'Bid already accepted'
|
||||
|
|
|
@ -22,3 +22,8 @@ LITECOIN_BINDIR = os.path.expanduser(os.getenv('LITECOIN_BINDIR', ''))
|
|||
LITECOIND = os.getenv('LITECOIND', 'litecoind' + ('.exe' if os.name == 'nt' else ''))
|
||||
LITECOIN_CLI = os.getenv('LITECOIN_CLI', 'litecoin-cli' + ('.exe' if os.name == 'nt' else ''))
|
||||
LITECOIN_TX = os.getenv('LITECOIN_TX', 'litecoin-tx' + ('.exe' if os.name == 'nt' else ''))
|
||||
|
||||
NAMECOIN_BINDIR = os.path.expanduser(os.getenv('NAMECOIN_BINDIR', ''))
|
||||
NAMECOIND = os.getenv('NAMECOIND', 'namecoind' + ('.exe' if os.name == 'nt' else ''))
|
||||
NAMECOIN_CLI = os.getenv('NAMECOIN_CLI', 'namecoin-cli' + ('.exe' if os.name == 'nt' else ''))
|
||||
NAMECOIN_TX = os.getenv('NAMECOIN_TX', 'namecoin-tx' + ('.exe' if os.name == 'nt' else ''))
|
||||
|
|
|
@ -14,6 +14,8 @@ message OfferMessage {
|
|||
NOT_SET = 0;
|
||||
SEQUENCE_LOCK_BLOCKS = 1;
|
||||
SEQUENCE_LOCK_TIME = 2;
|
||||
ABS_LOCK_BLOCKS = 3;
|
||||
ABS_LOCK_TIME = 4;
|
||||
}
|
||||
LockType lock_type = 7;
|
||||
uint32 lock_value = 8;
|
||||
|
|
|
@ -23,6 +23,7 @@ import time
|
|||
from urllib.request import urlretrieve
|
||||
import urllib.parse
|
||||
import logging
|
||||
import platform
|
||||
|
||||
import gnupg
|
||||
|
||||
|
@ -30,7 +31,10 @@ import basicswap.config as cfg
|
|||
from basicswap.util import callrpc_cli
|
||||
from bin.basicswap_run import startDaemon
|
||||
|
||||
BIN_ARCH = 'x86_64-linux-gnu.tar.gz'
|
||||
if platform.system() == 'Darwin':
|
||||
BIN_ARCH = 'osx64.tar.gz'
|
||||
else:
|
||||
BIN_ARCH = 'x86_64-linux-gnu.tar.gz'
|
||||
|
||||
known_coins = {
|
||||
'particl': '0.18.1.0',
|
||||
|
@ -358,6 +362,7 @@ def main():
|
|||
'datadir': os.path.join(data_dir, 'namecoin'),
|
||||
'bindir': os.path.join(data_dir, 'bins', 'namecoin'),
|
||||
'use_segwit': False,
|
||||
'use_csv': False,
|
||||
'blocks_confirmed': 1
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ import signal
|
|||
import subprocess
|
||||
import logging
|
||||
|
||||
import basicswap.config as cfg
|
||||
from basicswap import __version__
|
||||
from basicswap.basicswap import BasicSwap
|
||||
from basicswap.http_server import HttpThread
|
||||
|
@ -66,17 +65,10 @@ def runClient(fp, dataDir, chain):
|
|||
for c, v in settings['chainclients'].items():
|
||||
if v['manage_daemon'] is True:
|
||||
logger.info('Starting {} daemon'.format(c.capitalize()))
|
||||
if c == 'particl':
|
||||
daemons.append(startDaemon(v['datadir'], v['bindir'], cfg.PARTICLD))
|
||||
logger.info('Started {} {}'.format(cfg.PARTICLD, daemons[-1].pid))
|
||||
elif c == 'bitcoin':
|
||||
daemons.append(startDaemon(v['datadir'], v['bindir'], cfg.BITCOIND))
|
||||
logger.info('Started {} {}'.format(cfg.BITCOIND, daemons[-1].pid))
|
||||
elif c == 'litecoin':
|
||||
daemons.append(startDaemon(v['datadir'], v['bindir'], cfg.LITECOIND))
|
||||
logger.info('Started {} {}'.format(cfg.LITECOIND, daemons[-1].pid))
|
||||
else:
|
||||
logger.warning('Unknown chain', c)
|
||||
|
||||
filename = c + 'd' + ('.exe' if os.name == 'nt' else '')
|
||||
daemons.append(startDaemon(v['datadir'], v['bindir'], filename))
|
||||
logger.info('Started {} {}'.format(filename, daemons[-1].pid))
|
||||
|
||||
swap_client = BasicSwap(fp, dataDir, settings, chain)
|
||||
|
||||
|
|
545
tests/test_nmc.py
Normal file
545
tests/test_nmc.py
Normal file
|
@ -0,0 +1,545 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2019 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
"""
|
||||
basicswap]$ python tests/test_nmc.py
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
import json
|
||||
import logging
|
||||
import shutil
|
||||
import subprocess
|
||||
import time
|
||||
import signal
|
||||
import threading
|
||||
from urllib.request import urlopen
|
||||
|
||||
from basicswap.basicswap import (
|
||||
BasicSwap,
|
||||
Coins,
|
||||
SwapTypes,
|
||||
BidStates,
|
||||
TxStates,
|
||||
ABS_LOCK_BLOCKS,
|
||||
ABS_LOCK_TIME,
|
||||
)
|
||||
from basicswap.util import (
|
||||
COIN,
|
||||
toWIF,
|
||||
callrpc_cli,
|
||||
)
|
||||
from basicswap.key import (
|
||||
ECKey,
|
||||
)
|
||||
from basicswap.http_server import (
|
||||
HttpThread,
|
||||
)
|
||||
|
||||
import basicswap.config as cfg
|
||||
|
||||
logger = logging.getLogger()
|
||||
logger.level = logging.DEBUG
|
||||
if not len(logger.handlers):
|
||||
logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||
|
||||
NUM_NODES = 3
|
||||
BASE_PORT = 14792
|
||||
BASE_RPC_PORT = 19792
|
||||
BASE_ZMQ_PORT = 20792
|
||||
PREFIX_SECRET_KEY_REGTEST = 0x2e
|
||||
TEST_HTML_PORT = 1800
|
||||
NMC_NODE = 3
|
||||
BTC_NODE = 4
|
||||
stop_test = False
|
||||
|
||||
|
||||
def prepareOtherDir(datadir, nodeId, conf_file='namecoin.conf'):
|
||||
node_dir = os.path.join(datadir, str(nodeId))
|
||||
if not os.path.exists(node_dir):
|
||||
os.makedirs(node_dir)
|
||||
filePath = os.path.join(node_dir, conf_file)
|
||||
|
||||
with open(filePath, 'w+') as fp:
|
||||
fp.write('regtest=1\n')
|
||||
fp.write('[regtest]\n')
|
||||
fp.write('port=' + str(BASE_PORT + nodeId) + '\n')
|
||||
fp.write('rpcport=' + str(BASE_RPC_PORT + nodeId) + '\n')
|
||||
|
||||
fp.write('daemon=0\n')
|
||||
fp.write('printtoconsole=0\n')
|
||||
fp.write('server=1\n')
|
||||
fp.write('discover=0\n')
|
||||
fp.write('listenonion=0\n')
|
||||
fp.write('bind=127.0.0.1\n')
|
||||
fp.write('findpeers=0\n')
|
||||
fp.write('debug=1\n')
|
||||
fp.write('debugexclude=libevent\n')
|
||||
|
||||
fp.write('acceptnonstdtxn=0\n')
|
||||
|
||||
|
||||
def prepareDir(datadir, nodeId, network_key, network_pubkey):
|
||||
node_dir = os.path.join(datadir, str(nodeId))
|
||||
if not os.path.exists(node_dir):
|
||||
os.makedirs(node_dir)
|
||||
filePath = os.path.join(node_dir, 'particl.conf')
|
||||
|
||||
with open(filePath, 'w+') as fp:
|
||||
fp.write('regtest=1\n')
|
||||
fp.write('[regtest]\n')
|
||||
fp.write('port=' + str(BASE_PORT + nodeId) + '\n')
|
||||
fp.write('rpcport=' + str(BASE_RPC_PORT + nodeId) + '\n')
|
||||
|
||||
fp.write('daemon=0\n')
|
||||
fp.write('printtoconsole=0\n')
|
||||
fp.write('server=1\n')
|
||||
fp.write('discover=0\n')
|
||||
fp.write('listenonion=0\n')
|
||||
fp.write('bind=127.0.0.1\n')
|
||||
fp.write('findpeers=0\n')
|
||||
fp.write('debug=1\n')
|
||||
fp.write('debugexclude=libevent\n')
|
||||
fp.write('zmqpubsmsg=tcp://127.0.0.1:' + str(BASE_ZMQ_PORT + nodeId) + '\n')
|
||||
|
||||
fp.write('acceptnonstdtxn=0\n')
|
||||
fp.write('minstakeinterval=5\n')
|
||||
|
||||
for i in range(0, NUM_NODES):
|
||||
if nodeId == i:
|
||||
continue
|
||||
fp.write('addnode=127.0.0.1:%d\n' % (BASE_PORT + i))
|
||||
|
||||
if nodeId < 2:
|
||||
fp.write('spentindex=1\n')
|
||||
fp.write('txindex=1\n')
|
||||
|
||||
basicswap_dir = os.path.join(datadir, str(nodeId), 'basicswap')
|
||||
if not os.path.exists(basicswap_dir):
|
||||
os.makedirs(basicswap_dir)
|
||||
|
||||
nmcdatadir = os.path.join(datadir, str(NMC_NODE))
|
||||
btcdatadir = os.path.join(datadir, str(BTC_NODE))
|
||||
settings_path = os.path.join(basicswap_dir, 'basicswap.json')
|
||||
settings = {
|
||||
'zmqhost': 'tcp://127.0.0.1',
|
||||
'zmqport': BASE_ZMQ_PORT + nodeId,
|
||||
'htmlhost': 'localhost',
|
||||
'htmlport': 12700 + nodeId,
|
||||
'network_key': network_key,
|
||||
'network_pubkey': network_pubkey,
|
||||
'chainclients': {
|
||||
'particl': {
|
||||
'connection_type': 'rpc',
|
||||
'manage_daemon': False,
|
||||
'rpcport': BASE_RPC_PORT + nodeId,
|
||||
'datadir': node_dir,
|
||||
'bindir': cfg.PARTICL_BINDIR,
|
||||
'blocks_confirmed': 2, # Faster testing
|
||||
},
|
||||
'namecoin': {
|
||||
'connection_type': 'rpc',
|
||||
'manage_daemon': False,
|
||||
'rpcport': BASE_RPC_PORT + NMC_NODE,
|
||||
'datadir': nmcdatadir,
|
||||
'bindir': cfg.NAMECOIN_BINDIR,
|
||||
'use_csv': False,
|
||||
# 'use_segwit': True,
|
||||
},
|
||||
'bitcoin': {
|
||||
'connection_type': 'rpc',
|
||||
'manage_daemon': False,
|
||||
'rpcport': BASE_RPC_PORT + BTC_NODE,
|
||||
'datadir': btcdatadir,
|
||||
'bindir': cfg.BITCOIN_BINDIR,
|
||||
'use_segwit': True,
|
||||
}
|
||||
},
|
||||
'check_progress_seconds': 2,
|
||||
'check_watched_seconds': 4,
|
||||
'check_expired_seconds': 60
|
||||
}
|
||||
with open(settings_path, 'w') as fp:
|
||||
json.dump(settings, fp, indent=4)
|
||||
|
||||
|
||||
def startDaemon(nodeId, bin_dir=cfg.PARTICL_BINDIR, daemon_bin=cfg.PARTICLD):
|
||||
node_dir = os.path.join(cfg.DATADIRS, str(nodeId))
|
||||
daemon_bin = os.path.join(bin_dir, daemon_bin)
|
||||
|
||||
args = [daemon_bin, '-datadir=' + node_dir]
|
||||
logging.info('Starting node ' + str(nodeId) + ' ' + daemon_bin + ' ' + '-datadir=' + node_dir)
|
||||
return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
|
||||
def partRpc(cmd, node_id=0):
|
||||
return callrpc_cli(cfg.PARTICL_BINDIR, os.path.join(cfg.DATADIRS, str(node_id)), 'regtest', cmd, cfg.PARTICL_CLI)
|
||||
|
||||
|
||||
def btcRpc(cmd):
|
||||
return callrpc_cli(cfg.BITCOIN_BINDIR, os.path.join(cfg.DATADIRS, str(BTC_NODE)), 'regtest', cmd, cfg.BITCOIN_CLI)
|
||||
|
||||
|
||||
def nmcRpc(cmd):
|
||||
return callrpc_cli(cfg.NAMECOIN_BINDIR, os.path.join(cfg.DATADIRS, str(NMC_NODE)), 'regtest', cmd, cfg.NAMECOIN_CLI)
|
||||
|
||||
|
||||
def signal_handler(sig, frame):
|
||||
global stop_test
|
||||
print('signal {} detected.'.format(sig))
|
||||
stop_test = True
|
||||
|
||||
|
||||
def run_loop(self):
|
||||
while not stop_test:
|
||||
time.sleep(1)
|
||||
for c in self.swap_clients:
|
||||
c.update()
|
||||
nmcRpc('generatetoaddress 1 {}'.format(self.nmc_addr))
|
||||
btcRpc('generatetoaddress 1 {}'.format(self.btc_addr))
|
||||
|
||||
|
||||
def waitForRPC(rpc_func, wallet=None):
|
||||
for i in range(5):
|
||||
try:
|
||||
rpc_func('getwalletinfo')
|
||||
return
|
||||
except Exception as ex:
|
||||
logging.warning('Can\'t connect to daemon RPC: %s. Trying again in %d second/s.', str(ex), (1 + i))
|
||||
time.sleep(1 + i)
|
||||
raise ValueError('waitForRPC failed')
|
||||
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(Test, cls).setUpClass()
|
||||
|
||||
eckey = ECKey()
|
||||
eckey.generate()
|
||||
cls.network_key = toWIF(PREFIX_SECRET_KEY_REGTEST, eckey.get_bytes())
|
||||
cls.network_pubkey = eckey.get_pubkey().get_bytes().hex()
|
||||
|
||||
if os.path.isdir(cfg.DATADIRS):
|
||||
logging.info('Removing ' + cfg.DATADIRS)
|
||||
shutil.rmtree(cfg.DATADIRS)
|
||||
|
||||
for i in range(NUM_NODES):
|
||||
prepareDir(cfg.DATADIRS, i, cls.network_key, cls.network_pubkey)
|
||||
|
||||
prepareOtherDir(cfg.DATADIRS, NMC_NODE)
|
||||
prepareOtherDir(cfg.DATADIRS, BTC_NODE, 'bitcoin.conf')
|
||||
|
||||
cls.daemons = []
|
||||
cls.swap_clients = []
|
||||
|
||||
cls.daemons.append(startDaemon(BTC_NODE, cfg.BITCOIN_BINDIR, cfg.BITCOIND))
|
||||
logging.info('Started %s %d', cfg.BITCOIND, cls.daemons[-1].pid)
|
||||
cls.daemons.append(startDaemon(NMC_NODE, cfg.NAMECOIN_BINDIR, cfg.NAMECOIND))
|
||||
logging.info('Started %s %d', cfg.NAMECOIND, cls.daemons[-1].pid)
|
||||
|
||||
for i in range(NUM_NODES):
|
||||
cls.daemons.append(startDaemon(i))
|
||||
logging.info('Started %s %d', cfg.PARTICLD, cls.daemons[-1].pid)
|
||||
time.sleep(1)
|
||||
for i in range(NUM_NODES):
|
||||
basicswap_dir = os.path.join(os.path.join(cfg.DATADIRS, str(i)), 'basicswap')
|
||||
settings_path = os.path.join(basicswap_dir, 'basicswap.json')
|
||||
with open(settings_path) as fs:
|
||||
settings = json.load(fs)
|
||||
fp = open(os.path.join(basicswap_dir, 'basicswap.log'), 'w')
|
||||
cls.swap_clients.append(BasicSwap(fp, basicswap_dir, settings, 'regtest', log_name='BasicSwap{}'.format(i)))
|
||||
cls.swap_clients[-1].start()
|
||||
cls.swap_clients[0].callrpc('extkeyimportmaster', ['abandon baby cabbage dad eager fabric gadget habit ice kangaroo lab absorb'])
|
||||
cls.swap_clients[1].callrpc('extkeyimportmaster', ['pact mammal barrel matrix local final lecture chunk wasp survey bid various book strong spread fall ozone daring like topple door fatigue limb olympic', '', 'true'])
|
||||
cls.swap_clients[1].callrpc('getnewextaddress', ['lblExtTest'])
|
||||
cls.swap_clients[1].callrpc('rescanblockchain')
|
||||
|
||||
waitForRPC(nmcRpc)
|
||||
num_blocks = 500
|
||||
logging.info('Mining %d namecoin blocks', num_blocks)
|
||||
cls.nmc_addr = nmcRpc('getnewaddress mining_addr legacy')
|
||||
nmcRpc('generatetoaddress {} {}'.format(num_blocks, cls.nmc_addr))
|
||||
|
||||
ro = nmcRpc('getblockchaininfo')
|
||||
try:
|
||||
assert(ro['bip9_softforks']['csv']['status'] == 'active')
|
||||
except Exception:
|
||||
logging.info('nmc: csv is not active')
|
||||
try:
|
||||
assert(ro['bip9_softforks']['segwit']['status'] == 'active')
|
||||
except Exception:
|
||||
logging.info('nmc: segwit is not active')
|
||||
|
||||
waitForRPC(btcRpc)
|
||||
cls.btc_addr = btcRpc('getnewaddress mining_addr bech32')
|
||||
logging.info('Mining %d bitcoin blocks to %s', num_blocks, cls.btc_addr)
|
||||
btcRpc('generatetoaddress {} {}'.format(num_blocks, cls.btc_addr))
|
||||
|
||||
ro = btcRpc('getblockchaininfo')
|
||||
assert(ro['bip9_softforks']['csv']['status'] == 'active')
|
||||
assert(ro['bip9_softforks']['segwit']['status'] == 'active')
|
||||
|
||||
ro = nmcRpc('getwalletinfo')
|
||||
print('nmcRpc', ro)
|
||||
|
||||
cls.http_threads = []
|
||||
host = '0.0.0.0' # All interfaces (docker)
|
||||
for i in range(3):
|
||||
t = HttpThread(cls.swap_clients[i].fp, host, TEST_HTML_PORT + i, False, cls.swap_clients[i])
|
||||
cls.http_threads.append(t)
|
||||
t.start()
|
||||
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
cls.update_thread = threading.Thread(target=run_loop, args=(cls,))
|
||||
cls.update_thread.start()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
global stop_test
|
||||
logging.info('Finalising')
|
||||
stop_test = True
|
||||
cls.update_thread.join()
|
||||
for t in cls.http_threads:
|
||||
t.stop()
|
||||
t.join()
|
||||
for c in cls.swap_clients:
|
||||
c.fp.close()
|
||||
for d in cls.daemons:
|
||||
logging.info('Terminating %d', d.pid)
|
||||
d.terminate()
|
||||
d.wait(timeout=10)
|
||||
if d.stdout:
|
||||
d.stdout.close()
|
||||
if d.stderr:
|
||||
d.stderr.close()
|
||||
if d.stdin:
|
||||
d.stdin.close()
|
||||
|
||||
super(Test, cls).tearDownClass()
|
||||
|
||||
def wait_for_offer(self, swap_client, offer_id):
|
||||
logging.info('wait_for_offer %s', offer_id.hex())
|
||||
for i in range(20):
|
||||
time.sleep(1)
|
||||
offers = swap_client.listOffers()
|
||||
for offer in offers:
|
||||
if offer.offer_id == offer_id:
|
||||
return
|
||||
raise ValueError('wait_for_offer timed out.')
|
||||
|
||||
def wait_for_bid(self, swap_client, bid_id):
|
||||
logging.info('wait_for_bid %s', bid_id.hex())
|
||||
for i in range(20):
|
||||
time.sleep(1)
|
||||
bids = swap_client.listBids()
|
||||
for bid in bids:
|
||||
if bid.bid_id == bid_id and bid.was_received:
|
||||
return
|
||||
raise ValueError('wait_for_bid timed out.')
|
||||
|
||||
def wait_for_in_progress(self, swap_client, bid_id, sent=False):
|
||||
logging.info('wait_for_in_progress %s', bid_id.hex())
|
||||
for i in range(20):
|
||||
time.sleep(1)
|
||||
swaps = swap_client.listSwapsInProgress()
|
||||
for b in swaps:
|
||||
if b[0] == bid_id:
|
||||
return
|
||||
raise ValueError('wait_for_in_progress timed out.')
|
||||
|
||||
def wait_for_bid_state(self, swap_client, bid_id, state, sent=False, seconds_for=30):
|
||||
logging.info('wait_for_bid_state %s %s', bid_id.hex(), str(state))
|
||||
for i in range(seconds_for):
|
||||
time.sleep(1)
|
||||
bid = swap_client.getBid(bid_id)
|
||||
if bid.state >= state:
|
||||
return
|
||||
raise ValueError('wait_for_bid_state timed out.')
|
||||
|
||||
def wait_for_bid_tx_state(self, swap_client, bid_id, initiate_state, participate_state, seconds_for=30):
|
||||
logging.info('wait_for_bid_tx_state %s %s %s', bid_id.hex(), str(initiate_state), str(participate_state))
|
||||
for i in range(seconds_for):
|
||||
time.sleep(1)
|
||||
bid = swap_client.getBid(bid_id)
|
||||
if (initiate_state is None or bid.initiate_txn_state == initiate_state) \
|
||||
and (participate_state is None or bid.participate_txn_state == participate_state):
|
||||
return
|
||||
raise ValueError('wait_for_bid_tx_state timed out.')
|
||||
|
||||
def test_02_part_ltc(self):
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
logging.info('---------- Test PART to NMC')
|
||||
offer_id = swap_clients[0].postOffer(Coins.PART, Coins.NMC, 100 * COIN, 0.1 * COIN, 100 * COIN, SwapTypes.SELLER_FIRST, ABS_LOCK_TIME)
|
||||
|
||||
self.wait_for_offer(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)
|
||||
|
||||
self.wait_for_bid(swap_clients[0], bid_id)
|
||||
|
||||
swap_clients[0].acceptBid(bid_id)
|
||||
|
||||
self.wait_for_in_progress(swap_clients[1], bid_id, sent=True)
|
||||
|
||||
self.wait_for_bid_state(swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, seconds_for=60)
|
||||
self.wait_for_bid_state(swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, seconds_for=60)
|
||||
|
||||
js_0 = json.loads(urlopen('http://localhost:1800/json').read())
|
||||
js_1 = json.loads(urlopen('http://localhost:1801/json').read())
|
||||
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_nmc_part(self):
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
logging.info('---------- Test NMC to PART')
|
||||
offer_id = swap_clients[1].postOffer(Coins.NMC, Coins.PART, 10 * COIN, 9.0 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST, ABS_LOCK_TIME)
|
||||
|
||||
self.wait_for_offer(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)
|
||||
|
||||
self.wait_for_bid(swap_clients[1], bid_id)
|
||||
swap_clients[1].acceptBid(bid_id)
|
||||
|
||||
self.wait_for_in_progress(swap_clients[0], bid_id, sent=True)
|
||||
|
||||
self.wait_for_bid_state(swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, sent=True, seconds_for=60)
|
||||
self.wait_for_bid_state(swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, seconds_for=60)
|
||||
|
||||
js_0 = json.loads(urlopen('http://localhost:1800/json').read())
|
||||
js_1 = json.loads(urlopen('http://localhost:1801/json').read())
|
||||
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_nmc_btc(self):
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
logging.info('---------- Test NMC to BTC')
|
||||
offer_id = swap_clients[0].postOffer(Coins.NMC, Coins.BTC, 10 * COIN, 0.1 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST, ABS_LOCK_TIME)
|
||||
|
||||
self.wait_for_offer(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)
|
||||
|
||||
self.wait_for_bid(swap_clients[0], bid_id)
|
||||
swap_clients[0].acceptBid(bid_id)
|
||||
|
||||
self.wait_for_in_progress(swap_clients[1], bid_id, sent=True)
|
||||
|
||||
self.wait_for_bid_state(swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, seconds_for=60)
|
||||
self.wait_for_bid_state(swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, seconds_for=60)
|
||||
|
||||
js_0bid = json.loads(urlopen('http://localhost:1800/json/bids/{}'.format(bid_id.hex())).read())
|
||||
|
||||
js_0 = json.loads(urlopen('http://localhost:1800/json').read())
|
||||
js_1 = json.loads(urlopen('http://localhost:1801/json').read())
|
||||
|
||||
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
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
logging.info('---------- Test refund, NMC to BTC')
|
||||
|
||||
# Note the lock value is absolute.
|
||||
offer_id = swap_clients[0].postOffer(Coins.NMC, Coins.BTC, 10 * COIN, 0.1 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST,
|
||||
ABS_LOCK_BLOCKS, 20)
|
||||
|
||||
self.wait_for_offer(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)
|
||||
|
||||
self.wait_for_bid(swap_clients[0], bid_id)
|
||||
swap_clients[1].abandonBid(bid_id)
|
||||
swap_clients[0].acceptBid(bid_id)
|
||||
|
||||
self.wait_for_bid_state(swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, seconds_for=60)
|
||||
self.wait_for_bid_state(swap_clients[1], bid_id, BidStates.BID_ABANDONED, sent=True, seconds_for=60)
|
||||
|
||||
js_0 = json.loads(urlopen('http://localhost:1800/json').read())
|
||||
js_1 = json.loads(urlopen('http://localhost:1801/json').read())
|
||||
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):
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
logging.info('---------- Test same client, BTC to NMC')
|
||||
|
||||
js_0_before = json.loads(urlopen('http://localhost:1800/json').read())
|
||||
|
||||
offer_id = swap_clients[0].postOffer(Coins.NMC, Coins.BTC, 10 * COIN, 10 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST, ABS_LOCK_TIME)
|
||||
|
||||
self.wait_for_offer(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)
|
||||
|
||||
self.wait_for_bid(swap_clients[0], bid_id)
|
||||
swap_clients[0].acceptBid(bid_id)
|
||||
|
||||
self.wait_for_bid_tx_state(swap_clients[0], bid_id, TxStates.TX_REDEEMED, TxStates.TX_REDEEMED, seconds_for=60)
|
||||
self.wait_for_bid_state(swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, seconds_for=60)
|
||||
|
||||
js_0 = json.loads(urlopen('http://localhost:1800/json').read())
|
||||
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):
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
logging.info('---------- Test error, BTC to NMC, set fee above bid value')
|
||||
|
||||
js_0_before = json.loads(urlopen('http://localhost:1800/json').read())
|
||||
|
||||
offer_id = swap_clients[0].postOffer(Coins.NMC, Coins.BTC, 0.001 * COIN, 1.0 * COIN, 0.001 * COIN, SwapTypes.SELLER_FIRST, ABS_LOCK_TIME)
|
||||
|
||||
self.wait_for_offer(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)
|
||||
|
||||
self.wait_for_bid(swap_clients[0], bid_id)
|
||||
swap_clients[0].acceptBid(bid_id)
|
||||
swap_clients[0].coin_clients[Coins.BTC]['override_feerate'] = 10.0
|
||||
swap_clients[0].coin_clients[Coins.NMC]['override_feerate'] = 10.0
|
||||
|
||||
self.wait_for_bid_state(swap_clients[0], bid_id, BidStates.BID_ERROR, seconds_for=60)
|
||||
|
||||
def pass_99_delay(self):
|
||||
global stop_test
|
||||
logging.info('Delay')
|
||||
for i in range(60 * 5):
|
||||
if stop_test:
|
||||
break
|
||||
time.sleep(1)
|
||||
print('delay', i)
|
||||
stop_test = True
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
Loading…
Reference in a new issue