区块链技术方案
充值 / 提现 / 智能合约 / 环境×链 / 环境变量 / 资金安全 / 监控 / 审计+部署 —— 每个主题:支持的链 · 技术方案 · 大概流程 · 实现服务。
目标态设计规范:描述系统应当如何实现,非当前代码现状。
Target-state design spec — how the system should work, not current code.
充值
Deposit用户拿到专属 HD 地址 → 转 USDT → 系统自动扫块、确认、归一、入账,全程无操作、无 manual claim(用户主动报账式充值确认)。下面按事情实际发生的顺序展开。
支持的链与资产
| 链 | 链 ID / Network id | 标准 | USDT decimals | 确认数 / 不可逆依据 |
|---|---|---|---|---|
| ETH | 1 (EIP-155) | ERC-20 | 6 | 12 常规 / finalized ~2 epoch · Casper FFG |
| BSC | 56 (EIP-155) | BEP-20 | 18 | 15 · fast finality (BEP-126) |
| TRON | 0x2b6653dc 自有 | TRC-20 | 6 | 19 · 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>'/ TRONm/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 块,filteraddress=[USDT]+topics[0]。topics[1]=from、topics[2]=to(地址左填充 32 字节,取末 20)。value在log.data(非 indexed),不在 topics。
TRONTRC-20
TriggerSmartContract交易,优先transactioninfo.log的Transfer事件,calldata 兜底。- 必校
contractRet=="SUCCESS":允许「上链但执行失败」仍计费。 - calldata:selector
a9059cbb+ to(末 20 字节,0x41→ Base58Check)+ amount。 - 一笔即一交易,无 logIndex,幂等键
(chain, tx_hash)。
- 地址缓存:启动全量加载 Redis Set,新增经 Pub/Sub 增量;
to不命中直接丢弃。 - 多 RPC:每链主 + ≥2 备,健康探测自动切换。
怎么算「充值成功」:确认 → 归一 → 入账
uid 10000001 充值 1000 USDT,ETH / BSC / TRON 任选其一)。m/44'/60'/10000001' → 0x9F8e7D6c5B4a3F2e1D0c9B8a7F6e5D4c3B2a1F0e
TRONm/44'/195'/10000001' → TJ2pK9mR7nQ4sV6wX8yZ3aB5cD1eF7gHkN
wallet_deposit_address 仍按 UNIQUE(uid, execution_chain) 每链各存一行:
(10000001, ETH) → 0x9F8e7D6c…3B2a1F0e
BSC(10000001, BSC) → 0x9F8e7D6c…3B2a1F0e(与 ETH 同址,不同行)
TRON(10000001, TRON) → TJ2pK9mR…eF7gHkN
0x9F8e7D6c…3B2a1F0e
BSC→ 0x9F8e7D6c…3B2a1F0e(与 ETH 同地址)
TRON→ TJ2pK9mR…eF7gHkN
eth_getLogs Transfer / TRON transactioninfo.log
to 命中地址缓存 → 进入入账
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
- 确认数过滤:
latest - blockNumber ≥ confirmations(ETH 12 / BSC 15 / TRON 19)才不可逆,未达只挂起不入账。 - 幂等:DB unique 约束 + Redis SETNX 双层,重复事件忽略。
- 入账:账务事务内写 deposit + 归一金额加余额 + 发
deposit.confirmed(记充值与加余额原子)。 - Reorg 防线:周期比对
[head-50, head]blockHash,失配回滚 deposits+账务、回退游标重扫、告警。
value 必须按链 decimals(BSC=18 / ETH·TRON=6)归一到统一内部单位,严禁直接当统一单位——否则 BSC 放大 1e12 倍错误入账。入账之后:钱还在派生地址,要归集(谁 / 何时 / 到哪)
入账只是 DB 给用户加余额,链上 USDT 仍在该用户专属 HD 派生地址,不在平台热钱包。这些散落地址的钱必须定期归集 sweep 汇集起来,才能形成可用于出款的资金。
- 谁来做:全程在
chain-service—— 归集 planner/scheduler 挑地址、构造SweepReq→SweepService构造并广播 →SweepConfirmationPoller跟踪确认 →chain_sweep_record(键requestId)幂等。wallet-service不参与(不碰链)。 - 用哪把私钥(关键):钱从用户 HD 派生地址转出,必须用该地址自己的 HD 子私钥签 ——
chain-service把seedVersion+derivationPath给sign-service(SignEvmDerivedTxReq/SignTronDerivedTxReq)临时重派生签名、用完即焚。与提现用每链固定 operator 热钱包 KMSkeyId签完全不同。 - 什么时候: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,见 六)
实现服务
提现
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-service的ChainProperties(operator 地址 + KMS keyId)。 - 默认一链一个(既是 sweep 目的地又是出款源);仅为提 TPS 分片成多热钱包时才有「多个出款地址」,那时每个各自既被归集补给、也各自出款(见下)。
- 谁决定用哪个:
chain-service。wallet-service审核通过后只把一笔APPROVED单交给chain-service,不关心具体热钱包地址。 - 热钱包单签 + KMS,持覆盖 24-48h 出款量;签名在
sign-service。注意:出款用 operator 热钱包 KMSkeyId,与归集用「用户地址 HD 子私钥」签不同(见 一.2.6)。
多个出款热钱包如何分配(均 chain-service 广播侧)
- 单热钱包是默认:一链一热钱包,
chain-service出款分链消费、同链串行广播。EVM nonce 须严格递增、TRONref_block/expiration有时效,单地址并发会自相挤兑或断号。 - 提 TPS 才分片:一链开多个热钱包,
chain-service把单按hash(requestId)%N或轮询绑定某个热钱包,各自串行、各自独立 nonce /ref_block序列、各自独立补给,互不共享 nonce 计数器。
链上出款机制
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-155v=chainId×2+35+recId。
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
实现服务
智能合约
Smart contracts先一句话:钱锁在链上金库、由代码按规则放款(不是后端说了算);开奖用谁都操纵不了、人人可复算的链上随机数;敏感操作按角色授权;合约能升级修 bug 但要多签 + 公示延时;主网前过外部审计。源码 duobao-contracts/,Solidity ^0.8.26,Foundry,EVM 部署 BSC(主)/ ETH(备)。
🎲
三个合约各是干嘛的(先大白话)
| 合约 | 一句话职责 | 关键能力 |
|---|---|---|
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) |
主线:钱进 DuobaoVault → DrawCoordinator 找 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 ③EMERGENCYkey 刻意分开,一把被攻破不波及其他。 - 在 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 不重放 · 链上金库余额 ≥ 用户可用+冻结+待提现。
环境 × 链
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 余量监控自动告警。
- 全链路监控 + 多级告警。
大概流程
单一 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+主网 全量
环境变量
Environment variables密钥类只进 KMS / Secrets Manager,环境差异类进 Nacos,引导类走进程 env;任何密钥不进仓库、不进 Nacos 明文、不进日志。
| 层 | 内容 | 存放 |
|---|---|---|
| 密钥类 | HD master seed(KMS ARN)、KMS keyId、operator key、RPC API key | KMS / 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,业务代码不感知来源
资金安全
Treasury冷(~90%,多签)/ 温(缓冲补给)/ 热(~10%,单签 KMS,覆盖 24-48h 出款量)三层;HD 充值地址定期归集;风控地址筛查贯穿充提。
TIER分层
- 冷:EVM Gnosis Safe / TRON 原生多签,离线签名,大额审批后补温层。
- 温:热冷缓冲,定额补热,降冷钱包动用频率。
- 热:单签 + KMS,每链一个独立热钱包。
RISK归集 + 风控
- 谁:
chain-serviceplanner 触发 →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→冷)
监控
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:大额提现待审 / 可疑充值
合约审计 + 部署
Audit + DeploymentFoundry 一份部署脚本跑所有 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 急停(不可删已部署合约)
参考数据
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
kmsSignRequest(ECDSA_SHA_256·DIGEST· 返回 DER)
关键测试场景
- 同一交易处理 N 次(幂等)· watcher 崩溃断点续传
- 链重组模拟(Anvil
anvil_reorg)· nonce 冲突 / 断档自愈 - EVM 同 nonce 替换 · 同链并发出款串行
- VRF 边界(0 / 最大 / ==ticketTotal)· LINK 耗尽 ·
paused下充提
术语表
Glossary用户主动报账式充值确认