Poll chainstates.

Litecoin download link changed.
Fix fee comparison tx weight difference.
Remove format8.
New stalled for test bid state.
Moved sequence code to coin interfaces.
Display estimated time lock refund tx will be valid.
This commit is contained in:
tecnovert 2021-02-03 16:01:27 +02:00
parent c66160fb09
commit deb71856e8
No known key found for this signature in database
GPG Key ID: 8ED6D8750C4E3F93
13 changed files with 180 additions and 103 deletions

View File

@ -16,6 +16,7 @@ import struct
import hashlib import hashlib
import secrets import secrets
import datetime as dt import datetime as dt
import threading
import traceback import traceback
import sqlalchemy as sa import sqlalchemy as sa
import collections import collections
@ -33,7 +34,6 @@ from .interface_bitcore_btc import BitcoreBTCInterface
from . import __version__ from . import __version__
from .util import ( from .util import (
pubkeyToAddress, pubkeyToAddress,
format8,
format_amount, format_amount,
format_timestamp, format_timestamp,
encodeAddress, encodeAddress,
@ -89,6 +89,11 @@ from .explorers import (
ExplorerBitAps, ExplorerBitAps,
ExplorerChainz, ExplorerChainz,
) )
from .types import (
SEQUENCE_LOCK_BLOCKS,
SEQUENCE_LOCK_TIME,
ABS_LOCK_BLOCKS,
ABS_LOCK_TIME)
import basicswap.config as cfg import basicswap.config as cfg
import basicswap.network as bsn import basicswap.network as bsn
import basicswap.protocols.atomic_swap_1 as atomic_swap_1 import basicswap.protocols.atomic_swap_1 as atomic_swap_1
@ -152,6 +157,7 @@ class BidStates(IntEnum):
SWAP_TIMEDOUT = auto() SWAP_TIMEDOUT = auto()
BID_ABANDONED = auto() # Bid will no longer be processed BID_ABANDONED = auto() # Bid will no longer be processed
BID_ERROR = auto() # An error occurred BID_ERROR = auto() # An error occurred
BID_STALLED_FOR_TEST = auto()
class TxStates(IntEnum): class TxStates(IntEnum):
@ -221,14 +227,6 @@ class DebugTypes(IntEnum):
MAKE_INVALID_PTX = auto() 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 INITIATE_TX_TIMEOUT = 40 * 60 # TODO: make variable per coin
@ -263,6 +261,8 @@ def strBidState(state):
return 'Timed-out' return 'Timed-out'
if state == BidStates.BID_ABANDONED: if state == BidStates.BID_ABANDONED:
return 'Abandoned' return 'Abandoned'
if state == BidStates.BID_STALLED_FOR_TEST:
return 'Stalled (debug)'
if state == BidStates.BID_ERROR: if state == BidStates.BID_ERROR:
return 'Error' return 'Error'
if state == BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED: 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' 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): def getVoutByAddress(txjs, p2sh):
for o in txjs['vout']: for o in txjs['vout']:
try: try:
@ -420,6 +399,29 @@ def getOfferProofOfFundsHash(offer_msg, offer_addr):
return h.digest() 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 class WatchedOutput(): # Watch for spends
__slots__ = ('bid_id', 'txid_hex', 'vout', 'tx_type', 'swap_type') __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.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 # Encode key to match network
wif_prefix = chainparams[Coins.PART][self.chain]['key_prefix'] wif_prefix = chainparams[Coins.PART][self.chain]['key_prefix']
self.network_key = toWIF(wif_prefix, decodeWif(self.settings['network_key'])) self.network_key = toWIF(wif_prefix, decodeWif(self.settings['network_key']))
@ -551,11 +556,15 @@ class BasicSwap(BaseApp):
with self.mxDB: with self.mxDB:
self.is_running = False self.is_running = False
self.delay_event.set()
if self._network: if self._network:
self._network.stopNetwork() self._network.stopNetwork()
self._network = None self._network = None
for t in self.threads:
t.join()
def setCoinConnectParams(self, coin): def setCoinConnectParams(self, coin):
# Set anything that does not require the daemon to be running # Set anything that does not require the daemon to be running
chain_client_settings = self.getChainClientSettings(coin) chain_client_settings = self.getChainClientSettings(coin)
@ -594,7 +603,6 @@ class BasicSwap(BaseApp):
'conf_target': chain_client_settings.get('conf_target', 2), 'conf_target': chain_client_settings.get('conf_target', 2),
'watched_outputs': [], 'watched_outputs': [],
'last_height_checked': last_height_checked, 'last_height_checked': last_height_checked,
'last_height': None,
'use_segwit': chain_client_settings.get('use_segwit', False), 'use_segwit': chain_client_settings.get('use_segwit', False),
'use_csv': chain_client_settings.get('use_csv', True), 'use_csv': chain_client_settings.get('use_csv', True),
'core_version_group': chain_client_settings.get('core_version_group', 0), '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'), 'chain_lookups': chain_client_settings.get('chain_lookups', 'local'),
'restore_height': chain_client_settings.get('restore_height', 0), 'restore_height': chain_client_settings.get('restore_height', 0),
'fee_priority': chain_client_settings.get('fee_priority', 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': 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.log.info('%s Core version %d', ci.coin_name(), core_version)
self.coin_clients[c]['core_version'] = 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: if c == Coins.PART:
self.coin_clients[c]['have_spent_index'] = ci.haveSpentIndex() 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' assert(coin_from != coin_to), 'coin_from == coin_to'
try: try:
coin_from_t = Coins(coin_from) coin_from_t = Coins(coin_from)
ci_from = self.ci(coin_from_t)
except Exception: except Exception:
raise ValueError('Unknown coin from type') raise ValueError('Unknown coin from type')
try: try:
coin_to_t = Coins(coin_to) coin_to_t = Coins(coin_to)
ci_to = self.ci(coin_to_t)
except Exception: except Exception:
raise ValueError('Unknown coin to type') raise ValueError('Unknown coin to type')
@ -1124,10 +1143,10 @@ class BasicSwap(BaseApp):
xmr_offer = XmrOffer() xmr_offer = XmrOffer()
# Delay before the chain a lock refund tx can be mined # 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 # 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.a_fee_rate = msg_buf.fee_rate_from
xmr_offer.b_fee_rate = msg_buf.fee_rate_to # Unused: TODO - Set priority? 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'] return self.callcoinrpc(coin_type, 'getnetworkinfo')['relayfee']
def getFeeRateForCoin(self, coin_type, conf_target=2): 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: if override_feerate:
self.log.debug('Fee rate override used for %s: %f', str(coin_type), override_feerate) self.log.debug('Fee rate override used for %s: %f', str(coin_type), override_feerate)
return override_feerate, '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={}): 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 # 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) offer = self.getOffer(offer_id)
assert(offer), 'Offer not found: {}.'.format(offer_id.hex()) assert(offer), 'Offer not found: {}.'.format(offer_id.hex())
@ -1828,6 +1848,7 @@ class BasicSwap(BaseApp):
bid.contract_count = self.getNewContractId() bid.contract_count = self.getNewContractId()
coin_from = Coins(offer.coin_from) coin_from = Coins(offer.coin_from)
ci_from = self.ci(coin_from)
bid_date = dt.datetime.fromtimestamp(bid.created_at).date() bid_date = dt.datetime.fromtimestamp(bid.created_at).date()
secret = self.getContractSecret(bid_date, bid.contract_count) secret = self.getContractSecret(bid_date, bid.contract_count)
@ -1842,7 +1863,7 @@ class BasicSwap(BaseApp):
script = bid.initiate_tx.script script = bid.initiate_tx.script
else: else:
if offer.lock_type < ABS_LOCK_BLOCKS: 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) script = atomic_swap_1.buildContractScript(sequence, secret_hash, bid.pkhash_buyer, pkhash_refund)
else: else:
if offer.lock_type == ABS_LOCK_BLOCKS: if offer.lock_type == ABS_LOCK_BLOCKS:
@ -1907,7 +1928,7 @@ class BasicSwap(BaseApp):
def postXmrBid(self, offer_id, amount, addr_send_from=None): 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 # Bid to send bid.amount * offer.rate of coin_to in exchange for bid.amount of coin_from
# Send MSG1L F -> L # 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() self.mxDB.acquire()
try: try:
@ -2187,13 +2208,14 @@ class BasicSwap(BaseApp):
def createInitiateTxn(self, coin_type, bid_id, bid, initiate_script): def createInitiateTxn(self, coin_type, bid_id, bid, initiate_script):
if self.coin_clients[coin_type]['connection_type'] != 'rpc': if self.coin_clients[coin_type]['connection_type'] != 'rpc':
return None return None
ci = self.ci(coin_type)
if self.coin_clients[coin_type]['use_segwit']: if self.coin_clients[coin_type]['use_segwit']:
addr_to = self.encodeSegwitP2WSH(coin_type, getP2WSH(initiate_script)) addr_to = self.encodeSegwitP2WSH(coin_type, getP2WSH(initiate_script))
else: else:
addr_to = self.getScriptAddress(coin_type, initiate_script) 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()) 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 = { options = {
'lockUnspents': True, 'lockUnspents': True,
@ -2207,6 +2229,7 @@ class BasicSwap(BaseApp):
self.log.debug('deriveParticipateScript for bid %s', bid_id.hex()) self.log.debug('deriveParticipateScript for bid %s', bid_id.hex())
coin_to = Coins(offer.coin_to) coin_to = Coins(offer.coin_to)
ci_to = self.ci(coin_to)
bid_date = dt.datetime.fromtimestamp(bid.created_at).date() 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 # Participate txn is locked for half the time of the initiate txn
lock_value = offer.lock_value // 2 lock_value = offer.lock_value // 2
if offer.lock_type < ABS_LOCK_BLOCKS: 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) participate_script = atomic_swap_1.buildContractScript(sequence, secret_hash, pkhash_seller, pkhash_buyer_refund)
else: else:
# Lock from the height or time of the block containing the initiate txn # 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': if self.coin_clients[coin_to]['connection_type'] != 'rpc':
return None return None
ci = self.ci(coin_to)
amount_to = bid.amount_to amount_to = bid.amount_to
# Check required? # Check required?
@ -2275,7 +2299,7 @@ class BasicSwap(BaseApp):
else: else:
addr_to = self.getScriptAddress(coin_to, participate_script) 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 = { options = {
'lockUnspents': True, 'lockUnspents': True,
'conf_target': self.coin_clients[coin_to]['conf_target'], '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): 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)) self.log.debug('createRedeemTxn for coin %s', str(coin_type))
ci = self.ci(coin_type)
if for_txn_type == 'participate': if for_txn_type == 'participate':
prev_txnid = bid.participate_tx.txid.hex() prev_txnid = bid.participate_tx.txid.hex()
@ -2334,7 +2359,7 @@ class BasicSwap(BaseApp):
'vout': prev_n, 'vout': prev_n,
'scriptPubKey': script_pub_key, 'scriptPubKey': script_pub_key,
'redeemScript': txn_script.hex(), 'redeemScript': txn_script.hex(),
'amount': format8(prev_amount)} 'amount': ci.format_amount(prev_amount)}
bid_date = dt.datetime.fromtimestamp(bid.created_at).date() bid_date = dt.datetime.fromtimestamp(bid.created_at).date()
wif_prefix = chainparams[Coins.PART][self.chain]['key_prefix'] wif_prefix = chainparams[Coins.PART][self.chain]['key_prefix']
@ -2357,7 +2382,6 @@ class BasicSwap(BaseApp):
tx_vsize = self.getContractSpendTxVSize(coin_type) tx_vsize = self.getContractSpendTxVSize(coin_type)
tx_fee = (fee_rate * tx_vsize) / 1000 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)) 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) amount_out = prev_amount - ci.make_int(tx_fee, r=1)
@ -2373,7 +2397,7 @@ class BasicSwap(BaseApp):
else: else:
addr_redeem_out = replaceAddrPrefix(addr_redeem_out, Coins.PART, self.chain) addr_redeem_out = replaceAddrPrefix(addr_redeem_out, Coins.PART, self.chain)
self.log.debug('addr_redeem_out %s', addr_redeem_out) 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: if coin_type == Coins.PART:
redeem_txn = self.calltx('-create' + prevout_s + output_to) redeem_txn = self.calltx('-create' + prevout_s + output_to)
else: else:
@ -2471,7 +2495,7 @@ class BasicSwap(BaseApp):
addr_refund_out = replaceAddrPrefix(addr_refund_out, Coins.PART, self.chain) addr_refund_out = replaceAddrPrefix(addr_refund_out, Coins.PART, self.chain)
self.log.debug('addr_refund_out %s', addr_refund_out) 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: if coin_type == Coins.PART:
refund_txn = self.calltx('-create' + prevout_s + output_to) refund_txn = self.calltx('-create' + prevout_s + output_to)
else: else:
@ -2700,8 +2724,8 @@ class BasicSwap(BaseApp):
refund_tx = bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND] refund_tx = bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND]
if bid.was_received: if bid.was_received:
if bid.debug_ind == DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND: 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) self.log.debug('XMR bid %s: Stalling bid for testing: %d.', bid_id.hex(), bid.debug_ind)
bid.setState(BidStates.BID_ABANDONED) bid.setState(BidStates.BID_STALLED_FOR_TEST)
rv = True rv = True
self.saveBidInSession(bid_id, bid, session, xmr_swap) self.saveBidInSession(bid_id, bid, session, xmr_swap)
self.logBidEvent(bid, EventLogTypes.DEBUG_TWEAK_APPLIED, 'ind {}'.format(bid.debug_ind), session) self.logBidEvent(bid, EventLogTypes.DEBUG_TWEAK_APPLIED, 'ind {}'.format(bid.debug_ind), session)
@ -2799,7 +2823,6 @@ class BasicSwap(BaseApp):
bid_changed = False bid_changed = False
a_lock_tx_dest = ci_from.getScriptDest(xmr_swap.a_lock_tx_script) 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) 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: if len(utxos) < 1:
return rv 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: 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() 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: if chain_height - bid.xmr_b_lock_tx.chain_height >= ci_to.blocks_confirmed:
self.logBidEvent(bid, EventLogTypes.LOCK_TX_B_CONFIRMED, '', session) self.logBidEvent(bid, EventLogTypes.LOCK_TX_B_CONFIRMED, '', session)
@ -3476,7 +3498,9 @@ class BasicSwap(BaseApp):
# Validate data # Validate data
now = int(time.time()) now = int(time.time())
coin_from = Coins(offer_data.coin_from) coin_from = Coins(offer_data.coin_from)
ci_from = self.ci(coin_from)
coin_to = Coins(offer_data.coin_to) coin_to = Coins(offer_data.coin_to)
ci_to = self.ci(coin_to)
chain_from = chainparams[coin_from][self.chain] chain_from = chainparams[coin_from][self.chain]
assert(offer_data.coin_from != offer_data.coin_to), 'coin_from == coin_to' 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 = XmrOffer()
xmr_offer.offer_id = offer_id 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_1 = ci_from.getExpectedSequence(offer_data.lock_type, offer_data.lock_value)
xmr_offer.lock_time_2 = getExpectedSequence(offer_data.lock_type, offer_data.lock_value, coin_from) 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.a_fee_rate = offer_data.fee_rate_from
xmr_offer.b_fee_rate = offer_data.fee_rate_to 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(bid is not None and bid.was_sent is True), 'Unknown bidid'
assert(offer), 'Offer not found ' + bid.offer_id.hex() assert(offer), 'Offer not found ' + bid.offer_id.hex()
coin_from = Coins(offer.coin_from) coin_from = Coins(offer.coin_from)
ci_from = self.ci(coin_from)
assert(bid.expire_at > now + self._bid_expired_leeway), 'Bid expired' assert(bid.expire_at > now + self._bid_expired_leeway), 'Bid expired'
@ -3730,7 +3755,7 @@ class BasicSwap(BaseApp):
script_lock_value = int(scriptvalues[2]) script_lock_value = int(scriptvalues[2])
if use_csv: 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' assert(script_lock_value == expect_sequence), 'sequence mismatch'
else: else:
if offer.lock_type == ABS_LOCK_BLOCKS: if offer.lock_type == ABS_LOCK_BLOCKS:
@ -3803,7 +3828,7 @@ class BasicSwap(BaseApp):
if self.countAcceptedBids(bid.offer_id) > 0: if self.countAcceptedBids(bid.offer_id) > 0:
self.log.info('Not auto accepting bid %s, already have', bid.bid_id.hex()) self.log.info('Not auto accepting bid %s, already have', bid.bid_id.hex())
elif bid.amount != offer.amount_from: 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: else:
delay = random.randrange(self.min_delay_event, self.max_delay_event) 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) 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) ci_to = self.ci(coin_to)
if bid.debug_ind == DebugTypes.BID_STOP_AFTER_COIN_A_LOCK: 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) self.log.debug('XMR bid %s: Stalling bid for testing: %d.', bid_id.hex(), bid.debug_ind)
bid.setState(BidStates.BID_ABANDONED) bid.setState(BidStates.BID_STALLED_FOR_TEST)
self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer) 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) self.logBidEvent(bid, EventLogTypes.DEBUG_TWEAK_APPLIED, 'ind {}'.format(bid.debug_ind), session)
return return
@ -4890,7 +4915,8 @@ class BasicSwap(BaseApp):
now = int(time.time()) now = int(time.time())
session = scoped_session(self.session_factory) 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 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) 'LEFT JOIN transactions AS tx2 ON tx2.bid_id = bids.bid_id AND tx2.tx_type = {} '.format(TxTypes.PTX)

