TRON 虚拟机 (TVM)

TRON 虚拟机 (TVM) 是每个 TRON 节点执行智能合约的确定性运行时,也是智能合约层的状态转换函数。

📘

前置阅读

TRON 虚拟机(TVM, TRON Virtual Machine)是 TRON 智能合约的执行环境。每个节点在处理合约部署或合约调用交易时,都会在本地运行同一套 TVM 规则;只要输入的父状态和交易集合相同,所有节点必须得到完全一致的新状态。

因此,TVM 不只是“运行 Solidity 字节码”的组件。它定义了智能合约如何读取账户、区块和交易上下文,如何消耗能量,如何写入存储,以及出现 REVERTOUT_OF_ENERGYOUT_OF_TIME 等结果时如何回滚状态。

从账本到状态机

“分布式账本”常用于描述比特币一类区块链:账户或地址围绕资产余额展开,交易只是在账本中转移价值。 TRON 同样有原生资产 TRX,也遵循余额不能凭空支出的基本规则,但 TRON 的链上状态不止余额。

在 TRON 中,状态包含账户余额、合约字节码、合约存储、资源使用量、权限配置等数据。智能合约交易会读取这些数据,并按照字节码逻辑生成新的状态。换句话说, TRON 是一个分布式状态机;TVM 是智能合约层的状态转换引擎。

状态转换函数

TVM 的核心约束是确定性。给定旧状态 S 和一组有效交易 T,节点执行后必须得到同一个新状态 S'

Y(S, T) = S'

这个公式背后的协议要求很直接:任何节点只要看到相同的 ST,就必须复现同一个 S'。TVM 因此不能依赖本地时钟、线程调度、浮点数等非确定性来源;每个 opcode 的行为都必须在所有节点上保持一致。

全局状态

TRON 的全局状态可以理解为“地址到账户数据”的映射。账户数据通常包括:

  • 余额:账户持有的 TRX,单位为 sun
  • 代码:合约账户持有的已部署字节码。
  • 存储:合约账户持久化保存的键值数据。
  • 资源状态:带宽、能量以及相关恢复状态,参见资源模型
  • 权限:Owner、Active、Witness 等权限配置,参见账户权限管理

TRON 区分两类账户:外部账户(EOA)由私钥控制,合约账户由已部署字节码控制。只有合约账户拥有非空的 codestorage。地址格式和账户派生过程参见账户与密钥

📘

注意

TRON 账户没有用于防重放的 nonce 字段。交易防重放依赖 TAPOS:交易会引用近期区块 ID,并在构建后的一段时间内过期。

哪些交易会进入 TVM

TRON 交易大致分为两类:

  • 系统合约交易:TRX 转账、TRC-10 Token 发行与转账、投票、质押、资源代理等协议内置操作。这类交易由对应的系统合约逻辑处理,不进入 TVM。
  • 智能合约交易CreateSmartContract 用于部署新合约,TriggerSmartContract 用于调用既有合约。这两类交易会进入 TVM 执行。

合约部署成功后,网络会创建一个新的合约账户,并把编译后的字节码写入该账户。此后,其他账户对该合约发起调用时,TVM 会加载对应字节码并按 opcode 顺序执行。

完整交易结构和系统合约类型参见交易系统合约类型

执行上下文

每次 TVM 执行都运行在一组分层上下文中。由外到内可以分为四层:

  1. 世界状态:父区块结束时的全局账户状态。TVM 通过宿主接口读取和写入账户、余额、代码、存储等数据。
  2. 区块上下文:当前区块的元数据,例如区块高度、时间戳、出块 SR、父区块 ID。合约可通过 NUMBERTIMESTAMPCOINBASEBLOCKHASHCHAINID 等 opcode 读取部分信息。
  3. 交易上下文:触发本次执行的 CreateSmartContractTriggerSmartContract 交易。合约可通过 ORIGINGASPRICE 等 opcode 读取交易相关信息;在 TVM 中,GASPRICE 返回当前能量单价 energyPrice
  4. 调用帧:当前合约调用的局部执行环境。每一帧都有自己的 callervaluecallDatacodepc、操作数栈和临时内存。合约调用另一个合约时,新的调用帧会压入调用栈。

调用帧之间不会共享操作数栈和临时内存。跨帧传递数据只能通过 calldataRETURN / REVERT 返回数据,以及合约存储写入完成。

栈、内存与存储

TVM 是一台基于栈的虚拟机。执行时最容易混淆的是操作数栈、调用栈、内存和存储,它们的生命周期与限制不同:

区域生命周期用途关键限制
操作数栈单个调用帧内opcode 的直接操作对象,例如 PUSHPOPDUPSWAP最大 1024 个 256 位字
调用栈一次合约执行内记录合约间嵌套调用最大深度 64
内存单个调用帧内临时、按字节寻址的数据区每个调用帧最多 3 MiB
存储跨交易持久化合约账户的持久状态按 256 位键值写入,费用高于内存

