链上风控从状态差分开始:把检测、授权和熔断拆开

发布时间:2026/10/25 3:41:14
链上风控从状态差分开始:把检测、授权和熔断拆开

监控程序发现资产余额下降,就立刻调用暂停函数,听起来很直接。可下降可能来自合法提款、同一批结算的中间状态,或者两个 RPC 返回了不同区块的数据。没有负债、区块身份和权限上下文,余额差分只是一个变化量,不能自动解释成攻击。

10 月 5 日以太坊基金会讨论交易结果断言时,特别区分了签名动作和最终状态。对风控系统,这意味着事前模拟、执行中的约束与事后核对各有位置,不能用一次本地模拟替代全部环节。官方讨论

先得到同一条历史上的两份快照

快照至少包含链标识、区块哈希、父哈希、资产和负债,并注明数值单位。跨多个合约读取时,需要固定区块;只保存“latest”和采集时间,无法证明数据来自同一状态。遇到重组时,要撤销旧分支产生的告警,再基于规范链重新评估。

一个简单的金库教学模型可以检查资产是否覆盖负债,但这也依赖估值、清算和会计定义。资产少于负债是需要处理的状态,并不独立证明黑客入侵;资产仍覆盖负债,也不保证所有安全属性正常。

from dataclasses import dataclass @dataclass(frozen=True) class Snapshot: block_hash: str parent_hash: str assets: int liabilities: int def inspect(before, after, canonical_hash): if after.parent_hash != before.block_hash: return "RECHECK_HISTORY" if after.block_hash != canonical_hash: return "RECHECK_HISTORY" if min(before.assets, after.assets, before.liabilities, after.liabilities) < 0: return "INVALID_DATA" return "ALERT_COVERAGE" if after.assets < after.liabilities else "NO_COVERAGE_ALERT" before = Snapshot("h0", "old", 100, 90) assert inspect(before, Snapshot("h1", "h0", 70, 90), "h1") == "ALERT_COVERAGE" assert inspect(before, Snapshot("h1", "h0", 95, 90), "h1") == "NO_COVERAGE_ALERT" assert inspect(before, Snapshot("fork", "h0", 70, 90), "h1") == "RECHECK_HISTORY" assert inspect(before, Snapshot("h2", "other", 70, 90), "h2") == "RECHECK_HISTORY" print("4 snapshot cases checked; no pause transaction sent")

保存为snapshots.py,运行python3 snapshots.py。资产与负债数值是教学输入;canonical_hash由调用者提供,程序没有验证共识或最终性。四个用例只验证覆盖告警和分支核对,不是主网风险检测效果测试。

告警之后还有两次独立判断

第一判断是动作是否获准。哪个角色能暂停哪个功能,额度是多少,是否需要多个独立确认者,都应由合约和签名系统约束。OpenZeppelin 的访问控制文档提供了角色分工的实现基础;它不能替协议决定正确的应急权限。文档

第二判断是动作是否生效。发送暂停交易后,要检查回执及目标状态;不能在函数调用发出时打印“攻击已阻断”。若交易未入块、权限已撤销或合约状态改变,系统应保留未完成状态和人工处置入口。

这个最小例子没有接入节点、预言机、签名器或熔断合约。检测代码的执行耗时、RPC 获取快照的延迟和交易确认时间是三个不同指标,本地例子通过不能支持“毫秒级链上防御”的结论。风控要快,但更要说清楚自己看到了什么、依据什么动作,以及动作是否真的发生。