L1.3 - ReactVM和睿应式网络

双重实例

一个响应式合约虽然只有一套代码,但它会同时运行在两个不同的环境中:Reactive Network (RN)ReactVM。即每个响应式合约在部署后,实际上存在两个物理上的实例:

  • Reactive Network (RN) 实例:表现得像传统的 EVM 区块链,负责与系统合约交互,管理事件的订阅(Subscribe)和取消订阅(Unsubscribe)。
  • ReactVM 实例:这是一个受限的隔离环境,专门用于处理事件逻辑。它不直接与外部交互,而是通过 RN 接收事件并发送回调请求。

Reactive Network的本质是普通的 EVM 区块链 + 额外的"系统合约",其监听以太坊、BNB、Polygon、Optimism 等链上的事件,管理订阅关系(订阅/取消订阅),同时接收用户直接发起的交易。

而ReactVM本质是一个"隔离的小虚拟机",每个部署者地址专属一个,它是专门用来处理事件的,同一地址部署的合约可以互相交互,但不能与其他地址的 Reactive Network 合约交互,用户也不能直接调用它。

![[Pasted image 20260310174807.png]]

ReactVM 虽然是隔离的,但可以通过两种方式与外界沟通:

① 源链发生事件 → Reactive Network 转发 → ReactVM 接收并处理

② ReactVM 处理完 → 发请求给 Reactive Network → 目标链执行回调

特性 Reactive Network (前台/大厅) ReactVM (实验室/引擎)
本质 标准的 EVM 区块链(类似以太坊)。 受限的、隔离的执行环境。
主要任务 管理订阅:负责记录你要听哪些链的事件。 处理逻辑:负责计算接收到的事件并决定做什么。
交互对象 任何人(用户可以调用它来修改设置)。 只有事件:不直接接触外界,只吃“事件数据”。
可见性 公开,所有节点都能看到状态。 隔离,同一部署者的合约才能互通。
输出结果 记录状态、变更订阅列表。 产生回调(Callback),让前台去执行。

我们可以把整个流程看作一个自动化工厂的运作:

  1. 订阅(在前台中):你在 Reactive Network 上调用合约,告诉它:“帮我盯着以太坊上的 Uniswap 交易事件。”
  2. 捕获(从源链到前台):Reactive Network 捕获到了那个交易事件。
  3. 计算(在实验室里):Reactive Network 把事件数据塞进 ReactVM。在这里,合约的 react() 函数开始运行,计算:“现在的价格达到我的止损线了吗?”
  4. 执行(从实验室回到前台):如果满足条件,ReactVM 发出一个指令给 Reactive Network,说:“去帮我给目标链发一个回调,执行卖出操作。”

所以在代码里,我们会看到:

if (!vm) {
    // 这部分代码只会在“前台”跑,比如去注册订阅
} else {
    // 这部分逻辑只会在“实验室”里跑,处理具体的业务
}

双重实例的意思就是,代码里声明的变量(比如 uint256 public price),在 RN 里有一个值,在 ReactVM 里有另一个完全独立的值。它们就像是在平行宇宙里的同一个人,有着相同的外貌(代码),但经历(状态)完全不同。

使用双重实例是为了解决“高性能”“安全性”的问题:

  • 并行处理(高性能):ReactVM 是按部署者地址隔离的。如果你部署了 100 个合约,它们可以在不同的 ReactVM 里同时跑,互不干扰,也不会堵塞 Reactive Network 主链。
  • 状态隔离
    • RN 状态:保存的是“行政数据”(比如:我是否暂停了服务?我订阅了哪个地址?)。
    • ReactVM 状态:保存的是“逻辑数据”(比如:上次捕获的价格是多少?我已经触发过回调了吗?)。

识别执行上下文

前面我们知道,同一套代码会在两个环境中运行,但有些函数只能在特定环境中执行,比如:

  • 订阅事件 → 只能在 Reactive Network
  • 处理事件逻辑 → 只能在 ReactVM

所以合约需要知道其当前处于的环境是哪个。

在 Reactive Network 的设计中,有一个特殊的系统合约地址:0x0000000000000000000000000000000000fffFfF

  • 在 Reactive Network (RN) 中: 这个地址是真实存在的,上面部署了系统逻辑(用来处理订阅)。
  • 在 ReactVM 中: 这是一个完全隔离的沙盒环境。为了安全和性能,这个系统合约地址在 ReactVM 里是不存在的,也就是“真空地带”。