操作数栈只保存当前 opcode 需要处理的 256 位字。调用栈则用于限制合约之间的嵌套调用深度,包括递归调用。两者不是同一个栈。

内存是临时区域,调用结束后即失效;存储属于合约账户,会跨交易保留。TVM 对每个调用帧设置了 3 MiB 的固定内存上限。EVM 没有同样的固定内存上限,主要通过 Gas 成本约束内存扩张;因此,大规模内存分配类合约在以太坊上能跑完,不代表在 TVM 上一定能通过。

TVM 指令

Solidity 合约编译后会变成 TVM opcode 序列。TVM 支持常见栈操作和算术逻辑操作,例如 PUSHPOPADDSUBANDXOR,也支持区块链环境相关指令,例如 ADDRESSBALANCEBLOCKHASHCALLSSTORE

单次合约调用的大致流程如下:

  1. Solidity 编译器将源代码编译为 TVM 可执行字节码。
  2. TVM 按顺序执行 opcode,并在所有节点上保持确定性结果。
  3. opcode 需要账户、区块、交易或存储数据时,通过执行上下文读取。
  4. 执行结束后,节点把结果写入交易回执,并根据执行结果提交或回滚状态变更。

完整 opcode 参考参见 Opcodes

执行结果与异常

TVM 执行结果记录在交易回执的 receipt.result 字段中。常见结果可以分为三类:

  • SUCCESS:字节码正常执行到 STOPRETURN 或有效的终止路径。
  • REVERT:合约主动执行 REVERT,通常来自 Solidity 的 requirerevert 或自定义错误。该调用产生的状态变更会回滚,未消耗的能量会退还。
  • 异常停止:执行过程中触发协议级错误或资源耗尽,状态回滚。常见结果包括:
    • OUT_OF_ENERGY:分配给交易的能量预算耗尽。
    • OUT_OF_TIME:单笔交易 TVM 执行超过 80 毫秒物理时间预算。
    • STACK_TOO_LARGE / STACK_TOO_SMALL:操作数栈溢出或下溢。
    • JVM_STACK_OVER_FLOW:调用深度或实现层栈限制触发。
    • BAD_JUMP_DESTINATION:跳转目标非法。
    • ILLEGAL_OPERATION / INVALID_CODE:遇到未启用、非法或无效的 opcode。
    • PRECOMPILED_CONTRACT:预编译合约执行失败。
    • TRANSFER_FAILED:合约内部 TRX 或 TRC-10 Token 转账失败。
💡

80 毫秒执行上限

TRON 除了通过能量预算限制 TVM 执行开销,还通过链参数 #13 getMaxCpuTimeOfOneTx 限制单笔交易占用的物理 CPU 时间。能量预算通常由调用方 fee_limit、合约部署方 origin_energy_limit 和链级上限共同约束。私有链调试时可通过 --debug 绕过该限制;同步主网的节点不应启用该标志,否则本地执行结果可能与链上结果不一致,导致同步失败。

Solidity 层如何处理这些结果,参见 VM 异常处理

能量计量

TVM 用能量(Energy)计量智能合约执行成本。系统合约交易通常只消耗带宽;智能合约部署和调用才会进入 TVM 并消耗能量。

每个 opcode 在执行前扣除固定或动态计算的能量。单次调用可使用的能量受多重边界约束,常见约束包括:

  • 调用方设置的 fee_limit,按当前能量单价 energyPrice 折算为可购买能量。
  • 合约部署方配置的 origin_energy_limit,用于限制部署方愿意为调用方承担的能量。
  • 链级最大 fee_limit,由链参数 #47 getMaxFeeLimit 控制。

如果执行耗尽能量,TVM 会返回 OUT_OF_ENERGY,回滚本次调用造成的状态变更,并消耗该交易已分配的能量预算。完整费用模型参见 FeeLimit 与能量成本,带宽与能量来源参见资源模型

硬分叉门控

TVM 会逐步引入以太坊硬分叉中的部分 opcode 和行为,但这些功能并不是在所有网络上同时自动生效。每一组功能都有对应链参数门控,例如 getAllowTvmConstantinoplegetAllowTvmIstanbulgetAllowTvmLondongetAllowTvmShangHaigetAllowTvmCancungetAllowTvmBlob

在目标网络的门控参数启用之前,相关 opcode 会被视为 ILLEGAL_OPERATION。因此,合约是否能使用某个新 opcode,取决于当前网络的链参数状态,而不是仅取决于编译器是否支持。完整兼容矩阵参见 TVM 与 EVM 对比

相关资源