mirror of
https://github.com/basicswap/basicswap.git
synced 2025-11-06 02:38:11 +01:00
Decred: Secret hash swap tests.
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user