Engineer Deep Dive · 服务边界 / 协议 / 数据契约

一笔 1 USDT 走完的
6 段路 · 工程师视角 SERVICES · SCHEMAS · PROTOCOLS · MONITORING

给区块链工程师 / SRE / 安全审计看的版本 —— 同样的 6 段路, 但每一步讲到服务边界数据契约协议细节威胁模型

For engineers verifying that the design actually holds — service boundaries, protocols, schemas, threat models, monitoring points.

9
微服务 / Services
2 + 1
EVM 链 + BTC
mTLS
服务间通信
KMS
私钥保管
// 服务清单 · service inventory
gateway
API 网关 · 限流 + 鉴权 + 路由
user / agent
用户域 + 代理域
game
夺宝业务核心 · DrawEngine 在这里
pay (wallet)
账本 · 唯一可以扣账户的服务
chain
多链适配器 · BSC / TRON / BTC dialect
sign
签名服务 · KMS · 隔离子网
sms / admin
短信 / 后台
Chapter 01 · 充值

HD 派生 + per-user per-chain SIGN-SERVICE 持 MASTER SEED · 派生确定 · 不可枚举

充值地址由 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。

// implementation
service
sign-service · AddressDeriveService
persistence
user_chain_addr: (uid, execution_chain, address, derivation_index, created_at)
endpoint
POST /internal/v1/sign/derive · 仅内部 mTLS 调用
caller
chain-service(用户首次申请充值地址时)
idempotency
同 (uid, executionChain) 重复请求返回同一地址 (DB unique constraint)
master key
v1.0 本地: sign.localPrivateKeyHex(env); 生产: KMS-backed allocator
monitoring · 监控点 ① 派生 RT(P99 < 50ms) ② KMS 不可用告警 ③ 单 uid 派生重复率(应为 100%, 否则有派生算法 bug) ④ derivation_index gap 检测

deposit sequence

1 · 用户首次申请充值地址
客户端 → gatewaychain-service ·
user_chain_addr: 有则返回, 无则走 ② 派生
2 · chain-service 调 sign-service 派生
mTLS · POST /internal/v1/sign/derive · body: {uid, executionChain}
3 · sign-service 内部派生
master seed + executionChain + uid → 子地址 ·
结果落库 user_chain_addr ·
private key 不出 sign-service 进程
4 · 外部钱包转账
用户用自己的钱包发 USDT 到该地址 · 平台不参与
5 · 链上 tx 确认
BSC ~15 块 · TRON ~19 块 ·
HD 地址余额可见, 但账本未入(等 STEP 02 的 claim)
Chapter 02 · 归集

TD-16 manual claim + sweep VERIFY · BIND · LEDGER · SIGN · BROADCAST

Claim 验证 + trust-on-first-use 绑定 + 写账本 + 物理 sweep,4 件事一连串。

Manual claim:用户提交充值 tx 的 txHashchain-serviceverify-claim 校验 → 落 user_chain_addr_bind(from-addr ↔ uid 一对多) → wallet-service 写账本 + 发 Kafka deposit.claimed
物理 sweep:sign-service 用 HD 子私钥(按 derivation_index 派生)签 sweep tx, 同链多用户聚合成 batch, chain-service 广播。

// claim verification
endpoint
POST /internal/v1/chain/deposit/verify-claim
body
{uid, executionChain, txHash}
validations
tx 存在 + 在主链上(防钓鱼链)
tx.to_address = 用户 HD 地址
tx 未被 claim (UNIQUE (execution_chain, tx_hash))
tx.confirmations ≥ MIN_CONFIRMS
binding rule
from-address 第一次出现绑定到 uid, 后续从同源转账自动认领(no claim needed)
kafka topic
deposit.claimed · at-least-once · outbox pattern
// sweep
trigger
① 定时(每日低峰) ② 单地址 ≥ SWEEP_THRESHOLD 即时 ③ batch policy(同链 N 地址聚合)
nonce mgmt
每个 HD 地址独立 nonce; sign-service 维护 in-memory cache + DB 持久化
gas estimation
BSC: eth_estimateGas + 1.2x buffer; TRON: 带宽/能量, 平台垫付
reorg handling
MIN_CONFIRMS 后才视 sweep 完成; reorg 监控由 chain-service scanner 持续 head check
tech debt · 关注 TD-16 manual claim 是当前定案; TD-24 (passive scanner) 列为 Phase 2 备选, 启用前提是 trace 投毒 / 重组识别有可靠方案。launch 前 manual claim 不替换。

sweep sequence (with services)

1 · client → gateway → chain-service
POST /v1/deposit/claim body: {txHash}
2 · chain-service verify-claim
RPC 查 tx · 4 项校验 · UNIQUE 约束防 double-claim
3 · chain-service 写绑定 + 调 wallet-service
INSERT user_chain_addr_bind · Feign + mTLS → wallet-service.creditDeposit
4 · wallet-service 写账本 + outbox
TX: UPDATE user_account + INSERT account_event + INSERT outbox(Kafka 投递)
5 · sweep job (scheduler)
扫待 sweep 地址 · 同链 batch · 调 sign-service.signSweepchain-service.broadcast
Chapter 03 · 购票

