给区块链工程师 / SRE / 安全审计看的版本 —— 同样的 6 段路, 但每一步讲到服务边界、数据契约、协议细节、威胁模型。
For engineers verifying that the design actually holds — service boundaries, protocols, schemas, threat models, monitoring points.
充值地址由 sign-service 持 master seed,按 executionChain + uid 派生,落库 user_chain_addr.derivation_index。
派生是确定性的(同 uid+chain 总是同地址),又是不可枚举的(没有 master seed 没法预测其他用户的地址)。这两条性质对应到产品上就是「用户每次刷新都看到同一个充值地址」+「攻击者拿到一个用户的地址也推不出另一个用户的地址」。
充值识别走 manual claim(TD-16,见下一章),不依赖被动扫链 —— 这个选择避开了 trace 投毒 / 重组 / 多义性等问题,见 docs/8-tech-debt.md TD-16 + TD-24。
sign-service · AddressDeriveServiceuser_chain_addr: (uid, execution_chain, address, derivation_index, created_at)POST /internal/v1/sign/derive · 仅内部 mTLS 调用chain-service(用户首次申请充值地址时)sign.localPrivateKeyHex(env); 生产: KMS-backed allocatorderivation_index gap 检测
deposit sequence
Claim 验证 + trust-on-first-use 绑定 + 写账本 + 物理 sweep,4 件事一连串。
Manual claim:用户提交充值 tx 的 txHash → chain-service 走 verify-claim 校验 → 落 user_chain_addr_bind(from-addr ↔ uid 一对多) → wallet-service 写账本 + 发 Kafka deposit.claimed。
物理 sweep:sign-service 用 HD 子私钥(按 derivation_index 派生)签 sweep tx, 同链多用户聚合成 batch, chain-service 广播。
POST /internal/v1/chain/deposit/verify-claim{uid, executionChain, txHash}tx 存在 + 在主链上(防钓鱼链)tx.to_address = 用户 HD 地址tx 未被 claim (UNIQUE (execution_chain, tx_hash))tx.confirmations ≥ MIN_CONFIRMS
deposit.claimed · at-least-once · outbox patternSWEEP_THRESHOLD 即时 ③ batch policy(同链 N 地址聚合)eth_estimateGas + 1.2x buffer; TRON: 带宽/能量, 平台垫付MIN_CONFIRMS 后才视 sweep 完成; reorg 监控由 chain-service scanner 持续 head checksweep sequence (with services)
购票纯走 wallet-service 内部账本, 0 链上 tx, double-entry + audit log + Kafka 事件流。
核心数据结构:user_account(用户账户) + platform_account(平台子账户, game_id 作为 sub-key 表示活动奖池) + account_event(append-only event log, 用于对账和审计) + outbox(Kafka 投递保证 at-least-once)。
防并发 / 防重复:idempotency_key = uid + game_id + ticket_id(UNIQUE 约束); version_no 乐观锁防并发 UPDATE 覆盖; 失败重试幂等。
(uid, balance, frozen_balance, version_no, updated_at)(account_type, scope_key, balance, version_no)(event_id, related_account, delta, event_type, ref_id, idempotency_key, created_at) · append-only(id, topic, payload, status, retry_count) · CDC → Kafka producerPOST /v1/game/{gameId}/buy body: {count}game-service → mTLS → wallet-service.debitForTicketticket.bought · consumers: 风控 / 反刷 / 统计 / 代理分账why off-chain
wallet-service 的 mTLS 内部接口, 由它统一执行 TX + outbox。这条规则强 audit-able。
DrawEngine 接口 + 3 个 Spring bean 实现, 通过 Nacos config one.draw.mode 切换。
中奖者奖金 v1.0 两条出口:
① USDT 奖 → wallet-service.creditPrize 写中奖者账本 (out within tx) → 用户走 STEP 06 提现
② 实物奖 → INSERT physical_fulfillment 队列, TG 客服收件信息 + admin 后台录入发货
③ BTC 奖(后续开放)→ 设计保留 chain-service.scheduleBtcWithdraw → BIP84 P2WPKH,v1.0 不启用
DrawCoordinator.sol · address per env (testnet/staging/prod)requestRandomWords(keyHash, subId, requestConfirmations, callbackGasLimit, numWords)fulfillRandomWords · 链上写入 (game_id → randomSeed)3 engines
hash(salt + seed) + 签名公示; 开奖时 reveal salt; 用 hash + salt 计算中奖号. LOCAL only.TESTNET / STAGING / PROD 默认.one.draw.mode 修改后, 服务需要重启(@ConditionalOnProperty 启动时生效). 真热切换走运维流程: blue/green deploy, 不在单实例做 dynamic swap.
分三层放钱, 每层私钥保护强度不同, 阈值规则触发自动 / 半自动调度。
3 tiers
private key: sign-service in-memory (临时派生)private key: KMS · sign-service unlock per signing op (短暂内存)private key: 每个 signer 一片,物理隔离docs/9-ops/runbook-cold-wallet-refill.md」(待补): 多人到场 + 各自硬件钱包签名 + 合并 → admin-service 广播. 不可远程 self-serve.
威胁模型预设业务面早晚被打穿. 三道独立防线挡住:横向调用 / 业务逻辑绕过 / 私钥本身.
cert-manager 管理 cert 轮换. 单证书 TTL < 30 天. revocation list (CRL) 由 sign-service 启动 + 定时刷新.
per_tx_cap (单笔上限, Nacos managed) ·daily_cap (单日累计, Redis counter, 跨日重置) ·address_whitelist (toAddr 白名单 · 加白要走 admin 审批 + TOTP) ·nonce 顺序校验 (防 replay / 卡 nonce 攻击).Sign(payload), KMS 内部完成 ECDSA, 返回 signature bytes. sign-service 持有的是签名结果, 不是私钥.kms:Sign · CloudTrail 记录每次调用.
withdrawal sequence
从 service 视角看 6 个阶段, 每个阶段标主导服务. 一张图能看出来调用方向 + 数据流 + 安全关卡。