coins: Decode pivx v3 transactions correctly.

2024-05-20_merge
tecnovert 2 years ago
parent 45d6b9ecbf
commit f210024e93
No known key found for this signature in database
GPG Key ID: 8ED6D8750C4E3F93
  1. 6
      basicswap/base.py
  2. 38
      basicswap/basicswap.py
  3. 34
      basicswap/http_server.py
  4. 89
      basicswap/interface/contrib/pivx_test_framework/messages.py
  5. 2
      basicswap/ui/page_offers.py
  6. 2
      basicswap/ui/page_wallet.py
  7. 29
      tests/basicswap/extended/test_pivx.py

@ -11,6 +11,7 @@ import socket
import urllib import urllib
import logging import logging
import threading import threading
import traceback
import subprocess import subprocess
import basicswap.config as cfg import basicswap.config as cfg
@ -176,6 +177,11 @@ class BaseApp:
socket.getaddrinfo = self.default_socket_getaddrinfo socket.getaddrinfo = self.default_socket_getaddrinfo
socket.setdefaulttimeout(self.default_socket_timeout) socket.setdefaulttimeout(self.default_socket_timeout)
def logException(self, message):
self.log.error(message)
if self.debug:
self.log.error(traceback.format_exc())
def torControl(self, query): def torControl(self, query):
try: try:
command = 'AUTHENTICATE "{}"\r\n{}\r\nQUIT\r\n'.format(self.tor_control_password, query).encode('utf-8') command = 'AUTHENTICATE "{}"\r\n{}\r\nQUIT\r\n'.format(self.tor_control_password, query).encode('utf-8')

@ -903,17 +903,13 @@ class BasicSwap(BaseApp):
try: try:
self.activateBid(session, bid) self.activateBid(session, bid)
except Exception as ex: except Exception as ex:
self.log.error('Failed to activate bid! Error: %s', str(ex)) self.logException(f'Failed to activate bid! Error: {ex}')
if self.debug:
self.log.error(traceback.format_exc())
try: try:
bid.setState(BidStates.BID_ERROR, 'Failed to activate') bid.setState(BidStates.BID_ERROR, 'Failed to activate')
offer = session.query(Offer).filter_by(offer_id=bid.offer_id).first() offer = session.query(Offer).filter_by(offer_id=bid.offer_id).first()
self.deactivateBid(session, offer, bid) self.deactivateBid(session, offer, bid)
except Exception as ex: except Exception as ex:
self.log.error('Further error deactivating: %s', str(ex)) self.logException(f'Further error deactivating: {ex}')
if self.debug:
self.log.error(traceback.format_exc())
self.buildNotificationsCache(session) self.buildNotificationsCache(session)
finally: finally:
session.close() session.close()
@ -2464,8 +2460,6 @@ class BasicSwap(BaseApp):
coin_to = Coins(offer.coin_to) coin_to = Coins(offer.coin_to)
ci_to = self.ci(coin_to) ci_to = self.ci(coin_to)
bid_date = dt.datetime.fromtimestamp(bid.created_at).date()
secret_hash = atomic_swap_1.extractScriptSecretHash(bid.initiate_tx.script) secret_hash = atomic_swap_1.extractScriptSecretHash(bid.initiate_tx.script)
pkhash_seller = bid.pkhash_seller pkhash_seller = bid.pkhash_seller
pkhash_buyer_refund = bid.pkhash_buyer pkhash_buyer_refund = bid.pkhash_buyer
@ -3242,7 +3236,7 @@ class BasicSwap(BaseApp):
if bid.initiate_tx.conf is not None: if bid.initiate_tx.conf is not None:
self.log.debug('initiate_txnid %s confirms %d', initiate_txnid_hex, bid.initiate_tx.conf) self.log.debug('initiate_txnid %s confirms %d', initiate_txnid_hex, bid.initiate_tx.conf)
if bid.initiate_tx.vout is None: if bid.initiate_tx.vout is None and tx_height > 0:
bid.initiate_tx.vout = index bid.initiate_tx.vout = index
# Start checking for spends of initiate_txn before fully confirmed # 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.setLastHeightChecked(coin_from, tx_height)
@ -3480,9 +3474,7 @@ class BasicSwap(BaseApp):
self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer) self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer)
session.commit() session.commit()
except Exception as ex: except Exception as ex:
self.log.error('process_XMR_SWAP_A_LOCK_tx_spend %s', str(ex)) self.logException(f'process_XMR_SWAP_A_LOCK_tx_spend {ex}')
if self.debug:
self.log.error(traceback.format_exc())
finally: finally:
session.close() session.close()
session.remove() session.remove()
@ -3537,9 +3529,7 @@ class BasicSwap(BaseApp):
self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer) self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer)
session.commit() session.commit()
except Exception as ex: except Exception as ex:
self.log.error('process_XMR_SWAP_A_LOCK_REFUND_tx_spend %s', str(ex)) self.logException(f'process_XMR_SWAP_A_LOCK_REFUND_tx_spend {ex}')
if self.debug:
self.log.error(traceback.format_exc())
finally: finally:
session.close() session.close()
session.remove() session.remove()
@ -3598,7 +3588,7 @@ class BasicSwap(BaseApp):
last_height_checked = bci['pruneheight'] last_height_checked = bci['pruneheight']
continue continue
else: else:
self.log.error(f'getblock error {e}') self.logException(f'getblock error {e}')
break break
for tx in block['tx']: for tx in block['tx']:
@ -3688,9 +3678,7 @@ class BasicSwap(BaseApp):
else: else:
self.log.warning('Unknown event type: %d', row.event_type) self.log.warning('Unknown event type: %d', row.event_type)
except Exception as ex: except Exception as ex:
if self.debug: self.logException(f'checkQueuedActions failed: {ex}')
self.log.error(traceback.format_exc())
self.log.error('checkQueuedActions failed: {}'.format(str(ex)))
if self.debug: if self.debug:
session.execute('UPDATE actions SET active_ind = 2 WHERE trigger_at <= {}'.format(now)) session.execute('UPDATE actions SET active_ind = 2 WHERE trigger_at <= {}'.format(now))
@ -3988,9 +3976,7 @@ class BasicSwap(BaseApp):
use_session) use_session)
return False return False
except Exception as e: except Exception as e:
self.log.error('shouldAutoAcceptBid: %s', str(e)) self.logException(f'shouldAutoAcceptBid {e}')
if self.debug:
self.log.error(traceback.format_exc())
return False return False
finally: finally:
if session is None: if session is None:
@ -5101,9 +5087,7 @@ class BasicSwap(BaseApp):
except zmq.Again as ex: except zmq.Again as ex:
pass pass
except Exception as ex: except Exception as ex:
self.log.error('smsg zmq %s', str(ex)) self.logException(f'smsg zmq {ex}')
if self.debug:
self.log.error(traceback.format_exc())
self.mxDB.acquire() self.mxDB.acquire()
try: try:
@ -5150,9 +5134,7 @@ class BasicSwap(BaseApp):
self._last_checked_xmr_swaps = now self._last_checked_xmr_swaps = now
except Exception as ex: except Exception as ex:
self.log.error('update %s', str(ex)) self.logException(f'update {ex}')
if self.debug:
self.log.error(traceback.format_exc())
finally: finally:
self.mxDB.release() self.mxDB.release()

