ETH · BSC · TRON USDT ERC-20 / BEP-20 / TRC-20 Chainlink VRF v2.5 Foundry V1.0

区块链技术方案

充值 / 提现 / 智能合约 / 环境×链 / 环境变量 / 资金安全 / 监控 / 审计+部署 —— 每个主题:支持的链 · 技术方案 · 大概流程 · 实现服务。

文档定位 / Scope

目标态设计规范:描述系统应当如何实现,非当前代码现状。

Target-state design spec — how the system should work, not current code.

充值

Deposit

用户拿到专属 HD 地址 → 转 USDT → 系统自动扫块、确认、归一、入账,全程无操作、无 manual claim(用户主动报账式充值确认)。下面按事情实际发生的顺序展开。

支持的链与资产

链 ID / Network id标准USDT decimals确认数 / 不可逆依据
ETH1 (EIP-155)ERC-20612 常规 / finalized ~2 epoch · Casper FFG
BSC56 (EIP-155)BEP-201815 · fast finality (BEP-126)
TRON0x2b6653dc 自有TRC-20619 · solidified (27 SR DPoS, 2/3+1)
  • 只支持 USDT,不支持 USDC。
  • ETH/BSC 同为 EVM,同一 secp256k1 私钥地址相同,共用充值地址,靠 chainId 区分;TRON `0x2b6653dc` 非 EIP-155。
  • decimals 不一致:链下统一存「最小单位整数 + decimals」,永不存浮点(BSC 1 USDT = 1e18,ETH/TRON = 1e6)。

用户视角:一次充值长什么样

  • App 申请充值 → 拿到一个该用户专属的链上地址(每 uid×链族一个,长期固定)。
  • 从自己的钱包 / 交易所向该地址转 USDT。
  • 不用再做任何事;到账且满足确认数后余额自动加上,收到充值成功通知。

充值地址怎么来 + HD 钱包原理

分配专属地址

  • EVM(ETH/BSC 共用)+ TRON 各一个专属地址,铸一次后冻结(mint-once-and-freeze),永不轮换,免「一用户多地址」对账难题。
  • wallet-service 管地址生命周期 + 落库;私钥派生下沉 sign-service,wallet 永远拿不到私钥。

HDuid 与派生原理

  • master seed 在 sign-service/KMS(标签 duobao:hd:seed:v1),永不离开签名边界
  • uid 怎么来:就是 user-service 注册时铸的十进制用户 id(从 10000001 起、不复用),直接当 BIP-32 hardened index,不另设计数器/映射表;范围 [0, 2³¹)≈21 亿。
  • BIP-32/44:EVM m/44'/60'/<uid>' / TRON m/44'/195'/<uid>'(uid hardened)。
  • 地址长什么样:EVM = EIP-55 校验和 0x+40hex(MetaMask 同款,所有 EVM 链同一地址);TRON = 0x41 前缀 + Base58Check → T 开头 34 字符。
  • 子私钥用完即焚:进程内短暂存在,签完 Arrays.fill 清零,不落盘/日志。

转账之后:谁扫、怎么扫

谁扫:chain-service 每链一个常驻 watcher 线程,持 chain_sync_state 游标,崩溃续扫、不重不漏 —— 系统主动发现充值,不是 manual claim(用户主动报账式充值确认)

EVMETH / BSC

  • USDT 转账是 ERC-20 Transfer 事件日志,非 native transfer,扫 receipt log。
  • eth_getLogs 分批 ≤1000 块,filter address=[USDT] + topics[0]
  • topics[1]=fromtopics[2]=to(地址左填充 32 字节,取末 20)。
  • valuelog.data(非 indexed),不在 topics。

TRONTRC-20

  • TriggerSmartContract 交易,优先 transactioninfo.logTransfer 事件,calldata 兜底。
  • 必校 contractRet=="SUCCESS":允许「上链但执行失败」仍计费。
  • calldata:selector a9059cbb + to(末 20 字节,0x41 → Base58Check)+ amount。
  • 一笔即一交易,无 logIndex,幂等键 (chain, tx_hash)
  • 地址缓存:启动全量加载 Redis Set,新增经 Pub/Sub 增量;to 不命中直接丢弃。
  • 多 RPC:每链主 + ≥2 备,健康探测自动切换。

怎么算「充值成功」:确认 → 归一 → 入账