合约通过一段简单的汇编代码来做检测:

function detectVm() internal {
    uint256 size;
    // 使用内联汇编获取目标地址的代码大小
    assembly { size := extcodesize(0x0000000000000000000000000000000000fffFfF) }
    // 如果大小为 0,说明这个地址没东西 -> 我在 ReactVM 里
    vm = size == 0;
}

用汇编的原因是 Solidity 本身没有直接检查"某个地址代码大小"的内置函数,必须用底层的 EVM 汇编指令来实现。这里不直接调用系统合约来判断原因是在 ReactVM 里,合约不存在,所以直接调用是会报错崩溃的。

强制执行上下文

我们通过 detectVm() 识别不同的环境,而下面我们需要给合约的不同功能装上门禁,让其在不同的环境下工作。

我们来设想一个场景,由于同一份代码在两个环境里跑,如果 react() 函数(专门处理繁重逻辑的)不小心在 Reactive Network(主网)上跑了,会发生什么?

  • 浪费 Gas:主网上的计算非常昂贵。
  • 逻辑冲突:主网的状态和 VM 的状态不一样,混在一起会导致逻辑崩盘。

因此我们导入两个函数:

rnOnly()

modifier rnOnly() {
    require(!vm, 'Reactive Network only');
    _;
}
  • 逻辑:它检查 vm 是不是 false
  • 直白解释:如果你在 ReactVM(实验室)里尝试调用带这个修饰符的函数,它会直接报错弹出。
  • 例子:比如 pause() 函数。你只想在主网上通过手动操作来暂停合约,而不希望它在处理某个事件时被自动触发。

vmOnly()

modifier vmOnly() {
    require(vm, 'VM only');
    _;
}
  • 逻辑:它检查 vm 是不是 true
  • 直白解释:这个函数只能在 ReactVM 那个“黑盒子”里运行。
  • 例子:核心函数 react(LogRecord calldata log)。这个函数是专门给 ReactVM 的引擎调用的,它接收外界的事件并决定要不要做跨链操作。

双重变量

我们已经知道同一套代码在两个环境中运行,且各自有独立的状态。但是但这就带来一个问题:“合约里的变量,到底是给哪个环境用的?”

答案就是给两个环境各准备一套专属变量。需要注意的是,变量在两个环境中是独立的,也就是在概念上是两个变量。

RN变量

RN变量继承自 AbstractReactive 合约,当你写 contract MyContract is AbstractReactive 时,不需要自己定义,系统会自动塞给你两个重要的工具:

  • vm (布尔值):这就是判断环境的函数。合约运行的一瞬间,它就知道自己是在 ReactVM (true) 还是 Reactive Network (false)
  • service (接口):这是一个“电话机”。只有通过它,你才能告诉系统:“我要监听哪条链上的哪个动作。”

RM变量

RM变量是更加关键的部分,其在你自己的响应式合约里声明,职责就是记录业务逻辑所需的数据和在react() 函数中被读取和修改

实际运行

以Uniswap 止损订单响应式合约的构造函数为例子,如下:

// State specific to ReactVM instance of the contract.  
  
bool private triggered;  
bool private done;  
address private pair;  
address private stop_order;  
address private client;  
bool private token0;  
uint256 private coefficient;  
uint256 private threshold;  
  
constructor(  
address _pair,  
address _stop_order,  
address _client,  
bool _token0,  
uint256 _coefficient,  
uint256 _threshold  
) payable {  
triggered = false;  
done = false;  
pair = _pair;  
stop_order = _stop_order;  
client = _client;  
token0 = _token0;  
coefficient = _coefficient;  
threshold = _threshold;  
  
if (!vm) {  
service.subscribe(  
SEPOLIA_CHAIN_ID,  
pair,  
UNISWAP_V2_SYNC_TOPIC_0,  
REACTIVE_IGNORE,  
REACTIVE_IGNORE,  
REACTIVE_IGNORE  
);  
service.subscribe(  
SEPOLIA_CHAIN_ID,  
stop_order,  
STOP_ORDER_STOP_TOPIC_0,  
REACTIVE_IGNORE,  
REACTIVE_IGNORE,  
REACTIVE_IGNORE  
);  
}  
}

