From e1a17ac43f24f04db2d0bc8941362f1996f8102d Mon Sep 17 00:00:00 2001 From: scgbckbone Date: Wed, 19 Jun 2024 08:54:19 +0200 Subject: [PATCH] share push tx tests --- shared/auth.py | 8 +++- shared/nfc.py | 1 + shared/ux_q1.py | 2 +- testing/conftest.py | 11 ++++-- testing/test_nfc.py | 93 +++++++++++++++++++++++++++++++++------------ 5 files changed, 84 insertions(+), 31 deletions(-) diff --git a/shared/auth.py b/shared/auth.py index e2fa984b..f4d92857 100644 --- a/shared/auth.py +++ b/shared/auth.py @@ -625,7 +625,7 @@ class ApproveTransaction(UserAuthorizedAction): return '%s\n - to script -\n%s\n' % (val, dest) @staticmethod - async def try_push_tx(data, txid, txn_sha): + async def try_push_tx(data, txid, txn_sha=None): from glob import settings, PSRAM, NFC # if NFC PushTx is enabled, do that w/o questions. url = settings.get('ptxurl', False) @@ -633,6 +633,8 @@ class ApproveTransaction(UserAuthorizedAction): try: if isinstance(data, int): data = PSRAM.read_at(TXN_OUTPUT_OFFSET, data) + if txn_sha is None: + txn_sha = ngu.hash.sha256s(data)[-8:] await NFC.share_push_tx(url, txid, data, txn_sha) return True except: pass # continue normally if it fails, perhaps too big? @@ -1181,8 +1183,10 @@ async def sign_psbt_file(filename, force_vdisk=False, slot_b=None): base+'-final.txn' if not del_after else 'tmp.txn', out_path) with SFFile(TXN_OUTPUT_OFFSET, max_size=MAX_TXN_LEN, message="Saving...") as fd0: + await fd0.erase() txid = psbt.finalize(fd0) - tx_len, tx_sha = (fd0.tell(), fd0.checksum.digest()) + fd0.close() + tx_len, tx_sha = fd0.tell(), fd0.checksum.digest() if txid and await ApproveTransaction.try_push_tx(tx_len, txid, tx_sha): return # success, exit diff --git a/shared/nfc.py b/shared/nfc.py index 3cb56b79..d852d662 100644 --- a/shared/nfc.py +++ b/shared/nfc.py @@ -587,6 +587,7 @@ class NFCHandler: else: psbt.serialize(fd) + fd.close() self.result = (fd.tell(), fd.checksum.digest()) out_len, out_sha = self.result diff --git a/shared/ux_q1.py b/shared/ux_q1.py index 9cd6c872..fb31ce26 100644 --- a/shared/ux_q1.py +++ b/shared/ux_q1.py @@ -975,7 +975,7 @@ async def qr_psbt_sign(decoder, psbt_len, raw): txid = psbt.finalize(fd) else: psbt.serialize(fd) - + psram.close() data_len = psram.tell() sha = fd.checksum.digest() diff --git a/testing/conftest.py b/testing/conftest.py index 87847999..a1bab645 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -441,7 +441,7 @@ def cap_image(request, sim_exec, is_q1, is_headless): if is_headless: raise pytest.skip("headless mode: QR tests disabled") # trigger simulator to capture a snapshot into a named file, read it. - fn = os.path.realpath(f'./debug/snap-{random.randint(1E6, 9E6)}.png') + fn = os.path.realpath(f'./debug/snap-{random.randint(int(1E6), int(9E6))}.png') try: sim_exec(f"from glob import dis; dis.dis.save_snapshot({fn!r})") for _ in range(20): @@ -1167,11 +1167,13 @@ def check_against_bitcoind(bitcoind, use_regtest, sim_exec, sim_execfile): return doit @pytest.fixture -def try_sign_microsd(open_microsd, cap_story, pick_menu_item, goto_home, need_keypress, microsd_path): +def try_sign_microsd(open_microsd, cap_story, pick_menu_item, goto_home, + need_keypress, microsd_path, cap_screen): # like "try_sign" but use "air gapped" file transfer via microSD - def doit(f_or_data, accept=True, finalize=False, accept_ms_import=False, complete=False, encoding='binary', del_after=0): + def doit(f_or_data, accept=True, finalize=False, accept_ms_import=False, + complete=False, encoding='binary', del_after=0, nfc_push_tx=False): if f_or_data[0:5] == b'psbt\xff': ip = f_or_data filename = 'memory' @@ -1229,6 +1231,9 @@ def try_sign_microsd(open_microsd, cap_story, pick_menu_item, goto_home, need_ke # look for "Aborting..." ?? return ip, None, None + if nfc_push_tx: + return ip, None, None + # wait for it to finish for r in range(10): time.sleep(0.1) diff --git a/testing/test_nfc.py b/testing/test_nfc.py index 35233a31..85117179 100644 --- a/testing/test_nfc.py +++ b/testing/test_nfc.py @@ -13,7 +13,7 @@ from struct import pack, unpack import ndef from hashlib import sha256 from txn import * -from charcodes import KEY_NFC +from charcodes import KEY_NFC, KEY_QR @pytest.mark.parametrize('case', range(6)) @@ -158,7 +158,7 @@ def try_sign_nfc(cap_story, pick_menu_item, goto_home, need_keypress, sim_exec('from pyb import SDCard; SDCard.ejected = True; import nfc; nfc.NFCHandler.startup()') def doit(f_or_data, accept=True, expect_finalize=False, accept_ms_import=False, - complete=False, encoding='binary', over_nfc=True): + complete=False, encoding='binary', over_nfc=True, nfc_tools=False, nfc_push_tx=False): if f_or_data[0:5] == b'psbt\xff': ip = f_or_data @@ -198,14 +198,20 @@ def try_sign_nfc(cap_story, pick_menu_item, goto_home, need_keypress, time.sleep(.2) # required goto_home() - pick_menu_item('Ready To Sign') + if nfc_tools: + pick_menu_item("Advanced/Tools") + pick_menu_item("NFC Tools") + pick_menu_item("Sign PSBT") + else: + pick_menu_item('Ready To Sign') - time.sleep(.1) - _, story = cap_story() - assert 'NFC' in story + time.sleep(.1) + _, story = cap_story() + assert 'NFC' in story + + press_nfc() + time.sleep(.1) - press_nfc() - time.sleep(.1) nfc_write(ccfile) time.sleep(.5) @@ -230,6 +236,10 @@ def try_sign_nfc(cap_story, pick_menu_item, goto_home, need_keypress, # look for "Aborting..." ?? return ip, None, None + time.sleep(.1) + if nfc_push_tx: + return ip, None, None + if not over_nfc: # wait for it to finish for r in range(10): @@ -417,40 +427,73 @@ def test_ndef_roundtrip(load_shared_mod): assert cc_ndef.ccfile_decode(r) == (12, 399, False, 4096) -@pytest.mark.parametrize('num_outs', [2, 100, 250]) +@pytest.mark.parametrize('num_outs', [2, 5, 100, 250]) @pytest.mark.parametrize('chain', ['BTC', 'XTN']) +@pytest.mark.parametrize('way', ['sd', 'nfc', 'usb', 'qr']) def test_nfc_pushtx(num_outs, chain, enable_nfc, settings_set, settings_remove, try_sign, fake_txn, nfc_block4rf, nfc_read, press_cancel, - cap_story, cap_screen, has_qwerty -): + cap_story, cap_screen, has_qwerty, way, try_sign_microsd, + try_sign_nfc, scan_a_qr, need_keypress, press_select, + goto_home): # check the NFC push Tx feature, validating the URL's it makes # - not the UX # - 100 outs => 5000 or so # - 250 outs => 8800 # - not too many inputs so faster to sign from base64 import urlsafe_b64decode - from urllib.parse import urlsplit, urlunsplit, parse_qsl, unquote + from urllib.parse import urlsplit, parse_qsl, unquote settings_set('chain', chain) enable_nfc() + if way in ("nfc", "qr") and num_outs >= 100: + raise pytest.skip("too big") + prefix = 'http://10.0.0.10/pushtx#' settings_set('ptxurl', prefix) psbt = fake_txn(2, num_outs) - _, result = try_sign(psbt, finalize=True) + if way == "usb": + _, result = try_sign(psbt, finalize=True) + elif way == "sd": + ip, result, txid = try_sign_microsd(psbt, finalize=True, nfc_push_tx=True) + elif way == "nfc": + ip, result, txid = try_sign_nfc(psbt, expect_finalize=True, nfc_tools=True, + nfc_push_tx=True) + elif way == "qr": + goto_home() + need_keypress(KEY_QR) + from bbqr import split_qrs + actual_vers, parts = split_qrs(psbt, 'P') + for p in parts: + scan_a_qr(p) + time.sleep(4.0 / len(parts)) # just so we can watch - print(f'len = {len(result)}') + for r in range(20): + title, story = cap_story() + if 'OK TO SEND' in title: + break + time.sleep(.1) + else: + raise pytest.fail('never saw it?') + # approve it + press_select() + + # print(f'len = {len(result)}') + # if num_outs >= 250: # NFC will not be offered (too big) - assert len(result) > 8000 + time.sleep(.1) title, story = cap_story() - - assert title == 'Final TXID' - assert 'to share signed txn' in story - + if way == "usb": + assert title == 'Final TXID' + assert 'to share signed txn' in story + elif way == "sd": + assert title == "PSBT Signed" + else: + assert False return # expect NFC animation @@ -495,15 +538,15 @@ def test_nfc_pushtx(num_outs, chain, enable_nfc, settings_set, settings_remove, expect = sha256(decoded_txn).digest()[-8:] assert expect == decoded_chk - assert result == decoded_txn - settings_remove('ptxurl') settings_set('chain', 'XTN') -def test_share_by_pushtx(goto_home, cap_story, pick_menu_item, settings_set, settings_remove, - microsd_path, cap_menu, has_qwerty, cap_screen, - press_cancel, enable_nfc, nfc_block4rf, nfc_read): +@pytest.mark.parametrize("is_hex", [True, False]) +def test_share_by_pushtx(goto_home, cap_story, pick_menu_item, settings_set, + settings_remove, microsd_path, cap_menu, has_qwerty, + cap_screen, press_cancel, enable_nfc, nfc_block4rf, + nfc_read, is_hex): enable_nfc() @@ -514,7 +557,7 @@ def test_share_by_pushtx(goto_home, cap_story, pick_menu_item, settings_set, set fname = "fake-nfc.txn" with open(microsd_path(fname), "wb") as f: - f.write(fake_txn) + f.write(b2a_hex(fake_txn) if is_hex else fake_txn) goto_home() pick_menu_item("Advanced/Tools")