充值全流程 / Deposit lifecycle —— 从给用户分配地址开始,一步步走到余额到账(uid 10000001 充值 1000 USDT,ETH / BSC / TRON 任选其一)。
分配地址 ASSIGN
uid 10000001 申请 → sign-service 派生(EVM 一把私钥、TRON 一把;EVM 各链地址相同):
EVMm/44'/60'/10000001'0x9F8e7D6c5B4a3F2e1D0c9B8a7F6e5D4c3B2a1F0e TRONm/44'/195'/10000001'TJ2pK9mR7nQ4sV6wX8yZ3aB5cD1eF7gHkN
地址共用,但 wallet_deposit_address 仍按 UNIQUE(uid, execution_chain) 每链各存一行:
ETH(10000001, ETH)0x9F8e7D6c…3B2a1F0e BSC(10000001, BSC)0x9F8e7D6c…3B2a1F0e(与 ETH 同址,不同行) TRON(10000001, TRON)TJ2pK9mR…eF7gHkN
mint-once 冻结落库 · seed 不出 sign-service
用户转账 SEND
用户任选一条链转 1000 USDT,转入该链对应行的地址:
ETH0x9F8e7D6c…3B2a1F0e BSC0x9F8e7D6c…3B2a1F0e(与 ETH 同地址) TRONTJ2pK9mR…eF7gHkN
用户操作到此结束
扫描命中 SCAN
chain-service 常驻 watcher 按选定链扫:EVM eth_getLogs Transfer / TRON transactioninfo.log to 命中地址缓存 → 进入入账
确认 CONFIRM
确认数按链(满足才不可逆):ETH 12 · BSC 15 · TRON 19 幂等键 (chain, txHash, logIndex) · DB unique + Redis SETNX ✓ 不可逆 final
归一 NORMALIZE
ETHUSDT 6-dp · raw 1000×10⁶ = 1,000,000,000 · ÷10⁰ → micro 1,000,000,000 BSCUSDT 18-dp · raw 1000×10¹⁸ = 1e21 · ÷10¹² → micro 1,000,000,000 TRONUSDT 6-dp · raw 1000×10⁶ = 1,000,000,000 · ÷10⁰ → micro 1,000,000,000
三链归一后统一 = 1000.000000 USDT ✗ BSC 直当 micro → ×10¹² 错放大
入账 CREDIT
wallet-service 事务内 ledger.credit(TOPUP) uid 10000001 余额 0.000000+1000.000000(三链结果一致) ✓ 发 deposit.credited
① 分配地址(EVM 一行 + BSC 一行同址 + TRON 一行)→ ② 任选 ETH/BSC/TRON 转 1000 USDT → ③ 扫描 → ④ 确认(ETH12·BSC15·TRON19)→ ⑤ decimals 归一(三链统一到 1e9 micro)→ ⑥ ledger 入账 · uid 10000001
链重组 / Reorg —— 检测到近端区块被重写时,回滚受影响的入账与账务并重新确认;确认数足够深时概率极低。
区块链 →
$
到账 ⇄ 回滚 ⇄ 重新到账
REORG 链重组 → 回滚受影响入账 → 重新确认
  • 确认数过滤:latest - blockNumber ≥ confirmations(ETH 12 / BSC 15 / TRON 19)才不可逆,未达只挂起不入账。
  • 幂等:DB unique 约束 + Redis SETNX 双层,重复事件忽略。
  • 入账:账务事务内写 deposit + 归一金额加余额 + 发 deposit.confirmed(记充值与加余额原子)。
  • Reorg 防线:周期比对 [head-50, head] blockHash,失配回滚 deposits+账务、回退游标重扫、告警。
decimals 归一(资金硬要求):链上 raw value 必须按链 decimals(BSC=18 / ETH·TRON=6)归一到统一内部单位,严禁直接当统一单位——否则 BSC 放大 1e12 倍错误入账。

入账之后:钱还在派生地址,要归集(谁 / 何时 / 到哪)

入账只是 DB 给用户加余额,链上 USDT 仍在该用户专属 HD 派生地址,不在平台热钱包。这些散落地址的钱必须定期归集 sweep 汇集起来,才能形成可用于出款的资金。

  • 谁来做:全程在 chain-service —— 归集 planner/scheduler 挑地址、构造 SweepReqSweepService 构造并广播 → SweepConfirmationPoller 跟踪确认 → chain_sweep_record(键 requestId)幂等。wallet-service 不参与(不碰链)。
  • 用哪把私钥(关键):钱从用户 HD 派生地址转出,必须用该地址自己的 HD 子私钥签 —— chain-serviceseedVersion+derivationPathsign-service(SignEvmDerivedTxReq/SignTronDerivedTxReq)临时重派生签名、用完即焚。与提现用每链固定 operator 热钱包 KMS keyId完全不同
  • 什么时候:planner 周期性(链上低峰)扫各地址余额 —— 单地址 ≥ dust threshold(低于不归,gas 不划算)+ 大额优先;EVM 须先由 gas station 给该地址转少量 native 付 gas 并确认才动得了,TRON 由质押账户 DelegateResource 委托 energy(不转 TRX),完成后取消委托。
  • 归到哪个钱包:SweepReq.toAddress = 该链热钱包(归集地址),每链一个,是热钱包主要进项;热钱包只留 24-48h 出款量,多的再按 热→温→冷 分层上收(冷钱包多签离线)。另一条路径:合约金库内资金走 DuobaoVault.sweepToColdWallet(SWEEPER_ROLE)直接进冷钱包(vault→冷,非此处)。

分层(热/温/冷)、gas-station、风控筛查的完整设计见

大概流程

用户侧: 申请充值地址 → sign-service HD 派生(EVM[ETH/BSC 共用] m/44'/60'/<uid>' / TRON m/44'/195'/<uid>') → 转 USDT → 等待

chain-service(每链常驻 watcher,负责一切链交互):
  1. cursor   = chain_sync_state.last_scanned_block(chain)
  2. safeHead = latestBlock - confirmations
  3. batchEnd = min(cursor+1000, safeHead); [cursor+1 .. batchEnd] 的 USDT Transfer
  4. to 命中用户地址 → 幂等校验 (chain, tx_hash, logIndex)
  5. 通过 → 按链 decimals 归一为 micro → 把 (uid, amountMicro, txHash…) 交给 wallet-service
  6. chain_sync_state.last_scanned_block = batchEnd
  7. 周期 reorg 检查: blockHash 失配 → 回滚 + 回退游标重扫