View File

@ -10,28 +10,29 @@ CONFIG_FILENAME = 'basicswap.json'
DEFAULT_DATADIR = '~/.basicswap' DEFAULT_DATADIR = '~/.basicswap'
DEFAULT_ALLOW_CORS = False DEFAULT_ALLOW_CORS = False
TEST_DATADIRS = os.path.expanduser(os.getenv('DATADIRS', '/tmp/basicswap')) 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 '') 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) PARTICLD = os.getenv('PARTICLD', 'particld' + bin_suffix)
PARTICL_CLI = os.getenv('PARTICL_CLI', 'particl-cli' + bin_suffix) PARTICL_CLI = os.getenv('PARTICL_CLI', 'particl-cli' + bin_suffix)
PARTICL_TX = os.getenv('PARTICL_TX', 'particl-tx' + 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) BITCOIND = os.getenv('BITCOIND', 'bitcoind' + bin_suffix)
BITCOIN_CLI = os.getenv('BITCOIN_CLI', 'bitcoin-cli' + bin_suffix) BITCOIN_CLI = os.getenv('BITCOIN_CLI', 'bitcoin-cli' + bin_suffix)
BITCOIN_TX = os.getenv('BITCOIN_TX', 'bitcoin-tx' + 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) LITECOIND = os.getenv('LITECOIND', 'litecoind' + bin_suffix)
LITECOIN_CLI = os.getenv('LITECOIN_CLI', 'litecoin-cli' + bin_suffix) LITECOIN_CLI = os.getenv('LITECOIN_CLI', 'litecoin-cli' + bin_suffix)
LITECOIN_TX = os.getenv('LITECOIN_TX', 'litecoin-tx' + 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) NAMECOIND = os.getenv('NAMECOIND', 'namecoind' + bin_suffix)
NAMECOIN_CLI = os.getenv('NAMECOIN_CLI', 'namecoin-cli' + bin_suffix) NAMECOIN_CLI = os.getenv('NAMECOIN_CLI', 'namecoin-cli' + bin_suffix)
NAMECOIN_TX = os.getenv('NAMECOIN_TX', 'namecoin-tx' + 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) XMRD = os.getenv('XMRD', 'monerod' + bin_suffix)
XMR_WALLET_RPC = os.getenv('XMR_WALLET_RPC', 'monero-wallet-rpc' + bin_suffix) XMR_WALLET_RPC = os.getenv('XMR_WALLET_RPC', 'monero-wallet-rpc' + bin_suffix)