Off-chain ledger · double-entry PAY-SERVICE 独占扣款 · IDEMPOTENT · OPTIMISTIC LOCK

购票纯走 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 覆盖; 失败重试幂等。

// data model
user_account
(uid, balance, frozen_balance, version_no, updated_at)
platform_account
(account_type, scope_key, balance, version_no)
account_type = "GAME_POOL", scope_key = game_id
account_event
(event_id, related_account, delta, event_type, ref_id, idempotency_key, created_at) · append-only
outbox
(id, topic, payload, status, retry_count) · CDC → Kafka producer
// ticket purchase TX
endpoint
POST /v1/game/{gameId}/buy body: {count}
service
game-service → mTLS → wallet-service.debitForTicket
db TX
① UPDATE user_account · 乐观锁
② UPDATE platform_account · 同步加
③ INSERT account_event × 2 (one per side)
④ INSERT outbox (ticket.bought)
kafka topic
ticket.bought · consumers: 风控 / 反刷 / 统计 / 代理分账
RT target
P50 < 30ms · P99 < 100ms (DB-bound)
economics · 经济学 BSC 单笔 gas $0.3-0.8, TRON 单笔约 $0.1 (但要平台垫付带宽/能量)。1 USDT 票如果上链, gas 吃掉 30-80% 票价。账本方案: 0 gas + 毫秒确认 + 完整审计 trail。

why off-chain

on-chain
每笔 gas $0.3-0.8 · 5 min 等确认 · 合约风险 · 奖池组合再叠 tx · 不可行
off-chain
DB TX · double-entry · idempotency_key · version_no 乐观锁 · outbox → Kafka · audit trail · 毫秒确认
// wallet-service is the only ledger writer 其他服务不能直接 UPDATE 账户表。所有变动通过 wallet-service 的 mTLS 内部接口, 由它统一执行 TX + outbox。这条规则强 audit-able。
reconciliation · 对账 每日 EOD job: ① 链上余额(HD addr + hot wallet + cold wallet) sum ② 账本余额(user_account + platform_account) sum ③ diff 应为 0 (允许 in-flight tx 容差)。出错触发 admin-service 告警。
Chapter 04 · 开奖

DrawEngine 插件化 @CONDITIONALONPROPERTY · NACOS HOT-SWAP · 3 IMPL

DrawEngine 接口 + 3 个 Spring bean 实现, 通过 Nacos config one.draw.mode 切换。

// game-service · DrawEngine.java public interface DrawEngine { DrawResult draw(DrawRequest req); EngineMode mode(); } // 三个实现注解 @ConditionalOnProperty(name = "one.draw.mode", havingValue = "BACKEND") public class BackendDrawEngine implements DrawEngine { ... } @ConditionalOnProperty(name = "one.draw.mode", havingValue = "VRF_ONLY") public class VrfDrawEngine implements DrawEngine { ... } @ConditionalOnProperty(name = "one.draw.mode", havingValue = "FULL_CHAIN") public class FullChainDrawEngine implements DrawEngine { ... }

中奖者奖金 v1.0 两条出口:
USDT 奖wallet-service.creditPrize 写中奖者账本 (out within tx) → 用户走 STEP 06 提现
实物奖 → INSERT physical_fulfillment 队列, TG 客服收件信息 + admin 后台录入发货
BTC 奖(后续开放)→ 设计保留 chain-service.scheduleBtcWithdraw → BIP84 P2WPKH,v1.0 不启用

// VRF_ONLY (production default)
provider
Chainlink VRF v2 · subscription-based
contract
DrawCoordinator.sol · address per env (testnet/staging/prod)
request
requestRandomWords(keyHash, subId, requestConfirmations, callbackGasLimit, numWords)
callback
VRF 回调 fulfillRandomWords · 链上写入 (game_id → randomSeed)
off-chain decode
game-service 监听事件 · 用 randomSeed 计算中奖号 · 验证 proof
fee
平台预充 LINK 到 subscription · 每次调用扣 LINK

3 engines

BACKEND
commit-reveal off-chain. game-service 开奖前 commit hash(salt + seed) + 签名公示; 开奖时 reveal salt; 用 hash + salt 计算中奖号. LOCAL only.
VRF_ONLY
Chainlink VRF v2. 链上请求 → callback → off-chain decode. 链上 verifiable, 平台无操纵能力. TESTNET / STAGING / PROD 默认.
FULL_CHAIN
All-on-chain. 中奖号计算 + 奖金分配整套在合约里. v1.0 预留接口, Phase 2 视需求开.
operability Nacos 推 one.draw.mode 修改后, 服务需要重启(@ConditionalOnProperty 启动时生效). 真热切换走运维流程: blue/green deploy, 不在单实例做 dynamic swap.
VRF gas + LINK monitoring 监控点: LINK subscription 余额 (低于阈值告警) · VRF callback 失败率 · randomSeed 落库 lag · 中奖号生成耗时 P99. PRD 7.6 节有具体值.
Chapter 05 · 冷热钱包