wallet-service(纯账务,无链交互): 收归一结果 → 写 deposit + ledger.credit → after-commit 发 deposit.credited
后台: 充值地址 --sweep--> 热钱包(gas-station/TRON delegate energy,见 六)

实现服务

wallet-service
充值地址记录分配(无链交互,调 sign 取址)
WalletDepositAddressService · wallet_deposit_address · Feign sign /derive-addr
wallet-service
入账 / ledger 加余额(无链交互,收 chain 结果)
WalletDepositService · ledger.credit TOPUP · WalletDepositCreditedPublisher
sign-service
HD 派生(seed 不出边界)
AddressDeriveService · /derive-addr · MasterSeedProvider
chain-service
EVM 充值解码 + decimals 归一
EvmChainAdapter · Transfer topic + data · 按链 decimals
chain-service
TRON 充值解码 + decimals 归一
TronChainAdapter · gettransactioninfobyid
chain-service
扫块 watcher + 游标
chain_sync_state · 常驻线程
chain-service
幂等
DepositIdempotencyGuard · DB unique
chain-service
Reorg 回滚
blockHash 比对 + 账务回滚
chain-service
入账后归集到热钱包(planner+执行)
SweepService · SweepConfirmationPoller · chain_sweep_record;签名走 sign-service 子私钥(详见 六)

提现

Withdrawal

用户发起 → 系统校验/审核 → 热钱包出款 → 上链确认。下面按用户视角与资金流向的发生顺序展开。

用户视角:一次提现长什么样

  • App 选链、选币、填目标地址 + 金额,发起提现。
  • 校验通过后冻结这笔余额(默认初始态 PENDING_AUDIT),用户看到「处理中」。
  • 默认全部走人工审核;审批通过后系统从热钱包出款上链(小额自动放行是后续可配置开关)。
  • 上链确认 → 收到成功通知;失败则解冻、余额退回。

校验与审核

余额 / 单笔单日单周限额 / 地址校验(EVM EIP-55、TRON Base58Check)/ 黑名单(混币器、OFAC SDN、本平台充值地址池防内部误转)/ 风控。通过 → 冻结余额 + 落 withdrawal。

  • 审核默认从严:全部 PENDING_AUDIT 逐笔人工审批,不分金额 —— 资金类系统先保守全人工,稳定后再放开。
  • 小额自动放行 = 可配置开关:启用并设阈值后,金额 < 阈值才自动 APPROVED,≥ 阈值仍 PENDING_AUDIT;不启用一律人工。
  • 命中黑名单直接 REJECTED(不进人工队列)。审核是业务规则 + 人工,不接触私钥。
服务边界:wallet-service纯账务/记录方,无任何链库、不发 RPC。它做提现单状态机、限额、审核、冻结/解冻,经 Feign 调 sign-service消费 chain-service 链上回调。一切链交互(选热钱包、nonce、gas、构造、广播、确认)在 chain-service;私钥/签名在 sign-service

审批之后:用哪个钱包、哪个地址出款

  • 出款不从用户 HD 充值地址出,而是从平台每链一个独立热钱包统一出款 —— 该热钱包地址即该链「出款地址」。
  • 这个热钱包 = 充值归集的目的地钱包,是同一个,不是另开一批出款地址:归集(一.2.6 / 六)把用户 HD 地址的钱 sweep 进该链 operator 热钱包,提现又从同一个 operator 热钱包出 —— 归集进、提现出,资金闭环。地址/keyId 都配在 chain-serviceChainProperties(operator 地址 + KMS keyId)。
  • 默认一链一个(既是 sweep 目的地又是出款源);仅为提 TPS 分片成多热钱包时才有「多个出款地址」,那时每个各自既被归集补给、也各自出款(见下)。
  • 谁决定用哪个:chain-servicewallet-service 审核通过后只把一笔 APPROVED 单交给 chain-service,不关心具体热钱包地址
  • 热钱包单签 + KMS,持覆盖 24-48h 出款量;签名在 sign-service。注意:出款用 operator 热钱包 KMS keyId,与归集用「用户地址 HD 子私钥」签不同(见 一.2.6)。

多个出款热钱包如何分配(均 chain-service 广播侧)

  • 单热钱包是默认:一链一热钱包,chain-service 出款分链消费、同链串行广播。EVM nonce 须严格递增、TRON ref_block/expiration 有时效,单地址并发会自相挤兑或断号。
  • 提 TPS 才分片:一链开多个热钱包,chain-service 把单按 hash(requestId)%N 或轮询绑定某个热钱包,各自串行、各自独立 nonce / ref_block 序列、各自独立补给,互不共享 nonce 计数器。

链上出款机制

Nonce 顺序与替换 / Nonce ordering & replacement —— 出款交易按 account nonce 严格排序;卡单以同 nonce、更高 gas 的交易顶替,后续才能继续。
已上链 ON-CHAIN 出款队列 ← QUEUE
已上链confirmed
#5nonce
#6 卡住stuck
#6 gas↑顶替 replace
#7nonce
NONCE 顺序队列 · 卡单 → 同 nonce + 更高 gas 顶替