View File

@ -62,10 +62,19 @@ from .contrib.test_framework.script import (
SegwitV0SignatureHash, SegwitV0SignatureHash,
hash160) hash160)
from .types import (
SEQUENCE_LOCK_BLOCKS,
SEQUENCE_LOCK_TIME)
from .chainparams import CoinInterface, Coins, chainparams from .chainparams import CoinInterface, Coins, chainparams
from .rpc import make_rpc_func 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): def findOutput(tx, script_pk):
for i in range(len(tx.vout)): for i in range(len(tx.vout)):
if tx.vout[i].scriptPubKey == script_pk: if tx.vout[i].scriptPubKey == script_pk:
@ -121,6 +130,27 @@ class BTCInterface(CoinInterface):
def txoType(): def txoType():
return CTxOut 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): def __init__(self, coin_settings, network, swap_client=None):
super().__init__() super().__init__()
rpc_host = coin_settings.get('rpchost', '127.0.0.1') 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.blocks_confirmed = coin_settings['blocks_confirmed']
self.setConfTarget(coin_settings['conf_target']) self.setConfTarget(coin_settings['conf_target'])
self._sc = swap_client 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): def setConfTarget(self, new_conf_target):
assert(new_conf_target >= 1 and new_conf_target < 33), 'Invalid conf_target value' 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]) block_hash = self.rpc_callback('getblockhash', [height])
return self.rpc_callback('getblockheader', [block_hash]) return self.rpc_callback('getblockheader', [block_hash])
def getBlockHeader(self, block_hash):
return self.rpc_callback('getblockheader', [block_hash])
def initialiseWallet(self, key_bytes): def initialiseWallet(self, key_bytes):
wif_prefix = chainparams[self.coin_type()][self._network]['key_prefix'] wif_prefix = chainparams[self.coin_type()][self._network]['key_prefix']
key_wif = toWIF(wif_prefix, key_bytes) 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))) tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_refund_to)))
witness_bytes = len(script_lock_refund) 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 += 4 # 1 empty, 1 true witness stack values
witness_bytes += getCompactSizeLen(witness_bytes) witness_bytes += getCompactSizeLen(witness_bytes)
vsize = self.getTxVSize(tx, add_witness_bytes=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))) tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest)))
witness_bytes = len(script_lock_refund) 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 += 1 # 1 empty stack value
witness_bytes += getCompactSizeLen(witness_bytes) witness_bytes += getCompactSizeLen(witness_bytes)
vsize = self.getTxVSize(tx, add_witness_bytes=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 = len(script_lock)
witness_bytes += 33 # sv, size 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 += 4 # 1 empty, 1 true witness stack values
witness_bytes += getCompactSizeLen(witness_bytes) witness_bytes += getCompactSizeLen(witness_bytes)
vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes) vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
@ -546,7 +579,7 @@ class BTCInterface(CoinInterface):
assert(fee_paid > 0) assert(fee_paid > 0)
witness_bytes = len(prevout_script) 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 += 2 # 2 empty witness stack values
witness_bytes += getCompactSizeLen(witness_bytes) witness_bytes += getCompactSizeLen(witness_bytes)
vsize = self.getTxVSize(tx, add_witness_bytes=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) self._log.info('tx amount, vsize, feerate: %ld, %ld, %ld', locked_coin, vsize, fee_rate_paid)
if not self.compareFeeRates(fee_rate_paid, feerate): 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 return tx_hash, locked_coin
@ -592,7 +625,7 @@ class BTCInterface(CoinInterface):
assert(fee_paid > 0) assert(fee_paid > 0)
witness_bytes = len(prevout_script) 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 += 4 # 1 empty, 1 true witness stack values
witness_bytes += getCompactSizeLen(witness_bytes) witness_bytes += getCompactSizeLen(witness_bytes)
vsize = self.getTxVSize(tx, add_witness_bytes=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) self._log.info('tx amount, vsize, feerate: %ld, %ld, %ld', tx_value, vsize, fee_rate_paid)
if not self.compareFeeRates(fee_rate_paid, feerate): if not self.compareFeeRates(fee_rate_paid, feerate):
raise ValueError('Bad fee rate') raise ValueError('Bad fee rate, expected: {}'.format(feerate))
return True return True
@ -641,7 +674,7 @@ class BTCInterface(CoinInterface):
witness_bytes = len(lock_tx_script) witness_bytes = len(lock_tx_script)
witness_bytes += 33 # sv, size 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 += 4 # 1 empty, 1 true witness stack values
witness_bytes += getCompactSizeLen(witness_bytes) witness_bytes += getCompactSizeLen(witness_bytes)
vsize = self.getTxVSize(tx, add_witness_bytes=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) 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): if not self.compareFeeRates(fee_rate_paid, feerate):
raise ValueError('Bad fee rate') raise ValueError('Bad fee rate, expected: {}'.format(feerate))
return True return True

