Decred: Secret hash swap tests.

This commit is contained in:
tecnovert
2024-05-15 11:39:32 +02:00
parent d527ec4974
commit 76879a2ff5
24 changed files with 1025 additions and 301 deletions

View File

@@ -5,7 +5,6 @@
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import os
import re
import sys
import zmq
import copy
@@ -16,7 +15,6 @@ import random
import shutil
import string
import struct
import hashlib
import secrets
import datetime as dt
import threading
@@ -53,11 +51,13 @@ from .util.script import (
)
from .util.address import (
toWIF,
getKeyID,
decodeWif,
decodeAddress,
pubkeyToAddress,
)
from .util.crypto import (
sha256,
)
from basicswap.util.network import is_private_ip_address
from .chainparams import (
Coins,
@@ -147,7 +147,7 @@ from basicswap.db_util import (
remove_expired_data,
)
PROTOCOL_VERSION_SECRET_HASH = 4
PROTOCOL_VERSION_SECRET_HASH = 5
MINPROTO_VERSION_SECRET_HASH = 4
PROTOCOL_VERSION_ADAPTOR_SIG = 4
@@ -209,6 +209,16 @@ class WatchedOutput(): # Watch for spends
self.swap_type = swap_type
class WatchedScript(): # Watch for txns containing outputs
__slots__ = ('bid_id', 'script', 'tx_type', 'swap_type')
def __init__(self, bid_id: bytes, script: bytes, tx_type, swap_type):
self.bid_id = bid_id
self.script = script
self.tx_type = tx_type
self.swap_type = swap_type
class WatchedTransaction():
# TODO
# Watch for presence in mempool (getrawtransaction)
@@ -454,6 +464,10 @@ class BasicSwap(BaseApp):
last_height_checked = session.query(DBKVInt).filter_by(key='last_height_checked_' + chainparams[coin]['name']).first().value
except Exception:
last_height_checked = 0
try:
block_check_min_time = session.query(DBKVInt).filter_by(key='block_check_min_time_' + chainparams[coin]['name']).first().value
except Exception:
block_check_min_time = 0xffffffffffffffff
session.close()
session.remove()
@@ -472,7 +486,9 @@ class BasicSwap(BaseApp):
'blocks_confirmed': chain_client_settings.get('blocks_confirmed', 6),
'conf_target': chain_client_settings.get('conf_target', 2),
'watched_outputs': [],
'watched_scripts': [],
'last_height_checked': last_height_checked,
'block_check_min_time': block_check_min_time,
'use_segwit': chain_client_settings.get('use_segwit', default_segwit),
'use_csv': chain_client_settings.get('use_csv', default_csv),
'core_version_group': chain_client_settings.get('core_version_group', 0),
@@ -1150,12 +1166,17 @@ class BasicSwap(BaseApp):
if bid.participate_tx and bid.participate_tx.txid:
self.addWatchedOutput(coin_to, bid.bid_id, bid.participate_tx.txid.hex(), bid.participate_tx.vout, BidStates.SWAP_PARTICIPATING)
if bid.participate_tx and bid.participate_tx.txid is None:
self.addWatchedScript(coin_to, bid.bid_id, self.ci(coin_to).getScriptDest(bid.participate_tx.script), TxTypes.PTX)
if bid.initiate_tx and bid.initiate_tx.chain_height:
self.setLastHeightCheckedStart(coin_to, bid.initiate_tx.chain_height)
if self.coin_clients[coin_from]['last_height_checked'] < 1:
if bid.initiate_tx and bid.initiate_tx.chain_height:
self.coin_clients[coin_from]['last_height_checked'] = bid.initiate_tx.chain_height
self.setLastHeightCheckedStart(coin_from, bid.initiate_tx.chain_height)
if self.coin_clients[coin_to]['last_height_checked'] < 1:
if bid.participate_tx and bid.participate_tx.chain_height:
self.coin_clients[coin_to]['last_height_checked'] = bid.participate_tx.chain_height
self.setLastHeightCheckedStart(coin_to, bid.participate_tx.chain_height)
# TODO process addresspool if bid has previously been abandoned
@@ -1172,6 +1193,9 @@ class BasicSwap(BaseApp):
self.removeWatchedOutput(Coins(offer.coin_from), bid.bid_id, None)
self.removeWatchedOutput(Coins(offer.coin_to), bid.bid_id, None)
self.removeWatchedScript(Coins(offer.coin_from), bid.bid_id, None)
self.removeWatchedScript(Coins(offer.coin_to), bid.bid_id, None)
if bid.state in (BidStates.BID_ABANDONED, BidStates.SWAP_COMPLETED):
# Return unused addrs to pool
itx_state = bid.getITxState()
@@ -1859,7 +1883,7 @@ class BasicSwap(BaseApp):
path += '/' + str(date.year) + '/' + str(date.month) + '/' + str(date.day)
path += '/' + str(contract_count)
return hashlib.sha256(bytes(self.callcoinrpc(Coins.PART, 'extkey', ['info', evkey, path])['key_info']['result'], 'utf-8')).digest()
return sha256(bytes(self.callcoinrpc(Coins.PART, 'extkey', ['info', evkey, path])['key_info']['result'], 'utf-8'))
def getReceiveAddressFromPool(self, coin_type, bid_id: bytes, tx_type):
self.log.debug('Get address from pool bid_id {}, type {}, coin {}'.format(bid_id.hex(), tx_type, coin_type))
@@ -2337,7 +2361,12 @@ class BasicSwap(BaseApp):
msg_buf.proof_utxos = ci_to.encodeProofUtxos(proof_utxos)
contract_count = self.getNewContractId()
msg_buf.pkhash_buyer = getKeyID(self.getContractPubkey(dt.datetime.fromtimestamp(now).date(), contract_count))
contract_pubkey = self.getContractPubkey(dt.datetime.fromtimestamp(now).date(), contract_count)
msg_buf.pkhash_buyer = ci_from.pkh(contract_pubkey)
pkhash_buyer_to = ci_to.pkh(contract_pubkey)
if pkhash_buyer_to != msg_buf.pkhash_buyer:
# Different pubkey hash
msg_buf.pkhash_buyer_to = pkhash_buyer_to
else:
raise ValueError('TODO')
@@ -2370,6 +2399,9 @@ class BasicSwap(BaseApp):
)
bid.setState(BidStates.BID_SENT)
if len(msg_buf.pkhash_buyer_to) > 0:
bid.pkhash_buyer_to = msg_buf.pkhash_buyer_to
try:
session = scoped_session(self.session_factory)
self.saveBidInSession(bid_id, bid, session)
@@ -2554,13 +2586,19 @@ class BasicSwap(BaseApp):
coin_from = Coins(offer.coin_from)
ci_from = self.ci(coin_from)
ci_to = self.ci(offer.coin_to)
bid_date = dt.datetime.fromtimestamp(bid.created_at).date()
secret = self.getContractSecret(bid_date, bid.contract_count)
secret_hash = hashlib.sha256(secret).digest()
secret_hash = sha256(secret)
pubkey_refund = self.getContractPubkey(bid_date, bid.contract_count)
pkhash_refund = getKeyID(pubkey_refund)
pkhash_refund = ci_from.pkh(pubkey_refund)
if coin_from in (Coins.DCR, ):
op_hash = OpCodes.OP_SHA256_DECRED
else:
op_hash = OpCodes.OP_SHA256
if bid.initiate_tx is not None:
self.log.warning('Initiate txn %s already exists for bid %s', bid.initiate_tx.txid, bid_id.hex())
@@ -2569,21 +2607,19 @@ class BasicSwap(BaseApp):
else:
if offer.lock_type < TxLockTypes.ABS_LOCK_BLOCKS:
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, op_hash=op_hash)
else:
if offer.lock_type == TxLockTypes.ABS_LOCK_BLOCKS:
lock_value = self.callcoinrpc(coin_from, 'getblockcount') + offer.lock_value
lock_value = ci_from.getChainHeight() + offer.lock_value
else:
lock_value = self.getTime() + offer.lock_value
self.log.debug('Initiate %s lock_value %d %d', ci_from.coin_name(), offer.lock_value, lock_value)
script = atomic_swap_1.buildContractScript(lock_value, secret_hash, bid.pkhash_buyer, pkhash_refund, OpCodes.OP_CHECKLOCKTIMEVERIFY)
script = atomic_swap_1.buildContractScript(lock_value, secret_hash, bid.pkhash_buyer, pkhash_refund, OpCodes.OP_CHECKLOCKTIMEVERIFY, op_hash=op_hash)
p2sh = self.callcoinrpc(Coins.PART, 'decodescript', [script.hex()])['p2sh']
bid.pkhash_seller = pkhash_refund
bid.pkhash_seller = ci_to.pkh(pubkey_refund)
prefunded_tx = self.getPreFundedTx(Concepts.OFFER, offer.offer_id, TxTypes.ITX_PRE_FUNDED)
txn = self.createInitiateTxn(coin_from, bid_id, bid, script, prefunded_tx)
txn, lock_tx_vout = self.createInitiateTxn(coin_from, bid_id, bid, script, prefunded_tx)
# Store the signed refund txn in case wallet is locked when refund is possible
refund_txn = self.createRefundTxn(coin_from, txn, offer, bid, script)
@@ -2595,6 +2631,7 @@ class BasicSwap(BaseApp):
bid_id=bid_id,
tx_type=TxTypes.ITX,
txid=bytes.fromhex(txid),
vout=lock_tx_vout,
tx_data=bytes.fromhex(txn),
script=script,
)
@@ -2615,6 +2652,11 @@ class BasicSwap(BaseApp):
msg_buf.initiate_txid = bytes.fromhex(txid)
msg_buf.contract_script = bytes(script)
# pkh sent in script is hashed with sha256, Decred expects blake256
if bid.pkhash_seller != pkhash_refund:
assert (ci_to.coin_type() == Coins.DCR or ci_from.coin_type() == Coins.DCR) # [rm]
msg_buf.pkhash_seller = bid.pkhash_seller
bid_bytes = msg_buf.SerializeToString()
payload_hex = str.format('{:02x}', MessageTypes.BID_ACCEPT) + bid_bytes.hex()
@@ -3137,9 +3179,9 @@ class BasicSwap(BaseApp):
if save_bid:
self.saveBid(bid_id, bid, xmr_swap=xmr_swap)
def createInitiateTxn(self, coin_type, bid_id: bytes, bid, initiate_script, prefunded_tx=None) -> Optional[str]:
def createInitiateTxn(self, coin_type, bid_id: bytes, bid, initiate_script, prefunded_tx=None) -> (Optional[str], Optional[int]):
if self.coin_clients[coin_type]['connection_type'] != 'rpc':
return None
return None, None
ci = self.ci(coin_type)
if ci.using_segwit():
@@ -3154,7 +3196,12 @@ class BasicSwap(BaseApp):
txn_signed = pi.promoteMockTx(ci, prefunded_tx, initiate_script).hex()
else:
txn_signed = ci.createRawSignedTransaction(addr_to, bid.amount)
return txn_signed
txjs = ci.describeTx(txn_signed)
vout = getVoutByAddress(txjs, addr_to)
assert (vout is not None)
return txn_signed, vout
def deriveParticipateScript(self, bid_id: bytes, bid, offer) -> bytearray:
self.log.debug('deriveParticipateScript for bid %s', bid_id.hex())
@@ -3164,18 +3211,28 @@ class BasicSwap(BaseApp):
secret_hash = atomic_swap_1.extractScriptSecretHash(bid.initiate_tx.script)
pkhash_seller = bid.pkhash_seller
pkhash_buyer_refund = bid.pkhash_buyer
if bid.pkhash_buyer_to and len(bid.pkhash_buyer_to) > 0:
pkhash_buyer_refund = bid.pkhash_buyer_to
else:
pkhash_buyer_refund = bid.pkhash_buyer
if coin_to in (Coins.DCR, ):
op_hash = OpCodes.OP_SHA256_DECRED
else:
op_hash = OpCodes.OP_SHA256
# Participate txn is locked for half the time of the initiate txn
lock_value = offer.lock_value // 2
if offer.lock_type < TxLockTypes.ABS_LOCK_BLOCKS:
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, op_hash=op_hash)
else:
# Lock from the height or time of the block containing the initiate txn
coin_from = Coins(offer.coin_from)
initiate_tx_block_hash = self.callcoinrpc(coin_from, 'getblockhash', [bid.initiate_tx.chain_height, ])
initiate_tx_block_time = int(self.callcoinrpc(coin_from, 'getblock', [initiate_tx_block_hash, ])['time'])
block_header = self.ci(coin_from).getBlockHeaderFromHeight(bid.initiate_tx.chain_height)
initiate_tx_block_hash = block_header['hash']
initiate_tx_block_time = block_header['time']
if offer.lock_type == TxLockTypes.ABS_LOCK_BLOCKS:
# Walk the coin_to chain back until block time matches
block_header_at = ci_to.getBlockHeaderAt(initiate_tx_block_time, block_after=True)
@@ -3188,7 +3245,7 @@ class BasicSwap(BaseApp):
self.log.debug('Setting lock value from time of block %s %s', Coins(coin_from).name, initiate_tx_block_hash)
contract_lock_value = initiate_tx_block_time + lock_value
self.log.debug('participate %s lock_value %d %d', Coins(coin_to).name, lock_value, contract_lock_value)
participate_script = atomic_swap_1.buildContractScript(contract_lock_value, secret_hash, pkhash_seller, pkhash_buyer_refund, OpCodes.OP_CHECKLOCKTIMEVERIFY)
participate_script = atomic_swap_1.buildContractScript(contract_lock_value, secret_hash, pkhash_seller, pkhash_buyer_refund, OpCodes.OP_CHECKLOCKTIMEVERIFY, op_hash=op_hash)
return participate_script
def createParticipateTxn(self, bid_id: bytes, bid, offer, participate_script: bytearray):
@@ -3219,7 +3276,7 @@ class BasicSwap(BaseApp):
refund_txn = self.createRefundTxn(coin_to, txn_signed, offer, bid, participate_script, tx_type=TxTypes.PTX_REFUND)
bid.participate_txn_refund = bytes.fromhex(refund_txn)
chain_height = self.callcoinrpc(coin_to, 'getblockcount')
chain_height = ci.getChainHeight()
txjs = self.callcoinrpc(coin_to, 'decoderawtransaction', [txn_signed])
txid = txjs['txid']
@@ -3252,7 +3309,7 @@ class BasicSwap(BaseApp):
prev_p2wsh = ci.getScriptDest(txn_script)
script_pub_key = prev_p2wsh.hex()
else:
script_pub_key = getP2SHScriptForHash(getKeyID(txn_script)).hex()
script_pub_key = getP2SHScriptForHash(ci.pkh(txn_script)).hex()
prevout = {
'txid': prev_txnid,
@@ -3262,18 +3319,17 @@ class BasicSwap(BaseApp):
'amount': ci.format_amount(prev_amount)}
bid_date = dt.datetime.fromtimestamp(bid.created_at).date()
if coin_type in (Coins.NAV, ):
wif_prefix = chainparams[coin_type][self.chain]['key_prefix']
else:
wif_prefix = chainparams[Coins.PART][self.chain]['key_prefix']
pubkey = self.getContractPubkey(bid_date, bid.contract_count)
privkey = toWIF(wif_prefix, self.getContractPrivkey(bid_date, bid.contract_count))
privkey = self.getContractPrivkey(bid_date, bid.contract_count)
pubkey = ci.getPubkey(privkey)
secret = bid.recovered_secret
if secret is None:
secret = self.getContractSecret(bid_date, bid.contract_count)
ensure(len(secret) == 32, 'Bad secret length')
self.log.debug('secret {}'.format(secret.hex()))
self.log.debug('sha256(secret) {}'.format(sha256(secret).hex()))
if self.coin_clients[coin_type]['connection_type'] != 'rpc':
return None
@@ -3294,40 +3350,40 @@ class BasicSwap(BaseApp):
self.log.debug('addr_redeem_out %s', addr_redeem_out)
if ci.use_p2shp2wsh():
redeem_txn = ci.createRedeemTxn(prevout, addr_redeem_out, amount_out, txn_script)
else:
redeem_txn = ci.createRedeemTxn(prevout, addr_redeem_out, amount_out)
redeem_txn = ci.createRedeemTxn(prevout, addr_redeem_out, amount_out, txn_script)
options = {}
if ci.using_segwit():
options['force_segwit'] = True
if coin_type in (Coins.NAV, ):
redeem_sig = ci.getTxSignature(redeem_txn, prevout, privkey)
if coin_type in (Coins.NAV, Coins.DCR):
privkey_wif = self.ci(coin_type).encodeKey(privkey)
redeem_sig = ci.getTxSignature(redeem_txn, prevout, privkey_wif)
else:
redeem_sig = self.callcoinrpc(Coins.PART, 'createsignaturewithkey', [redeem_txn, prevout, privkey, 'ALL', options])
privkey_wif = self.ci(Coins.PART).encodeKey(privkey)
redeem_sig = self.callcoinrpc(Coins.PART, 'createsignaturewithkey', [redeem_txn, prevout, privkey_wif, 'ALL', options])
if coin_type == Coins.PART or ci.using_segwit():
witness_stack = [
bytes.fromhex(redeem_sig),
pubkey,
secret,
bytes((1,)),
bytes((1,)), # Converted to OP_1 in Decred push_script_data
txn_script]
redeem_txn = ci.setTxSignature(bytes.fromhex(redeem_txn), witness_stack).hex()
else:
script = format(len(redeem_sig) // 2, '02x') + redeem_sig
script += format(33, '02x') + pubkey.hex()
script += format(32, '02x') + secret.hex()
script += format(OpCodes.OP_1, '02x')
script += format(OpCodes.OP_PUSHDATA1, '02x') + format(len(txn_script), '02x') + txn_script.hex()
redeem_txn = ci.setTxScriptSig(bytes.fromhex(redeem_txn), 0, bytes.fromhex(script)).hex()
script = (len(redeem_sig) // 2).to_bytes(1) + bytes.fromhex(redeem_sig)
script += (33).to_bytes(1) + pubkey
script += (32).to_bytes(1) + secret
script += (OpCodes.OP_1).to_bytes(1)
script += (OpCodes.OP_PUSHDATA1).to_bytes(1) + (len(txn_script)).to_bytes(1) + txn_script
redeem_txn = ci.setTxScriptSig(bytes.fromhex(redeem_txn), 0, script).hex()
if coin_type in (Coins.NAV, ):
if coin_type in (Coins.NAV, Coins.DCR):
# Only checks signature
ro = ci.verifyRawTransaction(redeem_txn, [prevout])
else:
ro = self.callcoinrpc(Coins.PART, 'verifyrawtransaction', [redeem_txn, [prevout]])
ensure(ro['inputs_valid'] is True, 'inputs_valid is false')
# outputs_valid will be false if not a Particl txn
# ensure(ro['complete'] is True, 'complete is false')
@@ -3337,7 +3393,11 @@ class BasicSwap(BaseApp):
# Check fee
if ci.get_connection_type() == 'rpc':
redeem_txjs = self.callcoinrpc(coin_type, 'decoderawtransaction', [redeem_txn])
if ci.using_segwit() or coin_type in (Coins.PART, ):
if coin_type in (Coins.DCR, ):
txsize = len(redeem_txn) // 2
self.log.debug('size paid, actual size %d %d', tx_vsize, txsize)
ensure(tx_vsize >= txsize, 'underpaid fee')
elif ci.use_tx_vsize():
self.log.debug('vsize paid, actual vsize %d %d', tx_vsize, redeem_txjs['vsize'])
ensure(tx_vsize >= redeem_txjs['vsize'], 'underpaid fee')
else:
@@ -3355,11 +3415,9 @@ class BasicSwap(BaseApp):
ci = self.ci(coin_type)
if coin_type in (Coins.NAV, Coins.DCR):
wif_prefix = chainparams[coin_type][self.chain]['key_prefix']
prevout = ci.find_prevout_info(txn, txn_script)
else:
# TODO: Sign in bsx for all coins
wif_prefix = chainparams[Coins.PART][self.chain]['key_prefix']
txjs = self.callcoinrpc(Coins.PART, 'decoderawtransaction', [txn])
if ci.using_segwit():
p2wsh = ci.getScriptDest(txn_script)
@@ -3377,8 +3435,9 @@ class BasicSwap(BaseApp):
}
bid_date = dt.datetime.fromtimestamp(bid.created_at).date()
pubkey = self.getContractPubkey(bid_date, bid.contract_count)
privkey = toWIF(wif_prefix, self.getContractPrivkey(bid_date, bid.contract_count))
privkey = self.getContractPrivkey(bid_date, bid.contract_count)
pubkey = ci.getPubkey(privkey)
lock_value = DeserialiseNum(txn_script, 64)
sequence: int = 1
@@ -3405,19 +3464,25 @@ class BasicSwap(BaseApp):
if offer.lock_type == TxLockTypes.ABS_LOCK_BLOCKS or offer.lock_type == TxLockTypes.ABS_LOCK_TIME:
locktime = lock_value
if ci.use_p2shp2wsh():
refund_txn = ci.createRefundTxn(prevout, addr_refund_out, amount_out, locktime, sequence, txn_script)
else:
refund_txn = ci.createRefundTxn(prevout, addr_refund_out, amount_out, locktime, sequence)
refund_txn = ci.createRefundTxn(prevout, addr_refund_out, amount_out, locktime, sequence, txn_script)
options = {}
if self.coin_clients[coin_type]['use_segwit']:
options['force_segwit'] = True
if coin_type in (Coins.NAV, Coins.DCR):
refund_sig = ci.getTxSignature(refund_txn, prevout, privkey)
privkey_wif = ci.encodeKey(privkey)
refund_sig = ci.getTxSignature(refund_txn, prevout, privkey_wif)
else:
refund_sig = self.callcoinrpc(Coins.PART, 'createsignaturewithkey', [refund_txn, prevout, privkey, 'ALL', options])
if coin_type == Coins.PART or self.coin_clients[coin_type]['use_segwit']:
privkey_wif = self.ci(Coins.PART).encodeKey(privkey)
refund_sig = self.callcoinrpc(Coins.PART, 'createsignaturewithkey', [refund_txn, prevout, privkey_wif, 'ALL', options])
if coin_type in (Coins.DCR, ):
witness_stack = [
bytes.fromhex(refund_sig),
pubkey,
(OpCodes.OP_0).to_bytes(1),
txn_script]
refund_txn = ci.setTxSignature(bytes.fromhex(refund_txn), witness_stack).hex()
elif coin_type in (Coins.PART, ) or self.coin_clients[coin_type]['use_segwit']:
witness_stack = [
bytes.fromhex(refund_sig),
pubkey,
@@ -3425,11 +3490,11 @@ class BasicSwap(BaseApp):
txn_script]
refund_txn = ci.setTxSignature(bytes.fromhex(refund_txn), witness_stack).hex()
else:
script = format(len(refund_sig) // 2, '02x') + refund_sig
script += format(33, '02x') + pubkey.hex()
script += format(OpCodes.OP_0, '02x')
script += format(OpCodes.OP_PUSHDATA1, '02x') + format(len(txn_script), '02x') + txn_script.hex()
refund_txn = ci.setTxScriptSig(bytes.fromhex(refund_txn), 0, bytes.fromhex(script)).hex()
script = (len(refund_sig) // 2).to_bytes(1) + bytes.fromhex(refund_sig)
script += (33).to_bytes(1) + pubkey
script += (OpCodes.OP_0).to_bytes(1)
script += (OpCodes.OP_PUSHDATA1).to_bytes(1) + (len(txn_script)).to_bytes(1) + txn_script
refund_txn = ci.setTxScriptSig(bytes.fromhex(refund_txn), 0, script)
if coin_type in (Coins.NAV, Coins.DCR):
# Only checks signature
@@ -3445,8 +3510,12 @@ class BasicSwap(BaseApp):
if self.debug:
# Check fee
if ci.get_connection_type() == 'rpc':
refund_txjs = self.callcoinrpc(coin_type, 'decoderawtransaction', [refund_txn])
if ci.using_segwit() or coin_type in (Coins.PART, ):
refund_txjs = self.callcoinrpc(coin_type, 'decoderawtransaction', [refund_txn,])
if coin_type in (Coins.DCR, ):
txsize = len(refund_txn) // 2
self.log.debug('size paid, actual size %d %d', tx_vsize, txsize)
ensure(tx_vsize >= txsize, 'underpaid fee')
elif ci.use_tx_vsize():
self.log.debug('vsize paid, actual vsize %d %d', tx_vsize, refund_txjs['vsize'])
ensure(tx_vsize >= refund_txjs['vsize'], 'underpaid fee')
else:
@@ -3490,20 +3559,31 @@ class BasicSwap(BaseApp):
tx_type=TxTypes.PTX,
script=participate_script,
)
ci = self.ci(offer.coin_to)
if ci.watch_blocks_for_scripts() is True:
self.addWatchedScript(offer.coin_to, bid_id, ci.getScriptDest(participate_script), TxTypes.PTX)
self.setLastHeightCheckedStart(offer.coin_to, bid.initiate_tx.chain_height)
# Bid saved in checkBidState
def setLastHeightChecked(self, coin_type, tx_height: int) -> int:
coin_name = self.ci(coin_type).coin_name()
def setLastHeightCheckedStart(self, coin_type, tx_height: int) -> int:
ci = self.ci(coin_type)
coin_name = ci.coin_name()
if tx_height < 1:
tx_height = self.lookupChainHeight(coin_type)
if len(self.coin_clients[coin_type]['watched_outputs']) == 0:
self.coin_clients[coin_type]['last_height_checked'] = tx_height
block_header = ci.getBlockHeaderFromHeight(tx_height)
block_time = block_header['time']
cc = self.coin_clients[coin_type]
if len(cc['watched_outputs']) == 0 and len(cc['watched_scripts']) == 0:
cc['last_height_checked'] = tx_height
cc['block_check_min_time'] = block_time
self.setIntKV('block_check_min_time_' + coin_name, block_time)
self.log.debug('Start checking %s chain at height %d', coin_name, tx_height)
if self.coin_clients[coin_type]['last_height_checked'] > tx_height:
self.coin_clients[coin_type]['last_height_checked'] = tx_height
elif cc['last_height_checked'] > tx_height:
cc['last_height_checked'] = tx_height
cc['block_check_min_time'] = block_time
self.setIntKV('block_check_min_time_' + coin_name, block_time)
self.log.debug('Rewind checking of %s chain to height %d', coin_name, tx_height)
return tx_height
@@ -3511,7 +3591,7 @@ class BasicSwap(BaseApp):
def addParticipateTxn(self, bid_id: bytes, bid, coin_type, txid_hex: str, vout, tx_height) -> None:
# TODO: Check connection type
participate_txn_height = self.setLastHeightChecked(coin_type, tx_height)
participate_txn_height = self.setLastHeightCheckedStart(coin_type, tx_height)
if bid.participate_tx is None:
bid.participate_tx = SwapTx(
@@ -3529,6 +3609,11 @@ class BasicSwap(BaseApp):
def participateTxnConfirmed(self, bid_id: bytes, bid, offer) -> None:
self.log.debug('participateTxnConfirmed for bid %s', bid_id.hex())
if bid.debug_ind == DebugTypes.DONT_CONFIRM_PTX:
self.log.debug('Not confirming PTX for debugging', bid_id.hex())
return
bid.setState(BidStates.SWAP_PARTICIPATING)
bid.setPTxState(TxStates.TX_CONFIRMED)
@@ -3774,9 +3859,9 @@ class BasicSwap(BaseApp):
return rv
# TODO: Timeout waiting for transactions
bid_changed = False
bid_changed: bool = False
a_lock_tx_addr = ci_from.getSCLockScriptAddress(xmr_swap.a_lock_tx_script)
lock_tx_chain_info = ci_from.getLockTxHeight(bid.xmr_a_lock_tx.txid, a_lock_tx_addr, bid.amount, bid.chain_a_height_start)
lock_tx_chain_info = ci_from.getLockTxHeight(bid.xmr_a_lock_tx.txid, a_lock_tx_addr, bid.amount, bid.chain_a_height_start, vout=bid.xmr_a_lock_tx.vout)
if lock_tx_chain_info is None:
return rv
@@ -3863,7 +3948,7 @@ class BasicSwap(BaseApp):
refund_tx = bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND]
if refund_tx.block_time is None:
refund_tx_addr = ci_from.getSCLockScriptAddress(xmr_swap.a_lock_refund_tx_script)
lock_refund_tx_chain_info = ci_from.getLockTxHeight(refund_tx.txid, refund_tx_addr, 0, bid.chain_a_height_start)
lock_refund_tx_chain_info = ci_from.getLockTxHeight(refund_tx.txid, refund_tx_addr, 0, bid.chain_a_height_start, vout=refund_tx.vout)
if lock_refund_tx_chain_info is not None and lock_refund_tx_chain_info.get('height', 0) > 0:
self.setTxBlockInfoFromHeight(ci_from, refund_tx, lock_refund_tx_chain_info['height'])
@@ -3904,14 +3989,14 @@ class BasicSwap(BaseApp):
return True # Mark bid for archiving
if state == BidStates.BID_ACCEPTED:
# Waiting for initiate txn to be confirmed in 'from' chain
initiate_txnid_hex = bid.initiate_tx.txid.hex()
p2sh = ci_from.encode_p2sh(bid.initiate_tx.script)
index = None
tx_height = None
initiate_txnid_hex = bid.initiate_tx.txid.hex()
last_initiate_txn_conf = bid.initiate_tx.conf
ci_from = self.ci(coin_from)
if coin_from == Coins.PART: # Has txindex
try:
p2sh = ci_from.encode_p2sh(bid.initiate_tx.script)
initiate_txn = self.callcoinrpc(coin_from, 'getrawtransaction', [initiate_txnid_hex, True])
# Verify amount
vout = getVoutByAddress(initiate_txn, p2sh)
@@ -3932,24 +4017,29 @@ class BasicSwap(BaseApp):
dest_script = ci_from.getScriptDest(bid.initiate_tx.script)
addr = ci_from.encodeScriptDest(dest_script)
else:
addr = p2sh
addr = ci_from.encode_p2sh(bid.initiate_tx.script)
found = ci_from.getLockTxHeight(bytes.fromhex(initiate_txnid_hex), addr, bid.amount, bid.chain_a_height_start, find_index=True)
found = ci_from.getLockTxHeight(bid.initiate_tx.txid, addr, bid.amount, bid.chain_a_height_start, find_index=True, vout=bid.initiate_tx.vout)
index = None
if found:
bid.initiate_tx.conf = found['depth']
index = found['index']
if 'index' in found:
index = found['index']
tx_height = found['height']
if bid.initiate_tx.conf != last_initiate_txn_conf:
save_bid = True
if bid.initiate_tx.vout is None and index is not None:
bid.initiate_tx.vout = index
save_bid = True
if bid.initiate_tx.conf is not None:
self.log.debug('initiate_txnid %s confirms %d', initiate_txnid_hex, bid.initiate_tx.conf)
if bid.initiate_tx.vout is None and tx_height > 0:
bid.initiate_tx.vout = index
if (last_initiate_txn_conf is None or last_initiate_txn_conf < 1) and tx_height > 0:
# Start checking for spends of initiate_txn before fully confirmed
bid.initiate_tx.chain_height = self.setLastHeightChecked(coin_from, tx_height)
bid.initiate_tx.chain_height = self.setLastHeightCheckedStart(coin_from, tx_height)
self.setTxBlockInfoFromHeight(ci_from, bid.initiate_tx, tx_height)
self.addWatchedOutput(coin_from, bid_id, initiate_txnid_hex, bid.initiate_tx.vout, BidStates.SWAP_INITIATED)
@@ -3978,17 +4068,22 @@ class BasicSwap(BaseApp):
ci_to = self.ci(coin_to)
participate_txid = None if bid.participate_tx is None or bid.participate_tx.txid is None else bid.participate_tx.txid
found = ci_to.getLockTxHeight(participate_txid, addr, bid.amount_to, bid.chain_b_height_start, find_index=True)
participate_txvout = None if bid.participate_tx is None or bid.participate_tx.vout is None else bid.participate_tx.vout
found = ci_to.getLockTxHeight(participate_txid, addr, bid.amount_to, bid.chain_b_height_start, find_index=True, vout=participate_txvout)
if found:
index = found.get('index', participate_txvout)
if bid.participate_tx.conf != found['depth']:
save_bid = True
if bid.participate_tx.conf is None and bid.participate_tx.state != TxStates.TX_SENT:
txid = found.get('txid', None if participate_txid is None else participate_txid.hex())
self.log.debug('Found bid %s participate txn %s in chain %s', bid_id.hex(), txid, Coins(coin_to).name)
self.addParticipateTxn(bid_id, bid, coin_to, txid, index, found['height'])
# Only update tx state if tx hasn't already been seen
if bid.participate_tx.state is None or bid.participate_tx.state < TxStates.TX_SENT:
bid.setPTxState(TxStates.TX_SENT)
bid.participate_tx.conf = found['depth']
index = found['index']
if bid.participate_tx is None or bid.participate_tx.txid is None:
self.log.debug('Found bid %s participate txn %s in chain %s', bid_id.hex(), found['txid'], Coins(coin_to).name)
self.addParticipateTxn(bid_id, bid, coin_to, found['txid'], found['index'], found['height'])
bid.setPTxState(TxStates.TX_SENT)
save_bid = True
if found['height'] > 0 and bid.participate_tx.block_height is None:
self.setTxBlockInfoFromHeight(ci_to, bid.participate_tx, found['height'])
@@ -4047,13 +4142,17 @@ class BasicSwap(BaseApp):
self.logEvent(Concepts.BID, bid.bid_id, EventLogTypes.PTX_REFUND_PUBLISHED, '', None)
# State will update when spend is detected
except Exception as ex:
if 'non-BIP68-final' not in str(ex) and 'non-final' not in str(ex):
if 'non-BIP68-final' not in str(ex) and 'non-final' not in str(ex) and 'locks on inputs not met' not in str(ex):
self.log.warning('Error trying to submit participate refund txn: %s', str(ex))
return False # Bid is still active
def extractSecret(self, coin_type, bid, spend_in):
try:
if coin_type == Coins.PART or self.coin_clients[coin_type]['use_segwit']:
if coin_type in (Coins.DCR, ):
script_sig = spend_in['scriptSig']['asm'].split(' ')
ensure(len(script_sig) == 5, 'Bad witness size')
return bytes.fromhex(script_sig[2])
elif coin_type in (Coins.PART, ) or self.coin_clients[coin_type]['use_segwit']:
ensure(len(spend_in['txinwitness']) == 5, 'Bad witness size')
return bytes.fromhex(spend_in['txinwitness'][2])
else:
@@ -4064,7 +4163,7 @@ class BasicSwap(BaseApp):
return None
def addWatchedOutput(self, coin_type, bid_id, txid_hex, vout, tx_type, swap_type=None):
self.log.debug('Adding watched output %s bid %s tx %s type %s', coin_type, bid_id.hex(), txid_hex, tx_type)
self.log.debug('Adding watched output %s bid %s tx %s type %s', Coins(coin_type).name, bid_id.hex(), txid_hex, tx_type)
watched = self.coin_clients[coin_type]['watched_outputs']
@@ -4085,7 +4184,29 @@ class BasicSwap(BaseApp):
del self.coin_clients[coin_type]['watched_outputs'][i]
self.log.debug('Removed watched output %s %s %s', Coins(coin_type).name, bid_id.hex(), wo.txid_hex)
def initiateTxnSpent(self, bid_id: bytes, spend_txid: str, spend_n: int, spend_txn):
def addWatchedScript(self, coin_type, bid_id, script, tx_type, swap_type=None):
self.log.debug('Adding watched script %s bid %s type %s', Coins(coin_type).name, bid_id.hex(), tx_type)
watched = self.coin_clients[coin_type]['watched_scripts']
for ws in watched:
if ws.bid_id == bid_id and ws.tx_type == tx_type and ws.script == script:
self.log.debug('Script already being watched.')
return
watched.append(WatchedScript(bid_id, script, tx_type, swap_type))
def removeWatchedScript(self, coin_type, bid_id: bytes, script: bytes) -> None:
# Remove all for bid if txid is None
self.log.debug('removeWatchedScript %s %s', Coins(coin_type).name, bid_id.hex())
old_len = len(self.coin_clients[coin_type]['watched_scripts'])
for i in range(old_len - 1, -1, -1):
ws = self.coin_clients[coin_type]['watched_scripts'][i]
if ws.bid_id == bid_id and (script is None or ws.script == script):
del self.coin_clients[coin_type]['watched_scripts'][i]
self.log.debug('Removed watched script %s %s', Coins(coin_type).name, bid_id.hex())
def initiateTxnSpent(self, bid_id: bytes, spend_txid: str, spend_n: int, spend_txn) -> None:
self.log.debug('Bid %s initiate txn spent by %s %d', bid_id.hex(), spend_txid, spend_n)
if bid_id in self.swaps_in_progress:
@@ -4112,7 +4233,7 @@ class BasicSwap(BaseApp):
self.removeWatchedOutput(coin_from, bid_id, bid.initiate_tx.txid.hex())
self.saveBid(bid_id, bid)
def participateTxnSpent(self, bid_id: bytes, spend_txid: str, spend_n: int, spend_txn):
def participateTxnSpent(self, bid_id: bytes, spend_txid: str, spend_n: int, spend_txn) -> None:
self.log.debug('Bid %s participate txn spent by %s %d', bid_id.hex(), spend_txid, spend_n)
# TODO: More SwapTypes
@@ -4268,7 +4389,7 @@ class BasicSwap(BaseApp):
session.remove()
self.mxDB.release()
def processSpentOutput(self, coin_type, watched_output, spend_txid_hex, spend_n, spend_txn):
def processSpentOutput(self, coin_type, watched_output, spend_txid_hex, spend_n, spend_txn) -> None:
if watched_output.swap_type == SwapTypes.XMR_SWAP:
if watched_output.tx_type == TxTypes.XMR_SWAP_A_LOCK:
self.process_XMR_SWAP_A_LOCK_tx_spend(watched_output.bid_id, spend_txid_hex, spend_txn['hex'])
@@ -4283,13 +4404,65 @@ class BasicSwap(BaseApp):
else:
self.initiateTxnSpent(watched_output.bid_id, spend_txid_hex, spend_n, spend_txn)
def processFoundScript(self, coin_type, watched_script, txid: bytes, vout: int) -> None:
if watched_script.tx_type == TxTypes.PTX:
if watched_script.bid_id in self.swaps_in_progress:
bid = self.swaps_in_progress[watched_script.bid_id][0]
bid.participate_tx.txid = txid
bid.participate_tx.vout = vout
bid.setPTxState(TxStates.TX_IN_CHAIN)
self.saveBid(watched_script.bid_id, bid)
else:
self.log.warning('Could not find active bid for found watched script: {}'.format(watched_script.bid_id.hex()))
else:
self.log.warning('Unknown found watched script tx type for bid {}'.format(watched_script.bid_id.hex()))
self.removeWatchedScript(coin_type, watched_script.bid_id, watched_script.script)
def checkNewBlock(self, coin_type, c):
pass
def haveCheckedPrevBlock(self, ci, c, block, session=None) -> bool:
previousblockhash = bytes.fromhex(block['previousblockhash'])
try:
use_session = self.openSession(session)
q = use_session.execute('SELECT COUNT(*) FROM checkedblocks WHERE block_hash = :block_hash', {'block_hash': previousblockhash}).first()
if q[0] > 0:
return True
finally:
if session is None:
self.closeSession(use_session, commit=False)
return False
def updateCheckedBlock(self, ci, cc, block, session=None) -> None:
now: int = self.getTime()
try:
use_session = self.openSession(session)
block_height = int(block['height'])
if cc['last_height_checked'] != block_height:
cc['last_height_checked'] = block_height
self.setIntKVInSession('last_height_checked_' + ci.coin_name().lower(), block_height, use_session)
query = '''INSERT INTO checkedblocks (created_at, coin_type, block_height, block_hash, block_time)
VALUES (:now, :coin_type, :block_height, :block_hash, :block_time)'''
use_session.execute(query, {'now': now, 'coin_type': int(ci.coin_type()), 'block_height': block_height, 'block_hash': bytes.fromhex(block['hash']), 'block_time': int(block['time'])})
finally:
if session is None:
self.closeSession(use_session)
def checkForSpends(self, coin_type, c):
# assert (self.mxDB.locked())
self.log.debug('checkForSpends %s', Coins(coin_type).name)
# TODO: Check for spends on watchonly txns where possible
if 'have_spent_index' in self.coin_clients[coin_type] and self.coin_clients[coin_type]['have_spent_index']:
if self.coin_clients[coin_type].get('have_spent_index', False):
# TODO: batch getspentinfo
for o in c['watched_outputs']:
found_spend = None
@@ -4304,39 +4477,56 @@ class BasicSwap(BaseApp):
spend_n = found_spend['index']
spend_txn = self.callcoinrpc(Coins.PART, 'getrawtransaction', [spend_txid, True])
self.processSpentOutput(coin_type, o, spend_txid, spend_n, spend_txn)
else:
ci = self.ci(coin_type)
chain_blocks = ci.getChainHeight()
last_height_checked = c['last_height_checked']
self.log.debug('chain_blocks, last_height_checked %s %s', chain_blocks, last_height_checked)
while last_height_checked < chain_blocks:
block_hash = self.callcoinrpc(coin_type, 'getblockhash', [last_height_checked + 1])
try:
block = ci.getBlockWithTxns(block_hash)
except Exception as e:
if 'Block not available (pruned data)' in str(e):
# TODO: Better solution?
bci = self.callcoinrpc(coin_type, 'getblockchaininfo')
self.log.error('Coin %s last_height_checked %d set to pruneheight %d', self.ci(coin_type).coin_name(), last_height_checked, bci['pruneheight'])
last_height_checked = bci['pruneheight']
continue
else:
self.logException(f'getblock error {e}')
break
return
for tx in block['tx']:
ci = self.ci(coin_type)
chain_blocks = ci.getChainHeight()
last_height_checked: int = c['last_height_checked']
block_check_min_time: int = c['block_check_min_time']
self.log.debug('chain_blocks, last_height_checked %d %d', chain_blocks, last_height_checked)
while last_height_checked < chain_blocks:
block_hash = ci.rpc('getblockhash', [last_height_checked + 1])
try:
block = ci.getBlockWithTxns(block_hash)
except Exception as e:
if 'Block not available (pruned data)' in str(e):
# TODO: Better solution?
bci = self.callcoinrpc(coin_type, 'getblockchaininfo')
self.log.error('Coin %s last_height_checked %d set to pruneheight %d', self.ci(coin_type).coin_name(), last_height_checked, bci['pruneheight'])
last_height_checked = bci['pruneheight']
continue
else:
self.logException(f'getblock error {e}')
break
if block_check_min_time > block['time'] or last_height_checked < 1:
pass
elif not self.haveCheckedPrevBlock(ci, c, block):
last_height_checked -= 1
self.log.debug('Have not seen previousblockhash {} for block {}'.format(block['previousblockhash'], block['hash']))
continue
for tx in block['tx']:
for s in c['watched_scripts']:
for i, txo in enumerate(tx['vout']):
if 'scriptPubKey' in txo and 'hex' in txo['scriptPubKey']:
# TODO: Optimise by loading rawtx in CTransaction
if bytes.fromhex(txo['scriptPubKey']['hex']) == s.script:
self.log.debug('Found script from search for bid %s: %s %d', s.bid_id.hex(), tx['txid'], i)
self.processFoundScript(coin_type, s, bytes.fromhex(tx['txid']), i)
for o in c['watched_outputs']:
for i, inp in enumerate(tx['vin']):
for o in c['watched_outputs']:
inp_txid = inp.get('txid', None)
if inp_txid is None: # Coinbase
continue
if inp_txid == o.txid_hex and inp['vout'] == o.vout:
self.log.debug('Found spend from search %s %d in %s %d', o.txid_hex, o.vout, tx['txid'], i)
self.processSpentOutput(coin_type, o, tx['txid'], i, tx)
last_height_checked += 1
if c['last_height_checked'] != last_height_checked:
c['last_height_checked'] = last_height_checked
self.setIntKV('last_height_checked_' + ci.coin_name().lower(), last_height_checked)
inp_txid = inp.get('txid', None)
if inp_txid is None: # Coinbase
continue
if inp_txid == o.txid_hex and inp['vout'] == o.vout:
self.log.debug('Found spend from search %s %d in %s %d', o.txid_hex, o.vout, tx['txid'], i)
self.processSpentOutput(coin_type, o, tx['txid'], i, tx)
last_height_checked += 1
self.updateCheckedBlock(ci, c, block)
def expireMessages(self) -> None:
if self._is_locked is True:
@@ -4572,7 +4762,7 @@ class BasicSwap(BaseApp):
return
offer_data.ParseFromString(offer_bytes)
# Validate data
# Validate offer data
now: int = self.getTime()
coin_from = Coins(offer_data.coin_from)
ci_from = self.ci(coin_from)
@@ -4839,7 +5029,7 @@ class BasicSwap(BaseApp):
bid_data = BidMessage()
bid_data.ParseFromString(bid_bytes)
# Validate data
# Validate bid data
ensure(bid_data.protocol_version >= MINPROTO_VERSION_SECRET_HASH, 'Invalid protocol version')
ensure(len(bid_data.offer_msg_id) == 28, 'Bad offer_id length')
@@ -4899,6 +5089,9 @@ class BasicSwap(BaseApp):
chain_a_height_start=ci_from.getChainHeight(),
chain_b_height_start=ci_to.getChainHeight(),
)
if len(bid_data.pkhash_buyer_to) > 0:
bid.pkhash_buyer_to = bid_data.pkhash_buyer_to
else:
ensure(bid.state == BidStates.BID_SENT, 'Wrong bid state: {}'.format(BidStates(bid.state).name))
bid.created_at = msg['sent']
@@ -4952,33 +5145,29 @@ class BasicSwap(BaseApp):
use_csv = True if offer.lock_type < TxLockTypes.ABS_LOCK_BLOCKS else False
# TODO: Verify script without decoding?
decoded_script = self.callcoinrpc(Coins.PART, 'decodescript', [bid_accept_data.contract_script.hex()])
lock_check_op = 'OP_CHECKSEQUENCEVERIFY' if use_csv else 'OP_CHECKLOCKTIMEVERIFY'
prog = re.compile(r'OP_IF OP_SIZE 32 OP_EQUALVERIFY OP_SHA256 (\w+) OP_EQUALVERIFY OP_DUP OP_HASH160 (\w+) OP_ELSE (\d+) {} OP_DROP OP_DUP OP_HASH160 (\w+) OP_ENDIF OP_EQUALVERIFY OP_CHECKSIG'.format(lock_check_op))
rr = prog.match(decoded_script['asm'])
if not rr:
if coin_from in (Coins.DCR, ):
op_hash = OpCodes.OP_SHA256_DECRED
else:
op_hash = OpCodes.OP_SHA256
op_lock = OpCodes.OP_CHECKSEQUENCEVERIFY if use_csv else OpCodes.OP_CHECKLOCKTIMEVERIFY
script_valid, script_hash, script_pkhash1, script_lock_val, script_pkhash2 = atomic_swap_1.verifyContractScript(bid_accept_data.contract_script, op_lock=op_lock, op_hash=op_hash)
if not script_valid:
raise ValueError('Bad script')
scriptvalues = rr.groups()
ensure(len(scriptvalues[0]) == 64, 'Bad secret_hash length')
ensure(bytes.fromhex(scriptvalues[1]) == bid.pkhash_buyer, 'pkhash_buyer mismatch')
ensure(script_pkhash1 == bid.pkhash_buyer, 'pkhash_buyer mismatch')
script_lock_value = int(scriptvalues[2])
if use_csv:
expect_sequence = ci_from.getExpectedSequence(offer.lock_type, offer.lock_value)
ensure(script_lock_value == expect_sequence, 'sequence mismatch')
ensure(script_lock_val == expect_sequence, 'sequence mismatch')
else:
if offer.lock_type == TxLockTypes.ABS_LOCK_BLOCKS:
block_header_from = ci_from.getBlockHeaderAt(now)
chain_height_at_bid_creation = block_header_from['height']
ensure(script_lock_value <= chain_height_at_bid_creation + offer.lock_value + atomic_swap_1.ABS_LOCK_BLOCKS_LEEWAY, 'script lock height too high')
ensure(script_lock_value >= chain_height_at_bid_creation + offer.lock_value - atomic_swap_1.ABS_LOCK_BLOCKS_LEEWAY, 'script lock height too low')
ensure(script_lock_val <= chain_height_at_bid_creation + offer.lock_value + atomic_swap_1.ABS_LOCK_BLOCKS_LEEWAY, 'script lock height too high')
ensure(script_lock_val >= chain_height_at_bid_creation + offer.lock_value - atomic_swap_1.ABS_LOCK_BLOCKS_LEEWAY, 'script lock height too low')
else:
ensure(script_lock_value <= now + offer.lock_value + atomic_swap_1.INITIATE_TX_TIMEOUT, 'script lock time too high')
ensure(script_lock_value >= now + offer.lock_value - atomic_swap_1.ABS_LOCK_TIME_LEEWAY, 'script lock time too low')
ensure(len(scriptvalues[3]) == 40, 'pkhash_refund bad length')
ensure(script_lock_val <= now + offer.lock_value + atomic_swap_1.INITIATE_TX_TIMEOUT, 'script lock time too high')
ensure(script_lock_val >= now + offer.lock_value - atomic_swap_1.ABS_LOCK_TIME_LEEWAY, 'script lock time too low')
ensure(self.countMessageLinks(Concepts.BID, bid_id, MessageTypes.BID_ACCEPT) == 0, 'Bid already accepted')
@@ -4991,7 +5180,12 @@ class BasicSwap(BaseApp):
txid=bid_accept_data.initiate_txid,
script=bid_accept_data.contract_script,
)
bid.pkhash_seller = bytes.fromhex(scriptvalues[3])
if len(bid_accept_data.pkhash_seller) == 20:
bid.pkhash_seller = bid_accept_data.pkhash_seller
else:
bid.pkhash_seller = script_pkhash2
bid.setState(BidStates.BID_ACCEPTED)
bid.setITxState(TxStates.TX_NONE)
@@ -5322,7 +5516,7 @@ class BasicSwap(BaseApp):
reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from)
coin_from = Coins(offer.coin_to if reverse_bid else offer.coin_from)
self.setLastHeightChecked(coin_from, bid.chain_a_height_start)
self.setLastHeightCheckedStart(coin_from, bid.chain_a_height_start)
self.addWatchedOutput(coin_from, bid.bid_id, bid.xmr_a_lock_tx.txid.hex(), bid.xmr_a_lock_tx.vout, TxTypes.XMR_SWAP_A_LOCK, SwapTypes.XMR_SWAP)
lock_refund_vout = self.ci(coin_from).getLockRefundTxSwapOutput(xmr_swap)
@@ -6351,9 +6545,9 @@ class BasicSwap(BaseApp):
if now - self._last_checked_watched >= self.check_watched_seconds:
for k, c in self.coin_clients.items():
if k == Coins.PART_ANON or k == Coins.PART_BLIND:
if k == Coins.PART_ANON or k == Coins.PART_BLIND or k == Coins.LTC_MWEB:
continue
if len(c['watched_outputs']) > 0:
if len(c['watched_outputs']) > 0 or len(c['watched_scripts']):
self.checkForSpends(k, c)
self._last_checked_watched = now