EVM出款

  • 集中式 nonce:严格递增不跳号;失败必补单;冷启从 getTransactionCount(pending) 播种(非 latest)。
  • ETH EIP-1559:maxFeePerGas ≈ baseFee×2 + tip(base 每块 ±12.5%,覆盖 ~6 块);gasLimit = estimate×1.2
  • BSC 常见 baseFee=0,不套 ETH 公式;按 BSC 策略取 feeHistory.reward/gasPrice/多 RPC 中位 + Nacos floor/ceiling。
  • 卡单 = 同 nonce 替换(非 RBF):相同 from+nonce、gas 比原 tx 高 ≥10%(geth pricebump)。

TRON出款

  • 无 nonce;ref_block + expiration(~60s),过期重构(无 nonce 包袱)。
  • 能量管理:转账耗 31,895 / ~64,895 energy;不足自动补充(Stake 2.0 FreezeBalanceV2 / DelegateResource / 租赁)。
  • 同热钱包串行广播;提 TPS 用多热钱包各自串行。
  • 签名:私钥永不出 KMS;DER 解 (r,s) → low-s 规范化(EIP-2 防可锻性)→ 求 recovery id;EIP-155 v=chainId×2+35+recId
非标准 ERC-20:主网 USDT transfer() 不返回 bool。链下不依赖返回值;合约侧必须 SafeERC20.safeTransfer。多 RPC 解决可用性,不防 front-running(主网大额走私有交易通道)。

出款钱从哪来:热钱包补给链路

  • 充值归集补热钱包:用户充值落各自 HD 派生地址,由 chain-service 定期归集 sweep 汇入热钱包 —— 热钱包主要进项(归集见 一 与 六)。
  • 冷 → 温 → 热 分层补给:冷钱包(~90%,EVM Gnosis Safe / TRON 原生多签)离线;温钱包缓冲;热钱包(~10%)低于阈值由温层补,温层不足再由冷钱包多签审批后补。
  • 大额出金:超阈值大额本身走冷钱包多签,不经热钱包单签。完整分层与风控见

大概流程

用户侧: 发起提现(选链/币/地址/金额) → 等待 → 收到成功/失败通知

wallet-service(纯账务,无链交互):
  1. 业务校验(余额/限额/地址格式/黑名单/风控) → 冻结余额 + INSERT withdrawal
     初始态默认 PENDING_AUDIT(全人工);仅开启小额自动放行且金额 < 阈值才 APPROVED
  2. 审核: PENDING_AUDIT 人工 → APPROVED|REJECTED;把 APPROVED 单交给 chain-service
chain-service(全部链交互):
  3. 选该链热钱包(多热钱包按 requestId 分片),同热钱包串行
  4. 构造 unsigned tx → 分配 nonce → 先持久化(nonce/RESERVED) → 调 sign-service 签名 → 广播
  5. 监听: 成功 → 回调 CHAIN_SUCCESS / 失败 → 回调 CHAIN_FAIL(wallet 解冻回滚)
          / 超时 → EVM 同 nonce 替换(+≥10% gas) / TRON expiration 重构
  6. wallet-service 收回调推进状态机 → 通知用户
资金补给(后台): 充值地址 --sweep--> 热钱包 ; 冷 --多签--> 温 --补给--> 热
WithdrawState(wallet 持有,6 态,无 SUBMITTED): 默认初始 PENDING_AUDIT(小额自动放行配置开启且<阈值才 APPROVED) → APPROVED → BROADCASTING → CHAIN_SUCCESS|CHAIN_FAIL,REJECTED

实现服务

wallet-service
提现单状态机 / 审核 / 冻结解冻(无链交互,消费链回调)
WalletWithdrawService 6 态 · WithdrawCallbackConsumer · WithdrawAddressValidator
chain-service
选热钱包 + 广播编排(链交互入口)
WithdrawBroadcastService · 热钱包/keyId 取自 ChainProperties · 按 requestId 分片
chain-service
Nonce 分配 + 断档自愈
NonceManager · chain_nonce_gap · NonceGapHealer
chain-service
EVM 构造 + 动态 gas + 替换
EvmChainAdapter · eth_feeHistory
chain-service
TRON 构造 + 能量 + 串行广播
TronChainAdapter
sign-service
签名 DER→低 s→recover v
Signer · LocalSigner / KmsSigner
wallet-service + 链上
热钱包补给 / 冷温热 / 大额多签
treasury 分层 · Gnosis Safe / TRON 原生多签(见 六)

智能合约

Smart contracts

先一句话:钱锁在链上金库、由代码按规则放款(不是后端说了算);开奖用谁都操纵不了、人人可复算的链上随机数;敏感操作按角色授权;合约能升级修 bug 但要多签 + 公示延时;主网前过外部审计。源码 duobao-contracts/,Solidity ^0.8.26,Foundry,EVM 部署 BSC(主)/ ETH(备)。

可验证随机开奖 / Verifiable draw —— Chainlink VRF 返回随机数及链上可验证证明,后端据此确定 winnerIndex,平台无法篡改。
后端request
Chainlink
🎲
合约 ✓verify proof
随机数 + 证明 random + proof 参与者 participants
开奖怎么做到谁都不能作弊:VRF 请求 → 链上验证密码学证明 → winnerIndex 选中(平台/预言机/矿工都改不了)

三个合约各是干嘛的(先大白话)