View File

@ -69,7 +69,7 @@ class XMRInterface(CoinInterface):
self._restore_height = coin_settings.get('restore_height', 0) self._restore_height = coin_settings.get('restore_height', 0)
self.setFeePriority(coin_settings.get('fee_priority', 0)) self.setFeePriority(coin_settings.get('fee_priority', 0))
self._sc = swap_client 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): def setFeePriority(self, new_priority):
assert(new_priority >= 0 and new_priority < 4), 'Invalid fee_priority value' assert(new_priority >= 0 and new_priority < 4), 'Invalid fee_priority value'

View File

@ -8,7 +8,6 @@ import json
import urllib.parse import urllib.parse
from .util import ( from .util import (
format8,
format_timestamp, format_timestamp,
) )
from .basicswap import ( from .basicswap import (
@ -174,7 +173,8 @@ def js_bids(self, url_split, post_string, is_json):
'bid_id': b[1].hex(), 'bid_id': b[1].hex(),
'offer_id': b[2].hex(), 'offer_id': b[2].hex(),
'created_at': format_timestamp(b[0]), '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]) 'bid_state': strBidState(b[4])
} for b in bids]), 'UTF-8') } for b in bids]), 'UTF-8')

View File

@ -23,6 +23,11 @@
<tr><td>Expired At</td><td>{{ data.expired_at }}</td></tr> <tr><td>Expired At</td><td>{{ data.expired_at }}</td></tr>
<tr><td>Sent</td><td>{{ data.was_sent }}</td></tr> <tr><td>Sent</td><td>{{ data.was_sent }}</td></tr>
<tr><td>Received</td><td>{{ data.was_received }}</td></tr> <tr><td>Received</td><td>{{ data.was_received }}</td></tr>
{% if data.coin_a_lock_refund_tx_est_final != 'None' %}
<tr><td>{{ data.ticker_from }} lock refund tx valid at</td><td>{{ data.coin_a_lock_refund_tx_est_final | formatts }}</td></tr>
<tr><td>{{ data.ticker_from }} chain median time</td><td>{{ data.coin_a_last_median_time | formatts }}</td></tr>
{% endif %}
</table> </table>
<form method="post"> <form method="post">

