|
50 | 50 | WALLET_DIR = os.path.join(SCRIPT_DIR, "btcrecover", "test", "test-wallets") |
51 | 51 | RESULTS_DIR = os.path.join(SCRIPT_DIR, "benchmark-results") |
52 | 52 |
|
| 53 | +# The secp256k1 backends btcrecover.crypto_backends can select between. |
| 54 | +BACKENDS = ("coincurve", "wallycore", "purepython") |
| 55 | + |
53 | 56 | # Extra seconds (on top of the test duration) allowed for a subprocess to |
54 | 57 | # reach and finish its measured run. This covers slow one-off setup such as |
55 | 58 | # first-time OpenCL kernel compilation, which can take a while on integrated |
@@ -875,6 +878,9 @@ def _append_opencl_args(cmd, opencl_args): |
875 | 878 |
|
876 | 879 | def run_all_benchmarks(args): |
877 | 880 | """Run all configured benchmarks and return results.""" |
| 881 | + # BTCR_BACKEND is exported by _configure_backend() before we get here, so |
| 882 | + # subprocesses inherit the requested backend. |
| 883 | + |
878 | 884 | # Build GPU/OpenCL argument dicts from CLI args |
879 | 885 | gpu_args = {} |
880 | 886 | opencl_args = {} |
@@ -906,6 +912,9 @@ def run_all_benchmarks(args): |
906 | 912 | "timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(), |
907 | 913 | "duration_per_test_seconds": args.duration, |
908 | 914 | "threads": threads, |
| 915 | + "backend": args.backend or "auto", |
| 916 | + # What actually loaded, which is not necessarily what was requested. |
| 917 | + "backend_actual": getattr(args, "backend_actual", None) or _get_active_backend(), |
909 | 918 | "comment": args.comment, |
910 | 919 | "gpu_args": gpu_args if gpu_args else None, |
911 | 920 | "opencl_args": opencl_args if opencl_args else None, |
@@ -990,6 +999,53 @@ def run_all_benchmarks(args): |
990 | 999 | return results |
991 | 1000 |
|
992 | 1001 |
|
| 1002 | +def _get_active_backend(): |
| 1003 | + """Get the secp256k1 backend btcrecover actually selected. |
| 1004 | +
|
| 1005 | + Queried in a subprocess rather than by importing crypto_backends here, so |
| 1006 | + that the answer reflects what the benchmark's own worker subprocesses will |
| 1007 | + select: same interpreter, same working directory, same BTCR_BACKEND. |
| 1008 | + Returns "unknown" if the backend could not be determined. |
| 1009 | + """ |
| 1010 | + try: |
| 1011 | + result = subprocess.run( |
| 1012 | + [sys.executable, "-c", |
| 1013 | + "from btcrecover import crypto_backends; print(crypto_backends.BACKEND_NAME)"], |
| 1014 | + capture_output=True, text=True, timeout=60, |
| 1015 | + cwd=SCRIPT_DIR, |
| 1016 | + ) |
| 1017 | + for line in result.stdout.splitlines(): |
| 1018 | + if line.strip() in BACKENDS: |
| 1019 | + return line.strip() |
| 1020 | + except Exception: |
| 1021 | + pass |
| 1022 | + return "unknown" |
| 1023 | + |
| 1024 | + |
| 1025 | +def _configure_backend(args): |
| 1026 | + """Force the requested backend, then confirm which one really loaded. |
| 1027 | +
|
| 1028 | + crypto_backends falls back to the next available backend when a forced one |
| 1029 | + cannot be imported, so a requested backend is not proof of what ran. Returns |
| 1030 | + the active backend name, or None if it did not match what was requested. |
| 1031 | + """ |
| 1032 | + if args.backend: |
| 1033 | + os.environ["BTCR_BACKEND"] = args.backend |
| 1034 | + |
| 1035 | + active = _get_active_backend() |
| 1036 | + |
| 1037 | + if args.backend and active != args.backend: |
| 1038 | + print(f"ERROR: --backend {args.backend} was requested, but btcrecover selected " |
| 1039 | + f"'{active}' instead.") |
| 1040 | + print(" btcrecover falls back to the next available backend when a forced one") |
| 1041 | + print(" cannot be imported, so continuing would benchmark the wrong library and") |
| 1042 | + print(f" label the results as '{args.backend}'. Install {args.backend}, or re-run") |
| 1043 | + print(f" with --backend {active}.") |
| 1044 | + return None |
| 1045 | + |
| 1046 | + return active |
| 1047 | + |
| 1048 | + |
993 | 1049 | def _get_btcrecover_version(): |
994 | 1050 | """Get the btcrecover version string.""" |
995 | 1051 | try: |
@@ -1074,8 +1130,11 @@ def main(): |
1074 | 1130 | %(prog)s --wallet-type password Only test password recovery |
1075 | 1131 | %(prog)s --global-ws 8192 GPU benchmarks with custom work size |
1076 | 1132 | %(prog)s --opencl-workgroup-size 1024 OpenCL with custom workgroup |
1077 | | - %(prog)s --comment "GitHub actions run" Add a free-form comment in metadata |
1078 | | - %(prog)s --output results.json Save results to a specific file |
| 1133 | + %(prog)s --comment "GitHub actions run" Add a free-form comment in metadata |
| 1134 | + %(prog)s --output results.json Save results to a specific file |
| 1135 | + %(prog)s --backend coincurve Force a specific secp256k1 backend |
| 1136 | + %(prog)s --backend purepython Test with pure-Python secp256k1 |
| 1137 | + %(prog)s --backend wallycore Test with wallycore secp256k1 |
1079 | 1138 | """ |
1080 | 1139 | ) |
1081 | 1140 |
|
@@ -1117,6 +1176,12 @@ def main(): |
1117 | 1176 | "--comment", type=str, default=None, |
1118 | 1177 | help="Optional free-form note to include in benchmark metadata" |
1119 | 1178 | ) |
| 1179 | + parser.add_argument( |
| 1180 | + "--backend", choices=list(BACKENDS), default=None, |
| 1181 | + help="Force a specific secp256k1 backend. Sets the BTCR_BACKEND environment " |
| 1182 | + "variable for all subprocesses (default: let btcrecover auto-select). " |
| 1183 | + "The run aborts if the requested backend is not the one that actually loads." |
| 1184 | + ) |
1120 | 1185 | parser.add_argument( |
1121 | 1186 | "--startup-timeout", type=int, default=SEARCH_PHASE_TIMEOUT, metavar="SECONDS", |
1122 | 1187 | help="Extra seconds beyond --duration to allow for subprocess startup / " |
@@ -1163,11 +1228,19 @@ def main(): |
1163 | 1228 | # Apply the (possibly overridden) startup timeout used by run_benchmark |
1164 | 1229 | SEARCH_PHASE_TIMEOUT = args.startup_timeout |
1165 | 1230 |
|
| 1231 | + # Export BTCR_BACKEND and confirm it took effect before spending time on a run |
| 1232 | + # whose results would be mislabeled. |
| 1233 | + args.backend_actual = _configure_backend(args) |
| 1234 | + if args.backend_actual is None: |
| 1235 | + return 1 |
| 1236 | + |
1166 | 1237 | print("BTCRecover Benchmarking Tool") |
1167 | 1238 | print(f"{'=' * 60}") |
1168 | 1239 | print(f"Duration per test: {args.duration} seconds") |
1169 | 1240 | print(f"Wallet types: {args.wallet_type}") |
1170 | 1241 | print(f"Threads: {args.threads if args.threads else 'auto (btcrecover default)'}") |
| 1242 | + print(f"Backend: {args.backend_actual}" |
| 1243 | + f"{' (forced via --backend)' if args.backend else ' (auto-selected)'}") |
1171 | 1244 | print(f"GPU benchmarks: {'Yes' if args.gpu else 'No'}") |
1172 | 1245 | print(f"OpenCL benchmarks: {'Yes' if args.opencl else 'No'}") |
1173 | 1246 | if args.comment: |
|
0 commit comments