合约一句话职责关键能力
AccessRegistry门禁表:谁能调哪些敏感操作角色 OPERATOR/WITHDRAWER/SWEEPER/EMERGENCY · hasRole 查权限
DuobaoVault金库:USDT 都存这,只能按规则出提现 withdraw(processedNonce 防重放)· 派奖 payout(仅 coordinator)· 归集 sweepToColdWallet · 急停 pause
DrawCoordinator开奖机:要不可操纵随机数、算中奖号requestRandom · rawFulfillRandomWords · 中奖号 randomWord%ticketTotal+1 · DrawMode{VRF_ONLY,FULL_CHAIN}(v1.0 VRF_ONLY)

主线:钱进 DuobaoVaultDrawCoordinator 找 Chainlink 要可验证随机数 → 定中奖号 → Vault.payout 给中奖者 / 用户 withdraw 提现;敏感操作全程过 AccessRegistry 角色检查。

金库怎么保证不被薅

  • 主网 USDT 不按标准来(non-standard ERC-20):transfer/approve 不返回 bool,require(token.transfer(...)) 会误判 → 所有 USDT 转账走 SafeERC20.safeTransfer
  • 防重入重放(CEI + nonReentrant):先 processedNonce[nonce]=true(effects)再外部转账(interaction);反了会被重入重复领钱。
  • 防取模偏心(modulo bias):randomWord 在 2²⁵⁶ 均匀,ticketTotal 远小于它,偏差 ≈ 0 可忽略。

开奖怎么做到谁都不能作弊

  • Chainlink VRF v2.5:随机数自带密码学证明,且证明由合约在链上校验 —— 平台/预言机/矿工都改不了。VRFConsumerBaseV2Plus+Subscription 计费;参数 keyHash(gas lane)·requestConfirmations(≥reorg 深度)·callbackGasLimit(太低回调 revert 还扣费)·numWords。运维:建 subscription、充 LINK(≥10)、把合约加成 consumer(最易漏)。
  • participantsHash 锚点:开奖前后端按确定序(user_id 升序)序列化名单取 sha256(大可用 Merkle root)上链;链上只存哈希+中奖序号,不存名单;第三方拿名单可自己复算 sha256(名单)==链上哈希随机数%人数==中奖序号

合约能改吗、出事能停吗

  • 能升级修 bug(UUPS / EIP-1822):升级入口 _authorizeUpgrade 在实现合约;EIP-1967 slot;initializer 代替 constructor;storage gap 防错位。
  • 但没人能偷偷换:部署后立即 transferOwnership 给 treasury 多签;规模化加 Timelock(升级公示 24h,期间可 cancel)。
  • 出事能止血:Pausable + EMERGENCY_ROLE 单签急停 Vault / DrawCoordinator。

操作合约用哪个钱包 · 哪条链 · 每次开奖多少钱

  • 单独的专用钱包(EOA),不是用户出款热钱包:它持 OPERATOR 角色,chain-service 用它发 requestRandom;与 ① 提现/归集 operator 热钱包(WITHDRAWER)② 一次性 deployer key ③ EMERGENCY key 刻意分开,一把被攻破不波及其他。
  • 在 BSC(合约部署 BSC 主 / ETH 备,VRF 跑 BSC):该 operator 是 BSC 地址,发交易耗 BNB 当 gas;VRF 订阅另用 LINK(BSC BEP-20 LINK)计费,二者分开。
  • 每次开奖成本 = 请求 gas + 回调 gas + VRF 验证 gas + LINK premium。量级:BSC ≈ $0.05–0.5/次;以太坊主网 ≈ $5–30/次(gas 主导)。随 gas/LINK 价波动,上线前以 Chainlink 官方 + 实测为准 —— 这就是开奖放 BSC 的原因。
  • 谁付:gas(BNB)从 operator 钱包出(监控其 BNB 余额);LINK 从 subscription 扣(保持 LINK ≥ 阈值,见七监控)。

大概流程

「部署一次 + 每轮开奖」的实际时序(开奖这条线就是上面「谁都不能作弊」怎么落地):

部署: AccessRegistry → DuobaoVault(→AccessRegistry+USDT)
      → DrawCoordinator(→Vault+VRF) → grant 角色
      → Vault.setDrawCoordinator → (上线前) transferOwnership 给多签/Timelock

开奖(VRF_ONLY):
  后端锁参与者快照 → sha256/Merkle root → DrawCoordinator.requestRandom(roundId, ticketTotal)
  → Chainlink 链上验证 VRF proof → rawFulfillRandomWords 回调 → emit DrawFulfilled
  → 后端扫事件: winnerIndex = randomWord % len,账务事务内结算(状态机防重复)

上线前怎么确认安全 + 实现服务

审计门禁:Slither(静态扫)→ Mythril(符号执行)→ 第三方(CertiK/SlowMist/PeckShield)→ 区块浏览器 verify(含 constructor args)→ 可选 Immunefi;改 1 行重审(code freeze)。Foundry forge test --fuzz-runs 10000;invariant 重点:不重复派奖 · nonce 不重放 · 链上金库余额 ≥ 用户可用+冻结+待提现。

duobao-contracts/
合约源码 + 部署脚本
三合约 · DeployMocknet.s.sol · ExportAbi.sh
chain-service
调合约 + 扫开奖事件
requestRandom · 消费 DrawFulfilled
game-service
开奖引擎 + 参与者快照
DrawEngine ×3 · DrawEngineRouter 按轮快照分发

环境 × 链

Env × Chain

