multiprocess simulator
This commit is contained in:
parent
89dfe1f6d4
commit
11da344abf
2
external/ckcc-protocol
vendored
2
external/ckcc-protocol
vendored
@ -1 +1 @@
|
||||
Subproject commit 0e686dbda686f76c4d3e8069558b2a31f9d1c2b1
|
||||
Subproject commit f87d30f220cb6334eb3c4ace93c1b62e04942022
|
||||
@ -51,7 +51,7 @@ class Bitcoind:
|
||||
[
|
||||
self.bitcoind_path,
|
||||
# needed for newest master
|
||||
# TODO legacy wallet will be deprecated in 26
|
||||
# TODO legacy wallet will be deprecated in 29
|
||||
"-deprecatedrpc=create_bdb",
|
||||
"-regtest",
|
||||
f"-datadir={self.datadir}",
|
||||
@ -59,6 +59,7 @@ class Bitcoind:
|
||||
"-fallbackfee=0.0002",
|
||||
"-server=1",
|
||||
"-keypool=1",
|
||||
"-listen=0"
|
||||
f"-port={self.p2p_port}",
|
||||
f"-rpcport={self.rpc_port}"
|
||||
]
|
||||
|
||||
@ -5,7 +5,7 @@ from charcodes import KEY_ENTER
|
||||
from core_fixtures import _pick_menu_item, _cap_story, _press_select
|
||||
from core_fixtures import _need_keypress, _cap_menu, _sim_exec
|
||||
from run_sim_tests import ColdcardSimulator, clean_sim_data
|
||||
from ckcc_protocol.client import ColdcardDevice, CKCC_SIMULATOR_PATH
|
||||
from ckcc_protocol.client import ColdcardDevice
|
||||
|
||||
|
||||
def _clone(source, target):
|
||||
@ -18,7 +18,7 @@ def _clone(source, target):
|
||||
clean_sim_data() # remove all from previous
|
||||
sim_target = ColdcardSimulator(args=[target_sim_arg, "-l"])
|
||||
sim_target.start(start_wait=6)
|
||||
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
|
||||
device = ColdcardDevice(is_simulator=True)
|
||||
_pick_menu_item(device, target_is_Q, "Import Existing")
|
||||
_pick_menu_item(device, target_is_Q, "Clone Coldcard")
|
||||
time.sleep(.1)
|
||||
@ -36,7 +36,7 @@ def _clone(source, target):
|
||||
sim_source = ColdcardSimulator(args=[source_sim_arg, "--ms", "--p2wsh",
|
||||
"--set", "nfc=1", "--set", "vidsk=1"])
|
||||
sim_source.start(start_wait=6)
|
||||
device_source = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
|
||||
device_source = ColdcardDevice(is_simulator=True)
|
||||
_pick_menu_item(device_source, source_is_Q, "Advanced/Tools")
|
||||
time.sleep(.1)
|
||||
_pick_menu_item(device_source, source_is_Q, "Backup")
|
||||
@ -89,7 +89,7 @@ def _clone(source, target):
|
||||
# TARGET again. Killed now - restart and verify settings
|
||||
sim_target = ColdcardSimulator(args=[target_sim_arg])
|
||||
sim_target.start(start_wait=6)
|
||||
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
|
||||
device = ColdcardDevice(is_simulator=True)
|
||||
_pick_menu_item(device, target_is_Q, "Settings")
|
||||
_pick_menu_item(device, target_is_Q, "Multisig Wallets")
|
||||
time.sleep(.1)
|
||||
|
||||
@ -13,13 +13,15 @@ from api import bitcoind_d_sim_watch, finalize_v2_v0_convert
|
||||
from binascii import b2a_hex, a2b_hex
|
||||
from constants import *
|
||||
from charcodes import *
|
||||
from core_fixtures import _need_keypress, _sim_exec, _cap_story, _cap_menu, _cap_screen
|
||||
from core_fixtures import _need_keypress, _sim_exec, _cap_story, _cap_menu, _cap_screen, _sim_eval
|
||||
from core_fixtures import _press_select, _pick_menu_item, _enter_complex, _dev_hw_label
|
||||
|
||||
|
||||
# lock down randomness
|
||||
random.seed(42)
|
||||
|
||||
# needs to be run from /testing directory
|
||||
os.environ["SRC_ROOT"] = os.path.join(os.getcwd().rsplit("/", 1)[0])
|
||||
if sys.platform == 'darwin':
|
||||
# BUGFIX: my ARM-based MacOS system uses rosetta to run Python in x86 mode
|
||||
# and so I needed this?
|
||||
@ -37,6 +39,7 @@ def pytest_addoption(parser):
|
||||
default=False, help="operator must press keys on real CC")
|
||||
|
||||
parser.addoption("--mk", default=4, help="Assume mark N hardware")
|
||||
parser.addoption("--sim-socket", "-S", type=str, help="Simulator .socket path", default=None)
|
||||
|
||||
parser.addoption("--duress", action="store_true",
|
||||
default=False, help="assume logged-in with duress PIN")
|
||||
@ -79,48 +82,42 @@ def simulator(request):
|
||||
raise pytest.skip('need simulator for this test, have real device')
|
||||
|
||||
try:
|
||||
return ColdcardDevice(sn=SIM_PATH)
|
||||
except:
|
||||
return ColdcardDevice(sn=request.config.getoption("--sim-socket"), is_simulator=True)
|
||||
except Exception as e:
|
||||
print("Simulator is required for this test")
|
||||
raise pytest.fail('missing simulator')
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def sim_exec(dev):
|
||||
# run code in the simulator's interpretor
|
||||
# run code in the simulator's interpreter
|
||||
# - can work on real product too, if "debug build" is used.
|
||||
f = functools.partial(_sim_exec, dev)
|
||||
return f
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def sim_eval(dev):
|
||||
# eval an expression in the simulator's interpretor
|
||||
# - can work on real product too, if "debug build" is used.
|
||||
f = functools.partial(_sim_eval, dev)
|
||||
return f
|
||||
|
||||
def doit(cmd, timeout=None):
|
||||
return dev.send_recv(b'EVAL' + cmd.encode('utf-8'), timeout=timeout).decode('utf-8')
|
||||
|
||||
return doit
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def sim_execfile(simulator):
|
||||
@pytest.fixture
|
||||
def sim_execfile(simulator, src_root_dir):
|
||||
# run a whole file in the simulator's interpretor
|
||||
# - requires shared filesystem
|
||||
import os
|
||||
|
||||
def doit(fname, timeout=None):
|
||||
fn = os.path.realpath(fname)
|
||||
hook = 'execfile("%s")' % fn
|
||||
hook = 'execfile("%s")' % (src_root_dir + "/testing/" + fname)
|
||||
return simulator.send_recv(b'EXEC' + hook.encode('utf-8'), timeout=timeout).decode('utf-8')
|
||||
|
||||
return doit
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def is_simulator(dev):
|
||||
def doit():
|
||||
return hasattr(dev.dev, 'pipe')
|
||||
return doit
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def send_ux_abort(simulator):
|
||||
|
||||
def doit():
|
||||
@ -138,7 +135,7 @@ def OK(is_q1):
|
||||
def X(is_q1):
|
||||
return "CANCEL" if is_q1 else "X"
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def need_keypress(dev, request):
|
||||
def doit(k, timeout=1000):
|
||||
if request.config.getoption("--manual"):
|
||||
@ -152,7 +149,7 @@ def need_keypress(dev, request):
|
||||
return doit
|
||||
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def enter_number(need_keypress, press_select):
|
||||
def doit(number):
|
||||
number = str(number) if not isinstance(number, str) else number
|
||||
@ -170,7 +167,7 @@ def enter_complex(dev, is_q1):
|
||||
f = functools.partial(_enter_complex, dev, is_q1)
|
||||
return f
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def enter_hex(need_keypress, enter_text, is_q1):
|
||||
def doit(hex_str):
|
||||
if is_q1:
|
||||
@ -185,7 +182,7 @@ def enter_hex(need_keypress, enter_text, is_q1):
|
||||
|
||||
return doit
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def enter_pin(enter_number, press_select, cap_screen, is_q1):
|
||||
def doit(pin):
|
||||
assert '-' in pin
|
||||
@ -207,7 +204,7 @@ def enter_pin(enter_number, press_select, cap_screen, is_q1):
|
||||
return doit
|
||||
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def do_keypresses(need_keypress):
|
||||
# do a series of keypresses, any kind
|
||||
def doit(value):
|
||||
@ -217,7 +214,7 @@ def do_keypresses(need_keypress):
|
||||
return doit
|
||||
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def enter_text(need_keypress, is_q1):
|
||||
# enter a text value, might be a number or string ... on Q can be multiline
|
||||
def doit(value, multiline=False):
|
||||
@ -260,14 +257,14 @@ def master_xpub(dev):
|
||||
|
||||
return r
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def unit_test(sim_execfile):
|
||||
def doit(filename):
|
||||
rv = sim_execfile(filename)
|
||||
if rv: pytest.fail(rv)
|
||||
return doit
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def get_settings(sim_execfile):
|
||||
# get all settings
|
||||
def doit():
|
||||
@ -278,7 +275,7 @@ def get_settings(sim_execfile):
|
||||
|
||||
return doit
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def get_setting(sim_execfile, sim_exec):
|
||||
# get an individual setting
|
||||
def doit(name, default=None):
|
||||
@ -290,15 +287,15 @@ def get_setting(sim_execfile, sim_exec):
|
||||
|
||||
return doit
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def addr_vs_path(master_xpub):
|
||||
from bip32 import BIP32Node
|
||||
from ckcc_protocol.constants import AF_CLASSIC, AFC_PUBKEY, AF_P2WPKH, AFC_SCRIPT
|
||||
from ckcc_protocol.constants import AF_P2WPKH_P2SH, AF_P2SH, AF_P2WSH, AF_P2WSH_P2SH
|
||||
from bech32 import bech32_decode, convertbits, Encoding
|
||||
from hashlib import sha256
|
||||
|
||||
def doit(given_addr, path=None, addr_fmt=None, script=None, testnet=True):
|
||||
from bip32 import BIP32Node
|
||||
from ckcc_protocol.constants import AF_CLASSIC, AFC_PUBKEY, AF_P2WPKH, AFC_SCRIPT
|
||||
from ckcc_protocol.constants import AF_P2WPKH_P2SH, AF_P2SH, AF_P2WSH, AF_P2WSH_P2SH
|
||||
from bech32 import bech32_decode, convertbits, Encoding
|
||||
from hashlib import sha256
|
||||
|
||||
if not script:
|
||||
try:
|
||||
# prefer using xpub if we can
|
||||
@ -369,13 +366,13 @@ def capture_enabled(sim_eval):
|
||||
# - could be xfail or xskip here
|
||||
assert sim_eval("'sim_display' in sys.modules") == 'True'
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def cap_menu(dev):
|
||||
"Return menu items as a list"
|
||||
f = functools.partial(_cap_menu, dev)
|
||||
return f
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def is_ftux_screen(sim_exec):
|
||||
"are we presenting a view from ftux.py??"
|
||||
def doit():
|
||||
@ -402,12 +399,12 @@ def expect_ftux(cap_menu, cap_story, press_select, is_ftux_screen):
|
||||
return doit
|
||||
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def cap_screen(dev):
|
||||
f = functools.partial(_cap_screen, dev)
|
||||
return f
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def cap_text_box(cap_screen):
|
||||
# provides text inside a lined box on the screen right now - Q1 only
|
||||
def doit():
|
||||
@ -423,15 +420,15 @@ def cap_text_box(cap_screen):
|
||||
|
||||
return doit
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def cap_story(dev):
|
||||
# returns (title, body) of whatever story is being actively shown
|
||||
f = functools.partial(_cap_story, dev)
|
||||
return f
|
||||
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def cap_image(request, sim_exec, is_q1, is_headless):
|
||||
@pytest.fixture
|
||||
def cap_image(request, sim_exec, is_q1, is_headless, sim_root_dir):
|
||||
|
||||
def flip(raw):
|
||||
reorg = bytearray(128*64)
|
||||
@ -451,7 +448,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(int(1E6), int(9E6))}.png')
|
||||
fn = os.path.realpath(f'{sim_root_dir}/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):
|
||||
@ -480,7 +477,7 @@ RV.write(b2a_hex(dis.dis.buffer))'''))
|
||||
QR_HISTORY = []
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def qr_quality_check():
|
||||
def qr_quality_check(sim_root_dir):
|
||||
# Use this with cap_screen_qr
|
||||
print("QR codes will be captured and shown at end of run.")
|
||||
yield None
|
||||
@ -529,13 +526,13 @@ def qr_quality_check():
|
||||
|
||||
#rv = rv.resize(tuple(c*4 for c in rv.size), resample=Image.NEAREST)
|
||||
|
||||
rv.save('debug/all-qrs.png')
|
||||
rv.save(f'{sim_root_dir}/debug/all-qrs.png')
|
||||
rv.show()
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def cap_screen_qr(cap_image):
|
||||
@pytest.fixture
|
||||
def cap_screen_qr(cap_image, sim_root_dir):
|
||||
def doit(no_history=False):
|
||||
# NOTE: version=4 QR is pixel doubled to be 66x66 with 2 missing lines at bottom
|
||||
# LATER: not doing that anymore; v={1,2,3} doubled, all higher 1:1 pixels (tiny)
|
||||
@ -570,7 +567,7 @@ def cap_screen_qr(cap_image):
|
||||
w, h = orig_img.size # 320x240
|
||||
img = orig_img.crop( (0, 0, w, h-5) ).convert('L')
|
||||
|
||||
img.save('debug/last-qr.png')
|
||||
img.save(f'{sim_root_dir}/debug/last-qr.png')
|
||||
#img.show()
|
||||
|
||||
# Above usually works @ zoom=1, but not always!
|
||||
@ -618,8 +615,8 @@ def verify_qr_address(cap_screen_qr, cap_screen, is_q1):
|
||||
# - skips first line, which on Q shows the index number sometimes
|
||||
# - insists on some spaces
|
||||
full = cap_screen()
|
||||
full_split = full.split("\n")
|
||||
if is_q1:
|
||||
full_split = full.split("\n")
|
||||
if is_change:
|
||||
for i, (c, line) in enumerate(zip("XXXXCHANGE", full_split)):
|
||||
if i > 3:
|
||||
@ -640,14 +637,18 @@ def verify_qr_address(cap_screen_qr, cap_screen, is_q1):
|
||||
for c, line in zip("XXXXXXBACK", full_split):
|
||||
assert not line.endswith(c)
|
||||
|
||||
txt = ''.join(l for l in full.split() if len(l)>4).replace('~', '')
|
||||
txt = ''.join(l for l in full_split if len(l)>4).replace('~', '')
|
||||
if txt:
|
||||
# just index remained
|
||||
int(txt)
|
||||
txt = None
|
||||
else:
|
||||
if is_change:
|
||||
assert "CHANGE BACK" in full
|
||||
elif is_change is False:
|
||||
assert "CHANGE BACK" not in full
|
||||
|
||||
txt = ''.join(full.split("\n")).replace('CHANGE BACK', '')
|
||||
txt = ''.join(full_split).replace('CHANGE BACK', '')
|
||||
|
||||
if txt:
|
||||
assert txt == qr
|
||||
@ -662,7 +663,7 @@ def verify_qr_address(cap_screen_qr, cap_screen, is_q1):
|
||||
|
||||
return doit
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def get_pp_sofar(sim_exec):
|
||||
# get entry value for bip39 passphrase
|
||||
def doit():
|
||||
@ -672,7 +673,7 @@ def get_pp_sofar(sim_exec):
|
||||
|
||||
return doit
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def get_secrets(sim_execfile):
|
||||
# returns big dict based on what we'd normally put into a backup file.
|
||||
def doit():
|
||||
@ -692,48 +693,48 @@ def get_secrets(sim_execfile):
|
||||
|
||||
return doit
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def press_select(dev, has_qwerty):
|
||||
f = functools.partial(_press_select, dev, has_qwerty)
|
||||
return f
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def press_cancel(need_keypress, has_qwerty):
|
||||
def doit(**kws):
|
||||
need_keypress(KEY_CANCEL if has_qwerty else 'x', **kws)
|
||||
return doit
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def press_delete(need_keypress, has_qwerty):
|
||||
def doit(**kws):
|
||||
need_keypress(KEY_DELETE if has_qwerty else 'x', **kws)
|
||||
return doit
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def press_nfc(need_keypress, has_qwerty):
|
||||
def doit(num=3, **kws):
|
||||
need_keypress(KEY_NFC if has_qwerty else str(num), **kws)
|
||||
return doit
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def press_up(need_keypress, has_qwerty):
|
||||
def doit(**kws):
|
||||
need_keypress(KEY_UP if has_qwerty else "5", **kws)
|
||||
return doit
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def press_down(need_keypress, has_qwerty):
|
||||
def doit(**kws):
|
||||
need_keypress(KEY_DOWN if has_qwerty else "8", **kws)
|
||||
return doit
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def press_left(need_keypress, has_qwerty):
|
||||
def doit(**kws):
|
||||
need_keypress(KEY_LEFT if has_qwerty else "7", **kws)
|
||||
return doit
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def press_right(need_keypress, has_qwerty):
|
||||
def doit(**kws):
|
||||
need_keypress(KEY_RIGHT if has_qwerty else "9", **kws)
|
||||
@ -775,24 +776,37 @@ def goto_home(cap_menu, press_cancel, press_select, pick_menu_item, cap_screen):
|
||||
|
||||
return doit
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
@pytest.fixture
|
||||
def pick_menu_item(dev, has_qwerty):
|
||||
f = functools.partial(_pick_menu_item, dev, has_qwerty)
|
||||
return f
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def virtdisk_path(request, is_simulator, needs_virtdisk):
|
||||
@pytest.fixture(scope='session')
|
||||
def src_root_dir():
|
||||
return os.environ.get("SRC_ROOT")
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def sim_root_dir(dev, request, src_root_dir):
|
||||
if request.config.getoption("--dev"):
|
||||
return os.path.join(src_root_dir, "unix/work")
|
||||
|
||||
cmd = f"import ckcc; RV.write(ckcc.get_sim_root_dirs()[0])"
|
||||
rv = _sim_exec(dev, cmd)
|
||||
return rv
|
||||
|
||||
@pytest.fixture
|
||||
def virtdisk_path(request, is_simulator, needs_virtdisk, sim_root_dir):
|
||||
# get a path to indicated filename on emulated/shared dir
|
||||
|
||||
def doit(fn):
|
||||
# could use: ckcc.get_sim_root_dirs() here
|
||||
if is_simulator():
|
||||
get_setting = request.getfixturevalue('get_setting')
|
||||
if not get_setting('vidsk', False):
|
||||
raise pytest.xfail('virtdisk disabled')
|
||||
assert os.path.isdir('../unix/work/VirtDisk')
|
||||
return '../unix/work/VirtDisk/' + fn
|
||||
|
||||
return sim_root_dir + '/VirtDisk/' + fn
|
||||
elif sys.platform == 'darwin':
|
||||
# TODO
|
||||
|
||||
if not request.config.getoption("--manual"):
|
||||
raise pytest.fail('must use --manual CLI option')
|
||||
@ -803,7 +817,7 @@ def virtdisk_path(request, is_simulator, needs_virtdisk):
|
||||
|
||||
return doit
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def virtdisk_wipe(dev, needs_virtdisk, virtdisk_path):
|
||||
def doit():
|
||||
for fn in glob.glob(virtdisk_path('*')):
|
||||
@ -815,13 +829,12 @@ def virtdisk_wipe(dev, needs_virtdisk, virtdisk_path):
|
||||
return doit
|
||||
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def microsd_path(simulator):
|
||||
@pytest.fixture
|
||||
def microsd_path(simulator, sim_root_dir):
|
||||
# open a file from the simulated microsd
|
||||
|
||||
def doit(fn):
|
||||
# could use: ckcc.get_sim_root_dirs() here
|
||||
return '../unix/work/MicroSD/' + fn
|
||||
return sim_root_dir + '/MicroSD/' + fn
|
||||
|
||||
return doit
|
||||
|
||||
@ -836,7 +849,7 @@ def microsd_wipe(microsd_path):
|
||||
os.remove(dir + fname)
|
||||
return doit
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture
|
||||
def open_microsd(simulator, microsd_path):
|
||||
# open a file from the simulated microsd
|
||||
|
||||
@ -846,13 +859,12 @@ def open_microsd(simulator, microsd_path):
|
||||
|
||||
return doit
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def settings_path(simulator):
|
||||
@pytest.fixture
|
||||
def settings_path(simulator, sim_root_dir):
|
||||
# open a file from the simulated microsd
|
||||
|
||||
def doit(fn):
|
||||
# could use: ckcc.get_sim_root_dirs() here
|
||||
return '../unix/work/settings/' + fn
|
||||
return sim_root_dir + '/settings/' + fn
|
||||
|
||||
return doit
|
||||
|
||||
@ -864,7 +876,7 @@ def settings_slots(settings_path):
|
||||
if fn.endswith(".aes")]
|
||||
return doit
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
@pytest.fixture
|
||||
def set_master_key(sim_exec, sim_execfile, simulator, reset_seed_words):
|
||||
# load simulator w/ a specific bip32 master key
|
||||
|
||||
@ -888,7 +900,7 @@ def set_master_key(sim_exec, sim_execfile, simulator, reset_seed_words):
|
||||
# - actually need seed words for all tests
|
||||
reset_seed_words()
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
@pytest.fixture
|
||||
def set_xfp(sim_exec):
|
||||
# set the XFP, without really knowing the private keys
|
||||
# - won't be able to sign, but should accept PSBT for signing
|
||||
@ -906,7 +918,7 @@ def set_xfp(sim_exec):
|
||||
sim_exec('from main import settings; settings.set("xfp", 0x%x);' % simulator_fixed_xfp)
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
@pytest.fixture
|
||||
def set_encoded_secret(sim_exec, sim_execfile, simulator, reset_seed_words):
|
||||
# load simulator w/ a specific secret
|
||||
|
||||
@ -932,21 +944,21 @@ def set_encoded_secret(sim_exec, sim_execfile, simulator, reset_seed_words):
|
||||
# - actually need seed words for all tests
|
||||
reset_seed_words()
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
@pytest.fixture
|
||||
def use_mainnet(settings_set):
|
||||
def doit():
|
||||
settings_set('chain', 'BTC')
|
||||
yield doit
|
||||
settings_set('chain', 'XTN')
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
@pytest.fixture
|
||||
def use_testnet(settings_set):
|
||||
def doit(do_testnet=True):
|
||||
settings_set('chain', 'XTN' if do_testnet else 'BTC')
|
||||
yield doit
|
||||
settings_set('chain', 'XTN')
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
@pytest.fixture
|
||||
def use_regtest(request, settings_set):
|
||||
if request.config.getoption("--manual"):
|
||||
def xrt_warn():
|
||||
@ -960,7 +972,7 @@ def use_regtest(request, settings_set):
|
||||
settings_set('chain', 'XTN')
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
@pytest.fixture
|
||||
def set_seed_words(change_seed_words, reset_seed_words):
|
||||
def doit(w):
|
||||
return change_seed_words(w)
|
||||
@ -971,7 +983,7 @@ def set_seed_words(change_seed_words, reset_seed_words):
|
||||
|
||||
reset_seed_words()
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
@pytest.fixture
|
||||
def change_seed_words(sim_exec, sim_execfile, simulator):
|
||||
# load simulator w/ a specific bip32 master key
|
||||
|
||||
@ -989,7 +1001,7 @@ def change_seed_words(sim_exec, sim_execfile, simulator):
|
||||
|
||||
return doit
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def reset_seed_words(change_seed_words):
|
||||
# load simulator w/ a specific bip39 seed phrase
|
||||
|
||||
@ -1004,7 +1016,7 @@ def reset_seed_words(change_seed_words):
|
||||
return doit
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def settings_set(sim_exec):
|
||||
|
||||
def doit(key, val, prelogin=False):
|
||||
@ -1014,7 +1026,7 @@ def settings_set(sim_exec):
|
||||
|
||||
return doit
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def settings_get(sim_exec):
|
||||
|
||||
def doit(key, def_val=None, prelogin=False):
|
||||
@ -1026,7 +1038,7 @@ def settings_get(sim_exec):
|
||||
|
||||
return doit
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def master_settings_get(sim_exec):
|
||||
|
||||
def doit(key):
|
||||
@ -1037,7 +1049,7 @@ def master_settings_get(sim_exec):
|
||||
|
||||
return doit
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def settings_remove(sim_exec):
|
||||
|
||||
def doit(key):
|
||||
@ -1046,19 +1058,14 @@ def settings_remove(sim_exec):
|
||||
|
||||
return doit
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def repl(request):
|
||||
return request.getfixturevalue('mk4_repl')
|
||||
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def mk4_repl(sim_eval, sim_exec):
|
||||
@pytest.fixture(scope='session')
|
||||
def repl(dev, request):
|
||||
# Provide an interactive connection to the REPL, using the debug build USB commands
|
||||
|
||||
class Mk4USBRepl:
|
||||
def eval(self, cmd, max_time=3):
|
||||
# send a command, wait for it to finish
|
||||
resp = sim_eval(cmd)
|
||||
resp = _sim_eval(dev, cmd)
|
||||
print(f"eval: {cmd} => {resp}")
|
||||
if 'Traceback' in resp:
|
||||
raise RuntimeError(resp)
|
||||
@ -1066,7 +1073,7 @@ def mk4_repl(sim_eval, sim_exec):
|
||||
|
||||
def exec(self, cmd, proc_time=1, raw=False):
|
||||
# send a (one line) command and read the one-line response
|
||||
resp = sim_exec(cmd)
|
||||
resp = _sim_exec(dev, cmd)
|
||||
print(f"exec: {cmd} => {resp}")
|
||||
if raw: return resp
|
||||
return eval(resp) if resp else None
|
||||
@ -1167,7 +1174,7 @@ def old_mk_repl(dev=None):
|
||||
|
||||
return USBRepl()
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def decode_with_bitcoind(bitcoind):
|
||||
|
||||
def doit(raw_txn):
|
||||
@ -1181,7 +1188,7 @@ def decode_with_bitcoind(bitcoind):
|
||||
|
||||
return doit
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def decode_psbt_with_bitcoind(bitcoind):
|
||||
|
||||
def doit(raw_psbt):
|
||||
@ -1196,7 +1203,7 @@ def decode_psbt_with_bitcoind(bitcoind):
|
||||
|
||||
return doit
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def check_against_bitcoind(bitcoind, use_regtest, sim_exec, sim_execfile):
|
||||
|
||||
def doit(hex_txn, fee, num_warn=0, change_outs=None, dests=[]):
|
||||
@ -1577,7 +1584,7 @@ def has_qwerty(is_q1):
|
||||
return is_q1
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def rf_interface(needs_nfc, sim_exec):
|
||||
def rf_interface(needs_nfc, dev):
|
||||
# provide a read/write connection over NFC
|
||||
# - requires pyscard module and desktop NFC-V reader which doesn't exist
|
||||
raise pytest.xfail('broken NFC-V challenges')
|
||||
@ -1629,15 +1636,15 @@ def rf_interface(needs_nfc, sim_exec):
|
||||
pass
|
||||
|
||||
# get the CC into NFC tap mode (but no UX)
|
||||
sim_exec('glob.NFC.set_rf_disable(0)')
|
||||
_sim_exec(dev, 'glob.NFC.set_rf_disable(0)')
|
||||
|
||||
time.sleep(3)
|
||||
|
||||
yield RFHandler()
|
||||
|
||||
sim_exec('glob.NFC.set_rf_disable(1)')
|
||||
_sim_exec(dev, 'glob.NFC.set_rf_disable(1)')
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def nfc_read(request, needs_nfc):
|
||||
# READ data from NFC chip
|
||||
# - perfer to do over NFC reader, but can work over USB too
|
||||
@ -1655,7 +1662,7 @@ def nfc_read(request, needs_nfc):
|
||||
except:
|
||||
return doit_usb
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def nfc_read_url(nfc_read, press_cancel):
|
||||
# gives URL from ndef
|
||||
|
||||
@ -1673,7 +1680,7 @@ def nfc_read_url(nfc_read, press_cancel):
|
||||
|
||||
return doit
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def nfc_write(request, needs_nfc, is_q1):
|
||||
# WRITE data into NFC "chip"
|
||||
def doit_usb(ccfile):
|
||||
@ -1690,26 +1697,26 @@ def nfc_write(request, needs_nfc, is_q1):
|
||||
except:
|
||||
return doit_usb
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def enable_nfc(needs_nfc, sim_exec, settings_set):
|
||||
def doit():
|
||||
settings_set('nfc', 1)
|
||||
sim_exec('import nfc; nfc.NFCHandler.startup()')
|
||||
return doit
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def nfc_disabled(settings_get):
|
||||
def doit():
|
||||
return not bool(settings_get('nfc', 0))
|
||||
return doit
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def vdisk_disabled(settings_get):
|
||||
def doit():
|
||||
return not bool(settings_get('vidsk', 0))
|
||||
return doit
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def scan_a_qr(sim_exec, is_q1):
|
||||
# simulate a QR being scanned
|
||||
# XXX limitation: our USB protocol can't send a v40 QR, limit is more like 30 or so
|
||||
@ -1742,14 +1749,14 @@ def ccfile_wrap(recs):
|
||||
|
||||
return rv
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def nfc_write_text(nfc_write):
|
||||
def doit(text):
|
||||
msg = b''.join(ndef.message_encoder([ndef.TextRecord(text), ]))
|
||||
return nfc_write(ccfile_wrap(msg))
|
||||
return doit
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def nfc_read_json(nfc_read):
|
||||
def doit():
|
||||
import json
|
||||
@ -1761,7 +1768,7 @@ def nfc_read_json(nfc_read):
|
||||
|
||||
return doit
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def nfc_read_text(nfc_read):
|
||||
def doit():
|
||||
got = list(ndef.message_decoder(nfc_read()))
|
||||
@ -1771,7 +1778,7 @@ def nfc_read_text(nfc_read):
|
||||
return got.text
|
||||
return doit
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def nfc_read_txn(nfc_read, press_select):
|
||||
def doit(txid=None, contents=None):
|
||||
if contents is None:
|
||||
@ -1807,7 +1814,7 @@ def nfc_read_txn(nfc_read, press_select):
|
||||
return doit
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def nfc_block4rf(sim_eval):
|
||||
# wait until RF is enabled and something to read (doesn't read it tho)
|
||||
def doit(timeout=15):
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
|
||||
SIM_PATH = '/tmp/ckcc-simulator.sock'
|
||||
|
||||
# Simulator normally powers up with this 'wallet'
|
||||
simulator_fixed_tprv = "tprv8ZgxMBicQKsPeXJHL3vPPgTAEqQ5P2FD9qDeCQT4Cp1EMY5QkwMPWFxHdxHrxZhhcVRJ2m7BNWTz9Xre68y7mX5vCdMJ5qXMUfnrZ2si2X4"
|
||||
simulator_fixed_tpub = "tpubD6NzVbkrYhZ4XzL5Dhayo67Gorv1YMS7j8pRUvVMd5odC2LBPLAygka9p7748JtSq82FNGPppFEz5xxZUdasBRCqJqXvUHq6xpnsMcYJzeh"
|
||||
|
||||
@ -17,6 +17,11 @@ def _sim_exec(device, cmd, binary=False, timeout=60000):
|
||||
# print(f'sim_exec: {cmd!r} -> {s!r}')
|
||||
return s.decode('utf-8') if not isinstance(s, str) else s
|
||||
|
||||
def _sim_eval(device, cmd, binary=False, timeout=None):
|
||||
s = device.send_recv(b'EVAL' + cmd.encode('utf-8'), timeout=timeout)
|
||||
if binary: return s
|
||||
return s.decode('utf-8')
|
||||
|
||||
def _cap_story(device):
|
||||
cmd = "RV.write('\0'.join(sim_display.story or []))"
|
||||
rv = _sim_exec(device, cmd)
|
||||
|
||||
5
testing/debug/.gitignore
vendored
5
testing/debug/.gitignore
vendored
@ -1,5 +0,0 @@
|
||||
*.psbt
|
||||
*.txn
|
||||
*.txt
|
||||
*.json
|
||||
*.png
|
||||
@ -34,7 +34,7 @@ for fn in ['had_witness', 'num_inputs', 'num_outputs',
|
||||
if 'destinations' in expect:
|
||||
for (val, addr), (idx, txo) in zip(expect['destinations'], p.output_iter()):
|
||||
assert val == txo.nValue
|
||||
txt = active_request.render_output(txo)
|
||||
txt, _ = active_request.render_output(txo)
|
||||
# normalize from display format
|
||||
address = txt.split("\n")[-2]
|
||||
assert address[0] == "\x02"
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
import pytest, time, pdb
|
||||
from core_fixtures import _pick_menu_item, _cap_menu, _cap_story, _cap_screen
|
||||
from core_fixtures import _need_keypress, _enter_complex, _press_select
|
||||
from ckcc_protocol.client import ColdcardDevice, CKCC_SIMULATOR_PATH
|
||||
from ckcc_protocol.client import ColdcardDevice
|
||||
from run_sim_tests import ColdcardSimulator, clean_sim_data
|
||||
|
||||
|
||||
@ -138,7 +138,7 @@ def test_set_nickname(nick, request):
|
||||
clean_sim_data() # remove all from previous
|
||||
sim = ColdcardSimulator(args=["--q1"] if is_Q else [])
|
||||
sim.start(start_wait=6)
|
||||
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
|
||||
device = ColdcardDevice(is_simulator=True)
|
||||
|
||||
_pick_menu_item(device, is_Q, "Settings")
|
||||
_pick_menu_item(device, is_Q, "Login Settings")
|
||||
@ -150,7 +150,7 @@ def test_set_nickname(nick, request):
|
||||
sim = ColdcardSimulator(args= ["--q1" if is_Q else "", "--early-usb"])
|
||||
|
||||
sim.start(start_wait=6)
|
||||
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
|
||||
device = ColdcardDevice(is_simulator=True)
|
||||
|
||||
scr = _cap_screen(device)
|
||||
target = "".join(scr.strip().split("\n"))
|
||||
@ -167,7 +167,7 @@ def test_randomize_pin_keys(request):
|
||||
clean_sim_data() # remove all from previous
|
||||
sim = ColdcardSimulator(args=["--q1"] if is_Q else [])
|
||||
sim.start(start_wait=6)
|
||||
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
|
||||
device = ColdcardDevice(is_simulator=True)
|
||||
|
||||
_pick_menu_item(device, is_Q, "Settings")
|
||||
_pick_menu_item(device, is_Q, "Login Settings")
|
||||
@ -177,7 +177,7 @@ def test_randomize_pin_keys(request):
|
||||
sim.stop() # power off
|
||||
sim = ColdcardSimulator(args=["--q1" if is_Q else "", "--pin", "22-22", "--early-usb"])
|
||||
sim.start(start_wait=6)
|
||||
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
|
||||
device = ColdcardDevice(is_simulator=True)
|
||||
_login(device, is_Q, "22-22", scrambled=True)
|
||||
time.sleep(3)
|
||||
m = _cap_menu(device)
|
||||
@ -190,7 +190,7 @@ def test_login_countdown(lcdwn, request):
|
||||
clean_sim_data() # remove all from previous
|
||||
sim = ColdcardSimulator(args=["--q1"] if is_Q else [])
|
||||
sim.start(start_wait=6)
|
||||
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
|
||||
device = ColdcardDevice(is_simulator=True)
|
||||
|
||||
_pick_menu_item(device, is_Q, "Settings")
|
||||
_pick_menu_item(device, is_Q, "Login Settings")
|
||||
@ -200,7 +200,7 @@ def test_login_countdown(lcdwn, request):
|
||||
sim.stop() # power off
|
||||
sim = ColdcardSimulator(args=["--q1" if is_Q else "", "--pin", "22-22", "--early-usb"])
|
||||
sim.start(start_wait=6)
|
||||
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
|
||||
device = ColdcardDevice(is_simulator=True)
|
||||
secs = int(lcdwn.strip().split()[0])
|
||||
_login(device, is_Q, "22-22")
|
||||
time.sleep(.15)
|
||||
@ -223,7 +223,7 @@ def test_kill_key(kbtn, when, request):
|
||||
clean_sim_data() # remove all from previous
|
||||
sim = ColdcardSimulator(args=["--q1"] if is_Q else [])
|
||||
sim.start(start_wait=6)
|
||||
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
|
||||
device = ColdcardDevice(is_simulator=True)
|
||||
|
||||
_pick_menu_item(device, is_Q, "Settings")
|
||||
_pick_menu_item(device, is_Q, "Login Settings")
|
||||
@ -233,7 +233,7 @@ def test_kill_key(kbtn, when, request):
|
||||
sim.stop() # power off
|
||||
sim = ColdcardSimulator(args= ["--q1" if is_Q else "", "--pin", "22-22", "--early-usb"])
|
||||
sim.start(start_wait=6)
|
||||
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
|
||||
device = ColdcardDevice(is_simulator=True)
|
||||
|
||||
if is_Q:
|
||||
possible_kbtn = [chr(65 + i) for i in range(26)] + [i for i in '\',./']
|
||||
@ -278,7 +278,7 @@ def test_terms_ok(request):
|
||||
clean_sim_data() # remove all from previous
|
||||
sim = ColdcardSimulator(args=["--early-usb", "-w", "--q1" if is_Q else ""])
|
||||
sim.start(start_wait=6)
|
||||
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
|
||||
device = ColdcardDevice(is_simulator=True)
|
||||
time.sleep(.1)
|
||||
|
||||
_, story = _cap_story(device)
|
||||
@ -317,7 +317,7 @@ def test_terms_ok(request):
|
||||
sim.stop() # power off
|
||||
sim = ColdcardSimulator(args=["-l", "--q1" if is_Q else "", "--early-usb", "--pin", "22-22"])
|
||||
sim.start(start_wait=6)
|
||||
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
|
||||
device = ColdcardDevice(is_simulator=True)
|
||||
_login(device, is_Q, "22-22")
|
||||
time.sleep(3)
|
||||
m = _cap_menu(device)
|
||||
@ -331,7 +331,7 @@ def test_wrong_pin_input(request, brick):
|
||||
clean_sim_data() # remove all from previous
|
||||
sim = ColdcardSimulator(args=["--early-usb", "--q1" if is_Q else "", "--pin", "22-22"])
|
||||
sim.start(start_wait=6)
|
||||
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
|
||||
device = ColdcardDevice(is_simulator=True)
|
||||
time.sleep(.1)
|
||||
num_attmeptss = 13
|
||||
for ii, i in enumerate(range(31, 43), start=1):
|
||||
@ -394,7 +394,7 @@ def test_login_integration(request, nick, randomize, login_ctdwn, kill_btn, kill
|
||||
clean_sim_data() # remove all from previous
|
||||
sim = ColdcardSimulator(args=["--q1"] if is_Q else [])
|
||||
sim.start(start_wait=6)
|
||||
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
|
||||
device = ColdcardDevice(is_simulator=True)
|
||||
_pick_menu_item(device, is_Q, "Settings")
|
||||
_pick_menu_item(device, is_Q, "Login Settings")
|
||||
|
||||
@ -416,7 +416,7 @@ def test_login_integration(request, nick, randomize, login_ctdwn, kill_btn, kill
|
||||
sim.stop() # power off
|
||||
sim = ColdcardSimulator(args=["--q1" if is_Q else "", "--pin", "22-22", "--early-usb"])
|
||||
sim.start(start_wait=6)
|
||||
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
|
||||
device = ColdcardDevice(is_simulator=True)
|
||||
|
||||
if nick:
|
||||
scr = _cap_screen(device)
|
||||
@ -484,7 +484,7 @@ def test_calc_login(request):
|
||||
clean_sim_data() # remove all from previous
|
||||
sim = ColdcardSimulator(args=["--q1"])
|
||||
sim.start(start_wait=6)
|
||||
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
|
||||
device = ColdcardDevice(is_simulator=True)
|
||||
|
||||
_pick_menu_item(device, is_Q, "Settings")
|
||||
_pick_menu_item(device, is_Q, "Login Settings")
|
||||
@ -494,7 +494,7 @@ def test_calc_login(request):
|
||||
sim.stop() # power off
|
||||
sim = ColdcardSimulator(args=["--q1", "--pin", "22-22", "--early-usb"])
|
||||
sim.start(start_wait=6)
|
||||
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
|
||||
device = ColdcardDevice(is_simulator=True)
|
||||
scr = _cap_screen(device)
|
||||
assert 'ECC Calculator' in scr
|
||||
|
||||
|
||||
@ -30,16 +30,82 @@ python run_sim_tests.py --collect veryslow # just print all
|
||||
python run_sim_tests.py --collect manual # just print all manual tests to stdout
|
||||
|
||||
Make sure to run manual test if you want to state that your changes passed all the tests.
|
||||
|
||||
Testing on multiple simulators in parallel
|
||||
|
||||
python run_sim_tests.py --q1 --multiproc # to run all Q tests in parallel (default num-proc=14 simulators)
|
||||
python run_sim_tests.py --multiproc --num-proc 6 # to run all Mk4 tests in parallel max 6 simulators at once
|
||||
python run_sim_tests.py -m test_addr.py -m test_bbqr.py --multiproc # just desired test
|
||||
python run_sim_tests.py --q1 -m test_sign.py --multiproc # just desired test
|
||||
python run_sim_tests --multiproc --turbo # turbo causes both Mk4 & Q tests to run simultaneously (turbo doubles num-procs)
|
||||
python run_sim_tests --multiproc --turbo # all Mk4 & Q tests run in 60 minutes total!!
|
||||
python run_sim_tests --multiproc --turbo -m test_addr.py -m test_ux.py # will spawn 4 simulators: one Q and one Mk4 for address tests & one Q and one Mk4 for ux tests
|
||||
|
||||
Console output has some useful info:
|
||||
* when job is started it will print its PID
|
||||
* when job is done you'll get elapsed time from start (test duration)
|
||||
* when all is done - complete test session duration
|
||||
|
||||
```
|
||||
$ python run_sim_tests.py -m test_addr.py -m test_drv_entro.py -m test_usb.py --multiproc --turbo
|
||||
started: Mk4 test_addr.py 38824
|
||||
started: Q test_addr.py 38935
|
||||
started: Mk4 test_drv_entro.py 39042
|
||||
started: Q test_drv_entro.py 39150
|
||||
started: Mk4 test_usb.py 39257
|
||||
started: Q test_usb.py 39364
|
||||
done: Mk4 test_usb.py 0:00:06.043072
|
||||
done: Q test_usb.py 0:00:06.081147
|
||||
done: Mk4 test_addr.py 0:00:51.141250
|
||||
done: Q test_addr.py 0:01:03.185571
|
||||
done: Mk4 test_drv_entro.py 0:03:24.234521
|
||||
done: Q test_drv_entro.py 0:03:30.278795
|
||||
|
||||
|
||||
elapsed: 0:03:50.308146
|
||||
```
|
||||
|
||||
After jobs are finished, or even during execution you can inspect `/tmp/cc-simulators` directory:
|
||||
* contains simulator work directories named as <PID> of specific simulator
|
||||
* log directories where pytest output is piped
|
||||
* mk4_logs
|
||||
* q1_logs
|
||||
|
||||
```
|
||||
$ pwd
|
||||
/tmp/cc-simulators
|
||||
$ ls
|
||||
38824 38935 39042 39150 39257 39364 mk4_logs q1_logs
|
||||
$ ls 39042/*
|
||||
39042/debug:
|
||||
last-qr.png
|
||||
|
||||
39042/MicroSD:
|
||||
drv-hex-idx0-2.txt drv-pw-idx0.txt drv-words-idx0-2.txt drv-words-idx0.txt
|
||||
drv-hex-idx0.txt drv-wif-idx0.txt drv-words-idx0-3.txt drv-xprv-idx0.txt
|
||||
|
||||
39042/settings:
|
||||
|
||||
39042/VirtDisk:
|
||||
README.md
|
||||
$ ls mk4_logs/
|
||||
test_addr.py.log test_drv_entro.py.log test_usb.py.log
|
||||
```
|
||||
|
||||
To parse only failures use below cmd in {mk4,q1}_logs directory:
|
||||
```
|
||||
for f in $(ls); do x=`grep -n "short test summary info" $f | grep -Eo '^[^:]+'`; if [ -n "$x" ];then tail -n +"$x" $f | grep -E '^FAILED|^ERROR';fi ;done
|
||||
```
|
||||
"""
|
||||
|
||||
import os, time, glob, json, pytest, atexit, signal, argparse, subprocess, contextlib
|
||||
import os, time, glob, json, pytest, atexit, signal, argparse, subprocess, contextlib, shutil
|
||||
from datetime import timedelta
|
||||
from typing import List
|
||||
|
||||
from pytest import ExitCode
|
||||
|
||||
|
||||
SIM_INIT_WAIT = 2 # 2 seconds, can be tweaked via cmdline arguments ( -w 6 )
|
||||
|
||||
DEFAULT_PYTEST_MARKS = "not onetime and not veryslow and not manual"
|
||||
|
||||
@contextlib.contextmanager
|
||||
def pushd(new_dir):
|
||||
@ -50,13 +116,17 @@ def pushd(new_dir):
|
||||
finally:
|
||||
os.chdir(previous_dir)
|
||||
|
||||
def clean_directory(pth):
|
||||
for root, dirs, files in os.walk(pth):
|
||||
for f in files:
|
||||
os.unlink(os.path.join(root, f))
|
||||
for d in dirs:
|
||||
shutil.rmtree(os.path.join(root, d))
|
||||
|
||||
def remove_client_sockets():
|
||||
def remove_all_client_sockets():
|
||||
with pushd("/tmp"):
|
||||
for fn in glob.glob("ckcc-client*.sock"):
|
||||
os.remove(fn)
|
||||
print("Removed all client sockets")
|
||||
|
||||
|
||||
def remove_cautious(fpath: str) -> None:
|
||||
if os.path.basename(fpath) in ["README.md", ".gitignore"]:
|
||||
@ -99,7 +169,7 @@ def is_ok(ec: ExitCode) -> bool:
|
||||
|
||||
|
||||
def _run_pytest_tests(test_module: str, pytest_marks: str, pytest_k: str, pdb: bool,
|
||||
failed_first: bool, psbt2=False, is_Q=False, headless=False) -> ExitCode:
|
||||
failed_first: bool, psbt2=False, is_Q=False, headless=False, sim_socket=None) -> ExitCode:
|
||||
cmd_list = [
|
||||
"--cache-clear", "-m", pytest_marks, "--sim",
|
||||
test_module if test_module is not None else ""
|
||||
@ -116,42 +186,50 @@ def _run_pytest_tests(test_module: str, pytest_marks: str, pytest_k: str, pdb: b
|
||||
cmd_list.insert(0, "--Q") # only changes behavior in login_settings_test
|
||||
if headless:
|
||||
cmd_list.append("--headless")
|
||||
if sim_socket:
|
||||
cmd_list.append("--sim-socket")
|
||||
cmd_list.append(sim_socket)
|
||||
|
||||
return pytest.main(cmd_list)
|
||||
|
||||
def _run_coldcard_tests(test_module: str, simulator_args: List[str], pytest_marks: str,
|
||||
def _run_coldcard_tests(test_module: str, simulator_args: List[str],
|
||||
pytest_k: str, pdb: bool, failed_first: bool, psbt2=False,
|
||||
is_Q=False, headless=False) -> ExitCode:
|
||||
is_Q=False, headless=False, pytest_marks: str = DEFAULT_PYTEST_MARKS,
|
||||
sim_segregate=False) -> ExitCode:
|
||||
sock_path = None
|
||||
if simulator_args is not None:
|
||||
sim = ColdcardSimulator(args=simulator_args, headless=headless)
|
||||
sim = ColdcardSimulator(args=simulator_args, headless=headless, segregate=sim_segregate)
|
||||
sim.start()
|
||||
time.sleep(1)
|
||||
sock_path = sim.socket
|
||||
|
||||
exit_code = _run_pytest_tests(test_module, pytest_marks, pytest_k, pdb,
|
||||
failed_first, psbt2, is_Q, headless)
|
||||
failed_first, psbt2, is_Q, headless, sock_path)
|
||||
|
||||
if simulator_args is not None:
|
||||
sim.stop()
|
||||
time.sleep(1)
|
||||
clean_sim_data()
|
||||
remove_all_client_sockets()
|
||||
|
||||
return exit_code
|
||||
|
||||
|
||||
def run_coldcard_tests(test_module=None, simulator_args=None, pytest_k=None, pdb=False,
|
||||
failed_first=False, psbt2=False, is_Q=False, headless=False,
|
||||
pytest_marks="not onetime and not veryslow and not manual"):
|
||||
pytest_marks=DEFAULT_PYTEST_MARKS):
|
||||
failed = []
|
||||
exit_code = _run_coldcard_tests(test_module, simulator_args, pytest_marks, pytest_k,
|
||||
pdb, failed_first, psbt2, is_Q, headless)
|
||||
exit_code = _run_coldcard_tests(test_module, simulator_args, pytest_k,
|
||||
pdb, failed_first, psbt2, is_Q, headless, pytest_marks)
|
||||
if not is_ok(exit_code):
|
||||
# no success, no nothing - give failed another try, each alone with its own simulator
|
||||
last_failed = get_last_failed()
|
||||
print("Running failed from last run", last_failed)
|
||||
exit_codes = []
|
||||
for failed_test in last_failed:
|
||||
exit_code_2 = _run_coldcard_tests(failed_test, simulator_args, pytest_marks,
|
||||
exit_code_2 = _run_coldcard_tests(failed_test, simulator_args,
|
||||
pytest_k, pdb, failed_first, psbt2, is_Q,
|
||||
headless)
|
||||
headless, pytest_marks)
|
||||
exit_codes.append(exit_code_2)
|
||||
if not is_ok(exit_code_2):
|
||||
failed.append(failed_test)
|
||||
@ -173,11 +251,12 @@ class PytestCollectMarked:
|
||||
|
||||
|
||||
class ColdcardSimulator:
|
||||
def __init__(self, path=None, args=None, headless=False):
|
||||
def __init__(self,args=None, headless=False, segregate=False):
|
||||
self.proc = None
|
||||
self.args = args
|
||||
self.path = "/tmp/ckcc-simulator.sock" if path is None else path
|
||||
self.headless = headless
|
||||
self.segregate = segregate
|
||||
self.socket = "/tmp/ckcc-simulator.sock"
|
||||
|
||||
def start(self, start_wait=None):
|
||||
# here we are in testing directory
|
||||
@ -188,14 +267,20 @@ class ColdcardSimulator:
|
||||
cmd_list.extend(self.args)
|
||||
if self.headless:
|
||||
cmd_list.append("--headless")
|
||||
if self.segregate:
|
||||
cmd_list.append("--segregate")
|
||||
|
||||
self.proc = subprocess.Popen(
|
||||
cmd_list,
|
||||
# this needs to be in firmware/unix - expected to be run from firmware/testing
|
||||
cwd="../unix",
|
||||
preexec_fn=os.setsid
|
||||
preexec_fn=os.setsid,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
time.sleep(start_wait or SIM_INIT_WAIT)
|
||||
if self.segregate:
|
||||
self.socket = "/tmp/ckcc-simulator-%d.sock" % self.proc.pid
|
||||
atexit.register(self.stop)
|
||||
|
||||
def stop(self):
|
||||
@ -205,7 +290,6 @@ class ColdcardSimulator:
|
||||
os.waitpid(os.getpgid(self.proc.pid), 0)
|
||||
|
||||
atexit.unregister(self.stop)
|
||||
remove_client_sockets()
|
||||
|
||||
|
||||
def main():
|
||||
@ -233,6 +317,12 @@ def main():
|
||||
help="only run tests which match the given substring expression")
|
||||
parser.add_argument("--headless", action="store_true", default=False,
|
||||
help="run simulator instance in headless mode")
|
||||
parser.add_argument("--multiproc", action="store_true", default=False,
|
||||
help="Run tests & simulators in parallel")
|
||||
parser.add_argument("--num-proc", type=int, default=14,
|
||||
help="How many executors/simulators to run in parallel in --multiproc mode")
|
||||
parser.add_argument("--turbo", action="store_true", default=False,
|
||||
help="Both Mk4 and Q at the same time")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.sim_init_wait:
|
||||
@ -258,47 +348,159 @@ def main():
|
||||
if args.module is None:
|
||||
test_modules = []
|
||||
elif len(args.module) == 1 and args.module[0].lower() == "all":
|
||||
test_modules = sorted(glob.glob("test_*.py"))
|
||||
test_modules = glob.glob("test_*.py")
|
||||
assert test_modules, "please run in ../testing subdir"
|
||||
else:
|
||||
for fn in args.module:
|
||||
if not os.path.exists(fn):
|
||||
raise RuntimeError(f"{fn} does not exist")
|
||||
test_modules = sorted(args.module)
|
||||
test_modules = args.module
|
||||
|
||||
result = []
|
||||
for test_module in test_modules:
|
||||
test_args = DEFAULT_SIMULATOR_ARGS
|
||||
if test_module in ["test_rng.py", "test_pincodes.py", "test_rolls.py"]:
|
||||
# test_pincodes.py can only be run against real device
|
||||
# test_rng.py not needed when using simulator
|
||||
# test_rolls.py should be run alone as it does not need simulator
|
||||
print("Skipped", test_module)
|
||||
continue
|
||||
# test_pincodes.py can only be run against real device
|
||||
# test_rng.py not needed when using simulator
|
||||
# test_rolls.py should be run alone as it does not need simulator
|
||||
# set diff
|
||||
test_modules = set(test_modules) - {"test_rng.py", "test_pincodes.py", "test_rolls.py"}
|
||||
|
||||
print("Started", test_module)
|
||||
module_args = []
|
||||
for test_module in sorted(list(test_modules)):
|
||||
sim_args = DEFAULT_SIMULATOR_ARGS
|
||||
if test_module in ["test_bsms.py", "test_address_explorer.py", "test_export.py",
|
||||
"test_multisig.py", "test_ux.py"]:
|
||||
test_args = DEFAULT_SIMULATOR_ARGS + ["--set", "vidsk=1"]
|
||||
sim_args = DEFAULT_SIMULATOR_ARGS + ["--set", "vidsk=1"]
|
||||
if test_module == "test_vdisk.py":
|
||||
test_args = ["--eject"] + DEFAULT_SIMULATOR_ARGS + ["--set", "vidsk=1"]
|
||||
sim_args = ["--eject"] + DEFAULT_SIMULATOR_ARGS + ["--set", "vidsk=1"]
|
||||
if test_module == "test_bip39pw.py":
|
||||
test_args = []
|
||||
sim_args = []
|
||||
if test_module in ["test_unit.py", "test_se2.py", "test_backup.py", "test_teleport.py"]:
|
||||
# test_nvram_mk4 needs to run without --eff
|
||||
# se2 duress wallet activated as ephemeral seed requires proper `settings.load`
|
||||
test_args = ["--set", "nfc=1"]
|
||||
sim_args = ["--set", "nfc=1"]
|
||||
if test_module in ["test_ephemeral.py", "test_notes.py", "test_ccc.py"]:
|
||||
# proper `settings.load` _ virtual disk
|
||||
test_args = ["--set", "nfc=1", "--set", "vidsk=1"]
|
||||
sim_args = ["--set", "nfc=1", "--set", "vidsk=1"]
|
||||
|
||||
if args.q1 and '--q1' not in test_args:
|
||||
test_args.append('--q1')
|
||||
if args.q1 and '--q1' not in sim_args:
|
||||
sim_args.append('--q1')
|
||||
|
||||
ec, failed_tests = run_coldcard_tests(test_module, simulator_args=test_args,
|
||||
pytest_k=args.pytest_k, pdb=args.pdb,
|
||||
failed_first=args.ff, psbt2=args.psbt2,
|
||||
headless=args.headless)
|
||||
module_args.append((test_module, sim_args, args.pytest_k, args.pdb,
|
||||
args.ff, args.psbt2, args.q1, args.headless))
|
||||
|
||||
if args.multiproc:
|
||||
start_time = time.time()
|
||||
def add_to_queue(module_name, simulator_args, queue):
|
||||
if module_name == "test_multisig.py":
|
||||
# split takes too much time
|
||||
queue.append((0, [module_name, simulator_args, "not tutorial and not airgapped and not ms_address and not descriptor_export", ""]))
|
||||
queue.append((0, [module_name, simulator_args, "airgapped", "-sep1"]))
|
||||
queue.append((0, [module_name, simulator_args, "tutorial", "-sep2"]))
|
||||
queue.append((0, [module_name, simulator_args, "ms_address", "-sep3"]))
|
||||
queue.append((0, [module_name, simulator_args, "descriptor_export", "-sep4"]))
|
||||
|
||||
elif module_name == "test_seed_xor.py":
|
||||
# split takes too much time
|
||||
queue.append((0, [module_name, simulator_args, "test_import_xor", "-sep1"]))
|
||||
queue.append((0, [module_name, simulator_args, "not test_import_xor", ""]))
|
||||
|
||||
elif module_name in ["test_export.py", "test_ephemeral.py", "test_sign.py", "test_msg.py",
|
||||
"test_backup.py"]:
|
||||
# higher priority
|
||||
queue.append((1, [module_name, simulator_args, None, ""]))
|
||||
|
||||
else:
|
||||
# standard priority
|
||||
queue.append((2, [module_name, simulator_args, None, ""]))
|
||||
|
||||
# will clear everything there from previous runs
|
||||
tmp_dir = "/tmp/cc-simulators"
|
||||
clean_directory(tmp_dir) # clean it
|
||||
mk4_log_dir = f"{tmp_dir}/mk4_logs"
|
||||
q1_log_dir = f"{tmp_dir}/q1_logs"
|
||||
os.makedirs(mk4_log_dir, exist_ok=True)
|
||||
os.makedirs(q1_log_dir, exist_ok=True)
|
||||
|
||||
q = [] # build priority queue
|
||||
for mod_name, sim_args, *_ in module_args:
|
||||
if args.turbo:
|
||||
if "--q1" in sim_args:
|
||||
add_to_queue(mod_name, sim_args, q)
|
||||
add_to_queue(mod_name, [i for i in sim_args if i == "--q1"], q)
|
||||
else:
|
||||
add_to_queue(mod_name, sim_args, q)
|
||||
add_to_queue(mod_name, sim_args + ["--q1"], q)
|
||||
|
||||
else:
|
||||
add_to_queue(mod_name, sim_args, q)
|
||||
|
||||
# sort queue by priority, highest priority elements at the end
|
||||
q = [i[1] for i in sorted(q, reverse=True)]
|
||||
|
||||
num_proc = args.num_proc
|
||||
if args.turbo:
|
||||
# double num-proc
|
||||
num_proc *= 2
|
||||
|
||||
procs = []
|
||||
while True:
|
||||
# create as many processes as allowed by --num-proc (default=14)
|
||||
if q and (len(procs) < num_proc):
|
||||
# start simulators first
|
||||
q_chunks = []
|
||||
for _ in range (num_proc - len(procs)):
|
||||
try:
|
||||
mn, sim_args, k, mod_add = q.pop() # remove element
|
||||
except IndexError:
|
||||
# priority queue is empty
|
||||
break
|
||||
sim = ColdcardSimulator(sim_args, segregate=True)
|
||||
sim.start(start_wait=0)
|
||||
ld = q1_log_dir if "--q1" in sim_args else mk4_log_dir
|
||||
q_chunks.append((sim, mn, mod_add, k, ld))
|
||||
|
||||
time.sleep(5)
|
||||
for sim, mn, mod_add, k, log_dir in q_chunks:
|
||||
assert sim.socket
|
||||
out_log_path = f"{log_dir}/%s.log" % (mn + mod_add)
|
||||
out_fd = open(out_log_path, "w")
|
||||
cmd_list = ["pytest", "--cache-clear", "-m", DEFAULT_PYTEST_MARKS, "--sim",
|
||||
mn, "--sim-socket", sim.socket]
|
||||
if k:
|
||||
cmd_list.extend(["-k", k])
|
||||
p = subprocess.Popen(cmd_list, preexec_fn=os.setsid, stdout=out_fd, stderr=out_fd)
|
||||
mark = "Q" if "q1" in log_dir else "Mk4"
|
||||
procs.append((mn+mod_add, p, out_fd, sim, mark, time.time()))
|
||||
print(f'started: {mark:<6}{mn+mod_add:<30}{sim.socket.split("-")[-1].split(".")[0]:<10}')
|
||||
|
||||
if not procs and not q:
|
||||
# done
|
||||
break
|
||||
|
||||
i = 0
|
||||
while i < len(procs):
|
||||
mn, p, out_fd, sim, mark, st = procs[i]
|
||||
if p.poll() is None:
|
||||
# still running
|
||||
i += 1
|
||||
continue
|
||||
else:
|
||||
# done
|
||||
p.communicate()
|
||||
out_fd.close()
|
||||
sim.stop()
|
||||
del procs[i]
|
||||
print(f"done: {mark:<6}{mn:<30}{str(timedelta(seconds=time.time()-st)):<15}")
|
||||
|
||||
time.sleep(3)
|
||||
|
||||
# multiprocess done
|
||||
print(f"\n\nelapsed: {str(timedelta(seconds=time.time()-start_time))}")
|
||||
return
|
||||
|
||||
result = []
|
||||
for arguments in module_args:
|
||||
test_module = arguments[0]
|
||||
print("Started", test_module)
|
||||
ec, failed_tests = run_coldcard_tests(*arguments)
|
||||
result.append((test_module, ec, failed_tests))
|
||||
print("Done", test_module)
|
||||
print(80 * "=")
|
||||
@ -363,5 +565,8 @@ def main():
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
# sim = ColdcardSimulator(args=["--eff", "--segregate"])
|
||||
# sim.start()
|
||||
# import pdb;pdb.set_trace()
|
||||
# x = 5
|
||||
# EOF
|
||||
|
||||
@ -3,9 +3,9 @@
|
||||
import pytest, pdb, time, random, os
|
||||
from charcodes import KEY_CANCEL
|
||||
from core_fixtures import _pick_menu_item, _press_select
|
||||
from core_fixtures import _need_keypress, _cap_screen, _sim_exec
|
||||
from core_fixtures import _need_keypress, _sim_exec
|
||||
from run_sim_tests import ColdcardSimulator, clean_sim_data
|
||||
from ckcc_protocol.client import ColdcardDevice, CKCC_SIMULATOR_PATH
|
||||
from ckcc_protocol.client import ColdcardDevice
|
||||
|
||||
|
||||
def test_status_bar_rewrite_after_restore_master(request):
|
||||
@ -13,7 +13,7 @@ def test_status_bar_rewrite_after_restore_master(request):
|
||||
clean_sim_data() # remove all from previous
|
||||
sim = ColdcardSimulator(args=["--q1", "-l"])
|
||||
sim.start(start_wait=3)
|
||||
device = ColdcardDevice(sn=CKCC_SIMULATOR_PATH)
|
||||
device = ColdcardDevice(is_simulator=True)
|
||||
|
||||
_pick_menu_item(device, True, "Advanced/Tools")
|
||||
_pick_menu_item(device, True, "Temporary Seed")
|
||||
|
||||
@ -570,13 +570,14 @@ def test_seed_vault_backup_frozen(reset_seed_words, settings_set, repl, build_te
|
||||
assert target in bk
|
||||
|
||||
|
||||
def test_clone_start(reset_seed_words, pick_menu_item, cap_story, goto_home):
|
||||
sd_dir = "../unix/work/MicroSD"
|
||||
def test_clone_start(reset_seed_words, pick_menu_item, cap_story, goto_home, src_root_dir,
|
||||
sim_root_dir):
|
||||
sd_dir = f"{sim_root_dir}/MicroSD"
|
||||
num_7z = len([i for i in os.listdir(sd_dir) if i.endswith(".7z")])
|
||||
fname = "ccbk-start.json"
|
||||
reset_seed_words()
|
||||
goto_home()
|
||||
shutil.copy(f"data/{fname}", sd_dir)
|
||||
shutil.copy(f"{src_root_dir}/testing/data/{fname}", sd_dir)
|
||||
pick_menu_item("Advanced/Tools")
|
||||
pick_menu_item("Backup")
|
||||
pick_menu_item("Clone Coldcard")
|
||||
@ -593,18 +594,23 @@ def test_clone_start(reset_seed_words, pick_menu_item, cap_story, goto_home):
|
||||
|
||||
def test_bkpw_override(reset_seed_words, override_bkpw, goto_home, pick_menu_item,
|
||||
cap_story, press_select, garbage_collector, microsd_path,
|
||||
restore_backup_cs):
|
||||
restore_backup_cs, is_q1):
|
||||
reset_seed_words() # clean slate
|
||||
old_pw = None
|
||||
test_cases = [
|
||||
"arm prob slot merc hub fiel wing aver tale undo diar boos army cabl mous teac drif risk frow achi poet ecol boss grit",
|
||||
" ".join(12 * ["elevator"]),
|
||||
" ".join(12 * ["fever"]),
|
||||
32 * "a",
|
||||
(16 * "0") + " " + (16 *"1"),
|
||||
64 * "Q",
|
||||
(26 * "?") + "!@#$%^&*()",
|
||||
]
|
||||
if is_q1:
|
||||
# not needed on mk4 - even tho works (takes too much time)
|
||||
# Mk4 display is not suitable for these type of passwords anyways
|
||||
test_cases += [
|
||||
"arm prob slot merc hub fiel wing aver tale undo diar boos army cabl mous teac drif risk frow achi poet ecol boss grit",
|
||||
" ".join(12 * ["elevator"]),
|
||||
" ".join(12 * ["fever"]),
|
||||
64 * "Q",
|
||||
]
|
||||
|
||||
fnames = []
|
||||
for pw in test_cases:
|
||||
override_bkpw(pw, old_pw)
|
||||
|
||||
@ -219,14 +219,14 @@ def test_show_bbqr_sizes(size, cap_screen_qr, sim_exec, render_bbqr):
|
||||
assert ft == 'U'
|
||||
|
||||
@pytest.mark.parametrize('src', [ 'rng', 'gpu', 'bigger'] )
|
||||
def test_show_bbqr_contents(src, cap_screen_qr, sim_exec, render_bbqr, load_shared_mod):
|
||||
def test_show_bbqr_contents(src, cap_screen_qr, sim_exec, render_bbqr, load_shared_mod, src_root_dir):
|
||||
|
||||
args = dict(msg=f'Test {src}', file_type='B')
|
||||
if src == 'rng':
|
||||
args['data'] = expect = prandom(500) # limited by simulated USB path
|
||||
elif src in { 'gpu', 'bigger' }:
|
||||
args['setup'] = 'from gpu_binary import BINARY'
|
||||
cc_gpu_bin = load_shared_mod('cc_gpu_bin', '../shared/gpu_binary.py')
|
||||
cc_gpu_bin = load_shared_mod('cc_gpu_bin', f'{src_root_dir}/shared/gpu_binary.py')
|
||||
if src == 'gpu':
|
||||
args['str_expr'] = 'BINARY'
|
||||
expect = cc_gpu_bin.BINARY
|
||||
@ -254,7 +254,7 @@ def test_bbqr_psbt(size, encoding, max_ver, partial, segwit, scan_a_qr, readback
|
||||
cap_screen_qr, render_bbqr, goto_home, use_regtest, cap_story,
|
||||
decode_psbt_with_bitcoind, decode_with_bitcoind, fake_txn, dev,
|
||||
start_sign, end_sign, press_cancel, press_select, need_keypress,
|
||||
base64str, try_sign_bbqr, signing_artifacts_reexport):
|
||||
base64str, try_sign_bbqr, signing_artifacts_reexport, sim_root_dir):
|
||||
|
||||
num_in = size
|
||||
num_out = size*10
|
||||
@ -274,7 +274,8 @@ def test_bbqr_psbt(size, encoding, max_ver, partial, segwit, scan_a_qr, readback
|
||||
if base64str:
|
||||
psbt = base64.b64encode(psbt).decode()
|
||||
|
||||
open('debug/last.psbt', 'w' if base64str else 'wb').write(psbt)
|
||||
with open(f'{sim_root_dir}/debug/last.psbt', 'w' if base64str else 'wb') as f:
|
||||
f.write(psbt)
|
||||
|
||||
_, file_type, rb = try_sign_bbqr(psbt, type_code="U" if base64str else "P",
|
||||
max_version=max_ver, encoding=encoding)
|
||||
|
||||
@ -50,7 +50,7 @@ def test_b9p_basic(pw, set_bip39_pw):
|
||||
set_bip39_pw(pw)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def set_bip39_pw(dev, need_keypress, reset_seed_words, cap_story,
|
||||
sim_execfile, press_select):
|
||||
|
||||
|
||||
@ -284,13 +284,13 @@ def setup_ccc(goto_home, pick_menu_item, cap_story, press_select, pass_word_quiz
|
||||
press_select()
|
||||
|
||||
pick_menu_item(vel_mi)
|
||||
pick_menu_item(vel) # actually a full menu item
|
||||
if vel == "Unlimited":
|
||||
target = 0
|
||||
else:
|
||||
target = int(vel.split()[0])
|
||||
|
||||
time.sleep(.2)
|
||||
pick_menu_item(vel) # actually a full menu item
|
||||
time.sleep(.3)
|
||||
assert settings_get("ccc")["pol"]["vel"] == target
|
||||
|
||||
if whitelist:
|
||||
@ -304,10 +304,14 @@ def setup_ccc(goto_home, pick_menu_item, cap_story, press_select, pass_word_quiz
|
||||
pick_menu_item("Scan QR")
|
||||
for i, addr in enumerate(whitelist, start=1):
|
||||
scan_a_qr(addr)
|
||||
time.sleep(.5)
|
||||
scr = cap_screen()
|
||||
assert f"Got {i} so far" in scr
|
||||
assert "ENTER to apply" in scr
|
||||
|
||||
for _ in range(10):
|
||||
scr = cap_screen()
|
||||
if (f"Got {i} so far" in scr) and ("ENTER to apply" in scr):
|
||||
break
|
||||
time.sleep(.2)
|
||||
else:
|
||||
assert False, "updating whitelist failed"
|
||||
|
||||
press_select()
|
||||
else:
|
||||
@ -385,7 +389,7 @@ def enter_enabled_ccc(goto_home, pick_menu_item, cap_story, press_select, is_q1,
|
||||
@pytest.fixture
|
||||
def ccc_ms_setup(microsd_path, virtdisk_path, scan_a_qr, is_q1, cap_menu, pick_menu_item,
|
||||
cap_story, press_select, need_keypress, enter_number, press_cancel,
|
||||
garbage_collector):
|
||||
garbage_collector, cap_screen):
|
||||
|
||||
def doit(N=3, b_words=12, way="sd", addr_fmt=AF_P2WSH, ftype="cc", bbqr=True):
|
||||
|
||||
@ -449,56 +453,72 @@ def ccc_ms_setup(microsd_path, virtdisk_path, scan_a_qr, is_q1, cap_menu, pick_m
|
||||
|
||||
time.sleep(.1)
|
||||
title, story = cap_story()
|
||||
if is_q1:
|
||||
assert title == "QR or SD Card?"
|
||||
if way in ("sd", "vdisk"):
|
||||
|
||||
if way in ("sd", "vdisk"):
|
||||
if is_q1:
|
||||
assert "ENTER to use SD card" in story
|
||||
press_select()
|
||||
|
||||
if addr_fmt == AF_P2WSH:
|
||||
press_select()
|
||||
else:
|
||||
need_keypress(KEY_QR)
|
||||
time.sleep(.1)
|
||||
title, story = cap_story()
|
||||
assert title == "Address Format"
|
||||
assert "Press ENTER for default address format (P2WSH" in story
|
||||
assert "press (1) for P2SH-P2WSH" in story
|
||||
if addr_fmt == AF_P2WSH:
|
||||
press_select()
|
||||
else:
|
||||
need_keypress("1")
|
||||
need_keypress("1")
|
||||
else:
|
||||
assert way == "qr"
|
||||
if not is_q1:
|
||||
raise pytest.skip("mk4 no qr")
|
||||
|
||||
for d, dd in res:
|
||||
if ftype == "cc":
|
||||
conts = json.dumps(dd)
|
||||
tc = "J"
|
||||
else:
|
||||
deriv = dd[f"{label}_deriv"].replace("m/", "")
|
||||
conts = f"[{dd['xfp']}/{deriv}]{dd[label]}"
|
||||
tc = "U"
|
||||
|
||||
if bbqr:
|
||||
_, parts = split_qrs(conts, tc, max_version=20)
|
||||
for p in parts:
|
||||
scan_a_qr(p)
|
||||
time.sleep(.1)
|
||||
else:
|
||||
scan_a_qr(conts)
|
||||
time.sleep(.1)
|
||||
|
||||
time.sleep(.5)
|
||||
|
||||
press_cancel() # after we're done scanning keys, exit QR animation to proceed
|
||||
|
||||
# casual on-device multisig create
|
||||
if way != "qr":
|
||||
assert title == "QR or SD Card?"
|
||||
need_keypress(KEY_QR)
|
||||
time.sleep(.1)
|
||||
title, story = cap_story()
|
||||
assert title == "Address Format"
|
||||
assert "Press ENTER for default address format (P2WSH" in story
|
||||
assert "press (1) for P2SH-P2WSH" in story
|
||||
if addr_fmt == AF_P2WSH:
|
||||
press_select()
|
||||
else:
|
||||
need_keypress("1")
|
||||
|
||||
for i, (d, dd) in enumerate(res, start=1):
|
||||
if ftype == "cc":
|
||||
conts = json.dumps(dd)
|
||||
tc = "J"
|
||||
else:
|
||||
deriv = dd[f"{label}_deriv"].replace("m/", "")
|
||||
conts = f"[{dd['xfp']}/{deriv}]{dd[label]}"
|
||||
tc = "U"
|
||||
|
||||
if bbqr:
|
||||
_, parts = split_qrs(conts, tc, max_version=20)
|
||||
for p in parts:
|
||||
scan_a_qr(p)
|
||||
time.sleep(.25)
|
||||
else:
|
||||
scan_a_qr(conts)
|
||||
|
||||
for _ in range(10):
|
||||
time.sleep(.2)
|
||||
scr = cap_screen()
|
||||
if ("Number of keys scanned: %d" % i) in scr:
|
||||
break
|
||||
else:
|
||||
assert False, f"failed to scan ms xpubs ({i})"
|
||||
|
||||
press_cancel() # after we're done scanning keys, exit QR animation to proceed
|
||||
|
||||
time.sleep(.1)
|
||||
# CCC C key account number
|
||||
enter_number("0")
|
||||
time.sleep(.1)
|
||||
title, story = cap_story()
|
||||
assert "Create new multisig wallet" in story
|
||||
for _ in range(5):
|
||||
time.sleep(.1)
|
||||
title, story = cap_story()
|
||||
if "Create new multisig wallet" in story:
|
||||
break
|
||||
else:
|
||||
press_cancel()
|
||||
assert False, "failed to create ms wallet"
|
||||
|
||||
assert f"Policy: 2 of {N}" in story
|
||||
if is_q1:
|
||||
assert "Coldcard Co-sign" in story
|
||||
|
||||
@ -122,7 +122,7 @@ def enable_hsm_commands(dev, sim_exec, only_mk4):
|
||||
sim_exec(cmd)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
@pytest.fixture
|
||||
def hsm_reset(dev, sim_exec):
|
||||
# filename for the policy file, as stored on simulated CC
|
||||
|
||||
@ -476,10 +476,11 @@ def wait_til_signed(dev):
|
||||
return result
|
||||
|
||||
@pytest.fixture
|
||||
def attempt_psbt(hsm_status, start_sign, dev):
|
||||
def attempt_psbt(hsm_status, start_sign, dev, sim_root_dir):
|
||||
|
||||
def doit(psbt, refuse=None, remote_error=None):
|
||||
open('debug/attempt.psbt', 'wb').write(psbt)
|
||||
with open(f'{sim_root_dir}/debug/attempt.psbt', 'wb') as f:
|
||||
f.write(psbt)
|
||||
start_sign(psbt)
|
||||
|
||||
try:
|
||||
@ -780,7 +781,7 @@ def test_multiple_signings(dev, quick_start_hsm, is_simulator,
|
||||
def test_multiple_signings_multisig(cc_first, M_N, dev, quick_start_hsm,
|
||||
is_simulator, attempt_psbt, fake_txn,
|
||||
load_hsm_users, auth_user, bitcoind,
|
||||
request):
|
||||
request, sim_root_dir):
|
||||
# signs 400 different PSBTs in loop beaing one leg of multisig
|
||||
# CC must be on regtest if testing with real thing
|
||||
af = "bech32"
|
||||
@ -850,7 +851,9 @@ def test_multiple_signings_multisig(cc_first, M_N, dev, quick_start_hsm,
|
||||
|
||||
# uploading only external to CC
|
||||
file_len, sha = dev.upload_file(desc_ext.encode('ascii'))
|
||||
open('debug/last-config.txt', 'wt').write(desc_ext)
|
||||
with open(f'{sim_root_dir}/debug/last-config.txt', 'wt') as f:
|
||||
f.write(desc_ext)
|
||||
|
||||
dev.send_recv(CCProtocolPacker.multisig_enroll(file_len, sha), timeout=30000)
|
||||
|
||||
time.sleep(.2)
|
||||
|
||||
@ -45,7 +45,7 @@ def str2ipath(s):
|
||||
|
||||
yield here
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
@pytest.fixture
|
||||
def has_ms_checks(request, sim_exec):
|
||||
# Add this fixture to any test that should FAIL if ms checks are disabled
|
||||
# - in other words, tests that test the checks which are disabled.
|
||||
@ -64,7 +64,7 @@ def has_ms_checks(request, sim_exec):
|
||||
return danger_mode
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def bitcoind_p2sh(bitcoind):
|
||||
# Use bitcoind to generate a p2sh addres based on public keys.
|
||||
|
||||
@ -142,12 +142,13 @@ def make_multisig(dev, sim_execfile):
|
||||
return doit
|
||||
|
||||
@pytest.fixture
|
||||
def offer_ms_import(cap_story, dev):
|
||||
def offer_ms_import(cap_story, dev, sim_root_dir):
|
||||
def doit(config, allow_non_ascii=False):
|
||||
# upload the file, trigger import
|
||||
file_len, sha = dev.upload_file(config.encode('utf-8' if allow_non_ascii else 'ascii'))
|
||||
|
||||
open('debug/last-config.txt', 'wt').write(config)
|
||||
with open(f'{sim_root_dir}/debug/last-config.txt', 'wt') as f:
|
||||
f.write(config)
|
||||
|
||||
dev.send_recv(CCProtocolPacker.multisig_enroll(file_len, sha))
|
||||
|
||||
@ -249,12 +250,12 @@ def import_multisig(request, is_q1, need_keypress, offer_ms_import):
|
||||
@pytest.fixture
|
||||
def import_ms_wallet(dev, make_multisig, offer_ms_import, press_select,
|
||||
is_q1, request, need_keypress, import_multisig,
|
||||
settings_set):
|
||||
settings_set, sim_root_dir):
|
||||
|
||||
def doit(M, N, addr_fmt=None, name=None, unique=0, accept=False, common=None,
|
||||
keys=None, do_import=True, derivs=None, descriptor=False,
|
||||
int_ext_desc=False, dev_key=False, way=None, bip67=True,
|
||||
force_unsort_ms=True, netcode="XTN"):
|
||||
force_unsort_ms=True, netcode="XTN", return_desc=False):
|
||||
# param: bip67 if false, only usable together with descriptor=True
|
||||
if not bip67:
|
||||
assert descriptor, "needs descriptor=True"
|
||||
@ -308,7 +309,8 @@ def import_ms_wallet(dev, make_multisig, offer_ms_import, press_select,
|
||||
xfp2str(xfp), dd.hwif(as_private=False))
|
||||
|
||||
#print(config)
|
||||
open('debug/last-ms.txt', 'wt').write(config)
|
||||
with open(f'{sim_root_dir}/debug/last-ms.txt', 'wt') as f:
|
||||
f.write(config)
|
||||
|
||||
title, story = import_multisig(data=config, way=way)
|
||||
|
||||
@ -331,6 +333,9 @@ def import_ms_wallet(dev, make_multisig, offer_ms_import, press_select,
|
||||
xor ^= xfp
|
||||
assert dev.send_recv(CCProtocolPacker.multisig_check(M, N, xor)) == 1
|
||||
|
||||
if return_desc and descriptor:
|
||||
return config
|
||||
|
||||
return keys
|
||||
|
||||
return doit
|
||||
@ -560,7 +565,7 @@ def test_import_ranges(m_of_n, use_regtest, addr_fmt, clear_ms, import_ms_wallet
|
||||
@pytest.mark.ms_danger
|
||||
def test_violate_bip67(clear_ms, use_regtest, import_ms_wallet,
|
||||
test_ms_show_addr, has_ms_checks,
|
||||
fake_ms_txn, try_sign):
|
||||
fake_ms_txn, try_sign, sim_root_dir):
|
||||
# detect when pubkeys are not in order in the redeem script
|
||||
clear_ms()
|
||||
M, N = 1, 15
|
||||
@ -578,7 +583,7 @@ def test_violate_bip67(clear_ms, use_regtest, import_ms_wallet,
|
||||
change_outputs=[1],
|
||||
violate_script_key_order=True)
|
||||
|
||||
with open('debug/last.psbt', 'wb') as f:
|
||||
with open(f'{sim_root_dir}/debug/last.psbt', 'wb') as f:
|
||||
f.write(psbt)
|
||||
|
||||
with pytest.raises(Exception) as e:
|
||||
@ -588,7 +593,8 @@ def test_violate_bip67(clear_ms, use_regtest, import_ms_wallet,
|
||||
|
||||
@pytest.mark.parametrize("has_change", [True, False])
|
||||
def test_violate_import_order_multi(has_change, clear_ms, import_ms_wallet,
|
||||
fake_ms_txn, try_sign, test_ms_show_addr):
|
||||
fake_ms_txn, try_sign, test_ms_show_addr,
|
||||
sim_root_dir):
|
||||
clear_ms()
|
||||
M, N = 3, 5
|
||||
keys = import_ms_wallet(M, N, accept=True, descriptor=True, bip67=False)
|
||||
@ -601,7 +607,7 @@ def test_violate_import_order_multi(has_change, clear_ms, import_ms_wallet,
|
||||
change_outputs=[1] if has_change else [],
|
||||
bip67=False, violate_script_key_order=True)
|
||||
|
||||
with open('debug/last.psbt', 'wb') as f:
|
||||
with open(f'{sim_root_dir}/debug/last.psbt', 'wb') as f:
|
||||
f.write(psbt)
|
||||
|
||||
with pytest.raises(Exception) as e:
|
||||
@ -1167,8 +1173,6 @@ def test_import_dup_xfp_fails(m_of_n, use_regtest, addr_fmt, clear_ms,
|
||||
@pytest.mark.parametrize('desc', ["multi", "sortedmulti"])
|
||||
def test_ms_cli(dev, addr_fmt, clear_ms, import_ms_wallet, addr_vs_path, desc):
|
||||
# exercise the p2sh command of ckcc:cli ... hard to do manually.
|
||||
from subprocess import check_output
|
||||
|
||||
M, N = 2, 3
|
||||
clear_ms()
|
||||
bip67, descriptor = (False, True) if desc == "multi" else (True, False)
|
||||
@ -1180,41 +1184,17 @@ def test_ms_cli(dev, addr_fmt, clear_ms, import_ms_wallet, addr_vs_path, desc):
|
||||
|
||||
scr, pubkeys, xfp_paths = make_redeem(M, keys, pmapper, bip67=bip67)
|
||||
|
||||
def decode_path(p):
|
||||
return '/'.join(str(i) if i < 0x80000000 else "%d'"%(i& 0x7fffffff) for i in p)
|
||||
addr = dev.send_recv(CCProtocolPacker.show_p2sh_address(
|
||||
scr[0] - 80, xfp_paths, scr, addr_fmt=addr_fmt), timeout=None
|
||||
)
|
||||
|
||||
if 1:
|
||||
args = ['ckcc']
|
||||
if dev.is_simulator:
|
||||
args += ['-x']
|
||||
addr_vs_path(addr, addr_fmt=addr_fmt, script=scr)
|
||||
|
||||
args += ['p2sh', '-q']
|
||||
|
||||
if addr_fmt == AF_P2WSH:
|
||||
args += ['-s']
|
||||
elif addr_fmt == AF_P2WSH_P2SH:
|
||||
args += ['-s', '-w']
|
||||
|
||||
args += [B2A(scr)]
|
||||
args += [xfp2str(x)+'/'+decode_path(path) for x,*path in xfp_paths]
|
||||
|
||||
import shlex
|
||||
print('CMD: ' + (' '.join(shlex.quote(i) for i in args)))
|
||||
|
||||
addr = check_output(args, encoding='ascii').strip()
|
||||
|
||||
print(addr)
|
||||
addr_vs_path(addr, addr_fmt=addr_fmt, script=scr)
|
||||
|
||||
# test case for make_ms_address really.
|
||||
expect_addr, _, scr2, _ = make_ms_address(M, keys, path_mapper=pmapper,
|
||||
addr_fmt=addr_fmt, bip67=bip67)
|
||||
assert expect_addr == addr
|
||||
assert scr2 == scr
|
||||
|
||||
# need to re-start our connection once ckcc has talked to simulator
|
||||
dev.start_encryption()
|
||||
dev.check_mitm()
|
||||
# test case for make_ms_address really.
|
||||
expect_addr, _, scr2, _ = make_ms_address(M, keys, path_mapper=pmapper,
|
||||
addr_fmt=addr_fmt, bip67=bip67)
|
||||
assert expect_addr == addr
|
||||
assert scr2 == scr
|
||||
|
||||
clear_ms()
|
||||
|
||||
@ -1446,7 +1426,7 @@ def fake_ms_txn(pytestconfig):
|
||||
@pytest.mark.parametrize('desc', ["multi", "sortedmulti"])
|
||||
def test_ms_sign_simple(M_N, num_ins, dev, addr_fmt, clear_ms, incl_xpubs, import_ms_wallet,
|
||||
addr_vs_path, fake_ms_txn, try_sign, try_sign_microsd, transport,
|
||||
has_change, settings_set, desc):
|
||||
has_change, settings_set, desc, sim_root_dir):
|
||||
M, N = M_N
|
||||
num_outs = num_ins-1
|
||||
descriptor, bip67 = (True, False) if desc == "multi" else (False, True)
|
||||
@ -1470,7 +1450,8 @@ def test_ms_sign_simple(M_N, num_ins, dev, addr_fmt, clear_ms, incl_xpubs, impor
|
||||
outstyles=ADDR_STYLES_MS, change_outputs=[1] if has_change else [],
|
||||
bip67=bip67)
|
||||
|
||||
open('debug/last.psbt', 'wb').write(psbt)
|
||||
with open(f'{sim_root_dir}/debug/last.psbt', 'wb') as f:
|
||||
f.write(psbt)
|
||||
|
||||
if transport == 'sd':
|
||||
try_sign_microsd(psbt, encoding=('binary', 'hex', 'base64')[random.randint(0,2)])
|
||||
@ -1484,7 +1465,7 @@ def test_ms_sign_simple(M_N, num_ins, dev, addr_fmt, clear_ms, incl_xpubs, impor
|
||||
@pytest.mark.parametrize('segwit', [True, False])
|
||||
@pytest.mark.parametrize('incl_xpubs', [ True, False ])
|
||||
def test_ms_sign_myself(M, use_regtest, make_myself_wallet, segwit, num_ins, dev, clear_ms,
|
||||
fake_ms_txn, try_sign, incl_xpubs, bitcoind):
|
||||
fake_ms_txn, try_sign, incl_xpubs, bitcoind, sim_root_dir):
|
||||
|
||||
# IMPORTANT: wont work if you start simulator with --ms flag. Use no args
|
||||
|
||||
@ -1502,11 +1483,13 @@ def test_ms_sign_myself(M, use_regtest, make_myself_wallet, segwit, num_ins, dev
|
||||
psbt = fake_ms_txn(num_ins, num_outs, M, keys, segwit_in=segwit, incl_xpubs=incl_xpubs,
|
||||
outstyles=all_out_styles, change_outputs=list(range(1,num_outs)))
|
||||
|
||||
open(f'debug/myself-before.psbt', 'w').write(b64encode(psbt).decode())
|
||||
with open(f'{sim_root_dir}/debug/myself-before.psbt', 'w') as f:
|
||||
f.write(b64encode(psbt).decode())
|
||||
for idx in range(M):
|
||||
select_wallet(idx)
|
||||
_, updated = try_sign(psbt, accept_ms_import=incl_xpubs)
|
||||
open(f'debug/myself-after.psbt', 'w').write(b64encode(updated).decode())
|
||||
with open(f'{sim_root_dir}/debug/myself-after.psbt', 'w') as f:
|
||||
f.write(b64encode(updated).decode())
|
||||
assert updated != psbt
|
||||
|
||||
aft = BasicPSBT().parse(updated)
|
||||
@ -1522,9 +1505,12 @@ def test_ms_sign_myself(M, use_regtest, make_myself_wallet, segwit, num_ins, dev
|
||||
assert all(inp['next'] in {'finalizer','updater'} for inp in anal['inputs']), "other issue: %r" % anal
|
||||
except:
|
||||
# XXX seems to be a bug in analyzepsbt function ... not fully studied
|
||||
pprint(anal, stream=open('debug/analyzed.txt', 'wt'))
|
||||
with open(f'{sim_root_dir}/debug/analyzed.txt', 'wt') as f:
|
||||
pprint(anal, stream=f)
|
||||
|
||||
decode = bitcoind.rpc.decodepsbt(b64encode(psbt).decode('ascii'))
|
||||
pprint(decode, stream=open('debug/decoded.txt', 'wt'))
|
||||
with open(f'{sim_root_dir}/debug/decoded.txt', 'wt') as f:
|
||||
pprint(decode, stream=f)
|
||||
|
||||
if M==N or segwit:
|
||||
# as observed, bug not trigged, so raise if it *does* happen
|
||||
@ -1749,7 +1735,7 @@ def test_make_airgapped(addr_fmt, acct_num, M_N, goto_home, cap_story, pick_menu
|
||||
@pytest.mark.parametrize('addr_style', ["legacy", "p2sh-segwit", "bech32"])
|
||||
@pytest.mark.parametrize('cc_sign_first', [True, False])
|
||||
def test_bitcoind_cosigning(cc_sign_first, dev, bitcoind, import_ms_wallet, clear_ms, try_sign,
|
||||
press_cancel, addr_style, use_regtest, is_q1):
|
||||
press_cancel, addr_style, use_regtest, is_q1, sim_root_dir):
|
||||
# Make a P2SH wallet with local bitcoind as a co-signer (and simulator)
|
||||
# - send an receive various
|
||||
# - following text of <https://github.com/bitcoin/bitcoin/blob/master/doc/psbt.md>
|
||||
@ -1851,7 +1837,8 @@ def test_bitcoind_cosigning(cc_sign_first, dev, bitcoind, import_ms_wallet, clea
|
||||
# assert resp['changepos'] == -1
|
||||
psbt = b64decode(resp['psbt'])
|
||||
|
||||
open('debug/funded.psbt', 'wb').write(psbt)
|
||||
with open(f'{sim_root_dir}/debug/funded.psbt', 'wb') as f:
|
||||
f.write(psbt)
|
||||
|
||||
# patch up the PSBT a little ... bitcoind doesn't know the path for the CC's key
|
||||
ex = BasicPSBT().parse(psbt)
|
||||
@ -1863,11 +1850,13 @@ def test_bitcoind_cosigning(cc_sign_first, dev, bitcoind, import_ms_wallet, clea
|
||||
|
||||
psbt = ex.as_bytes()
|
||||
|
||||
open('debug/patched.psbt', 'wb').write(psbt)
|
||||
with open(f'{sim_root_dir}/debug/patched.psbt', 'wb') as f:
|
||||
f.write(psbt)
|
||||
|
||||
_, updated = try_sign(psbt, finalize=False)
|
||||
|
||||
open('debug/cc-updated.psbt', 'wb').write(updated)
|
||||
with open(f'{sim_root_dir}/debug/cc-updated.psbt', 'wb') as f:
|
||||
f.write(updated)
|
||||
|
||||
if cc_sign_first:
|
||||
# cc signed first - bitcoind is now second
|
||||
@ -1879,7 +1868,9 @@ def test_bitcoind_cosigning(cc_sign_first, dev, bitcoind, import_ms_wallet, clea
|
||||
|
||||
# finalize and send
|
||||
rr = bitcoind.supply_wallet.finalizepsbt(both_signed, True)
|
||||
open('debug/bc-final-txn.txn', 'wt').write(rr['hex'])
|
||||
with open(f'{sim_root_dir}/debug/bc-final-txn.txn', 'wt') as f:
|
||||
f.write(rr['hex'])
|
||||
|
||||
assert rr['complete']
|
||||
tx_hex = rr["hex"]
|
||||
res = bitcoind.supply_wallet.testmempoolaccept([tx_hex])
|
||||
@ -1894,8 +1885,9 @@ def test_bitcoind_cosigning(cc_sign_first, dev, bitcoind, import_ms_wallet, clea
|
||||
@pytest.mark.parametrize('out_style', ['p2wsh'])
|
||||
@pytest.mark.parametrize('bitrot', list(range(0,6)) + [98, 99, 100] + list(range(-5, 0)))
|
||||
@pytest.mark.ms_danger
|
||||
def test_ms_sign_bitrot(num_ins, dev, addr_fmt, clear_ms, incl_xpubs, import_ms_wallet, addr_vs_path,
|
||||
fake_ms_txn, start_sign, end_sign, out_style, cap_story, bitrot, has_ms_checks):
|
||||
def test_ms_sign_bitrot(num_ins, dev, addr_fmt, clear_ms, incl_xpubs, import_ms_wallet,
|
||||
addr_vs_path, fake_ms_txn, start_sign, end_sign, out_style, cap_story,
|
||||
bitrot, has_ms_checks, sim_root_dir):
|
||||
M = 1
|
||||
N = 3
|
||||
num_outs = 2
|
||||
@ -1926,7 +1918,8 @@ def test_ms_sign_bitrot(num_ins, dev, addr_fmt, clear_ms, incl_xpubs, import_ms_
|
||||
|
||||
assert len(track) == 1
|
||||
|
||||
open('debug/last.psbt', 'wb').write(psbt)
|
||||
with open(f'{sim_root_dir}/debug/last.psbt', 'wb') as f:
|
||||
f.write(psbt)
|
||||
|
||||
start_sign(psbt)
|
||||
with pytest.raises(Exception) as ee:
|
||||
@ -1947,7 +1940,8 @@ def test_ms_sign_bitrot(num_ins, dev, addr_fmt, clear_ms, incl_xpubs, import_ms_
|
||||
@pytest.mark.parametrize('pk_num', range(4))
|
||||
@pytest.mark.parametrize('case', ['pubkey', 'path'])
|
||||
def test_ms_change_fraud(case, pk_num, num_ins, dev, addr_fmt, clear_ms, incl_xpubs, make_multisig,
|
||||
addr_vs_path, fake_ms_txn, start_sign, end_sign, out_style, cap_story):
|
||||
addr_vs_path, fake_ms_txn, start_sign, end_sign, out_style, cap_story,
|
||||
sim_root_dir):
|
||||
|
||||
M = 1
|
||||
N = 3
|
||||
@ -1982,7 +1976,8 @@ def test_ms_change_fraud(case, pk_num, num_ins, dev, addr_fmt, clear_ms, incl_xp
|
||||
hack_change_out=lambda idx: dict(tweak_pubkeys=
|
||||
lambda data: tweak(case, pk_num, data)))
|
||||
|
||||
open('debug/last.psbt', 'wb').write(psbt)
|
||||
with open(f'{sim_root_dir}/debug/last.psbt', 'wb') as f:
|
||||
f.write(psbt)
|
||||
|
||||
with pytest.raises(Exception) as ee:
|
||||
start_sign(psbt)
|
||||
@ -1999,7 +1994,7 @@ def test_ms_change_fraud(case, pk_num, num_ins, dev, addr_fmt, clear_ms, incl_xp
|
||||
|
||||
|
||||
@pytest.mark.parametrize('repeat', range(2) )
|
||||
def test_iss6743(repeat, set_seed_words, sim_execfile, try_sign):
|
||||
def test_iss6743(repeat, set_seed_words, sim_execfile, try_sign, sim_root_dir):
|
||||
# from SomberNight <https://github.com/spesmilo/electrum/issues/6743#issuecomment-729965813>
|
||||
psbt_b4 = bytes.fromhex('70736274ff0100520200000001bde05be36069e2e0fe44793c68ad8244bb1a52cc37f152e0fa5b75e40169d7f70000000000fdffffff018b1e000000000000160014ed5180f05c7b1dc980732602c50cda40530e00ad4de11c004f01024289ef0000000000000000007dd565da7ee1cf05c516e89a608968fed4a2450633a00c7b922df66b27afd2e1033a0a4fa4b0a997738ac2f142a395c1f02afcb31d7ffd46a90a0c927a4c411fd704094ef7844f01024289ef0431fcbdcc8000000112d4aaea7292e7870c7eeb3565fa1c1fa8f957fa7c4c24b411d5b4f5710d359a023e63d1e54063525bea286ccb2a0ad7b14560aa31ec4be826afa883141dfe1d53145c9e228d300000800100008000000080010000804f01024289ef04e44b38f1800000014a1960f3a3c86ba355a16a66a548cfb62eeb25663311f7cd662a192896f3777e038cc595159a395e4ec35e477c9523a1512f873e74d303fb03fc9a1503b1ba45271434652fae30000080010000800000008001000080000100df02000000000101d1a321707660769c7f8604d04c9ae2db58cf1ec7a01f4f285cdcbb25ce14bdfd0300000000fdffffff02401f00000000000017a914bfd0b8471a3706c1e17870a4d39b0354bcea57b687c864000000000000160014e608b171d63ec24d9fa252d5c1e45624b14e44700247304402205133eb96df167b895f657cce31c6882840a403013682d9d4651aed2730a7dad502202aaacc045d85d9c711af0c84e7f355cc18bf2f8e6d91774d42ba24de8418a39e012103a58d8eb325abb412eaf927cf11d8b7641c4a468ce412057e47892ca2d13ed6144de11c000104220020a3c65c4e376d82fb3ca45596feee5b08313ad64f38590c1b08bc530d1c0bbfea010569522102ab84641359fa22461b8461515231da63c196614cd22b26e556ed878e30db4da621034211ab0f75c3a307a2f6bf6f09a9e05d3c8edd0ba7a2ac31f432d1045ef6381921039690cf74941da5db291fa8be7348abe3807786732d969eac5d27e0afa909a55f53ae220602ab84641359fa22461b8461515231da63c196614cd22b26e556ed878e30db4da60c094ef78400000000030000002206034211ab0f75c3a307a2f6bf6f09a9e05d3c8edd0ba7a2ac31f432d1045ef638191c5c9e228d3000008001000080000000800100008000000000030000002206039690cf74941da5db291fa8be7348abe3807786732d969eac5d27e0afa909a55f1c34652fae3000008001000080000000800100008000000000030000000000')
|
||||
# pre 3.2.0 result
|
||||
@ -2033,7 +2028,8 @@ def test_iss6743(repeat, set_seed_words, sim_execfile, try_sign):
|
||||
assert out_psbt != psbt_wrong
|
||||
assert BasicPSBT().parse(out_psbt) == BasicPSBT().parse(psbt_right)
|
||||
|
||||
open('debug/i6.psbt', 'wt').write(out_psbt.hex())
|
||||
with open(f'{sim_root_dir}/debug/i6.psbt', 'wt') as f:
|
||||
f.write(out_psbt.hex())
|
||||
|
||||
@pytest.mark.parametrize('N', [ 3, 15])
|
||||
@pytest.mark.parametrize('xderiv', [ None, 'any', 'unknown', '*', '', 'none'])
|
||||
@ -2128,7 +2124,8 @@ def test_ms_import_many_derivs(M, N, way, make_multisig, clear_ms, offer_ms_impo
|
||||
|
||||
@pytest.mark.ms_danger
|
||||
@pytest.mark.parametrize('descriptor', [True, False])
|
||||
def test_danger_warning(request, descriptor, clear_ms, import_ms_wallet, cap_story, fake_ms_txn, start_sign, sim_exec):
|
||||
def test_danger_warning(request, descriptor, clear_ms, import_ms_wallet, cap_story, fake_ms_txn,
|
||||
start_sign, sim_exec, sim_root_dir):
|
||||
# note: cant use has_ms_checks fixture here
|
||||
danger_mode = (request.config.getoption('--ms-danger'))
|
||||
sim_exec(f'from multisig import MultisigWallet; MultisigWallet.disable_checks={danger_mode}')
|
||||
@ -2138,7 +2135,8 @@ def test_danger_warning(request, descriptor, clear_ms, import_ms_wallet, cap_sto
|
||||
keys = import_ms_wallet(M, N, accept=1, descriptor=descriptor, addr_fmt="p2wsh")
|
||||
psbt = fake_ms_txn(1, 1, M, keys, incl_xpubs=True)
|
||||
|
||||
open('debug/last.psbt', 'wb').write(psbt)
|
||||
with open(f'{sim_root_dir}/debug/last.psbt', 'wb') as f:
|
||||
f.write(psbt)
|
||||
|
||||
start_sign(psbt)
|
||||
title, story = cap_story()
|
||||
@ -2306,13 +2304,17 @@ def test_dup_ms_wallet_bug(goto_home, pick_menu_item, press_select, import_ms_wa
|
||||
@pytest.mark.parametrize('int_ext_desc', [True, False])
|
||||
@pytest.mark.parametrize('way', ["sd", "vdisk", "nfc"])
|
||||
@pytest.mark.parametrize('desc', ["multi", "sortedmulti"])
|
||||
def test_import_desciptor(M_N, addr_fmt, int_ext_desc, way, import_ms_wallet, goto_home, pick_menu_item,
|
||||
press_select, clear_ms, cap_story, microsd_path, virtdisk_path,
|
||||
nfc_read_text, load_export, is_q1, desc):
|
||||
def test_import_descriptor(M_N, addr_fmt, int_ext_desc, way, import_ms_wallet, goto_home, pick_menu_item,
|
||||
press_select, clear_ms, cap_story, microsd_path, virtdisk_path,
|
||||
nfc_read_text, load_export, is_q1, desc):
|
||||
clear_ms()
|
||||
M, N = M_N
|
||||
import_ms_wallet(M, N, addr_fmt=addr_fmt, accept=1, descriptor=True,
|
||||
int_ext_desc=int_ext_desc, bip67=False if desc == "multi" else True)
|
||||
desc_import = import_ms_wallet(
|
||||
M, N, addr_fmt=addr_fmt, accept=True, descriptor=True,
|
||||
int_ext_desc=int_ext_desc, bip67=False if desc == "multi" else True,
|
||||
return_desc=True
|
||||
)
|
||||
desc_import = desc_import.strip()
|
||||
|
||||
goto_home()
|
||||
pick_menu_item('Settings')
|
||||
@ -2322,8 +2324,7 @@ def test_import_desciptor(M_N, addr_fmt, int_ext_desc, way, import_ms_wallet, go
|
||||
pick_menu_item('Export')
|
||||
contents = load_export(way, label="Descriptor multisig setup", is_json=False)
|
||||
desc_export = contents.strip()
|
||||
with open("debug/last-ms.txt", "r") as f:
|
||||
desc_import = f.read().strip()
|
||||
|
||||
normalized = parse_desc_str(desc_export)
|
||||
# as new format is not widely supported we only allow to import it - no export yet
|
||||
if int_ext_desc:
|
||||
@ -2342,7 +2343,7 @@ def test_import_desciptor(M_N, addr_fmt, int_ext_desc, way, import_ms_wallet, go
|
||||
@pytest.mark.parametrize("start_idx", [2147483540, MAX_BIP32_IDX, 0])
|
||||
@pytest.mark.parametrize('M_N', [(2, 2), (3, 5), (15, 15)])
|
||||
@pytest.mark.parametrize('addr_fmt', [AF_P2WSH, AF_P2SH, AF_P2WSH_P2SH])
|
||||
@pytest.mark.parametrize('way', ["sd", "vdisk", "nfc"])
|
||||
@pytest.mark.parametrize('way', ["sd", "nfc"]) # vdisk
|
||||
def test_bitcoind_ms_address(change, M_N, addr_fmt, clear_ms, goto_home, need_keypress,
|
||||
pick_menu_item, cap_menu, cap_story, make_multisig, import_ms_wallet,
|
||||
microsd_path, bitcoind_d_wallet_w_sk, use_regtest, load_export, way,
|
||||
@ -3042,14 +3043,13 @@ def test_ms_wallet_ordering(clear_ms, import_ms_wallet, try_sign_microsd, fake_m
|
||||
|
||||
psbt = fake_ms_txn(5, 5, 3, keys3, outstyles=all_out_styles, segwit_in=True, incl_xpubs=True)
|
||||
|
||||
open('debug/last.psbt', 'wb').write(psbt)
|
||||
|
||||
try_sign_microsd(psbt, encoding='base64')
|
||||
|
||||
|
||||
@pytest.mark.parametrize("descriptor", [True, False])
|
||||
@pytest.mark.parametrize("m_n", [(2, 3), (3, 5), (5, 10)])
|
||||
def test_ms_xpub_ordering(descriptor, m_n, clear_ms, make_multisig, import_ms_wallet, try_sign_microsd, fake_ms_txn):
|
||||
def test_ms_xpub_ordering(descriptor, m_n, clear_ms, make_multisig, import_ms_wallet,
|
||||
try_sign_microsd, fake_ms_txn):
|
||||
clear_ms()
|
||||
M, N = m_n
|
||||
all_out_styles = list(unmap_addr_fmt.keys())
|
||||
@ -3063,13 +3063,11 @@ def test_ms_xpub_ordering(descriptor, m_n, clear_ms, make_multisig, import_ms_wa
|
||||
addr_fmt="p2wsh", descriptor=descriptor)
|
||||
psbt = fake_ms_txn(5, 5, M, opt, outstyles=all_out_styles,
|
||||
segwit_in=True, incl_xpubs=True)
|
||||
open('debug/last.psbt', 'wb').write(psbt)
|
||||
try_sign_microsd(psbt, encoding='base64')
|
||||
for opt_1 in all_options:
|
||||
# create PSBT with original keys order
|
||||
psbt = fake_ms_txn(5, 5, M, opt_1, outstyles=all_out_styles,
|
||||
segwit_in=True, incl_xpubs=True)
|
||||
open('debug/last.psbt', 'wb').write(psbt)
|
||||
try_sign_microsd(psbt, encoding='base64')
|
||||
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@ from charcodes import KEY_NFC, KEY_QR
|
||||
|
||||
|
||||
@pytest.mark.parametrize('case', range(6))
|
||||
def test_ndef(case, load_shared_mod):
|
||||
def test_ndef(case, load_shared_mod, src_root_dir):
|
||||
# NDEF unit tests -- runs in cpython
|
||||
|
||||
def get_body(efile):
|
||||
@ -36,7 +36,7 @@ def test_ndef(case, load_shared_mod):
|
||||
def decode(msg):
|
||||
return list(ndef.message_decoder(get_body(msg)))
|
||||
|
||||
cc_ndef = load_shared_mod('cc_ndef', '../shared/ndef.py')
|
||||
cc_ndef = load_shared_mod('cc_ndef', f'{src_root_dir}/shared/ndef.py')
|
||||
n = cc_ndef.ndefMaker()
|
||||
|
||||
if case == 0:
|
||||
@ -101,13 +101,13 @@ def test_ndef(case, load_shared_mod):
|
||||
'short',
|
||||
'long',
|
||||
])
|
||||
def test_ndef_ccfile(ccfile, load_shared_mod):
|
||||
def test_ndef_ccfile(ccfile, load_shared_mod, src_root_dir):
|
||||
# NDEF unit tests
|
||||
|
||||
def decode(body):
|
||||
return list(ndef.message_decoder(body))
|
||||
|
||||
cc_ndef = load_shared_mod('cc_ndef', '../shared/ndef.py')
|
||||
cc_ndef = load_shared_mod('cc_ndef', f'{src_root_dir}/shared/ndef.py')
|
||||
|
||||
txt_msg = None
|
||||
if ccfile == 'rx':
|
||||
@ -149,7 +149,8 @@ def test_ndef_ccfile(ccfile, load_shared_mod):
|
||||
@pytest.fixture
|
||||
def try_sign_nfc(cap_story, pick_menu_item, goto_home, need_keypress,
|
||||
sim_exec, nfc_read, nfc_write, nfc_block4rf, press_select,
|
||||
press_cancel, press_nfc, nfc_read_txn, ndef_parse_txn_psbt):
|
||||
press_cancel, press_nfc, nfc_read_txn, ndef_parse_txn_psbt,
|
||||
sim_root_dir):
|
||||
|
||||
# like "try_sign" but use NFC to send/receive PSBT/results
|
||||
|
||||
@ -182,7 +183,7 @@ def try_sign_nfc(cap_story, pick_menu_item, goto_home, need_keypress,
|
||||
ndef.TextRecord('some text'),
|
||||
]
|
||||
|
||||
with open('debug/nfc-sent.psbt', 'wb') as f:
|
||||
with open(f'{sim_root_dir}/debug/nfc-sent.psbt', 'wb') as f:
|
||||
f.write(ip)
|
||||
|
||||
# wrap in a CCFile
|
||||
@ -274,7 +275,7 @@ def try_sign_nfc(cap_story, pick_menu_item, goto_home, need_keypress,
|
||||
sim_exec('from pyb import SDCard; SDCard.ejected = False')
|
||||
|
||||
@pytest.fixture
|
||||
def ndef_parse_txn_psbt(press_cancel):
|
||||
def ndef_parse_txn_psbt(press_cancel, sim_root_dir):
|
||||
def doit(contents, txid=None, orig=None, expect_finalized=True):
|
||||
# from NFC data read, what did we get?
|
||||
got_txid = None
|
||||
@ -310,12 +311,14 @@ def ndef_parse_txn_psbt(press_cancel):
|
||||
assert got_txid == txid
|
||||
assert expect_finalized
|
||||
result = got_txn
|
||||
open("debug/nfc-result.txn", 'wb').write(result)
|
||||
with open(f"{sim_root_dir}/debug/nfc-result.txn", 'wb') as f:
|
||||
f.write(result)
|
||||
else:
|
||||
assert not expect_finalized
|
||||
result = got_psbt
|
||||
|
||||
open("debug/nfc-result.psbt", 'wb').write(result)
|
||||
with open(f"{sim_root_dir}/debug/nfc-result.psbt", 'wb') as f:
|
||||
f.write(result)
|
||||
|
||||
# read back final product
|
||||
if got_txn:
|
||||
@ -434,9 +437,9 @@ def test_rf_uid(rf_interface, cap_story, goto_home, pick_menu_item):
|
||||
print(uid)
|
||||
|
||||
|
||||
def test_ndef_roundtrip(load_shared_mod):
|
||||
def test_ndef_roundtrip(load_shared_mod, src_root_dir):
|
||||
# specific failing case
|
||||
cc_ndef = load_shared_mod('cc_ndef', '../shared/ndef.py')
|
||||
cc_ndef = load_shared_mod('cc_ndef', f'{src_root_dir}/shared/ndef.py')
|
||||
|
||||
r = open('data/ms-import.ndef', 'rb').read()
|
||||
|
||||
@ -605,10 +608,11 @@ def test_share_by_pushtx(goto_home, cap_story, pick_menu_item, settings_set,
|
||||
])
|
||||
def test_nfc_share_files(fname, mode, ftype, nfc_read_json, nfc_read_text,
|
||||
need_keypress, goto_home, pick_menu_item, is_q1,
|
||||
cap_menu, nfc_read, nfc_block4rf, press_select):
|
||||
cap_menu, nfc_read, nfc_block4rf, press_select,
|
||||
src_root_dir, sim_root_dir):
|
||||
goto_home()
|
||||
fpath = "data/" + fname
|
||||
shutil.copy2(fpath, '../unix/work/MicroSD')
|
||||
fpath = f"{src_root_dir}/testing/data/" + fname
|
||||
shutil.copy2(fpath, f'{sim_root_dir}/MicroSD')
|
||||
pick_menu_item("Advanced/Tools")
|
||||
pick_menu_item("File Management")
|
||||
pick_menu_item("NFC File Share")
|
||||
@ -659,6 +663,6 @@ def test_nfc_share_files(fname, mode, ftype, nfc_read_json, nfc_read_text,
|
||||
res = json.loads(res)
|
||||
|
||||
assert res == contents
|
||||
os.remove('../unix/work/MicroSD/' + fname)
|
||||
os.remove(f'{sim_root_dir}/MicroSD/' + fname)
|
||||
|
||||
# EOF
|
||||
|
||||
@ -159,7 +159,8 @@ def test_ux(valid, testnet, method,
|
||||
sim_exec, wipe_cache, make_myself_wallet, use_testnet, goto_home, pick_menu_item,
|
||||
press_cancel, press_select, settings_set, is_q1, nfc_write, need_keypress,
|
||||
cap_screen, cap_story, load_shared_mod, scan_a_qr, skip_if_useless_way,
|
||||
sign_msg_from_address, multisig, import_ms_wallet, clear_ms, verify_qr_address
|
||||
sign_msg_from_address, multisig, import_ms_wallet, clear_ms, verify_qr_address,
|
||||
src_root_dir, sim_root_dir
|
||||
):
|
||||
skip_if_useless_way(method)
|
||||
addr_fmt = AF_CLASSIC
|
||||
@ -201,7 +202,7 @@ def test_ux(valid, testnet, method,
|
||||
|
||||
elif method == 'nfc':
|
||||
|
||||
cc_ndef = load_shared_mod('cc_ndef', '../shared/ndef.py')
|
||||
cc_ndef = load_shared_mod('cc_ndef', f'{src_root_dir}/shared/ndef.py')
|
||||
n = cc_ndef.ndefMaker()
|
||||
n.add_text(addr)
|
||||
ccfile = n.bytes()
|
||||
@ -211,7 +212,8 @@ def test_ux(valid, testnet, method,
|
||||
pick_menu_item('Advanced/Tools')
|
||||
pick_menu_item('NFC Tools')
|
||||
pick_menu_item('Verify Address')
|
||||
open('debug/nfc-addr.ndef', 'wb').write(ccfile)
|
||||
with open(f'{sim_root_dir}/debug/nfc-addr.ndef', 'wb') as f:
|
||||
f.write(ccfile)
|
||||
nfc_write(ccfile)
|
||||
#press_select()
|
||||
|
||||
@ -255,7 +257,7 @@ def test_address_explorer_saver(af, wipe_cache, settings_set, goto_address_explo
|
||||
pick_menu_item, need_keypress, sim_exec, clear_ms,
|
||||
import_ms_wallet, press_select, goto_home, nfc_write,
|
||||
load_shared_mod, load_export_and_verify_signature,
|
||||
cap_story, is_q1):
|
||||
cap_story, is_q1, src_root_dir, sim_root_dir):
|
||||
goto_home()
|
||||
wipe_cache()
|
||||
settings_set('accts', [])
|
||||
@ -290,7 +292,7 @@ def test_address_explorer_saver(af, wipe_cache, settings_set, goto_address_explo
|
||||
if idx == 200:
|
||||
addr = addr
|
||||
|
||||
cc_ndef = load_shared_mod('cc_ndef', '../shared/ndef.py')
|
||||
cc_ndef = load_shared_mod('cc_ndef', f'{src_root_dir}/shared/ndef.py')
|
||||
n = cc_ndef.ndefMaker()
|
||||
n.add_text(addr)
|
||||
ccfile = n.bytes()
|
||||
@ -300,7 +302,9 @@ def test_address_explorer_saver(af, wipe_cache, settings_set, goto_address_explo
|
||||
pick_menu_item('Advanced/Tools')
|
||||
pick_menu_item('NFC Tools')
|
||||
pick_menu_item('Verify Address')
|
||||
open('debug/nfc-addr.ndef', 'wb').write(ccfile)
|
||||
with open(f'{sim_root_dir}/debug/nfc-addr.ndef', 'wb') as f:
|
||||
f.write(ccfile)
|
||||
|
||||
nfc_write(ccfile)
|
||||
|
||||
time.sleep(1)
|
||||
@ -317,7 +321,7 @@ def test_address_explorer_saver(af, wipe_cache, settings_set, goto_address_explo
|
||||
|
||||
|
||||
def test_regtest_addr_on_mainnet(goto_home, is_q1, pick_menu_item, scan_a_qr, nfc_write, cap_story,
|
||||
need_keypress, load_shared_mod, use_mainnet):
|
||||
need_keypress, load_shared_mod, use_mainnet, src_root_dir, sim_root_dir):
|
||||
# testing bug in chains.possible_address_fmt
|
||||
# allowed regtest addresses to be allowed on main chain
|
||||
goto_home()
|
||||
@ -335,7 +339,7 @@ def test_regtest_addr_on_mainnet(goto_home, is_q1, pick_menu_item, scan_a_qr, nf
|
||||
need_keypress('1')
|
||||
|
||||
else:
|
||||
cc_ndef = load_shared_mod('cc_ndef', '../shared/ndef.py')
|
||||
cc_ndef = load_shared_mod('cc_ndef', f'{src_root_dir}/shared/ndef.py')
|
||||
n = cc_ndef.ndefMaker()
|
||||
n.add_text(addr)
|
||||
ccfile = n.bytes()
|
||||
@ -344,7 +348,8 @@ def test_regtest_addr_on_mainnet(goto_home, is_q1, pick_menu_item, scan_a_qr, nf
|
||||
pick_menu_item('Advanced/Tools')
|
||||
pick_menu_item('NFC Tools')
|
||||
pick_menu_item('Verify Address')
|
||||
open('debug/nfc-addr.ndef', 'wb').write(ccfile)
|
||||
with open(f'{sim_root_dir}/debug/nfc-addr.ndef', 'wb') as f:
|
||||
f.write(ccfile)
|
||||
nfc_write(ccfile)
|
||||
# press_select()
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ from ckcc_protocol.constants import *
|
||||
@pytest.mark.parametrize('netcode', ["XTN", "BTC"])
|
||||
def test_generate(mode, pdf, netcode, dev, cap_menu, pick_menu_item, goto_home, cap_story,
|
||||
need_keypress, microsd_path, verify_detached_signature_file, settings_set,
|
||||
press_select):
|
||||
press_select, src_root_dir):
|
||||
# test UX and operation of the 'bitcoin core' wallet export
|
||||
mx = "Don't make PDF"
|
||||
|
||||
@ -47,7 +47,7 @@ def test_generate(mode, pdf, netcode, dev, cap_menu, pick_menu_item, goto_home,
|
||||
|
||||
if pdf:
|
||||
assert mx in cap_menu()
|
||||
shutil.copy('../docs/paperwallet.pdf', microsd_path('paperwallet.pdf'))
|
||||
shutil.copy(f'{src_root_dir}/docs/paperwallet.pdf', microsd_path('paperwallet.pdf'))
|
||||
pick_menu_item(mx)
|
||||
|
||||
time.sleep(0.2)
|
||||
|
||||
@ -6,7 +6,12 @@ import pytest, time, os, shutil
|
||||
from binascii import a2b_hex
|
||||
from constants import simulator_fixed_tprv
|
||||
|
||||
SIM_FNAME = '../unix/work/MicroSD/.tmp.tmp'
|
||||
|
||||
@pytest.fixture
|
||||
def simulator_db_file(sim_root_dir):
|
||||
def doit():
|
||||
return sim_root_dir + "/MicroSD/.tmp.tmp"
|
||||
return doit
|
||||
|
||||
@pytest.fixture
|
||||
def set_pw_phrase(pick_menu_item, word_menu_entry):
|
||||
@ -31,8 +36,9 @@ def set_pw_phrase(pick_menu_item, word_menu_entry):
|
||||
'ab'*25,
|
||||
])
|
||||
def test_first_time(pws, need_keypress, cap_story, pick_menu_item, enter_complex,
|
||||
cap_menu, go_to_passphrase, reset_seed_words, press_select):
|
||||
try: os.unlink(SIM_FNAME)
|
||||
cap_menu, go_to_passphrase, reset_seed_words, press_select,
|
||||
simulator_db_file):
|
||||
try: os.unlink(simulator_db_file())
|
||||
except: pass
|
||||
|
||||
pws = pws.split()
|
||||
@ -85,7 +91,7 @@ def test_first_time(pws, need_keypress, cap_story, pick_menu_item, enter_complex
|
||||
reset_seed_words()
|
||||
|
||||
|
||||
def test_crypto_unittest(sim_eval, sim_exec, simulator):
|
||||
def test_crypto_unittest(sim_exec, simulator, simulator_db_file):
|
||||
# unit test for AES key generation from SDCard and master secret
|
||||
card = sim_exec('import files; from h import b2a_hex; cs = files.CardSlot().__enter__(); RV.write(b2a_hex(cs.get_id_hash())); cs.__exit__()')
|
||||
|
||||
@ -121,7 +127,7 @@ p=PassphraseSaver(); p._calc_key(cs); RV.write(b2a_hex(p.key)); cs.__exit__()'''
|
||||
|
||||
# check that key works for decrypt and that the file was actually encrypted
|
||||
|
||||
with open(SIM_FNAME, 'rb') as fd:
|
||||
with open(simulator_db_file(), 'rb') as fd:
|
||||
raw = fd.read()
|
||||
|
||||
import pyaes
|
||||
@ -137,7 +143,7 @@ p=PassphraseSaver(); p._calc_key(cs); RV.write(b2a_hex(p.key)); cs.__exit__()'''
|
||||
assert j[0]['xfp']
|
||||
|
||||
def test_delete_one_by_one(go_to_passphrase, pick_menu_item, cap_menu,
|
||||
cap_story, press_select):
|
||||
cap_story, press_select, src_root_dir, sim_root_dir):
|
||||
# delete it one by one
|
||||
# when all deleted - we must be back in Passphrase
|
||||
# menu without Restore Saved option visible
|
||||
@ -145,7 +151,7 @@ def test_delete_one_by_one(go_to_passphrase, pick_menu_item, cap_menu,
|
||||
time.sleep(.1)
|
||||
m = cap_menu()
|
||||
if 'Restore Saved' not in m:
|
||||
shutil.copy2('data/pwsave.tmp', '../unix/work/MicroSD/.tmp.tmp')
|
||||
shutil.copy2(f'{src_root_dir}/testing/data/pwsave.tmp', f'{sim_root_dir}/MicroSD/.tmp.tmp')
|
||||
go_to_passphrase()
|
||||
pick_menu_item('Restore Saved')
|
||||
m = cap_menu()
|
||||
|
||||
@ -65,7 +65,7 @@ def decode_slot(data):
|
||||
assert len(data) == 128
|
||||
return SlotInfo(*struct.unpack(TRICK_FMT, data))
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
@pytest.fixture
|
||||
def se2_gate(sim_exec):
|
||||
# not-so-low-level method: include auth data for main PIN
|
||||
def doit(method_num, obj=None, buf=None):
|
||||
@ -252,7 +252,7 @@ def test_ux_trick_menus(goto_trick_menu, pick_menu_item, cap_menu,
|
||||
# all clear now
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
@pytest.fixture
|
||||
def new_trick_pin(goto_trick_menu, pick_menu_item, cap_menu, press_select,
|
||||
cap_story, enter_pin, se2_gate, is_simulator, is_q1):
|
||||
# using menus and UX, setup a new trick PIN
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
# test Seed XOR features
|
||||
#
|
||||
|
||||
import pytest, time, itertools
|
||||
import pytest, time, itertools, random
|
||||
from mnemonic import Mnemonic
|
||||
from constants import simulator_fixed_words
|
||||
from xor import prepare_test_pairs, xor
|
||||
@ -54,7 +54,7 @@ def restore_seed_xor(set_seed_words, goto_home, pick_menu_item, cap_story,
|
||||
word_menu_entry, verify_ephemeral_secret_ui,
|
||||
confirm_tmp_seed, seed_vault_enable, press_select,
|
||||
scan_a_qr, is_q1, cap_screen_qr, cap_screen, OK):
|
||||
def doit(parts, expect, incl_self=False, save_to_vault=False,
|
||||
def doit(parts, expect, incl_self=False, save_to_vault=None,
|
||||
is_master_tmp_fail=False, way=None):
|
||||
if expect is None:
|
||||
parts, expect = prepare_test_pairs(*parts)
|
||||
@ -66,6 +66,9 @@ def restore_seed_xor(set_seed_words, goto_home, pick_menu_item, cap_story,
|
||||
elif incl_self is False:
|
||||
set_seed_words(proper[num_words])
|
||||
|
||||
if save_to_vault is None:
|
||||
save_to_vault = random.getrandbits(1)
|
||||
|
||||
seed_vault_enable(save_to_vault)
|
||||
time.sleep(.2)
|
||||
|
||||
@ -171,7 +174,6 @@ def restore_seed_xor(set_seed_words, goto_home, pick_menu_item, cap_story,
|
||||
|
||||
@pytest.mark.parametrize('way', ["qr", "seedqr", "classic"])
|
||||
@pytest.mark.parametrize('incl_self', [False, True])
|
||||
@pytest.mark.parametrize('seed_vault', [False, True])
|
||||
@pytest.mark.parametrize('parts, expect', [
|
||||
# 24words - 3 parts
|
||||
(['romance wink lottery autumn shop bring dawn tongue range crater truth ability miss spice fitness easy legal release recall obey exchange recycle dragon room',
|
||||
@ -191,10 +193,10 @@ def restore_seed_xor(set_seed_words, goto_home, pick_menu_item, cap_story,
|
||||
# random generated
|
||||
*random_test_cases()
|
||||
])
|
||||
def test_import_xor(seed_vault, incl_self, parts, expect, restore_seed_xor, way, is_q1):
|
||||
def test_import_xor(incl_self, parts, expect, restore_seed_xor, way, is_q1):
|
||||
if not is_q1 and "qr" in way:
|
||||
raise pytest.skip("Q only")
|
||||
restore_seed_xor(parts, expect, incl_self, seed_vault, way=way)
|
||||
restore_seed_xor(parts, expect, incl_self, way=way)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('incl_self', [False, True])
|
||||
|
||||
@ -119,14 +119,14 @@ def xxx_test_sign_truncated(dev):
|
||||
'data/worked-combined.psbt',
|
||||
'data/worked-7.psbt',
|
||||
])
|
||||
def test_psbt_proxy_parsing(fn, sim_execfile, sim_exec):
|
||||
def test_psbt_proxy_parsing(fn, sim_execfile, sim_exec, src_root_dir, sim_root_dir):
|
||||
# unit test: parsing by the psbt proxy object
|
||||
|
||||
sim_exec('import main; main.FILENAME = %r; ' % ('../../testing/'+fn))
|
||||
sim_exec('import main; main.FILENAME = %r; ' % (f'{src_root_dir}/testing/'+fn))
|
||||
rv = sim_execfile('devtest/unit_psbt.py')
|
||||
assert not rv, rv
|
||||
|
||||
rb = '../unix/work/readback.psbt'
|
||||
rb = f'{sim_root_dir}/readback.psbt'
|
||||
|
||||
oo = BasicPSBT().parse(open(fn, 'rb').read())
|
||||
rb = BasicPSBT().parse(open(rb, 'rb').read())
|
||||
@ -134,7 +134,7 @@ def test_psbt_proxy_parsing(fn, sim_execfile, sim_exec):
|
||||
|
||||
@pytest.mark.unfinalized
|
||||
def test_speed_test(dev, fake_txn, is_mark3, is_mark4, start_sign, end_sign,
|
||||
press_select, press_cancel):
|
||||
press_select, press_cancel, sim_root_dir):
|
||||
# measure time to sign a larger txn
|
||||
if is_mark4:
|
||||
# Mk4: expect
|
||||
@ -151,7 +151,9 @@ def test_speed_test(dev, fake_txn, is_mark3, is_mark4, start_sign, end_sign,
|
||||
|
||||
psbt = fake_txn(num_in, num_out, dev.master_xpub, segwit_in=True)
|
||||
|
||||
open('debug/speed.psbt', 'wb').write(psbt)
|
||||
with open(f'{sim_root_dir}/debug/speed.psbt', 'wb') as f:
|
||||
f.write(psbt)
|
||||
|
||||
dt = time.time()
|
||||
start_sign(psbt, finalize=False)
|
||||
|
||||
@ -193,7 +195,7 @@ if 0:
|
||||
@pytest.mark.veryslow
|
||||
@pytest.mark.parametrize('segwit', [True, False])
|
||||
def test_io_size(request, use_regtest, decode_with_bitcoind, fake_txn,
|
||||
start_sign, end_sign, dev, segwit, accept = True):
|
||||
start_sign, end_sign, dev, segwit, sim_root_dir):
|
||||
|
||||
# try a bunch of different bigger sized txns
|
||||
# - important to test on real device, due to it's limited memory
|
||||
@ -212,7 +214,8 @@ def test_io_size(request, use_regtest, decode_with_bitcoind, fake_txn,
|
||||
|
||||
psbt = fake_txn(num_in, num_out, dev.master_xpub, segwit_in=segwit, outstyles=ADDR_STYLES)
|
||||
|
||||
open('debug/last.psbt', 'wb').write(psbt)
|
||||
with open(f'{sim_root_dir}/debug/last.psbt', 'wb') as f:
|
||||
f.write(psbt)
|
||||
|
||||
start_sign(psbt, finalize=True)
|
||||
|
||||
@ -225,9 +228,10 @@ def test_io_size(request, use_regtest, decode_with_bitcoind, fake_txn,
|
||||
except:
|
||||
cap_story = None
|
||||
|
||||
signed = end_sign(accept, finalize=True)
|
||||
signed = end_sign(accept=True, finalize=True)
|
||||
|
||||
open('debug/signed.txn', 'wb').write(signed)
|
||||
with open(f'{sim_root_dir}/debug/signed.txn', 'wb') as f:
|
||||
f.write(signed)
|
||||
|
||||
decoded = decode_with_bitcoind(signed)
|
||||
|
||||
@ -263,12 +267,14 @@ def test_io_size(request, use_regtest, decode_with_bitcoind, fake_txn,
|
||||
@pytest.mark.bitcoind
|
||||
@pytest.mark.parametrize('num_ins', [ 2, 7, 15 ])
|
||||
@pytest.mark.parametrize('segwit', [True, False])
|
||||
def test_real_signing(fake_txn, use_regtest, try_sign, dev, num_ins, segwit, decode_with_bitcoind):
|
||||
def test_real_signing(fake_txn, use_regtest, try_sign, dev, num_ins, segwit,
|
||||
decode_with_bitcoind, sim_root_dir):
|
||||
# create a TXN using actual addresses that are correct for DUT
|
||||
xp = dev.master_xpub
|
||||
|
||||
psbt = fake_txn(num_ins, 1, xp, segwit_in=segwit)
|
||||
open('debug/real-%d.psbt' % num_ins, 'wb').write(psbt)
|
||||
with open(f'{sim_root_dir}/debug/real-%d.psbt' % num_ins, 'wb') as f:
|
||||
f.write(psbt)
|
||||
|
||||
_, txn = try_sign(psbt, accept=True, finalize=True)
|
||||
|
||||
@ -288,7 +294,7 @@ def test_real_signing(fake_txn, use_regtest, try_sign, dev, num_ins, segwit, dec
|
||||
@pytest.mark.parametrize('num_dests', [ 1, 10, 25 ])
|
||||
@pytest.mark.bitcoind
|
||||
def test_vs_bitcoind(match_key, use_regtest, check_against_bitcoind, bitcoind,
|
||||
start_sign, end_sign, we_finalize, num_dests):
|
||||
start_sign, end_sign, we_finalize, num_dests, sim_root_dir):
|
||||
|
||||
wallet_xfp = match_key
|
||||
use_regtest()
|
||||
@ -344,7 +350,8 @@ def test_vs_bitcoind(match_key, use_regtest, check_against_bitcoind, bitcoind,
|
||||
fee = resp['fee']
|
||||
chg_pos = resp['changepos']
|
||||
|
||||
open('debug/vs.psbt', 'wb').write(psbt)
|
||||
with open(f'{sim_root_dir}/debug/vs.psbt', 'wb') as f:
|
||||
f.write(psbt)
|
||||
|
||||
# check some basics
|
||||
mine = BasicPSBT().parse(psbt)
|
||||
@ -366,14 +373,17 @@ def test_vs_bitcoind(match_key, use_regtest, check_against_bitcoind, bitcoind,
|
||||
assert mine.version == 2
|
||||
|
||||
signed = end_sign(accept=True, finalize=we_finalize)
|
||||
open('debug/vs-signed.psbt', 'wb').write(signed)
|
||||
with open(f'{sim_root_dir}/debug/vs-signed.psbt', 'wb') as f:
|
||||
f.write(signed)
|
||||
|
||||
if not we_finalize:
|
||||
b4 = BasicPSBT().parse(psbt)
|
||||
aft = BasicPSBT().parse(signed)
|
||||
assert b4 != aft, "signing didn't change anything?"
|
||||
|
||||
open('debug/signed.psbt', 'wb').write(signed)
|
||||
with open(f'{sim_root_dir}/debug/signed.psbt', 'wb') as f:
|
||||
f.write(signed)
|
||||
|
||||
resp = bitcoind.supply_wallet.finalizepsbt(str(b64encode(signed), 'ascii'), True)
|
||||
|
||||
#combined_psbt = b64decode(resp['psbt'])
|
||||
@ -385,7 +395,8 @@ def test_vs_bitcoind(match_key, use_regtest, check_against_bitcoind, bitcoind,
|
||||
|
||||
# assert resp['complete']
|
||||
print("Final txn: %r" % network)
|
||||
open('debug/finalized-by-btcd.txn', 'wb').write(network)
|
||||
with open(f'{sim_root_dir}/debug/finalized-by-btcd.txn', 'wb') as f:
|
||||
f.write(network)
|
||||
|
||||
# try to send it
|
||||
txed = bitcoind.supply_wallet.sendrawtransaction(B2A(network))
|
||||
@ -394,7 +405,8 @@ def test_vs_bitcoind(match_key, use_regtest, check_against_bitcoind, bitcoind,
|
||||
else:
|
||||
assert signed[0:4] != b'psbt', "expecting raw bitcoin txn"
|
||||
#print("Final txn: %s" % B2A(signed))
|
||||
open('debug/finalized-by-cc.txn', 'wb').write(signed)
|
||||
with open(f'{sim_root_dir}/debug/finalized-by-cc.txn', 'wb') as f:
|
||||
f.write(signed)
|
||||
|
||||
txed = bitcoind.supply_wallet.sendrawtransaction(B2A(signed))
|
||||
print("Final txn hash: %r" % txed)
|
||||
@ -426,7 +438,7 @@ def test_sign_example(set_master_key, sim_execfile, start_sign, end_sign):
|
||||
|
||||
@pytest.mark.bitcoind
|
||||
@pytest.mark.unfinalized
|
||||
def test_sign_p2sh_p2wpkh(match_key, use_regtest, start_sign, end_sign, bitcoind):
|
||||
def test_sign_p2sh_p2wpkh(match_key, use_regtest, start_sign, end_sign, bitcoind, sim_root_dir):
|
||||
# Check we can finalize p2sh_p2wpkh inputs right.
|
||||
|
||||
# TODO fix this
|
||||
@ -442,7 +454,8 @@ def test_sign_p2sh_p2wpkh(match_key, use_regtest, start_sign, end_sign, bitcoind
|
||||
start_sign(psbt, finalize=True)
|
||||
signed = end_sign(accept=True)
|
||||
#signed = end_sign(None)
|
||||
open('debug/p2sh-signed.psbt', 'wb').write(signed)
|
||||
with open(f'{sim_root_dir}/debug/p2sh-signed.psbt', 'wb') as f:
|
||||
f.write(signed)
|
||||
|
||||
#print('my finalization: ' + B2A(signed))
|
||||
|
||||
@ -450,7 +463,9 @@ def test_sign_p2sh_p2wpkh(match_key, use_regtest, start_sign, end_sign, bitcoind
|
||||
signed_psbt = end_sign(accept=True)
|
||||
|
||||
# use bitcoind to combine
|
||||
open('debug/signed.psbt', 'wb').write(signed_psbt)
|
||||
with open(f'{sim_root_dir}/debug/signed.psbt', 'wb') as f:
|
||||
f.write(signed_psbt)
|
||||
|
||||
resp = bitcoind.rpc.finalizepsbt(str(b64encode(signed_psbt), 'ascii'), True)
|
||||
|
||||
assert resp['complete'] == True, "bitcoind wasn't able to finalize it"
|
||||
@ -463,7 +478,8 @@ def test_sign_p2sh_p2wpkh(match_key, use_regtest, start_sign, end_sign, bitcoind
|
||||
@pytest.mark.bitcoind
|
||||
@pytest.mark.unfinalized
|
||||
def test_sign_p2sh_example(set_master_key, use_regtest, sim_execfile, start_sign, end_sign,
|
||||
decode_psbt_with_bitcoind, offer_ms_import, press_select, clear_ms):
|
||||
decode_psbt_with_bitcoind, offer_ms_import, press_select, clear_ms,
|
||||
sim_root_dir):
|
||||
# Use the private key given in BIP 174 and do similar signing
|
||||
# as the examples.
|
||||
|
||||
@ -504,7 +520,8 @@ def test_sign_p2sh_example(set_master_key, use_regtest, sim_execfile, start_sign
|
||||
start_sign(psbt)
|
||||
part_signed = end_sign(True)
|
||||
|
||||
open('debug/ex-signed-part.psbt', 'wb').write(part_signed)
|
||||
with open(f'{sim_root_dir}/debug/ex-signed-part.psbt', 'wb') as f:
|
||||
f.write(part_signed)
|
||||
|
||||
b4 = BasicPSBT().parse(psbt)
|
||||
aft = BasicPSBT().parse(part_signed)
|
||||
@ -514,7 +531,9 @@ def test_sign_p2sh_example(set_master_key, use_regtest, sim_execfile, start_sign
|
||||
start_sign(part_signed, finalize=False)
|
||||
signed = end_sign(True, finalize=False)
|
||||
|
||||
open('debug/ex-signed.psbt', 'wb').write(signed)
|
||||
with open(f'{sim_root_dir}/debug/ex-signed.psbt', 'wb') as f:
|
||||
f.write(signed)
|
||||
|
||||
aft2 = BasicPSBT().parse(signed)
|
||||
|
||||
decode = decode_psbt_with_bitcoind(signed)
|
||||
@ -542,7 +561,8 @@ def test_sign_p2sh_example(set_master_key, use_regtest, sim_execfile, start_sign
|
||||
|
||||
|
||||
@pytest.mark.bitcoind
|
||||
def test_change_case(start_sign, use_regtest, end_sign, check_against_bitcoind, cap_story):
|
||||
def test_change_case(start_sign, use_regtest, end_sign, check_against_bitcoind, cap_story,
|
||||
sim_root_dir):
|
||||
# is change shown/hidden at right times. no fraud checks
|
||||
|
||||
# NOTE: out#1 is change:
|
||||
@ -562,7 +582,8 @@ def test_change_case(start_sign, use_regtest, end_sign, check_against_bitcoind,
|
||||
check_against_bitcoind(B2A(b4.txn), Decimal('0.00000294'), change_outs=[1,])
|
||||
|
||||
signed = end_sign(True)
|
||||
open('debug/chg-signed.psbt', 'wb').write(signed)
|
||||
with open(f'{sim_root_dir}/debug/chg-signed.psbt', 'wb') as f:
|
||||
f.write(signed)
|
||||
|
||||
# modify it: remove bip32 path
|
||||
b4.outputs[1].bip32_paths = {}
|
||||
@ -581,14 +602,17 @@ def test_change_case(start_sign, use_regtest, end_sign, check_against_bitcoind,
|
||||
check_against_bitcoind(B2A(b4.txn), Decimal('0.00000294'), change_outs=[])
|
||||
|
||||
signed2 = end_sign(True)
|
||||
open('debug/chg-signed2.psbt', 'wb').write(signed)
|
||||
with open(f'{sim_root_dir}/debug/chg-signed2.psbt', 'wb') as f:
|
||||
f.write(signed)
|
||||
|
||||
aft = BasicPSBT().parse(signed)
|
||||
aft2 = BasicPSBT().parse(signed2)
|
||||
assert aft.txn == aft2.txn
|
||||
|
||||
@pytest.mark.parametrize('case', [ 1, 2])
|
||||
@pytest.mark.bitcoind
|
||||
def test_change_fraud_path(start_sign, use_regtest, end_sign, case, check_against_bitcoind, cap_story):
|
||||
def test_change_fraud_path(start_sign, use_regtest, end_sign, case, check_against_bitcoind,
|
||||
cap_story, sim_root_dir):
|
||||
# fraud: BIP-32 path of output doesn't lead to pubkey indicated
|
||||
|
||||
# NOTE: out#1 is change:
|
||||
@ -612,7 +636,8 @@ def test_change_fraud_path(start_sign, use_regtest, end_sign, case, check_agains
|
||||
b4.serialize(fd)
|
||||
mod_psbt = fd.getvalue()
|
||||
|
||||
open('debug/mod-%d.psbt' % case, 'wb').write(mod_psbt)
|
||||
with open(f'{sim_root_dir}/debug/mod-%d.psbt' % case, 'wb') as f:
|
||||
f.write(mod_psbt)
|
||||
|
||||
if case == 1:
|
||||
start_sign(mod_psbt)
|
||||
@ -631,7 +656,8 @@ def test_change_fraud_path(start_sign, use_regtest, end_sign, case, check_agains
|
||||
end_sign(True)
|
||||
|
||||
@pytest.mark.bitcoind
|
||||
def test_change_fraud_addr(start_sign, end_sign, use_regtest, check_against_bitcoind, cap_story):
|
||||
def test_change_fraud_addr(start_sign, end_sign, use_regtest, check_against_bitcoind, cap_story,
|
||||
sim_root_dir):
|
||||
# fraud: BIP-32 path of output doesn't match TXO address
|
||||
# NOTE: out#1 is change:
|
||||
#chg_addr = 'mvBGHpVtTyjmcfSsy6f715nbTGvwgbgbwo'
|
||||
@ -653,7 +679,8 @@ def test_change_fraud_addr(start_sign, end_sign, use_regtest, check_against_bitc
|
||||
b4.serialize(fd)
|
||||
mod_psbt = fd.getvalue()
|
||||
|
||||
open('debug/mod-addr.psbt', 'wb').write(mod_psbt)
|
||||
with open(f'{sim_root_dir}/debug/mod-addr.psbt', 'wb') as f:
|
||||
f.write(mod_psbt)
|
||||
|
||||
start_sign(mod_psbt)
|
||||
with pytest.raises(CCProtoError) as ee:
|
||||
@ -664,13 +691,15 @@ def test_change_fraud_addr(start_sign, end_sign, use_regtest, check_against_bitc
|
||||
@pytest.mark.parametrize('case', ['p2sh-p2wpkh', 'p2wpkh', 'p2sh', 'p2sh-p2pkh'])
|
||||
@pytest.mark.bitcoind
|
||||
def test_change_p2sh_p2wpkh(start_sign, end_sign, check_against_bitcoind, use_regtest,
|
||||
cap_story, case):
|
||||
cap_story, case, sim_root_dir):
|
||||
# not fraud: output address encoded in various equiv forms
|
||||
use_regtest()
|
||||
# NOTE: out#1 is change:
|
||||
#chg_addr = 'mvBGHpVtTyjmcfSsy6f715nbTGvwgbgbwo'
|
||||
|
||||
psbt = open('data/example-change.psbt', 'rb').read()
|
||||
with open('data/example-change.psbt', 'rb') as f:
|
||||
psbt = f.read()
|
||||
|
||||
b4 = BasicPSBT().parse(psbt)
|
||||
|
||||
t = CTransaction()
|
||||
@ -720,7 +749,8 @@ def test_change_p2sh_p2wpkh(start_sign, end_sign, check_against_bitcoind, use_re
|
||||
b4.serialize(fd)
|
||||
mod_psbt = fd.getvalue()
|
||||
|
||||
open('debug/mod-%s.psbt' % case, 'wb').write(mod_psbt)
|
||||
with open(f'{sim_root_dir}/debug/mod-%s.psbt' % case, 'wb') as f:
|
||||
f.write(mod_psbt)
|
||||
|
||||
start_sign(mod_psbt)
|
||||
|
||||
@ -817,7 +847,8 @@ def test_sign_multisig_partial_fail(start_sign, end_sign):
|
||||
assert 'None of the keys involved' in str(ee)
|
||||
|
||||
@pytest.mark.unfinalized
|
||||
def test_sign_wutxo(start_sign, set_seed_words, end_sign, cap_story, sim_exec, sim_execfile):
|
||||
def test_sign_wutxo(start_sign, set_seed_words, end_sign, cap_story, sim_exec, sim_execfile,
|
||||
sim_root_dir):
|
||||
|
||||
# Example from SomberNight: we can sign it, but signature won't be accepted by
|
||||
# network because the PSBT lies about the UTXO amount and tries to give away to miners,
|
||||
@ -852,11 +883,13 @@ def test_sign_wutxo(start_sign, set_seed_words, end_sign, cap_story, sim_exec, s
|
||||
|
||||
signed = end_sign(True, finalize=fin)
|
||||
|
||||
open('debug/sn-signed.'+ ('txn' if fin else 'psbt'), 'wt').write(B2A(signed))
|
||||
with open(f'{sim_root_dir}/debug/sn-signed.'+ ('txn' if fin else 'psbt'), 'wt') as f:
|
||||
f.write(B2A(signed))
|
||||
|
||||
@pytest.mark.parametrize('fee_max', [ 10, 25, 50])
|
||||
@pytest.mark.parametrize('under', [ False, True])
|
||||
def test_network_fee_amts(fee_max, under, fake_txn, try_sign, start_sign, dev, settings_set, sim_exec, cap_story):
|
||||
def test_network_fee_amts(fee_max, under, fake_txn, try_sign, start_sign, dev, settings_set,
|
||||
sim_exec, cap_story, sim_root_dir):
|
||||
|
||||
settings_set('fee_limit', fee_max)
|
||||
|
||||
@ -866,7 +899,8 @@ def test_network_fee_amts(fee_max, under, fake_txn, try_sign, start_sign, dev, s
|
||||
|
||||
psbt = fake_txn(1, 1, dev.master_xpub, fee=None, outvals=[outval])
|
||||
|
||||
open('debug/fee.psbt', 'wb').write(psbt)
|
||||
with open(f'{sim_root_dir}/debug/fee.psbt', 'wb') as f:
|
||||
f.write(psbt)
|
||||
|
||||
if not under:
|
||||
with pytest.raises(CCProtoError) as ee:
|
||||
@ -885,7 +919,8 @@ def test_network_fee_amts(fee_max, under, fake_txn, try_sign, start_sign, dev, s
|
||||
|
||||
settings_set('fee_limit', 10)
|
||||
|
||||
def test_network_fee_unlimited(fake_txn, start_sign, end_sign, dev, settings_set, cap_story):
|
||||
def test_network_fee_unlimited(fake_txn, start_sign, end_sign, dev, settings_set, cap_story,
|
||||
sim_root_dir):
|
||||
|
||||
settings_set('fee_limit', -1)
|
||||
|
||||
@ -894,7 +929,8 @@ def test_network_fee_unlimited(fake_txn, start_sign, end_sign, dev, settings_set
|
||||
|
||||
psbt = fake_txn(1, 1, dev.master_xpub, fee=None, outvals=[outval])
|
||||
|
||||
open('debug/fee-un.psbt', 'wb').write(psbt)
|
||||
with open(f'{sim_root_dir}/debug/fee-un.psbt', 'wb') as f:
|
||||
f.write(psbt)
|
||||
|
||||
# should be able to sign, but get warning
|
||||
start_sign(psbt, False)
|
||||
@ -917,15 +953,17 @@ def test_network_fee_unlimited(fake_txn, start_sign, end_sign, dev, settings_set
|
||||
@pytest.mark.parametrize('out_style', ADDR_STYLES_SINGLE)
|
||||
@pytest.mark.parametrize('visualized', [0, STXN_VISUALIZE, STXN_VISUALIZE|STXN_SIGNED])
|
||||
def test_change_outs(fake_txn, start_sign, end_sign, cap_story, dev, num_outs, master_xpub,
|
||||
act_outs, segwit, out_style, visualized, add_xpub, num_ins=3):
|
||||
act_outs, segwit, out_style, visualized, add_xpub, sim_root_dir):
|
||||
# create a TXN which has change outputs, which shouldn't be shown to user, and also not fail.
|
||||
xp = dev.master_xpub
|
||||
|
||||
num_ins = 3
|
||||
couts = num_outs if act_outs == -1 else num_ins-act_outs
|
||||
psbt = fake_txn(num_ins, num_outs, xp, segwit_in=segwit,
|
||||
outstyles=[out_style], change_outputs=range(couts), add_xpub=add_xpub)
|
||||
|
||||
open('debug/change.psbt', 'wb').write(psbt)
|
||||
with open(f'{sim_root_dir}/debug/change.psbt', 'wb') as f:
|
||||
f.write(psbt)
|
||||
|
||||
# should be able to sign, but get warning
|
||||
if not visualized:
|
||||
@ -1001,7 +1039,8 @@ def KEEP_test_random_psbt(try_sign, sim_exec, fname="data/ .psbt"):
|
||||
@pytest.mark.bitcoind
|
||||
@pytest.mark.unfinalized
|
||||
@pytest.mark.parametrize('num_dests', [ 1, 10, 25 ])
|
||||
def test_finalization_vs_bitcoind(match_key, use_regtest, check_against_bitcoind, bitcoind, start_sign, end_sign, num_dests):
|
||||
def test_finalization_vs_bitcoind(match_key, use_regtest, check_against_bitcoind, bitcoind,
|
||||
start_sign, end_sign, num_dests, sim_root_dir):
|
||||
# Compare how we finalize vs bitcoind ... should be exactly the same txn
|
||||
wallet_xfp = match_key
|
||||
# has to be after match key
|
||||
@ -1030,7 +1069,8 @@ def test_finalization_vs_bitcoind(match_key, use_regtest, check_against_bitcoind
|
||||
fee = resp['fee']
|
||||
chg_pos = resp['changepos']
|
||||
|
||||
open('debug/vs.psbt', 'wb').write(psbt)
|
||||
with open(f'{sim_root_dir}/debug/vs.psbt', 'wb') as f:
|
||||
f.write(psbt)
|
||||
|
||||
# check some basics
|
||||
mine = BasicPSBT().parse(psbt)
|
||||
@ -1053,13 +1093,15 @@ def test_finalization_vs_bitcoind(match_key, use_regtest, check_against_bitcoind
|
||||
|
||||
signed_final = end_sign(accept=True, finalize=True)
|
||||
assert signed_final[0:4] != b'psbt', "expecting raw bitcoin txn"
|
||||
open('debug/finalized-by-ckcc.txn', 'wt').write(B2A(signed_final))
|
||||
with open(f'{sim_root_dir}/debug/finalized-by-ckcc.txn', 'wt') as f:
|
||||
f.write(B2A(signed_final))
|
||||
|
||||
# Sign again, but don't finalize it.
|
||||
start_sign(psbt, finalize=False)
|
||||
signed = end_sign(accept=True)
|
||||
|
||||
open('debug/vs-signed-unfin.psbt', 'wb').write(signed)
|
||||
with open(f'{sim_root_dir}/debug/vs-signed-unfin.psbt', 'wb') as f:
|
||||
f.write(signed)
|
||||
|
||||
# Use bitcoind to finalize it this time.
|
||||
resp = bitcoind.supply_wallet.finalizepsbt(str(b64encode(signed), 'ascii'), True)
|
||||
@ -1069,7 +1111,8 @@ def test_finalization_vs_bitcoind(match_key, use_regtest, check_against_bitcoind
|
||||
|
||||
# assert resp['complete']
|
||||
#print("Final txn: %r" % network)
|
||||
open('debug/finalized-by-btcd.txn', 'wt').write(B2A(network))
|
||||
with open(f'{sim_root_dir}/debug/finalized-by-btcd.txn', 'wt') as f:
|
||||
f.write(B2A(network))
|
||||
|
||||
assert network == signed_final, "Finalized differently"
|
||||
|
||||
@ -1091,7 +1134,7 @@ def test_finalization_vs_bitcoind(match_key, use_regtest, check_against_bitcoind
|
||||
("44'/1'/0'/3000/5", '2nd last component'),
|
||||
("44'/1'/0'/3/5", '2nd last component'),
|
||||
])
|
||||
def test_change_troublesome(dev, start_sign, cap_story, try_path, expect):
|
||||
def test_change_troublesome(dev, start_sign, cap_story, try_path, expect, sim_root_dir):
|
||||
# NOTE: out#1 is change:
|
||||
# addr = 'mvBGHpVtTyjmcfSsy6f715nbTGvwgbgbwo'
|
||||
# path = (m=4369050F)/44'/1'/0'/1/5
|
||||
@ -1115,7 +1158,8 @@ def test_change_troublesome(dev, start_sign, cap_story, try_path, expect):
|
||||
b4.serialize(fd)
|
||||
mod_psbt = fd.getvalue()
|
||||
|
||||
open('debug/troublesome.psbt', 'wb').write(mod_psbt)
|
||||
with open(f'{sim_root_dir}/debug/troublesome.psbt', 'wb') as f:
|
||||
f.write(mod_psbt)
|
||||
|
||||
start_sign(mod_psbt)
|
||||
time.sleep(0.1)
|
||||
@ -1200,8 +1244,6 @@ def spend_outputs(funding_psbt, finalized_txn, tweaker=None):
|
||||
with BytesIO() as rv:
|
||||
nn.serialize(rv)
|
||||
raw = rv.getvalue()
|
||||
|
||||
open('debug/spend_outs.psbt', 'wb').write(raw)
|
||||
|
||||
return nn, raw
|
||||
|
||||
@ -1231,7 +1273,7 @@ def txid_from_export_prompt(cap_story, cap_screen_qr, cap_screen, need_keypress)
|
||||
@pytest.mark.parametrize('segwit_in', [False, True])
|
||||
def test_bip143_attack_data_capture(num_utxo, segwit_in, try_sign, fake_txn, settings_set,
|
||||
settings_get, cap_story, sim_exec, hist_count,
|
||||
txid_from_export_prompt, press_cancel):
|
||||
txid_from_export_prompt, press_cancel, sim_root_dir):
|
||||
|
||||
# cleanup prev runs, if very first time thru
|
||||
sim_exec('import history; history.OutptValueCache.clear()')
|
||||
@ -1243,7 +1285,8 @@ def test_bip143_attack_data_capture(num_utxo, segwit_in, try_sign, fake_txn, set
|
||||
outstyles=(['p2wpkh']*num_utxo) + ['p2wpkh-p2sh', 'p2pkh'])
|
||||
_, txn = try_sign(psbt, accept=True, finalize=True, exit_export_loop=False)
|
||||
|
||||
open('debug/funding.psbt', 'wb').write(psbt)
|
||||
with open(f'{sim_root_dir}/debug/funding.psbt', 'wb') as f:
|
||||
f.write(psbt)
|
||||
|
||||
num_inp_utxo = (1 if segwit_in else 0)
|
||||
|
||||
@ -1266,6 +1309,8 @@ def test_bip143_attack_data_capture(num_utxo, segwit_in, try_sign, fake_txn, set
|
||||
assert all_utxo == hist_b4+num_utxo+num_inp_utxo
|
||||
# build a new PSBT based on those change outputs
|
||||
psbt2, raw = spend_outputs(psbt, txn)
|
||||
with open(f'{sim_root_dir}/debug/spend_outs.psbt', 'wb') as f:
|
||||
f.write(raw)
|
||||
|
||||
# try to sign that ... should work fine
|
||||
try_sign(raw, accept=True, finalize=True)
|
||||
@ -1281,6 +1326,8 @@ def test_bip143_attack_data_capture(num_utxo, segwit_in, try_sign, fake_txn, set
|
||||
spendables[0][1].nValue += amt
|
||||
|
||||
psbt3, raw = spend_outputs(psbt, txn, tweaker=value_tweak)
|
||||
with open(f'{sim_root_dir}/debug/spend_outs.psbt', 'wb') as f:
|
||||
f.write(raw)
|
||||
with pytest.raises(CCProtoError) as ee:
|
||||
orig, result = try_sign(raw, accept=True, finalize=True)
|
||||
|
||||
@ -1352,7 +1399,8 @@ def test_sdcard_signing(encoding, num_outs, del_after, partial, try_sign_microsd
|
||||
@pytest.mark.unfinalized
|
||||
@pytest.mark.parametrize('num_ins', [2,3,8])
|
||||
@pytest.mark.parametrize('num_outs', [1,2,8])
|
||||
def test_payjoin_signing(num_ins, num_outs, fake_txn, try_sign, start_sign, end_sign, cap_story):
|
||||
def test_payjoin_signing(num_ins, num_outs, fake_txn, try_sign, start_sign, end_sign,
|
||||
cap_story, sim_root_dir):
|
||||
|
||||
# Try to simulate a PSBT that might be involved in a Payjoin (BIP-78 txn)
|
||||
|
||||
@ -1362,7 +1410,8 @@ def test_payjoin_signing(num_ins, num_outs, fake_txn, try_sign, start_sign, end_
|
||||
|
||||
psbt = fake_txn(num_ins, num_outs, segwit_in=True, psbt_hacker=hack)
|
||||
|
||||
open('debug/payjoin.psbt', 'wb').write(psbt)
|
||||
with open(f'{sim_root_dir}/debug/payjoin.psbt', 'wb') as f:
|
||||
f.write(psbt)
|
||||
|
||||
ip = start_sign(psbt, finalize=False)
|
||||
time.sleep(.1)
|
||||
@ -1414,7 +1463,7 @@ def test_wrong_xfp(fake_txn, try_sign, segwit):
|
||||
assert 'found 12345678' in str(ee)
|
||||
|
||||
@pytest.mark.parametrize('segwit', [False, True])
|
||||
def test_wrong_xfp_multi(fake_txn, try_sign, segwit):
|
||||
def test_wrong_xfp_multi(fake_txn, try_sign, segwit, sim_root_dir):
|
||||
|
||||
# A PSBT which is unsigned and doesn't involve our XFP value
|
||||
# - but multiple wrong XFP values
|
||||
@ -1431,7 +1480,9 @@ def test_wrong_xfp_multi(fake_txn, try_sign, segwit):
|
||||
|
||||
psbt = fake_txn(7, 2, segwit_in=segwit, psbt_hacker=hack)
|
||||
|
||||
open('debug/wrong-xfp.psbt', 'wb').write(psbt)
|
||||
with open(f'{sim_root_dir}/debug/wrong-xfp.psbt', 'wb') as f:
|
||||
f.write(psbt)
|
||||
|
||||
with pytest.raises(CCProtoError) as ee:
|
||||
orig, result = try_sign(psbt, accept=True)
|
||||
|
||||
@ -1445,7 +1496,8 @@ def test_wrong_xfp_multi(fake_txn, try_sign, segwit):
|
||||
@pytest.mark.parametrize('out_style', ADDR_STYLES_SINGLE)
|
||||
@pytest.mark.parametrize('segwit', [False, True])
|
||||
@pytest.mark.parametrize('outval', ['.5', '.788888', '0.92640866'])
|
||||
def test_render_outs(out_style, segwit, outval, fake_txn, start_sign, end_sign, dev):
|
||||
def test_render_outs(out_style, segwit, outval, fake_txn, start_sign, end_sign, dev,
|
||||
sim_root_dir):
|
||||
# check how we render the value of outputs
|
||||
# - works on simulator and connected USB real-device
|
||||
xp = dev.master_xpub
|
||||
@ -1454,7 +1506,8 @@ def test_render_outs(out_style, segwit, outval, fake_txn, start_sign, end_sign,
|
||||
psbt = fake_txn(1, 2, dev.master_xpub, segwit_in=segwit, outvals=[oi, int(1E8-oi)],
|
||||
outstyles=[out_style], change_outputs=[1])
|
||||
|
||||
open('debug/render.psbt', 'wb').write(psbt)
|
||||
with open(f'{sim_root_dir}/debug/render.psbt', 'wb') as f:
|
||||
f.write(psbt)
|
||||
|
||||
# should be able to sign, but get warning
|
||||
|
||||
@ -1501,7 +1554,8 @@ def test_negative_fee(dev, fake_txn, try_sign):
|
||||
( 5, 'mXTN'),
|
||||
( 2, 'bits'),
|
||||
( 0, 'sats')])
|
||||
def test_value_render(dev, units, fake_txn, start_sign, cap_story, settings_set, settings_remove):
|
||||
def test_value_render(dev, units, fake_txn, start_sign, cap_story, settings_set,
|
||||
settings_remove, sim_root_dir):
|
||||
|
||||
# Check we are rendering values in right units.
|
||||
decimal, units = units
|
||||
@ -1517,7 +1571,8 @@ def test_value_render(dev, units, fake_txn, start_sign, cap_story, settings_set,
|
||||
need = sum(outputs)
|
||||
psbt = fake_txn(1, len(outputs), dev.master_xpub, segwit_in=True, outvals=outputs, invals=[need])
|
||||
|
||||
open('debug/values.psbt', 'wb').write(psbt)
|
||||
with open(f'{sim_root_dir}/debug/values.psbt', 'wb') as f:
|
||||
f.write(psbt)
|
||||
|
||||
ip = start_sign(psbt, finalize=False)
|
||||
time.sleep(.1)
|
||||
@ -1542,12 +1597,13 @@ def test_value_render(dev, units, fake_txn, start_sign, cap_story, settings_set,
|
||||
@pytest.mark.parametrize('num_out', [1,2,3])
|
||||
@pytest.mark.parametrize('segwit', [True, False])
|
||||
def test_qr_txn(num_in, num_out, segwit, fake_txn, try_sign, dev, cap_screen_qr,
|
||||
qr_quality_check, cap_story, need_keypress, is_q1, press_cancel):
|
||||
qr_quality_check, cap_story, need_keypress, is_q1, press_cancel,
|
||||
sim_root_dir):
|
||||
|
||||
psbt = fake_txn(num_in, num_out, dev.master_xpub, segwit_in=segwit)
|
||||
|
||||
_, txn = try_sign(psbt, accept=True, finalize=True, exit_export_loop=False)
|
||||
with open('debug/last.txn', 'wb') as f:
|
||||
with open(f'{sim_root_dir}/debug/last.txn', 'wb') as f:
|
||||
f.write(txn)
|
||||
|
||||
title, story = cap_story()
|
||||
@ -1833,7 +1889,7 @@ def test_op_return_signing(op_return_data, dev, fake_txn, bitcoind_d_sim_watch,
|
||||
({b"x" * 64: b"y" * 128}, {b"q" * 64: b"p" * 128}, {b"w" * 90: b"z" * 256}),
|
||||
({b"x" * 32: b"y" * 256}, {b"q" * 32: b"p" * 256, b"f" * 15: 32 * b"\x01"}, {b"w": b"z"}),
|
||||
])
|
||||
def test_unknow_values_in_psbt(unknowns, dev, start_sign, end_sign, fake_txn):
|
||||
def test_unknow_values_in_psbt(unknowns, dev, start_sign, end_sign, fake_txn, sim_root_dir):
|
||||
unknown_global, unknown_ins, unknown_outs = unknowns
|
||||
def hack(psbt):
|
||||
psbt.unknown = unknown_global
|
||||
@ -1843,7 +1899,8 @@ def test_unknow_values_in_psbt(unknowns, dev, start_sign, end_sign, fake_txn):
|
||||
o.unknown = unknown_outs
|
||||
|
||||
psbt = fake_txn(5, 5, dev.master_xpub, segwit_in=True, psbt_hacker=hack)
|
||||
open('debug/last.psbt', 'wb').write(psbt)
|
||||
with open(f'{sim_root_dir}/debug/last.psbt', 'wb') as f:
|
||||
f.write(psbt)
|
||||
psbt_o = BasicPSBT().parse(psbt)
|
||||
assert psbt_o.unknown == unknown_global
|
||||
for inp in psbt_o.inputs:
|
||||
@ -1860,7 +1917,7 @@ def test_unknow_values_in_psbt(unknowns, dev, start_sign, end_sign, fake_txn):
|
||||
for out in res.outputs:
|
||||
assert out.unknown == unknown_outs
|
||||
|
||||
def test_read_write_prop_attestation_keys(try_sign, fake_txn):
|
||||
def test_read_write_prop_attestation_keys(try_sign, fake_txn, sim_root_dir):
|
||||
from psbt import ser_prop_key, PSBT_PROP_CK_ID
|
||||
def attach_attest_to_outs(psbt):
|
||||
for idx, o in enumerate(psbt.outputs):
|
||||
@ -1869,7 +1926,8 @@ def test_read_write_prop_attestation_keys(try_sign, fake_txn):
|
||||
o.proprietary[key] = value
|
||||
|
||||
psbt = fake_txn(2, 2, psbt_hacker=attach_attest_to_outs)
|
||||
open('debug/propkeys.psbt', 'wb').write(psbt)
|
||||
with open(f'{sim_root_dir}/debug/propkeys.psbt', 'wb') as f:
|
||||
f.write(psbt)
|
||||
orig, signed = try_sign(psbt)
|
||||
res = BasicPSBT().parse(signed)
|
||||
|
||||
@ -1910,7 +1968,7 @@ def test_duplicate_unknow_values_in_psbt(dev, start_sign, end_sign, fake_txn):
|
||||
@pytest.fixture
|
||||
def _test_single_sig_sighash(cap_story, press_select, start_sign, end_sign, dev,
|
||||
bitcoind, bitcoind_d_dev_watch, settings_set,
|
||||
finalize_v2_v0_convert, pytestconfig):
|
||||
finalize_v2_v0_convert, pytestconfig, sim_root_dir):
|
||||
def doit(addr_fmt, sighash, num_inputs=2, num_outputs=2, consolidation=False, sh_checks=False,
|
||||
psbt_v2=None, tx_check=True):
|
||||
|
||||
@ -1984,7 +2042,7 @@ def _test_single_sig_sighash(cap_story, press_select, start_sign, end_sign, dev,
|
||||
psbt_sh = x.as_b64_str()
|
||||
|
||||
# make useful reference psbt along the way
|
||||
with open(f'debug/sighash-{sighash[0] if len(sighash) == 1 else "MIX"}.psbt'\
|
||||
with open(f'{sim_root_dir}/debug/sighash-{sighash[0] if len(sighash) == 1 else "MIX"}.psbt'\
|
||||
.replace('|', '-'), 'wt') as f:
|
||||
f.write(psbt_sh)
|
||||
|
||||
@ -2847,7 +2905,7 @@ def random_nLockTime_test_cases(num=10):
|
||||
*random_nLockTime_test_cases()
|
||||
])
|
||||
def test_timelocks_visualize(start_sign, end_sign, dev, bitcoind, use_regtest,
|
||||
bitcoind_d_sim_watch, nLockTime):
|
||||
bitcoind_d_sim_watch, nLockTime, sim_root_dir):
|
||||
# - works on simulator and connected USB real-device
|
||||
nLockTime, expect_ux = nLockTime
|
||||
num_ins = 10
|
||||
@ -2883,7 +2941,8 @@ def test_timelocks_visualize(start_sign, end_sign, dev, bitcoind, use_regtest,
|
||||
)
|
||||
psbt = base64.b64decode(psbt_resp.get("psbt"))
|
||||
|
||||
open('debug/locktimes.psbt', 'wb').write(psbt)
|
||||
with open(f'{sim_root_dir}/debug/locktimes.psbt', 'wb') as f:
|
||||
f.write(psbt)
|
||||
|
||||
# should be able to sign, but get warning
|
||||
|
||||
@ -2905,7 +2964,7 @@ def test_timelocks_visualize(start_sign, end_sign, dev, bitcoind, use_regtest,
|
||||
def test_base64_psbt_qr(in_out, partial, segwit, scan_a_qr, readback_bbqr,
|
||||
goto_home, use_regtest, cap_story, fake_txn, dev,
|
||||
decode_psbt_with_bitcoind, decode_with_bitcoind,
|
||||
press_cancel, press_select, need_keypress):
|
||||
press_cancel, press_select, need_keypress, sim_root_dir):
|
||||
def hack(psbt):
|
||||
if partial:
|
||||
# change first input to not be ours
|
||||
@ -2923,7 +2982,8 @@ def test_base64_psbt_qr(in_out, partial, segwit, scan_a_qr, readback_bbqr,
|
||||
|
||||
psbt = base64.b64encode(psbt).decode()
|
||||
|
||||
open('debug/last.psbt', 'w').write(psbt)
|
||||
with open(f'{sim_root_dir}/debug/last.psbt', 'w') as f:
|
||||
f.write(psbt)
|
||||
|
||||
goto_home()
|
||||
need_keypress(KEY_QR)
|
||||
@ -3104,8 +3164,7 @@ def test_txout_explorer_op_return(finalize, data, fake_txn, start_sign, cap_stor
|
||||
assert s == dd0
|
||||
assert e == dd1
|
||||
qr = qr_list[i - 20]
|
||||
assert qr.startswith(s)
|
||||
assert qr.endswith(e)
|
||||
assert qr == ""
|
||||
|
||||
press_cancel() # exit txn out explorer
|
||||
end_sign(finalize=finalize)
|
||||
@ -3180,7 +3239,7 @@ def test_zero_value_outputs(num_outs, change, fake_txn, start_sign, end_sign, re
|
||||
psbt = fake_txn(1, num_outs, outvals=num_outs*[0], change_outputs=change_outs, input_amount=1)
|
||||
start_sign(psbt, False, stxn_flags=STXN_VISUALIZE)
|
||||
story = end_sign(accept=None, expect_txn=False).decode()
|
||||
assert f"Zero Value: Non-standard zero value outputs: {num_outs}" in story
|
||||
assert f"Zero Value: Non-standard zero value output(s)" in story
|
||||
assert "1 input" in story
|
||||
assert f"{num_outs} output{'' if num_outs == 1 else 's'}" in story
|
||||
assert 'Network fee 0.00000001 XTN' in story
|
||||
|
||||
@ -20,7 +20,7 @@ def THIS_FILE_requires_q1(is_q1, is_headless):
|
||||
if not is_q1 or is_headless:
|
||||
raise pytest.skip('Q1 only (not headless)')
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def rx_start(grab_payload, goto_home, pick_menu_item):
|
||||
def doit(**kws):
|
||||
goto_home()
|
||||
@ -31,7 +31,7 @@ def rx_start(grab_payload, goto_home, pick_menu_item):
|
||||
|
||||
return doit
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def main_do_over(unit_test, settings_get, settings_set):
|
||||
# reset all contents, including master secret ... except ktrx
|
||||
# - so you can test backup-restore onto blank unit
|
||||
@ -42,7 +42,7 @@ def main_do_over(unit_test, settings_get, settings_set):
|
||||
|
||||
return doit
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def grab_payload(press_select, need_keypress, press_cancel, nfc_read_url, cap_story, nfc_block4rf, cap_screen_qr, readback_bbqr):
|
||||
|
||||
# started the process; capture pw/code and QR contents, verify NFC works
|
||||
@ -108,7 +108,7 @@ def grab_payload(press_select, need_keypress, press_cancel, nfc_read_url, cap_s
|
||||
|
||||
return doit
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def rx_complete(press_select, need_keypress, press_cancel, cap_story, scan_a_qr, enter_complex, cap_screen, goto_home, split_scan_bbqr):
|
||||
# finish the teleport by doing QR and getting data
|
||||
def doit(data, pw, expect_fail=False, expect_xfp=None):
|
||||
@ -126,10 +126,13 @@ def rx_complete(press_select, need_keypress, press_cancel, cap_story, scan_a_qr,
|
||||
if expect_fail:
|
||||
time.sleep(.200)
|
||||
return
|
||||
for retries in range(20):
|
||||
|
||||
for _ in range(10):
|
||||
scr = cap_screen()
|
||||
if 'Teleport Password' in scr: break
|
||||
time.sleep(.200)
|
||||
time.sleep(.2)
|
||||
else:
|
||||
assert False, "Teleport Password not in screen"
|
||||
|
||||
if expect_xfp:
|
||||
assert xfp2str(expect_xfp) in scr
|
||||
@ -140,7 +143,7 @@ def rx_complete(press_select, need_keypress, press_cancel, cap_story, scan_a_qr,
|
||||
|
||||
return doit
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def tx_start(press_select, need_keypress, press_cancel, goto_home, pick_menu_item, cap_story, scan_a_qr, enter_complex, cap_screen):
|
||||
|
||||
# start the Tx process, capturing password and leaving you are picker menu
|
||||
@ -150,13 +153,15 @@ def tx_start(press_select, need_keypress, press_cancel, goto_home, pick_menu_ite
|
||||
time.sleep(.250) # required
|
||||
scan_a_qr(rx_qr)
|
||||
|
||||
time.sleep(.250) # required
|
||||
scr = cap_screen()
|
||||
if expect_fail:
|
||||
assert expect_fail in scr
|
||||
return
|
||||
|
||||
assert 'Teleport Password (number)' in scr
|
||||
for _ in range(10):
|
||||
scr = cap_screen()
|
||||
if expect_fail and expect_fail in scr:
|
||||
return
|
||||
elif 'Teleport Password (number)' in scr:
|
||||
break
|
||||
time.sleep(.2)
|
||||
else:
|
||||
assert False, "Teleport Password not in screen"
|
||||
|
||||
enter_complex(rx_code)
|
||||
time.sleep(.150) # required
|
||||
@ -416,10 +421,10 @@ def test_tx_wrong_pub(rx_start, tx_start, cap_menu, enter_complex, pick_menu_ite
|
||||
@pytest.mark.parametrize('segwit', [True])
|
||||
@pytest.mark.parametrize('incl_xpubs', [ False ])
|
||||
def test_teleport_ms_sign(M, use_regtest, make_myself_wallet, segwit, num_ins, dev, clear_ms,
|
||||
fake_ms_txn, try_sign, incl_xpubs, bitcoind, cap_story, need_keypress,
|
||||
fake_ms_txn, try_sign, incl_xpubs, bitcoind, cap_story, need_keypress,
|
||||
cap_menu, pick_menu_item, grab_payload, rx_complete, press_select,
|
||||
ndef_parse_txn_psbt, press_nfc, nfc_read, settings_get, settings_set,
|
||||
txid_from_export_prompt):
|
||||
txid_from_export_prompt, sim_root_dir):
|
||||
|
||||
# IMPORTANT: won't work if you start simulator with --ms flag. Use no args
|
||||
all_out_styles = list(unmap_addr_fmt.keys())
|
||||
@ -436,13 +441,15 @@ def test_teleport_ms_sign(M, use_regtest, make_myself_wallet, segwit, num_ins, d
|
||||
psbt = fake_ms_txn(num_ins, num_outs, M, keys, segwit_in=segwit, incl_xpubs=incl_xpubs,
|
||||
outstyles=all_out_styles, change_outputs=list(range(1,num_outs)))
|
||||
|
||||
open(f'debug/myself-before.psbt', 'wb').write(psbt)
|
||||
with open(f'{sim_root_dir}/debug/myself-before.psbt', 'wb') as f:
|
||||
f.write(psbt)
|
||||
|
||||
cur_wallet = 0
|
||||
my_xfp = select_wallet(cur_wallet)
|
||||
|
||||
_, updated = try_sign(psbt, accept_ms_import=incl_xpubs, exit_export_loop=False)
|
||||
open(f'debug/myself-after-1.psbt', 'wb').write(updated)
|
||||
with open(f'{sim_root_dir}/debug/myself-after-1.psbt', 'wb') as f:
|
||||
f.write(updated)
|
||||
assert updated != psbt
|
||||
|
||||
title, body = cap_story()
|
||||
@ -484,7 +491,8 @@ def test_teleport_ms_sign(M, use_regtest, make_myself_wallet, segwit, num_ins, d
|
||||
assert len(pw) == 8
|
||||
|
||||
nn = xfp2str(next_xfp)
|
||||
open(f'debug/next_qr_{nn}.txt', 'wt').write(f'{nn}\n\n{pw}\n\n{data}')
|
||||
with open(f'{sim_root_dir}/debug/next_qr_{nn}.txt', 'wt') as f:
|
||||
f.write(f'{nn}\n\n{pw}\n\n{data}')
|
||||
|
||||
time.sleep(.1)
|
||||
title, story = cap_story()
|
||||
@ -628,8 +636,7 @@ def test_teleport_real_ms(dev, fake_ms_txn):
|
||||
# py.test test_teleport.py --dev --manual -k test_teleport_real_ms
|
||||
#
|
||||
from bip32 import BIP32Node
|
||||
from struct import unpack
|
||||
from ckcc_protocol.protocol import CCProtocolPacker, CCProtoError
|
||||
from ckcc_protocol.protocol import CCProtocolPacker
|
||||
|
||||
M = N = 2
|
||||
|
||||
|
||||
@ -25,13 +25,13 @@ def upload_file(dev):
|
||||
return doit
|
||||
|
||||
@pytest.fixture
|
||||
def make_firmware():
|
||||
def doit(hw_compat, fname='../stm32/firmware-signed.bin', outname='tmp-firmware.bin'):
|
||||
def make_firmware(src_root_dir):
|
||||
def doit(hw_compat, fname=f'{src_root_dir}/stm32/firmware-signed.bin', outname='tmp-firmware.bin'):
|
||||
# os.system(f'signit sign 3.0.99 --keydir ../stm32/keys -r {fname} -o {outname} --hw-compat=0x{hw_compat:02x}')
|
||||
p = subprocess.run(
|
||||
[
|
||||
'signit', 'sign', '3.0.99',
|
||||
'--keydir', '../stm32/keys',
|
||||
'--keydir', f'{src_root_dir}/stm32/keys',
|
||||
'-r', f'{fname}',
|
||||
'-o', f'{outname}',
|
||||
f'--hw-compat={hw_compat}'
|
||||
@ -50,7 +50,7 @@ def make_firmware():
|
||||
return doit
|
||||
|
||||
@pytest.fixture
|
||||
def upgrade_by_sd(open_microsd, cap_story, pick_menu_item, goto_home, press_select, microsd_path, sim_exec):
|
||||
def upgrade_by_sd(open_microsd, cap_story, pick_menu_item, goto_home, press_select, microsd_path, sim_exec, src_root_dir):
|
||||
|
||||
# send a firmware file over the microSD card
|
||||
|
||||
@ -64,7 +64,7 @@ def upgrade_by_sd(open_microsd, cap_story, pick_menu_item, goto_home, press_sele
|
||||
# create DFU file (wrapper)
|
||||
open(f'{fname}.bin', 'wb').write(data)
|
||||
dfu = microsd_path('tmp-firmware.dfu')
|
||||
cmd = f'../external/micropython/tools/dfu.py -b 0x08008000:{fname}.bin {dfu}'
|
||||
cmd = f'{src_root_dir}/external/micropython/tools/dfu.py -b 0x08008000:{fname}.bin {dfu}'
|
||||
print(cmd)
|
||||
os.system(cmd)
|
||||
|
||||
|
||||
@ -826,7 +826,8 @@ def test_sign_file_from_list_files(f_len, goto_home, cap_story, pick_menu_item,
|
||||
|
||||
|
||||
def test_bip39_pw_signing_xfp_ux(pick_menu_item, press_select, cap_story, enter_complex,
|
||||
reset_seed_words, cap_menu, go_to_passphrase):
|
||||
reset_seed_words, cap_menu, go_to_passphrase, microsd_wipe):
|
||||
microsd_wipe() # need to wipe all PSBT on SD card so we do not proceed to signing
|
||||
go_to_passphrase()
|
||||
enter_complex("21coinkite21", apply=True)
|
||||
time.sleep(0.3)
|
||||
@ -834,6 +835,7 @@ def test_bip39_pw_signing_xfp_ux(pick_menu_item, press_select, cap_story, enter_
|
||||
assert title == "[0C9DC99D]"
|
||||
assert 'Above is the master key fingerprint of the new wallet' in story
|
||||
press_select() # confirm passphrase
|
||||
time.sleep(0.1)
|
||||
m = cap_menu()
|
||||
assert m[0] == "[0C9DC99D]"
|
||||
pick_menu_item("Ready To Sign")
|
||||
@ -973,8 +975,8 @@ def test_custom_pushtx_url(goto_home, pick_menu_item, press_select, enter_comple
|
||||
("coldcard-export.json", "J"),
|
||||
("coldcard-export.sig", "U"),
|
||||
])
|
||||
def test_bbqr_share_files(fname, ftype, readback_bbqr, need_keypress,
|
||||
goto_home, pick_menu_item, is_q1, cap_menu):
|
||||
def test_bbqr_share_files(fname, ftype, readback_bbqr, need_keypress, src_root_dir,
|
||||
goto_home, pick_menu_item, is_q1, cap_menu, sim_root_dir):
|
||||
goto_home()
|
||||
if not is_q1:
|
||||
pick_menu_item("Advanced/Tools")
|
||||
@ -982,8 +984,8 @@ def test_bbqr_share_files(fname, ftype, readback_bbqr, need_keypress,
|
||||
assert "BBQr File Share" not in cap_menu()
|
||||
return
|
||||
|
||||
fpath = "data/" + fname
|
||||
shutil.copy2(fpath, '../unix/work/MicroSD')
|
||||
fpath = f"{src_root_dir}/testing/data/" + fname
|
||||
shutil.copy2(fpath, f'{sim_root_dir}/MicroSD')
|
||||
pick_menu_item("Advanced/Tools")
|
||||
pick_menu_item("File Management")
|
||||
pick_menu_item("BBQr File Share")
|
||||
@ -995,14 +997,15 @@ def test_bbqr_share_files(fname, ftype, readback_bbqr, need_keypress,
|
||||
res = f.read()
|
||||
|
||||
assert res == rb
|
||||
os.remove('../unix/work/MicroSD/' + fname)
|
||||
os.remove(f'{sim_root_dir}/MicroSD/' + fname)
|
||||
|
||||
@pytest.mark.parametrize("fname", [
|
||||
"ccbk-start.json",
|
||||
"devils-txn.txn",
|
||||
"payjoin.psbt", # base64 string in file
|
||||
])
|
||||
def test_qr_share_files(fname, pick_menu_item, goto_home, is_q1, cap_menu, cap_screen_qr):
|
||||
def test_qr_share_files(fname, pick_menu_item, goto_home, is_q1, cap_menu, cap_screen_qr,
|
||||
src_root_dir, sim_root_dir):
|
||||
goto_home()
|
||||
if not is_q1:
|
||||
pick_menu_item("Advanced/Tools")
|
||||
@ -1010,8 +1013,8 @@ def test_qr_share_files(fname, pick_menu_item, goto_home, is_q1, cap_menu, cap_s
|
||||
assert "QR File Share" not in cap_menu()
|
||||
return
|
||||
|
||||
fpath = "data/" + fname
|
||||
shutil.copy2(fpath, '../unix/work/MicroSD')
|
||||
fpath = f"{src_root_dir}/testing/data/" + fname
|
||||
shutil.copy2(fpath, f'{sim_root_dir}/MicroSD')
|
||||
pick_menu_item("Advanced/Tools")
|
||||
pick_menu_item("File Management")
|
||||
pick_menu_item("QR File Share")
|
||||
@ -1022,7 +1025,7 @@ def test_qr_share_files(fname, pick_menu_item, goto_home, is_q1, cap_menu, cap_s
|
||||
res = f.read()
|
||||
|
||||
assert res == qr.decode()
|
||||
os.remove('../unix/work/MicroSD/' + fname)
|
||||
os.remove(f'{sim_root_dir}/MicroSD/' + fname)
|
||||
|
||||
|
||||
@pytest.mark.onetime
|
||||
|
||||
@ -30,7 +30,7 @@ def test_vd_basics(dev, virtdisk_path, is_simulator):
|
||||
|
||||
@pytest.fixture
|
||||
def try_sign_virtdisk(press_select, virtdisk_path, cap_story, virtdisk_wipe, press_cancel,
|
||||
pick_menu_item, goto_home):
|
||||
pick_menu_item, goto_home, sim_root_dir):
|
||||
|
||||
# like "try_sign" but use Virtual Disk to send/receive PSBT/results
|
||||
# - on real dev, need user to manually say yes ... alot
|
||||
@ -142,10 +142,9 @@ def try_sign_virtdisk(press_select, virtdisk_path, cap_story, virtdisk_wipe, pre
|
||||
assert expect_finalize
|
||||
assert got_txn
|
||||
assert got_txid == txid == result_txid
|
||||
with open("debug/vd-result.txn", 'wb') as f:
|
||||
with open(f"{sim_root_dir}/debug/vd-result.txn", 'wb') as f:
|
||||
f.write(got_txid)
|
||||
|
||||
|
||||
# check output encoding matches input (for PSBT only)
|
||||
if got_psbt:
|
||||
if encoding == 'hex':
|
||||
@ -170,7 +169,7 @@ def try_sign_virtdisk(press_select, virtdisk_path, cap_story, virtdisk_wipe, pre
|
||||
|
||||
if got_psbt:
|
||||
assert got_psbt[0:5] == b'psbt\xff'
|
||||
with open("debug/vd-result.psbt", 'wb') as f:
|
||||
with open(f"{sim_root_dir}/debug/vd-result.psbt", 'wb') as f:
|
||||
f.write(got_psbt)
|
||||
|
||||
from psbt import BasicPSBT
|
||||
|
||||
@ -14,7 +14,7 @@ from serialize import uint256_from_str
|
||||
from ctransaction import CTransaction, COutPoint, CTxIn, CTxOut
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@pytest.fixture
|
||||
def fake_txn(dev, pytestconfig):
|
||||
# make various size txn's ... completely fake and pointless values
|
||||
# - but has UTXO's to match needs
|
||||
|
||||
@ -60,6 +60,7 @@ wallet (on testnet, always with the same seed). But there are other options:
|
||||
- `--scan` => (Q) use attached serial port connected to a QR scanner module (not simulation)
|
||||
- `--battery` => (Q) assume the USB cable is NOT connected (ie. on battery power)
|
||||
- `--early-usb` => start simulated USB interface even before user is login (useful for login testing)
|
||||
- `--segregate` => scroll down to `Running simulators in parallel` section
|
||||
|
||||
See `variant/sim_settings.py` for the details of settings-related options.
|
||||
|
||||
@ -78,7 +79,7 @@ See `variant/sim_settings.py` for the details of settings-related options.
|
||||
|
||||
## Requirements
|
||||
|
||||
- uses good olde `xterm` for console input and output
|
||||
- uses good old `xterm` for console input and output
|
||||
- this directory has additional `requirements.txt` (a superset of other requirements of the project)
|
||||
- run "brew install sdl2" before/after doing python requirements
|
||||
- run "make setup" then "make"
|
||||
@ -99,4 +100,39 @@ See `variant/sim_settings.py` for the details of settings-related options.
|
||||
- linux supported (only tested on debian based Ubuntu 20.04), please check main README.md
|
||||
- Windows can work under WSL but is not supported by our team. Follow instructions on <https://www.reddit.com/r/coldcard/comments/14etq8i/coldcard_simulator_for_windows_mac_and_linux_to/>
|
||||
|
||||
# Running simulators in parallel
|
||||
|
||||
Normally, when simulator is spwned with `./simulator.py --eff --q1` (or similar)
|
||||
the default socket file is produced (`/tmp/ckcc-simulator.sock`) for simulator to be able to emulate various types of comms.
|
||||
Each time new simulator is spwned (while som older still running) the socket file gets claimed by the most recently opened simulator.
|
||||
You can continue to use older simulator manually, but it is no longer possible to communicate via the socket file.
|
||||
Besides shared socket file, all simulators share some working directories (`work` & previously `testing/debug`, currently `work/debug`).
|
||||
|
||||
To enable full parallel operation on multiple simulators use `--segregate` simulator flag that:
|
||||
* creates unique simulator socket file `/tmp/ckcc-simulator-<PID>.sock` for every simulator spwned with the flag
|
||||
* creates separate simulator work directory in `/tmp/cc-simulators/<PID>` (every simulator has its own debug dir in work dir)
|
||||
|
||||
Spawn two simulators:
|
||||
|
||||
```shell
|
||||
./simulator.py --eff --segregate # Mk4
|
||||
./simulator.py --eff --segregate --q1 # Q
|
||||
```
|
||||
|
||||
Two directories were created inside `/tmp/cc-simulators` and two socket files in `/tmp` with same PID in names as directory names created.
|
||||
To operate above simulators new `--socket`/`-c` flag needs to be used with client: `ckcc -c /tmp/ckcc-simulator-35156.sock ...`
|
||||
|
||||
```shell
|
||||
ckcc -c /tmp/ckcc-simulator-35156.sock addr -s
|
||||
ckcc -c /tmp/ckcc-simulator-35291.sock addr -s
|
||||
```
|
||||
|
||||
Simulator socket path is dumped to STDOUT after simulator is started:
|
||||
```shell
|
||||
Coldcard Simulator: Commands (over simulated window):
|
||||
- Control-Q to quit
|
||||
- ^Z to snapshot screen.
|
||||
- ^S/^E to start/end movie recording
|
||||
- ^N to capture NFC data (tap it)
|
||||
- socket: /tmp/ckcc-simulator-35291.sock
|
||||
```
|
||||
|
||||
@ -1,18 +1,16 @@
|
||||
diff --git a/unix/variant/pyb.py b/unix/variant/pyb.py
|
||||
index d22bb1b..fe8e7ca 100644
|
||||
index 5e108da3..5a15fc9c 100644
|
||||
--- a/unix/variant/pyb.py
|
||||
+++ b/unix/variant/pyb.py
|
||||
@@ -36,10 +36,10 @@ class USB_HID:
|
||||
import usocket as socket
|
||||
@@ -40,9 +40,9 @@ class USB_HID:
|
||||
sfp_b = SOCKET_FILE_PATH.encode()
|
||||
self.pipe = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
|
||||
# If on linux, try commenting the following line
|
||||
- addr = bytes([len(self.fn)+2, socket.AF_UNIX] + list(self.fn))
|
||||
+ # addr = bytes([len(self.fn)+2, socket.AF_UNIX] + list(self.fn))
|
||||
- addr = bytes([len(sfp_b)+2, socket.AF_UNIX] + list(sfp_b))
|
||||
+ #addr = bytes([len(sfp_b)+2, socket.AF_UNIX] + list(sfp_b))
|
||||
# If on linux, try uncommenting the following two lines
|
||||
- #import struct
|
||||
- #addr = struct.pack('H108s', socket.AF_UNIX, self.fn)
|
||||
+ import struct
|
||||
+ addr = struct.pack('H108s', socket.AF_UNIX, self.fn)
|
||||
- # addr = struct.pack('H108s', socket.AF_UNIX, sfp_b)
|
||||
+ addr = struct.pack('H108s', socket.AF_UNIX, sfp_b)
|
||||
while 1:
|
||||
try:
|
||||
self.pipe.bind(addr)
|
||||
|
||||
@ -6,6 +6,11 @@
|
||||
|
||||
import machine, pyb, sys
|
||||
|
||||
socket_path = sys.argv.pop() # last arg must be a socket path - remove
|
||||
assert ("ckcc-simulator" in socket_path) and (".sock" in socket_path)
|
||||
pyb.SOCKET_FILE_PATH = socket_path
|
||||
print("socket:", pyb.SOCKET_FILE_PATH)
|
||||
|
||||
if '--metal' in sys.argv:
|
||||
# next in argv will be two open file descriptors to use for serial I/O to a real Coldcard
|
||||
import bare_metal
|
||||
|
||||
@ -14,12 +14,10 @@
|
||||
# Limitations:
|
||||
# - USB light not fully implemented, because happens at irq level on real product
|
||||
#
|
||||
import os, sys, signal, time, pdb, tempfile, struct, zlib
|
||||
import subprocess, asyncio
|
||||
import os, sys, signal, time, pdb, tempfile, struct, zlib, subprocess, shutil
|
||||
from dataclasses import dataclass
|
||||
import sdl2.ext
|
||||
import PIL
|
||||
from PIL import Image, ImageSequence, ImageOps
|
||||
from PIL import Image, ImageOps
|
||||
from select import select
|
||||
import fcntl
|
||||
from bare import BareMetal
|
||||
@ -31,6 +29,7 @@ UNIX_SOCKET_PATH = '/tmp/ckcc-simulator.sock'
|
||||
|
||||
current_led_state = 0x0
|
||||
|
||||
|
||||
def activate_file(filename):
|
||||
# see <https://stackoverflow.com/questions/17317219>
|
||||
if sys.platform == "win32":
|
||||
@ -745,6 +744,14 @@ def handle_q1_key_events(event, numpad_tx, data_tx):
|
||||
|
||||
def start():
|
||||
is_q1 = ('--q1' in sys.argv)
|
||||
segregate = ("--segregate" in sys.argv)
|
||||
pid = os.getpid()
|
||||
# for compatibility with old clients
|
||||
# UNIX_SOCKET_PATH is always used if not segregate
|
||||
socket_path = UNIX_SOCKET_PATH
|
||||
if segregate:
|
||||
socket_path = '/tmp/ckcc-simulator-%d.sock' % pid
|
||||
|
||||
if "--headless" in sys.argv:
|
||||
sys.argv.remove("--headless")
|
||||
is_headless = True
|
||||
@ -760,6 +767,7 @@ def start():
|
||||
- ^S/^E to start/end movie recording
|
||||
- ^N to capture NFC data (tap it)'''
|
||||
)
|
||||
print(" - socket: %s" % socket_path)
|
||||
if is_q1:
|
||||
print('''\
|
||||
Q1 specials:
|
||||
@ -818,10 +826,10 @@ Q1 specials:
|
||||
# manage unix socket cleanup for client
|
||||
def sock_cleanup():
|
||||
import os
|
||||
fp = UNIX_SOCKET_PATH
|
||||
fp = socket_path
|
||||
if os.path.exists(fp):
|
||||
os.remove(fp)
|
||||
sock_cleanup()
|
||||
|
||||
import atexit
|
||||
atexit.register(sock_cleanup)
|
||||
|
||||
@ -849,11 +857,30 @@ Q1 specials:
|
||||
scan_args = [ '--scan', str(port.fileno()) ]
|
||||
sys.argv.remove('--scan')
|
||||
|
||||
os.chdir('./work')
|
||||
cc_cmd = ['../coldcard-mpy',
|
||||
'-X', 'heapsize=9m',
|
||||
'-i', '../sim_boot.py'] + [str(i) for i in pass_fds] \
|
||||
+ metal_args + scan_args + sys.argv[1:]
|
||||
# unix
|
||||
cwd = os.getcwd()
|
||||
# abs paths
|
||||
cc_mpy = os.path.join(cwd, "coldcard-mpy")
|
||||
sim_boot = os.path.join(cwd, "sim_boot.py")
|
||||
|
||||
if segregate:
|
||||
os.makedirs("/tmp/cc-simulators", exist_ok=True)
|
||||
os.chdir("/tmp/cc-simulators")
|
||||
# our new work /tmp/cc-simulators/<PID>
|
||||
os.mkdir(str(pid))
|
||||
os.chdir(str(pid))
|
||||
os.mkdir("MicroSD")
|
||||
os.mkdir("settings")
|
||||
os.mkdir("VirtDisk")
|
||||
os.mkdir("debug")
|
||||
# needed for VirtDisk test
|
||||
shutil.copy(os.path.join(cwd, "work", "VirtDisk", "README.md"),
|
||||
os.path.join(os.getcwd(), "VirtDisk", "README.md"))
|
||||
else:
|
||||
os.chdir('./work')
|
||||
|
||||
cc_cmd = [cc_mpy, '-X', 'heapsize=9m', '-i', sim_boot] + [str(i) for i in pass_fds] \
|
||||
+ metal_args + scan_args + sys.argv[1:] + [socket_path]
|
||||
|
||||
if is_headless:
|
||||
pass_fds.remove("-1")
|
||||
|
||||
@ -198,10 +198,9 @@ def is_simulator():
|
||||
def is_debug_build():
|
||||
return True
|
||||
|
||||
|
||||
def get_sim_root_dirs():
|
||||
# return a single path and list of files to pretend to find there
|
||||
import ffilib, os
|
||||
import ffilib
|
||||
libc = ffilib.libc()
|
||||
|
||||
b = bytearray(500)
|
||||
|
||||
@ -2,9 +2,10 @@
|
||||
#
|
||||
import utime as time
|
||||
import uerrno as errno
|
||||
import sys
|
||||
import usocket as socket
|
||||
import sys, struct, os
|
||||
|
||||
from machine import Pin
|
||||
SOCKET_FILE_PATH = None
|
||||
|
||||
class USB_VCP:
|
||||
@staticmethod
|
||||
@ -28,22 +29,20 @@ def usb_mode(nm=UNSET, **kws):
|
||||
return _umode
|
||||
|
||||
class USB_HID:
|
||||
fn = b'/tmp/ckcc-simulator.sock'
|
||||
|
||||
def __init__(self):
|
||||
self.pipe = None
|
||||
self.last_from = None
|
||||
self._open()
|
||||
|
||||
def _open(self):
|
||||
import sys
|
||||
import usocket as socket
|
||||
|
||||
assert SOCKET_FILE_PATH # has to be set in sim_boot.py by caller
|
||||
sfp_b = SOCKET_FILE_PATH.encode()
|
||||
self.pipe = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
|
||||
# If on linux, try commenting the following line
|
||||
addr = bytes([len(self.fn)+2, socket.AF_UNIX] + list(self.fn))
|
||||
addr = bytes([len(sfp_b)+2, socket.AF_UNIX] + list(sfp_b))
|
||||
# If on linux, try uncommenting the following two lines
|
||||
#import struct
|
||||
#addr = struct.pack('H108s', socket.AF_UNIX, self.fn)
|
||||
# addr = struct.pack('H108s', socket.AF_UNIX, sfp_b)
|
||||
while 1:
|
||||
try:
|
||||
self.pipe.bind(addr)
|
||||
@ -51,8 +50,7 @@ class USB_HID:
|
||||
except OSError as exc:
|
||||
if exc.args[0] == errno.EADDRINUSE:
|
||||
# handle restart after first run
|
||||
import os
|
||||
os.remove(self.fn)
|
||||
os.remove(SOCKET_FILE_PATH)
|
||||
continue
|
||||
|
||||
def recv(self, buf, timeout=0):
|
||||
|
||||
11
unix/work/debug/.gitignore
vendored
Normal file
11
unix/work/debug/.gitignore
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
*.7z
|
||||
*.txt
|
||||
*.json
|
||||
*.psbt
|
||||
*.csv
|
||||
*.pdf
|
||||
*.dfu
|
||||
*.txn
|
||||
*.sig
|
||||
*.png
|
||||
.tmp.tmp
|
||||
Loading…
Reference in New Issue
Block a user