原文链接:filecoin-project.github.io/specs/#syst…
本文由星际联盟Boni Liu翻译,转载请注明出处。
星际联盟提供对Filecoin Spec 的全文翻译,便于Filecoin项目广大中国参与者理解Filecoin的深层原理。文章将定期更新章节,请持续关注"IPFS星际联盟"&"星际联盟Filecoin"公众号。
2.3 VM - 虚拟机
import msg "github.com/filecoin-project/specs/systems/filecoin_vm/message"
import st "github.com/filecoin-project/specs/systems/filecoin_vm/state_tree"
// VM is the object that controls execution.
// It is a stateless, pure function. It uses no local storage.
//
// TODO: make it just a function: VMExec(...) ?
type VM struct {
// Execute computes and returns outTree, a new StateTree which is the
// application of msgs to inTree.
//
// *Important:* Execute is intended to be a pure function, with no side-effects.
// however, storage of the new parts of the computed outTree may exist in
// local storage.
//
// *TODO:* define whether this should take 0, 1, or 2 IpldStores:
// - (): storage of IPLD datastructures is assumed implicit
// - (store): get and put to same IpldStore
// - (inStore, outStore): get from inStore, put new structures into outStore
//
// This decision impacts callers, and potentially impacts how we reason about
// local storage, and intermediate storage. It is definitely the case that
// implementations may want to operate on this differently, depending on
// how their IpldStores work.
Execute(inTree st.StateTree, msgs [msg.UnsignedMessage]) union {outTree st.StateTree, err error}
}
2.3.1 VM Actor 接口
// This contains actor things that are _outside_ of VM exection.
// The VM uses this to execute abi.
import abi "github.com/filecoin-project/specs-actors/actors/abi"
import actor "github.com/filecoin-project/specs-actors/actors"
import cid "github.com/ipfs/go-cid"
// CallSeqNum is an invocation (Call) sequence (Seq) number (Num).
// This is a value used for securing against replay attacks:
// each AccountActor (user) invocation must have a unique CallSeqNum
// value. The sequenctiality of the numbers is used to make it
// easy to verify, and to order messages.
//
// Q&A
// - > Does it have to be sequential?
// No, a random nonce could work against replay attacks, but
// making it sequential makes it much easier to verify.
// - > Can it be used to order events?
// Yes, a user may submit N separate messages with increasing
// sequence number, causing them to execute in order.
//
type CallSeqNum int64
// Actor is a base computation object in the Filecoin VM. Similar
// to Actors in the Actor Model (programming), or Objects in Object-
// Oriented Programming, or Ethereum Contracts in the EVM.
//
// ActorState represents the on-chain storage all actors keep.
type ActorState struct {
// Identifies the code this actor executes.
CodeID abi.ActorCodeID
// CID of the root of optional actor-specific sub-state.
State actor.ActorSubstateCID
// Balance of tokens held by this actor.
Balance abi.TokenAmount
// Expected sequence number of the next message sent by this actor.
// Initially zero, incremented when an account actor originates a top-level message.
// Always zero for other abi.
CallSeqNum
}
type ActorSystemStateCID cid.Cid
// ActorState represents the on-chain storage actors keep. This type is a
// union of concrete types, for each of the Actors:
// - InitActor
// - CronActor
// - AccountActor
// - PaymentChannelActor
// - StoragePowerActor
// - StorageMinerActor
// - StroageMarketActor
//
// TODO: move this into a directory inside the VM that patches in all
// the actors from across the system. this will be where we declare/mount
// all actors in the VM.
// type ActorState union {
// Init struct {
// AddressMap {addr.Address: ActorID}
// NextID ActorID
// }
// }
package actor
import (
util "github.com/filecoin-project/specs/util"
cid "github.com/ipfs/go-cid"
)
var IMPL_FINISH = util.IMPL_FINISH
var IMPL_TODO = util.IMPL_TODO
var TODO = util.TODO
type Serialization = util.Serialization
func (st *ActorState_I) CID() cid.Cid {
panic("TODO")
}
2.3.2 状态树
状态树是在Filecoin区块链上应用操作产生的输出。
import abi "github.com/filecoin-project/specs-actors/actors/abi"
import addr "github.com/filecoin-project/go-address"
import actor "github.com/filecoin-project/specs/systems/filecoin_vm/actor"
import cid "github.com/ipfs/go-cid"
// The on-chain state data structure is a map (HAMT) of addresses to actor states.
// Only ID addresses are expected as keys.
type StateTree struct {
ActorStates {addr.Address: actor.ActorState} // HAMT
// Returns the CID of the root node of the HAMT.
RootCID() cid.Cid
// Looks up an actor state by address.
GetActor(a addr.Address) (state actor.ActorState, ok bool)
// Looks up an abi.ActorCodeID by address.
GetActorCodeID_Assert(a addr.Address) abi.ActorCodeID
}
TODO
- 添加ConvenienceAPI状态,以提供更加用户友好的视图。
2.3.3 宏观经济指标
Indices是通过状态树(State Tree) 计算得到的一组全球经济指标,以及一些基于用户状态/动作来计算策略输出的纯函数。 Indices用于计算和实现系统的经济机制和政策。Indices中不存在持久性状态,也不能引入任何状态突变。 请注意,指标应该存放在哪里是一个设计上的决策。 一旦确定了所有经济机制,就有可能将Indices分解为多个文件或将其放置到不同的Actor中。 暂时来说,Indices是所有潜在宏观经济指标的临时保留文件,这些指标需要被系统留意。
package indices
import (
"math/big"
abi "github.com/filecoin-project/specs-actors/actors/abi"
actor_util "github.com/filecoin-project/specs-actors/actors/util"
)
var PARAM_FINISH = actor_util.PARAM_FINISH
// Data in Indices are populated at instantiation with data from the state tree
// Indices itself has no state tree or access to the runtime
// it is a passive data structure that allows for convenience access to network indices
// and pure functions in implementing economic policies given states
type Indices interface {
Epoch() abi.ChainEpoch
NetworkKPI() big.Int
TotalNetworkSectorWeight() abi.SectorWeight
TotalPledgeCollateral() abi.TokenAmount
TotalNetworkEffectivePower() abi.StoragePower // power above minimum miner size
TotalNetworkPower() abi.StoragePower // total network power irrespective of meeting minimum miner size
TotalMinedFIL() abi.TokenAmount
TotalUnminedFIL() abi.TokenAmount
TotalBurnedFIL() abi.TokenAmount
LastEpochReward() abi.TokenAmount
StorageDeal_DurationBounds(
pieceSize abi.PieceSize,
startEpoch abi.ChainEpoch,
) (minDuration abi.ChainEpoch, maxDuration abi.ChainEpoch)
StorageDeal_StoragePricePerEpochBounds(
pieceSize abi.PieceSize,
startEpoch abi.ChainEpoch,
endEpoch abi.ChainEpoch,
) (minPrice abi.TokenAmount, maxPrice abi.TokenAmount)
StorageDeal_ProviderCollateralBounds(
pieceSize abi.PieceSize,
startEpoch abi.ChainEpoch,
endEpoch abi.ChainEpoch,
) (minProviderCollateral abi.TokenAmount, maxProviderCollateral abi.TokenAmount)
StorageDeal_ClientCollateralBounds(
pieceSize abi.PieceSize,
startEpoch abi.ChainEpoch,
endEpoch abi.ChainEpoch,
) (minClientCollateral abi.TokenAmount, maxClientCollateral abi.TokenAmount)
SectorWeight(
sectorSize abi.SectorSize,
startEpoch abi.ChainEpoch,
endEpoch abi.ChainEpoch,
dealWeight abi.DealWeight,
) abi.SectorWeight
PledgeCollateralReq(minerNominalPower abi.StoragePower) abi.TokenAmount
SectorWeightProportion(minerActiveSectorWeight abi.SectorWeight) big.Int
PledgeCollateralProportion(minerPledgeCollateral abi.TokenAmount) big.Int
StoragePower(
minerActiveSectorWeight abi.SectorWeight,
minerInactiveSectorWeight abi.SectorWeight,
minerPledgeCollateral abi.TokenAmount,
) abi.StoragePower
StoragePowerProportion(
minerStoragePower abi.StoragePower,
) big.Int
CurrEpochBlockReward() abi.TokenAmount
GetCurrBlockRewardRewardForMiner(
minerStoragePower abi.StoragePower,
minerPledgeCollateral abi.TokenAmount,
// TODO extend or eliminate
) abi.TokenAmount
StoragePower_PledgeSlashForSectorTermination(
storageWeightDesc actor_util.SectorStorageWeightDesc,
terminationType actor_util.SectorTermination,
) abi.TokenAmount
StoragePower_PledgeSlashForSurprisePoStFailure(
minerClaimedPower abi.StoragePower,
numConsecutiveFailures int64,
) abi.TokenAmount
StorageMining_PreCommitDeposit(
sectorSize abi.SectorSize,
expirationEpoch abi.ChainEpoch,
) abi.TokenAmount
StorageMining_TemporaryFaultFee(
storageWeightDescs []actor_util.SectorStorageWeightDesc,
duration abi.ChainEpoch,
) abi.TokenAmount
NetworkTransactionFee(
toActorCodeID abi.ActorCodeID,
methodNum abi.MethodNum,
) abi.TokenAmount
GetCurrBlockRewardForMiner(
minerStoragePower abi.StoragePower,
minerPledgeCollateral abi.TokenAmount,
) abi.TokenAmount
}
type IndicesImpl struct {
// these fields are computed from StateTree upon construction
// they are treated as globally available states
Epoch abi.ChainEpoch
NetworkKPI big.Int
TotalNetworkSectorWeight abi.SectorWeight
TotalPledgeCollateral abi.TokenAmount
TotalNetworkEffectivePower abi.StoragePower // power above minimum miner size
TotalNetworkPower abi.StoragePower // total network power irrespective of meeting minimum miner size
TotalMinedFIL abi.TokenAmount
TotalUnminedFIL abi.TokenAmount
TotalBurnedFIL abi.TokenAmount
LastEpochReward abi.TokenAmount
}
func (inds *IndicesImpl) StorageDeal_DurationBounds(
pieceSize abi.PieceSize,
startEpoch abi.ChainEpoch,
) (minDuration abi.ChainEpoch, maxDuration abi.ChainEpoch) {
// placeholder
PARAM_FINISH()
minDuration = abi.ChainEpoch(0)
maxDuration = abi.ChainEpoch(1 << 20)
return
}
func (inds *IndicesImpl) StorageDeal_StoragePricePerEpochBounds(
pieceSize abi.PieceSize,
startEpoch abi.ChainEpoch,
endEpoch abi.ChainEpoch,
) (minPrice abi.TokenAmount, maxPrice abi.TokenAmount) {
// placeholder
PARAM_FINISH()
panic("")
}
func (inds *IndicesImpl) StorageDeal_ProviderCollateralBounds(
pieceSize abi.PieceSize,
startEpoch abi.ChainEpoch,
endEpoch abi.ChainEpoch,
) (minProviderCollateral abi.TokenAmount, maxProviderCollateral abi.TokenAmount) {
// placeholder
PARAM_FINISH()
panic("")
}
func (inds *IndicesImpl) StorageDeal_ClientCollateralBounds(
pieceSize abi.PieceSize,
startEpoch abi.ChainEpoch,
endEpoch abi.ChainEpoch,
) (minClientCollateral abi.TokenAmount, maxClientCollateral abi.TokenAmount) {
// placeholder
PARAM_FINISH()
panic("")
}
func (inds *IndicesImpl) SectorWeight(
sectorSize abi.SectorSize,
startEpoch abi.ChainEpoch,
endEpoch abi.ChainEpoch,
dealWeight abi.DealWeight,
) abi.SectorWeight {
// for every sector, given its size, start, end, and deals within the sector
// assign sector power for the duration of its lifetime
PARAM_FINISH()
panic("")
}
func (inds *IndicesImpl) PledgeCollateralReq(minerNominalPower abi.StoragePower) abi.TokenAmount {
PARAM_FINISH()
panic("")
}
func (inds *IndicesImpl) SectorWeightProportion(minerActiveSectorWeight abi.SectorWeight) big.Int {
// return proportion of SectorWeight for miner
PARAM_FINISH()
panic("")
}
func (inds *IndicesImpl) PledgeCollateralProportion(minerPledgeCollateral abi.TokenAmount) big.Int {
// return proportion of Pledge Collateral for miner
PARAM_FINISH()
panic("")
}
func (inds *IndicesImpl) StoragePower(
minerActiveSectorWeight abi.SectorWeight,
minerInactiveSectorWeight abi.SectorWeight,
minerPledgeCollateral abi.TokenAmount,
) abi.StoragePower {
// return StoragePower based on inputs
// StoragePower for miner = func(ActiveSectorWeight for miner, PledgeCollateral for miner, global indices)
PARAM_FINISH()
panic("")
}
func (inds *IndicesImpl) StoragePowerProportion(
minerStoragePower abi.StoragePower,
) big.Int {
PARAM_FINISH()
panic("")
}
func (inds *IndicesImpl) CurrEpochBlockReward() abi.TokenAmount {
// total block reward allocated for CurrEpoch
// each expected winner get an equal share of this reward
// computed as a function of NetworkKPI, LastEpochReward, TotalUnmminedFIL, etc
PARAM_FINISH()
panic("")
}
func (inds *IndicesImpl) GetCurrBlockRewardRewardForMiner(
minerStoragePower abi.StoragePower,
minerPledgeCollateral abi.TokenAmount,
// TODO extend or eliminate
) abi.TokenAmount {
PARAM_FINISH()
panic("")
}
// TerminationFault
func (inds *IndicesImpl) StoragePower_PledgeSlashForSectorTermination(
storageWeightDesc actor_util.SectorStorageWeightDesc,
terminationType actor_util.SectorTermination,
) abi.TokenAmount {
PARAM_FINISH()
panic("")
}
// DetectedFault
func (inds *IndicesImpl) StoragePower_PledgeSlashForSurprisePoStFailure(
minerClaimedPower abi.StoragePower,
numConsecutiveFailures int64,
) abi.TokenAmount {
PARAM_FINISH()
panic("")
}
func (inds *IndicesImpl) StorageMining_PreCommitDeposit(
sectorSize abi.SectorSize,
expirationEpoch abi.ChainEpoch,
) abi.TokenAmount {
PARAM_FINISH()
panic("")
}
func (inds *IndicesImpl) StorageMining_TemporaryFaultFee(
storageWeightDescs []actor_util.SectorStorageWeightDesc,
duration abi.ChainEpoch,
) abi.TokenAmount {
PARAM_FINISH()
panic("")
}
func (inds *IndicesImpl) NetworkTransactionFee(
toActorCodeID abi.ActorCodeID,
methodNum abi.MethodNum,
) abi.TokenAmount {
PARAM_FINISH()
panic("")
}
func (inds *IndicesImpl) GetCurrBlockRewardForMiner(
minerStoragePower abi.StoragePower,
minerPledgeCollateral abi.TokenAmount,
) abi.TokenAmount {
PARAM_FINISH()
panic("")
}
func ConsensusPowerForStorageWeight(
storageWeightDesc actor_util.SectorStorageWeightDesc,
) abi.StoragePower {
PARAM_FINISH()
panic("")
}
func StorageDeal_ProviderInitTimedOutSlashAmount(providerCollateral abi.TokenAmount) abi.TokenAmount {
// placeholder
PARAM_FINISH()
return providerCollateral
}
func StoragePower_ConsensusMinMinerPower() abi.StoragePower {
PARAM_FINISH()
panic("")
}
func StorageMining_PoStNoChallengePeriod() abi.ChainEpoch {
PARAM_FINISH()
panic("")
}
func StorageMining_SurprisePoStProvingPeriod() abi.ChainEpoch {
PARAM_FINISH()
panic("")
}
func StoragePower_SurprisePoStMaxConsecutiveFailures() int64 {
PARAM_FINISH()
panic("")
}
func StorageMining_DeclaredFaultEffectiveDelay() abi.ChainEpoch {
PARAM_FINISH()
panic("")
}
2.3.4 虚拟机消息 - Actor方法调用
消息是两个Actor之间进行通信的单位,因此是状态变化的根本原因。一条消息包含:
- 从发送方转移到接收方的代币数量
- 在接收方被调用的,带有参数的方法(可选)
Actor代码使得Actor可以在处理收到的消息的同时,向其他Actor发送额外的消息。 消息会被同步执行:Actor在恢复控制之前会等待发送的消息执行完毕。
消息的处理的过程消耗以gas计价的计算单位和存储。消息的gas limit(gas限额) 为其计算量提供了上限。消息的发送者按照自身决定的gas price(单价) 支付其执行消息(包括所有嵌套的消息)所消耗的gas。 区块出产者选择将哪些消息打包到区块中,并根据每条消息的gas price(单价) 和消耗量获得奖励,从而形成市场。
2.3.4.1 消息语法验证
语法错误的消息一定不能传输,不能保留在消息池中,也不能包含在区块中
一条语法正确的UnsignedMessage(未签名消息):
- 有一个格式正确的非空的
To(接收) 地址 - 有一个格式正确的非空的
From(发送) 地址 - 有一个非负的
CallSeqNum - 有不小于0且不大于代币总供应量(2e9 * 1e18) 的
Value(值) - 有一个非负的
MethodNum - 仅当
MethodNum为0时才具有非空的Params - 有非负的
GasPrice - 其
GasLimit大于等于gas的消耗量,而gas的消耗量与消息序列化后的字节大小有关 GasLimit的值不大于网络中的区块的gas limit参数
当消息被单独传输时(包含在区块中之前),无论使用哪种签名方案,都会被打包为SignedMessage(签名消息) 。一条有效的签名消息:
- 其序列化后的总大小不大于
message.MessageMaxSize
2.3.4.2 消息语义验证
语义验证是指需要消息本身之外的信息的验证。
一条语义正确的SignedMessage必须带有一个签名,该签名可以验证From地址所标识的账户actor的公钥是否已对payload进行签名。
注意:
- 当
From地址是一个ID-address时,必须在区块所标识的父状态中,发送帐户actor的状态下查找公钥。 - 发送actor必须存在于包含本消息的区块所标识的父state中。 这意味着,对于一个区块而言,既包含某条创建“新帐户actor”的消息,又包含同一actor发出的消息的行为是不合法的。该actor发出的第一条消息必须等待紧随其后的下一个epoch(时期)。 消息池可能会排除尚未出现在链状态下的actor的消息。
消息没有进一步的语义验证,可能导致包含该消息的区块无效。 每个语义有效且签名正确的消息都可以包含在一个区块中,并会从执行中产生一个receipt(回执)。 但是,消息可能无法执行到完成,这种情况下就不会对期望的状态改变带来影响。
使用这种“无消息语义验证”策略的原因是,在消息作为tipset的一部分执行之前,无法知道消息将被应用的状态。区块出产者不知道在tipset中是否有另一个区块会抢先执行此消息,从而修改该区块消息被应用到的,来自公开声明的父state的状态。
import filcrypto "github.com/filecoin-project/specs/algorithms/crypto"
import addr "github.com/filecoin-project/go-address"
import actor "github.com/filecoin-project/specs/systems/filecoin_vm/actor"
import abi "github.com/filecoin-project/specs-actors/actors/abi"
// GasAmount is a quantity of gas.
type GasAmount struct {
value BigInt
Add(GasAmount) GasAmount
Subtract(GasAmount) GasAmount
SubtractIfNonnegative(GasAmount) (ret GasAmount, ok bool)
LessThan(GasAmount) bool
Equals(GasAmount) bool
Scale(int) GasAmount
}
type UnsignedMessage struct {
// Address of the receiving actor.
To addr.Address
// Address of the sending actor.
From addr.Address
// Expected CallSeqNum of the sending actor (only for top-level messages).
CallSeqNum actor.CallSeqNum
// Amount of value to transfer from sender's to receiver's balance.
Value abi.TokenAmount
// GasPrice is a Gas-to-FIL cost
GasPrice abi.TokenAmount
GasLimit GasAmount
// Optional method to invoke on receiver, zero for a plain value send.
Method abi.MethodNum
/// Serialized parameters to the method (if method is non-zero).
Params abi.MethodParams
} // representation tuple
type SignedMessage struct {
Message UnsignedMessage
Signature filcrypto.Signature
} // representation tuple
package message
import (
filcrypto "github.com/filecoin-project/specs/algorithms/crypto"
util "github.com/filecoin-project/specs/util"
)
var IMPL_FINISH = util.IMPL_FINISH
type Serialization = util.Serialization
// The maximum serialized size of a SignedMessage.
const MessageMaxSize = 32 * 1024
func SignedMessage_Make(message UnsignedMessage, signature filcrypto.Signature) SignedMessage {
return &SignedMessage_I{
Message_: message,
Signature_: signature,
}
}
func Sign(message UnsignedMessage, keyPair filcrypto.SigKeyPair) (SignedMessage, error) {
sig, err := filcrypto.Sign(keyPair, util.Bytes(Serialize_UnsignedMessage(message)))
if err != nil {
return nil, err
}
return SignedMessage_Make(message, sig), nil
}
func SignatureVerificationError() error {
IMPL_FINISH()
panic("")
}
func Verify(message SignedMessage, publicKey filcrypto.PublicKey) (UnsignedMessage, error) {
m := util.Bytes(Serialize_UnsignedMessage(message.Message()))
sigValid, err := filcrypto.Verify(publicKey, message.Signature(), m)
if err != nil {
return nil, err
}
if !sigValid {
return nil, SignatureVerificationError()
}
return message.Message(), nil
}
func (x *GasAmount_I) Add(y GasAmount) GasAmount {
IMPL_FINISH()
panic("")
}
func (x *GasAmount_I) Subtract(y GasAmount) GasAmount {
IMPL_FINISH()
panic("")
}
func (x *GasAmount_I) SubtractIfNonnegative(y GasAmount) (ret GasAmount, ok bool) {
ret = x.Subtract(y)
ok = true
if ret.LessThan(GasAmount_Zero()) {
ret = x
ok = false
}
return
}
func (x *GasAmount_I) LessThan(y GasAmount) bool {
IMPL_FINISH()
panic("")
}
func (x *GasAmount_I) Equals(y GasAmount) bool {
IMPL_FINISH()
panic("")
}
func (x *GasAmount_I) Scale(count int) GasAmount {
IMPL_FINISH()
panic("")
}
func GasAmount_Affine(b GasAmount, x int, m GasAmount) GasAmount {
return b.Add(m.Scale(x))
}
func GasAmount_Zero() GasAmount {
return GasAmount_FromInt(0)
}
func GasAmount_FromInt(x int) GasAmount {
IMPL_FINISH()
panic("")
}
func GasAmount_SentinelUnlimited() GasAmount {
// Amount of gas larger than any feasible execution; meant to indicated unlimited gas
// (e.g., for builtin system method invocations).
return GasAmount_FromInt(1).Scale(1e9).Scale(1e9) // 10^18
}
2.3.5 VM运行环境(在VM内部)
2.3.5.1 回执
MessageReceipt包含顶级消息执行的结果。 语法正确的回执具有:
- 非负的
ExitCode - 仅当退出代码为零时,才返回非空的
ReturnValue - 非负的
GasUsed
2.3.5.2 vm/runtime 接口
package runtime
import actor "github.com/filecoin-project/specs-actors/actors"
import abi "github.com/filecoin-project/specs-actors/actors/abi"
import crypto "github.com/filecoin-project/specs-actors/actors/crypto"
import exitcode "github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
import addr "github.com/filecoin-project/go-address"
import indices "github.com/filecoin-project/specs-actors/actors/runtime/indices"
import cid "github.com/ipfs/go-cid"
// Runtime is the VM's internal runtime object.
// this is everything that is accessible to actors, beyond parameters.
type Runtime interface {
CurrEpoch() abi.ChainEpoch
// Randomness returns a (pseudo)random string for the given epoch and tag.
GetRandomness(epoch abi.ChainEpoch) abi.RandomnessSeed
// The address of the immediate calling actor.
// Not necessarily the actor in the From field of the initial on-chain Message.
// Always an ID-address.
ImmediateCaller() addr.Address
ValidateImmediateCallerIs(caller addr.Address)
ValidateImmediateCallerInSet(callers []addr.Address)
ValidateImmediateCallerAcceptAnyOfType(type_ abi.ActorCodeID)
ValidateImmediateCallerAcceptAnyOfTypes(types []abi.ActorCodeID)
ValidateImmediateCallerAcceptAny()
ValidateImmediateCallerMatches(CallerPattern)
// The address of the actor receiving the message. Always an ID-address.
CurrReceiver() addr.Address
// The actor who mined the block in which the initial on-chain message appears.
// Always an ID-address.
ToplevelBlockWinner() addr.Address
AcquireState() ActorStateHandle
SuccessReturn() InvocOutput
ValueReturn([]byte) InvocOutput
// Throw an error indicating a failure condition has occurred, from which the given actor
// code is unable to recover.
Abort(errExitCode exitcode.ExitCode, msg string)
// Calls Abort with InvalidArguments_User.
AbortArgMsg(msg string)
AbortArg()
// Calls Abort with InconsistentState_User.
AbortStateMsg(msg string)
AbortState()
// Calls Abort with InsufficientFunds_User.
AbortFundsMsg(msg string)
AbortFunds()
// Calls Abort with RuntimeAPIError.
// For internal use only (not in actor code).
AbortAPI(msg string)
// Check that the given condition is true (and call Abort if not).
Assert(bool)
CurrentBalance() abi.TokenAmount
ValueReceived() abi.TokenAmount
// Look up the current values of several system-wide economic indices.
CurrIndices() indices.Indices
// Look up the code ID of a given actor address.
GetActorCodeID(addr addr.Address) (ret abi.ActorCodeID, ok bool)
// Run a (pure function) computation, consuming the gas cost associated with that function.
// This mechanism is intended to capture the notion of an ABI between the VM and native
// functions, and should be used for any function whose computation is expensive.
Compute(ComputeFunctionID, args []interface{}) interface{}
// Sends a message to another actor.
// If the invoked method does not return successfully, this caller will be aborted too.
SendPropagatingErrors(input InvocInput) InvocOutput
Send(
toAddr addr.Address,
methodNum abi.MethodNum,
params abi.MethodParams,
value abi.TokenAmount,
) InvocOutput
SendQuery(
toAddr addr.Address,
methodNum abi.MethodNum,
params abi.MethodParams,
) []byte
SendFunds(toAddr addr.Address, value abi.TokenAmount)
// Sends a message to another actor, trapping an unsuccessful execution.
// This may only be invoked by the singleton Cron actor.
SendCatchingErrors(input InvocInput) (output InvocOutput, exitCode exitcode.ExitCode)
// Computes an address for a new actor. The returned address is intended to uniquely refer to
// the actor even in the event of a chain re-org (whereas an ID-address might refer to a
// different actor after messages are re-ordered).
// Always an ActorExec address.
NewActorAddress() addr.Address
// Creates an actor in the state tree, with empty state. May only be called by InitActor.
CreateActor(
// The new actor's code identifier.
codeId abi.ActorCodeID,
// Address under which the new actor's state will be stored. Must be an ID-address.
address addr.Address,
)
// Deletes an actor in the state tree. May only be called by the actor itself,
// or by StoragePowerActor in the case of StorageMinerActors.
DeleteActor(address addr.Address)
// Retrieves and deserializes an object from the store into o. Returns whether successful.
IpldGet(c cid.Cid, o interface{}) bool
// Serializes and stores an object, returning its CID.
IpldPut(x interface{}) cid.Cid
// Provides the system call interface.
Syscalls() Syscalls
}
type Syscalls interface {
// Verifies that a signature is valid for an address and plaintext.
VerifySignature(
signature crypto.Signature,
signer addr.Address,
plaintext []byte,
) bool
// Computes an unsealed sector CID (CommD) from its constituent piece CIDs (CommPs) and sizes.
ComputeUnsealedSectorCID(sectorSize abi.SectorSize, pieces []abi.PieceInfo) (abi.UnsealedSectorCID, error)
// Verifies a sector seal proof.
VerifySeal(sectorSize abi.SectorSize, vi abi.SealVerifyInfo) bool
// Verifies a proof of spacetime.
VerifyPoSt(sectorSize abi.SectorSize, vi abi.PoStVerifyInfo) bool
}
type InvocInput struct {
To addr.Address
Method abi.MethodNum
Params abi.MethodParams
Value abi.TokenAmount
}
type InvocOutput struct {
ReturnValue []byte
}
type ActorStateHandle interface {
UpdateRelease(newStateCID actor.ActorSubstateCID)
Release(checkStateCID actor.ActorSubstateCID)
Take() actor.ActorSubstateCID
}
type ComputeFunctionID int64
const (
Compute_VerifySignature = ComputeFunctionID(1)
)
2.3.5.3 vm/runtime 实现
package impl
import (
"bytes"
"encoding/binary"
"fmt"
addr "github.com/filecoin-project/go-address"
actor "github.com/filecoin-project/specs-actors/actors"
abi "github.com/filecoin-project/specs-actors/actors/abi"
builtin "github.com/filecoin-project/specs-actors/actors/builtin"
acctact "github.com/filecoin-project/specs-actors/actors/builtin/account"
initact "github.com/filecoin-project/specs-actors/actors/builtin/init"
vmr "github.com/filecoin-project/specs-actors/actors/runtime"
exitcode "github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
indices "github.com/filecoin-project/specs-actors/actors/runtime/indices"
ipld "github.com/filecoin-project/specs/libraries/ipld"
chain "github.com/filecoin-project/specs/systems/filecoin_blockchain/struct/chain"
actstate "github.com/filecoin-project/specs/systems/filecoin_vm/actor"
msg "github.com/filecoin-project/specs/systems/filecoin_vm/message"
gascost "github.com/filecoin-project/specs/systems/filecoin_vm/runtime/gascost"
st "github.com/filecoin-project/specs/systems/filecoin_vm/state_tree"
util "github.com/filecoin-project/specs/util"
cid "github.com/ipfs/go-cid"
cbornode "github.com/ipfs/go-ipld-cbor"
mh "github.com/multiformats/go-multihash"
)
type ActorSubstateCID = actor.ActorSubstateCID
type ExitCode = exitcode.ExitCode
type CallerPattern = vmr.CallerPattern
type Runtime = vmr.Runtime
type InvocInput = vmr.InvocInput
type InvocOutput = vmr.InvocOutput
type ActorStateHandle = vmr.ActorStateHandle
var EnsureErrorCode = exitcode.EnsureErrorCode
type Bytes = util.Bytes
var Assert = util.Assert
var IMPL_FINISH = util.IMPL_FINISH
var IMPL_TODO = util.IMPL_TODO
var TODO = util.TODO
var EmptyCBOR cid.Cid
type RuntimeError struct {
ExitCode ExitCode
ErrMsg string
}
func init() {
n, err := cbornode.WrapObject(map[string]struct{}{}, mh.SHA2_256, -1)
Assert(err == nil)
EmptyCBOR = n.Cid()
}
func (x *RuntimeError) String() string {
ret := fmt.Sprintf("Runtime error: %v", x.ExitCode)
if x.ErrMsg != "" {
ret += fmt.Sprintf(" (\"%v\")", x.ErrMsg)
}
return ret
}
func RuntimeError_Make(exitCode ExitCode, errMsg string) *RuntimeError {
exitCode = EnsureErrorCode(exitCode)
return &RuntimeError{
ExitCode: exitCode,
ErrMsg: errMsg,
}
}
func ActorSubstateCID_Equals(x, y ActorSubstateCID) bool {
IMPL_FINISH()
panic("")
}
type ActorStateHandle_I struct {
_initValue *ActorSubstateCID
_rt *VMContext
}
func (h *ActorStateHandle_I) UpdateRelease(newStateCID ActorSubstateCID) {
h._rt._updateReleaseActorSubstate(newStateCID)
}
func (h *ActorStateHandle_I) Release(checkStateCID ActorSubstateCID) {
h._rt._releaseActorSubstate(checkStateCID)
}
func (h *ActorStateHandle_I) Take() ActorSubstateCID {
if h._initValue == nil {
h._rt._apiError("Must call Take() only once on actor substate object")
}
ret := *h._initValue
h._initValue = nil
return ret
}
// Concrete instantiation of the Runtime interface. This should be instantiated by the
// interpreter once per actor method invocation, and responds to that method's Runtime
// API calls.
type VMContext struct {
_store ipld.GraphStore
_globalStateInit st.StateTree
_globalStatePending st.StateTree
_running bool
_chain chain.Chain
_actorAddress addr.Address
_actorStateAcquired bool
// Tracks whether actor substate has changed in order to charge gas just once
// regardless of how many times it's written.
_actorSubstateUpdated bool
_immediateCaller addr.Address
// Note: This is the actor in the From field of the initial on-chain message.
// Not necessarily the immediate caller.
_toplevelSender addr.Address
_toplevelBlockWinner addr.Address
// call sequence number of the top level message that began this execution sequence
_toplevelMsgCallSeqNum actstate.CallSeqNum
// Sequence number representing the total number of calls (to any actor, any method)
// during the current top-level message execution.
// Note: resets with every top-level message, and therefore not necessarily monotonic.
_internalCallSeqNum actstate.CallSeqNum
_valueReceived abi.TokenAmount
_gasRemaining msg.GasAmount
_numValidateCalls int
_output vmr.InvocOutput
}
func VMContext_Make(
store ipld.GraphStore,
chain chain.Chain,
toplevelSender addr.Address,
toplevelBlockWinner addr.Address,
toplevelMsgCallSeqNum actstate.CallSeqNum,
internalCallSeqNum actstate.CallSeqNum,
globalState st.StateTree,
actorAddress addr.Address,
valueReceived abi.TokenAmount,
gasRemaining msg.GasAmount) *VMContext {
return &VMContext{
_store: store,
_chain: chain,
_globalStateInit: globalState,
_globalStatePending: globalState,
_running: false,
_actorAddress: actorAddress,
_actorStateAcquired: false,
_actorSubstateUpdated: false,
_toplevelSender: toplevelSender,
_toplevelBlockWinner: toplevelBlockWinner,
_toplevelMsgCallSeqNum: toplevelMsgCallSeqNum,
_internalCallSeqNum: internalCallSeqNum,
_valueReceived: valueReceived,
_gasRemaining: gasRemaining,
_numValidateCalls: 0,
_output: vmr.InvocOutput{},
}
}
func (rt *VMContext) AbortArgMsg(msg string) {
rt.Abort(exitcode.InvalidArguments_User, msg)
}
func (rt *VMContext) AbortArg() {
rt.AbortArgMsg("Invalid arguments")
}
func (rt *VMContext) AbortStateMsg(msg string) {
rt.Abort(exitcode.InconsistentState_User, msg)
}
func (rt *VMContext) AbortState() {
rt.AbortStateMsg("Inconsistent state")
}
func (rt *VMContext) AbortFundsMsg(msg string) {
rt.Abort(exitcode.InsufficientFunds_User, msg)
}
func (rt *VMContext) AbortFunds() {
rt.AbortFundsMsg("Insufficient funds")
}
func (rt *VMContext) AbortAPI(msg string) {
rt.Abort(exitcode.RuntimeAPIError, msg)
}
func (rt *VMContext) CreateActor(codeID abi.ActorCodeID, address addr.Address) {
if rt._actorAddress != builtin.InitActorAddr {
rt.AbortAPI("Only InitActor may call rt.CreateActor")
}
if address.Protocol() != addr.ID {
rt.AbortAPI("New actor adddress must be an ID-address")
}
rt._createActor(codeID, address)
}
func (rt *VMContext) _createActor(codeID abi.ActorCodeID, address addr.Address) {
// Create empty actor state.
actorState := &actstate.ActorState_I{
CodeID_: codeID,
State_: actor.ActorSubstateCID(EmptyCBOR),
Balance_: abi.TokenAmount(0),
CallSeqNum_: 0,
}
// Put it in the state tree.
actorStateCID := actstate.ActorSystemStateCID(rt.IpldPut(actorState))
rt._updateActorSystemStateInternal(address, actorStateCID)
rt._rtAllocGas(gascost.ExecNewActor)
}
func (rt *VMContext) DeleteActor(address addr.Address) {
// Only a given actor may delete itself.
if rt._actorAddress != address {
rt.AbortAPI("Invalid actor deletion request")
}
rt._deleteActor(address)
}
func (rt *VMContext) _deleteActor(address addr.Address) {
rt._globalStatePending = rt._globalStatePending.Impl().WithDeleteActorSystemState(address)
rt._rtAllocGas(gascost.DeleteActor)
}
func (rt *VMContext) _updateActorSystemStateInternal(actorAddress addr.Address, newStateCID actstate.ActorSystemStateCID) {
newGlobalStatePending, err := rt._globalStatePending.Impl().WithActorSystemState(rt._actorAddress, newStateCID)
if err != nil {
panic("Error in runtime implementation: failed to update actor system state")
}
rt._globalStatePending = newGlobalStatePending
}
func (rt *VMContext) _updateActorSubstateInternal(actorAddress addr.Address, newStateCID actor.ActorSubstateCID) {
newGlobalStatePending, err := rt._globalStatePending.Impl().WithActorSubstate(rt._actorAddress, newStateCID)
if err != nil {
panic("Error in runtime implementation: failed to update actor substate")
}
rt._globalStatePending = newGlobalStatePending
}
func (rt *VMContext) _updateReleaseActorSubstate(newStateCID ActorSubstateCID) {
rt._checkRunning()
rt._checkActorStateAcquired()
rt._updateActorSubstateInternal(rt._actorAddress, newStateCID)
rt._actorSubstateUpdated = true
rt._actorStateAcquired = false
}
func (rt *VMContext) _releaseActorSubstate(checkStateCID ActorSubstateCID) {
rt._checkRunning()
rt._checkActorStateAcquired()
prevState, ok := rt._globalStatePending.GetActor(rt._actorAddress)
util.Assert(ok)
prevStateCID := prevState.State()
if !ActorSubstateCID_Equals(prevStateCID, checkStateCID) {
rt.AbortAPI("State CID differs upon release call")
}
rt._actorStateAcquired = false
}
func (rt *VMContext) Assert(cond bool) {
if !cond {
rt.Abort(exitcode.RuntimeAssertFailure, "Runtime assertion failed")
}
}
func (rt *VMContext) _checkActorStateAcquiredFlag(expected bool) {
rt._checkRunning()
if rt._actorStateAcquired != expected {
rt._apiError("State updates and message sends must be disjoint")
}
}
func (rt *VMContext) _checkActorStateAcquired() {
rt._checkActorStateAcquiredFlag(true)
}
func (rt *VMContext) _checkActorStateNotAcquired() {
rt._checkActorStateAcquiredFlag(false)
}
func (rt *VMContext) Abort(errExitCode exitcode.ExitCode, errMsg string) {
errExitCode = exitcode.EnsureErrorCode(errExitCode)
rt._throwErrorFull(errExitCode, errMsg)
}
func (rt *VMContext) ImmediateCaller() addr.Address {
return rt._immediateCaller
}
func (rt *VMContext) CurrReceiver() addr.Address {
return rt._actorAddress
}
func (rt *VMContext) ToplevelBlockWinner() addr.Address {
return rt._toplevelBlockWinner
}
func (rt *VMContext) ValidateImmediateCallerMatches(
callerExpectedPattern CallerPattern) {
rt._checkRunning()
rt._checkNumValidateCalls(0)
caller := rt.ImmediateCaller()
if !callerExpectedPattern.Matches(caller) {
rt.AbortAPI("Method invoked by incorrect caller")
}
rt._numValidateCalls += 1
}
func CallerPattern_MakeAcceptAnyOfTypes(rt *VMContext, types []abi.ActorCodeID) CallerPattern {
return CallerPattern{
Matches: func(y addr.Address) bool {
codeID, ok := rt.GetActorCodeID(y)
if !ok {
panic("Internal runtime error: actor not found")
}
for _, type_ := range types {
if codeID == type_ {
return true
}
}
return false
},
}
}
func (rt *VMContext) ValidateImmediateCallerIs(callerExpected addr.Address) {
rt.ValidateImmediateCallerMatches(vmr.CallerPattern_MakeSingleton(callerExpected))
}
func (rt *VMContext) ValidateImmediateCallerInSet(callersExpected []addr.Address) {
rt.ValidateImmediateCallerMatches(vmr.CallerPattern_MakeSet(callersExpected))
}
func (rt *VMContext) ValidateImmediateCallerAcceptAnyOfType(type_ abi.ActorCodeID) {
rt.ValidateImmediateCallerAcceptAnyOfTypes([]abi.ActorCodeID{type_})
}
func (rt *VMContext) ValidateImmediateCallerAcceptAnyOfTypes(types []abi.ActorCodeID) {
rt.ValidateImmediateCallerMatches(CallerPattern_MakeAcceptAnyOfTypes(rt, types))
}
func (rt *VMContext) ValidateImmediateCallerAcceptAny() {
rt.ValidateImmediateCallerMatches(vmr.CallerPattern_MakeAcceptAny())
}
func (rt *VMContext) _checkNumValidateCalls(x int) {
if rt._numValidateCalls != x {
rt.AbortAPI("Method must validate caller identity exactly once")
}
}
func (rt *VMContext) _checkRunning() {
if !rt._running {
panic("Internal runtime error: actor API called with no actor code running")
}
}
func (rt *VMContext) SuccessReturn() InvocOutput {
return vmr.InvocOutput_Make(nil)
}
func (rt *VMContext) ValueReturn(value util.Bytes) InvocOutput {
return vmr.InvocOutput_Make(value)
}
func (rt *VMContext) _throwError(exitCode ExitCode) {
rt._throwErrorFull(exitCode, "")
}
func (rt *VMContext) _throwErrorFull(exitCode ExitCode, errMsg string) {
panic(RuntimeError_Make(exitCode, errMsg))
}
func (rt *VMContext) _apiError(errMsg string) {
rt._throwErrorFull(exitcode.RuntimeAPIError, errMsg)
}
func _gasAmountAssertValid(x msg.GasAmount) {
if x.LessThan(msg.GasAmount_Zero()) {
panic("Interpreter error: negative gas amount")
}
}
// Deduct an amount of gas corresponding to cost about to be incurred, but not necessarily
// incurred yet.
func (rt *VMContext) _rtAllocGas(x msg.GasAmount) {
_gasAmountAssertValid(x)
var ok bool
rt._gasRemaining, ok = rt._gasRemaining.SubtractIfNonnegative(x)
if !ok {
rt._throwError(exitcode.OutOfGas)
}
}
func (rt *VMContext) _transferFunds(from addr.Address, to addr.Address, amount abi.TokenAmount) error {
rt._checkRunning()
rt._checkActorStateNotAcquired()
newGlobalStatePending, err := rt._globalStatePending.Impl().WithFundsTransfer(from, to, amount)
if err != nil {
return err
}
rt._globalStatePending = newGlobalStatePending
return nil
}
func (rt *VMContext) GetActorCodeID(actorAddr addr.Address) (ret abi.ActorCodeID, ok bool) {
IMPL_FINISH()
panic("")
}
type ErrorHandlingSpec int
const (
PropagateErrors ErrorHandlingSpec = 1 + iota
CatchErrors
)
// TODO: This function should be private (not intended to be exposed to actors).
// (merging runtime and interpreter packages should solve this)
// TODO: this should not use the MessageReceipt return type, even though it needs the same triple
// of values. This method cannot compute the total gas cost and the returned receipt will never
// go on chain.
func (rt *VMContext) SendToplevelFromInterpreter(input InvocInput) (MessageReceipt, st.StateTree) {
rt._running = true
ret := rt._sendInternal(input, CatchErrors)
rt._running = false
return ret, rt._globalStatePending
}
func _catchRuntimeErrors(f func() InvocOutput) (output InvocOutput, exitCode exitcode.ExitCode) {
defer func() {
if r := recover(); r != nil {
switch r.(type) {
case *RuntimeError:
output = vmr.InvocOutput_Make(nil)
exitCode = (r.(*RuntimeError).ExitCode)
default:
panic(r)
}
}
}()
output = f()
exitCode = exitcode.OK()
return
}
func _invokeMethodInternal(
rt *VMContext,
actorCode vmr.ActorCode,
method abi.MethodNum,
params abi.MethodParams) (
ret InvocOutput, exitCode exitcode.ExitCode, internalCallSeqNumFinal actstate.CallSeqNum) {
if method == builtin.MethodSend {
ret = vmr.InvocOutput_Make(nil)
return
}
rt._running = true
ret, exitCode = _catchRuntimeErrors(func() InvocOutput {
IMPL_TODO("dispatch to actor code")
var methodOutput vmr.InvocOutput // actorCode.InvokeMethod(rt, method, params)
if rt._actorSubstateUpdated {
rt._rtAllocGas(gascost.UpdateActorSubstate)
}
rt._checkActorStateNotAcquired()
rt._checkNumValidateCalls(1)
return methodOutput
})
rt._running = false
internalCallSeqNumFinal = rt._internalCallSeqNum
return
}
func (rtOuter *VMContext) _sendInternal(input InvocInput, errSpec ErrorHandlingSpec) MessageReceipt {
rtOuter._checkRunning()
rtOuter._checkActorStateNotAcquired()
initGasRemaining := rtOuter._gasRemaining
rtOuter._rtAllocGas(gascost.InvokeMethod(input.Value, input.Method))
receiver, receiverAddr := rtOuter._resolveReceiver(input.To)
receiverCode, err := loadActorCode(receiver.CodeID())
if err != nil {
rtOuter._throwError(exitcode.ActorCodeNotFound)
}
err = rtOuter._transferFunds(rtOuter._actorAddress, receiverAddr, input.Value)
if err != nil {
rtOuter._throwError(exitcode.InsufficientFunds_System)
}
rtInner := VMContext_Make(
rtOuter._store,
rtOuter._chain,
rtOuter._toplevelSender,
rtOuter._toplevelBlockWinner,
rtOuter._toplevelMsgCallSeqNum,
rtOuter._internalCallSeqNum+1,
rtOuter._globalStatePending,
receiverAddr,
input.Value,
rtOuter._gasRemaining,
)
invocOutput, exitCode, internalCallSeqNumFinal := _invokeMethodInternal(
rtInner,
receiverCode,
input.Method,
input.Params,
)
_gasAmountAssertValid(rtOuter._gasRemaining.Subtract(rtInner._gasRemaining))
rtOuter._gasRemaining = rtInner._gasRemaining
gasUsed := initGasRemaining.Subtract(rtOuter._gasRemaining)
_gasAmountAssertValid(gasUsed)
rtOuter._internalCallSeqNum = internalCallSeqNumFinal
if exitCode == exitcode.OutOfGas {
// OutOfGas error cannot be caught
rtOuter._throwError(exitCode)
}
if errSpec == PropagateErrors && exitCode.IsError() {
rtOuter._throwError(exitcode.MethodSubcallError)
}
if exitCode.AllowsStateUpdate() {
rtOuter._globalStatePending = rtInner._globalStatePending
}
return MessageReceipt_Make(invocOutput, exitCode, gasUsed)
}
// Loads a receiving actor state from the state tree, resolving non-ID addresses through the InitActor state.
// If it doesn't exist, and the message is a simple value send to a pubkey-style address,
// creates the receiver as an account actor in the returned state.
// Aborts otherwise.
func (rt *VMContext) _resolveReceiver(targetRaw addr.Address) (actstate.ActorState, addr.Address) {
// Resolve the target address via the InitActor, and attempt to load state.
initSubState := rt._loadInitActorState()
targetIdAddr := initSubState.ResolveAddress(targetRaw)
act, found := rt._globalStatePending.GetActor(targetIdAddr)
if found {
return act, targetIdAddr
}
if targetRaw.Protocol() != addr.SECP256K1 && targetRaw.Protocol() != addr.BLS {
// Don't implicitly create an account actor for an address without an associated key.
rt._throwError(exitcode.ActorNotFound)
}
// Allocate an ID address from the init actor and map the pubkey To address to it.
newIdAddr := initSubState.MapAddressToNewID(targetRaw)
rt._saveInitActorState(initSubState)
// Create new account actor (charges gas).
rt._createActor(builtin.AccountActorCodeID, newIdAddr)
// Initialize account actor substate with it's pubkey address.
substate := &acctact.AccountActorState{
Address: targetRaw,
}
rt._saveAccountActorState(newIdAddr, *substate)
act, _ = rt._globalStatePending.GetActor(newIdAddr)
return act, newIdAddr
}
func (rt *VMContext) _loadInitActorState() initact.InitActorState {
initState, ok := rt._globalStatePending.GetActor(builtin.InitActorAddr)
util.Assert(ok)
var initSubState initact.InitActorState
ok = rt.IpldGet(cid.Cid(initState.State()), &initSubState)
util.Assert(ok)
return initSubState
}
func (rt *VMContext) _saveInitActorState(state initact.InitActorState) {
// Gas is charged here separately from _actorSubstateUpdated because this is a different actor
// than the receiver.
rt._rtAllocGas(gascost.UpdateActorSubstate)
rt._updateActorSubstateInternal(builtin.InitActorAddr, actor.ActorSubstateCID(rt.IpldPut(&state)))
}
func (rt *VMContext) _saveAccountActorState(address addr.Address, state acctact.AccountActorState) {
// Gas is charged here separately from _actorSubstateUpdated because this is a different actor
// than the receiver.
rt._rtAllocGas(gascost.UpdateActorSubstate)
rt._updateActorSubstateInternal(address, actor.ActorSubstateCID(rt.IpldPut(state)))
}
func (rt *VMContext) _sendInternalOutputs(input InvocInput, errSpec ErrorHandlingSpec) (InvocOutput, exitcode.ExitCode) {
ret := rt._sendInternal(input, errSpec)
return vmr.InvocOutput_Make(ret.ReturnValue), ret.ExitCode
}
func (rt *VMContext) Send(
toAddr addr.Address, methodNum abi.MethodNum, params abi.MethodParams, value abi.TokenAmount) InvocOutput {
return rt.SendPropagatingErrors(vmr.InvocInput_Make(toAddr, methodNum, params, value))
}
func (rt *VMContext) SendQuery(toAddr addr.Address, methodNum abi.MethodNum, params abi.MethodParams) util.Serialization {
invocOutput := rt.Send(toAddr, methodNum, params, abi.TokenAmount(0))
ret := invocOutput.ReturnValue
Assert(ret != nil)
return ret
}
func (rt *VMContext) SendFunds(toAddr addr.Address, value abi.TokenAmount) {
rt.Send(toAddr, builtin.MethodSend, nil, value)
}
func (rt *VMContext) SendPropagatingErrors(input InvocInput) InvocOutput {
ret, _ := rt._sendInternalOutputs(input, PropagateErrors)
return ret
}
func (rt *VMContext) SendCatchingErrors(input InvocInput) (InvocOutput, exitcode.ExitCode) {
rt.ValidateImmediateCallerIs(builtin.CronActorAddr)
return rt._sendInternalOutputs(input, CatchErrors)
}
func (rt *VMContext) CurrentBalance() abi.TokenAmount {
IMPL_FINISH()
panic("")
}
func (rt *VMContext) ValueReceived() abi.TokenAmount {
return rt._valueReceived
}
func (rt *VMContext) GetRandomness(epoch abi.ChainEpoch) abi.RandomnessSeed {
return rt._chain.RandomnessAtEpoch(epoch)
}
func (rt *VMContext) NewActorAddress() addr.Address {
addrBuf := new(bytes.Buffer)
senderState, ok := rt._globalStatePending.GetActor(rt._toplevelSender)
util.Assert(ok)
var aast acctact.AccountActorState
ok = rt.IpldGet(cid.Cid(senderState.State()), &aast)
util.Assert(ok)
err := aast.Address.MarshalCBOR(addrBuf)
util.Assert(err == nil)
err = binary.Write(addrBuf, binary.BigEndian, rt._toplevelMsgCallSeqNum)
util.Assert(err != nil)
err = binary.Write(addrBuf, binary.BigEndian, rt._internalCallSeqNum)
util.Assert(err != nil)
newAddr, err := addr.NewActorAddress(addrBuf.Bytes())
util.Assert(err == nil)
return newAddr
}
func (rt *VMContext) IpldPut(x ipld.Object) cid.Cid {
IMPL_FINISH() // Serialization
serialized := []byte{}
cid := rt._store.Put(serialized)
rt._rtAllocGas(gascost.IpldPut(len(serialized)))
return cid
}
func (rt *VMContext) IpldGet(c cid.Cid, o ipld.Object) bool {
serialized, ok := rt._store.Get(c)
if ok {
rt._rtAllocGas(gascost.IpldGet(len(serialized)))
}
IMPL_FINISH() // Deserialization into o
return ok
}
func (rt *VMContext) CurrEpoch() abi.ChainEpoch {
IMPL_FINISH()
panic("")
}
func (rt *VMContext) CurrIndices() indices.Indices {
// TODO: compute from state tree (rt._globalStatePending), using individual actor
// state helper functions when possible
TODO()
panic("")
}
func (rt *VMContext) AcquireState() ActorStateHandle {
rt._checkRunning()
rt._checkActorStateNotAcquired()
rt._actorStateAcquired = true
state, ok := rt._globalStatePending.GetActor(rt._actorAddress)
util.Assert(ok)
stateRef := state.State().Ref()
return &ActorStateHandle_I{
_initValue: &stateRef,
_rt: rt,
}
}
func (rt *VMContext) Compute(f ComputeFunctionID, args []util.Any) Any {
def, found := _computeFunctionDefs[f]
if !found {
rt.AbortAPI("Function definition in rt.Compute() not found")
}
gasCost := def.GasCostFn(args)
rt._rtAllocGas(gasCost)
return def.Body(args)
}
2.3.5.4 代码加载
package impl
import (
abi "github.com/filecoin-project/specs-actors/actors/abi"
vmr "github.com/filecoin-project/specs-actors/actors/runtime"
)
func loadActorCode(codeID abi.ActorCodeID) (vmr.ActorCode, error) {
panic("TODO")
// TODO: resolve circular dependency
// // load the code from StateTree.
// // TODO: this is going to be enabled in the future.
// // code, err := loadCodeFromStateTree(input.InTree, codeCID)
// return staticActorCodeRegistry.LoadActor(codeCID)
}
2.3.5.5 退出码(Exit codes)
package exitcode
type ExitCode int64
const (
// TODO: remove once canonical error codes are finalized
SystemErrorCode_Placeholder = ExitCode(-(1 << 30))
UserDefinedErrorCode_Placeholder = ExitCode(-(1 << 30))
)
const Ok = ExitCode(0)
// TODO: assign all of these.
const (
// ActorNotFound represents a failure to find an actor.
ActorNotFound = SystemErrorCode_Placeholder + iota
// ActorCodeNotFound represents a failure to find the code for a
// particular actor in the VM registry.
ActorCodeNotFound
// InvalidMethod represents a failure to find a method in
// an actor
InvalidMethod
// InvalidArgumentsSystem indicates that a method was called with the incorrect
// number of arguments, or that its arguments did not satisfy its
// preconditions
InvalidArguments_System
// InsufficientFunds represents a failure to apply a message, as
// it did not carry sufficient funds for its application.
InsufficientFunds_System
// InvalidCallSeqNum represents a message invocation out of sequence.
// This happens when message.CallSeqNum is not exactly actor.CallSeqNum + 1
InvalidCallSeqNum
// OutOfGas is returned when the execution of an actor method
// (including its subcalls) uses more gas than initially allocated.
OutOfGas
// RuntimeAPIError is returned when an actor method invocation makes a call
// to the runtime that does not satisfy its preconditions.
RuntimeAPIError
// RuntimeAssertFailure is returned when an actor method invocation calls
// rt.Assert with a false condition.
RuntimeAssertFailure
// MethodSubcallError is returned when an actor method's Send call has
// returned with a failure error code (and the Send call did not specify
// to ignore errors).
MethodSubcallError
)
const (
InsufficientFunds_User = UserDefinedErrorCode_Placeholder + iota
InvalidArguments_User
InconsistentState_User
InvalidSectorPacking
SealVerificationFailed
PoStVerificationFailed
DeadlineExceeded
InsufficientPledgeCollateral
)
func (x ExitCode) IsSuccess() bool {
return x == Ok
}
func (x ExitCode) IsError() bool {
return !x.IsSuccess()
}
func (x ExitCode) AllowsStateUpdate() bool {
return x.IsSuccess()
}
func OK() ExitCode {
return Ok
}
func EnsureErrorCode(x ExitCode) ExitCode {
if !x.IsError() {
// Throwing an error with a non-error exit code is itself an error
x = (RuntimeAPIError)
}
return x
}