10
basicswap/types.py Normal file
View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
SEQUENCE_LOCK_BLOCKS = 1
SEQUENCE_LOCK_TIME = 2
ABS_LOCK_BLOCKS = 3
ABS_LOCK_TIME = 4

View File

@ -20,6 +20,9 @@ from .basicswap import (
strBidState, strBidState,
strTxState, strTxState,
) )
from .types import (
SEQUENCE_LOCK_TIME,
)
PAGE_LIMIT = 50 PAGE_LIMIT = 50
@ -186,15 +189,15 @@ def describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, bid_events, edit_b
txns = [] txns = []
if bid.xmr_a_lock_tx: if bid.xmr_a_lock_tx:
confirms = None confirms = None
if swap_client.coin_clients[ci_from.coin_type()]['last_height'] and bid.xmr_a_lock_tx.chain_height: if swap_client.coin_clients[ci_from.coin_type()]['chain_height'] and bid.xmr_a_lock_tx.chain_height:
confirms = (swap_client.coin_clients[ci_from.coin_type()]['last_height'] - bid.xmr_a_lock_tx.chain_height) + 1 confirms = (swap_client.coin_clients[ci_from.coin_type()]['chain_height'] - bid.xmr_a_lock_tx.chain_height) + 1
txns.append({'type': 'Chain A Lock', 'txid': bid.xmr_a_lock_tx.txid.hex(), 'confirms': confirms}) txns.append({'type': 'Chain A Lock', 'txid': bid.xmr_a_lock_tx.txid.hex(), 'confirms': confirms})
if bid.xmr_a_lock_spend_tx: if bid.xmr_a_lock_spend_tx:
txns.append({'type': 'Chain A Lock Spend', 'txid': bid.xmr_a_lock_spend_tx.txid.hex()}) txns.append({'type': 'Chain A Lock Spend', 'txid': bid.xmr_a_lock_spend_tx.txid.hex()})
if bid.xmr_b_lock_tx: if bid.xmr_b_lock_tx:
confirms = None confirms = None
if swap_client.coin_clients[ci_to.coin_type()]['last_height'] and bid.xmr_b_lock_tx.chain_height: if swap_client.coin_clients[ci_to.coin_type()]['chain_height'] and bid.xmr_b_lock_tx.chain_height:
confirms = (swap_client.coin_clients[ci_to.coin_type()]['last_height'] - bid.xmr_b_lock_tx.chain_height) + 1 confirms = (swap_client.coin_clients[ci_to.coin_type()]['chain_height'] - bid.xmr_b_lock_tx.chain_height) + 1
txns.append({'type': 'Chain B Lock', 'txid': bid.xmr_b_lock_tx.txid.hex(), 'confirms': confirms}) txns.append({'type': 'Chain B Lock', 'txid': bid.xmr_b_lock_tx.txid.hex(), 'confirms': confirms})
if bid.xmr_b_lock_tx and bid.xmr_b_lock_tx.spend_txid: if bid.xmr_b_lock_tx and bid.xmr_b_lock_tx.spend_txid:
txns.append({'type': 'Chain B Lock Spend', 'txid': bid.xmr_b_lock_tx.spend_txid.hex()}) txns.append({'type': 'Chain B Lock Spend', 'txid': bid.xmr_b_lock_tx.spend_txid.hex()})
@ -214,6 +217,15 @@ def describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, bid_events, edit_b
data['participate_tx_spend'] = getTxSpendHex(bid, TxTypes.PTX) data['participate_tx_spend'] = getTxSpendHex(bid, TxTypes.PTX)
if offer.swap_type == SwapTypes.XMR_SWAP: if offer.swap_type == SwapTypes.XMR_SWAP:
data['coin_a_lock_refund_tx_est_final'] = 'None'
if bid.xmr_a_lock_tx and bid.xmr_a_lock_tx.block_time:
if offer.lock_type == SEQUENCE_LOCK_TIME:
raw_sequence = ci_from.getExpectedSequence(offer.lock_type, offer.lock_value)
seconds_locked = ci_from.decodeSequence(raw_sequence)
data['coin_a_lock_refund_tx_est_final'] = bid.xmr_a_lock_tx.block_time + seconds_locked
data['coin_a_last_median_time'] = swap_client.coin_clients[offer.coin_from]['chain_median_time']
if view_tx_ind: if view_tx_ind:
data['view_tx_ind'] = view_tx_ind data['view_tx_ind'] = view_tx_ind
view_tx_id = bytes.fromhex(view_tx_ind) view_tx_id = bytes.fromhex(view_tx_ind)