单一构建 artifact 跑所有 env,差异外置 Nacos 按 env namespace 覆盖;钱包密钥按 env 物理隔离。同一 env 可指向不同链网络(本地链 / 公共测试网 / 主网),由 ExecutionChain + RPC 决定 —— 不用 mocknet/mainnet 命名。

  • Env { LOCAL, TESTNET, STAGING, PRODUCTION } · ExecutionChain { ETH_LOCAL, ETH_SEPOLIA, ETH_MAINNET, BSC_LOCAL, BSC_TESTNET, BSC_MAINNET, TRON_LOCAL, TRON_TESTNET, TRON_MAINNET, BTC_LOCAL, BTC_TESTNET, BTC_MAINNET }(两维正交,从不合并)。
  • EVM chainId:ETH=1 · Sepolia=11155111 · BSC=56 · BSC testnet=97(TRON 用自有标识)。
  • 业务代码统一经 ChainProperties 访问,不感知 env、也不感知连的是本地链/测试网/主网。

两个正交维度怎么组合

业务 Env 决定「资源规格 / 监控 / 密钥」;ExecutionChain + RPC 决定「连哪条链网络」。同一个 LOCAL 既能连本地 Anvil 自测,也能临时切公共测试网调真链问题。

业务 Env链网络(ExecutionChain + RPC)用途推荐
LOCAL本地链:Anvil(EVM,ETH_LOCAL/BSC_LOCAL)/ java-tron 私链(TRON_LOCAL)日常开发自测,即时/秒级出块,draw 默认 BACKEND
LOCAL公共测试网:ETH_SEPOLIA/BSC_TESTNET/TRON Nile·Shasta偶尔切换调真链问题(RPC 限流、真 gas、真 VRF)
TESTNET本地链(CI 跑)CI 自动化集成回归,秒级反馈
TESTNET公共测试网 + 真 Chainlink VRF集成测试 + 真 VRF 端到端(唯一能验真 VRF)
STAGING公共测试网(配置 100% 镜像生产)上线前回归 + 性能基线 + 故障演练
STAGING主网(小额)真金小额冒烟 24-48h
PRODUCTION主网 BSC_MAINNET/ETH_MAINNET/TRON_MAINNET生产唯一
LOCAL / TESTNET / STAGING主网(用真生产钱包)严禁

三类链网络各自要准备什么

local本地链

  • EVM 用 Foundry anvil(各链一实例、独立 chainId/端口、即时出块、预置富余币);TRON 用 java-tron 单节点 witness 私链。
  • USDT 用 MockUSDT(decimals 与主网不一致,见 一 decimals 归一);VRF 用 MockVRFCoordinator,手动触发 fulfillRandomWords(可指定任意随机数测边界)。
  • 重启即重置,不验证真 Chainlink 行为

testnet公共测试网

  • Sepolia / BSC Testnet(97)/ TRON Nile·Shasta;真出块时间 + 真 reorg。
  • 真 Chainlink VRF(唯一能端到端验:真回调能否到、callbackGasLimit 够不够)。
  • 测试币 / 测试 LINK 走 faucet 限频;节点偶发不稳,多 RPC 备份。

mainnet主网

  • 自建节点为主 + 公共 RPC 备;严格热冷分离 + 多签。
  • VRF 用 BSC 主网官方 Coordinator,Subscription LINK 余量监控自动告警。
  • 全链路监控 + 多级告警。
钱包隔离硬约束:测试钱包密钥永不进 STAGING/PRODUCTION,生产密钥永不进 LOCAL/TESTNET;每 env 独立 KMS keyId / HD seed,encryption context 绑定 env(跨 env 密文不可解)。

大概流程

单一 artifact + 共享 application.yaml → 部署到目标 env
  → 该 env Nacos namespace 覆盖: RPC / USDT 合约 / keyId / confirmations / draw mode
  → 启动绑定 ChainProperties,业务代码不感知 env(也不感知本地链/测试网/主网)
上线推进(env 不变,逐步切换其指向的链网络):
  LOCAL+本地链 → TESTNET+本地链(CI 全绿) → TESTNET+公共测试网(真 VRF 端到端)
  → 合约审计 → STAGING+公共测试网回归 → STAGING+主网小额冒烟
  → PRODUCTION+主网 灰度 → PRODUCTION+主网 全量
chain-service
链 / 环境配置绑定
duobao.chain @ConfigurationProperties → ChainProperties(他服务经 Feign 取)
Nacos
多环境覆盖
每 env 一个 namespace
duobao-api
env / executionChain 枚举
Env · ExecutionChain
运维 / Ops
钱包隔离 / 部署矩阵
测试钱包密钥永不进 STAGING/PRODUCTION;每 env 独立 keyId / HD seed

环境变量

Environment variables

密钥类只进 KMS / Secrets Manager,环境差异类进 Nacos,引导类走进程 env;任何密钥不进仓库、不进 Nacos 明文、不进日志

内容存放
密钥类HD master seed(KMS ARN)、KMS keyId、operator key、RPC API keyKMS / Secrets Manager,运行时取,业务只持引用
环境差异RPC URL、USDT 合约、confirmations、draw mode、fee/energy 参数Nacos 每 env namespace
引导当前 env、Nacos namespace、KMS region / IAM role进程启动 env
  • 命名统一前缀(如 DUOBAO_),env 差异由 namespace 区分;密钥引用只存 ARN/keyId,不存值。
  • KMS envelope encryption:CMK 加密数据密钥;encryption context 绑定 duobao-env + duobao-seed-version;least-privilege IAM 限定可调 kms:Sign/Decrypt 身份,全调用审计。
