Browse Source

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.
pivx
tecnovert 3 years ago
parent
commit
deb71856e8
No known key found for this signature in database GPG Key ID: 8ED6D8750C4E3F93
  1. 136
      basicswap/basicswap.py
  2. 11
      basicswap/config.py
  3. 53
      basicswap/interface_btc.py
  4. 2
      basicswap/interface_xmr.py
  5. 4
      basicswap/js_server.py
  6. 5
      basicswap/templates/bid_xmr.html
  7. 10
      basicswap/types.py
  8. 20
      basicswap/ui.py
  9. 10
      basicswap/util.py
  10. 2
      bin/basicswap_prepare.py
  11. 20
      tests/basicswap/test_other.py
  12. 8
      tests/basicswap/test_run.py
  13. 2
      tests/basicswap/test_xmr.py

136
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)

11
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)

53
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

2
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'

4
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')

5
basicswap/templates/bid_xmr.html

@ -23,6 +23,11 @@
<tr><td>Expired At</td><td>{{ data.expired_at }}</td></tr>
<tr><td>Sent</td><td>{{ data.was_sent }}</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>
<form method="post">

10
basicswap/types.py

@ -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

20
basicswap/ui.py

@ -20,6 +20,9 @@ from .basicswap import (
strBidState,
strTxState,
)
from .types import (
SEQUENCE_LOCK_TIME,
)
PAGE_LIMIT = 50
@ -186,15 +189,15 @@ def describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, bid_events, edit_b
txns = []
if bid.xmr_a_lock_tx:
confirms = None
if swap_client.coin_clients[ci_from.coin_type()]['last_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
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()]['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})
if bid.xmr_a_lock_spend_tx:
txns.append({'type': 'Chain A Lock Spend', 'txid': bid.xmr_a_lock_spend_tx.txid.hex()})
if bid.xmr_b_lock_tx:
confirms = None
if swap_client.coin_clients[ci_to.coin_type()]['last_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
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()]['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})
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()})
@ -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)
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:
data['view_tx_ind'] = view_tx_ind
view_tx_id = bytes.fromhex(view_tx_ind)

10
basicswap/util.py

@ -27,16 +27,6 @@ def assert_cond(v, err='Bad opcode'):
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):
return s.lower() in ["1", "true"]

2
bin/basicswap_prepare.py

@ -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)
elif coin == 'litecoin':
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_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':

20
tests/basicswap/test_other.py

@ -30,10 +30,7 @@ from basicswap.util import (
make_int,
format_amount,
validate_amount)
from basicswap.basicswap import (
Coins,
getExpectedSequence,
decodeSequence,
from basicswap.types import (
SEQUENCE_LOCK_BLOCKS,
SEQUENCE_LOCK_TIME)
@ -57,21 +54,24 @@ class Test(unittest.TestCase):
test_case(4194642)
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
encoded = getExpectedSequence(SEQUENCE_LOCK_TIME, time_val, Coins.PART)
decoded = decodeSequence(encoded)
encoded = ci.getExpectedSequence(SEQUENCE_LOCK_TIME, time_val)
decoded = ci.decodeSequence(encoded)
assert(decoded >= time_val)
assert(decoded <= time_val + 512)
time_val = 24 * 60
encoded = getExpectedSequence(SEQUENCE_LOCK_TIME, time_val, Coins.PART)
decoded = decodeSequence(encoded)
encoded = ci.getExpectedSequence(SEQUENCE_LOCK_TIME, time_val)
decoded = ci.decodeSequence(encoded)
assert(decoded >= time_val)
assert(decoded <= time_val + 512)
blocks_val = 123
encoded = getExpectedSequence(SEQUENCE_LOCK_BLOCKS, blocks_val, Coins.PART)
decoded = decodeSequence(encoded)
encoded = ci.getExpectedSequence(SEQUENCE_LOCK_BLOCKS, blocks_val)
decoded = ci.decodeSequence(encoded)
assert(decoded == blocks_val)
def test_make_int(self):

8
tests/basicswap/test_run.py

@ -554,14 +554,14 @@ class Test(unittest.TestCase):
wait_for_bid(delay_event, swap_clients[0], bid_id)
swap_clients[0].acceptBid(bid_id)
swap_clients[0].coin_clients[Coins.BTC]['override_feerate'] = 10.0
swap_clients[0].coin_clients[Coins.LTC]['override_feerate'] = 10.0
swap_clients[0].getChainClientSettings(Coins.BTC)['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)
swap_clients[0].abandonBid(bid_id)
del swap_clients[0].coin_clients[Coins.BTC]['override_feerate']
del swap_clients[0].coin_clients[Coins.LTC]['override_feerate']
del swap_clients[0].getChainClientSettings(Coins.BTC)['override_feerate']
del swap_clients[0].getChainClientSettings(Coins.LTC)['override_feerate']
def test_08_part_ltc_buyer_first(self):
logging.info('---------- Test PART to LTC, buyer first')

2
tests/basicswap/test_xmr.py

@ -515,7 +515,7 @@ class Test(unittest.TestCase):
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)
js_w0_after = json.loads(urlopen('http://127.0.0.1:1800/json/wallets').read())

Loading…
Cancel
Save