mirror of
https://github.com/basicswap/basicswap.git
synced 2024-12-23 03:49:25 +00:00
particl: Can swap anon outputs
This commit is contained in:
parent
6dd6063182
commit
1ef71ea79b
10 changed files with 145 additions and 40 deletions
|
@ -1,3 +1,3 @@
|
|||
name = "basicswap"
|
||||
|
||||
__version__ = "0.0.23"
|
||||
__version__ = "0.0.24"
|
||||
|
|
|
@ -18,6 +18,9 @@ from .rpc import (
|
|||
from .util import (
|
||||
pubkeyToAddress,
|
||||
)
|
||||
from .basicswap_util import (
|
||||
TemporaryError,
|
||||
)
|
||||
from .chainparams import (
|
||||
Coins,
|
||||
chainparams,
|
||||
|
@ -136,5 +139,7 @@ class BaseApp:
|
|||
return out[0].decode('utf-8').strip()
|
||||
|
||||
def is_transient_error(self, ex):
|
||||
if isinstance(ex, TemporaryError):
|
||||
return True
|
||||
str_error = str(ex).lower()
|
||||
return 'read timed out' in str_error or 'no connection to daemon' in str_error
|
||||
|
|
|
@ -1756,7 +1756,7 @@ class BasicSwap(BaseApp):
|
|||
script = atomic_swap_1.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
|
||||
lock_value = self.callcoinrpc(coin_from, 'getblockcount') + offer.lock_value
|
||||
else:
|
||||
lock_value = int(time.time()) + offer.lock_value
|
||||
self.log.debug('Initiate %s lock_value %d %d', coin_from, offer.lock_value, lock_value)
|
||||
|
@ -2225,7 +2225,7 @@ class BasicSwap(BaseApp):
|
|||
refund_txn = self.createRefundTxn(coin_to, txn_signed, offer, bid, participate_script, tx_type=TxTypes.PTX_REFUND)
|
||||
bid.participate_txn_refund = bytes.fromhex(refund_txn)
|
||||
|
||||
chain_height = self.callcoinrpc(coin_to, 'getblockchaininfo')['blocks']
|
||||
chain_height = self.callcoinrpc(coin_to, 'getblockcount')
|
||||
txjs = self.callcoinrpc(coin_to, 'decoderawtransaction', [txn_signed])
|
||||
txid = txjs['txid']
|
||||
|
||||
|
@ -2554,7 +2554,7 @@ class BasicSwap(BaseApp):
|
|||
return self.lookupUnspentByAddress(coin_type, address, sum_output=True)
|
||||
|
||||
def lookupChainHeight(self, coin_type):
|
||||
return self.callcoinrpc(coin_type, 'getblockchaininfo')['blocks']
|
||||
return self.callcoinrpc(coin_type, 'getblockcount')
|
||||
|
||||
def lookupUnspentByAddress(self, coin_type, address, sum_output=False, assert_amount=None, assert_txid=None):
|
||||
|
||||
|
@ -2589,7 +2589,7 @@ class BasicSwap(BaseApp):
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
num_blocks = self.callcoinrpc(coin_type, 'getblockchaininfo')['blocks']
|
||||
num_blocks = self.callcoinrpc(coin_type, 'getblockcount')
|
||||
|
||||
sum_unspent = 0
|
||||
self.log.debug('[rm] scantxoutset start') # scantxoutset is slow
|
||||
|
@ -2782,7 +2782,7 @@ class BasicSwap(BaseApp):
|
|||
|
||||
bid_changed = False
|
||||
# Have to use findTxB instead of relying on the first seen height to detect chain reorgs
|
||||
found_tx = ci_to.findTxB(xmr_swap.vkbv, xmr_swap.pkbs, bid.amount_to, ci_to.blocks_confirmed, xmr_swap.b_restore_height)
|
||||
found_tx = ci_to.findTxB(xmr_swap.vkbv, xmr_swap.pkbs, bid.amount_to, ci_to.blocks_confirmed, xmr_swap.b_restore_height, bid.was_sent)
|
||||
|
||||
if isinstance(found_tx, int) and found_tx == -1:
|
||||
if self.countBidEvents(bid, EventLogTypes.LOCK_TX_B_INVALID, session) < 1:
|
||||
|
@ -3249,7 +3249,7 @@ class BasicSwap(BaseApp):
|
|||
spend_txn = self.callcoinrpc(Coins.PART, 'getrawtransaction', [spend_txid, True])
|
||||
self.processSpentOutput(coin_type, o, spend_txid, spend_n, spend_txn)
|
||||
else:
|
||||
chain_blocks = self.callcoinrpc(coin_type, 'getblockchaininfo')['blocks']
|
||||
chain_blocks = self.callcoinrpc(coin_type, 'getblockcount')
|
||||
last_height_checked = c['last_height_checked']
|
||||
self.log.debug('chain_blocks, last_height_checked %s %s', chain_blocks, last_height_checked)
|
||||
while last_height_checked < chain_blocks:
|
||||
|
@ -4288,8 +4288,11 @@ class BasicSwap(BaseApp):
|
|||
vkbs = ci_to.sumKeys(kbsl, kbsf)
|
||||
|
||||
try:
|
||||
address_to = self.getCachedMainWalletAddress(ci_to)
|
||||
txid = ci_to.spendBLockTx(address_to, xmr_swap.vkbv, vkbs, bid.amount_to, xmr_offer.b_fee_rate, xmr_swap.b_restore_height)
|
||||
if coin_to == Coins.XMR:
|
||||
address_to = self.getCachedMainWalletAddress(ci_to)
|
||||
else:
|
||||
address_to = self.getCachedStealthAddressForCoin(coin_to)
|
||||
txid = ci_to.spendBLockTx(xmr_swap.b_lock_tx_id, address_to, xmr_swap.vkbv, vkbs, bid.amount_to, xmr_offer.b_fee_rate, xmr_swap.b_restore_height)
|
||||
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)
|
||||
except Exception as ex:
|
||||
|
@ -4345,7 +4348,7 @@ class BasicSwap(BaseApp):
|
|||
|
||||
try:
|
||||
address_to = self.getCachedMainWalletAddress(ci_to)
|
||||
txid = ci_to.spendBLockTx(address_to, xmr_swap.vkbv, vkbs, bid.amount_to, xmr_offer.b_fee_rate, xmr_swap.b_restore_height)
|
||||
txid = ci_to.spendBLockTx(xmr_swap.b_lock_tx_id, address_to, xmr_swap.vkbv, vkbs, bid.amount_to, xmr_offer.b_fee_rate, xmr_swap.b_restore_height)
|
||||
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)
|
||||
except Exception as ex:
|
||||
|
|
|
@ -371,3 +371,7 @@ def isActiveBidState(state):
|
|||
if state == BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class TemporaryError(ValueError):
|
||||
pass
|
||||
|
|
|
@ -175,7 +175,7 @@ class BTCInterface(CoinInterface):
|
|||
return self.rpc_callback('getblockchaininfo')
|
||||
|
||||
def getChainHeight(self):
|
||||
return self.rpc_callback('getblockchaininfo')['blocks']
|
||||
return self.rpc_callback('getblockcount')
|
||||
|
||||
def getMempoolTx(self, txid):
|
||||
return self.rpc_callback('getrawtransaction', [txid.hex()])
|
||||
|
@ -866,7 +866,7 @@ class BTCInterface(CoinInterface):
|
|||
weight = len_nwit * (wsf - 1) + len_full
|
||||
return (weight + wsf - 1) // wsf
|
||||
|
||||
def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height):
|
||||
def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height, bid_sender):
|
||||
raw_dest = self.getPkDest(Kbs)
|
||||
|
||||
rv = self.scanTxOutset(raw_dest)
|
||||
|
@ -898,7 +898,7 @@ class BTCInterface(CoinInterface):
|
|||
return True
|
||||
return False
|
||||
|
||||
def spendBLockTx(self, 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):
|
||||
print('TODO: spendBLockTx')
|
||||
|
||||
def getOutput(self, txid, dest_script, expect_value):
|
||||
|
|
|
@ -15,7 +15,13 @@ from .contrib.test_framework.script import (
|
|||
OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG
|
||||
)
|
||||
|
||||
from .util import encodeStealthAddress
|
||||
from .util import (
|
||||
encodeStealthAddress,
|
||||
toWIF,
|
||||
ensure,
|
||||
make_int)
|
||||
from .basicswap_util import (
|
||||
TemporaryError)
|
||||
from .chainparams import Coins, chainparams
|
||||
from .interface_btc import BTCInterface
|
||||
|
||||
|
@ -117,8 +123,83 @@ class PARTInterfaceAnon(PARTInterface):
|
|||
txid = self.rpc_callback('sendtypeto', params)
|
||||
return bytes.fromhex(txid)
|
||||
|
||||
def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height):
|
||||
raise ValueError('TODO - new core release')
|
||||
def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height, bid_sender):
|
||||
Kbv = self.getPubkey(kbv)
|
||||
sx_addr = self.formatStealthAddress(Kbv, Kbs)
|
||||
self._log.debug('sx_addr: {}'.format(sx_addr))
|
||||
|
||||
def spendBLockTx(self, address_to, kbv, kbs, cb_swap_value, b_fee, restore_height):
|
||||
raise ValueError('TODO - new core release')
|
||||
# Tx recipient must import the stealth address as watch only
|
||||
if bid_sender:
|
||||
cb_swap_value *= -1
|
||||
else:
|
||||
addr_info = self.rpc_callback('getaddressinfo', [sx_addr])
|
||||
if not addr_info['iswatchonly']:
|
||||
wif_prefix = chainparams[self.coin_type()][self._network]['key_prefix']
|
||||
wif_scan_key = toWIF(wif_prefix, kbv)
|
||||
self.rpc_callback('importstealthaddress', [wif_scan_key, Kbs.hex()])
|
||||
self._log.info('Imported watch-only sx_addr: {}'.format(sx_addr))
|
||||
self._log.info('Rescanning chain from height: {}'.format(restore_height))
|
||||
self.rpc_callback('rescanblockchain', [restore_height])
|
||||
|
||||
params = [{'include_watchonly': True, 'search': sx_addr}]
|
||||
txns = self.rpc_callback('filtertransactions', params)
|
||||
|
||||
if len(txns) == 1:
|
||||
tx = txns[0]
|
||||
assert(tx['outputs'][0]['stealth_address'] == sx_addr) # Should not be possible
|
||||
ensure(tx['outputs'][0]['type'] == 'anon', 'Output is not anon')
|
||||
|
||||
if make_int(tx['outputs'][0]['amount']) == cb_swap_value:
|
||||
height = 0
|
||||
if tx['confirmations'] > 0:
|
||||
chain_height = self.rpc_callback('getblockcount')
|
||||
height = chain_height - (tx['confirmations'] - 1)
|
||||
return {'txid': tx['txid'], 'amount': cb_swap_value, 'height': height}
|
||||
else:
|
||||
self._log.warning('Incorrect amount detected for coin b lock txn: {}'.format(tx['txid']))
|
||||
return -1
|
||||
return None
|
||||
|
||||
def spendBLockTx(self, chain_b_lock_txid, address_to, kbv, kbs, cb_swap_value, b_fee, restore_height):
|
||||
Kbv = self.getPubkey(kbv)
|
||||
Kbs = self.getPubkey(kbs)
|
||||
sx_addr = self.formatStealthAddress(Kbv, Kbs)
|
||||
addr_info = self.rpc_callback('getaddressinfo', [sx_addr])
|
||||
if not addr_info['ismine']:
|
||||
wif_prefix = chainparams[self.coin_type()][self._network]['key_prefix']
|
||||
wif_scan_key = toWIF(wif_prefix, kbv)
|
||||
wif_spend_key = toWIF(wif_prefix, kbs)
|
||||
self.rpc_callback('importstealthaddress', [wif_scan_key, wif_spend_key])
|
||||
self._log.info('Imported spend key for sx_addr: {}'.format(sx_addr))
|
||||
self._log.info('Rescanning chain from height: {}'.format(restore_height))
|
||||
self.rpc_callback('rescanblockchain', [restore_height])
|
||||
|
||||
autxos = self.rpc_callback('listunspentanon')
|
||||
if len(autxos) < 1:
|
||||
raise TemporaryError('No spendable outputs')
|
||||
elif len(autxos) > 1:
|
||||
raise ValueError('Too many spendable outputs')
|
||||
|
||||
utxo = autxos[0]
|
||||
|
||||
inputs = [{'tx': utxo['txid'], 'n': utxo['vout']}, ]
|
||||
params = ['anon', 'anon',
|
||||
[{'address': address_to, 'amount': self.format_amount(cb_swap_value), 'subfee': True}, ],
|
||||
'', '', self._anon_tx_ring_size, 1, False,
|
||||
{'conf_target': self._conf_target, 'inputs': inputs, 'show_fee': True}]
|
||||
rv = self.rpc_callback('sendtypeto', params)
|
||||
return bytes.fromhex(rv['txid'])
|
||||
|
||||
def findTxnByHash(self, txid_hex):
|
||||
# txindex is enabled for Particl
|
||||
|
||||
try:
|
||||
rv = self.rpc_callback('getrawtransaction', [txid_hex, True])
|
||||
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['height']}
|
||||
|
||||
return None
|
||||
|
|
|
@ -27,6 +27,8 @@ from .util import (
|
|||
dumpj,
|
||||
make_int,
|
||||
format_amount)
|
||||
from .basicswap_util import (
|
||||
TemporaryError)
|
||||
from .rpc_xmr import (
|
||||
make_xmr_rpc_func,
|
||||
make_xmr_rpc2_func,
|
||||
|
@ -232,7 +234,7 @@ class XMRInterface(CoinInterface):
|
|||
|
||||
return tx_hash
|
||||
|
||||
def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height):
|
||||
def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height, bid_sender):
|
||||
with self._mx_wallet:
|
||||
Kbv = self.getPubkey(kbv)
|
||||
address_b58 = xmr_util.encode_address(Kbv, Kbs)
|
||||
|
@ -363,7 +365,7 @@ class XMRInterface(CoinInterface):
|
|||
|
||||
return None
|
||||
|
||||
def spendBLockTx(self, address_to, kbv, kbs, cb_swap_value, b_fee_rate, restore_height):
|
||||
def spendBLockTx(self, chain_b_lock_txid, address_to, kbv, kbs, cb_swap_value, b_fee_rate, restore_height):
|
||||
with self._mx_wallet:
|
||||
Kbv = self.getPubkey(kbv)
|
||||
Kbs = self.getPubkey(kbs)
|
||||
|
@ -401,10 +403,10 @@ class XMRInterface(CoinInterface):
|
|||
time.sleep(1 + i)
|
||||
if rv['balance'] < cb_swap_value:
|
||||
self._log.error('wallet {} balance {}, expected {}'.format(wallet_filename, rv['balance'], cb_swap_value))
|
||||
raise ValueError('Invalid balance')
|
||||
raise TemporaryError('Invalid balance')
|
||||
if rv['unlocked_balance'] < cb_swap_value:
|
||||
self._log.error('wallet {} balance {}, expected {}, blocks_to_unlock {}'.format(wallet_filename, rv['unlocked_balance'], cb_swap_value, rv['blocks_to_unlock']))
|
||||
raise ValueError('Invalid unlocked_balance')
|
||||
raise TemporaryError('Invalid unlocked_balance')
|
||||
|
||||
params = {'address': address_to}
|
||||
if self._fee_priority > 0:
|
||||
|
|
|
@ -246,6 +246,10 @@ def make_int(v, scale=8, r=0): # r = 0, no rounding, fail, r > 0 round up, r <
|
|||
elif type(v) == int:
|
||||
return v * 10 ** scale
|
||||
|
||||
sign = 1
|
||||
if v[0] == '-':
|
||||
v = v[1:]
|
||||
sign = -1
|
||||
ep = 10 ** scale
|
||||
have_dp = False
|
||||
rv = 0
|
||||
|
@ -255,7 +259,6 @@ def make_int(v, scale=8, r=0): # r = 0, no rounding, fail, r > 0 round up, r <
|
|||
have_dp = True
|
||||
continue
|
||||
if not c.isdigit():
|
||||
|
||||
raise ValueError('Invalid char: ' + c)
|
||||
if have_dp:
|
||||
ep //= 10
|
||||
|
@ -267,13 +270,12 @@ def make_int(v, scale=8, r=0): # r = 0, no rounding, fail, r > 0 round up, r <
|
|||
if int(c) > 4:
|
||||
rv += 1
|
||||
break
|
||||
|
||||
rv += ep * int(c)
|
||||
else:
|
||||
rv = rv * 10 + int(c)
|
||||
if not have_dp:
|
||||
rv *= ep
|
||||
return rv
|
||||
return rv * sign
|
||||
|
||||
|
||||
def validate_amount(amount, scale=8) -> bool:
|
||||
|
|
|
@ -171,7 +171,6 @@ class Test(unittest.TestCase):
|
|||
cls.part_daemons = []
|
||||
cls.btc_daemons = []
|
||||
|
||||
cls.part_stakelimit = 0
|
||||
cls.btc_addr = None
|
||||
|
||||
logger.propagate = False
|
||||
|
|
|
@ -33,6 +33,7 @@ from basicswap.util import (
|
|||
COIN,
|
||||
toWIF,
|
||||
make_int,
|
||||
format_amount,
|
||||
)
|
||||
from basicswap.rpc import (
|
||||
callrpc,
|
||||
|
@ -279,7 +280,6 @@ class Test(unittest.TestCase):
|
|||
cls.xmr_daemons = []
|
||||
cls.xmr_wallet_auth = []
|
||||
|
||||
cls.part_stakelimit = 0
|
||||
cls.xmr_addr = None
|
||||
cls.btc_addr = None
|
||||
|
||||
|
@ -395,6 +395,14 @@ class Test(unittest.TestCase):
|
|||
callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': num_blocks})
|
||||
logging.info('XMR blocks: %d', callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'get_block_count')['count'])
|
||||
|
||||
logging.info('Adding anon outputs')
|
||||
outputs = []
|
||||
for i in range(8):
|
||||
sx_addr = callnoderpc(1, 'getnewstealthaddress')
|
||||
outputs.append({'address': sx_addr, 'amount': 0.5})
|
||||
for i in range(6):
|
||||
callnoderpc(0, 'sendtypeto', ['part', 'anon', outputs])
|
||||
|
||||
logging.info('Starting update thread.')
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
cls.update_thread = threading.Thread(target=run_loop, args=(cls,))
|
||||
|
@ -805,9 +813,12 @@ class Test(unittest.TestCase):
|
|||
|
||||
js_0 = json.loads(urlopen('http://127.0.0.1:1800/json/wallets/part').read())
|
||||
assert(float(js_0['anon_balance']) == 0.0)
|
||||
node0_anon_before = js_0['anon_balance'] + js_0['anon_pending']
|
||||
|
||||
wait_for_balance(test_delay_event, 'http://127.0.0.1:1801/json/wallets/part', 'balance', 200.0)
|
||||
js_1 = json.loads(urlopen('http://127.0.0.1:1801/json/wallets/part').read())
|
||||
assert(float(js_1['balance']) > 200.0)
|
||||
node1_anon_before = js_1['anon_balance'] + js_1['anon_pending']
|
||||
|
||||
callnoderpc(1, 'reservebalance', [True, 1000000]) # Stop staking to avoid conflicts (input used by tx->anon staked before tx gets in the chain)
|
||||
post_json = {
|
||||
|
@ -819,17 +830,10 @@ class Test(unittest.TestCase):
|
|||
json_rv = json.loads(post_json_req('http://127.0.0.1:1801/json/wallets/part/withdraw', post_json))
|
||||
assert(len(json_rv['txid']) == 64)
|
||||
|
||||
post_json['value'] = 0.5
|
||||
for i in range(22):
|
||||
json_rv = json.loads(post_json_req('http://127.0.0.1:1801/json/wallets/part/withdraw', post_json))
|
||||
assert(len(json_rv['txid']) == 64)
|
||||
|
||||
logging.info('Waiting for anon balance')
|
||||
try:
|
||||
wait_for_balance(test_delay_event, 'http://127.0.0.1:1801/json/wallets/part', 'anon_balance', 110.0)
|
||||
except Exception as e:
|
||||
ft = callnoderpc(0, 'filtertransactions', [{'count': 0}])
|
||||
raise e
|
||||
wait_for_balance(test_delay_event, 'http://127.0.0.1:1801/json/wallets/part', 'anon_balance', 100.0 + node1_anon_before)
|
||||
js_1 = json.loads(urlopen('http://127.0.0.1:1801/json/wallets/part').read())
|
||||
node1_anon_before = js_1['anon_balance'] + js_1['anon_pending']
|
||||
|
||||
callnoderpc(1, 'reservebalance', [False])
|
||||
post_json = {
|
||||
|
@ -847,8 +851,6 @@ class Test(unittest.TestCase):
|
|||
if float(js_0['blind_balance']) >= 10.0:
|
||||
raise ValueError('Expect blind balance < 10')
|
||||
|
||||
return # TODO
|
||||
|
||||
amt_swap = make_int(random.uniform(0.1, 2.0), scale=8, r=1)
|
||||
rate_swap = make_int(random.uniform(2.0, 20.0), scale=8, r=1)
|
||||
offer_id = swap_clients[0].postOffer(Coins.BTC, Coins.PART_ANON, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP)
|
||||
|
@ -862,6 +864,7 @@ class Test(unittest.TestCase):
|
|||
|
||||
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
|
||||
assert(xmr_swap)
|
||||
amount_to = float(format_amount(bid.amount_to, 8))
|
||||
|
||||
swap_clients[0].acceptXmrBid(bid_id)
|
||||
|
||||
|
@ -869,7 +872,13 @@ class Test(unittest.TestCase):
|
|||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True)
|
||||
|
||||
js_1 = json.loads(urlopen('http://127.0.0.1:1801/json/wallets/part').read())
|
||||
print('[rm] js_1', js_1)
|
||||
assert(js_1['anon_balance'] < node1_anon_before - amount_to)
|
||||
|
||||
js_0 = json.loads(urlopen('http://127.0.0.1:1800/json/wallets/part').read())
|
||||
assert(js_0['anon_balance'] + js_0['anon_pending'] > node0_anon_before + (amount_to - 0.1))
|
||||
|
||||
def test_12_particl_blind(self):
|
||||
return # TODO
|
||||
|
||||
def test_98_withdraw_all(self):
|
||||
logging.info('---------- Test XMR withdrawal all')
|
||||
|
|
Loading…
Reference in a new issue