启动: 读引导 env(ENV / NACOS_NS / KMS region)
  → 拉该 env Nacos namespace 环境差异配置
  → 解析密钥引用(keyId / seed ARN) → 运行时调 KMS,明文不落配置/磁盘/日志
  → 绑定 ChainProperties / SignProperties,业务代码不感知来源
chain-service
链 / 环境差异配置绑定
ChainProperties (duobao.chain)
sign-service
密钥引用 → KMS 解析
SignProperties (keyId → KMS ARN)
运维 / Ops
引导 + 密钥加载流程
读引导 env → 解析引用 → 运行时 KMS

资金安全

Treasury

冷(~90%,多签)/ 温(缓冲补给)/ 热(~10%,单签 KMS,覆盖 24-48h 出款量)三层;HD 充值地址定期归集;风控地址筛查贯穿充提。

资金分层与归集 / Treasury tiers & sweep —— 用户充值地址定期归集至热钱包供日常出款,主资金存多签冷钱包。
用户充值地址 deposit addrs
热钱包HOT 单签
WARM
冷钱包 🔒COLD 多签
充值地址 → 归集 SWEEP → 热 → 温 → 冷(多签)

TIER分层

  • :EVM Gnosis Safe / TRON 原生多签,离线签名,大额审批后补温层。
  • :热冷缓冲,定额补热,降冷钱包动用频率。
  • :单签 + KMS,每链一个独立热钱包。

RISK归集 + 风控

  • :chain-service planner 触发 → SweepService 执行 → SweepConfirmationPoller 跟踪 → chain_sweep_record 幂等。
  • 签名:用充值地址自己的 HD 子私钥(sign-service 按 seedVersion+path 重派生),提现的 operator KMS keyId。
  • 何时/到哪:dust threshold + 低峰 + 大额优先;EVM 先 gas station 预转 native、TRON DelegateResource 委托 energy;目的地 = 该链热钱包。
  • address screening:混币器 / OFAC SDN / 诈骗地址(可接 Chainalysis/TRM);提现黑名单含本平台充值地址池。
谁/何时: chain-service 归集 planner(定时低峰扫余额,≥dust threshold + 大额优先)→ 构造 SweepReq
EVM 归集: 余额≥threshold → gas station 转 native → 确认
  → sign-service 按 seedVersion+path 重派生子私钥签 → 广播 → 确认(私钥用完即焚)
TRON 归集: 余额≥threshold → DelegateResource 委托 energy → 派生子私钥签→广播→确认 → 取消委托
到哪: SweepReq.toAddress = 该链热钱包 → 再 热→温→冷 分层(冷钱包多签)
另一条: 合约金库内资金 DuobaoVault.sweepToColdWallet(SWEEPER_ROLE)→ 冷(vault→冷)
chain-service
归集 planner / 调度(谁·何时)
定时扫余额 + dust threshold + 低峰 + 大额优先 → SweepReq
chain-service
归集执行 + 确认 + 幂等
SweepService · SweepConfirmationPoller · chain_sweep_record(requestId)
sign-service
归集签名(地址 HD 子私钥)
SignEvmDerivedTxReq / SignTronDerivedTxReq(非 operator keyId)
链上
vault→冷(另一条路径)
DuobaoVault.sweepToColdWallet (SWEEPER_ROLE)
wallet-service
热/温/冷分层 + 风控
treasury 分层 · address screening

监控

Monitoring

指标全量采集 → Prometheus → Grafana + 告警规则 → 分级响应(on-call paging)。

SLI业务指标

  • watcher chain-head lag
  • 充提队列长度 · 提现失败率
  • VRF 成功率 / 回调时延 · subscription LINK 余额

$资金指标

  • 三链热钱包 native / USDT 余额
  • 冷/温钱包余额 · 待归集额
  • TRON energy / bandwidth 池余量
  • P0:节点全失联 / 提现卡死 / 热钱包或 energy 耗尽 / VRF >5min 未回调
  • P1:chain-head lag >5min / LINK 低 / 单 RPC 故障
  • P2:大额提现待审 / 可疑充值
各服务
指标暴露
metrics endpoint
运维 / Ops
采集 + 看板 + 告警
docker/prometheus · docker/grafana + 规则
on-call
分级响应
P0 page · P1/P2 告警渠道

合约审计 + 部署

Audit + Deployment

Foundry 一份部署脚本跑所有 EVM 链,部署后立即交多签/Timelock,主网前过外部审计

  • 审计门禁:Slither → Mythril → 第三方审计 → 区块浏览器 verify → 可选 Immunefi;未过审不上主网;改动需重审(code freeze)。
  • 密钥分离:部署 ≠ 运营 ≠ 冷钱包密钥;部署执行人 ≠ 多签持签人。脚本无明文私钥(vm.envUint + keystore);打印 addressBook 人工核对再写 Nacos;强制 --verify
  • 抢跑防护:多 RPC 不防 front-running;主网大额走私有交易通道 / Flashbots Protect / MEV-Boost private relay,不进公共 mempool。
部署顺序: AccessRegistry → DuobaoVault(→AccessRegistry+USDT)
  → DrawCoordinator(→Vault+VRF) → grant WITHDRAWER/SWEEPER
  → Vault.setDrawCoordinator → (上线前) transferOwnership 给 treasury 多签/Timelock
