mirror of
https://github.com/basicswap/basicswap.git
synced 2025-11-06 02:38:11 +01:00
particl: Can swap anon outputs
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
name = "basicswap"
|
||||
|
||||
__version__ = "0.0.23"
|
||||
__version__ = "0.0.24"
|
||||
|
||||
@@ -18,6 +18,9 @@ from .rpc import (
|
||||
from .util import (
|
||||
pubkeyToAddress,
|
||||
)
|
||||
from .basicswap_util import (
|
||||
TemporaryError,
|
||||
)
|
||||
from .chainparams import (
|
||||
Coins,
|
||||
chainparams,
|
||||
@@ -136,5 +139,7 @@ class BaseApp:
|
||||
return out[0].decode('utf-8').strip()
|
||||
|
||||
def is_transient_error(self, ex):
|
||||
if isinstance(ex, TemporaryError):
|
||||
return True
|
||||
str_error = str(ex).lower()
|
||||
return 'read timed out' in str_error or 'no connection to daemon' in str_error
|
||||
|
||||
@@ -1756,7 +1756,7 @@ class BasicSwap(BaseApp):
|
||||
script = atomic_swap_1.buildContractScript(sequence, secret_hash, bid.pkhash_buyer, pkhash_refund)
|
||||
else:
|
||||
if offer.lock_type == ABS_LOCK_BLOCKS:
|
||||
lock_value = self.callcoinrpc(coin_from, 'getblockchaininfo')['blocks'] + offer.lock_value
|
||||
lock_value = self.callcoinrpc(coin_from, 'getblockcount') + offer.lock_value
|
||||
else:
|
||||
lock_value = int(time.time()) + offer.lock_value
|
||||
self.log.debug('Initiate %s lock_value %d %d', coin_from, offer.lock_value, lock_value)
|
||||
@@ -2225,7 +2225,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, 'getblockchaininfo')['blocks']
|
||||
chain_height = self.callcoinrpc(coin_to, 'getblockcount')
|
||||
txjs = self.callcoinrpc(coin_to, 'decoderawtransaction', [txn_signed])
|
||||
txid = txjs['txid']
|
||||
|
||||
@@ -2554,7 +2554,7 @@ class BasicSwap(BaseApp):
|
||||
return self.lookupUnspentByAddress(coin_type, address, sum_output=True)
|
||||
|
||||
def lookupChainHeight(self, coin_type):
|
||||
return self.callcoinrpc(coin_type, 'getblockchaininfo')['blocks']
|
||||
return self.callcoinrpc(coin_type, 'getblockcount')
|
||||
|
||||
def lookupUnspentByAddress(self, coin_type, address, sum_output=False, assert_amount=None, assert_txid=None):
|
||||
|
||||
@@ -2589,7 +2589,7 @@ class BasicSwap(BaseApp):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
num_blocks = self.callcoinrpc(coin_type, 'getblockchaininfo')['blocks']
|
||||
num_blocks = self.callcoinrpc(coin_type, 'getblockcount')
|
||||
|
||||
sum_unspent = 0
|
||||
self.log.debug('[rm] scantxoutset start') # scantxoutset is slow
|
||||
@@ -2782,7 +2782,7 @@ class BasicSwap(BaseApp):
|
||||
|
||||
bid_changed = False
|
||||
# Have to use findTxB instead of relying on the first seen height to detect chain reorgs
|
||||
found_tx = ci_to.findTxB(xmr_swap.vkbv, xmr_swap.pkbs, bid.amount_to, ci_to.blocks_confirmed, xmr_swap.b_restore_height)
|
||||
found_tx = ci_to.findTxB(xmr_swap.vkbv, xmr_swap.pkbs, bid.amount_to, ci_to.blocks_confirmed, xmr_swap.b_restore_height, bid.was_sent)
|
||||
|
||||
if isinstance(found_tx, int) and found_tx == -1:
|
||||
if self.countBidEvents(bid, EventLogTypes.LOCK_TX_B_INVALID, session) < 1:
|
||||
@@ -3249,7 +3249,7 @@ class BasicSwap(BaseApp):
|
||||
spend_txn = self.callcoinrpc(Coins.PART, 'getrawtransaction', [spend_txid, True])
|
||||
self.processSpentOutput(coin_type, o, spend_txid, spend_n, spend_txn)
|
||||
else:
|
||||
chain_blocks = self.callcoinrpc(coin_type, 'getblockchaininfo')['blocks']
|
||||
chain_blocks = self.callcoinrpc(coin_type, 'getblockcount')
|
||||
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:
|
||||
@@ -4288,8 +4288,11 @@ class BasicSwap(BaseApp):
|
||||
vkbs = ci_to.sumKeys(kbsl, kbsf)
|
||||
|
||||
try:
|
||||
address_to = self.getCachedMainWalletAddress(ci_to)
|
||||
txid = ci_to.spendBLockTx(address_to, xmr_swap.vkbv, vkbs, bid.amount_to, xmr_offer.b_fee_rate, xmr_swap.b_restore_height)
|
||||
if coin_to == Coins.XMR:
|
||||
address_to = self.getCachedMainWalletAddress(ci_to)
|
||||
else:
|
||||
address_to = self.getCachedStealthAddressForCoin(coin_to)
|
||||
txid = ci_to.spendBLockTx(xmr_swap.b_lock_tx_id, address_to, xmr_swap.vkbv, vkbs, bid.amount_to, xmr_offer.b_fee_rate, xmr_swap.b_restore_height)
|
||||
self.log.debug('Submitted lock B spend txn %s to %s chain for bid %s', txid.hex(), ci_to.coin_name(), bid_id.hex())
|
||||
self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_SPEND_TX_PUBLISHED, '', session)
|
||||
except Exception as ex:
|
||||
@@ -4345,7 +4348,7 @@ class BasicSwap(BaseApp):
|
||||
|
||||
try:
|
||||
address_to = self.getCachedMainWalletAddress(ci_to)
|
||||
txid = ci_to.spendBLockTx(address_to, xmr_swap.vkbv, vkbs, bid.amount_to, xmr_offer.b_fee_rate, xmr_swap.b_restore_height)
|
||||
txid = ci_to.spendBLockTx(xmr_swap.b_lock_tx_id, address_to, xmr_swap.vkbv, vkbs, bid.amount_to, xmr_offer.b_fee_rate, xmr_swap.b_restore_height)
|
||||
self.log.debug('Submitted lock B refund txn %s to %s chain for bid %s', txid.hex(), ci_to.coin_name(), bid_id.hex())
|
||||
self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_REFUND_TX_PUBLISHED, '', session)
|
||||
except Exception as ex:
|
||||
|
||||
@@ -371,3 +371,7 @@ def isActiveBidState(state):
|
||||
if state == BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class TemporaryError(ValueError):
|
||||
pass
|
||||
|
||||
@@ -175,7 +175,7 @@ class BTCInterface(CoinInterface):
|
||||
return self.rpc_callback('getblockchaininfo')
|
||||
|
||||
def getChainHeight(self):
|
||||
return self.rpc_callback('getblockchaininfo')['blocks']
|
||||
return self.rpc_callback('getblockcount')
|
||||
|
||||
def getMempoolTx(self, txid):
|
||||
return self.rpc_callback('getrawtransaction', [txid.hex()])
|
||||
@@ -866,7 +866,7 @@ class BTCInterface(CoinInterface):
|
||||
weight = len_nwit * (wsf - 1) + len_full
|
||||
return (weight + wsf - 1) // wsf
|
||||
|
||||
def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height):
|
||||
def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height, bid_sender):
|
||||
raw_dest = self.getPkDest(Kbs)
|
||||
|
||||
rv = self.scanTxOutset(raw_dest)
|
||||
@@ -898,7 +898,7 @@ class BTCInterface(CoinInterface):
|
||||
return True
|
||||
return False
|
||||
|
||||
def spendBLockTx(self, address_to, kbv, kbs, cb_swap_value, b_fee, restore_height):
|
||||
def spendBLockTx(self, chain_b_lock_txid, address_to, kbv, kbs, cb_swap_value, b_fee, restore_height):
|
||||
print('TODO: spendBLockTx')
|
||||
|
||||
def getOutput(self, txid, dest_script, expect_value):
|
||||
|
||||
@@ -15,7 +15,13 @@ from .contrib.test_framework.script import (
|
||||
OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG
|
||||
)
|
||||
|
||||
from .util import encodeStealthAddress
|
||||
from .util import (
|
||||
encodeStealthAddress,
|
||||
toWIF,
|
||||
ensure,
|
||||
make_int)
|
||||
from .basicswap_util import (
|
||||
TemporaryError)
|
||||
from .chainparams import Coins, chainparams
|
||||
from .interface_btc import BTCInterface
|
||||
|
||||
@@ -117,8 +123,83 @@ class PARTInterfaceAnon(PARTInterface):
|
||||
txid = self.rpc_callback('sendtypeto', params)
|
||||
return bytes.fromhex(txid)
|
||||
|
||||
def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height):
|
||||
raise ValueError('TODO - new core release')
|
||||
def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height, bid_sender):
|
||||
Kbv = self.getPubkey(kbv)
|
||||
sx_addr = self.formatStealthAddress(Kbv, Kbs)
|
||||
self._log.debug('sx_addr: {}'.format(sx_addr))
|
||||
|
||||
def spendBLockTx(self, address_to, kbv, kbs, cb_swap_value, b_fee, restore_height):
|
||||
raise ValueError('TODO - new core release')
|
||||
# Tx recipient must import the stealth address as watch only
|
||||
if bid_sender:
|
||||
cb_swap_value *= -1
|
||||
else:
|
||||
addr_info = self.rpc_callback('getaddressinfo', [sx_addr])
|
||||
if not addr_info['iswatchonly']:
|
||||
wif_prefix = chainparams[self.coin_type()][self._network]['key_prefix']
|
||||
wif_scan_key = toWIF(wif_prefix, kbv)
|
||||
self.rpc_callback('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(restore_height))
|
||||
self.rpc_callback('rescanblockchain', [restore_height])
|
||||
|
||||
params = [{'include_watchonly': True, 'search': sx_addr}]
|
||||
txns = self.rpc_callback('filtertransactions', params)
|
||||
|
||||
if len(txns) == 1:
|
||||
tx = txns[0]
|
||||
assert(tx['outputs'][0]['stealth_address'] == sx_addr) # Should not be possible
|
||||
ensure(tx['outputs'][0]['type'] == 'anon', 'Output is not anon')
|
||||
|
||||
if make_int(tx['outputs'][0]['amount']) == cb_swap_value:
|
||||
height = 0
|
||||
if tx['confirmations'] > 0:
|
||||
chain_height = self.rpc_callback('getblockcount')
|
||||
height = chain_height - (tx['confirmations'] - 1)
|
||||
return {'txid': tx['txid'], 'amount': cb_swap_value, 'height': height}
|
||||
else:
|
||||
self._log.warning('Incorrect amount detected for coin b lock txn: {}'.format(tx['txid']))
|
||||
return -1
|
||||
return None
|
||||
|
||||
def spendBLockTx(self, chain_b_lock_txid, address_to, kbv, kbs, cb_swap_value, b_fee, restore_height):
|
||||
Kbv = self.getPubkey(kbv)
|
||||
Kbs = self.getPubkey(kbs)
|
||||
sx_addr = self.formatStealthAddress(Kbv, Kbs)
|
||||
addr_info = self.rpc_callback('getaddressinfo', [sx_addr])
|
||||
if not addr_info['ismine']:
|
||||
wif_prefix = chainparams[self.coin_type()][self._network]['key_prefix']
|
||||
wif_scan_key = toWIF(wif_prefix, kbv)
|
||||
wif_spend_key = toWIF(wif_prefix, kbs)
|
||||
self.rpc_callback('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(restore_height))
|
||||
self.rpc_callback('rescanblockchain', [restore_height])
|
||||
|
||||
autxos = self.rpc_callback('listunspentanon')
|
||||
if len(autxos) < 1:
|
||||
raise TemporaryError('No spendable outputs')
|
||||
elif len(autxos) > 1:
|
||||
raise ValueError('Too many spendable outputs')
|
||||
|
||||
utxo = autxos[0]
|
||||
|
||||
inputs = [{'tx': utxo['txid'], 'n': utxo['vout']}, ]
|
||||
params = ['anon', 'anon',
|
||||
[{'address': address_to, 'amount': self.format_amount(cb_swap_value), 'subfee': True}, ],
|
||||
'', '', self._anon_tx_ring_size, 1, False,
|
||||
{'conf_target': self._conf_target, 'inputs': inputs, 'show_fee': True}]
|
||||
rv = self.rpc_callback('sendtypeto', params)
|
||||
return bytes.fromhex(rv['txid'])
|
||||
|
||||
def findTxnByHash(self, txid_hex):
|
||||
# txindex is enabled for Particl
|
||||
|
||||
try:
|
||||
rv = self.rpc_callback('getrawtransaction', [txid_hex, True])
|
||||
except Exception as ex:
|
||||
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
|
||||
return None
|
||||
|
||||
if 'confirmations' in rv and rv['confirmations'] >= self.blocks_confirmed:
|
||||
return {'txid': txid_hex, 'amount': 0, 'height': rv['height']}
|
||||
|
||||
return None
|
||||
|
||||
@@ -27,6 +27,8 @@ from .util import (
|
||||
dumpj,
|
||||
make_int,
|
||||
format_amount)
|
||||
from .basicswap_util import (
|
||||
TemporaryError)
|
||||
from .rpc_xmr import (
|
||||
make_xmr_rpc_func,
|
||||
make_xmr_rpc2_func,
|
||||
@@ -232,7 +234,7 @@ class XMRInterface(CoinInterface):
|
||||
|
||||
return tx_hash
|
||||
|
||||
def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height):
|
||||
def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height, bid_sender):
|
||||
with self._mx_wallet:
|
||||
Kbv = self.getPubkey(kbv)
|
||||
address_b58 = xmr_util.encode_address(Kbv, Kbs)
|
||||
@@ -363,7 +365,7 @@ class XMRInterface(CoinInterface):
|
||||
|
||||
return None
|
||||
|
||||
def spendBLockTx(self, address_to, kbv, kbs, cb_swap_value, b_fee_rate, restore_height):
|
||||
def spendBLockTx(self, chain_b_lock_txid, address_to, kbv, kbs, cb_swap_value, b_fee_rate, restore_height):
|
||||
with self._mx_wallet:
|
||||
Kbv = self.getPubkey(kbv)
|
||||
Kbs = self.getPubkey(kbs)
|
||||
@@ -401,10 +403,10 @@ class XMRInterface(CoinInterface):
|
||||
time.sleep(1 + i)
|
||||
if rv['balance'] < cb_swap_value:
|
||||
self._log.error('wallet {} balance {}, expected {}'.format(wallet_filename, rv['balance'], cb_swap_value))
|
||||
raise ValueError('Invalid balance')
|
||||
raise TemporaryError('Invalid balance')
|
||||
if rv['unlocked_balance'] < cb_swap_value:
|
||||
self._log.error('wallet {} balance {}, expected {}, blocks_to_unlock {}'.format(wallet_filename, rv['unlocked_balance'], cb_swap_value, rv['blocks_to_unlock']))
|
||||
raise ValueError('Invalid unlocked_balance')
|
||||
raise TemporaryError('Invalid unlocked_balance')
|
||||
|
||||
params = {'address': address_to}
|
||||
if self._fee_priority > 0:
|
||||
|
||||
@@ -246,6 +246,10 @@ def make_int(v, scale=8, r=0): # r = 0, no rounding, fail, r > 0 round up, r <
|
||||
elif type(v) == int:
|
||||
return v * 10 ** scale
|
||||
|
||||
sign = 1
|
||||
if v[0] == '-':
|
||||
v = v[1:]
|
||||
sign = -1
|
||||
ep = 10 ** scale
|
||||
have_dp = False
|
||||
rv = 0
|
||||
@@ -255,7 +259,6 @@ def make_int(v, scale=8, r=0): # r = 0, no rounding, fail, r > 0 round up, r <
|
||||
have_dp = True
|
||||
continue
|
||||
if not c.isdigit():
|
||||
|
||||
raise ValueError('Invalid char: ' + c)
|
||||
if have_dp:
|
||||
ep //= 10
|
||||
@@ -267,13 +270,12 @@ def make_int(v, scale=8, r=0): # r = 0, no rounding, fail, r > 0 round up, r <
|
||||
if int(c) > 4:
|
||||
rv += 1
|
||||
break
|
||||
|
||||
rv += ep * int(c)
|
||||
else:
|
||||
rv = rv * 10 + int(c)
|
||||
if not have_dp:
|
||||
rv *= ep
|
||||
return rv
|
||||
return rv * sign
|
||||
|
||||
|
||||
def validate_amount(amount, scale=8) -> bool:
|
||||
|
||||
Reference in New Issue
Block a user