diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index 996acbd..7b56a6d 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -16,6 +16,7 @@ import struct import hashlib import secrets import datetime as dt +import threading import traceback import sqlalchemy as sa import collections @@ -33,7 +34,6 @@ from .interface_bitcore_btc import BitcoreBTCInterface from . import __version__ from .util import ( pubkeyToAddress, - format8, format_amount, format_timestamp, encodeAddress, @@ -89,6 +89,11 @@ from .explorers import ( ExplorerBitAps, ExplorerChainz, ) +from .types import ( + SEQUENCE_LOCK_BLOCKS, + SEQUENCE_LOCK_TIME, + ABS_LOCK_BLOCKS, + ABS_LOCK_TIME) import basicswap.config as cfg import basicswap.network as bsn import basicswap.protocols.atomic_swap_1 as atomic_swap_1 @@ -152,6 +157,7 @@ class BidStates(IntEnum): SWAP_TIMEDOUT = auto() BID_ABANDONED = auto() # Bid will no longer be processed BID_ERROR = auto() # An error occurred + BID_STALLED_FOR_TEST = auto() class TxStates(IntEnum): @@ -221,14 +227,6 @@ class DebugTypes(IntEnum): MAKE_INVALID_PTX = auto() -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 INITIATE_TX_TIMEOUT = 40 * 60 # TODO: make variable per coin @@ -263,6 +261,8 @@ def strBidState(state): return 'Timed-out' if state == BidStates.BID_ABANDONED: return 'Abandoned' + if state == BidStates.BID_STALLED_FOR_TEST: + return 'Stalled (debug)' if state == BidStates.BID_ERROR: return 'Error' if state == BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED: @@ -366,27 +366,6 @@ def describeEventEntry(event_type, event_msg): return 'Lock tx B refund tx published' -def getExpectedSequence(lockType, lockVal, coin_type): - assert(lockVal >= 1), 'Bad lockVal' - if lockType == SEQUENCE_LOCK_BLOCKS: - return lockVal - if lockType == SEQUENCE_LOCK_TIME: - secondsLocked = lockVal - # Ensure the locked time is never less than lockVal - if secondsLocked % (1 << SEQUENCE_LOCKTIME_GRANULARITY) != 0: - secondsLocked += (1 << SEQUENCE_LOCKTIME_GRANULARITY) - secondsLocked >>= SEQUENCE_LOCKTIME_GRANULARITY - return secondsLocked | SEQUENCE_LOCKTIME_TYPE_FLAG - raise ValueError('Unknown lock type') - - -def decodeSequence(lock_value): - # 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 - - def getVoutByAddress(txjs, p2sh): for o in txjs['vout']: try: @@ -420,6 +399,29 @@ def getOfferProofOfFundsHash(offer_msg, offer_addr): return h.digest() +def threadPollChainState(swap_client, coin_type): + while not swap_client.delay_event.is_set(): + try: + ci = swap_client.ci(coin_type) + if coin_type == Coins.XMR: + new_height = ci.getChainHeight() + if new_height != swap_client.coin_clients[coin_type]['chain_height']: + swap_client.log.debug('New {} block at height: {}'.format(str(coin_type), new_height)) + with swap_client.mxDB: + swap_client.coin_clients[coin_type]['chain_height'] = new_height + else: + chain_state = ci.getBlockchainInfo() + if chain_state['bestblockhash'] != swap_client.coin_clients[coin_type]['chain_best_block']: + swap_client.log.debug('New {} block at height: {}'.format(str(coin_type), chain_state['blocks'])) + with swap_client.mxDB: + swap_client.coin_clients[coin_type]['chain_height'] = chain_state['blocks'] + swap_client.coin_clients[coin_type]['chain_best_block'] = chain_state['bestblockhash'] + swap_client.coin_clients[coin_type]['chain_median_time'] = chain_state['mediantime'] + except Exception as e: + swap_client.log.warning('threadPollChainState error: {}'.format(str(e))) + swap_client.delay_event.wait(random.randrange(20, 30)) # random to stagger updates + + class WatchedOutput(): # Watch for spends __slots__ = ('bid_id', 'txid_hex', 'vout', 'tx_type', 'swap_type') @@ -476,6 +478,9 @@ class BasicSwap(BaseApp): self.SMSG_SECONDS_IN_HOUR = 60 * 2 if self.chain == 'regtest' else 60 * 60 + self.delay_event = threading.Event() + self.threads = [] + # Encode key to match network wif_prefix = chainparams[Coins.PART][self.chain]['key_prefix'] self.network_key = toWIF(wif_prefix, decodeWif(self.settings['network_key'])) @@ -551,11 +556,15 @@ class BasicSwap(BaseApp): with self.mxDB: self.is_running = False + self.delay_event.set() if self._network: self._network.stopNetwork() self._network = None + for t in self.threads: + t.join() + def setCoinConnectParams(self, coin): # Set anything that does not require the daemon to be running chain_client_settings = self.getChainClientSettings(coin) @@ -594,7 +603,6 @@ class BasicSwap(BaseApp): 'conf_target': chain_client_settings.get('conf_target', 2), 'watched_outputs': [], 'last_height_checked': last_height_checked, - 'last_height': None, 'use_segwit': chain_client_settings.get('use_segwit', False), 'use_csv': chain_client_settings.get('use_csv', True), 'core_version_group': chain_client_settings.get('core_version_group', 0), @@ -604,6 +612,11 @@ class BasicSwap(BaseApp): 'chain_lookups': chain_client_settings.get('chain_lookups', 'local'), 'restore_height': chain_client_settings.get('restore_height', 0), 'fee_priority': chain_client_settings.get('fee_priority', 0), + + # Chain state + 'chain_height': None, + 'chain_best_block': None, + 'chain_median_time': None, } if self.coin_clients[coin]['connection_type'] == 'rpc': @@ -697,6 +710,10 @@ class BasicSwap(BaseApp): self.log.info('%s Core version %d', ci.coin_name(), core_version) self.coin_clients[c]['core_version'] = core_version + t = threading.Thread(target=threadPollChainState, args=(self, c)) + self.threads.append(t) + t.start() + if c == Coins.PART: self.coin_clients[c]['have_spent_index'] = ci.haveSpentIndex() @@ -1065,10 +1082,12 @@ class BasicSwap(BaseApp): assert(coin_from != coin_to), 'coin_from == coin_to' try: coin_from_t = Coins(coin_from) + ci_from = self.ci(coin_from_t) except Exception: raise ValueError('Unknown coin from type') try: coin_to_t = Coins(coin_to) + ci_to = self.ci(coin_to_t) except Exception: raise ValueError('Unknown coin to type') @@ -1124,10 +1143,10 @@ class BasicSwap(BaseApp): xmr_offer = XmrOffer() # Delay before the chain a lock refund tx can be mined - xmr_offer.lock_time_1 = getExpectedSequence(lock_type, lock_value, coin_from) + xmr_offer.lock_time_1 = ci_from.getExpectedSequence(lock_type, lock_value) # Delay before the follower can spend from the chain a lock refund tx - xmr_offer.lock_time_2 = getExpectedSequence(lock_type, lock_value, coin_from) + xmr_offer.lock_time_2 = ci_from.getExpectedSequence(lock_type, lock_value) xmr_offer.a_fee_rate = msg_buf.fee_rate_from xmr_offer.b_fee_rate = msg_buf.fee_rate_to # Unused: TODO - Set priority? @@ -1359,7 +1378,8 @@ class BasicSwap(BaseApp): return self.callcoinrpc(coin_type, 'getnetworkinfo')['relayfee'] def getFeeRateForCoin(self, coin_type, conf_target=2): - override_feerate = self.coin_clients[coin_type].get('override_feerate', None) + chain_client_settings = self.getChainClientSettings(coin_type) + override_feerate = chain_client_settings.get('override_feerate', None) if override_feerate: self.log.debug('Fee rate override used for %s: %f', str(coin_type), override_feerate) return override_feerate, 'override_feerate' @@ -1592,7 +1612,7 @@ class BasicSwap(BaseApp): def postBid(self, offer_id, amount, addr_send_from=None, extra_options={}): # Bid to send bid.amount * offer.rate of coin_to in exchange for bid.amount of coin_from - self.log.debug('postBid %s %s', offer_id.hex(), format8(amount)) + self.log.debug('postBid %s', offer_id.hex()) offer = self.getOffer(offer_id) assert(offer), 'Offer not found: {}.'.format(offer_id.hex()) @@ -1828,6 +1848,7 @@ class BasicSwap(BaseApp): bid.contract_count = self.getNewContractId() coin_from = Coins(offer.coin_from) + ci_from = self.ci(coin_from) bid_date = dt.datetime.fromtimestamp(bid.created_at).date() secret = self.getContractSecret(bid_date, bid.contract_count) @@ -1842,7 +1863,7 @@ class BasicSwap(BaseApp): script = bid.initiate_tx.script else: if offer.lock_type < ABS_LOCK_BLOCKS: - sequence = getExpectedSequence(offer.lock_type, offer.lock_value, coin_from) + sequence = ci_from.getExpectedSequence(offer.lock_type, offer.lock_value) script = atomic_swap_1.buildContractScript(sequence, secret_hash, bid.pkhash_buyer, pkhash_refund) else: if offer.lock_type == ABS_LOCK_BLOCKS: @@ -1907,7 +1928,7 @@ class BasicSwap(BaseApp): def postXmrBid(self, offer_id, amount, addr_send_from=None): # Bid to send bid.amount * offer.rate of coin_to in exchange for bid.amount of coin_from # Send MSG1L F -> L - self.log.debug('postXmrBid %s %s', offer_id.hex(), format8(amount)) + self.log.debug('postXmrBid %s', offer_id.hex()) self.mxDB.acquire() try: @@ -2187,13 +2208,14 @@ class BasicSwap(BaseApp): def createInitiateTxn(self, coin_type, bid_id, bid, initiate_script): if self.coin_clients[coin_type]['connection_type'] != 'rpc': return None + ci = self.ci(coin_type) if self.coin_clients[coin_type]['use_segwit']: addr_to = self.encodeSegwitP2WSH(coin_type, getP2WSH(initiate_script)) else: addr_to = self.getScriptAddress(coin_type, initiate_script) self.log.debug('Create initiate txn for coin %s to %s for bid %s', str(coin_type), addr_to, bid_id.hex()) - txn = self.callcoinrpc(coin_type, 'createrawtransaction', [[], {addr_to: format8(bid.amount)}]) + txn = self.callcoinrpc(coin_type, 'createrawtransaction', [[], {addr_to: ci.format_amount(bid.amount)}]) options = { 'lockUnspents': True, @@ -2207,6 +2229,7 @@ class BasicSwap(BaseApp): self.log.debug('deriveParticipateScript for bid %s', bid_id.hex()) coin_to = Coins(offer.coin_to) + ci_to = self.ci(coin_to) bid_date = dt.datetime.fromtimestamp(bid.created_at).date() @@ -2217,7 +2240,7 @@ class BasicSwap(BaseApp): # 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) + sequence = ci_to.getExpectedSequence(offer.lock_type, lock_value) participate_script = atomic_swap_1.buildContractScript(sequence, secret_hash, pkhash_seller, pkhash_buyer_refund) else: # Lock from the height or time of the block containing the initiate txn @@ -2259,6 +2282,7 @@ class BasicSwap(BaseApp): if self.coin_clients[coin_to]['connection_type'] != 'rpc': return None + ci = self.ci(coin_to) amount_to = bid.amount_to # Check required? @@ -2275,7 +2299,7 @@ class BasicSwap(BaseApp): else: addr_to = self.getScriptAddress(coin_to, participate_script) - txn = self.callcoinrpc(coin_to, 'createrawtransaction', [[], {addr_to: format8(amount_to)}]) + txn = self.callcoinrpc(coin_to, 'createrawtransaction', [[], {addr_to: ci.format_amount(amount_to)}]) options = { 'lockUnspents': True, 'conf_target': self.coin_clients[coin_to]['conf_target'], @@ -2311,6 +2335,7 @@ class BasicSwap(BaseApp): def createRedeemTxn(self, coin_type, bid, for_txn_type='participate', addr_redeem_out=None, fee_rate=None): self.log.debug('createRedeemTxn for coin %s', str(coin_type)) + ci = self.ci(coin_type) if for_txn_type == 'participate': prev_txnid = bid.participate_tx.txid.hex() @@ -2334,7 +2359,7 @@ class BasicSwap(BaseApp): 'vout': prev_n, 'scriptPubKey': script_pub_key, 'redeemScript': txn_script.hex(), - 'amount': format8(prev_amount)} + 'amount': ci.format_amount(prev_amount)} bid_date = dt.datetime.fromtimestamp(bid.created_at).date() wif_prefix = chainparams[Coins.PART][self.chain]['key_prefix'] @@ -2357,7 +2382,6 @@ class BasicSwap(BaseApp): tx_vsize = self.getContractSpendTxVSize(coin_type) tx_fee = (fee_rate * tx_vsize) / 1000 - ci = self.ci(coin_type) self.log.debug('Redeem tx fee %s, rate %s', ci.format_amount(tx_fee, conv_int=True, r=1), str(fee_rate)) amount_out = prev_amount - ci.make_int(tx_fee, r=1) @@ -2373,7 +2397,7 @@ class BasicSwap(BaseApp): else: addr_redeem_out = replaceAddrPrefix(addr_redeem_out, Coins.PART, self.chain) self.log.debug('addr_redeem_out %s', addr_redeem_out) - output_to = ' outaddr={}:{}'.format(format8(amount_out), addr_redeem_out) + output_to = ' outaddr={}:{}'.format(ci.format_amount(amount_out), addr_redeem_out) if coin_type == Coins.PART: redeem_txn = self.calltx('-create' + prevout_s + output_to) else: @@ -2471,7 +2495,7 @@ class BasicSwap(BaseApp): addr_refund_out = replaceAddrPrefix(addr_refund_out, Coins.PART, self.chain) self.log.debug('addr_refund_out %s', addr_refund_out) - output_to = ' outaddr={}:{}'.format(format8(amount_out), addr_refund_out) + output_to = ' outaddr={}:{}'.format(ci.format_amount(amount_out), addr_refund_out) if coin_type == Coins.PART: refund_txn = self.calltx('-create' + prevout_s + output_to) else: @@ -2700,8 +2724,8 @@ class BasicSwap(BaseApp): refund_tx = bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND] if bid.was_received: if bid.debug_ind == DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND: - self.log.debug('XMR bid %s: Abandoning bid for testing: %d.', bid_id.hex(), bid.debug_ind) - bid.setState(BidStates.BID_ABANDONED) + self.log.debug('XMR bid %s: Stalling bid for testing: %d.', bid_id.hex(), bid.debug_ind) + bid.setState(BidStates.BID_STALLED_FOR_TEST) rv = True self.saveBidInSession(bid_id, bid, session, xmr_swap) self.logBidEvent(bid, EventLogTypes.DEBUG_TWEAK_APPLIED, 'ind {}'.format(bid.debug_ind), session) @@ -2799,7 +2823,6 @@ class BasicSwap(BaseApp): bid_changed = False a_lock_tx_dest = ci_from.getScriptDest(xmr_swap.a_lock_tx_script) utxos, chain_height = ci_from.getOutput(bid.xmr_a_lock_tx.txid, a_lock_tx_dest, bid.amount) - self.coin_clients[ci_from.coin_type()]['last_height'] = chain_height if len(utxos) < 1: return rv @@ -2869,7 +2892,6 @@ class BasicSwap(BaseApp): if bid.xmr_b_lock_tx and bid.xmr_b_lock_tx.chain_height is not None and bid.xmr_b_lock_tx.chain_height > 0: chain_height = ci_to.getChainHeight() - self.coin_clients[ci_to.coin_type()]['last_height'] = chain_height if chain_height - bid.xmr_b_lock_tx.chain_height >= ci_to.blocks_confirmed: self.logBidEvent(bid, EventLogTypes.LOCK_TX_B_CONFIRMED, '', session) @@ -3476,7 +3498,9 @@ class BasicSwap(BaseApp): # Validate data now = int(time.time()) coin_from = Coins(offer_data.coin_from) + ci_from = self.ci(coin_from) coin_to = Coins(offer_data.coin_to) + ci_to = self.ci(coin_to) chain_from = chainparams[coin_from][self.chain] assert(offer_data.coin_from != offer_data.coin_to), 'coin_from == coin_to' @@ -3536,8 +3560,8 @@ class BasicSwap(BaseApp): xmr_offer = XmrOffer() xmr_offer.offer_id = offer_id - xmr_offer.lock_time_1 = getExpectedSequence(offer_data.lock_type, offer_data.lock_value, coin_from) - xmr_offer.lock_time_2 = getExpectedSequence(offer_data.lock_type, offer_data.lock_value, coin_from) + xmr_offer.lock_time_1 = ci_from.getExpectedSequence(offer_data.lock_type, offer_data.lock_value) + xmr_offer.lock_time_2 = ci_from.getExpectedSequence(offer_data.lock_type, offer_data.lock_value) xmr_offer.a_fee_rate = offer_data.fee_rate_from xmr_offer.b_fee_rate = offer_data.fee_rate_to @@ -3705,6 +3729,7 @@ class BasicSwap(BaseApp): 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) + ci_from = self.ci(coin_from) assert(bid.expire_at > now + self._bid_expired_leeway), 'Bid expired' @@ -3730,7 +3755,7 @@ class BasicSwap(BaseApp): script_lock_value = int(scriptvalues[2]) if use_csv: - expect_sequence = getExpectedSequence(offer.lock_type, offer.lock_value, coin_from) + expect_sequence = ci_from.getExpectedSequence(offer.lock_type, offer.lock_value) assert(script_lock_value == expect_sequence), 'sequence mismatch' else: if offer.lock_type == ABS_LOCK_BLOCKS: @@ -3803,7 +3828,7 @@ class BasicSwap(BaseApp): if self.countAcceptedBids(bid.offer_id) > 0: self.log.info('Not auto accepting bid %s, already have', bid.bid_id.hex()) elif bid.amount != offer.amount_from: - self.log.info('Not auto accepting bid %s, want exact amount match', bid_id.hex()) + self.log.info('Not auto accepting bid %s, want exact amount match', bid.bid_id.hex()) else: delay = random.randrange(self.min_delay_event, self.max_delay_event) self.log.info('Auto accepting xmr bid %s in %d seconds', bid.bid_id.hex(), delay) @@ -4155,8 +4180,8 @@ class BasicSwap(BaseApp): ci_to = self.ci(coin_to) 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) + self.log.debug('XMR bid %s: Stalling bid for testing: %d.', bid_id.hex(), bid.debug_ind) + bid.setState(BidStates.BID_STALLED_FOR_TEST) 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 @@ -4890,7 +4915,8 @@ class BasicSwap(BaseApp): now = int(time.time()) session = scoped_session(self.session_factory) - query_str = 'SELECT bids.created_at, bids.bid_id, bids.offer_id, bids.amount, bids.state, bids.was_received, tx1.state, tx2.state FROM bids ' + \ + query_str = 'SELECT bids.created_at, bids.bid_id, bids.offer_id, bids.amount, bids.state, bids.was_received, tx1.state, tx2.state, offers.coin_from FROM bids ' + \ + 'LEFT JOIN offers ON offers.offer_id = bids.offer_id ' + \ 'LEFT JOIN transactions AS tx1 ON tx1.bid_id = bids.bid_id AND tx1.tx_type = {} '.format(TxTypes.ITX) + \ 'LEFT JOIN transactions AS tx2 ON tx2.bid_id = bids.bid_id AND tx2.tx_type = {} '.format(TxTypes.PTX) diff --git a/basicswap/config.py b/basicswap/config.py index 4be7f33..59fad0f 100644 --- a/basicswap/config.py +++ b/basicswap/config.py @@ -10,28 +10,29 @@ CONFIG_FILENAME = 'basicswap.json' DEFAULT_DATADIR = '~/.basicswap' DEFAULT_ALLOW_CORS = False TEST_DATADIRS = os.path.expanduser(os.getenv('DATADIRS', '/tmp/basicswap')) +DEFAULT_TEST_BINDIR = os.path.expanduser(os.getenv('DEFAULT_TEST_BINDIR', '~/tmp/bin')) bin_suffix = ('.exe' if os.name == 'nt' else '') -PARTICL_BINDIR = os.path.expanduser(os.getenv('PARTICL_BINDIR', '')) +PARTICL_BINDIR = os.path.expanduser(os.getenv('PARTICL_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'particl'))) PARTICLD = os.getenv('PARTICLD', 'particld' + bin_suffix) PARTICL_CLI = os.getenv('PARTICL_CLI', 'particl-cli' + bin_suffix) PARTICL_TX = os.getenv('PARTICL_TX', 'particl-tx' + bin_suffix) -BITCOIN_BINDIR = os.path.expanduser(os.getenv('BITCOIN_BINDIR', '')) +BITCOIN_BINDIR = os.path.expanduser(os.getenv('BITCOIN_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'bitcoin'))) BITCOIND = os.getenv('BITCOIND', 'bitcoind' + bin_suffix) BITCOIN_CLI = os.getenv('BITCOIN_CLI', 'bitcoin-cli' + bin_suffix) BITCOIN_TX = os.getenv('BITCOIN_TX', 'bitcoin-tx' + bin_suffix) -LITECOIN_BINDIR = os.path.expanduser(os.getenv('LITECOIN_BINDIR', '')) +LITECOIN_BINDIR = os.path.expanduser(os.getenv('LITECOIN_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'litecoin'))) LITECOIND = os.getenv('LITECOIND', 'litecoind' + bin_suffix) LITECOIN_CLI = os.getenv('LITECOIN_CLI', 'litecoin-cli' + bin_suffix) LITECOIN_TX = os.getenv('LITECOIN_TX', 'litecoin-tx' + bin_suffix) -NAMECOIN_BINDIR = os.path.expanduser(os.getenv('NAMECOIN_BINDIR', '')) +NAMECOIN_BINDIR = os.path.expanduser(os.getenv('NAMECOIN_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'namecoin'))) NAMECOIND = os.getenv('NAMECOIND', 'namecoind' + bin_suffix) NAMECOIN_CLI = os.getenv('NAMECOIN_CLI', 'namecoin-cli' + bin_suffix) NAMECOIN_TX = os.getenv('NAMECOIN_TX', 'namecoin-tx' + bin_suffix) -XMR_BINDIR = os.path.expanduser(os.getenv('XMR_BINDIR', '')) +XMR_BINDIR = os.path.expanduser(os.getenv('XMR_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'monero'))) XMRD = os.getenv('XMRD', 'monerod' + bin_suffix) XMR_WALLET_RPC = os.getenv('XMR_WALLET_RPC', 'monero-wallet-rpc' + bin_suffix) diff --git a/basicswap/interface_btc.py b/basicswap/interface_btc.py index 1db63da..0f888a8 100644 --- a/basicswap/interface_btc.py +++ b/basicswap/interface_btc.py @@ -62,10 +62,19 @@ from .contrib.test_framework.script import ( SegwitV0SignatureHash, hash160) +from .types import ( + SEQUENCE_LOCK_BLOCKS, + SEQUENCE_LOCK_TIME) + from .chainparams import CoinInterface, Coins, chainparams from .rpc import make_rpc_func +SEQUENCE_LOCKTIME_GRANULARITY = 9 # 512 seconds +SEQUENCE_LOCKTIME_TYPE_FLAG = (1 << 22) +SEQUENCE_LOCKTIME_MASK = 0x0000ffff + + def findOutput(tx, script_pk): for i in range(len(tx.vout)): if tx.vout[i].scriptPubKey == script_pk: @@ -121,6 +130,27 @@ class BTCInterface(CoinInterface): def txoType(): return CTxOut + @staticmethod + def getExpectedSequence(lockType, lockVal): + assert(lockVal >= 1), 'Bad lockVal' + if lockType == SEQUENCE_LOCK_BLOCKS: + return lockVal + if lockType == SEQUENCE_LOCK_TIME: + secondsLocked = lockVal + # Ensure the locked time is never less than lockVal + if secondsLocked % (1 << SEQUENCE_LOCKTIME_GRANULARITY) != 0: + secondsLocked += (1 << SEQUENCE_LOCKTIME_GRANULARITY) + secondsLocked >>= SEQUENCE_LOCKTIME_GRANULARITY + return secondsLocked | SEQUENCE_LOCKTIME_TYPE_FLAG + raise ValueError('Unknown lock type') + + @staticmethod + def decodeSequence(lock_value): + # 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 + def __init__(self, coin_settings, network, swap_client=None): super().__init__() rpc_host = coin_settings.get('rpchost', '127.0.0.1') @@ -129,7 +159,7 @@ class BTCInterface(CoinInterface): 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 + self._log = self._sc.log if self._sc and 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' @@ -154,6 +184,9 @@ class BTCInterface(CoinInterface): block_hash = self.rpc_callback('getblockhash', [height]) return self.rpc_callback('getblockheader', [block_hash]) + def getBlockHeader(self, block_hash): + return self.rpc_callback('getblockheader', [block_hash]) + def initialiseWallet(self, key_bytes): wif_prefix = chainparams[self.coin_type()][self._network]['key_prefix'] key_wif = toWIF(wif_prefix, key_bytes) @@ -363,7 +396,7 @@ class BTCInterface(CoinInterface): 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) + witness_bytes += 74 * 2 # 2 signatures (72 + 1 byte sighashtype + 1 byte size) - Use maximum txn size for estimate witness_bytes += 4 # 1 empty, 1 true witness stack values witness_bytes += getCompactSizeLen(witness_bytes) vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes) @@ -398,7 +431,7 @@ class BTCInterface(CoinInterface): 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) + witness_bytes += 74 # 2 signatures (72 + 1 byte sighashtype + 1 byte size) - Use maximum txn size for estimate witness_bytes += 1 # 1 empty stack value witness_bytes += getCompactSizeLen(witness_bytes) vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes) @@ -429,7 +462,7 @@ class BTCInterface(CoinInterface): witness_bytes = len(script_lock) witness_bytes += 33 # sv, size - witness_bytes += 73 * 2 # 2 signatures (72 + 1 byte size) + witness_bytes += 74 * 2 # 2 signatures (72 + 1 byte sighashtype + 1 byte size) - Use maximum txn size for estimate witness_bytes += 4 # 1 empty, 1 true witness stack values witness_bytes += getCompactSizeLen(witness_bytes) vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes) @@ -546,7 +579,7 @@ class BTCInterface(CoinInterface): assert(fee_paid > 0) witness_bytes = len(prevout_script) - 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) @@ -555,7 +588,7 @@ class BTCInterface(CoinInterface): 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') + raise ValueError('Bad fee rate, expected: {}'.format(feerate)) return tx_hash, locked_coin @@ -592,7 +625,7 @@ class BTCInterface(CoinInterface): assert(fee_paid > 0) witness_bytes = len(prevout_script) - 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 += 4 # 1 empty, 1 true witness stack values witness_bytes += getCompactSizeLen(witness_bytes) vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes) @@ -601,7 +634,7 @@ class BTCInterface(CoinInterface): 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') + raise ValueError('Bad fee rate, expected: {}'.format(feerate)) return True @@ -641,7 +674,7 @@ class BTCInterface(CoinInterface): witness_bytes = len(lock_tx_script) witness_bytes += 33 # sv, size - 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 += 4 # 1 empty, 1 true witness stack values witness_bytes += getCompactSizeLen(witness_bytes) vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes) @@ -650,7 +683,7 @@ class BTCInterface(CoinInterface): 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') + raise ValueError('Bad fee rate, expected: {}'.format(feerate)) return True diff --git a/basicswap/interface_xmr.py b/basicswap/interface_xmr.py index e84c6da..fc84fc9 100644 --- a/basicswap/interface_xmr.py +++ b/basicswap/interface_xmr.py @@ -69,7 +69,7 @@ class XMRInterface(CoinInterface): 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 + self._log = self._sc.log if self._sc and self._sc.log else logging def setFeePriority(self, new_priority): assert(new_priority >= 0 and new_priority < 4), 'Invalid fee_priority value' diff --git a/basicswap/js_server.py b/basicswap/js_server.py index 638a6b0..77713b2 100644 --- a/basicswap/js_server.py +++ b/basicswap/js_server.py @@ -8,7 +8,6 @@ import json import urllib.parse from .util import ( - format8, format_timestamp, ) from .basicswap import ( @@ -174,7 +173,8 @@ def js_bids(self, url_split, post_string, is_json): 'bid_id': b[1].hex(), 'offer_id': b[2].hex(), 'created_at': format_timestamp(b[0]), - 'amount_from': format8(b[3]), + 'coin_from': b[8], + 'amount_from': swap_client.ci(b[8]).format_amount(b[3]), 'bid_state': strBidState(b[4]) } for b in bids]), 'UTF-8') diff --git a/basicswap/templates/bid_xmr.html b/basicswap/templates/bid_xmr.html index 609cd49..887ec59 100644 --- a/basicswap/templates/bid_xmr.html +++ b/basicswap/templates/bid_xmr.html @@ -23,6 +23,11 @@