View File

@ -27,16 +27,6 @@ def assert_cond(v, err='Bad opcode'):
raise ValueError(err) raise ValueError(err)
def format8(i):
n = abs(i)
quotient = n // COIN
remainder = n % COIN
rv = "%d.%08d" % (quotient, remainder)
if i < 0:
rv = '-' + rv
return rv
def toBool(s): def toBool(s):
return s.lower() in ["1", "true"] return s.lower() in ["1", "true"]

View File

@ -205,7 +205,7 @@ def prepareCore(coin, version, settings, data_dir):
assert_url = 'https://raw.githubusercontent.com/tecnovert/gitian.sigs/master/%s-%s/%s/%s' % (version, os_dir_name, signing_key_name, assert_filename) assert_url = 'https://raw.githubusercontent.com/tecnovert/gitian.sigs/master/%s-%s/%s/%s' % (version, os_dir_name, signing_key_name, assert_filename)
elif coin == 'litecoin': elif coin == 'litecoin':
signing_key_name = 'thrasher' signing_key_name = 'thrasher'
release_url = 'https://download.litecoin.org/litecoin-{}/{}/{}'.format(version, os_name, release_filename) release_url = 'https://download2.litecoin.org/litecoin-{}/{}/{}'.format(version, os_name, release_filename)
assert_filename = '{}-{}-{}-build.assert'.format(coin, os_name, version.rsplit('.', 1)[0]) assert_filename = '{}-{}-{}-build.assert'.format(coin, os_name, version.rsplit('.', 1)[0])
assert_url = 'https://raw.githubusercontent.com/litecoin-project/gitian.sigs.ltc/master/%s-%s/%s/%s' % (version, os_dir_name, signing_key_name, assert_filename) assert_url = 'https://raw.githubusercontent.com/litecoin-project/gitian.sigs.ltc/master/%s-%s/%s/%s' % (version, os_dir_name, signing_key_name, assert_filename)
elif coin == 'bitcoin': elif coin == 'bitcoin':

