# -*- coding: utf-8 -*- """PoW (Proof of Work) 计算模块""" import base64 import ctypes import json import struct import threading import time from curl_cffi import requests from wasmtime import Engine, Linker, Module, Store from .config import CONFIG, WASM_PATH, logger from .utils import get_account_identifier # ---------------------------------------------------------------------- # WASM 模块缓存 - 避免每次请求都重新加载 # ---------------------------------------------------------------------- _wasm_cache_lock = threading.Lock() _wasm_engine = None _wasm_module = None def _get_cached_wasm_module(wasm_path: str): """获取缓存的 WASM 模块,首次调用时加载""" global _wasm_engine, _wasm_module if _wasm_module is not None: return _wasm_engine, _wasm_module with _wasm_cache_lock: # 双重检查锁定 if _wasm_module is not None: return _wasm_engine, _wasm_module try: with open(wasm_path, "rb") as f: wasm_bytes = f.read() _wasm_engine = Engine() _wasm_module = Module(_wasm_engine, wasm_bytes) logger.info(f"[WASM] 已缓存 WASM 模块: {wasm_path}") except Exception as e: logger.error(f"[WASM] 加载 WASM 模块失败: {e}") raise RuntimeError(f"加载 wasm 文件失败: {wasm_path}, 错误: {e}") return _wasm_engine, _wasm_module # 启动时预加载 WASM 模块 try: _get_cached_wasm_module(WASM_PATH) except Exception as e: logger.warning(f"[WASM] 启动时预加载失败(将在首次使用时重试): {e}") # get_account_identifier 已移至 core.utils # ---------------------------------------------------------------------- # 使用 WASM 模块计算 PoW 答案的辅助函数 # ---------------------------------------------------------------------- def compute_pow_answer( algorithm: str, challenge_str: str, salt: str, difficulty: int, expire_at: int, signature: str, target_path: str, wasm_path: str, ) -> int: """ 使用 WASM 模块计算 DeepSeekHash 答案(answer)。 根据 JS 逻辑: - 拼接前缀: "{salt}_{expire_at}_" - 将 challenge 与前缀写入 wasm 内存后调用 wasm_solve 进行求解, - 从 wasm 内存中读取状态与求解结果, - 若状态非 0,则返回整数形式的答案,否则返回 None。 优化:使用缓存的 WASM 模块,避免每次请求都重新加载文件。 """ if algorithm != "DeepSeekHashV1": raise ValueError(f"不支持的算法:{algorithm}") prefix = f"{salt}_{expire_at}_" # 获取缓存的 WASM 模块(避免重复加载文件) engine, module = _get_cached_wasm_module(wasm_path) # 每次调用创建新的 Store 和实例(必须的,因为 Store 不是线程安全的) store = Store(engine) linker = Linker(engine) instance = linker.instantiate(store, module) exports = instance.exports(store) try: memory = exports["memory"] add_to_stack = exports["__wbindgen_add_to_stack_pointer"] alloc = exports["__wbindgen_export_0"] wasm_solve = exports["wasm_solve"] except KeyError as e: raise RuntimeError(f"缺少 wasm 导出函数: {e}") def write_memory(offset: int, data: bytes): size = len(data) base_addr = ctypes.cast(memory.data_ptr(store), ctypes.c_void_p).value ctypes.memmove(base_addr + offset, data, size) def read_memory(offset: int, size: int) -> bytes: base_addr = ctypes.cast(memory.data_ptr(store), ctypes.c_void_p).value return ctypes.string_at(base_addr + offset, size) def encode_string(text: str): data = text.encode("utf-8") length = len(data) ptr_val = alloc(store, length, 1) ptr = int(ptr_val.value) if hasattr(ptr_val, "value") else int(ptr_val) write_memory(ptr, data) return ptr, length # 1. 申请 16 字节栈空间 retptr = add_to_stack(store, -16) # 2. 编码 challenge 与 prefix 到 wasm 内存中 ptr_challenge, len_challenge = encode_string(challenge_str) ptr_prefix, len_prefix = encode_string(prefix) # 3. 调用 wasm_solve(注意:difficulty 以 float 形式传入) wasm_solve( store, retptr, ptr_challenge, len_challenge, ptr_prefix, len_prefix, float(difficulty), ) # 4. 从 retptr 处读取 4 字节状态和 8 字节求解结果 status_bytes = read_memory(retptr, 4) if len(status_bytes) != 4: add_to_stack(store, 16) raise RuntimeError("读取状态字节失败") status = struct.unpack("