在代码中triggered = false 等初始化变量在两个环境中都初始化,但是if (!vm)里的关于监听的代码只在VN中进行。这些变量都是为了react()中的逻辑判断。以下是例子:

// Methods specific to ReactVM instance of the contract.  
function react(LogRecord calldata log) external vmOnly {  
assert(!done);  
  
if (log._contract == stop_order) {  
if (  
triggered &&  
log.topic_0 == STOP_ORDER_STOP_TOPIC_0 &&  
log.topic_1 == uint256(uint160(pair)) &&  
log.topic_2 == uint256(uint160(client))  
) {  
done = true;  
emit Done();  
}  
} else {  
Reserves memory sync = abi.decode(log.data, ( Reserves ));  
if (below_threshold(sync) && !triggered) {  
emit CallbackSent();  
bytes memory payload = abi.encodeWithSignature(  
"stop(address,address,address,bool,uint256,uint256)",  
address(0),  
pair,  
client,  
token0,  
coefficient,  
threshold  
);  
triggered = true;  
emit Callback(log.chain_id, stop_order, CALLBACK_GAS_LIMIT, payload);  
}  
}  
}

react()函数被标记为vmOnly,它处理两种情报:

来自 stop_order 合约的反馈

if (log._contract == stop_order) {
    if (triggered && ... ) {
        done = true; // 任务圆满完成,贴上封条
        emit Done();
    }
}
  • 逻辑:它在等一个信号。如果它发现已经触发了卖出(triggered 为真),并且收到了目标链传回来的“确认卖出成功”的日志,它就会把 done 设为 true。代表这笔止损交易彻底结束了。

来自 Uniswap pair 的价格波动

else {
    Reserves memory sync = abi.decode(log.data, ( Reserves )); // 解码价格情报
    if (below_threshold(sync) && !triggered) { // 跌破线了且还没触发过
        triggered = true; // 立刻锁定开关,防止二次触发
        emit Callback(...); // 向 Reactive Network 发送“攻击指令”
    }
}
  • 逻辑:它不断接收 Uniswap 的储备量(Reserves)数据。一旦计算发现价格跌破了 threshold,它会执行两件事:
    1. triggered 设为 true(从此之后,这个 if 就再也进不来了,防止重复触发)。
    2. 发出 Callback 事件。注意:在 ReactVM 里发出 Callback 事件,就相当于向 Reactive Network 发出了一个“跨链代操作”的请求。

交易执行

使用响应式合约(RC)时,交易主要发生在两个环境中:睿应式网络(Reactive Network)和 ReactVM。每个环境在发起和处理交易方面均遵循不同的规则。

RN交易

在 Reactive Network (RN) 这一侧,交易就像你在以太坊上操作合约一样,是显式的。这里的交易发起者是合约管理员,而操作RN交易的原因一般是执行管理任务,比如“现在市场太波动了,我要暂时关闭这个止损程序”。

比如pause()resume()

  • 这两个函数带有 rnOnly
  • 它们会调用 service.unsubscribe。这就像是你打电话给接线员说:“别再把那个交易对的消息转接给我了。”
  • 结果: 这一动作发生在 RN 的存储状态里,它直接切断了传往 ReactVM 的“情报流”。

同时,RN也可以发起一些事件分发交易,这里就是说订阅事件的时候,当源链(如 Ethereum)产生一个事件时,RN 会启动一个内部交易,把这个事件“快递”给对应的 ReactVM 实例。

RM交易

在 ReactVM 内部,唯一的触发源就是来自 RN 的事件分发,收到RN的分发后去执行react()函数,如果符合就去执行callback。

维度 Reactive Network 交易 ReactVM 交易
发起者 用户(你)或系统节点 仅限系统转发的事件
可达性 公开,可以通过钱包/脚本调用 私密,外界无法直接触达
主要修饰符 rnOnly vmOnly
目的 修改合约配置、订阅列表 运行算法逻辑、产生跨链回调
Gas 消耗 消耗 RN 链上的 Gas 运行逻辑本身不耗 RN Gas(沙盒执行)