From f210024e9354665e47f16f30b74ac99054b0c05a Mon Sep 17 00:00:00 2001 From: tecnovert Date: Wed, 26 Oct 2022 17:47:30 +0200 Subject: [PATCH] coins: Decode pivx v3 transactions correctly. --- basicswap/base.py | 6 ++ basicswap/basicswap.py | 38 +++----- basicswap/http_server.py | 34 ++++--- .../contrib/pivx_test_framework/messages.py | 89 +++++++++++++++++-- basicswap/ui/page_offers.py | 2 +- basicswap/ui/page_wallet.py | 2 +- tests/basicswap/extended/test_pivx.py | 29 +++++- 7 files changed, 148 insertions(+), 52 deletions(-) diff --git a/basicswap/base.py b/basicswap/base.py index 681c450..eee3dee 100644 --- a/basicswap/base.py +++ b/basicswap/base.py @@ -11,6 +11,7 @@ import socket import urllib import logging import threading +import traceback import subprocess import basicswap.config as cfg @@ -176,6 +177,11 @@ class BaseApp: socket.getaddrinfo = self.default_socket_getaddrinfo 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): try: command = 'AUTHENTICATE "{}"\r\n{}\r\nQUIT\r\n'.format(self.tor_control_password, query).encode('utf-8') diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index f8622c5..99ce727 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -903,17 +903,13 @@ class BasicSwap(BaseApp): try: self.activateBid(session, bid) except Exception as ex: - self.log.error('Failed to activate bid! Error: %s', str(ex)) - if self.debug: - self.log.error(traceback.format_exc()) + self.logException(f'Failed to activate bid! Error: {ex}') try: bid.setState(BidStates.BID_ERROR, 'Failed to activate') offer = session.query(Offer).filter_by(offer_id=bid.offer_id).first() self.deactivateBid(session, offer, bid) except Exception as ex: - self.log.error('Further error deactivating: %s', str(ex)) - if self.debug: - self.log.error(traceback.format_exc()) + self.logException(f'Further error deactivating: {ex}') self.buildNotificationsCache(session) finally: session.close() @@ -2464,8 +2460,6 @@ class BasicSwap(BaseApp): coin_to = Coins(offer.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) pkhash_seller = bid.pkhash_seller pkhash_buyer_refund = bid.pkhash_buyer @@ -3242,7 +3236,7 @@ class BasicSwap(BaseApp): 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: + if bid.initiate_tx.vout is None and tx_height > 0: bid.initiate_tx.vout = index # Start checking for spends of initiate_txn before fully confirmed 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) session.commit() except Exception as ex: - self.log.error('process_XMR_SWAP_A_LOCK_tx_spend %s', str(ex)) - if self.debug: - self.log.error(traceback.format_exc()) + self.logException(f'process_XMR_SWAP_A_LOCK_tx_spend {ex}') finally: session.close() session.remove() @@ -3537,9 +3529,7 @@ class BasicSwap(BaseApp): self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer) session.commit() except Exception as ex: - self.log.error('process_XMR_SWAP_A_LOCK_REFUND_tx_spend %s', str(ex)) - if self.debug: - self.log.error(traceback.format_exc()) + self.logException(f'process_XMR_SWAP_A_LOCK_REFUND_tx_spend {ex}') finally: session.close() session.remove() @@ -3598,7 +3588,7 @@ class BasicSwap(BaseApp): last_height_checked = bci['pruneheight'] continue else: - self.log.error(f'getblock error {e}') + self.logException(f'getblock error {e}') break for tx in block['tx']: @@ -3688,9 +3678,7 @@ class BasicSwap(BaseApp): else: self.log.warning('Unknown event type: %d', row.event_type) except Exception as ex: - if self.debug: - self.log.error(traceback.format_exc()) - self.log.error('checkQueuedActions failed: {}'.format(str(ex))) + self.logException(f'checkQueuedActions failed: {ex}') if self.debug: session.execute('UPDATE actions SET active_ind = 2 WHERE trigger_at <= {}'.format(now)) @@ -3988,9 +3976,7 @@ class BasicSwap(BaseApp): use_session) return False except Exception as e: - self.log.error('shouldAutoAcceptBid: %s', str(e)) - if self.debug: - self.log.error(traceback.format_exc()) + self.logException(f'shouldAutoAcceptBid {e}') return False finally: if session is None: @@ -5101,9 +5087,7 @@ class BasicSwap(BaseApp): except zmq.Again as ex: pass except Exception as ex: - self.log.error('smsg zmq %s', str(ex)) - if self.debug: - self.log.error(traceback.format_exc()) + self.logException(f'smsg zmq {ex}') self.mxDB.acquire() try: @@ -5150,9 +5134,7 @@ class BasicSwap(BaseApp): self._last_checked_xmr_swaps = now except Exception as ex: - self.log.error('update %s', str(ex)) - if self.debug: - self.log.error(traceback.format_exc()) + self.logException(f'update {ex}') finally: self.mxDB.release() diff --git a/basicswap/http_server.py b/basicswap/http_server.py index ee59edb..db1268e 100644 --- a/basicswap/http_server.py +++ b/basicswap/http_server.py @@ -100,8 +100,8 @@ class HttpHandler(BaseHTTPRequestHandler): form_id = form_data[b'formid'][0].decode('utf-8') if self.server.last_form_id.get(name, None) == form_id: messages.append('Prevented double submit for form {}.'.format(form_id)) - else: - self.server.last_form_id[name] = form_id + return None + self.server.last_form_id[name] = form_id return form_data def render_template(self, template, args_dict, status_code=200): @@ -184,7 +184,8 @@ class HttpHandler(BaseHTTPRequestHandler): explorer = -1 action = -1 messages = [] - form_data = self.checkForm(post_string, 'explorers', messages) + err_messages = [] + form_data = self.checkForm(post_string, 'explorers', err_messages) if form_data: explorer = form_data[b'explorer'][0].decode('utf-8') @@ -211,6 +212,8 @@ class HttpHandler(BaseHTTPRequestHandler): template = env.get_template('explorers.html') return self.render_template(template, { + 'messages': messages, + 'err_messages': err_messages, 'explorers': listAvailableExplorers(swap_client), 'explorer': explorer, 'actions': listExplorerActions(swap_client), @@ -227,7 +230,8 @@ class HttpHandler(BaseHTTPRequestHandler): coin_type = -1 coin_id = -1 messages = [] - form_data = self.checkForm(post_string, 'rpc', messages) + err_messages = [] + form_data = self.checkForm(post_string, 'rpc', err_messages) if form_data: try: coin_id = int(form_data[b'coin_type'][0]) @@ -273,6 +277,8 @@ class HttpHandler(BaseHTTPRequestHandler): coins.append((-4, 'Monero Wallet')) return self.render_template(template, { + 'messages': messages, + 'err_messages': err_messages, 'coins': coins, 'coin_type': coin_id, 'result': result, @@ -286,18 +292,20 @@ class HttpHandler(BaseHTTPRequestHandler): result = None messages = [] - form_data = self.checkForm(post_string, 'wallets', messages) + err_messages = [] + form_data = self.checkForm(post_string, 'wallets', err_messages) if form_data: if have_data_entry(form_data, 'reinit_xmr'): try: swap_client.initialiseWallet(Coins.XMR) messages.append('Done.') except Exception as a: - messages.append('Failed.') + err_messages.append('Failed.') template = env.get_template('debug.html') return self.render_template(template, { 'messages': messages, + 'err_messages': err_messages, 'result': result, 'summary': summary, }) @@ -319,7 +327,8 @@ class HttpHandler(BaseHTTPRequestHandler): summary = swap_client.getSummary() messages = [] - form_data = self.checkForm(post_string, 'settings', messages) + err_messages = [] + form_data = self.checkForm(post_string, 'settings', err_messages) if form_data: for name, c in swap_client.settings['chainclients'].items(): if have_data_entry(form_data, 'apply_' + name): @@ -389,6 +398,7 @@ class HttpHandler(BaseHTTPRequestHandler): template = env.get_template('settings.html') return self.render_template(template, { 'messages': messages, + 'err_messages': err_messages, 'chains': chains_formatted, 'summary': summary, }) @@ -412,10 +422,11 @@ class HttpHandler(BaseHTTPRequestHandler): page_data = {} messages = [] + err_messages = [] smsgaddresses = [] listaddresses = True - form_data = self.checkForm(post_string, 'smsgaddresses', messages) + form_data = self.checkForm(post_string, 'smsgaddresses', err_messages) if form_data: edit_address_id = None for key in form_data: @@ -473,6 +484,7 @@ class HttpHandler(BaseHTTPRequestHandler): template = env.get_template('smsgaddresses.html') return self.render_template(template, { 'messages': messages, + 'err_messages': err_messages, 'data': page_data, 'smsgaddresses': smsgaddresses, 'network_addr': network_addr, @@ -487,7 +499,8 @@ class HttpHandler(BaseHTTPRequestHandler): page_data = {'identity_address': identity_address} messages = [] - form_data = self.checkForm(post_string, 'identity', messages) + err_messages = [] + form_data = self.checkForm(post_string, 'identity', err_messages) if form_data: if have_data_entry(form_data, 'edit'): page_data['show_edit_form'] = True @@ -498,7 +511,7 @@ class HttpHandler(BaseHTTPRequestHandler): swap_client.updateIdentity(identity_address, new_label) messages.append('Updated') except Exception as e: - messages.append('Error') + err_messages.append(str(e)) try: identity = swap_client.getIdentity(identity_address) @@ -517,6 +530,7 @@ class HttpHandler(BaseHTTPRequestHandler): template = env.get_template('identity.html') return self.render_template(template, { 'messages': messages, + 'err_messages': err_messages, 'data': page_data, 'summary': summary, }) diff --git a/basicswap/interface/contrib/pivx_test_framework/messages.py b/basicswap/interface/contrib/pivx_test_framework/messages.py index be74041..b5fd312 100755 --- a/basicswap/interface/contrib/pivx_test_framework/messages.py +++ b/basicswap/interface/contrib/pivx_test_framework/messages.py @@ -418,43 +418,114 @@ class CTxOut: 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("= 2: - self.sapData = deser_string(f) + if self.nVersion >= 3: + self.sapData = SaplingTxData() + self.sapData.deserialize(f) + if self.nType != 0: + self.extraData = deser_string(f) self.sha256 = None self.hash = None def serialize_without_witness(self): r = b"" - r += struct.pack("= 2: - r += ser_string(self.sapData) + if self.nVersion >= 3: + r += self.sapData.serialize() + if self.nType != 0: + r += ser_string(self.extraData) return r # 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 def __repr__(self): - return "CTransaction(nVersion=%i vin=%s vout=%s nLockTime=%i)" \ - % (self.nVersion, repr(self.vin), repr(self.vout), self.nLockTime) + return "CTransaction(nVersion=%i nType=%i vin=%s vout=%s nLockTime=%i)" \ + % (self.nVersion, self.nType, repr(self.vin), repr(self.vout), self.nLockTime) class CBlockHeader: diff --git a/basicswap/ui/page_offers.py b/basicswap/ui/page_offers.py index 65a6d1f..ce3531a 100644 --- a/basicswap/ui/page_offers.py +++ b/basicswap/ui/page_offers.py @@ -417,9 +417,9 @@ def page_offer(self, url_split, post_string): ci_from = swap_client.ci(Coins(offer.coin_from)) ci_to = swap_client.ci(Coins(offer.coin_to)) - debugind = -1 # Set defaults + debugind = -1 bid_amount = ci_from.format_amount(offer.amount_from) bid_rate = ci_to.format_amount(offer.rate) diff --git a/basicswap/ui/page_wallet.py b/basicswap/ui/page_wallet.py index 447cbf7..1f08f88 100644 --- a/basicswap/ui/page_wallet.py +++ b/basicswap/ui/page_wallet.py @@ -172,8 +172,8 @@ def page_wallet(self, url_split, post_string): page_data = {} messages = [] err_messages = [] - form_data = self.checkForm(post_string, 'wallet', err_messages) show_utxo_groups = False + form_data = self.checkForm(post_string, 'wallet', err_messages) if form_data: cid = str(int(coin_id)) diff --git a/tests/basicswap/extended/test_pivx.py b/tests/basicswap/extended/test_pivx.py index 29eec78..7d6c88b 100644 --- a/tests/basicswap/extended/test_pivx.py +++ b/tests/basicswap/extended/test_pivx.py @@ -349,9 +349,6 @@ class Test(unittest.TestCase): ro = btcRpc('getblockchaininfo') checkForks(ro) - ro = pivxRpc('getwalletinfo') - print('pivxRpc', ro) - signal.signal(signal.SIGINT, signal_handler) cls.update_thread = threading.Thread(target=run_loop, args=(cls,)) 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)) 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): global stop_test logging.info('Delay')