CI: forge build / forge test --fuzz / Slither 静态扫
回滚: 业务版本回滚 / DB 迁移回滚 / 合约经多签升级或 Pausable 急停(不可删已部署合约)
duobao-contracts/
合约构建 / 测试 / 部署
Foundry · DeployMocknet.s.sol · ExportAbi.sh
运维 / Ops
部署 / 升级 / 回滚 runbook
多签升级 · Pausable · 版本+DB 回滚
链上多签/Timelock
合约升级流程
treasury 多签 + Pausable

参考数据

Reference data

主网合约地址

地址
USDT ETH (ERC-20)0xdAC17F958D2ee523a2206206994597C13D831ec7
USDT BSC (BEP-20)0x55d398326f99059fF775485246999027B3197955
USDT TRON (TRC-20)TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t
VRF Coordinator (BSC)0xd691f04bc0C9a24Edb78af9E005Cf85768F694C9
VRF Coordinator (ETH)0x271682DEB8C4E0901D1a1550aD2e64D568E69909

关键 Java 库

  • EVM:org.web3j:core(Bip32ECKeyPair · RawTransaction · eth_feeHistory)
  • TRON coinType 195:org.bitcoinj:core 或 TRON SDK;交易走 /wallet/*
  • KMS:AWS SDK kms SignRequest(ECDSA_SHA_256 · DIGEST · 返回 DER)

关键测试场景

  • 同一交易处理 N 次(幂等)· watcher 崩溃断点续传
  • 链重组模拟(Anvil anvil_reorg)· nonce 冲突 / 断档自愈
  • EVM 同 nonce 替换 · 同链并发出款串行
  • VRF 边界(0 / 最大 / ==ticketTotal)· LINK 耗尽 · paused 下充提
~

术语表

Glossary
HD wallet / BIP-32/44
分层确定性钱包,一颗 seed 按路径派生;coin type 用 SLIP-0044(60=EVM,195=TRON)
hardened derivation (')
硬化派生,子私钥泄露不反推父/兄弟
confirmation depth
入账要求的确认区块数
reorg
链重组,已确认交易可能回滚
finality
不可逆终态(FFG / fast finality / solidified)
idempotency key
幂等键,同一充提只处理一次
account nonce
EVM 账户交易序号,严格递增
EIP-1559
type-2 tx,baseFee + tip + maxFee
EIP-155
chainId 进签名,防跨链重放
EVM chainId
EVM 网络标识 ETH=1 / BSC=56;TRON 用自有 0x2b6653dc,非 EIP-155
EIP-55
EVM 地址 mixed-case 校验和编码,防错填地址
EIP-2 / low-s
s≤n/2,消除签名可锻性
recovery id / yParity
从签名恢复公钥的标志位
ECDSA / secp256k1
EVM 与 TRON 共用签名算法
keccak256 / sha3
以太坊系哈希(非 FIPS SHA3);算地址 / topic0 / 待签 digest
ASN.1 DER
KMS 返回的签名编码,需解 (r,s)
non-standard ERC-20
主网 USDT 无 bool 返回,须 SafeERC20
SafeERC20
OpenZeppelin 包装库:容忍非标 ERC-20,失败必 revert
ABI 编码 / 左填充
合约调用/事件二进制编码;indexed address 左填充 32 字节取末 20
Base58Check
TRON 地址编码(0x41 + 双 SHA-256 校验)
Energy / Bandwidth
TRON 资源,转账耗 energy
Stake 2.0 / DelegateResource
TRON 质押换/委托资源
ref_block / expiration
TRON 交易锚定块 + 过期(替代 nonce)
同 nonce 交易替换
EVM 卡单顶替(非 RBF),新 gas +≥10%
VRF
带链上可验证密码学证明的随机数
keyHash / gas lane
Chainlink VRF gas 价档
subscription / consumer
VRF 计费订阅 / 授权合约
nativePayment / LINK premium
VRF 计费:原生币或 LINK 付;premium 是预言机服务溢价(算进每次开奖成本)
participantsHash
参与者快照承诺,链上可验证开奖
sha256 / Merkle root
快照承诺算法:确定序列化取 sha256;大集合用 Merkle root 支持 inclusion proof
modulo bias
取模偏差:随机数%N 当 N 不整除会偏心;本设计 N≪2²⁵⁶,偏差≈0 可忽略
EOA
外部账户,私钥直接控制的普通地址(区别于合约账户);operator/deployer 都是 EOA
Foundry / Anvil
Solidity 合约开发测试框架 / Foundry 自带本地 EVM 链
Slither / Mythril
合约漏洞扫描:Slither 静态分析、Mythril 符号执行;上线门禁
fuzz / invariant 测试
随机输入模糊测试 / 不变量测试(某断言任何状态都必须成立)
UUPS / EIP-1822/1967
可升级代理标准
RBAC / AccessRegistry
合约角色权限矩阵:OPERATOR / WITHDRAWER / SWEEPER / EMERGENCY
CEI / ReentrancyGuard
checks-effects-interactions + 重入保护
multisig / Timelock
多签治理 + 升级公示窗口
KMS / envelope encryption
密钥托管 / 信封加密 / 上下文绑定 env
gas station
给充值地址预转 native 付归集 gas
address screening / OFAC SDN
链上地址风控筛查 / 制裁名单
front-running / private relay
mempool 抢跑 / Flashbots 私有通道
manual claim
用户主动报账式充值确认
用户自报 txHash、系统只校验该笔再入账(对比 watcher 主动扫块);本设计仅作兜底