VM Interpreter - Message Invocation (Outside VM) #


The VM interpreter orchestrates the execution of messages from a tipset on that tipset’s parent state, producing a new state and a sequence of message receipts. The CIDs of this new state and of the receipt collection are included in blocks from the subsequent epoch, which must agree about those CIDs in order to form a new tipset.

Every state change is driven by the execution of a message. The messages from all the blocks in a tipset must be executed in order to produce a next state. All messages from the first block are executed before those of second and subsequent blocks in the tipset. For each block, BLS-aggregated messages are executed first, then SECP signed messages.

Implicit messages #

In addition to the messages explicitly included in each block, a few state changes at each epoch are made by implicit messages. Implicit messages are not transmitted between nodes, but constructed by the interpreter at evaluation time.

For each block in a tipset, an implicit message:

  • invokes the block producer’s miner actor to process the (already-validated) election PoSt submission, as the first message in the block;
  • invokes the reward actor to pay the block reward to the miner’s owner account, as the final message in the block;

For each tipset, an implicit message:

  • invokes the cron actor to process automated checks and payments, as the final message in the tipset.

All implicit messages are constructed with a From address being the distinguished system account actor. They specify a gas price of zero, but must be included in the computation. They must succeed (have an exit code of zero) in order for the new state to be computed. Receipts for implicit messages are not included in the receipt list; only explicit messages have an explicit receipt.

Gas payments #

In most cases, the sender of a message pays the miner which produced the block including that message a gas fee for its execution.

The gas payments for each message execution are paid to the miner owner account immediately after that message is executed. There are no encumbrances to either the block reward or gas fees earned: both may be spent immediately.

Duplicate messages #

Since different miners produce blocks in the same epoch, multiple blocks in a single tipset may include the same message (identified by the same CID). When this happens, the message is processed only the first time it is encountered in the tipset’s canonical order. Subsequent instances of the message are ignored and do not result in any state mutation, produce a receipt, or pay gas to the block producer.

The sequence of executions for a tipset is thus summarised:

  • pay reward for first block
  • process election post for first block
  • messages for first block (BLS before SECP)
  • pay reward for second block
  • process election post for second block
  • messages for second block (BLS before SECP, skipping any already encountered)
  • [… subsequent blocks …]
  • cron tick

Message validity and failure #

Every message in a valid block can be processed and produce a receipt (note that block validity implies all messages are syntactically valid – see Message Syntax – and correctly signed). However, execution may or may not succeed, depending on the state to which the message is applied. If the execution of a message fails, the corresponding receipt will carry a non-zero exit code.

If a message fails due to a reason that can reasonably be attributed to the miner including a message that could never have succeeded in the parent state, or because the sender lacks funds to cover the maximum message cost, then the miner pays a penalty by burning the gas fee (rather than the sender paying fees to the block miner).

The only state changes resulting from a message failure are either:

  • incrementing of the sending actor’s CallSeqNum, and payment of gas fees from the sender to the owner of the miner of the block including the message; or
  • a penalty equivalent to the gas fee for the failed message, burnt by the miner (sender’s CallSeqNum unchanged).

A message execution will fail if, in the immediately preceding state:

  • the From actor does not exist in the state (miner penalized),
  • the From actor is not an account actor (miner penalized),
  • the CallSeqNum of the message does not match the CallSeqNum of the From actor (miner penalized),
  • the From actor does not have sufficient balance to cover the sum of the message Value plus the maximum gas cost, GasLimit * GasPrice (miner penalized),
  • the To actor does not exist in state and the To address is not a pubkey-style address,
  • the To actor exists (or is implicitly created as an account) but does not have a method corresponding to the non-zero MethodNum,
  • deserialized Params is not an array of length matching the arity of the To actor’s MethodNum method,
  • deserialized Params are not valid for the types specified by the To actor’s MethodNum method,
  • the invoked method consumes more gas than the GasLimit allows,
  • the invoked method exits with a non-zero code (via Runtime.Abort()), or
  • any inner message sent by the receiver fails for any of the above reasons.

Note that if the To actor does not exist in state and the address is a valid H(pubkey) address, it will be created as an account actor.

(You can see the old VM interpreter here )

vm/interpreter interface #

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
    }
}

vm/interpreter implementation #

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")
}

vm/interpreter/registry #

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,
	})
}