View File

@ -30,10 +30,7 @@ from basicswap.util import (
make_int, make_int,
format_amount, format_amount,
validate_amount) validate_amount)
from basicswap.basicswap import ( from basicswap.types import (
Coins,
getExpectedSequence,
decodeSequence,
SEQUENCE_LOCK_BLOCKS, SEQUENCE_LOCK_BLOCKS,
SEQUENCE_LOCK_TIME) SEQUENCE_LOCK_TIME)
@ -57,21 +54,24 @@ class Test(unittest.TestCase):
test_case(4194642) test_case(4194642)
def test_sequence(self): def test_sequence(self):
coin_settings = {'rpcport': 0, 'rpcauth': 'none', 'blocks_confirmed': 1, 'conf_target': 1}
ci = BTCInterface(coin_settings, 'regtest')
time_val = 48 * 60 * 60 time_val = 48 * 60 * 60
encoded = getExpectedSequence(SEQUENCE_LOCK_TIME, time_val, Coins.PART) encoded = ci.getExpectedSequence(SEQUENCE_LOCK_TIME, time_val)
decoded = decodeSequence(encoded) decoded = ci.decodeSequence(encoded)
assert(decoded >= time_val) assert(decoded >= time_val)
assert(decoded <= time_val + 512) assert(decoded <= time_val + 512)
time_val = 24 * 60 time_val = 24 * 60
encoded = getExpectedSequence(SEQUENCE_LOCK_TIME, time_val, Coins.PART) encoded = ci.getExpectedSequence(SEQUENCE_LOCK_TIME, time_val)
decoded = decodeSequence(encoded) decoded = ci.decodeSequence(encoded)
assert(decoded >= time_val) assert(decoded >= time_val)
assert(decoded <= time_val + 512) assert(decoded <= time_val + 512)
blocks_val = 123 blocks_val = 123
encoded = getExpectedSequence(SEQUENCE_LOCK_BLOCKS, blocks_val, Coins.PART) encoded = ci.getExpectedSequence(SEQUENCE_LOCK_BLOCKS, blocks_val)
decoded = decodeSequence(encoded) decoded = ci.decodeSequence(encoded)
assert(decoded == blocks_val) assert(decoded == blocks_val)
def test_make_int(self): def test_make_int(self):