@ -100,8 +100,8 @@ class HttpHandler(BaseHTTPRequestHandler):
form_id = form_data[b'formid'][0].decode('utf-8') form_id = form_data[b'formid'][0].decode('utf-8')
if self.server.last_form_id.get(name, None) == form_id: if self.server.last_form_id.get(name, None) == form_id:
messages.append('Prevented double submit for form {}.'.format(form_id)) messages.append('Prevented double submit for form {}.'.format(form_id))
else: return None
self.server.last_form_id[name] = form_id self.server.last_form_id[name] = form_id
return form_data return form_data
def render_template(self, template, args_dict, status_code=200): def render_template(self, template, args_dict, status_code=200):
@ -184,7 +184,8 @@ class HttpHandler(BaseHTTPRequestHandler):
explorer = -1 explorer = -1
action = -1 action = -1
messages = [] messages = []
form_data = self.checkForm(post_string, 'explorers', messages) err_messages = []
form_data = self.checkForm(post_string, 'explorers', err_messages)
if form_data: if form_data:
explorer = form_data[b'explorer'][0].decode('utf-8') explorer = form_data[b'explorer'][0].decode('utf-8')
@ -211,6 +212,8 @@ class HttpHandler(BaseHTTPRequestHandler):
template = env.get_template('explorers.html') template = env.get_template('explorers.html')
return self.render_template(template, { return self.render_template(template, {
'messages': messages,
'err_messages': err_messages,
'explorers': listAvailableExplorers(swap_client), 'explorers': listAvailableExplorers(swap_client),
'explorer': explorer, 'explorer': explorer,
'actions': listExplorerActions(swap_client), 'actions': listExplorerActions(swap_client),
@ -227,7 +230,8 @@ class HttpHandler(BaseHTTPRequestHandler):
coin_type = -1 coin_type = -1
coin_id = -1 coin_id = -1
messages = [] messages = []
form_data = self.checkForm(post_string, 'rpc', messages) err_messages = []
form_data = self.checkForm(post_string, 'rpc', err_messages)
if form_data: if form_data:
try: try:
coin_id = int(form_data[b'coin_type'][0]) coin_id = int(form_data[b'coin_type'][0])
@ -273,6 +277,8 @@ class HttpHandler(BaseHTTPRequestHandler):
coins.append((-4, 'Monero Wallet')) coins.append((-4, 'Monero Wallet'))
return self.render_template(template, { return self.render_template(template, {
'messages': messages,
'err_messages': err_messages,
'coins': coins, 'coins': coins,
'coin_type': coin_id, 'coin_type': coin_id,
'result': result, 'result': result,
@ -286,18 +292,20 @@ class HttpHandler(BaseHTTPRequestHandler):
result = None result = None
messages = [] messages = []
form_data = self.checkForm(post_string, 'wallets', messages) err_messages = []
form_data = self.checkForm(post_string, 'wallets', err_messages)
if form_data: if form_data:
if have_data_entry(form_data, 'reinit_xmr'): if have_data_entry(form_data, 'reinit_xmr'):
try: try:
swap_client.initialiseWallet(Coins.XMR) swap_client.initialiseWallet(Coins.XMR)
messages.append('Done.') messages.append('Done.')
except Exception as a: except Exception as a:
messages.append('Failed.') err_messages.append('Failed.')
template = env.get_template('debug.html') template = env.get_template('debug.html')
return self.render_template(template, { return self.render_template(template, {
'messages': messages, 'messages': messages,
'err_messages': err_messages,
'result': result, 'result': result,
'summary': summary, 'summary': summary,
}) })
@ -319,7 +327,8 @@ class HttpHandler(BaseHTTPRequestHandler):
summary = swap_client.getSummary() summary = swap_client.getSummary()
messages = [] messages = []
form_data = self.checkForm(post_string, 'settings', messages) err_messages = []
form_data = self.checkForm(post_string, 'settings', err_messages)
if form_data: if form_data:
for name, c in swap_client.settings['chainclients'].items(): for name, c in swap_client.settings['chainclients'].items():
if have_data_entry(form_data, 'apply_' + name): if have_data_entry(form_data, 'apply_' + name):
@ -389,6 +398,7 @@ class HttpHandler(BaseHTTPRequestHandler):
template = env.get_template('settings.html') template = env.get_template('settings.html')
return self.render_template(template, { return self.render_template(template, {
'messages': messages, 'messages': messages,
'err_messages': err_messages,
'chains': chains_formatted, 'chains': chains_formatted,
'summary': summary, 'summary': summary,
}) })
@ -412,10 +422,11 @@ class HttpHandler(BaseHTTPRequestHandler):
page_data = {} page_data = {}
messages = [] messages = []
err_messages = []
smsgaddresses = [] smsgaddresses = []
listaddresses = True listaddresses = True
form_data = self.checkForm(post_string, 'smsgaddresses', messages) form_data = self.checkForm(post_string, 'smsgaddresses', err_messages)
if form_data: if form_data:
edit_address_id = None edit_address_id = None
for key in form_data: for key in form_data:
@ -473,6 +484,7 @@ class HttpHandler(BaseHTTPRequestHandler):
template = env.get_template('smsgaddresses.html') template = env.get_template('smsgaddresses.html')
return self.render_template(template, { return self.render_template(template, {
'messages': messages, 'messages': messages,
'err_messages': err_messages,
'data': page_data, 'data': page_data,
'smsgaddresses': smsgaddresses, 'smsgaddresses': smsgaddresses,
'network_addr': network_addr, 'network_addr': network_addr,
@ -487,7 +499,8 @@ class HttpHandler(BaseHTTPRequestHandler):
page_data = {'identity_address': identity_address} page_data = {'identity_address': identity_address}
messages = [] messages = []
form_data = self.checkForm(post_string, 'identity', messages) err_messages = []
form_data = self.checkForm(post_string, 'identity', err_messages)
if form_data: if form_data:
if have_data_entry(form_data, 'edit'): if have_data_entry(form_data, 'edit'):
page_data['show_edit_form'] = True page_data['show_edit_form'] = True
@ -498,7 +511,7 @@ class HttpHandler(BaseHTTPRequestHandler):
swap_client.updateIdentity(identity_address, new_label) swap_client.updateIdentity(identity_address, new_label)
messages.append('Updated') messages.append('Updated')
except Exception as e: except Exception as e:
messages.append('Error') err_messages.append(str(e))
try: try:
identity = swap_client.getIdentity(identity_address) identity = swap_client.getIdentity(identity_address)
@ -517,6 +530,7 @@ class HttpHandler(BaseHTTPRequestHandler):
template = env.get_template('identity.html') template = env.get_template('identity.html')
return self.render_template(template, { return self.render_template(template, {
'messages': messages, 'messages': messages,
'err_messages': err_messages,
'data': page_data, 'data': page_data,
'summary': summary, 'summary': summary,
}) })

@ -418,43 +418,114 @@ class CTxOut:
bytes_to_hex_str(self.scriptPubKey)) bytes_to_hex_str(self.scriptPubKey))
class SpendDescription:
def deserialize(self, f):
self.cv = deser_uint256(f)
self.anchor = deser_uint256(f)
self.nullifier = deser_uint256(f)
self.rk = deser_uint256(f)
self.zkproof = f.read(192)
self.spendAuthSig = f.read(64)
def serialize(self):
r = b""
r += ser_uint256(self.cv)
r += ser_uint256(self.anchor)
r += ser_uint256(self.nullifier)
r += ser_uint256(self.rk)
r += self.zkproof
r += self.spendAuthSig
return r
class OutputDescription:
def deserialize(self, f):
self.cv = deser_uint256(f)
self.cmu = deser_uint256(f)
self.ephemeralKey = deser_uint256(f)
self.encCiphertext = f.read(580)
self.outCiphertext = f.read(80)
self.zkproof = f.read(192)
def serialize(self):
r = b""
r += ser_uint256(self.cv)
r += ser_uint256(self.cmu)
r += ser_uint256(self.ephemeralKey)
r += self.encCiphertext
r += self.outCiphertext
r += self.zkproof
return r
class SaplingTxData:
def deserialize(self, f):
self.pre = f.read(1)
self.valueBalance = struct.unpack("<q", f.read(8))[0]
self.vShieldedSpend = deser_vector(f, SpendDescription)
self.vShieldedOutput = deser_vector(f, OutputDescription)
self.bindingSig = f.read(64)
def serialize(self):
r = b""
r += self.pre
r += struct.pack("<q", self.valueBalance)
r += ser_vector(self.vShieldedSpend)
r += ser_vector(self.vShieldedOutput)
r += self.bindingSig
return r
class CTransaction: class CTransaction:
def __init__(self, tx=None): def __init__(self, tx=None):
if tx is None: if tx is None:
self.nVersion = 1 self.nVersion = 1
self.nType = 0
self.vin = [] self.vin = []
self.vout = [] self.vout = []
self.sapData = b"" self.sapData = None
self.extraData = b""
self.nLockTime = 0 self.nLockTime = 0
self.sha256 = None self.sha256 = None
self.hash = None self.hash = None
else: else:
self.nVersion = tx.nVersion self.nVersion = tx.nVersion
self.nType = tx.nType
self.vin = copy.deepcopy(tx.vin) self.vin = copy.deepcopy(tx.vin)
self.vout = copy.deepcopy(tx.vout) self.vout = copy.deepcopy(tx.vout)
self.nLockTime = tx.nLockTime self.nLockTime = tx.nLockTime
self.sapData = tx.sapData self.sapData = tx.sapData
self.extraData = tx.extraData
self.sha256 = tx.sha256 self.sha256 = tx.sha256
self.hash = tx.hash self.hash = tx.hash
def deserialize(self, f): def deserialize(self, f):
self.nVersion = struct.unpack("<i", f.read(4))[0] self.nVersion = struct.unpack("<h", f.read(2))[0]
self.nType = struct.unpack("<h", f.read(2))[0]
self.vin = deser_vector(f, CTxIn) self.vin = deser_vector(f, CTxIn)
self.vout = deser_vector(f, CTxOut) self.vout = deser_vector(f, CTxOut)
self.nLockTime = struct.unpack("<I", f.read(4))[0] self.nLockTime = struct.unpack("<I", f.read(4))[0]
if self.nVersion >= 2: if self.nVersion >= 3:
self.sapData = deser_string(f) self.sapData = SaplingTxData()
self.sapData.deserialize(f)
if self.nType != 0:
self.extraData = deser_string(f)
self.sha256 = None self.sha256 = None
self.hash = None self.hash = None
def serialize_without_witness(self): def serialize_without_witness(self):
r = b"" r = b""
r += struct.pack("<i", self.nVersion) r += struct.pack("<h", self.nVersion)
r += struct.pack("<h", self.nType)
r += ser_vector(self.vin) r += ser_vector(self.vin)
r += ser_vector(self.vout) r += ser_vector(self.vout)
r += struct.pack("<I", self.nLockTime) r += struct.pack("<I", self.nLockTime)
if self.nVersion >= 2: if self.nVersion >= 3:
r += ser_string(self.sapData) r += self.sapData.serialize()
if self.nType != 0:
r += ser_string(self.extraData)
return r return r
# Regular serialization is with witness -- must explicitly # Regular serialization is with witness -- must explicitly
@ -504,8 +575,8 @@ class CTransaction:
x.prevout.hash == outpoint.hash and x.prevout.n == outpoint.n]) > 0 x.prevout.hash == outpoint.hash and x.prevout.n == outpoint.n]) > 0
def __repr__(self): def __repr__(self):
return "CTransaction(nVersion=%i vin=%s vout=%s nLockTime=%i)" \ return "CTransaction(nVersion=%i nType=%i vin=%s vout=%s nLockTime=%i)" \
% (self.nVersion, repr(self.vin), repr(self.vout), self.nLockTime) % (self.nVersion, self.nType, repr(self.vin), repr(self.vout), self.nLockTime)
class CBlockHeader: class CBlockHeader:

@ -417,9 +417,9 @@ def page_offer(self, url_split, post_string):
ci_from = swap_client.ci(Coins(offer.coin_from)) ci_from = swap_client.ci(Coins(offer.coin_from))
ci_to = swap_client.ci(Coins(offer.coin_to)) ci_to = swap_client.ci(Coins(offer.coin_to))
debugind = -1
# Set defaults # Set defaults
debugind = -1
bid_amount = ci_from.format_amount(offer.amount_from) bid_amount = ci_from.format_amount(offer.amount_from)
bid_rate = ci_to.format_amount(offer.rate) bid_rate = ci_to.format_amount(offer.rate)

@ -172,8 +172,8 @@ def page_wallet(self, url_split, post_string):
page_data = {} page_data = {}
messages = [] messages = []
err_messages = [] err_messages = []
form_data = self.checkForm(post_string, 'wallet', err_messages)
show_utxo_groups = False show_utxo_groups = False
form_data = self.checkForm(post_string, 'wallet', err_messages)
if form_data: if form_data:
cid = str(int(coin_id)) cid = str(int(coin_id))

@ -349,9 +349,6 @@ class Test(unittest.TestCase):
ro = btcRpc('getblockchaininfo') ro = btcRpc('getblockchaininfo')
checkForks(ro) checkForks(ro)
ro = pivxRpc('getwalletinfo')
print('pivxRpc', ro)
signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGINT, signal_handler)
cls.update_thread = threading.Thread(target=run_loop, args=(cls,)) cls.update_thread = threading.Thread(target=run_loop, args=(cls,))
cls.update_thread.start() cls.update_thread.start()
@ -553,6 +550,32 @@ class Test(unittest.TestCase):
json_rv = json.loads(post_json_req('http://127.0.0.1:{}/json/wallets/pivx/withdraw'.format(TEST_HTTP_PORT + 0), post_json)) json_rv = json.loads(post_json_req('http://127.0.0.1:{}/json/wallets/pivx/withdraw'.format(TEST_HTTP_PORT + 0), post_json))
assert (len(json_rv['txid']) == 64) assert (len(json_rv['txid']) == 64)
def test_09_v3_tx(self):
logging.info('---------- Test PIVX v3 txns')
generate_addr = pivxRpc('getnewaddress \"generate test\"')
pivx_addr = pivxRpc('getnewaddress \"Sapling test\"')
pivx_sapling_addr = pivxRpc('getnewshieldaddress \"shield addr\"')
pivxRpc(f'sendtoaddress \"{pivx_addr}\" 6.0')
pivxRpc(f'generatetoaddress 1 \"{generate_addr}\"')
txid = pivxRpc('shieldsendmany "{}" "[{{\\"address\\": \\"{}\\", \\"amount\\": 1}}]"'.format(pivx_addr, pivx_sapling_addr))
rtx = pivxRpc(f'getrawtransaction \"{txid}\" true')
assert(rtx['version'] == 3)
block_hash = pivxRpc(f'generatetoaddress 1 \"{generate_addr}\"')[0]
ci = self.swap_clients[0].ci(Coins.PIVX)
block = ci.getBlockWithTxns(block_hash)
found = False
for tx in block['tx']:
if txid == tx['txid']:
found = True
break
assert found
def pass_99_delay(self): def pass_99_delay(self):
global stop_test global stop_test
logging.info('Delay') logging.info('Delay')

Loading…
Cancel
Save