mirror of
https://github.com/basicswap/basicswap.git
synced 2024-12-23 03:49:25 +00:00
Decred: Secret hash swap tests.
This commit is contained in:
parent
026b222e90
commit
a3f5bc1a5a
24 changed files with 1025 additions and 301 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -202,6 +202,7 @@ class DebugTypes(IntEnum):
|
|||
SEND_LOCKED_XMR = auto()
|
||||
B_LOCK_TX_MISSED_SEND = auto()
|
||||
DUPLICATE_ACTIONS = auto()
|
||||
DONT_CONFIRM_PTX = auto()
|
||||
|
||||
|
||||
class NotificationTypes(IntEnum):
|
||||
|
|
|
@ -11,7 +11,7 @@ from enum import IntEnum, auto
|
|||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
|
||||
CURRENT_DB_VERSION = 23
|
||||
CURRENT_DB_VERSION = 24
|
||||
CURRENT_DB_DATA_VERSION = 4
|
||||
Base = declarative_base()
|
||||
|
||||
|
@ -127,6 +127,7 @@ class Bid(Base):
|
|||
amount_to = sa.Column(sa.BigInteger) # amount * offer.rate
|
||||
|
||||
pkhash_buyer = sa.Column(sa.LargeBinary)
|
||||
pkhash_buyer_to = sa.Column(sa.LargeBinary) # Used for the ptx if coin pubkey hashes differ
|
||||
amount = sa.Column(sa.BigInteger)
|
||||
rate = sa.Column(sa.BigInteger)
|
||||
|
||||
|
@ -522,3 +523,14 @@ class MessageLink(Base):
|
|||
msg_type = sa.Column(sa.Integer)
|
||||
msg_sequence = sa.Column(sa.Integer)
|
||||
msg_id = sa.Column(sa.LargeBinary)
|
||||
|
||||
|
||||
class CheckedBlock(Base):
|
||||
__tablename__ = 'checkedblocks'
|
||||
|
||||
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
|
||||
created_at = sa.Column(sa.BigInteger)
|
||||
coin_type = sa.Column(sa.Integer)
|
||||
block_height = sa.Column(sa.Integer)
|
||||
block_hash = sa.Column(sa.LargeBinary)
|
||||
block_time = sa.Column(sa.BigInteger)
|
||||
|
|
|
@ -300,6 +300,18 @@ def upgradeDatabase(self, db_version):
|
|||
elif current_version == 22:
|
||||
db_version += 1
|
||||
session.execute('ALTER TABLE offers ADD COLUMN amount_to INTEGER')
|
||||
elif current_version == 23:
|
||||
db_version += 1
|
||||
session.execute('''
|
||||
CREATE TABLE checkedblocks (
|
||||
record_id INTEGER NOT NULL,
|
||||
created_at BIGINT,
|
||||
coin_type INTEGER,
|
||||
block_height INTEGER,
|
||||
block_hash BLOB,
|
||||
block_time INTEGER,
|
||||
PRIMARY KEY (record_id))''')
|
||||
session.execute('ALTER TABLE bids ADD COLUMN pkhash_buyer_to BLOB')
|
||||
if current_version != db_version:
|
||||
self.db_version = db_version
|
||||
self.setIntKVInSession('db_version', db_version, session)
|
||||
|
|
|
@ -52,5 +52,7 @@ def remove_expired_data(self, time_offset: int = 0):
|
|||
if num_offers > 0 or num_bids > 0:
|
||||
self.log.info('Removed data for {} expired offer{} and {} bid{}.'.format(num_offers, 's' if num_offers != 1 else '', num_bids, 's' if num_bids != 1 else ''))
|
||||
|
||||
session.execute('DELETE FROM checkedblocks WHERE created_at <= :expired_at', {'expired_at': now - time_offset})
|
||||
|
||||
finally:
|
||||
self.closeSession(session)
|
||||
|
|
|
@ -19,6 +19,9 @@ from basicswap.util import (
|
|||
format_amount,
|
||||
TemporaryError,
|
||||
)
|
||||
from basicswap.util.crypto import (
|
||||
hash160,
|
||||
)
|
||||
from basicswap.util.ecc import (
|
||||
ep,
|
||||
getSecretInt,
|
||||
|
@ -37,6 +40,10 @@ class Curves(IntEnum):
|
|||
|
||||
|
||||
class CoinInterface:
|
||||
@staticmethod
|
||||
def watch_blocks_for_scripts() -> bool:
|
||||
return False
|
||||
|
||||
def __init__(self, network):
|
||||
self.setDefaults()
|
||||
self._network = network
|
||||
|
@ -101,6 +108,10 @@ class CoinInterface:
|
|||
def has_segwit(self) -> bool:
|
||||
return chainparams[self.coin_type()].get('has_segwit', True)
|
||||
|
||||
def use_p2shp2wsh(self) -> bool:
|
||||
# p2sh-p2wsh
|
||||
return False
|
||||
|
||||
def is_transient_error(self, ex) -> bool:
|
||||
if isinstance(ex, TemporaryError):
|
||||
return True
|
||||
|
@ -128,6 +139,16 @@ class CoinInterface:
|
|||
def walletRestoreHeight(self) -> int:
|
||||
return self._restore_height
|
||||
|
||||
def get_connection_type(self):
|
||||
return self._connection_type
|
||||
|
||||
def using_segwit(self) -> bool:
|
||||
# Using btc native segwit
|
||||
return self._use_segwit
|
||||
|
||||
def use_tx_vsize(self) -> bool:
|
||||
return self._use_segwit
|
||||
|
||||
|
||||
class Secp256k1Interface(CoinInterface):
|
||||
@staticmethod
|
||||
|
@ -137,9 +158,12 @@ class Secp256k1Interface(CoinInterface):
|
|||
def getNewSecretKey(self) -> bytes:
|
||||
return i2b(getSecretInt())
|
||||
|
||||
def getPubkey(self, privkey):
|
||||
def getPubkey(self, privkey: bytes) -> bytes:
|
||||
return PublicKey.from_secret(privkey).format()
|
||||
|
||||
def pkh(self, pubkey: bytes) -> bytes:
|
||||
return hash160(pubkey)
|
||||
|
||||
def verifyKey(self, k: bytes) -> bool:
|
||||
i = b2i(k)
|
||||
return (i < ep.o and i > 0)
|
||||
|
|
|
@ -66,8 +66,8 @@ from basicswap.contrib.test_framework.messages import (
|
|||
CTxIn,
|
||||
CTxInWitness,
|
||||
CTxOut,
|
||||
uint256_from_str)
|
||||
|
||||
uint256_from_str,
|
||||
)
|
||||
from basicswap.contrib.test_framework.script import (
|
||||
CScript, CScriptOp,
|
||||
OP_IF, OP_ELSE, OP_ENDIF,
|
||||
|
@ -231,17 +231,6 @@ class BTCInterface(Secp256k1Interface):
|
|||
|
||||
return len(wallets)
|
||||
|
||||
def using_segwit(self) -> bool:
|
||||
# Using btc native segwit
|
||||
return self._use_segwit
|
||||
|
||||
def use_p2shp2wsh(self) -> bool:
|
||||
# p2sh-p2wsh
|
||||
return False
|
||||
|
||||
def get_connection_type(self):
|
||||
return self._connection_type
|
||||
|
||||
def open_rpc(self, wallet=None):
|
||||
return openrpc(self._rpcport, self._rpcauth, wallet=wallet, host=self._rpc_host)
|
||||
|
||||
|
@ -1163,7 +1152,7 @@ class BTCInterface(Secp256k1Interface):
|
|||
lock_tx_dest = self.getScriptDest(lock_script)
|
||||
return self.encodeScriptDest(lock_tx_dest)
|
||||
|
||||
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False):
|
||||
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False, vout: int = -1):
|
||||
# Add watchonly address and rescan if required
|
||||
|
||||
if not self.isAddressMine(dest_address, or_watch_only=True):
|
||||
|
@ -1509,7 +1498,7 @@ class BTCInterface(Secp256k1Interface):
|
|||
return {'txid': txid_hex, 'amount': 0, 'height': rv['blockheight']}
|
||||
return None
|
||||
|
||||
def createRedeemTxn(self, prevout, output_addr: str, output_value: int) -> str:
|
||||
def createRedeemTxn(self, prevout, output_addr: str, output_value: int, txn_script: bytes = None) -> str:
|
||||
tx = CTransaction()
|
||||
tx.nVersion = self.txVersion()
|
||||
prev_txid = uint256_from_str(bytes.fromhex(prevout['txid'])[::-1])
|
||||
|
@ -1520,7 +1509,7 @@ class BTCInterface(Secp256k1Interface):
|
|||
tx.rehash()
|
||||
return tx.serialize().hex()
|
||||
|
||||
def createRefundTxn(self, prevout, output_addr: str, output_value: int, locktime: int, sequence: int) -> str:
|
||||
def createRefundTxn(self, prevout, output_addr: str, output_value: int, locktime: int, sequence: int, txn_script: bytes = None) -> str:
|
||||
tx = CTransaction()
|
||||
tx.nVersion = self.txVersion()
|
||||
tx.nLockTime = locktime
|
||||
|
|
|
@ -15,6 +15,9 @@ from basicswap.basicswap_util import (
|
|||
TxLockTypes
|
||||
)
|
||||
from basicswap.chainparams import Coins
|
||||
from basicswap.contrib.test_framework.messages import (
|
||||
uint256_from_str,
|
||||
)
|
||||
from basicswap.interface.btc import Secp256k1Interface
|
||||
from basicswap.util import (
|
||||
ensure,
|
||||
|
@ -34,8 +37,22 @@ from basicswap.util.script import (
|
|||
from basicswap.util.extkey import ExtKeyPair
|
||||
from basicswap.util.integer import encode_varint
|
||||
from basicswap.interface.dcr.rpc import make_rpc_func
|
||||
from .messages import CTransaction, CTxOut, SigHashType, TxSerializeType
|
||||
from .script import push_script_data, OP_HASH160, OP_EQUAL, OP_DUP, OP_EQUALVERIFY, OP_CHECKSIG
|
||||
from .messages import (
|
||||
CTransaction,
|
||||
CTxIn,
|
||||
CTxOut,
|
||||
COutPoint,
|
||||
SigHashType,
|
||||
TxSerializeType,
|
||||
)
|
||||
from .script import (
|
||||
push_script_data,
|
||||
OP_HASH160,
|
||||
OP_EQUAL,
|
||||
OP_DUP,
|
||||
OP_EQUALVERIFY,
|
||||
OP_CHECKSIG,
|
||||
)
|
||||
|
||||
from coincurve.keys import (
|
||||
PrivateKey,
|
||||
|
@ -124,6 +141,20 @@ def DCRSignatureHash(sign_script: bytes, hash_type: SigHashType, tx: CTransactio
|
|||
return blake256(hash_buffer)
|
||||
|
||||
|
||||
def extract_sig_and_pk(sig_script: bytes) -> (bytes, bytes):
|
||||
sig = None
|
||||
pk = None
|
||||
o: int = 0
|
||||
num_bytes = sig_script[o]
|
||||
o += 1
|
||||
sig = sig_script[o: o + num_bytes]
|
||||
o += num_bytes
|
||||
num_bytes = sig_script[o]
|
||||
o += 1
|
||||
pk = sig_script[o: o + num_bytes]
|
||||
return sig, pk
|
||||
|
||||
|
||||
class DCRInterface(Secp256k1Interface):
|
||||
|
||||
@staticmethod
|
||||
|
@ -168,6 +199,10 @@ class DCRInterface(Secp256k1Interface):
|
|||
return secondsLocked | SEQUENCE_LOCKTIME_TYPE_FLAG
|
||||
raise ValueError('Unknown lock type')
|
||||
|
||||
@staticmethod
|
||||
def watch_blocks_for_scripts() -> bool:
|
||||
return True
|
||||
|
||||
def __init__(self, coin_settings, network, swap_client=None):
|
||||
super().__init__(network)
|
||||
self._rpc_host = coin_settings.get('rpchost', '127.0.0.1')
|
||||
|
@ -183,7 +218,11 @@ class DCRInterface(Secp256k1Interface):
|
|||
self.blocks_confirmed = coin_settings['blocks_confirmed']
|
||||
self.setConfTarget(coin_settings['conf_target'])
|
||||
|
||||
self._use_segwit = coin_settings['use_segwit']
|
||||
self._use_segwit = True # Decred is natively segwit
|
||||
self._connection_type = coin_settings['connection_type']
|
||||
|
||||
def use_tx_vsize(self) -> bool:
|
||||
return False
|
||||
|
||||
def pkh(self, pubkey: bytes) -> bytes:
|
||||
return ripemd160(blake256(pubkey))
|
||||
|
@ -235,9 +274,6 @@ class DCRInterface(Secp256k1Interface):
|
|||
def getBlockchainInfo(self):
|
||||
return self.rpc('getblockchaininfo')
|
||||
|
||||
def using_segwit(self) -> bool:
|
||||
return self._use_segwit
|
||||
|
||||
def getWalletInfo(self):
|
||||
rv = {}
|
||||
rv = self.rpc_wallet('getinfo')
|
||||
|
@ -276,6 +312,13 @@ class DCRInterface(Secp256k1Interface):
|
|||
raise ValueError('Checksum mismatch')
|
||||
return key[2], key[3:]
|
||||
|
||||
def encodeKey(self, key_bytes: bytes) -> str:
|
||||
wif_prefix = self.chainparams_network()['key_prefix']
|
||||
key_type = 0 # STEcdsaSecp256k1
|
||||
b = wif_prefix.to_bytes(2, 'big') + key_type.to_bytes(1) + key_bytes
|
||||
b += blake256(b)[:4]
|
||||
return b58encode(b)
|
||||
|
||||
def loadTx(self, tx_bytes: bytes) -> CTransaction:
|
||||
tx = CTransaction()
|
||||
tx.deserialize(tx_bytes)
|
||||
|
@ -288,14 +331,21 @@ class DCRInterface(Secp256k1Interface):
|
|||
eck = PrivateKey(key_bytes)
|
||||
return eck.sign(sig_hash, hasher=None) + bytes((SigHashType.SigHashAll,))
|
||||
|
||||
def setTxSignature(self, tx_bytes: bytes, stack, txi: int = 0) -> bytes:
|
||||
def setTxSignatureScript(self, tx_bytes: bytes, script: bytes, txi: int = 0) -> bytes:
|
||||
tx = self.loadTx(tx_bytes)
|
||||
|
||||
tx.vin[txi].signature_script = script
|
||||
return tx.serialize()
|
||||
|
||||
def setTxSignature(self, tx_bytes: bytes, stack, txi: int = 0) -> bytes:
|
||||
tx = self.loadTx(tx_bytes)
|
||||
script_data = bytearray()
|
||||
for data in stack:
|
||||
push_script_data(script_data, data)
|
||||
|
||||
tx.vin[txi].signature_script = script_data
|
||||
test_ser = tx.serialize()
|
||||
test_tx = self.loadTx(test_ser)
|
||||
|
||||
return tx.serialize()
|
||||
|
||||
|
@ -310,6 +360,20 @@ class DCRInterface(Secp256k1Interface):
|
|||
|
||||
return sig.hex()
|
||||
|
||||
def verifyTxSig(self, tx_bytes: bytes, sig: bytes, K: bytes, input_n: int, prevout_script: bytes, prevout_value: int) -> bool:
|
||||
tx = self.loadTx(tx_bytes)
|
||||
|
||||
sig_hash = DCRSignatureHash(prevout_script, SigHashType.SigHashAll, tx, input_n)
|
||||
pubkey = PublicKey(K)
|
||||
return pubkey.verify(sig[: -1], sig_hash, hasher=None) # Pop the hashtype byte
|
||||
|
||||
def getTxid(self, tx) -> bytes:
|
||||
if isinstance(tx, str):
|
||||
tx = bytes.fromhex(tx)
|
||||
if isinstance(tx, bytes):
|
||||
tx = self.loadTx(tx)
|
||||
return tx.TxHash()
|
||||
|
||||
def getScriptDest(self, script: bytes) -> bytes:
|
||||
# P2SH
|
||||
script_hash = self.pkh(script)
|
||||
|
@ -323,7 +387,6 @@ class DCRInterface(Secp256k1Interface):
|
|||
|
||||
def getPubkeyHashDest(self, pkh: bytes) -> bytes:
|
||||
# P2PKH
|
||||
|
||||
assert len(pkh) == 20
|
||||
return OP_DUP.to_bytes(1) + OP_HASH160.to_bytes(1) + len(pkh).to_bytes(1) + pkh + OP_EQUALVERIFY.to_bytes(1) + OP_CHECKSIG.to_bytes(1)
|
||||
|
||||
|
@ -424,7 +487,7 @@ class DCRInterface(Secp256k1Interface):
|
|||
return self.rpc_wallet('sendtoaddress', params)
|
||||
|
||||
def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool:
|
||||
addr_info = self.rpc('validateaddress', [address])
|
||||
addr_info = self.rpc_wallet('validateaddress', [address])
|
||||
return addr_info.get('ismine', False)
|
||||
|
||||
def encodeProofUtxos(self, proof_utxos):
|
||||
|
@ -504,18 +567,133 @@ class DCRInterface(Secp256k1Interface):
|
|||
txn_funded = self.createRawFundedTransaction(addr_to, amount)
|
||||
return self.rpc_wallet('signrawtransaction', [txn_funded])['hex']
|
||||
|
||||
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False):
|
||||
self._log.debug('TODO: getLockTxHeight')
|
||||
return None
|
||||
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False, vout: int = -1):
|
||||
if txid is None:
|
||||
self._log.debug('TODO: getLockTxHeight')
|
||||
return None
|
||||
|
||||
found_vout = None
|
||||
# Search for txo at vout 0 and 1 if vout is not known
|
||||
if vout is None:
|
||||
test_range = range(2)
|
||||
else:
|
||||
test_range = (vout, )
|
||||
for try_vout in test_range:
|
||||
try:
|
||||
txout = self.rpc('gettxout', [txid.hex(), try_vout, 0, True])
|
||||
addresses = txout['scriptPubKey']['addresses']
|
||||
if len(addresses) != 1 or addresses[0] != dest_address:
|
||||
continue
|
||||
if self.make_int(txout['value']) != bid_amount:
|
||||
self._log.warning('getLockTxHeight found txout {} with incorrect amount {}'.format(txid.hex(), txout['value']))
|
||||
continue
|
||||
found_vout = try_vout
|
||||
break
|
||||
except Exception as e:
|
||||
# self._log.warning('gettxout {}'.format(e))
|
||||
return None
|
||||
|
||||
block_height: int = 0
|
||||
confirmations: int = 0 if 'confirmations' not in txout else txout['confirmations']
|
||||
|
||||
# TODO: Better way?
|
||||
if confirmations > 0:
|
||||
block_height = self.getChainHeight() - confirmations
|
||||
|
||||
rv = {
|
||||
'depth': confirmations,
|
||||
'index': found_vout,
|
||||
'height': block_height}
|
||||
|
||||
return rv
|
||||
|
||||
def find_prevout_info(self, txn_hex: str, txn_script: bytes):
|
||||
txjs = self.rpc('decoderawtransaction', [txn_hex])
|
||||
n = getVoutByScriptPubKey(txjs, self.getScriptDest(txn_script).hex())
|
||||
|
||||
txo = txjs['vout'][n]
|
||||
return {
|
||||
'txid': txjs['txid'],
|
||||
'vout': n,
|
||||
'scriptPubKey': txjs['vout'][n]['scriptPubKey']['hex'],
|
||||
'scriptPubKey': txo['scriptPubKey']['hex'],
|
||||
'redeemScript': txn_script.hex(),
|
||||
'amount': txjs['vout'][n]['value']
|
||||
'amount': txo['value'],
|
||||
}
|
||||
|
||||
def getHTLCSpendTxVSize(self, redeem: bool = True) -> int:
|
||||
tx_vsize = 5 # Add a few bytes, sequence in script takes variable amount of bytes
|
||||
tx_vsize += 348 if redeem else 316
|
||||
return tx_vsize
|
||||
|
||||
def createRedeemTxn(self, prevout, output_addr: str, output_value: int, txn_script: bytes = None) -> str:
|
||||
tx = CTransaction()
|
||||
tx.version = self.txVersion()
|
||||
prev_txid = uint256_from_str(bytes.fromhex(prevout['txid'])[::-1])
|
||||
tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout'], 0)))
|
||||
pkh = self.decode_address(output_addr)[2:]
|
||||
script = self.getPubkeyHashDest(pkh)
|
||||
tx.vout.append(self.txoType()(output_value, script))
|
||||
return tx.serialize().hex()
|
||||
|
||||
def createRefundTxn(self, prevout, output_addr: str, output_value: int, locktime: int, sequence: int, txn_script: bytes = None) -> str:
|
||||
tx = CTransaction()
|
||||
tx.version = self.txVersion()
|
||||
tx.locktime = locktime
|
||||
prev_txid = uint256_from_str(bytes.fromhex(prevout['txid'])[::-1])
|
||||
tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout'], 0), sequence=sequence,))
|
||||
pkh = self.decode_address(output_addr)[2:]
|
||||
script = self.getPubkeyHashDest(pkh)
|
||||
tx.vout.append(self.txoType()(output_value, script))
|
||||
return tx.serialize().hex()
|
||||
|
||||
def verifyRawTransaction(self, tx_hex: str, prevouts):
|
||||
inputs_valid: bool = True
|
||||
validscripts: int = 0
|
||||
|
||||
tx_bytes = bytes.fromhex(tx_hex)
|
||||
tx = self.loadTx(bytes.fromhex(tx_hex))
|
||||
|
||||
for i, txi in enumerate(tx.vin):
|
||||
prevout_data = prevouts[i]
|
||||
redeem_script = bytes.fromhex(prevout_data['redeemScript'])
|
||||
prevout_value = self.make_int(prevout_data['amount'])
|
||||
sig, pk = extract_sig_and_pk(txi.signature_script)
|
||||
|
||||
if not sig or not pk:
|
||||
self._log.warning(f'verifyRawTransaction failed to extract signature for input {i}')
|
||||
continue
|
||||
|
||||
if self.verifyTxSig(tx_bytes, sig, pk, i, redeem_script, prevout_value):
|
||||
validscripts += 1
|
||||
|
||||
# TODO: validate inputs
|
||||
inputs_valid = True
|
||||
|
||||
return {
|
||||
'inputs_valid': inputs_valid,
|
||||
'validscripts': validscripts,
|
||||
}
|
||||
|
||||
def getBlockHeaderFromHeight(self, height):
|
||||
block_hash = self.rpc('getblockhash', [height])
|
||||
return self.rpc('getblockheader', [block_hash])
|
||||
|
||||
def getBlockWithTxns(self, block_hash: str):
|
||||
block = self.rpc('getblock', [block_hash, True, True])
|
||||
|
||||
return {
|
||||
'hash': block['hash'],
|
||||
'previousblockhash': block['previousblockhash'],
|
||||
'tx': block['rawtx'],
|
||||
'confirmations': block['confirmations'],
|
||||
'height': block['height'],
|
||||
'time': block['time'],
|
||||
'version': block['version'],
|
||||
'merkleroot': block['merkleroot'],
|
||||
}
|
||||
|
||||
def publishTx(self, tx: bytes):
|
||||
return self.rpc('sendrawtransaction', [tx.hex()])
|
||||
|
||||
def describeTx(self, tx_hex: str):
|
||||
return self.rpc('decoderawtransaction', [tx_hex])
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import copy
|
||||
from enum import IntEnum
|
||||
from basicswap.util.crypto import blake256
|
||||
from basicswap.util.integer import decode_varint, encode_varint
|
||||
from basicswap.util.integer import decode_compactsize, encode_compactsize
|
||||
|
||||
|
||||
class TxSerializeType(IntEnum):
|
||||
|
@ -86,12 +86,12 @@ class CTransaction:
|
|||
def deserialize(self, data: bytes) -> None:
|
||||
|
||||
version = int.from_bytes(data[:4], 'little')
|
||||
self.version = self.version & 0xffff
|
||||
self.version = version & 0xffff
|
||||
ser_type: int = version >> 16
|
||||
o = 4
|
||||
|
||||
if ser_type == TxSerializeType.Full or ser_type == TxSerializeType.NoWitness:
|
||||
num_txin, nb = decode_varint(data, o)
|
||||
num_txin, nb = decode_compactsize(data, o)
|
||||
o += nb
|
||||
|
||||
for i in range(num_txin):
|
||||
|
@ -107,7 +107,7 @@ class CTransaction:
|
|||
o += 4
|
||||
self.vin.append(txi)
|
||||
|
||||
num_txout, nb = decode_varint(data, o)
|
||||
num_txout, nb = decode_compactsize(data, o)
|
||||
o += nb
|
||||
|
||||
for i in range(num_txout):
|
||||
|
@ -116,7 +116,7 @@ class CTransaction:
|
|||
o += 8
|
||||
txo.version = int.from_bytes(data[o:o + 2], 'little')
|
||||
o += 2
|
||||
script_bytes, nb = decode_varint(data, o)
|
||||
script_bytes, nb = decode_compactsize(data, o)
|
||||
o += nb
|
||||
txo.script_pubkey = data[o:o + script_bytes]
|
||||
o += script_bytes
|
||||
|
@ -130,7 +130,7 @@ class CTransaction:
|
|||
if ser_type == TxSerializeType.NoWitness:
|
||||
return
|
||||
|
||||
num_wit_scripts, nb = decode_varint(data, o)
|
||||
num_wit_scripts, nb = decode_compactsize(data, o)
|
||||
o += nb
|
||||
|
||||
if ser_type == TxSerializeType.OnlyWitness:
|
||||
|
@ -147,7 +147,7 @@ class CTransaction:
|
|||
o += 4
|
||||
txi.block_index = int.from_bytes(data[o:o + 4], 'little')
|
||||
o += 4
|
||||
script_bytes, nb = decode_varint(data, o)
|
||||
script_bytes, nb = decode_compactsize(data, o)
|
||||
o += nb
|
||||
txi.signature_script = data[o:o + script_bytes]
|
||||
o += script_bytes
|
||||
|
@ -158,31 +158,31 @@ class CTransaction:
|
|||
data += version.to_bytes(4, 'little')
|
||||
|
||||
if ser_type == TxSerializeType.Full or ser_type == TxSerializeType.NoWitness:
|
||||
data += encode_varint(len(self.vin))
|
||||
data += encode_compactsize(len(self.vin))
|
||||
for txi in self.vin:
|
||||
data += txi.prevout.hash.to_bytes(32, 'little')
|
||||
data += txi.prevout.n.to_bytes(4, 'little')
|
||||
data += txi.prevout.tree.to_bytes(1)
|
||||
data += txi.sequence.to_bytes(4, 'little')
|
||||
|
||||
data += encode_varint(len(self.vout))
|
||||
data += encode_compactsize(len(self.vout))
|
||||
for txo in self.vout:
|
||||
data += txo.value.to_bytes(8, 'little')
|
||||
data += txo.version.to_bytes(2, 'little')
|
||||
data += encode_varint(len(txo.script_pubkey))
|
||||
data += encode_compactsize(len(txo.script_pubkey))
|
||||
data += txo.script_pubkey
|
||||
|
||||
data += self.locktime.to_bytes(4, 'little')
|
||||
data += self.expiry.to_bytes(4, 'little')
|
||||
|
||||
if ser_type == TxSerializeType.Full or ser_type == TxSerializeType.OnlyWitness:
|
||||
data += encode_varint(len(self.vin))
|
||||
data += encode_compactsize(len(self.vin))
|
||||
for txi in self.vin:
|
||||
tc_value_in = txi.value_in & 0xffffffffffffffff # Convert negative values
|
||||
data += tc_value_in.to_bytes(8, 'little')
|
||||
data += txi.block_height.to_bytes(4, 'little')
|
||||
data += txi.block_index.to_bytes(4, 'little')
|
||||
data += encode_varint(len(txi.signature_script))
|
||||
data += encode_compactsize(len(txi.signature_script))
|
||||
data += txi.signature_script
|
||||
|
||||
return data
|
||||
|
|
|
@ -87,7 +87,7 @@ class FIROInterface(BTCInterface):
|
|||
|
||||
return address
|
||||
|
||||
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False):
|
||||
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False, vout: int = -1):
|
||||
# Add watchonly address and rescan if required
|
||||
|
||||
if not self.isAddressMine(dest_address, or_watch_only=True):
|
||||
|
@ -337,7 +337,7 @@ class FIROInterface(BTCInterface):
|
|||
return
|
||||
current_height -= 1
|
||||
|
||||
def getBlockWithTxns(self, block_hash):
|
||||
def getBlockWithTxns(self, block_hash: str):
|
||||
# TODO: Bypass decoderawtransaction and getblockheader
|
||||
block = self.rpc('getblock', [block_hash, False])
|
||||
block_header = self.rpc('getblockheader', [block_hash])
|
||||
|
@ -355,9 +355,11 @@ class FIROInterface(BTCInterface):
|
|||
|
||||
block_rv = {
|
||||
'hash': block_hash,
|
||||
'previousblockhash': block_header['previousblockhash'],
|
||||
'tx': tx_rv,
|
||||
'confirmations': block_header['confirmations'],
|
||||
'height': block_header['height'],
|
||||
'time': block_header['time'],
|
||||
'version': block_header['version'],
|
||||
'merkleroot': block_header['merkleroot'],
|
||||
}
|
||||
|
|
|
@ -415,7 +415,7 @@ class NAVInterface(BTCInterface):
|
|||
return
|
||||
current_height -= 1
|
||||
|
||||
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False):
|
||||
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False, vout: int = -1):
|
||||
# Add watchonly address and rescan if required
|
||||
|
||||
if not self.isAddressMine(dest_address, or_watch_only=True):
|
||||
|
@ -479,9 +479,11 @@ class NAVInterface(BTCInterface):
|
|||
|
||||
block_rv = {
|
||||
'hash': block_hash,
|
||||
'previousblockhash': block_header['previousblockhash'],
|
||||
'tx': tx_rv,
|
||||
'confirmations': block_header['confirmations'],
|
||||
'height': block_header['height'],
|
||||
'time': block_header['time'],
|
||||
'version': block_header['version'],
|
||||
'merkleroot': block_header['merkleroot'],
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ class NMCInterface(BTCInterface):
|
|||
def coin_type():
|
||||
return Coins.NMC
|
||||
|
||||
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index=False):
|
||||
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False, vout: int = -1):
|
||||
self._log.debug('[rm] scantxoutset start') # scantxoutset is slow
|
||||
ro = self.rpc('scantxoutset', ['start', ['addr({})'.format(dest_address)]]) # TODO: Use combo(address) where possible
|
||||
self._log.debug('[rm] scantxoutset end')
|
||||
|
|
|
@ -14,7 +14,7 @@ from basicswap.contrib.test_framework.messages import (
|
|||
from basicswap.contrib.test_framework.script import (
|
||||
CScript,
|
||||
OP_0,
|
||||
OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG
|
||||
OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG,
|
||||
)
|
||||
from basicswap.util import (
|
||||
ensure,
|
||||
|
@ -26,8 +26,8 @@ from basicswap.util.script import (
|
|||
getWitnessElementLen,
|
||||
)
|
||||
from basicswap.util.address import (
|
||||
toWIF,
|
||||
encodeStealthAddress)
|
||||
encodeStealthAddress,
|
||||
)
|
||||
from basicswap.chainparams import Coins, chainparams
|
||||
from .btc import BTCInterface
|
||||
|
||||
|
@ -73,6 +73,9 @@ class PARTInterface(BTCInterface):
|
|||
super().__init__(coin_settings, network, swap_client)
|
||||
self.setAnonTxRingSize(int(coin_settings.get('anon_tx_ring_size', 12)))
|
||||
|
||||
def use_tx_vsize(self) -> bool:
|
||||
return True
|
||||
|
||||
def setAnonTxRingSize(self, value):
|
||||
ensure(value >= 3 and value < 33, 'Invalid anon_tx_ring_size value')
|
||||
self._anon_tx_ring_size = value
|
||||
|
@ -687,8 +690,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||
else:
|
||||
addr_info = self.rpc_wallet('getaddressinfo', [sx_addr])
|
||||
if not addr_info['iswatchonly']:
|
||||
wif_prefix = self.chainparams_network()['key_prefix']
|
||||
wif_scan_key = toWIF(wif_prefix, kbv)
|
||||
wif_scan_key = self.encodeKey(kbv)
|
||||
self.rpc_wallet('importstealthaddress', [wif_scan_key, Kbs.hex()])
|
||||
self._log.info('Imported watch-only sx_addr: {}'.format(sx_addr))
|
||||
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height))
|
||||
|
@ -719,9 +721,8 @@ class PARTInterfaceBlind(PARTInterface):
|
|||
sx_addr = self.formatStealthAddress(Kbv, Kbs)
|
||||
addr_info = self.rpc_wallet('getaddressinfo', [sx_addr])
|
||||
if not addr_info['ismine']:
|
||||
wif_prefix = self.chainparams_network()['key_prefix']
|
||||
wif_scan_key = toWIF(wif_prefix, kbv)
|
||||
wif_spend_key = toWIF(wif_prefix, kbs)
|
||||
wif_scan_key = self.encodeKey(kbv)
|
||||
wif_spend_key = self.encodeKey(kbs)
|
||||
self.rpc_wallet('importstealthaddress', [wif_scan_key, wif_spend_key])
|
||||
self._log.info('Imported spend key for sx_addr: {}'.format(sx_addr))
|
||||
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height))
|
||||
|
@ -825,8 +826,7 @@ class PARTInterfaceAnon(PARTInterface):
|
|||
else:
|
||||
addr_info = self.rpc_wallet('getaddressinfo', [sx_addr])
|
||||
if not addr_info['iswatchonly']:
|
||||
wif_prefix = self.chainparams_network()['key_prefix']
|
||||
wif_scan_key = toWIF(wif_prefix, kbv)
|
||||
wif_scan_key = self.encodeKey(kbv)
|
||||
self.rpc_wallet('importstealthaddress', [wif_scan_key, Kbs.hex()])
|
||||
self._log.info('Imported watch-only sx_addr: {}'.format(sx_addr))
|
||||
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height))
|
||||
|
@ -857,9 +857,8 @@ class PARTInterfaceAnon(PARTInterface):
|
|||
sx_addr = self.formatStealthAddress(Kbv, Kbs)
|
||||
addr_info = self.rpc_wallet('getaddressinfo', [sx_addr])
|
||||
if not addr_info['ismine']:
|
||||
wif_prefix = self.chainparams_network()['key_prefix']
|
||||
wif_scan_key = toWIF(wif_prefix, kbv)
|
||||
wif_spend_key = toWIF(wif_prefix, kbs)
|
||||
wif_scan_key = self.encodeKey(kbv)
|
||||
wif_spend_key = self.encodeKey(kbs)
|
||||
self.rpc_wallet('importstealthaddress', [wif_scan_key, wif_spend_key])
|
||||
self._log.info('Imported spend key for sx_addr: {}'.format(sx_addr))
|
||||
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height))
|
||||
|
|
|
@ -75,9 +75,11 @@ class PIVXInterface(BTCInterface):
|
|||
|
||||
block_rv = {
|
||||
'hash': block_hash,
|
||||
'previousblockhash': block_header['previousblockhash'],
|
||||
'tx': tx_rv,
|
||||
'confirmations': block_header['confirmations'],
|
||||
'height': block_header['height'],
|
||||
'time': block_header['time'],
|
||||
'version': block_header['version'],
|
||||
'merkleroot': block_header['merkleroot'],
|
||||
}
|
||||
|
|
|
@ -49,6 +49,9 @@ message BidMessage {
|
|||
string proof_signature = 8;
|
||||
|
||||
bytes proof_utxos = 9; /* 32 byte txid 2 byte vout, repeated */
|
||||
|
||||
/* optional */
|
||||
bytes pkhash_buyer_to = 13; /* When pubkey hash is different on the to-chain */
|
||||
}
|
||||
|
||||
/* For tests */
|
||||
|
@ -65,6 +68,7 @@ message BidAcceptMessage {
|
|||
bytes bid_msg_id = 1;
|
||||
bytes initiate_txid = 2;
|
||||
bytes contract_script = 3;
|
||||
bytes pkhash_seller = 4;
|
||||
}
|
||||
|
||||
message OfferRevokeMessage {
|
||||
|
|
|
@ -14,7 +14,7 @@ _sym_db = _symbol_database.Default()
|
|||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0emessages.proto\x12\tbasicswap\"\xc0\x04\n\x0cOfferMessage\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x11\n\tcoin_from\x18\x02 \x01(\r\x12\x0f\n\x07\x63oin_to\x18\x03 \x01(\r\x12\x13\n\x0b\x61mount_from\x18\x04 \x01(\x04\x12\x11\n\tamount_to\x18\x05 \x01(\x04\x12\x16\n\x0emin_bid_amount\x18\x06 \x01(\x04\x12\x12\n\ntime_valid\x18\x07 \x01(\x04\x12\x33\n\tlock_type\x18\x08 \x01(\x0e\x32 .basicswap.OfferMessage.LockType\x12\x12\n\nlock_value\x18\t \x01(\r\x12\x11\n\tswap_type\x18\n \x01(\r\x12\x15\n\rproof_address\x18\x0b \x01(\t\x12\x17\n\x0fproof_signature\x18\x0c \x01(\t\x12\x15\n\rpkhash_seller\x18\r \x01(\x0c\x12\x13\n\x0bsecret_hash\x18\x0e \x01(\x0c\x12\x15\n\rfee_rate_from\x18\x0f \x01(\x04\x12\x13\n\x0b\x66\x65\x65_rate_to\x18\x10 \x01(\x04\x12\x19\n\x11\x61mount_negotiable\x18\x11 \x01(\x08\x12\x17\n\x0frate_negotiable\x18\x12 \x01(\x08\x12\x13\n\x0bproof_utxos\x18\x13 \x01(\x0c\"q\n\x08LockType\x12\x0b\n\x07NOT_SET\x10\x00\x12\x18\n\x14SEQUENCE_LOCK_BLOCKS\x10\x01\x12\x16\n\x12SEQUENCE_LOCK_TIME\x10\x02\x12\x13\n\x0f\x41\x42S_LOCK_BLOCKS\x10\x03\x12\x11\n\rABS_LOCK_TIME\x10\x04\"\xce\x01\n\nBidMessage\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x14\n\x0coffer_msg_id\x18\x02 \x01(\x0c\x12\x12\n\ntime_valid\x18\x03 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x04 \x01(\x04\x12\x11\n\tamount_to\x18\x05 \x01(\x04\x12\x14\n\x0cpkhash_buyer\x18\x06 \x01(\x0c\x12\x15\n\rproof_address\x18\x07 \x01(\t\x12\x17\n\x0fproof_signature\x18\x08 \x01(\t\x12\x13\n\x0bproof_utxos\x18\t \x01(\x0c\"s\n\x0f\x42idMessage_test\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x14\n\x0coffer_msg_id\x18\x02 \x01(\x0c\x12\x12\n\ntime_valid\x18\x03 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x04 \x01(\x04\x12\x0c\n\x04rate\x18\x05 \x01(\x04\"V\n\x10\x42idAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x15\n\rinitiate_txid\x18\x02 \x01(\x0c\x12\x17\n\x0f\x63ontract_script\x18\x03 \x01(\x0c\"=\n\x12OfferRevokeMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x11\n\tsignature\x18\x02 \x01(\x0c\";\n\x10\x42idRejectMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x13\n\x0breject_code\x18\x02 \x01(\r\"\xb7\x01\n\rXmrBidMessage\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x14\n\x0coffer_msg_id\x18\x02 \x01(\x0c\x12\x12\n\ntime_valid\x18\x03 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x04 \x01(\x04\x12\x11\n\tamount_to\x18\x05 \x01(\x04\x12\x0c\n\x04pkaf\x18\x06 \x01(\x0c\x12\x0c\n\x04kbvf\x18\x07 \x01(\x0c\x12\x12\n\nkbsf_dleag\x18\x08 \x01(\x0c\x12\x0f\n\x07\x64\x65st_af\x18\t \x01(\x0c\"T\n\x0fXmrSplitMessage\x12\x0e\n\x06msg_id\x18\x01 \x01(\x0c\x12\x10\n\x08msg_type\x18\x02 \x01(\r\x12\x10\n\x08sequence\x18\x03 \x01(\r\x12\r\n\x05\x64leag\x18\x04 \x01(\x0c\"\x80\x02\n\x13XmrBidAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x0c\n\x04pkal\x18\x02 \x01(\x0c\x12\x0c\n\x04kbvl\x18\x03 \x01(\x0c\x12\x12\n\nkbsl_dleag\x18\x04 \x01(\x0c\x12\x11\n\ta_lock_tx\x18\x05 \x01(\x0c\x12\x18\n\x10\x61_lock_tx_script\x18\x06 \x01(\x0c\x12\x18\n\x10\x61_lock_refund_tx\x18\x07 \x01(\x0c\x12\x1f\n\x17\x61_lock_refund_tx_script\x18\x08 \x01(\x0c\x12\x1e\n\x16\x61_lock_refund_spend_tx\x18\t \x01(\x0c\x12\x1d\n\x15\x61l_lock_refund_tx_sig\x18\n \x01(\x0c\"r\n\x17XmrBidLockTxSigsMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12$\n\x1c\x61\x66_lock_refund_spend_tx_esig\x18\x02 \x01(\x0c\x12\x1d\n\x15\x61\x66_lock_refund_tx_sig\x18\x03 \x01(\x0c\"X\n\x18XmrBidLockSpendTxMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x17\n\x0f\x61_lock_spend_tx\x18\x02 \x01(\x0c\x12\x0f\n\x07kal_sig\x18\x03 \x01(\x0c\"M\n\x18XmrBidLockReleaseMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x1d\n\x15\x61l_lock_spend_tx_esig\x18\x02 \x01(\x0c\"\x81\x01\n\x13\x41\x44SBidIntentMessage\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x14\n\x0coffer_msg_id\x18\x02 \x01(\x0c\x12\x12\n\ntime_valid\x18\x03 \x01(\x04\x12\x13\n\x0b\x61mount_from\x18\x04 \x01(\x04\x12\x11\n\tamount_to\x18\x05 \x01(\x04\"p\n\x19\x41\x44SBidIntentAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x0c\n\x04pkaf\x18\x02 \x01(\x0c\x12\x0c\n\x04kbvf\x18\x03 \x01(\x0c\x12\x12\n\nkbsf_dleag\x18\x04 \x01(\x0c\x12\x0f\n\x07\x64\x65st_af\x18\x05 \x01(\x0c\x62\x06proto3')
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0emessages.proto\x12\tbasicswap\"\xc0\x04\n\x0cOfferMessage\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x11\n\tcoin_from\x18\x02 \x01(\r\x12\x0f\n\x07\x63oin_to\x18\x03 \x01(\r\x12\x13\n\x0b\x61mount_from\x18\x04 \x01(\x04\x12\x11\n\tamount_to\x18\x05 \x01(\x04\x12\x16\n\x0emin_bid_amount\x18\x06 \x01(\x04\x12\x12\n\ntime_valid\x18\x07 \x01(\x04\x12\x33\n\tlock_type\x18\x08 \x01(\x0e\x32 .basicswap.OfferMessage.LockType\x12\x12\n\nlock_value\x18\t \x01(\r\x12\x11\n\tswap_type\x18\n \x01(\r\x12\x15\n\rproof_address\x18\x0b \x01(\t\x12\x17\n\x0fproof_signature\x18\x0c \x01(\t\x12\x15\n\rpkhash_seller\x18\r \x01(\x0c\x12\x13\n\x0bsecret_hash\x18\x0e \x01(\x0c\x12\x15\n\rfee_rate_from\x18\x0f \x01(\x04\x12\x13\n\x0b\x66\x65\x65_rate_to\x18\x10 \x01(\x04\x12\x19\n\x11\x61mount_negotiable\x18\x11 \x01(\x08\x12\x17\n\x0frate_negotiable\x18\x12 \x01(\x08\x12\x13\n\x0bproof_utxos\x18\x13 \x01(\x0c\"q\n\x08LockType\x12\x0b\n\x07NOT_SET\x10\x00\x12\x18\n\x14SEQUENCE_LOCK_BLOCKS\x10\x01\x12\x16\n\x12SEQUENCE_LOCK_TIME\x10\x02\x12\x13\n\x0f\x41\x42S_LOCK_BLOCKS\x10\x03\x12\x11\n\rABS_LOCK_TIME\x10\x04\"\xe7\x01\n\nBidMessage\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x14\n\x0coffer_msg_id\x18\x02 \x01(\x0c\x12\x12\n\ntime_valid\x18\x03 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x04 \x01(\x04\x12\x11\n\tamount_to\x18\x05 \x01(\x04\x12\x14\n\x0cpkhash_buyer\x18\x06 \x01(\x0c\x12\x15\n\rproof_address\x18\x07 \x01(\t\x12\x17\n\x0fproof_signature\x18\x08 \x01(\t\x12\x13\n\x0bproof_utxos\x18\t \x01(\x0c\x12\x17\n\x0fpkhash_buyer_to\x18\r \x01(\x0c\"s\n\x0f\x42idMessage_test\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x14\n\x0coffer_msg_id\x18\x02 \x01(\x0c\x12\x12\n\ntime_valid\x18\x03 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x04 \x01(\x04\x12\x0c\n\x04rate\x18\x05 \x01(\x04\"m\n\x10\x42idAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x15\n\rinitiate_txid\x18\x02 \x01(\x0c\x12\x17\n\x0f\x63ontract_script\x18\x03 \x01(\x0c\x12\x15\n\rpkhash_seller\x18\x04 \x01(\x0c\"=\n\x12OfferRevokeMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x11\n\tsignature\x18\x02 \x01(\x0c\";\n\x10\x42idRejectMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x13\n\x0breject_code\x18\x02 \x01(\r\"\xb7\x01\n\rXmrBidMessage\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x14\n\x0coffer_msg_id\x18\x02 \x01(\x0c\x12\x12\n\ntime_valid\x18\x03 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x04 \x01(\x04\x12\x11\n\tamount_to\x18\x05 \x01(\x04\x12\x0c\n\x04pkaf\x18\x06 \x01(\x0c\x12\x0c\n\x04kbvf\x18\x07 \x01(\x0c\x12\x12\n\nkbsf_dleag\x18\x08 \x01(\x0c\x12\x0f\n\x07\x64\x65st_af\x18\t \x01(\x0c\"T\n\x0fXmrSplitMessage\x12\x0e\n\x06msg_id\x18\x01 \x01(\x0c\x12\x10\n\x08msg_type\x18\x02 \x01(\r\x12\x10\n\x08sequence\x18\x03 \x01(\r\x12\r\n\x05\x64leag\x18\x04 \x01(\x0c\"\x80\x02\n\x13XmrBidAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x0c\n\x04pkal\x18\x02 \x01(\x0c\x12\x0c\n\x04kbvl\x18\x03 \x01(\x0c\x12\x12\n\nkbsl_dleag\x18\x04 \x01(\x0c\x12\x11\n\ta_lock_tx\x18\x05 \x01(\x0c\x12\x18\n\x10\x61_lock_tx_script\x18\x06 \x01(\x0c\x12\x18\n\x10\x61_lock_refund_tx\x18\x07 \x01(\x0c\x12\x1f\n\x17\x61_lock_refund_tx_script\x18\x08 \x01(\x0c\x12\x1e\n\x16\x61_lock_refund_spend_tx\x18\t \x01(\x0c\x12\x1d\n\x15\x61l_lock_refund_tx_sig\x18\n \x01(\x0c\"r\n\x17XmrBidLockTxSigsMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12$\n\x1c\x61\x66_lock_refund_spend_tx_esig\x18\x02 \x01(\x0c\x12\x1d\n\x15\x61\x66_lock_refund_tx_sig\x18\x03 \x01(\x0c\"X\n\x18XmrBidLockSpendTxMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x17\n\x0f\x61_lock_spend_tx\x18\x02 \x01(\x0c\x12\x0f\n\x07kal_sig\x18\x03 \x01(\x0c\"M\n\x18XmrBidLockReleaseMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x1d\n\x15\x61l_lock_spend_tx_esig\x18\x02 \x01(\x0c\"\x81\x01\n\x13\x41\x44SBidIntentMessage\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x14\n\x0coffer_msg_id\x18\x02 \x01(\x0c\x12\x12\n\ntime_valid\x18\x03 \x01(\x04\x12\x13\n\x0b\x61mount_from\x18\x04 \x01(\x04\x12\x11\n\tamount_to\x18\x05 \x01(\x04\"p\n\x19\x41\x44SBidIntentAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x0c\n\x04pkaf\x18\x02 \x01(\x0c\x12\x0c\n\x04kbvf\x18\x03 \x01(\x0c\x12\x12\n\nkbsf_dleag\x18\x04 \x01(\x0c\x12\x0f\n\x07\x64\x65st_af\x18\x05 \x01(\x0c\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
|
@ -26,29 +26,29 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
|||
_globals['_OFFERMESSAGE_LOCKTYPE']._serialized_start=493
|
||||
_globals['_OFFERMESSAGE_LOCKTYPE']._serialized_end=606
|
||||
_globals['_BIDMESSAGE']._serialized_start=609
|
||||
_globals['_BIDMESSAGE']._serialized_end=815
|
||||
_globals['_BIDMESSAGE_TEST']._serialized_start=817
|
||||
_globals['_BIDMESSAGE_TEST']._serialized_end=932
|
||||
_globals['_BIDACCEPTMESSAGE']._serialized_start=934
|
||||
_globals['_BIDACCEPTMESSAGE']._serialized_end=1020
|
||||
_globals['_OFFERREVOKEMESSAGE']._serialized_start=1022
|
||||
_globals['_OFFERREVOKEMESSAGE']._serialized_end=1083
|
||||
_globals['_BIDREJECTMESSAGE']._serialized_start=1085
|
||||
_globals['_BIDREJECTMESSAGE']._serialized_end=1144
|
||||
_globals['_XMRBIDMESSAGE']._serialized_start=1147
|
||||
_globals['_XMRBIDMESSAGE']._serialized_end=1330
|
||||
_globals['_XMRSPLITMESSAGE']._serialized_start=1332
|
||||
_globals['_XMRSPLITMESSAGE']._serialized_end=1416
|
||||
_globals['_XMRBIDACCEPTMESSAGE']._serialized_start=1419
|
||||
_globals['_XMRBIDACCEPTMESSAGE']._serialized_end=1675
|
||||
_globals['_XMRBIDLOCKTXSIGSMESSAGE']._serialized_start=1677
|
||||
_globals['_XMRBIDLOCKTXSIGSMESSAGE']._serialized_end=1791
|
||||
_globals['_XMRBIDLOCKSPENDTXMESSAGE']._serialized_start=1793
|
||||
_globals['_XMRBIDLOCKSPENDTXMESSAGE']._serialized_end=1881
|
||||
_globals['_XMRBIDLOCKRELEASEMESSAGE']._serialized_start=1883
|
||||
_globals['_XMRBIDLOCKRELEASEMESSAGE']._serialized_end=1960
|
||||
_globals['_ADSBIDINTENTMESSAGE']._serialized_start=1963
|
||||
_globals['_ADSBIDINTENTMESSAGE']._serialized_end=2092
|
||||
_globals['_ADSBIDINTENTACCEPTMESSAGE']._serialized_start=2094
|
||||
_globals['_ADSBIDINTENTACCEPTMESSAGE']._serialized_end=2206
|
||||
_globals['_BIDMESSAGE']._serialized_end=840
|
||||
_globals['_BIDMESSAGE_TEST']._serialized_start=842
|
||||
_globals['_BIDMESSAGE_TEST']._serialized_end=957
|
||||
_globals['_BIDACCEPTMESSAGE']._serialized_start=959
|
||||
_globals['_BIDACCEPTMESSAGE']._serialized_end=1068
|
||||
_globals['_OFFERREVOKEMESSAGE']._serialized_start=1070
|
||||
_globals['_OFFERREVOKEMESSAGE']._serialized_end=1131
|
||||
_globals['_BIDREJECTMESSAGE']._serialized_start=1133
|
||||
_globals['_BIDREJECTMESSAGE']._serialized_end=1192
|
||||
_globals['_XMRBIDMESSAGE']._serialized_start=1195
|
||||
_globals['_XMRBIDMESSAGE']._serialized_end=1378
|
||||
_globals['_XMRSPLITMESSAGE']._serialized_start=1380
|
||||
_globals['_XMRSPLITMESSAGE']._serialized_end=1464
|
||||
_globals['_XMRBIDACCEPTMESSAGE']._serialized_start=1467
|
||||
_globals['_XMRBIDACCEPTMESSAGE']._serialized_end=1723
|
||||
_globals['_XMRBIDLOCKTXSIGSMESSAGE']._serialized_start=1725
|
||||
_globals['_XMRBIDLOCKTXSIGSMESSAGE']._serialized_end=1839
|
||||
_globals['_XMRBIDLOCKSPENDTXMESSAGE']._serialized_start=1841
|
||||
_globals['_XMRBIDLOCKSPENDTXMESSAGE']._serialized_end=1929
|
||||
_globals['_XMRBIDLOCKRELEASEMESSAGE']._serialized_start=1931
|
||||
_globals['_XMRBIDLOCKRELEASEMESSAGE']._serialized_end=2008
|
||||
_globals['_ADSBIDINTENTMESSAGE']._serialized_start=2011
|
||||
_globals['_ADSBIDINTENTMESSAGE']._serialized_end=2140
|
||||
_globals['_ADSBIDINTENTACCEPTMESSAGE']._serialized_start=2142
|
||||
_globals['_ADSBIDINTENTACCEPTMESSAGE']._serialized_end=2254
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2020-2023 tecnovert
|
||||
# Copyright (c) 2020-2024 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
|
@ -10,12 +10,15 @@ from basicswap.db import (
|
|||
from basicswap.util import (
|
||||
SerialiseNum,
|
||||
)
|
||||
from basicswap.util.script import (
|
||||
decodeScriptNum,
|
||||
)
|
||||
from basicswap.script import (
|
||||
OpCodes,
|
||||
)
|
||||
from basicswap.basicswap_util import (
|
||||
SwapTypes,
|
||||
EventLogTypes,
|
||||
SwapTypes,
|
||||
)
|
||||
from . import ProtocolInterface
|
||||
|
||||
|
@ -23,13 +26,13 @@ INITIATE_TX_TIMEOUT = 40 * 60 # TODO: make variable per coin
|
|||
ABS_LOCK_TIME_LEEWAY = 10 * 60
|
||||
|
||||
|
||||
def buildContractScript(lock_val: int, secret_hash: bytes, pkh_redeem: bytes, pkh_refund: bytes, op_lock=OpCodes.OP_CHECKSEQUENCEVERIFY) -> bytearray:
|
||||
def buildContractScript(lock_val: int, secret_hash: bytes, pkh_redeem: bytes, pkh_refund: bytes, op_lock=OpCodes.OP_CHECKSEQUENCEVERIFY, op_hash=OpCodes.OP_SHA256) -> bytearray:
|
||||
script = bytearray([
|
||||
OpCodes.OP_IF,
|
||||
OpCodes.OP_SIZE,
|
||||
0x01, 0x20, # 32
|
||||
OpCodes.OP_EQUALVERIFY,
|
||||
OpCodes.OP_SHA256,
|
||||
op_hash,
|
||||
0x20]) \
|
||||
+ secret_hash \
|
||||
+ bytearray([
|
||||
|
@ -54,6 +57,46 @@ def buildContractScript(lock_val: int, secret_hash: bytes, pkh_redeem: bytes, pk
|
|||
return script
|
||||
|
||||
|
||||
def verifyContractScript(script, op_lock=OpCodes.OP_CHECKSEQUENCEVERIFY, op_hash=OpCodes.OP_SHA256):
|
||||
if script[0] != OpCodes.OP_IF or \
|
||||
script[1] != OpCodes.OP_SIZE or \
|
||||
script[2] != 0x01 or script[3] != 0x20 or \
|
||||
script[4] != OpCodes.OP_EQUALVERIFY or \
|
||||
script[5] != op_hash or \
|
||||
script[6] != 0x20:
|
||||
return False, None, None, None, None
|
||||
o = 7
|
||||
script_hash = script[o: o + 32]
|
||||
o += 32
|
||||
if script[o] != OpCodes.OP_EQUALVERIFY or \
|
||||
script[o + 1] != OpCodes.OP_DUP or \
|
||||
script[o + 2] != OpCodes.OP_HASH160 or \
|
||||
script[o + 3] != 0x14:
|
||||
return False, script_hash, None, None, None
|
||||
o += 4
|
||||
pkh_redeem = script[o: o + 20]
|
||||
o += 20
|
||||
if script[o] != OpCodes.OP_ELSE:
|
||||
return False, script_hash, pkh_redeem, None, None
|
||||
o += 1
|
||||
lock_val, nb = decodeScriptNum(script, o)
|
||||
o += nb
|
||||
if script[o] != op_lock or \
|
||||
script[o + 1] != OpCodes.OP_DROP or \
|
||||
script[o + 2] != OpCodes.OP_DUP or \
|
||||
script[o + 3] != OpCodes.OP_HASH160 or \
|
||||
script[o + 4] != 0x14:
|
||||
return False, script_hash, pkh_redeem, lock_val, None
|
||||
o += 5
|
||||
pkh_refund = script[o: o + 20]
|
||||
o += 20
|
||||
if script[o] != OpCodes.OP_ENDIF or \
|
||||
script[o + 1] != OpCodes.OP_EQUALVERIFY or \
|
||||
script[o + 2] != OpCodes.OP_CHECKSIG:
|
||||
return False, script_hash, pkh_redeem, lock_val, pkh_refund
|
||||
return True, script_hash, pkh_redeem, lock_val, pkh_refund
|
||||
|
||||
|
||||
def extractScriptSecretHash(script):
|
||||
return script[7:39]
|
||||
|
||||
|
|
|
@ -26,3 +26,5 @@ class OpCodes(IntEnum):
|
|||
OP_CHECKSIG = 0xac,
|
||||
OP_CHECKLOCKTIMEVERIFY = 0xb1,
|
||||
OP_CHECKSEQUENCEVERIFY = 0xb2,
|
||||
|
||||
OP_SHA256_DECRED = 0xc0,
|
||||
|
|
|
@ -5,6 +5,29 @@
|
|||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
|
||||
def decode_compactsize(b: bytes, offset: int = 0) -> (int, int):
|
||||
i = b[offset]
|
||||
if i < 0xfd:
|
||||
return i, 1
|
||||
offset += 1
|
||||
if i == 0xfd:
|
||||
return int.from_bytes(b[offset: offset + 2]), 3
|
||||
if i == 0xfe:
|
||||
return int.from_bytes(b[offset: offset + 4]), 5
|
||||
# 0xff
|
||||
return int.from_bytes(b[offset: offset + 8]), 9
|
||||
|
||||
|
||||
def encode_compactsize(i: int) -> bytes:
|
||||
if i < 0xfd:
|
||||
return bytes((i,))
|
||||
if i <= 0xffff:
|
||||
return bytes((0xfd,)) + i.to_bytes(2, 'little')
|
||||
if i <= 0xffffffff:
|
||||
return bytes((0xfe,)) + i.to_bytes(4, 'little')
|
||||
return bytes((0xff,)) + i.to_bytes(8, 'little')
|
||||
|
||||
|
||||
def decode_varint(b: bytes, offset: int = 0) -> (int, int):
|
||||
i: int = 0
|
||||
num_bytes: int = 0
|
||||
|
|
|
@ -389,7 +389,7 @@ def extract_states_from_xu_file(file_path, prefix):
|
|||
return states
|
||||
|
||||
|
||||
def compare_bid_states(states, expect_states, exact_match=True):
|
||||
def compare_bid_states(states, expect_states, exact_match: bool = True) -> bool:
|
||||
|
||||
for i in range(len(states) - 1, -1, -1):
|
||||
if states[i][1] == 'Bid Delaying':
|
||||
|
@ -417,3 +417,19 @@ def compare_bid_states(states, expect_states, exact_match=True):
|
|||
logging.info('Have states: {}'.format(json.dumps(states, indent=4)))
|
||||
raise e
|
||||
return True
|
||||
|
||||
|
||||
def compare_bid_states_unordered(states, expect_states) -> bool:
|
||||
for i in range(len(states) - 1, -1, -1):
|
||||
if states[i][1] == 'Bid Delaying':
|
||||
del states[i]
|
||||
|
||||
try:
|
||||
assert len(states) == len(expect_states)
|
||||
for state in expect_states:
|
||||
assert (any(state in s[1] for s in states))
|
||||
except Exception as e:
|
||||
logging.info('Expecting states: {}'.format(json.dumps(expect_states, indent=4)))
|
||||
logging.info('Have states: {}'.format(json.dumps(states, indent=4)))
|
||||
raise e
|
||||
return True
|
||||
|
|
|
@ -5,8 +5,13 @@
|
|||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
# TODO
|
||||
# - Occasionally DCR simnet chain stalls.
|
||||
|
||||
import copy
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import select
|
||||
import subprocess
|
||||
import unittest
|
||||
|
@ -14,7 +19,14 @@ import unittest
|
|||
import basicswap.config as cfg
|
||||
|
||||
from basicswap.basicswap import (
|
||||
BidStates,
|
||||
Coins,
|
||||
DebugTypes,
|
||||
SwapTypes,
|
||||
TxStates,
|
||||
)
|
||||
from basicswap.basicswap_util import (
|
||||
TxLockTypes,
|
||||
)
|
||||
from basicswap.util.crypto import (
|
||||
hash160
|
||||
|
@ -27,9 +39,14 @@ from basicswap.interface.dcr.messages import (
|
|||
TxSerializeType,
|
||||
)
|
||||
from tests.basicswap.common import (
|
||||
compare_bid_states,
|
||||
compare_bid_states_unordered,
|
||||
stopDaemons,
|
||||
waitForRPC,
|
||||
wait_for_balance,
|
||||
wait_for_bid,
|
||||
wait_for_bid_tx_state,
|
||||
wait_for_offer,
|
||||
waitForRPC,
|
||||
)
|
||||
from tests.basicswap.util import (
|
||||
read_json_api,
|
||||
|
@ -63,6 +80,176 @@ def make_rpc_func(node_id, base_rpc_port):
|
|||
return rpc_func
|
||||
|
||||
|
||||
def test_success_path(self, coin_from: Coins, coin_to: Coins):
|
||||
logging.info(f'---------- Test {coin_from.name} to {coin_to.name}')
|
||||
|
||||
node_from = 0
|
||||
node_to = 1
|
||||
swap_clients = self.swap_clients
|
||||
ci_from = swap_clients[node_from].ci(coin_from)
|
||||
ci_to = swap_clients[node_to].ci(coin_to)
|
||||
|
||||
self.prepare_balance(coin_to, 100.0, 1801, 1800)
|
||||
self.prepare_balance(coin_from, 100.0, 1800, 1801)
|
||||
|
||||
amt_swap = ci_from.make_int(random.uniform(0.1, 5.0), r=1)
|
||||
rate_swap = ci_to.make_int(random.uniform(0.2, 10.0), r=1)
|
||||
|
||||
offer_id = swap_clients[node_from].postOffer(coin_from, coin_to, amt_swap, rate_swap, amt_swap, SwapTypes.SELLER_FIRST)
|
||||
|
||||
wait_for_offer(test_delay_event, swap_clients[node_to], offer_id)
|
||||
|
||||
offer = swap_clients[node_to].getOffer(offer_id)
|
||||
bid_id = swap_clients[node_to].postBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[node_from], bid_id)
|
||||
swap_clients[node_from].acceptBid(bid_id)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[node_from], bid_id, BidStates.SWAP_COMPLETED, wait_for=120)
|
||||
wait_for_bid(test_delay_event, swap_clients[node_to], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=120)
|
||||
|
||||
# Verify lock tx spends are found in the expected wallets
|
||||
bid, offer = swap_clients[node_from].getBidAndOffer(bid_id)
|
||||
max_fee: int = 10000
|
||||
itx_spend = bid.initiate_tx.spend_txid.hex()
|
||||
node_to_ci_from = swap_clients[node_to].ci(coin_from)
|
||||
wtx = node_to_ci_from.rpc_wallet('gettransaction', [itx_spend,])
|
||||
assert (amt_swap - node_to_ci_from.make_int(wtx['details'][0]['amount']) < max_fee)
|
||||
|
||||
node_from_ci_to = swap_clients[node_from].ci(coin_to)
|
||||
ptx_spend = bid.participate_tx.spend_txid.hex()
|
||||
wtx = node_from_ci_to.rpc_wallet('gettransaction', [ptx_spend,])
|
||||
assert (bid.amount_to - node_from_ci_to.make_int(wtx['details'][0]['amount']) < max_fee)
|
||||
|
||||
js_0 = read_json_api(1800 + node_from)
|
||||
js_1 = read_json_api(1800 + node_to)
|
||||
assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
|
||||
assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0)
|
||||
|
||||
bid_id_hex = bid_id.hex()
|
||||
path = f'bids/{bid_id_hex}/states'
|
||||
offerer_states = read_json_api(1800 + node_from, path)
|
||||
bidder_states = read_json_api(1800 + node_to, path)
|
||||
|
||||
expect_states = copy.deepcopy(self.states_offerer_sh[0])
|
||||
# Will miss PTX Sent event as PTX is found by searching the chain.
|
||||
if coin_to == Coins.DCR:
|
||||
expect_states[5] = 'PTX In Chain'
|
||||
assert (compare_bid_states(offerer_states, expect_states) is True)
|
||||
assert (compare_bid_states(bidder_states, self.states_bidder_sh[0]) is True)
|
||||
|
||||
|
||||
def test_bad_ptx(self, coin_from: Coins, coin_to: Coins):
|
||||
# Invalid PTX sent, swap should stall and ITx and PTx should be reclaimed by senders
|
||||
logging.info(f'---------- Test bad ptx {coin_from.name} to {coin_to.name}')
|
||||
|
||||
node_from = 0
|
||||
node_to = 1
|
||||
swap_clients = self.swap_clients
|
||||
ci_from = swap_clients[node_from].ci(coin_from)
|
||||
ci_to = swap_clients[node_to].ci(coin_to)
|
||||
|
||||
self.prepare_balance(coin_to, 100.0, 1801, 1800)
|
||||
self.prepare_balance(coin_from, 100.0, 1800, 1801)
|
||||
|
||||
amt_swap = ci_from.make_int(random.uniform(1.1, 10.0), r=1)
|
||||
rate_swap = ci_to.make_int(random.uniform(0.1, 2.0), r=1)
|
||||
|
||||
offer_id = swap_clients[node_from].postOffer(coin_from, coin_to, amt_swap, rate_swap, amt_swap, SwapTypes.SELLER_FIRST,
|
||||
TxLockTypes.SEQUENCE_LOCK_BLOCKS, 10, auto_accept_bids=True)
|
||||
|
||||
wait_for_offer(test_delay_event, swap_clients[node_to], offer_id)
|
||||
offer = swap_clients[node_to].getOffer(offer_id)
|
||||
bid_id = swap_clients[node_to].postBid(offer_id, offer.amount_from)
|
||||
swap_clients[node_to].setBidDebugInd(bid_id, DebugTypes.MAKE_INVALID_PTX)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[node_from], bid_id, BidStates.SWAP_COMPLETED, wait_for=120)
|
||||
wait_for_bid(test_delay_event, swap_clients[node_to], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=120)
|
||||
|
||||
js_0_bid = read_json_api(1800 + node_from, 'bids/{}'.format(bid_id.hex()))
|
||||
js_1_bid = read_json_api(1800 + node_to, 'bids/{}'.format(bid_id.hex()))
|
||||
assert (js_0_bid['itx_state'] == 'Refunded')
|
||||
assert (js_1_bid['ptx_state'] == 'Refunded')
|
||||
|
||||
# Verify lock tx spends are found in the expected wallets
|
||||
bid, offer = swap_clients[node_from].getBidAndOffer(bid_id)
|
||||
max_fee: int = 10000
|
||||
itx_spend = bid.initiate_tx.spend_txid.hex()
|
||||
node_from_ci_from = swap_clients[node_from].ci(coin_from)
|
||||
wtx = node_from_ci_from.rpc_wallet('gettransaction', [itx_spend,])
|
||||
assert (amt_swap - node_from_ci_from.make_int(wtx['details'][0]['amount']) < max_fee)
|
||||
|
||||
node_to_ci_to = swap_clients[node_to].ci(coin_to)
|
||||
bid, offer = swap_clients[node_to].getBidAndOffer(bid_id)
|
||||
ptx_spend = bid.participate_tx.spend_txid.hex()
|
||||
wtx = node_to_ci_to.rpc_wallet('gettransaction', [ptx_spend,])
|
||||
assert (bid.amount_to - node_to_ci_to.make_int(wtx['details'][0]['amount']) < max_fee)
|
||||
|
||||
bid_id_hex = bid_id.hex()
|
||||
path = f'bids/{bid_id_hex}/states'
|
||||
offerer_states = read_json_api(1800 + node_from, path)
|
||||
bidder_states = read_json_api(1800 + node_to, path)
|
||||
|
||||
# Hard to get the timing right
|
||||
assert (compare_bid_states_unordered(offerer_states, self.states_offerer_sh[1]) is True)
|
||||
assert (compare_bid_states_unordered(bidder_states, self.states_bidder_sh[1]) is True)
|
||||
|
||||
js_0 = read_json_api(1800 + node_from)
|
||||
js_1 = read_json_api(1800 + node_to)
|
||||
assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
|
||||
assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0)
|
||||
|
||||
|
||||
def test_itx_refund(self, coin_from: Coins, coin_to: Coins):
|
||||
# Offerer claims PTX and refunds ITX after lock expires
|
||||
# Bidder loses PTX value without gaining ITX value
|
||||
logging.info(f'---------- Test itx refund {coin_from.name} to {coin_to.name}')
|
||||
|
||||
node_from = 0
|
||||
node_to = 1
|
||||
swap_clients = self.swap_clients
|
||||
ci_from = swap_clients[node_from].ci(coin_from)
|
||||
ci_to = swap_clients[node_to].ci(coin_to)
|
||||
|
||||
self.prepare_balance(coin_to, 100.0, 1801, 1800)
|
||||
self.prepare_balance(coin_from, 100.0, 1800, 1801)
|
||||
|
||||
swap_value = ci_from.make_int(random.uniform(2.0, 20.0), r=1)
|
||||
rate_swap = ci_to.make_int(0.5, r=1)
|
||||
offer_id = swap_clients[node_from].postOffer(coin_from, coin_to, swap_value, rate_swap, swap_value, SwapTypes.SELLER_FIRST,
|
||||
TxLockTypes.SEQUENCE_LOCK_BLOCKS, 12)
|
||||
|
||||
wait_for_offer(test_delay_event, swap_clients[node_to], offer_id)
|
||||
offer = swap_clients[node_to].getOffer(offer_id)
|
||||
bid_id = swap_clients[node_to].postBid(offer_id, offer.amount_from)
|
||||
swap_clients[node_to].setBidDebugInd(bid_id, DebugTypes.DONT_SPEND_ITX)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[node_from], bid_id)
|
||||
|
||||
# For testing: Block refunding the ITX until PTX has been redeemed, else ITX refund can become spendable before PTX confirms
|
||||
swap_clients[node_from].setBidDebugInd(bid_id, DebugTypes.SKIP_LOCK_TX_REFUND)
|
||||
swap_clients[node_from].acceptBid(bid_id)
|
||||
wait_for_bid_tx_state(test_delay_event, swap_clients[node_from], bid_id, TxStates.TX_CONFIRMED, TxStates.TX_REDEEMED, wait_for=120)
|
||||
swap_clients[node_from].setBidDebugInd(bid_id, DebugTypes.NONE)
|
||||
|
||||
wait_for_bid_tx_state(test_delay_event, swap_clients[node_from], bid_id, TxStates.TX_REFUNDED, TxStates.TX_REDEEMED, wait_for=90)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
|
||||
|
||||
# Verify lock tx spends are found in the expected wallets
|
||||
bid, offer = swap_clients[node_from].getBidAndOffer(bid_id)
|
||||
max_fee: int = 10000
|
||||
itx_spend = bid.initiate_tx.spend_txid.hex()
|
||||
node_from_ci_from = swap_clients[node_from].ci(coin_from)
|
||||
wtx = node_from_ci_from.rpc_wallet('gettransaction', [itx_spend,])
|
||||
assert (swap_value - node_from_ci_from.make_int(wtx['details'][0]['amount']) < max_fee)
|
||||
|
||||
node_from_ci_to = swap_clients[node_from].ci(coin_to)
|
||||
ptx_spend = bid.participate_tx.spend_txid.hex()
|
||||
wtx = node_from_ci_to.rpc_wallet('gettransaction', [ptx_spend,])
|
||||
assert (bid.amount_to - node_from_ci_to.make_int(wtx['details'][0]['amount']) < max_fee)
|
||||
|
||||
|
||||
def prepareDCDDataDir(datadir, node_id, conf_file, dir_prefix, num_nodes=3):
|
||||
node_dir = os.path.join(datadir, dir_prefix + str(node_id))
|
||||
if not os.path.exists(node_dir):
|
||||
|
@ -326,7 +513,7 @@ class Test(BaseTest):
|
|||
swap_clients = self.swap_clients
|
||||
ci0 = swap_clients[0].ci(self.test_coin)
|
||||
|
||||
utxos = ci0.getNewAddress()
|
||||
utxos = ci0.rpc_wallet('listunspent')
|
||||
addr_out = ci0.rpc_wallet('getnewaddress')
|
||||
rtx = ci0.rpc_wallet('createrawtransaction', [[], {addr_out: 2.0}])
|
||||
|
||||
|
@ -530,7 +717,8 @@ class Test(BaseTest):
|
|||
assert (len(unspents) == 1)
|
||||
utxo = unspents[0]
|
||||
|
||||
txout = ci0.rpc('gettxout', [utxo['txid'], utxo['vout'], utxo['tree']])
|
||||
include_mempool: bool = False
|
||||
txout = ci0.rpc('gettxout', [utxo['txid'], utxo['vout'], utxo['tree'], include_mempool])
|
||||
|
||||
# Lock utxo so it's not spent for tickets, while waiting for depth
|
||||
rv = ci0.rpc_wallet('lockunspent', [False, [utxo, ]])
|
||||
|
@ -538,7 +726,7 @@ class Test(BaseTest):
|
|||
def wait_for_depth():
|
||||
for i in range(20):
|
||||
logging.info('Waiting for txout depth, iter {}'.format(i))
|
||||
txout = ci0.rpc('gettxout', [utxo['txid'], utxo['vout'], utxo['tree']])
|
||||
txout = ci0.rpc('gettxout', [utxo['txid'], utxo['vout'], utxo['tree'], True])
|
||||
if txout['confirmations'] > 0:
|
||||
return txout
|
||||
test_delay_event.wait(1)
|
||||
|
@ -555,11 +743,11 @@ class Test(BaseTest):
|
|||
sent_txid = ci0.rpc_wallet('sendrawtransaction', [stx['hex'], ])
|
||||
|
||||
# NOTE: UTXO is still found when spent in the mempool (tested in loop, not delay from wallet to core)
|
||||
txout = ci0.rpc('gettxout', [utxo['txid'], utxo['vout'], utxo['tree']])
|
||||
txout = ci0.rpc('gettxout', [utxo['txid'], utxo['vout'], utxo['tree'], include_mempool])
|
||||
assert (addr in txout['scriptPubKey']['addresses'])
|
||||
|
||||
for i in range(20):
|
||||
txout = ci0.rpc('gettxout', [utxo['txid'], utxo['vout'], utxo['tree']])
|
||||
txout = ci0.rpc('gettxout', [utxo['txid'], utxo['vout'], utxo['tree'], include_mempool])
|
||||
if txout is None:
|
||||
logging.info('txout spent, height before spent {}, height spent {}'.format(chain_height_before_send, ci0.getChainHeight()))
|
||||
break
|
||||
|
@ -574,6 +762,24 @@ class Test(BaseTest):
|
|||
amount_proved = ci0.verifyProofOfFunds(funds_proof[0], funds_proof[1], funds_proof[2], 'test'.encode('utf-8'))
|
||||
assert (amount_proved >= require_amount)
|
||||
|
||||
def test_02_part_coin(self):
|
||||
test_success_path(self, Coins.PART, self.test_coin)
|
||||
|
||||
def test_03_coin_part(self):
|
||||
test_success_path(self, self.test_coin, Coins.PART)
|
||||
|
||||
def test_04_part_coin_bad_ptx(self):
|
||||
test_bad_ptx(self, Coins.PART, self.test_coin)
|
||||
|
||||
def test_05_coin_part_bad_ptx(self):
|
||||
test_bad_ptx(self, self.test_coin, Coins.PART)
|
||||
|
||||
def test_06_part_coin_itx_refund(self):
|
||||
test_itx_refund(self, Coins.PART, self.test_coin)
|
||||
|
||||
def test_07_coin_part_itx_refund(self):
|
||||
test_itx_refund(self, self.test_coin, Coins.PART)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
|
@ -319,7 +319,6 @@ class Test(unittest.TestCase):
|
|||
ci_btc = BTCInterface(coin_settings, 'regtest')
|
||||
|
||||
for i in range(10000):
|
||||
|
||||
test_pairs = random.randint(0, 3)
|
||||
if test_pairs == 0:
|
||||
ci_from = ci_btc
|
||||
|
@ -425,6 +424,9 @@ class Test(unittest.TestCase):
|
|||
msg_buf_v2.ParseFromString(serialised_msg)
|
||||
assert (msg_buf_v2.protocol_version == 2)
|
||||
assert (msg_buf_v2.time_valid == 1024)
|
||||
assert (msg_buf_v2.amount == 0)
|
||||
assert (msg_buf_v2.pkhash_buyer is not None)
|
||||
assert (len(msg_buf_v2.pkhash_buyer) == 0)
|
||||
|
||||
# Decode only the first field
|
||||
msg_buf_v2.ParseFromString(serialised_msg[:2])
|
||||
|
|
|
@ -13,17 +13,16 @@ $ pytest -v -s tests/basicswap/test_run.py::Test::test_04_ltc_btc
|
|||
|
||||
"""
|
||||
|
||||
import os
|
||||
import random
|
||||
import logging
|
||||
import unittest
|
||||
|
||||
from basicswap.basicswap import (
|
||||
Coins,
|
||||
SwapTypes,
|
||||
BidStates,
|
||||
TxStates,
|
||||
Coins,
|
||||
DebugTypes,
|
||||
SwapTypes,
|
||||
TxStates,
|
||||
)
|
||||
from basicswap.basicswap_util import (
|
||||
TxLockTypes,
|
||||
|
@ -40,24 +39,23 @@ from tests.basicswap.util import (
|
|||
read_json_api,
|
||||
)
|
||||
from tests.basicswap.common import (
|
||||
wait_for_offer,
|
||||
wait_for_bid,
|
||||
wait_for_balance,
|
||||
wait_for_unspent,
|
||||
wait_for_bid_tx_state,
|
||||
wait_for_in_progress,
|
||||
TEST_HTTP_PORT,
|
||||
LTC_BASE_RPC_PORT,
|
||||
BTC_BASE_RPC_PORT,
|
||||
compare_bid_states,
|
||||
extract_states_from_xu_file,
|
||||
LTC_BASE_RPC_PORT,
|
||||
TEST_HTTP_PORT,
|
||||
wait_for_balance,
|
||||
wait_for_bid,
|
||||
wait_for_bid_tx_state,
|
||||
wait_for_in_progress,
|
||||
wait_for_offer,
|
||||
wait_for_unspent,
|
||||
)
|
||||
from basicswap.contrib.test_framework.messages import (
|
||||
ToHex,
|
||||
CTxIn,
|
||||
COutPoint,
|
||||
CTransaction,
|
||||
CTxIn,
|
||||
CTxInWitness,
|
||||
ToHex,
|
||||
)
|
||||
from basicswap.contrib.test_framework.script import (
|
||||
CScript,
|
||||
|
@ -88,10 +86,6 @@ class Test(BaseTest):
|
|||
wait_for_balance(test_delay_event, 'http://127.0.0.1:1801/json/wallets/btc', 'balance', 1000.0)
|
||||
wait_for_balance(test_delay_event, 'http://127.0.0.1:1801/json/wallets/ltc', 'balance', 1000.0)
|
||||
|
||||
diagrams_dir = 'doc/protocols/sequence_diagrams'
|
||||
cls.states_bidder = extract_states_from_xu_file(os.path.join(diagrams_dir, 'bidder.alt.xu'), 'B')
|
||||
cls.states_offerer = extract_states_from_xu_file(os.path.join(diagrams_dir, 'offerer.alt.xu'), 'O')
|
||||
|
||||
# Wait for height, or sequencelock is thrown off by genesis blocktime
|
||||
cls.waitForParticlHeight(3)
|
||||
|
||||
|
@ -329,7 +323,8 @@ class Test(BaseTest):
|
|||
logging.info('---------- Test PART to LTC')
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
offer_id = swap_clients[0].postOffer(Coins.PART, Coins.LTC, 100 * COIN, 0.1 * COIN, 100 * COIN, SwapTypes.SELLER_FIRST)
|
||||
swap_value = 100 * COIN
|
||||
offer_id = swap_clients[0].postOffer(Coins.PART, Coins.LTC, swap_value, 0.1 * COIN, swap_value, SwapTypes.SELLER_FIRST)
|
||||
|
||||
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
||||
offer = swap_clients[1].getOffer(offer_id)
|
||||
|
@ -341,21 +336,34 @@ class Test(BaseTest):
|
|||
|
||||
wait_for_in_progress(test_delay_event, swap_clients[1], bid_id, sent=True)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60)
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=80)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=80)
|
||||
|
||||
js_0 = read_json_api(1800)
|
||||
js_1 = read_json_api(1801)
|
||||
assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
|
||||
assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0)
|
||||
# Verify lock tx spends are found in the expected wallets
|
||||
bid, offer = swap_clients[0].getBidAndOffer(bid_id)
|
||||
max_fee: int = 1000
|
||||
itx_spend = bid.initiate_tx.spend_txid.hex()
|
||||
ci_node_1_from = swap_clients[1].ci(Coins.PART)
|
||||
wtx = ci_node_1_from.rpc_wallet('gettransaction', [itx_spend,])
|
||||
assert (swap_value - ci_node_1_from.make_int(wtx['details'][0]['amount']) < max_fee)
|
||||
|
||||
ci_node_0_to = swap_clients[0].ci(Coins.LTC)
|
||||
ptx_spend = bid.participate_tx.spend_txid.hex()
|
||||
wtx = ci_node_0_to.rpc_wallet('gettransaction', [ptx_spend,])
|
||||
assert (bid.amount_to - ci_node_0_to.make_int(wtx['details'][0]['amount']) < max_fee)
|
||||
|
||||
bid_id_hex = bid_id.hex()
|
||||
path = f'bids/{bid_id_hex}/states'
|
||||
offerer_states = read_json_api(1800, path)
|
||||
bidder_states = read_json_api(1801, path)
|
||||
|
||||
assert (compare_bid_states(offerer_states, self.states_offerer[0]) is True)
|
||||
assert (compare_bid_states(bidder_states, self.states_bidder[0]) is True)
|
||||
assert (compare_bid_states(offerer_states, self.states_offerer_sh[0]) is True)
|
||||
assert (compare_bid_states(bidder_states, self.states_bidder_sh[0]) is True)
|
||||
|
||||
js_0 = read_json_api(1800)
|
||||
js_1 = read_json_api(1801)
|
||||
assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
|
||||
assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0)
|
||||
|
||||
def test_03_ltc_part(self):
|
||||
logging.info('---------- Test LTC to PART')
|
||||
|
@ -372,8 +380,8 @@ class Test(BaseTest):
|
|||
|
||||
wait_for_in_progress(test_delay_event, swap_clients[0], bid_id, sent=True)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=80)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, wait_for=80)
|
||||
|
||||
js_0 = read_json_api(1800)
|
||||
js_1 = read_json_api(1801)
|
||||
|
@ -395,8 +403,8 @@ class Test(BaseTest):
|
|||
|
||||
wait_for_in_progress(test_delay_event, swap_clients[1], bid_id, sent=True)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60)
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=80)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=80)
|
||||
|
||||
js_0 = read_json_api(1800)
|
||||
js_1 = read_json_api(1801)
|
||||
|
@ -419,8 +427,8 @@ class Test(BaseTest):
|
|||
read_json_api(1801, 'bids/{}'.format(bid_id.hex()), {'abandon': True})
|
||||
swap_clients[0].acceptBid(bid_id)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.BID_ABANDONED, sent=True, wait_for=60)
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=80)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.BID_ABANDONED, sent=True, wait_for=80)
|
||||
|
||||
js_0_bid = read_json_api(1800, 'bids/{}'.format(bid_id.hex()))
|
||||
js_1_bid = read_json_api(1801, 'bids/{}'.format(bid_id.hex()))
|
||||
|
@ -437,7 +445,7 @@ class Test(BaseTest):
|
|||
offerer_states = read_json_api(1800, path)
|
||||
bidder_states = read_json_api(1801, path)
|
||||
|
||||
assert (compare_bid_states(offerer_states, self.states_offerer[1]) is True)
|
||||
assert (compare_bid_states(offerer_states, self.states_offerer_sh[1]) is True)
|
||||
assert (bidder_states[-1][1] == 'Bid Abandoned')
|
||||
|
||||
def test_06_self_bid(self):
|
||||
|
@ -455,8 +463,8 @@ class Test(BaseTest):
|
|||
wait_for_bid(test_delay_event, swap_clients[0], bid_id)
|
||||
swap_clients[0].acceptBid(bid_id)
|
||||
|
||||
wait_for_bid_tx_state(test_delay_event, swap_clients[0], bid_id, TxStates.TX_REDEEMED, TxStates.TX_REDEEMED, wait_for=60)
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
|
||||
wait_for_bid_tx_state(test_delay_event, swap_clients[0], bid_id, TxStates.TX_REDEEMED, TxStates.TX_REDEEMED, wait_for=80)
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=80)
|
||||
|
||||
js_0 = read_json_api(1800)
|
||||
assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
|
||||
|
@ -504,8 +512,8 @@ class Test(BaseTest):
|
|||
offer = swap_clients[1].getOffer(offer_id)
|
||||
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60)
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=80)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=80)
|
||||
|
||||
def test_10_bad_ptx(self):
|
||||
# Invalid PTX sent, swap should stall and ITx and PTx should be reclaimed by senders
|
||||
|
@ -543,8 +551,8 @@ class Test(BaseTest):
|
|||
offerer_states = read_json_api(1800, path)
|
||||
bidder_states = read_json_api(1801, path)
|
||||
|
||||
assert (compare_bid_states(offerer_states, self.states_offerer[1]) is True)
|
||||
assert (compare_bid_states(bidder_states, self.states_bidder[1]) is True)
|
||||
assert (compare_bid_states(offerer_states, self.states_offerer_sh[1]) is True)
|
||||
assert (compare_bid_states(bidder_states, self.states_bidder_sh[1]) is True)
|
||||
|
||||
'''
|
||||
def test_11_refund(self):
|
||||
|
@ -654,8 +662,8 @@ class Test(BaseTest):
|
|||
offerer_states = read_json_api(1800, path)
|
||||
bidder_states = read_json_api(1801, path)
|
||||
|
||||
assert (compare_bid_states(offerer_states, self.states_offerer[2]) is True)
|
||||
assert (compare_bid_states(bidder_states, self.states_bidder[2], exact_match=False) is True)
|
||||
assert (compare_bid_states(offerer_states, self.states_offerer_sh[2]) is True)
|
||||
assert (compare_bid_states(bidder_states, self.states_bidder_sh[2], exact_match=False) is True)
|
||||
|
||||
def test_14_sweep_balance(self):
|
||||
logging.info('---------- Test sweep balance offer')
|
||||
|
@ -718,8 +726,8 @@ class Test(BaseTest):
|
|||
wait_for_bid(test_delay_event, swap_clients[2], bid_id)
|
||||
swap_clients[2].acceptBid(bid_id)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[2], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60)
|
||||
wait_for_bid(test_delay_event, swap_clients[2], bid_id, BidStates.SWAP_COMPLETED, wait_for=80)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=80)
|
||||
|
||||
# Verify expected inputs were used
|
||||
bid, offer = swap_clients[2].getBidAndOffer(bid_id)
|
||||
|
@ -753,8 +761,8 @@ class Test(BaseTest):
|
|||
|
||||
wait_for_in_progress(test_delay_event, swap_clients[0], bid_id, sent=True)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, wait_for=60)
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=80)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, wait_for=80)
|
||||
|
||||
def pass_99_delay(self):
|
||||
logging.info('Delay')
|
||||
|
|
|
@ -334,6 +334,9 @@ class BaseTest(unittest.TestCase):
|
|||
cls.states_bidder = extract_states_from_xu_file(os.path.join(diagrams_dir, 'ads.bidder.alt.xu'), 'B')
|
||||
cls.states_offerer = extract_states_from_xu_file(os.path.join(diagrams_dir, 'ads.offerer.alt.xu'), 'O')
|
||||
|
||||
cls.states_bidder_sh = extract_states_from_xu_file(os.path.join(diagrams_dir, 'bidder.alt.xu'), 'B')
|
||||
cls.states_offerer_sh = extract_states_from_xu_file(os.path.join(diagrams_dir, 'offerer.alt.xu'), 'O')
|
||||
|
||||
if os.path.isdir(TEST_DIR):
|
||||
if RESET_TEST:
|
||||
logging.info('Removing ' + TEST_DIR)
|
||||
|
|
Loading…
Reference in a new issue