原文链接:filecoin-project.github.io/specs/#syst…
本文由星际联盟Boni Liu翻译,转载请注明出处。
星际联盟提供对Filecoin Spec 的全文翻译,便于Filecoin项目广大中国参与者理解Filecoin的深层原理。文章将定期更新章节,请持续关注"IPFS星际联盟"&"星际联盟Filecoin"公众号。
2.3.5.6 VM Gas费用常数
package runtime
import (
abi "github.com/filecoin-project/specs-actors/actors/abi"
actor "github.com/filecoin-project/specs-actors/actors/builtin"
msg "github.com/filecoin-project/specs/systems/filecoin_vm/message"
util "github.com/filecoin-project/specs/util"
)
type Bytes = util.Bytes
var TODO = util.TODO
var (
// TODO: assign all of these.
GasAmountPlaceholder = msg.GasAmount_FromInt(1)
GasAmountPlaceholder_UpdateStateTree = GasAmountPlaceholder
)
var (
///////////////////////////////////////////////////////////////////////////
// System operations
///////////////////////////////////////////////////////////////////////////
// Gas cost charged to the originator of an on-chain message (regardless of
// whether it succeeds or fails in application) is given by:
// OnChainMessageBase + len(serialized message)*OnChainMessagePerByte
// Together, these account for the cost of message propagation and validation,
// up to but excluding any actual processing by the VM.
// This is the cost a block producer burns when including an invalid message.
OnChainMessageBase = GasAmountPlaceholder
OnChainMessagePerByte = GasAmountPlaceholder
// Gas cost charged to the originator of a non-nil return value produced
// by an on-chain message is given by:
// len(return value)*OnChainReturnValuePerByte
OnChainReturnValuePerByte = GasAmountPlaceholder
// Gas cost for any message send execution(including the top-level one
// initiated by an on-chain message).
// This accounts for the cost of loading sender and receiver actors and
// (for top-level messages) incrementing the sender's sequence number.
// Load and store of actor sub-state is charged separately.
SendBase = GasAmountPlaceholder
// Gas cost charged, in addition to SendBase, if a message send
// is accompanied by any nonzero currency amount.
// Accounts for writing receiver's new balance (the sender's state is
// already accounted for).
SendTransferFunds = GasAmountPlaceholder
// Gas cost charged, in addition to SendBase, if a message invokes
// a method on the receiver.
// Accounts for the cost of loading receiver code and method dispatch.
SendInvokeMethod = GasAmountPlaceholder
// Gas cost (Base + len*PerByte) for any Get operation to the IPLD store
// in the runtime VM context.
IpldGetBase = GasAmountPlaceholder
IpldGetPerByte = GasAmountPlaceholder
// Gas cost (Base + len*PerByte) for any Put operation to the IPLD store
// in the runtime VM context.
//
// Note: these costs should be significantly higher than the costs for Get
// operations, since they reflect not only serialization/deserialization
// but also persistent storage of chain data.
IpldPutBase = GasAmountPlaceholder
IpldPutPerByte = GasAmountPlaceholder
// Gas cost for updating an actor's substate (i.e., UpdateRelease).
// This is in addition to a per-byte fee for the state as for IPLD Get/Put.
UpdateActorSubstate = GasAmountPlaceholder_UpdateStateTree
// Gas cost for creating a new actor (via InitActor's Exec method).
// Actor sub-state is charged separately.
ExecNewActor = GasAmountPlaceholder
// Gas cost for deleting an actor.
DeleteActor = GasAmountPlaceholder
///////////////////////////////////////////////////////////////////////////
// Pure functions (VM ABI)
///////////////////////////////////////////////////////////////////////////
// Gas cost charged per public-key cryptography operation (e.g., signature
// verification).
PublicKeyCryptoOp = GasAmountPlaceholder
)
func OnChainMessage(onChainMessageLen int) msg.GasAmount {
return msg.GasAmount_Affine(OnChainMessageBase, onChainMessageLen, OnChainMessagePerByte)
}
func OnChainReturnValue(returnValue Bytes) msg.GasAmount {
retLen := 0
if returnValue != nil {
retLen = len(returnValue)
}
return msg.GasAmount_Affine(msg.GasAmount_Zero(), retLen, OnChainReturnValuePerByte)
}
func IpldGet(dataSize int) msg.GasAmount {
return msg.GasAmount_Affine(IpldGetBase, dataSize, IpldGetPerByte)
}
func IpldPut(dataSize int) msg.GasAmount {
return msg.GasAmount_Affine(IpldPutBase, dataSize, IpldPutPerByte)
}
func InvokeMethod(value abi.TokenAmount, method abi.MethodNum) msg.GasAmount {
ret := SendBase
if value != abi.TokenAmount(0) {
ret = ret.Add(SendTransferFunds)
}
if method != actor.MethodSend {
ret = ret.Add(SendInvokeMethod)
}
return ret
}
2.3.6 系统Actors
- VM处理需要两个系统Actor:
- 还有另外两个VM级的Actor:
- AccountActor - 用于用户账户(单例)
- RewardActor - 用于区块奖励和代币授予(非单例)
2.3.6.1 InitActor
package init
import (
"bytes"
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"
vmr "github.com/filecoin-project/specs-actors/actors/runtime"
autil "github.com/filecoin-project/specs-actors/actors/util"
cid "github.com/ipfs/go-cid"
)
type InvocOutput = vmr.InvocOutput
type Runtime = vmr.Runtime
type Bytes = abi.Bytes
var AssertMsg = autil.AssertMsg
type InitActorState struct {
// responsible for create new actors
AddressMap map[addr.Address]abi.ActorID
NextID abi.ActorID
NetworkName string
}
func (s *InitActorState) ResolveAddress(address addr.Address) addr.Address {
actorID, ok := s.AddressMap[address]
if ok {
idAddr, err := addr.NewIDAddress(uint64(actorID))
autil.Assert(err == nil)
return idAddr
}
return address
}
func (s *InitActorState) MapAddressToNewID(address addr.Address) addr.Address {
actorID := s.NextID
s.NextID++
s.AddressMap[address] = actorID
idAddr, err := addr.NewIDAddress(uint64(actorID))
autil.Assert(err == nil)
return idAddr
}
func (st *InitActorState) CID() cid.Cid {
panic("TODO")
}
type InitActor struct{}
func (a *InitActor) Constructor(rt Runtime) InvocOutput {
rt.ValidateImmediateCallerIs(builtin.SystemActorAddr)
h := rt.AcquireState()
st := InitActorState{
AddressMap: map[addr.Address]abi.ActorID{}, // TODO: HAMT
NextID: abi.ActorID(builtin.FirstNonSingletonActorId),
NetworkName: vmr.NetworkName(),
}
UpdateRelease(rt, h, st)
return rt.ValueReturn(nil)
}
func (a *InitActor) Exec(rt Runtime, execCodeID abi.ActorCodeID, constructorParams abi.MethodParams) InvocOutput {
rt.ValidateImmediateCallerAcceptAny()
callerCodeID, ok := rt.GetActorCodeID(rt.ImmediateCaller())
AssertMsg(ok, "no code for actor at %s", rt.ImmediateCaller())
if !_codeIDSupportsExec(callerCodeID, execCodeID) {
rt.AbortArgMsg("Caller type cannot create an actor of requested type")
}
// Compute a re-org-stable address.
// This address exists for use by messages coming from outside the system, in order to
// stably address the newly created actor even if a chain re-org causes it to end up with
// a different ID.
newAddr := rt.NewActorAddress()
// Allocate an ID for this actor.
// Store mapping of pubkey or actor address to actor ID
h, st := _loadState(rt)
idAddr := st.MapAddressToNewID(newAddr)
UpdateRelease(rt, h, st)
// Create an empty actor.
rt.CreateActor(execCodeID, idAddr)
// Invoke constructor. If construction fails, the error should propagate and cause
// Exec to fail too.
rt.SendPropagatingErrors(vmr.InvocInput{
To: idAddr,
Method: builtin.MethodConstructor,
Params: constructorParams,
Value: rt.ValueReceived(),
})
var addrBuf bytes.Buffer
err := idAddr.MarshalCBOR(&addrBuf)
autil.Assert(err == nil)
return rt.ValueReturn(addrBuf.Bytes())
}
// This method is disabled until proven necessary.
//func (a *InitActorCode_I) GetActorIDForAddress(rt Runtime, address addr.Address) InvocOutput {
// h, st := _loadState(rt)
// actorID := st.AddressMap[address]
// Release(rt, h, st)
// return rt.ValueReturn(Bytes(addr.Serialize_ActorID(actorID)))
//}
func _codeIDSupportsExec(callerCodeID abi.ActorCodeID, execCodeID abi.ActorCodeID) bool {
if execCodeID == builtin.AccountActorCodeID {
// Special case: account actors must be created implicitly by sending value;
// cannot be created via exec.
return false
}
if execCodeID == builtin.PaymentChannelActorCodeID {
return true
}
if execCodeID == builtin.StorageMinerActorCodeID {
if callerCodeID == builtin.StoragePowerActorCodeID {
return true
}
}
return false
}
///// Boilerplate /////
func _loadState(rt Runtime) (vmr.ActorStateHandle, InitActorState) {
h := rt.AcquireState()
stateCID := cid.Cid(h.Take())
var state InitActorState
if !rt.IpldGet(stateCID, &state) {
rt.AbortAPI("state not found")
}
return h, state
}
func Release(rt Runtime, h vmr.ActorStateHandle, st InitActorState) {
checkCID := actor.ActorSubstateCID(rt.IpldPut(&st))
h.Release(checkCID)
}
func UpdateRelease(rt Runtime, h vmr.ActorStateHandle, st InitActorState) {
newCID := actor.ActorSubstateCID(rt.IpldPut(&st))
h.UpdateRelease(newCID)
}
2.3.6.2 CronActor
package cron
import (
addr "github.com/filecoin-project/go-address"
abi "github.com/filecoin-project/specs-actors/actors/abi"
builtin "github.com/filecoin-project/specs-actors/actors/builtin"
vmr "github.com/filecoin-project/specs-actors/actors/runtime"
)
type CronActorState struct{}
type CronActor struct {
// TODO move Entries into the CronActorState struct
Entries []CronTableEntry
}
type CronTableEntry struct {
ToAddr addr.Address
MethodNum abi.MethodNum
}
func (a *CronActor) Constructor(rt vmr.Runtime) vmr.InvocOutput {
// Nothing. intentionally left blank.
rt.ValidateImmediateCallerIs(builtin.SystemActorAddr)
return rt.SuccessReturn()
}
func (a *CronActor) EpochTick(rt vmr.Runtime) vmr.InvocOutput {
rt.ValidateImmediateCallerIs(builtin.SystemActorAddr)
// a.Entries is basically a static registry for now, loaded
// in the interpreter static registry.
for _, entry := range a.Entries {
rt.SendCatchingErrors(vmr.InvocInput{
To: entry.ToAddr,
Method: entry.MethodNum,
Params: nil,
Value: abi.TokenAmount(0),
})
}
return rt.SuccessReturn()
}
2.3.6.3 AccountActor
package account
import (
addr "github.com/filecoin-project/go-address"
builtin "github.com/filecoin-project/specs-actors/actors/builtin"
vmr "github.com/filecoin-project/specs-actors/actors/runtime"
cid "github.com/ipfs/go-cid"
)
type InvocOutput = vmr.InvocOutput
type AccountActor struct{}
func (a *AccountActor) Constructor(rt vmr.Runtime) InvocOutput {
// Nothing. intentionally left blank.
rt.ValidateImmediateCallerIs(builtin.SystemActorAddr)
return rt.SuccessReturn()
}
type AccountActorState struct {
Address addr.Address
}
func (AccountActorState) CID() cid.Cid {
panic("TODO")
}
2.3.6.4 RewardActor
RewardActor是保存未铸造的Filecoin代币的地方。RewardActor包含一个RewardMap,RewardMap是从所有者账户地址到Reward结构的映射。
Reward结构的创建是为了保留在协议中引入区块奖励归属的灵活性。 MintReward创建一个新的Reward结构并将其添加到RewardMap中。
Reward结构包含:
StartEpoch,用于追踪该Reward的创建时间Value,代表所奖励的代币总数EndEpoch,代表该奖励完全授予的时间VestingFunction,目前是一个枚举,代表不同归属函数的灵活性AmountWithdrawn记录到目前为止已从Reward结构中提取出了多少代币- 所有者地址可以调用
WithdrawReward,它将从到目前为止的RewardMap中提取所有已授予的代币
当AmountWithdrawn等于Reward结构中的Value时,Reward结构将会从RewardMap中被移除。
package reward
import (
"math"
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"
vmr "github.com/filecoin-project/specs-actors/actors/runtime"
serde "github.com/filecoin-project/specs-actors/actors/serde"
autil "github.com/filecoin-project/specs-actors/actors/util"
cid "github.com/ipfs/go-cid"
)
type InvocOutput = vmr.InvocOutput
type Runtime = vmr.Runtime
var IMPL_FINISH = autil.IMPL_FINISH
var IMPL_TODO = autil.IMPL_TODO
var TODO = autil.TODO
type VestingFunction int64
const (
None VestingFunction = iota
Linear
// TODO: potential options
// PieceWise
// Quadratic
// Exponential
)
type Reward struct {
VestingFunction
StartEpoch abi.ChainEpoch
EndEpoch abi.ChainEpoch
Value abi.TokenAmount
AmountWithdrawn abi.TokenAmount
}
func (r *Reward) AmountVested(elapsedEpoch abi.ChainEpoch) abi.TokenAmount {
switch r.VestingFunction {
case None:
return r.Value
case Linear:
TODO() // BigInt
vestedProportion := math.Max(1.0, float64(elapsedEpoch)/float64(r.StartEpoch-r.EndEpoch))
return abi.TokenAmount(uint64(r.Value) * uint64(vestedProportion))
default:
return abi.TokenAmount(0)
}
}
// ownerAddr to a collection of Reward
// TODO: AMT
type RewardBalanceAMT map[addr.Address][]Reward
type RewardActorState struct {
RewardMap RewardBalanceAMT
}
func (st *RewardActorState) CID() cid.Cid {
panic("TODO")
}
func (st *RewardActorState) _withdrawReward(rt vmr.Runtime, ownerAddr addr.Address) abi.TokenAmount {
rewards, found := st.RewardMap[ownerAddr]
if !found {
rt.AbortStateMsg("ra._withdrawReward: ownerAddr not found in RewardMap.")
}
rewardToWithdrawTotal := abi.TokenAmount(0)
indicesToRemove := make([]int, len(rewards))
for i, r := range rewards {
elapsedEpoch := rt.CurrEpoch() - r.StartEpoch
unlockedReward := r.AmountVested(elapsedEpoch)
withdrawableReward := unlockedReward - r.AmountWithdrawn
if withdrawableReward < 0 {
rt.AbortStateMsg("ra._withdrawReward: negative withdrawableReward.")
}
r.AmountWithdrawn = unlockedReward // modify rewards in place
rewardToWithdrawTotal += withdrawableReward
if r.AmountWithdrawn == r.Value {
indicesToRemove = append(indicesToRemove, i)
}
}
updatedRewards := removeIndices(rewards, indicesToRemove)
st.RewardMap[ownerAddr] = updatedRewards
return rewardToWithdrawTotal
}
type RewardActor struct{}
func (a *RewardActor) Constructor(rt vmr.Runtime) InvocOutput {
rt.ValidateImmediateCallerIs(builtin.SystemActorAddr)
// initialize Reward Map with investor accounts
panic("TODO")
}
func (a *RewardActor) State(rt Runtime) (vmr.ActorStateHandle, RewardActorState) {
h := rt.AcquireState()
stateCID := cid.Cid(h.Take())
var state RewardActorState
if !rt.IpldGet(stateCID, &state) {
rt.AbortAPI("state not found")
}
return h, state
}
func (a *RewardActor) WithdrawReward(rt vmr.Runtime) {
vmr.RT_ValidateImmediateCallerIsSignable(rt)
ownerAddr := rt.ImmediateCaller()
h, st := a.State(rt)
// withdraw available funds from RewardMap
withdrawableReward := st._withdrawReward(rt, ownerAddr)
UpdateReleaseRewardActorState(rt, h, st)
rt.SendFunds(ownerAddr, withdrawableReward)
}
func (a *RewardActor) AwardBlockReward(
rt vmr.Runtime,
miner addr.Address,
penalty abi.TokenAmount,
minerNominalPower abi.StoragePower,
currPledge abi.TokenAmount,
) {
rt.ValidateImmediateCallerIs(builtin.SystemActorAddr)
inds := rt.CurrIndices()
pledgeReq := inds.PledgeCollateralReq(minerNominalPower)
currReward := inds.GetCurrBlockRewardForMiner(minerNominalPower, currPledge)
TODO() // BigInt
underPledge := math.Max(float64(abi.TokenAmount(0)), float64(pledgeReq-currPledge)) // 0 if over collateralized
rewardToGarnish := math.Min(float64(currReward), float64(underPledge))
TODO()
// handle penalty here
// also handle penalty greater than reward
actualReward := currReward - abi.TokenAmount(rewardToGarnish)
if rewardToGarnish > 0 {
// Send fund to SPA for collateral
rt.Send(
builtin.StoragePowerActorAddr,
builtin.Method_StoragePowerActor_AddBalance,
serde.MustSerializeParams(miner),
abi.TokenAmount(rewardToGarnish),
)
}
h, st := a.State(rt)
if actualReward > 0 {
// put Reward into RewardMap
newReward := &Reward{
StartEpoch: rt.CurrEpoch(),
EndEpoch: rt.CurrEpoch(),
Value: actualReward,
AmountWithdrawn: abi.TokenAmount(0),
VestingFunction: None,
}
rewards, found := st.RewardMap[miner]
if !found {
rewards = make([]Reward, 0)
}
rewards = append(rewards, *newReward)
st.RewardMap[miner] = rewards
}
UpdateReleaseRewardActorState(rt, h, st)
}
func UpdateReleaseRewardActorState(rt Runtime, h vmr.ActorStateHandle, st RewardActorState) {
newCID := actor.ActorSubstateCID(rt.IpldPut(&st))
h.UpdateRelease(newCID)
}
func removeIndices(rewards []Reward, indices []int) []Reward {
// remove fully paid out Rewards by indices
panic("TODO")
}
2.3.7 VM解释器 - 消息调用(VM外部)
VM解释器在tipset的父状态上,协调该tipset中的消息的执行,进而产生一个新的状态和一系列的消息回执。 这个新状态的CID和回执集合的CID都包含在后续时期的区块中,区块必须就这些CID达成一致才能形成一个新的tipset。
每一个状态更改都由消息的执行来驱动。一个tipset内所有区块中的消息都必须执行才能产生下一个状态。 在tipset中,来自第一个区块的所有消息都会先于第二个和后续区块内的消息执行。 对于每个区块,会首先执行BLS聚合的消息,然后再执行SECP签名的消息。
2.3.7.1 隐式消息
除了被显式地包含在每个区块中的消息之外,隐式消息还会在每个时期进行一些状态更改。 隐式消息不在节点之间传输,而是由解释器在评估时构造。
对于tipset中的每个区块,隐式消息:
- 调用区块出产者的矿工actor来处理(已验证的)选举PoSt的提交,作为区块中的第一条消息;
- 调用奖励actor,将区块奖励支付给矿工拥有者帐户,作为区块中的最后一条消息;
对于每个tipset,隐式消息:
- 调用cron actor来处理自动化检测和支付,作为tipset中的最后一条消息。
所有隐式消息都以From地址作为特定的系统帐户actor进行构建。 他们指定的gas单价为零,但必须被包含进计算中。 为了计算新状态,它们必须被成功执行(退出码为零)。 隐式消息的回执不包括在回执列表中;只有显式的消息才有明确的回执。
2.3.7.2 Gas支付
在大多数情况下,消息的发送者向产出包含该消息的区块的矿工支付执行该消息所需的gas费用。
执行每条消息所产生的gas费用,会在消息执行完毕后立即支付给矿工拥有者账户。 区块奖励或赚得的gas费用都可以自由使用:两者都可以被立即花掉。
2.3.7.3 重复消息
既然不同的矿工在同一时期产生区块,那么单个tipset中的多个区块就有可能包含相同的消息(由相同的CID标识)。当这种情况发生时,会按照tipset的规范顺序,仅在该消息第一次出现时,才对其进行处理。 随后的消息实例将会被忽略,且不会导致任何状态突变,产生回执或向区块产出者支付gas。
因此,总结一个tipset的执行顺序如下:
- 为首个区块支付奖励
- 为首个区块进行“选举post”
- 执行首个区块的消息(BLS优先于SECP)
- 为第二个区块支付奖励
- 为第二块进行“选举post”
- 执行第二个区块的消息(BLS优先于SECP,跳过任何已出现的消息)
- …同上处理接下来的区块…
- 计时标记
2.3.7.4 消息合法性与消息失败
有效区块中的每个消息都可以被处理并产生回执(注意,区块合法性表示所有消息在语法上均正确(请参阅Message Syntax(消息语法)) 并正确签名)。然而,执行成功与否将取决于消息被应用到的状态。 如果消息执行失败,则相应的回执将携带非零的退出码。
如果消息执行失败,可以合理地归因于矿工包含了某条在父状态中永远不可能执行成功的消息,或者由于发送方缺乏资金来支付最大消息费用,如此矿工就得通过燃烧gas经费来支付罚款(而不是发送方来向出块矿工支付费用)。
消息失败导致的唯一状态更改是以下两者之一:
- 增加发送方actor的
CallSeqNum,并从发送方向包含区块消息的矿工拥有者账户支付gas费用 - 一次与失败消息的gas费用相等的罚款,由矿工承担(发送方的CallSeqNum不变)。
如果处于以下状态,消息的执行会立即失败:
- From actor不存在于状态中(矿工受罚)
- From actor不是一个账户actor(矿工受罚)
- 消息的
CallSeqNum与From actor的CallSeqNum不匹配(矿工受罚) - From actor没有足够的余额来支付消息value与最大的gas消耗(
GasLimit*GasPrice) 之和(矿工受罚) - To actor在状态中不存在,并且To账户地址不是一个公钥类型的地址,
- To actor存在(或被隐式地创建为一个账户),但没有对应于非零的
MethodNum的方法 - 反序列化的
Params不是一个长度与To actor的MethodNum方法的参数数量相匹配的数组, - 反序列化的
Params对于由To actor的MethodNum方法指定的类型无效, - 调用的方法所消耗的gas比
GasLimit所允许的要多 - 调用的方法以非零退出码返回(通过Runtime.Abort())
- 发生任何上述原因,接收方发送的任何内部消息都会失败。
请注意,如果To actor在状态中不存在,并且该地址是有效的H(pubkey)地址,则会将其创建为一个账户actor。
(旧版的VM解释器参见这里 )
2.3.7.5 vm/interpreter接口
import addr "github.com/filecoin-project/go-address"
import msg "github.com/filecoin-project/specs/systems/filecoin_vm/message"
import st "github.com/filecoin-project/specs/systems/filecoin_vm/state_tree"
import vmri "github.com/filecoin-project/specs/systems/filecoin_vm/runtime/impl"
import node_base "github.com/filecoin-project/specs/systems/filecoin_nodes/node_base"
import chain "github.com/filecoin-project/specs/systems/filecoin_blockchain/struct/chain"
import abi "github.com/filecoin-project/specs-actors/actors/abi"
type UInt64 UInt
// The messages from one block in a tipset.
type BlockMessages struct {
BLSMessages [msg.UnsignedMessage]
SECPMessages [msg.SignedMessage]
Miner addr.Address // The block miner's actor address
PoStProof Bytes // The miner's Election PoSt proof output
}
// The messages from a tipset, grouped by block.
type TipSetMessages struct {
Blocks [BlockMessages]
Epoch UInt64 // The chain epoch of the blocks
}
type VMInterpreter struct {
Node node_base.FilecoinNode
ApplyTipSetMessages(
inTree st.StateTree
tipset chain.Tipset
msgs TipSetMessages
) struct {outTree st.StateTree, ret [vmri.MessageReceipt]}
ApplyMessage(
inTree st.StateTree
chain chain.Chain
msg msg.UnsignedMessage
onChainMsgSize int
minerAddr addr.Address
) struct {
outTree st.StateTree
ret vmri.MessageReceipt
retMinerPenalty abi.TokenAmount
}
}
2.3.7.6 vm/interpreter实现
package interpreter
import (
addr "github.com/filecoin-project/go-address"
abi "github.com/filecoin-project/specs-actors/actors/abi"
builtin "github.com/filecoin-project/specs-actors/actors/builtin"
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"
serde "github.com/filecoin-project/specs-actors/actors/serde"
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"
vmri "github.com/filecoin-project/specs/systems/filecoin_vm/runtime/impl"
st "github.com/filecoin-project/specs/systems/filecoin_vm/state_tree"
util "github.com/filecoin-project/specs/util"
cid "github.com/ipfs/go-cid"
)
type Bytes = util.Bytes
var Assert = util.Assert
var TODO = util.TODO
var IMPL_FINISH = util.IMPL_FINISH
type SenderResolveSpec int
const (
SenderResolveSpec_OK SenderResolveSpec = 1 + iota
SenderResolveSpec_Invalid
)
// Applies all the message in a tipset, along with implicit block- and tipset-specific state
// transitions.
func (vmi *VMInterpreter_I) ApplyTipSetMessages(inTree st.StateTree, tipset chain.Tipset, msgs TipSetMessages) (outTree st.StateTree, receipts []vmri.MessageReceipt) {
outTree = inTree
seenMsgs := make(map[cid.Cid]struct{}) // CIDs of messages already seen once.
var receipt vmri.MessageReceipt
store := vmi.Node().Repository().StateStore()
// get chain from Tipset
chainRand := &chain.Chain_I{
HeadTipset_: tipset,
}
for _, blk := range msgs.Blocks() {
minerAddr := blk.Miner()
util.Assert(minerAddr.Protocol() == addr.ID) // Block syntactic validation requires this.
// Process block miner's Election PoSt.
epostMessage := _makeElectionPoStMessage(outTree, minerAddr)
outTree = _applyMessageBuiltinAssert(store, outTree, chainRand, epostMessage, minerAddr)
minerPenaltyTotal := abi.TokenAmount(0)
var minerPenaltyCurr abi.TokenAmount
minerGasRewardTotal := abi.TokenAmount(0)
var minerGasRewardCurr abi.TokenAmount
// Process BLS messages from the block.
for _, m := range blk.BLSMessages() {
_, found := seenMsgs[_msgCID(m)]
if found {
continue
}
onChainMessageLen := len(msg.Serialize_UnsignedMessage(m))
outTree, receipt, minerPenaltyCurr, minerGasRewardCurr = vmi.ApplyMessage(outTree, chainRand, m, onChainMessageLen, minerAddr)
minerPenaltyTotal += minerPenaltyCurr
minerGasRewardTotal += minerGasRewardCurr
receipts = append(receipts, receipt)
seenMsgs[_msgCID(m)] = struct{}{}
}
// Process SECP messages from the block.
for _, sm := range blk.SECPMessages() {
m := sm.Message()
_, found := seenMsgs[_msgCID(m)]
if found {
continue
}
onChainMessageLen := len(msg.Serialize_SignedMessage(sm))
outTree, receipt, minerPenaltyCurr, minerGasRewardCurr = vmi.ApplyMessage(outTree, chainRand, m, onChainMessageLen, minerAddr)
minerPenaltyTotal += minerPenaltyCurr
minerGasRewardTotal += minerGasRewardCurr
receipts = append(receipts, receipt)
seenMsgs[_msgCID(m)] = struct{}{}
}
// transfer gas reward from BurntFundsActor to RewardActor
_withTransferFundsAssert(outTree, builtin.BurntFundsActorAddr, builtin.RewardActorAddr, minerGasRewardTotal)
// Pay block reward.
rewardMessage := _makeBlockRewardMessage(outTree, minerAddr, minerPenaltyTotal, minerGasRewardTotal)
outTree = _applyMessageBuiltinAssert(store, outTree, chainRand, rewardMessage, minerAddr)
}
// Invoke cron tick.
// Since this is outside any block, the top level block winner is declared as the system actor.
cronMessage := _makeCronTickMessage(outTree)
outTree = _applyMessageBuiltinAssert(store, outTree, chainRand, cronMessage, builtin.SystemActorAddr)
return
}
func (vmi *VMInterpreter_I) ApplyMessage(inTree st.StateTree, chain chain.Chain, message msg.UnsignedMessage, onChainMessageSize int, minerAddr addr.Address) (
retTree st.StateTree, retReceipt vmri.MessageReceipt, retMinerPenalty abi.TokenAmount, retMinerGasReward abi.TokenAmount) {
store := vmi.Node().Repository().StateStore()
senderAddr := _resolveSender(store, inTree, message.From())
vmiGasRemaining := message.GasLimit()
vmiGasUsed := msg.GasAmount_Zero()
_applyReturn := func(
tree st.StateTree, invocOutput vmr.InvocOutput, exitCode exitcode.ExitCode,
senderResolveSpec SenderResolveSpec) {
vmiGasRemainingFIL := _gasToFIL(vmiGasRemaining, message.GasPrice())
vmiGasUsedFIL := _gasToFIL(vmiGasUsed, message.GasPrice())
switch senderResolveSpec {
case SenderResolveSpec_OK:
// In this case, the sender is valid and has already transferred funds to the burnt funds actor
// sufficient for the gas limit. Thus, we may refund the unused gas funds to the sender here.
Assert(!message.GasLimit().LessThan(vmiGasUsed))
Assert(message.GasLimit().Equals(vmiGasUsed.Add(vmiGasRemaining)))
tree = _withTransferFundsAssert(tree, builtin.BurntFundsActorAddr, senderAddr, vmiGasRemainingFIL)
retMinerGasReward = vmiGasUsedFIL
retMinerPenalty = abi.TokenAmount(0)
case SenderResolveSpec_Invalid:
retMinerPenalty = vmiGasUsedFIL
retMinerGasReward = abi.TokenAmount(0)
default:
Assert(false)
}
retTree = tree
retReceipt = vmri.MessageReceipt_Make(invocOutput, exitCode, vmiGasUsed)
}
// TODO move this to a package with a less redundant name
_applyError := func(tree st.StateTree, errExitCode exitcode.ExitCode, senderResolveSpec SenderResolveSpec) {
_applyReturn(tree, vmr.InvocOutput_Make(nil), errExitCode, senderResolveSpec)
}
// Deduct an amount of gas corresponding to cost about to be incurred, but not necessarily
// incurred yet.
_vmiAllocGas := func(amount msg.GasAmount) (vmiAllocGasOK bool) {
vmiGasRemaining, vmiAllocGasOK = vmiGasRemaining.SubtractIfNonnegative(amount)
vmiGasUsed = message.GasLimit().Subtract(vmiGasRemaining)
Assert(!vmiGasRemaining.LessThan(msg.GasAmount_Zero()))
Assert(!vmiGasUsed.LessThan(msg.GasAmount_Zero()))
return
}
// Deduct an amount of gas corresponding to costs already incurred, and for which the
// gas cost must be paid even if it would cause the gas used to exceed the limit.
_vmiBurnGas := func(amount msg.GasAmount) (vmiBurnGasOK bool) {
vmiGasUsedPre := vmiGasUsed
vmiBurnGasOK = _vmiAllocGas(amount)
if !vmiBurnGasOK {
vmiGasRemaining = msg.GasAmount_Zero()
vmiGasUsed = vmiGasUsedPre.Add(amount)
}
return
}
ok := _vmiBurnGas(gascost.OnChainMessage(onChainMessageSize))
if !ok {
// Invalid message; insufficient gas limit to pay for the on-chain message size.
_applyError(inTree, exitcode.OutOfGas, SenderResolveSpec_Invalid)
return
}
fromActor, ok := inTree.GetActor(senderAddr)
if !ok {
// Execution error; sender does not exist at time of message execution.
_applyError(inTree, exitcode.ActorNotFound, SenderResolveSpec_Invalid)
return
}
// make sure this is the right message order for fromActor
if message.CallSeqNum() != fromActor.CallSeqNum() {
_applyError(inTree, exitcode.InvalidCallSeqNum, SenderResolveSpec_Invalid)
return
}
// Check sender balance.
gasLimitCost := _gasToFIL(message.GasLimit(), message.GasPrice())
tidx := indicesFromStateTree(inTree)
networkTxnFee := tidx.NetworkTransactionFee(
inTree.GetActorCodeID_Assert(message.To()), message.Method())
totalCost := message.Value() + gasLimitCost + networkTxnFee
if fromActor.Balance() < totalCost {
// Execution error; sender does not have sufficient funds to pay for the gas limit.
_applyError(inTree, exitcode.InsufficientFunds_System, SenderResolveSpec_Invalid)
return
}
// At this point, construct compTreePreSend as a state snapshot which includes
// the sender paying gas, and the sender's CallSeqNum being incremented;
// at least that much state change will be persisted even if the
// method invocation subsequently fails.
compTreePreSend := _withTransferFundsAssert(inTree, senderAddr, builtin.BurntFundsActorAddr, gasLimitCost+networkTxnFee)
compTreePreSend = compTreePreSend.Impl().WithIncrementedCallSeqNum_Assert(senderAddr)
invoc := _makeInvocInput(message)
sendRet, compTreePostSend := _applyMessageInternal(store, compTreePreSend, chain, message.CallSeqNum(), senderAddr, invoc, vmiGasRemaining, minerAddr)
ok = _vmiBurnGas(sendRet.GasUsed)
if !ok {
panic("Interpreter error: runtime execution used more gas than provided")
}
ok = _vmiAllocGas(gascost.OnChainReturnValue(sendRet.ReturnValue))
if !ok {
// Insufficient gas remaining to cover the on-chain return value; proceed as in the case
// of method execution failure.
_applyError(compTreePreSend, exitcode.OutOfGas, SenderResolveSpec_OK)
return
}
compTreeRet := compTreePreSend
if sendRet.ExitCode.AllowsStateUpdate() {
compTreeRet = compTreePostSend
}
_applyReturn(
compTreeRet, vmr.InvocOutput_Make(sendRet.ReturnValue), sendRet.ExitCode, SenderResolveSpec_OK)
return
}
// Resolves an address through the InitActor's map.
// Returns the resolved address (which will be an ID address) if found, else the original address.
func _resolveSender(store ipld.GraphStore, tree st.StateTree, address addr.Address) addr.Address {
initState, ok := tree.GetActor(builtin.InitActorAddr)
util.Assert(ok)
serialized, ok := store.Get(cid.Cid(initState.State()))
var initSubState initact.InitActorState
serde.MustDeserialize(serialized, &initSubState)
return initSubState.ResolveAddress(address)
}
func _applyMessageBuiltinAssert(store ipld.GraphStore, tree st.StateTree, chain chain.Chain, message msg.UnsignedMessage, minerAddr addr.Address) st.StateTree {
senderAddr := message.From()
Assert(senderAddr == builtin.SystemActorAddr)
Assert(senderAddr.Protocol() == addr.ID)
// Note: this message CallSeqNum is never checked (b/c it's created in this file), but probably should be.
// Since it changes state, we should be sure about the state transition.
// Alternatively we could special-case the system actor and declare that its CallSeqNumber
// never changes (saving us the state-change overhead).
tree = tree.Impl().WithIncrementedCallSeqNum_Assert(senderAddr)
invoc := _makeInvocInput(message)
retReceipt, retTree := _applyMessageInternal(store, tree, chain, message.CallSeqNum(), senderAddr, invoc, message.GasLimit(), minerAddr)
if retReceipt.ExitCode != exitcode.OK() {
panic("internal message application failed")
}
return retTree
}
func _applyMessageInternal(store ipld.GraphStore, tree st.StateTree, chain chain.Chain, messageCallSequenceNumber actstate.CallSeqNum, senderAddr addr.Address, invoc vmr.InvocInput,
gasRemainingInit msg.GasAmount, topLevelBlockWinner addr.Address) (vmri.MessageReceipt, st.StateTree) {
rt := vmri.VMContext_Make(
store,
chain,
senderAddr,
topLevelBlockWinner,
messageCallSequenceNumber,
actstate.CallSeqNum(0),
tree,
senderAddr,
abi.TokenAmount(0),
gasRemainingInit,
)
return rt.SendToplevelFromInterpreter(invoc)
}
func _withTransferFundsAssert(tree st.StateTree, from addr.Address, to addr.Address, amount abi.TokenAmount) st.StateTree {
// TODO: assert amount nonnegative
retTree, err := tree.Impl().WithFundsTransfer(from, to, amount)
if err != nil {
panic("Interpreter error: insufficient funds (or transfer error) despite checks")
} else {
return retTree
}
}
func indicesFromStateTree(st st.StateTree) indices.Indices {
TODO()
panic("")
}
func _gasToFIL(gas msg.GasAmount, price abi.TokenAmount) abi.TokenAmount {
IMPL_FINISH()
panic("") // BigInt arithmetic
// return abi.TokenAmount(util.UVarint(gas) * util.UVarint(price))
}
func _makeInvocInput(message msg.UnsignedMessage) vmr.InvocInput {
return vmr.InvocInput{
To: message.To(), // Receiver address is resolved during execution.
Method: message.Method(),
Params: message.Params(),
Value: message.Value(),
}
}
// Builds a message for paying block reward to a miner's owner.
func _makeBlockRewardMessage(state st.StateTree, minerAddr addr.Address, penalty abi.TokenAmount, gasReward abi.TokenAmount) msg.UnsignedMessage {
params := serde.MustSerializeParams(minerAddr, penalty)
TODO() // serialize other inputs to BlockRewardMessage or get this from query in RewardActor
sysActor, ok := state.GetActor(builtin.SystemActorAddr)
Assert(ok)
return &msg.UnsignedMessage_I{
From_: builtin.SystemActorAddr,
To_: builtin.RewardActorAddr,
Method_: builtin.Method_RewardActor_AwardBlockReward,
Params_: params,
CallSeqNum_: sysActor.CallSeqNum(),
Value_: 0,
GasPrice_: 0,
GasLimit_: msg.GasAmount_SentinelUnlimited(),
}
}
// Builds a message for submitting ElectionPost on behalf of a miner actor.
func _makeElectionPoStMessage(state st.StateTree, minerActorAddr addr.Address) msg.UnsignedMessage {
sysActor, ok := state.GetActor(builtin.SystemActorAddr)
Assert(ok)
return &msg.UnsignedMessage_I{
From_: builtin.SystemActorAddr,
To_: minerActorAddr,
Method_: builtin.Method_StorageMinerActor_OnVerifiedElectionPoSt,
Params_: nil,
CallSeqNum_: sysActor.CallSeqNum(),
Value_: 0,
GasPrice_: 0,
GasLimit_: msg.GasAmount_SentinelUnlimited(),
}
}
// Builds a message for invoking the cron actor tick.
func _makeCronTickMessage(state st.StateTree) msg.UnsignedMessage {
sysActor, ok := state.GetActor(builtin.SystemActorAddr)
Assert(ok)
return &msg.UnsignedMessage_I{
From_: builtin.SystemActorAddr,
To_: builtin.CronActorAddr,
Method_: builtin.Method_CronActor_EpochTick,
Params_: nil,
CallSeqNum_: sysActor.CallSeqNum(),
Value_: 0,
GasPrice_: 0,
GasLimit_: msg.GasAmount_SentinelUnlimited(),
}
}
func _msgCID(msg msg.UnsignedMessage) cid.Cid {
panic("TODO")
}
2.3.7.7 虚拟机/释义器/注册表
package interpreter
import (
"errors"
abi "github.com/filecoin-project/specs-actors/actors/abi"
builtin "github.com/filecoin-project/specs-actors/actors/builtin"
accact "github.com/filecoin-project/specs-actors/actors/builtin/account"
cronact "github.com/filecoin-project/specs-actors/actors/builtin/cron"
initact "github.com/filecoin-project/specs-actors/actors/builtin/init"
smarkact "github.com/filecoin-project/specs-actors/actors/builtin/storage_market"
spowact "github.com/filecoin-project/specs-actors/actors/builtin/storage_power"
vmr "github.com/filecoin-project/specs-actors/actors/runtime"
)
var (
ErrActorNotFound = errors.New("Actor Not Found")
)
var staticActorCodeRegistry = &actorCodeRegistry{}
type actorCodeRegistry struct {
code map[abi.ActorCodeID]vmr.ActorCode
}
func (r *actorCodeRegistry) _registerActor(id abi.ActorCodeID, actor vmr.ActorCode) {
r.code[id] = actor
}
func (r *actorCodeRegistry) _loadActor(id abi.ActorCodeID) (vmr.ActorCode, error) {
a, ok := r.code[id]
if !ok {
return nil, ErrActorNotFound
}
return a, nil
}
func RegisterActor(id abi.ActorCodeID, actor vmr.ActorCode) {
staticActorCodeRegistry._registerActor(id, actor)
}
func LoadActor(id abi.ActorCodeID) (vmr.ActorCode, error) {
return staticActorCodeRegistry._loadActor(id)
}
// init is called in Go during initialization of a program.
// this is an idiomatic way to do this. Implementations should approach this
// however they wish. The point is to initialize a static registry with
// built in pure types that have the code for each actor. Once we have
// a way to load code from the StateTree, use that instead.
func init() {
_registerBuiltinActors()
}
func _registerBuiltinActors() {
// TODO
cron := &cronact.CronActor{}
RegisterActor(builtin.InitActorCodeID, &initact.InitActor{})
RegisterActor(builtin.CronActorCodeID, cron)
RegisterActor(builtin.AccountActorCodeID, &accact.AccountActor{})
RegisterActor(builtin.StoragePowerActorCodeID, &spowact.StoragePowerActor{})
RegisterActor(builtin.StorageMarketActorCodeID, &smarkact.StorageMarketActor{})
// wire in CRON actions.
// TODO: move this to CronActor's constructor method
cron.Entries = append(cron.Entries, cronact.CronTableEntry{
ToAddr: builtin.StoragePowerActorAddr,
MethodNum: builtin.Method_StoragePowerActor_OnEpochTickEnd,
})
cron.Entries = append(cron.Entries, cronact.CronTableEntry{
ToAddr: builtin.StorageMarketActorAddr,
MethodNum: builtin.Method_StorageMarketActor_OnEpochTickEnd,
})
}