三层 vault · 攻击面分层 HD ADDR · HOT (KMS) · COLD (HSM + M-OF-N)

分三层放钱, 每层私钥保护强度不同, 阈值规则触发自动 / 半自动调度。

// threshold rules
target
hot wallet 目标 $10k (covers ~24h 峰值出款)
auto push
hot > $15k → 自动签 hot → cold transfer (push 5k)
alert
hot < $5k → PagerDuty 告警 → 运维 cold → hot 回填
cold refill
m-of-n 多签离线签名(物理) · 回填 tx 由 chain-service 广播
scan job
chain-service scheduler · 30s 周期检查 hot wallet balance · Gauge metric to Grafana

3 tiers

HD 充值地址
per-user · per-chain
sign-service derive · sweep to hot 定时 / 阈值
private key: sign-service in-memory (临时派生)
attack surface: 单用户单笔充值
Hot Wallet
in-line
chain-service 配置 hot address (per chain)
private key: KMS · sign-service unlock per signing op (短暂内存)
attack surface: ≤ $15k 限额 + 白名单
Cold Wallet
offline
硬件钱包 + 多签 (Gnosis Safe / 等同方案 · m-of-n, m≥3)
private key: 每个 signer 一片,物理隔离
attack surface: ≈ 0 (远程攻击无法触达)
operational runbook cold wallet refill 走「docs/9-ops/runbook-cold-wallet-refill.md」(待补): 多人到场 + 各自硬件钱包签名 + 合并 → admin-service 广播. 不可远程 self-serve.
Chapter 06 · 出款

三道防线 · mTLS / 限额 / KMS EACH LAYER INDEPENDENT · BUSINESS BREACH ≠ FUND DRAIN

威胁模型预设业务面早晚被打穿. 三道独立防线挡住:横向调用 / 业务逻辑绕过 / 私钥本身.

mTLS · 服务间双向认证
sign-service 只接受持有效内部 CA 签发客户端证书的请求. 业务服务进程里不含 CA private key, 业务面被打穿不等于能调到 sign-service.

cert-manager 管理 cert 轮换. 单证书 TTL < 30 天. revocation list (CRL) 由 sign-service 启动 + 定时刷新.
sign-service 内部校验
即使 mTLS 通过, sign-service 还会做 4 项校验:
per_tx_cap (单笔上限, Nacos managed) ·
daily_cap (单日累计, Redis counter, 跨日重置) ·
address_whitelist (toAddr 白名单 · 加白要走 admin 审批 + TOTP) ·
nonce 顺序校验 (防 replay / 卡 nonce 攻击).
任何一项失败 → 拒签 + 告警 (PagerDuty)
KMS · 主私钥永不出 KMS
热钱包主私钥永远不离开 KMS. 每次签名 sign-service 调 KMS Sign(payload), KMS 内部完成 ECDSA, 返回 signature bytes. sign-service 持有的是签名结果, 不是私钥.

KMS 选型: AWS KMS (Multi-Region prod) · IAM policy 限制只允许 sign-service IAM role 调 kms:Sign · CloudTrail 记录每次调用.

withdrawal sequence

1 · client → gateway → wallet-service
POST /v1/withdraw body: {amount, toAddr, chain}
2 · wallet-service 验额度 + 锁余额
TX: 减 balance + 加 frozen_balance · INSERT account_event · 状态 PENDING
3 · 分流: 自动 / 审核
amount < threshold → 自动放行 · ≥ → admin-service 审批队列 (TOTP)
4 · wallet-service → sign-service (mTLS)
POST /internal/v1/sign/sign-withdraw-tx · LAYER 1 把守
5 · sign-service: 校验 + KMS 签
LAYER 2 (4 项校验) · LAYER 3 (KMS Sign) · 返回 signed raw tx
6 · chain-service 广播 + 监听
broadcast · 等 MIN_CONFIRMS · 回 callback wallet-service → 释放 frozen_balance + Bot 推送
threat model 假设: 业务服务(gateway / api / pay)可能被 SQL 注入 / 依赖漏洞 / 内鬼 / 横向移动打穿. 三道独立防线对应不同攻击向量, 任意一道挡住即守住资金. 安全 audit 应分别 verify 三道.
monitoring + alert ① sign-service 拒签率(基线 + 突增告警) ② KMS 调用 latency P99 ③ withdraw P99 端到端 ④ frozen_balance 长期挂起检测(可能 chain-service 广播失败)
Summary · 总览

6 stages · service map FULL PIPELINE AT A GLANCE

从 service 视角看 6 个阶段, 每个阶段标主导服务. 一张图能看出来调用方向 + 数据流 + 安全关卡。

01
充值
sign · chain
02
归集
chain · pay · sign
03
购票
pay (ledger only)
04
开奖
game · DrawEngine
05
冷热钱包
chain (auto) · admin (manual)
06
出款
pay · sign · chain
▶ 跑一遍交互演示 · interactive demo