View File

@ -554,14 +554,14 @@ class Test(unittest.TestCase):
wait_for_bid(delay_event, swap_clients[0], bid_id) wait_for_bid(delay_event, swap_clients[0], bid_id)
swap_clients[0].acceptBid(bid_id) swap_clients[0].acceptBid(bid_id)
swap_clients[0].coin_clients[Coins.BTC]['override_feerate'] = 10.0 swap_clients[0].getChainClientSettings(Coins.BTC)['override_feerate'] = 10.0
swap_clients[0].coin_clients[Coins.LTC]['override_feerate'] = 10.0 swap_clients[0].getChainClientSettings(Coins.LTC)['override_feerate'] = 10.0
wait_for_bid(delay_event, swap_clients[0], bid_id, BidStates.BID_ERROR, wait_for=60) wait_for_bid(delay_event, swap_clients[0], bid_id, BidStates.BID_ERROR, wait_for=60)
swap_clients[0].abandonBid(bid_id) swap_clients[0].abandonBid(bid_id)
del swap_clients[0].coin_clients[Coins.BTC]['override_feerate'] del swap_clients[0].getChainClientSettings(Coins.BTC)['override_feerate']
del swap_clients[0].coin_clients[Coins.LTC]['override_feerate'] del swap_clients[0].getChainClientSettings(Coins.LTC)['override_feerate']
def test_08_part_ltc_buyer_first(self): def test_08_part_ltc_buyer_first(self):
logging.info('---------- Test PART to LTC, buyer first') logging.info('---------- Test PART to LTC, buyer first')

View File

@ -515,7 +515,7 @@ class Test(unittest.TestCase):
swap_clients[0].acceptXmrBid(bid_id) swap_clients[0].acceptXmrBid(bid_id)
wait_for_bid(delay_event, swap_clients[0], bid_id, BidStates.BID_ABANDONED, wait_for=180) wait_for_bid(delay_event, swap_clients[0], bid_id, BidStates.BID_STALLED_FOR_TEST, wait_for=180)
wait_for_bid(delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_SWIPED, wait_for=80, sent=True) wait_for_bid(delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_SWIPED, wait_for=80, sent=True)
js_w0_after = json.loads(urlopen('http://127.0.0.1:1800/json/wallets').read()) js_w0_after = json.loads(urlopen('http://127.0.0.1:1800/json/wallets').read())