From 2e0edef9da2f99b52bce1605491b7ea3fb06f00a Mon Sep 17 00:00:00 2001 From: tecnovert Date: Sat, 30 Jan 2021 01:45:24 +0200 Subject: [PATCH] Raise max signature size for fee estimate. Fix logging. Valid sequence lock range settings. Bid debugind can be set through api. --- basicswap/__init__.py | 2 +- basicswap/basicswap.py | 85 ++++++++++++------- basicswap/db.py | 6 +- basicswap/http_server.py | 2 + basicswap/interface_btc.py | 65 +++++++------- basicswap/interface_part.py | 11 +-- basicswap/interface_xmr.py | 40 ++++----- basicswap/js_server.py | 38 ++++++--- .../basicswap/extended/test_xmr_persistent.py | 1 + 9 files changed, 149 insertions(+), 101 deletions(-) diff --git a/basicswap/__init__.py b/basicswap/__init__.py index f10c9af..fcc0e06 100644 --- a/basicswap/__init__.py +++ b/basicswap/__init__.py @@ -1,3 +1,3 @@ name = "basicswap" -__version__ = "0.0.13" +__version__ = "0.0.14" diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index 3dd30c9..0db581b 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -14,7 +14,6 @@ import random import shutil import struct import hashlib -import logging import secrets import datetime as dt import traceback @@ -200,6 +199,7 @@ class EventLogTypes(IntEnum): LOCK_TX_A_CONFIRMED = auto() LOCK_TX_B_SEEN = auto() LOCK_TX_B_CONFIRMED = auto() + DEBUG_TWEAK_APPLIED = auto() class XmrSplitMsgTypes(IntEnum): @@ -342,6 +342,8 @@ def describeEventEntry(event_type, event_msg): return 'Lock tx b seen in chain' if event_type == EventLogTypes.LOCK_TX_B_CONFIRMED: return 'Lock tx b confirmed in chain' + if event_type == EventLogTypes.DEBUG_TWEAK_APPLIED: + return 'Debug tweak applied ' + event_msg def getExpectedSequence(lockType, lockVal, coin_type): @@ -445,6 +447,9 @@ class BasicSwap(BaseApp): self.min_delay_retry = self.settings.get('min_delay_retry', 60) self.max_delay_retry = self.settings.get('max_delay_retry', 5 * 60) + self.min_sequence_lock_seconds = self.settings.get('min_sequence_lock_seconds', 1 * 60 * 60) + self.max_sequence_lock_seconds = self.settings.get('max_sequence_lock_seconds', 96 * 60 * 60) + self._bid_expired_leeway = 5 self.swaps_in_progress = dict() @@ -595,15 +600,15 @@ class BasicSwap(BaseApp): def createInterface(self, coin): if coin == Coins.PART: - return PARTInterface(self.coin_clients[coin], self.chain) + return PARTInterface(self.coin_clients[coin], self.chain, self) elif coin == Coins.BTC: - return BTCInterface(self.coin_clients[coin], self.chain) + return BTCInterface(self.coin_clients[coin], self.chain, self) elif coin == Coins.LTC: - return LTCInterface(self.coin_clients[coin], self.chain) + return LTCInterface(self.coin_clients[coin], self.chain, self) elif coin == Coins.NMC: - return NMCInterface(self.coin_clients[coin], self.chain) + return NMCInterface(self.coin_clients[coin], self.chain, self) elif coin == Coins.XMR: - xmr_i = XMRInterface(self.coin_clients[coin], self.chain) + xmr_i = XMRInterface(self.coin_clients[coin], self.chain, self) chain_client_settings = self.getChainClientSettings(coin) xmr_i.setWalletFilename(chain_client_settings['walletfile']) return xmr_i @@ -735,6 +740,20 @@ class BasicSwap(BaseApp): self.log.info('Upgrading database from version %d to %d.', db_version, CURRENT_DB_VERSION) while True: + if db_version == 6: + session = scoped_session(self.session_factory) + + session.execute('ALTER TABLE bids ADD COLUMN security_token BLOB') + session.execute('ALTER TABLE offers ADD COLUMN security_token BLOB') + + db_version += 1 + self.db_version = db_version + self.setIntKVInSession('db_version', db_version, session) + session.commit() + session.close() + session.remove() + self.log.info('Upgraded database to version {}'.format(self.db_version)) + continue if db_version == 4: session = scoped_session(self.session_factory) @@ -743,12 +762,7 @@ class BasicSwap(BaseApp): db_version += 1 self.db_version = db_version - kv = session.query(DBKVInt).filter_by(key='db_version').first() - if not kv: - kv = DBKVInt(key='db_version', value=db_version) - else: - kv.value = db_version - session.add(kv) + self.setIntKVInSession('db_version', db_version, session) session.commit() session.close() session.remove() @@ -804,16 +818,19 @@ class BasicSwap(BaseApp): key_str = 'main_wallet_seedid_' + chainparams[coin_type]['name'] self.setStringKV(key_str, root_hash.hex()) + def setIntKVInSession(self, str_key, int_val, session): + kv = session.query(DBKVInt).filter_by(key=str_key).first() + if not kv: + kv = DBKVInt(key=str_key, value=int_val) + else: + kv.value = int_val + session.add(kv) + def setIntKV(self, str_key, int_val): self.mxDB.acquire() try: session = scoped_session(self.session_factory) - kv = session.query(DBKVInt).filter_by(key=str_key).first() - if not kv: - kv = DBKVInt(key=str_key, value=int_val) - else: - kv.value = int_val - session.add(kv) + self.setIntKVInSession(str_key, int_val, session) session.commit() finally: session.close() @@ -1004,7 +1021,7 @@ class BasicSwap(BaseApp): def validateOfferLockValue(self, coin_from, coin_to, lock_type, lock_value): if lock_type == OfferMessage.SEQUENCE_LOCK_TIME: - assert(lock_value >= 1 * 60 * 60 and lock_value <= 96 * 60 * 60), 'Invalid lock_value time' + assert(lock_value >= self.min_sequence_lock_seconds and lock_value <= self.max_sequence_lock_seconds), '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' @@ -1109,6 +1126,10 @@ class BasicSwap(BaseApp): offer_id = bytes.fromhex(msg_id) + security_token = extra_options.get('security_token', None) + if security_token is not None and len(security_token) != 20: + raise ValueError('Security token must be 20 bytes long.') + session = scoped_session(self.session_factory) offer = Offer( offer_id=offer_id, @@ -1128,7 +1149,8 @@ class BasicSwap(BaseApp): created_at=offer_created_at, expire_at=offer_created_at + msg_buf.time_valid, was_sent=True, - auto_accept_bids=auto_accept_bids,) + auto_accept_bids=auto_accept_bids, + security_token=security_token) offer.setState(OfferStates.OFFER_SENT) if swap_type == SwapTypes.XMR_SWAP: @@ -1149,7 +1171,7 @@ class BasicSwap(BaseApp): self.log.info('Sent OFFER %s', offer_id.hex()) return offer_id - def revokeOffer(self, offer_id): + def revokeOffer(self, offer_id, security_token=None): self.log.info('Revoking offer %s', offer_id.hex()) session = None @@ -1159,6 +1181,9 @@ class BasicSwap(BaseApp): offer = session.query(Offer).filter_by(offer_id=offer_id).first() + if len(offer.security_token > 0) and offer.security_token != security_token: + raise ValueError('Mismatched security token') + msg_buf = OfferRevokeMessage() msg_buf.offer_msg_id = offer_id @@ -2652,7 +2677,7 @@ class BasicSwap(BaseApp): self.saveBidInSession(bid_id, bid, session, xmr_swap) session.commit() except Exception as ex: - logging.debug('Trying to publish coin a lock refund spend tx: %s', str(ex)) + self.log.debug('Trying to publish coin a lock refund spend tx: %s', str(ex)) if bid.was_sent: if xmr_swap.a_lock_refund_swipe_tx is None: @@ -2672,7 +2697,7 @@ class BasicSwap(BaseApp): self.saveBidInSession(bid_id, bid, session, xmr_swap) session.commit() except Exception as ex: - logging.debug('Trying to publish coin a lock refund swipe tx: %s', str(ex)) + self.log.debug('Trying to publish coin a lock refund swipe tx: %s', str(ex)) if BidStates(bid.state) == BidStates.XMR_SWAP_NOSCRIPT_TX_RECOVERED: txid_hex = bid.xmr_b_lock_tx.spend_txid.hex() @@ -3271,9 +3296,9 @@ class BasicSwap(BaseApp): num_removed += 1 if num_messages + num_removed > 0: - logging.info('Expired {} / {} messages.'.format(num_removed, num_messages)) + self.log.info('Expired {} / {} messages.'.format(num_removed, num_messages)) - logging.debug('TODO: Expire records from db') + self.log.debug('TODO: Expire records from db') finally: self.mxDB.release() @@ -3410,7 +3435,7 @@ class BasicSwap(BaseApp): elif offer_data.swap_type == SwapTypes.XMR_SWAP: assert(coin_from != Coins.XMR) assert(coin_to == Coins.XMR) - logging.debug('TODO - More restrictions') + self.log.debug('TODO - More restrictions') else: raise ValueError('Unknown swap type {}.'.format(offer_data.swap_type)) @@ -3788,7 +3813,7 @@ class BasicSwap(BaseApp): ci_from = self.ci(Coins(offer.coin_from)) ci_to = self.ci(Coins(offer.coin_to)) - logging.debug('TODO: xmr bid validation') + self.log.debug('TODO: xmr bid validation') assert(ci_to.verifyKey(bid_data.kbvf)) assert(ci_from.verifyPubkey(bid_data.pkaf)) @@ -3892,7 +3917,7 @@ class BasicSwap(BaseApp): xmr_swap.a_swap_refund_value, xmr_offer.a_fee_rate ) - logging.info('Checking leader\'s lock refund tx signature') + self.log.info('Checking leader\'s lock refund tx signature') v = ci_from.verifyTxSig(xmr_swap.a_lock_refund_tx, xmr_swap.al_lock_refund_tx_sig, xmr_swap.pkal, 0, xmr_swap.a_lock_tx_script, bid.amount) bid.setState(BidStates.BID_RECEIVING_ACC) @@ -4056,7 +4081,9 @@ class BasicSwap(BaseApp): if bid.debug_ind == DebugTypes.BID_STOP_AFTER_COIN_A_LOCK: self.log.debug('XMR bid %s: Abandoning bid for testing: %d.', bid_id.hex(), bid.debug_ind) - # bid.setState(BidStates.BID_ABANDONED) # TODO: Retry if state + bid.setState(BidStates.BID_ABANDONED) + self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer) + self.logBidEvent(bid, EventLogTypes.DEBUG_TWEAK_APPLIED, 'ind {}'.format(bid.debug_ind), session) return if bid.debug_ind == DebugTypes.CREATE_INVALID_COIN_B_LOCK: diff --git a/basicswap/db.py b/basicswap/db.py index f84e73d..c3e8ebd 100644 --- a/basicswap/db.py +++ b/basicswap/db.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2019-2020 tecnovert +# Copyright (c) 2019-2021 tecnovert # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -12,7 +12,7 @@ from sqlalchemy.ext.declarative import declarative_base from enum import IntEnum, auto -CURRENT_DB_VERSION = 6 +CURRENT_DB_VERSION = 7 Base = declarative_base() @@ -62,6 +62,7 @@ class Offer(Base): # Local fields auto_accept_bids = sa.Column(sa.Boolean) withdraw_to_addr = sa.Column(sa.String) # Address to spend lock tx to - address from wallet if empty TODO + security_token = sa.Column(sa.LargeBinary) state = sa.Column(sa.Integer) states = sa.Column(sa.LargeBinary) # Packed states and times @@ -114,6 +115,7 @@ class Bid(Base): state_note = sa.Column(sa.String) debug_ind = sa.Column(sa.Integer) + security_token = sa.Column(sa.LargeBinary) initiate_tx = None participate_tx = None diff --git a/basicswap/http_server.py b/basicswap/http_server.py index 1446ed1..60e84b0 100644 --- a/basicswap/http_server.py +++ b/basicswap/http_server.py @@ -393,6 +393,8 @@ class HttpHandler(BaseHTTPRequestHandler): if have_data_entry(form_data, 'lockhrs'): page_data['lockhrs'] = int(get_data_entry(form_data, 'lockhrs')) parsed_data['lock_seconds'] = page_data['lockhrs'] * 60 * 60 + elif have_data_entry(form_data, 'lockseconds'): + parsed_data['lock_seconds'] = int(get_data_entry(form_data, 'lockseconds')) page_data['autoaccept'] = True if have_data_entry(form_data, 'autoaccept') else False parsed_data['autoaccept'] = page_data['autoaccept'] diff --git a/basicswap/interface_btc.py b/basicswap/interface_btc.py index d601538..bfc1f12 100644 --- a/basicswap/interface_btc.py +++ b/basicswap/interface_btc.py @@ -117,14 +117,19 @@ class BTCInterface(CoinInterface): def xmr_swap_alock_spend_tx_vsize(): return 147 - def __init__(self, coin_settings, network): + @staticmethod + def txoType(): + return CTxOut + + def __init__(self, coin_settings, network, swap_client=None): super().__init__() rpc_host = coin_settings.get('rpchost', '127.0.0.1') self.rpc_callback = make_rpc_func(coin_settings['rpcport'], coin_settings['rpcauth'], host=rpc_host) - self.txoType = CTxOut self._network = network self.blocks_confirmed = coin_settings['blocks_confirmed'] self.setConfTarget(coin_settings['conf_target']) + self._sc = swap_client + self._log = self._sc.log if self._sc.log else logging def setConfTarget(self, new_conf_target): assert(new_conf_target >= 1 and new_conf_target < 33), 'Invalid conf_target value' @@ -153,7 +158,7 @@ class BTCInterface(CoinInterface): self.rpc_callback('sethdseed', [True, key_wif]) except Exception as e: # < 0.21: Cannot set a new HD seed while still in Initial Block Download. - logging.error('sethdseed failed: {}'.format(str(e))) + self._log.error('sethdseed failed: {}'.format(str(e))) def getWalletInfo(self): return self.rpc_callback('getwalletinfo') @@ -252,7 +257,7 @@ class BTCInterface(CoinInterface): script = self.genScriptLockTxScript(Kal, Kaf) tx = CTransaction() tx.nVersion = self.txVersion() - tx.vout.append(self.txoType(value, self.getScriptDest(script))) + tx.vout.append(self.txoType()(value, self.getScriptDest(script))) return tx.serialize(), script @@ -316,10 +321,10 @@ class BTCInterface(CoinInterface): tx = CTransaction() tx.nVersion = self.txVersion() tx.vin.append(CTxIn(COutPoint(tx_lock_hash_int, locked_n), nSequence=lock1_value)) - tx.vout.append(self.txoType(locked_coin, CScript([OP_0, hashlib.sha256(refund_script).digest()]))) + tx.vout.append(self.txoType()(locked_coin, CScript([OP_0, hashlib.sha256(refund_script).digest()]))) witness_bytes = len(script_lock) - witness_bytes += 73 * 2 # 2 signatures (72 + 1 byts size) + witness_bytes += 74 * 2 # 2 signatures (72 + 1 byte sighashtype + 1 byte size) - Use maximum txn size for estimate witness_bytes += 2 # 2 empty witness stack values witness_bytes += getCompactSizeLen(witness_bytes) vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes) @@ -327,8 +332,8 @@ class BTCInterface(CoinInterface): tx.vout[0].nValue = locked_coin - pay_fee tx.rehash() - logging.info('createScriptLockRefundTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', - i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) + self._log.info('createScriptLockRefundTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', + i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) return tx.serialize(), refund_script, tx.vout[0].nValue @@ -351,7 +356,7 @@ class BTCInterface(CoinInterface): tx.nVersion = self.txVersion() tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n), nSequence=0)) - tx.vout.append(self.txoType(locked_coin, self.getScriptForPubkeyHash(pkh_refund_to))) + tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_refund_to))) witness_bytes = len(script_lock_refund) witness_bytes += 73 * 2 # 2 signatures (72 + 1 byte size) @@ -362,8 +367,8 @@ class BTCInterface(CoinInterface): tx.vout[0].nValue = locked_coin - pay_fee tx.rehash() - logging.info('createScriptLockRefundSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', - i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) + self._log.info('createScriptLockRefundSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', + i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) return tx.serialize() @@ -386,7 +391,7 @@ class BTCInterface(CoinInterface): tx.nVersion = self.txVersion() tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n), nSequence=lock2_value)) - tx.vout.append(self.txoType(locked_coin, self.getScriptForPubkeyHash(pkh_dest))) + tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest))) witness_bytes = len(script_lock_refund) witness_bytes += 73 # signature (72 + 1 byte size) @@ -397,8 +402,8 @@ class BTCInterface(CoinInterface): tx.vout[0].nValue = locked_coin - pay_fee tx.rehash() - logging.info('createScriptLockRefundSpendToFTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', - i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) + self._log.info('createScriptLockRefundSpendToFTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', + i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) return tx.serialize() @@ -416,7 +421,7 @@ class BTCInterface(CoinInterface): tx.nVersion = self.txVersion() tx.vin.append(CTxIn(COutPoint(tx_lock_hash_int, locked_n))) - tx.vout.append(self.txoType(locked_coin, self.getScriptForPubkeyHash(pkh_dest))) + tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest))) witness_bytes = len(script_lock) witness_bytes += 33 # sv, size @@ -428,8 +433,8 @@ class BTCInterface(CoinInterface): tx.vout[0].nValue = locked_coin - pay_fee tx.rehash() - logging.info('createScriptLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', - i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) + self._log.info('createScriptLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', + i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) return tx.serialize() @@ -447,7 +452,7 @@ class BTCInterface(CoinInterface): tx = self.loadTx(tx_bytes) tx_hash = self.getTxHash(tx) - logging.info('Verifying lock tx: {}.'.format(b2h(tx_hash))) + self._log.info('Verifying lock tx: {}.'.format(b2h(tx_hash))) assert_cond(tx.nVersion == self.txVersion(), 'Bad version') assert_cond(tx.nLockTime == 0, 'Bad nLockTime') @@ -491,10 +496,10 @@ class BTCInterface(CoinInterface): vsize = self.getTxVSize(tx, add_bytes, add_witness_bytes) fee_rate_paid = fee_paid * 1000 / vsize - logging.info('tx amount, vsize, feerate: %ld, %ld, %ld', locked_coin, vsize, fee_rate_paid) + self._log.info('tx amount, vsize, feerate: %ld, %ld, %ld', locked_coin, vsize, fee_rate_paid) if not self.compareFeeRates(fee_rate_paid, feerate): - logging.warning('feerate paid doesn\'t match expected: %ld, %ld', fee_rate_paid, feerate) + self._log.warning('feerate paid doesn\'t match expected: %ld, %ld', fee_rate_paid, feerate) # TODO: Display warning to user return tx_hash, locked_n @@ -509,7 +514,7 @@ class BTCInterface(CoinInterface): tx = self.loadTx(tx_bytes) tx_hash = self.getTxHash(tx) - logging.info('Verifying lock refund tx: {}.'.format(b2h(tx_hash))) + self._log.info('Verifying lock refund tx: {}.'.format(b2h(tx_hash))) assert_cond(tx.nVersion == self.txVersion(), 'Bad version') assert_cond(tx.nLockTime == 0, 'nLockTime not 0') @@ -543,7 +548,7 @@ class BTCInterface(CoinInterface): vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes) fee_rate_paid = fee_paid * 1000 / vsize - logging.info('tx amount, vsize, feerate: %ld, %ld, %ld', locked_coin, vsize, fee_rate_paid) + self._log.info('tx amount, vsize, feerate: %ld, %ld, %ld', locked_coin, vsize, fee_rate_paid) if not self.compareFeeRates(fee_rate_paid, feerate): raise ValueError('Bad fee rate') @@ -559,7 +564,7 @@ class BTCInterface(CoinInterface): # Must have only one output sending lock refund tx value - fee to leader's address, TODO: follower shouldn't need to verify destination addr tx = self.loadTx(tx_bytes) tx_hash = self.getTxHash(tx) - logging.info('Verifying lock refund spend tx: {}.'.format(b2h(tx_hash))) + self._log.info('Verifying lock refund spend tx: {}.'.format(b2h(tx_hash))) assert_cond(tx.nVersion == self.txVersion(), 'Bad version') assert_cond(tx.nLockTime == 0, 'nLockTime not 0') @@ -589,7 +594,7 @@ class BTCInterface(CoinInterface): vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes) fee_rate_paid = fee_paid * 1000 / vsize - logging.info('tx amount, vsize, feerate: %ld, %ld, %ld', tx_value, vsize, fee_rate_paid) + self._log.info('tx amount, vsize, feerate: %ld, %ld, %ld', tx_value, vsize, fee_rate_paid) if not self.compareFeeRates(fee_rate_paid, feerate): raise ValueError('Bad fee rate') @@ -605,7 +610,7 @@ class BTCInterface(CoinInterface): tx = self.loadTx(tx_bytes) tx_hash = self.getTxHash(tx) - logging.info('Verifying lock spend tx: {}.'.format(b2h(tx_hash))) + self._log.info('Verifying lock spend tx: {}.'.format(b2h(tx_hash))) assert_cond(tx.nVersion == self.txVersion(), 'Bad version') assert_cond(tx.nLockTime == 0, 'nLockTime not 0') @@ -638,7 +643,7 @@ class BTCInterface(CoinInterface): vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes) fee_rate_paid = fee_paid * 1000 / vsize - logging.info('tx amount, vsize, feerate: %ld, %ld, %ld', tx.vout[0].nValue, vsize, fee_rate_paid) + self._log.info('tx amount, vsize, feerate: %ld, %ld, %ld', tx.vout[0].nValue, vsize, fee_rate_paid) if not self.compareFeeRates(fee_rate_paid, feerate): raise ValueError('Bad fee rate') @@ -768,7 +773,7 @@ class BTCInterface(CoinInterface): tx = CTransaction() tx.nVersion = self.txVersion() p2wpkh = self.getPkDest(Kbs) - tx.vout.append(self.txoType(output_amount, p2wpkh)) + tx.vout.append(self.txoType()(output_amount, p2wpkh)) return tx.serialize() def publishBLockTx(self, Kbv, Kbs, output_amount, feerate): @@ -799,7 +804,7 @@ class BTCInterface(CoinInterface): for utxo in rv['unspents']: if 'height' in utxo and utxo['height'] > 0 and rv['height'] - utxo['height'] > cb_block_confirmed: if self.make_int(utxo['amount']) != cb_swap_value: - logging.warning('Found output to lock tx pubkey of incorrect value: %s', str(utxo['amount'])) + self._log.warning('Found output to lock tx pubkey of incorrect value: %s', str(utxo['amount'])) else: return {'txid': utxo['txid'], 'vout': utxo['vout'], 'amount': utxo['amount'], 'height': utxo['height']} return None @@ -817,7 +822,7 @@ class BTCInterface(CoinInterface): if 'height' in utxo and utxo['height'] > 0 and rv['height'] - utxo['height'] > cb_block_confirmed: if self.make_int(utxo['amount']) != cb_swap_value: - logging.warning('Found output to lock tx pubkey of incorrect value: %s', str(utxo['amount'])) + self._log.warning('Found output to lock tx pubkey of incorrect value: %s', str(utxo['amount'])) else: return True return False @@ -873,7 +878,7 @@ class BTCInterface(CoinInterface): try: pubkey = PublicKey.from_signature_and_message(signature_bytes, message_hash, hasher=None) except Exception as e: - logging.info('verifyMessage failed: ' + str(e)) + self._log.info('verifyMessage failed: ' + str(e)) return False address_hash = self.decodeAddress(address) diff --git a/basicswap/interface_part.py b/basicswap/interface_part.py index 4d34496..5718142 100644 --- a/basicswap/interface_part.py +++ b/basicswap/interface_part.py @@ -15,7 +15,6 @@ from .contrib.test_framework.script import ( from .interface_btc import BTCInterface from .chainparams import Coins -from .rpc import make_rpc_func class PARTInterface(BTCInterface): @@ -35,13 +34,9 @@ class PARTInterface(BTCInterface): def xmr_swap_alock_spend_tx_vsize(): return 213 - def __init__(self, coin_settings, network): - rpc_host = coin_settings.get('rpchost', '127.0.0.1') - self.rpc_callback = make_rpc_func(coin_settings['rpcport'], coin_settings['rpcauth'], host=rpc_host) - self.txoType = CTxOutPart - self._network = network - self.blocks_confirmed = coin_settings['blocks_confirmed'] - self._conf_target = coin_settings['conf_target'] + @staticmethod + def txoType(): + return CTxOutPart def knownWalletSeed(self): # TODO: Double check diff --git a/basicswap/interface_xmr.py b/basicswap/interface_xmr.py index cb91ad8..cf9f54c 100644 --- a/basicswap/interface_xmr.py +++ b/basicswap/interface_xmr.py @@ -58,7 +58,7 @@ class XMRInterface(CoinInterface): def nbK(): # No. of bytes requires to encode a public key return 32 - def __init__(self, coin_settings, network): + def __init__(self, coin_settings, network, swap_client=None): super().__init__() self.rpc_cb = make_xmr_rpc_func(coin_settings['rpcport'], host=coin_settings.get('rpchost', '127.0.0.1')) self.rpc_cb2 = make_xmr_rpc2_func(coin_settings['rpcport'], host=coin_settings.get('rpchost', '127.0.0.1')) # non-json endpoint @@ -68,6 +68,8 @@ class XMRInterface(CoinInterface): self.blocks_confirmed = coin_settings['blocks_confirmed'] self._restore_height = coin_settings.get('restore_height', 0) self.setFeePriority(coin_settings.get('fee_priority', 0)) + self._sc = swap_client + self._log = self._sc.log if self._sc.log else logging def setFeePriority(self, new_priority): assert(new_priority >= 0 and new_priority < 4), 'Invalid fee_priority value' @@ -96,7 +98,7 @@ class XMRInterface(CoinInterface): 'restore_height': self._restore_height, } rv = self.rpc_wallet_cb('generate_from_keys', params) - logging.info('generate_from_keys %s', dumpj(rv)) + self._log.info('generate_from_keys %s', dumpj(rv)) self.rpc_wallet_cb('open_wallet', {'filename': self._wallet_filename}) def ensureWalletExists(self): @@ -137,12 +139,12 @@ class XMRInterface(CoinInterface): return self.rpc_wallet_cb('get_address')['address'] def getNewAddress(self, placeholder): - logging.warning('TODO - subaddress?') + self._log.warning('TODO - subaddress?') self.rpc_wallet_cb('open_wallet', {'filename': self._wallet_filename}) return self.rpc_wallet_cb('get_address')['address'] def get_fee_rate(self, conf_target=2): - logging.warning('TODO - estimate fee rate?') + self._log.warning('TODO - estimate fee rate?') return 0.0, 'unused' def isValidKey(self, key_bytes): @@ -208,14 +210,14 @@ class XMRInterface(CoinInterface): if self._fee_priority > 0: params['priority'] = self._fee_priority rv = self.rpc_wallet_cb('transfer', params) - logging.info('publishBLockTx %s to address_b58 %s', rv['tx_hash'], shared_addr) + self._log.info('publishBLockTx %s to address_b58 %s', rv['tx_hash'], shared_addr) tx_hash = bytes.fromhex(rv['tx_hash']) # Debug for i in range(10): params = {'out': True, 'pending': True, 'failed': True, 'pool': True, } rv = self.rpc_wallet_cb('get_transfers', params) - logging.info('[rm] get_transfers {}'.format(dumpj(rv))) + self._log.info('[rm] get_transfers {}'.format(dumpj(rv))) if 'pending' not in rv: break time.sleep(1) @@ -229,7 +231,7 @@ class XMRInterface(CoinInterface): try: self.rpc_wallet_cb('close_wallet') except Exception as e: - logging.warning('close_wallet failed %s', str(e)) + self._log.warning('close_wallet failed %s', str(e)) kbv_le = kbv[::-1] params = { @@ -243,7 +245,7 @@ class XMRInterface(CoinInterface): rv = self.rpc_wallet_cb('open_wallet', {'filename': address_b58}) except Exception as e: rv = self.rpc_wallet_cb('generate_from_keys', params) - logging.info('generate_from_keys %s', dumpj(rv)) + self._log.info('generate_from_keys %s', dumpj(rv)) rv = self.rpc_wallet_cb('open_wallet', {'filename': address_b58}) rv = self.rpc_wallet_cb('refresh', timeout=600) @@ -252,9 +254,9 @@ class XMRInterface(CoinInterface): # Debug try: current_height = self.rpc_wallet_cb('get_height')['height'] - logging.info('findTxB XMR current_height %d\nAddress: %s', current_height, address_b58) + self._log.info('findTxB XMR current_height %d\nAddress: %s', current_height, address_b58) except Exception as e: - logging.info('rpc_cb failed %s', str(e)) + self._log.info('rpc_cb failed %s', str(e)) current_height = None # If the transfer is available it will be deep enough # and (current_height is None or current_height - transfer['block_height'] > cb_block_confirmed): ''' @@ -265,7 +267,7 @@ class XMRInterface(CoinInterface): if transfer['amount'] == cb_swap_value: return {'txid': transfer['tx_hash'], 'amount': transfer['amount'], 'height': 0 if 'block_height' not in transfer else transfer['block_height']} else: - logging.warning('Incorrect amount detected for coin b lock txn: {}'.format(transfer['tx_hash'])) + self._log.warning('Incorrect amount detected for coin b lock txn: {}'.format(transfer['tx_hash'])) return None @@ -277,7 +279,7 @@ class XMRInterface(CoinInterface): try: self.rpc_wallet_cb('close_wallet') except Exception as e: - logging.warning('close_wallet failed %s', str(e)) + self._log.warning('close_wallet failed %s', str(e)) params = { 'filename': address_b58, @@ -296,7 +298,7 @@ class XMRInterface(CoinInterface): current_height = self.rpc_cb2('get_height')['height'] print('current_height', current_height) except Exception as e: - logging.warning('rpc_cb failed %s', str(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 @@ -336,9 +338,9 @@ class XMRInterface(CoinInterface): try: current_height = self.rpc_cb2('get_height')['height'] - logging.info('findTxnByHash XMR current_height %d\nhash: %s', current_height, txid) + self._log.info('findTxnByHash XMR current_height %d\nhash: %s', current_height, txid) except Exception as e: - logging.info('rpc_cb failed %s', str(e)) + self._log.info('rpc_cb failed %s', str(e)) current_height = None # If the transfer is available it will be deep enough params = {'transfer_type': 'available'} @@ -361,7 +363,7 @@ class XMRInterface(CoinInterface): try: self.rpc_wallet_cb('close_wallet') except Exception as e: - logging.warning('close_wallet failed %s', str(e)) + self._log.warning('close_wallet failed %s', str(e)) wallet_filename = address_b58 + '_spend' @@ -377,7 +379,7 @@ class XMRInterface(CoinInterface): self.rpc_wallet_cb('open_wallet', {'filename': wallet_filename}) except Exception as e: rv = self.rpc_wallet_cb('generate_from_keys', params) - logging.info('generate_from_keys %s', dumpj(rv)) + self._log.info('generate_from_keys %s', dumpj(rv)) self.rpc_wallet_cb('open_wallet', {'filename': wallet_filename}) # For a while after opening the wallet rpc cmds return empty data @@ -389,10 +391,10 @@ class XMRInterface(CoinInterface): time.sleep(1 + i) if rv['balance'] < cb_swap_value: - logging.error('wallet {} balance {}, expected {}'.format(wallet_filename, rv['balance'], cb_swap_value)) + self._log.error('wallet {} balance {}, expected {}'.format(wallet_filename, rv['balance'], cb_swap_value)) raise ValueError('Invalid balance') if rv['unlocked_balance'] < cb_swap_value: - logging.error('wallet {} balance {}, expected {}, blocks_to_unlock {}'.format(wallet_filename, rv['unlocked_balance'], cb_swap_value, rv['blocks_to_unlock'])) + 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') params = {'address': address_to} diff --git a/basicswap/js_server.py b/basicswap/js_server.py index 567e84a..638a6b0 100644 --- a/basicswap/js_server.py +++ b/basicswap/js_server.py @@ -21,6 +21,7 @@ from .ui import ( describeBid, setCoinFilter, get_data_entry, + have_data_entry, ) @@ -70,11 +71,11 @@ def js_offers(self, url_split, post_string, is_json, sent=False): filters['coin_from'] = setCoinFilter(post_data, 'coin_from') filters['coin_to'] = setCoinFilter(post_data, 'coin_to') - if b'sort_by' in post_data: + if have_data_entry(post_data, 'sort_by'): sort_by = get_data_entry(post_data, 'sort_by') assert(sort_by in ['created_at', 'rate']), 'Invalid sort by' filters['sort_by'] = sort_by - if b'sort_dir' in post_data: + if have_data_entry(post_data, 'sort_dir'): sort_dir = get_data_entry(post_data, 'sort_dir') assert(sort_dir in ['asc', 'desc']), 'Invalid sort dir' filters['sort_dir'] = sort_dir @@ -113,38 +114,51 @@ def js_bids(self, url_split, post_string, is_json): if url_split[3] == 'new': if post_string == '': raise ValueError('No post data') - post_data = urllib.parse.parse_qs(post_string) + if is_json: + post_data = json.loads(post_string) + post_data['is_json'] = True + else: + post_data = urllib.parse.parse_qs(post_string) - offer_id = bytes.fromhex(post_data[b'offer_id'][0].decode('utf-8')) + offer_id = bytes.fromhex(get_data_entry(post_data, 'offer_id')) assert(len(offer_id) == 28) offer = swap_client.getOffer(offer_id) assert(offer), 'Offer not found.' ci_from = swap_client.ci(offer.coin_from) - amount_from = inputAmount(post_data[b'amount_from'][0].decode('utf-8'), ci_from) + amount_from = inputAmount(get_data_entry(post_data, 'amount_from'), ci_from) addr_from = None - if b'addr_from' in post_data: - addr_from = post_data[b'addr_from'][0].decode('utf-8') + if have_data_entry(post_data, 'addr_from'): + addr_from = get_data_entry(post_data, 'addr_from') if addr_from == '-1': addr_from = None if offer.swap_type == SwapTypes.XMR_SWAP: - bid_id = swap_client.postXmrBid(offer_id, amount_from, addr_send_from=addr_from).hex() + bid_id = swap_client.postXmrBid(offer_id, amount_from, addr_send_from=addr_from) else: - bid_id = swap_client.postBid(offer_id, amount_from, addr_send_from=addr_from).hex() + bid_id = swap_client.postBid(offer_id, amount_from, addr_send_from=addr_from) - rv = {'bid_id': bid_id} + if have_data_entry(post_data, 'debugind'): + swap_client.setBidDebugInd(bid_id, int(get_data_entry(post_data, 'debugind'))) + + rv = {'bid_id': bid_id.hex()} return bytes(json.dumps(rv), 'UTF-8') bid_id = bytes.fromhex(url_split[3]) assert(len(bid_id) == 28) if post_string != '': - post_data = urllib.parse.parse_qs(post_string) - if b'accept' in post_data: + if is_json: + post_data = json.loads(post_string) + post_data['is_json'] = True + else: + post_data = urllib.parse.parse_qs(post_string) + if have_data_entry(post_data, 'accept'): swap_client.acceptBid(bid_id) + elif have_data_entry(post_data, 'debugind'): + swap_client.setBidDebugInd(bid_id, int(get_data_entry(post_data, 'debugind'))) bid, xmr_swap, offer, xmr_offer, events = swap_client.getXmrBidAndOffer(bid_id) assert(bid), 'Unknown bid ID' diff --git a/tests/basicswap/extended/test_xmr_persistent.py b/tests/basicswap/extended/test_xmr_persistent.py index feea6ca..f71de7d 100644 --- a/tests/basicswap/extended/test_xmr_persistent.py +++ b/tests/basicswap/extended/test_xmr_persistent.py @@ -206,6 +206,7 @@ class Test(unittest.TestCase): settings['max_delay_event'] = 4 settings['min_delay_retry'] = 10 settings['max_delay_retry'] = 20 + settings['min_sequence_lock_seconds'] = 60 settings['check_progress_seconds'] = 5 settings['check_watched_seconds'] = 5