VM Runtime Environment (Inside the VM) #
Receipts #
A MessageReceipt
contains the result of a top-level message execution.
A syntactically valid receipt has:
- a non-negative
ExitCode
, - a non empty
ReturnValue
only if the exit code is zero, - a non-negative
GasUsed
.
vm/runtime
interface
#
package runtime
import (
"bytes"
"context"
"io"
"github.com/filecoin-project/go-address"
addr "github.com/filecoin-project/go-address"
cid "github.com/ipfs/go-cid"
abi "github.com/filecoin-project/specs-actors/actors/abi"
crypto "github.com/filecoin-project/specs-actors/actors/crypto"
exitcode "github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
)
// Specifies importance of message, LogLevel numbering is consistent with the uber-go/zap package.
type LogLevel int
const (
// DebugLevel logs are typically voluminous, and are usually disabled in
// production.
DEBUG LogLevel = iota - 1
// InfoLevel is the default logging priority.
INFO
// WarnLevel logs are more important than Info, but don't need individual
// human review.
WARN
// ErrorLevel logs are high-priority. If an application is running smoothly,
// it shouldn't generate any error-level logs.
ERROR
)
// Runtime is the VM's internal runtime object.
// this is everything that is accessible to actors, beyond parameters.
type Runtime interface {
// Information related to the current message being executed.
Message() Message
// The current chain epoch number. The genesis block has epoch zero.
CurrEpoch() abi.ChainEpoch
// Validates the caller against some predicate.
// Exported actor methods must invoke at least one caller validation before returning.
ValidateImmediateCallerAcceptAny()
ValidateImmediateCallerIs(addrs ...addr.Address)
ValidateImmediateCallerType(types ...cid.Cid)
// The balance of the receiver.
CurrentBalance() abi.TokenAmount
// Resolves an address of any protocol to an ID address (via the Init actor's table).
// This allows resolution of externally-provided SECP, BLS, or actor addresses to the canonical form.
// If the argument is an ID address it is returned directly.
ResolveAddress(address addr.Address) (addr.Address, bool)
// Look up the code ID at an actor address.
GetActorCodeCID(addr addr.Address) (ret cid.Cid, ok bool)
// Randomness returns a (pseudo)random byte array drawing from a
// random beacon at a given epoch and incorporating reequisite entropy
GetRandomness(personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) abi.Randomness
// Provides a handle for the actor's state object.
State() StateHandle
Store() Store
// Sends a message to another actor, returning the exit code and return value envelope.
// If the invoked method does not return successfully, its state changes (and that of any messages it sent in turn)
// will be rolled back.
// The result is never a bare nil, but may be (a wrapper of) adt.Empty.
Send(toAddr addr.Address, methodNum abi.MethodNum, params CBORMarshaler, value abi.TokenAmount) (SendReturn, exitcode.ExitCode)
// Halts execution upon an error from which the receiver cannot recover. The caller will receive the exitcode and
// an empty return value. State changes made within this call will be rolled back.
// This method does not return.
// The message and args are for diagnostic purposes and do not persist on chain. They should be suitable for
// passing to fmt.Errorf(msg, args...).
Abortf(errExitCode exitcode.ExitCode, msg string, args ...interface{})
// 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 with code `codeID` and address `address`, with empty state. May only be called by Init actor.
CreateActor(codeId cid.Cid, address addr.Address)
// Deletes the executing actor from the state tree, transferring any balance to beneficiary.
// Aborts if the beneficiary does not exist.
// May only be called by the actor itself.
DeleteActor(beneficiary addr.Address)
// Provides the system call interface.
Syscalls() Syscalls
// Returns the total token supply in circulation at the beginning of the current epoch.
// The circulating supply is the sum of:
// - rewards emitted by the reward actor,
// - funds vested from lock-ups in the genesis state,
// less the sum of:
// - funds burnt,
// - pledge collateral locked in storage miner actors (recorded in the storage power actor)
// - deal collateral locked by the storage market actor
TotalFilCircSupply() abi.TokenAmount
// Provides a Go context for use by HAMT, etc.
// The VM is intended to provide an idealised machine abstraction, with infinite storage etc, so this context
// should not be used by actor code directly.
Context() context.Context
// Starts a new tracing span. The span must be End()ed explicitly, typically with a deferred invocation.
StartSpan(name string) TraceSpan
// ChargeGas charges specified amount of `gas` for execution.
// `name` provides information about gas charging point
// `virtual` sets virtual amount of gas to charge, this amount is not counted
// toward execution cost. This functionality is used for observing global changes
// in total gas charged if amount of gas charged was to be changed.
ChargeGas(name string, gas int64, virtual int64)
// Note events that may make debugging easier
Log(level LogLevel, msg string, args ...interface{})
}
// Store defines the storage module exposed to actors.
type Store interface {
// Retrieves and deserializes an object from the store into `o`. Returns whether successful.
Get(c cid.Cid, o CBORUnmarshaler) bool
// Serializes and stores an object, returning its CID.
Put(x CBORMarshaler) cid.Cid
}
// Message contains information available to the actor about the executing message.
type Message interface {
// The address of the immediate calling actor. Always an ID-address.
Caller() addr.Address
// The address of the actor receiving the message. Always an ID-address.
Receiver() addr.Address
// The value attached to the message being processed, implicitly added to CurrentBalance() before method invocation.
ValueReceived() abi.TokenAmount
}
// Pure functions implemented as primitives by the runtime.
type Syscalls interface {
// Verifies that a signature is valid for an address and plaintext.
VerifySignature(signature crypto.Signature, signer addr.Address, plaintext []byte) error
// Hashes input data using blake2b with 256 bit output.
HashBlake2b(data []byte) [32]byte
// Computes an unsealed sector CID (CommD) from its constituent piece CIDs (CommPs) and sizes.
ComputeUnsealedSectorCID(reg abi.RegisteredSealProof, pieces []abi.PieceInfo) (cid.Cid, error)
// Verifies a sector seal proof.
VerifySeal(vi abi.SealVerifyInfo) error
BatchVerifySeals(vis map[address.Address][]abi.SealVerifyInfo) (map[address.Address][]bool, error)
// Verifies a proof of spacetime.
VerifyPoSt(vi abi.WindowPoStVerifyInfo) error
// Verifies that two block headers provide proof of a consensus fault:
// - both headers mined by the same actor
// - headers are different
// - first header is of the same or lower epoch as the second
// - the headers provide evidence of a fault (see the spec for the different fault types).
// The parameters are all serialized block headers. The third "extra" parameter is consulted only for
// the "parent grinding fault", in which case it must be the sibling of h1 (same parent tipset) and one of the
// blocks in an ancestor of h2.
// Returns nil and an error if the headers don't prove a fault.
VerifyConsensusFault(h1, h2, extra []byte) (*ConsensusFault, error)
}
// The return type from a message send from one actor to another. This abstracts over the internal representation of
// the return, in particular whether it has been serialized to bytes or just passed through.
// Production code is expected to de/serialize, but test and other code may pass the value straight through.
type SendReturn interface {
Into(CBORUnmarshaler) error
}
// Provides (minimal) tracing facilities to actor code.
type TraceSpan interface {
// Ends the span
End()
}
// StateHandle provides mutable, exclusive access to actor state.
type StateHandle interface {
// Create initializes the state object.
// This is only valid in a constructor function and when the state has not yet been initialized.
Create(obj CBORMarshaler)
// Readonly loads a readonly copy of the state into the argument.
//
// Any modification to the state is illegal and will result in an abort.
Readonly(obj CBORUnmarshaler)
// Transaction loads a mutable version of the state into the `obj` argument and protects
// the execution from side effects (including message send).
//
// The second argument is a function which allows the caller to mutate the state.
// The return value from that function will be returned from the call to Transaction().
//
// If the state is modified after this function returns, execution will abort.
//
// The gas cost of this method is that of a Store.Put of the mutated state object.
//
// Note: the Go signature is not ideal due to lack of type system power.
//
// # Usage
// ```go
// var state SomeState
// ret := rt.State().Transaction(&state, func() (interface{}) {
// // make some changes
// st.ImLoaded = True
// return st.Thing, nil
// })
// // state.ImLoaded = False // BAD!! state is readonly outside the lambda, it will panic
// ```
Transaction(obj CBORer, f func() interface{}) interface{}
}
// Result of checking two headers for a consensus fault.
type ConsensusFault struct {
// Address of the miner at fault (always an ID address).
Target addr.Address
// Epoch of the fault, which is the higher epoch of the two blocks causing it.
Epoch abi.ChainEpoch
// Type of fault.
Type ConsensusFaultType
}
type ConsensusFaultType int64
const (
//ConsensusFaultNone ConsensusFaultType = 0
ConsensusFaultDoubleForkMining ConsensusFaultType = 1
ConsensusFaultParentGrinding ConsensusFaultType = 2
ConsensusFaultTimeOffsetMining ConsensusFaultType = 3
)
// These interfaces are intended to match those from whyrusleeping/cbor-gen, such that code generated from that
// system is automatically usable here (but not mandatory).
type CBORMarshaler interface {
MarshalCBOR(w io.Writer) error
}
type CBORUnmarshaler interface {
UnmarshalCBOR(r io.Reader) error
}
type CBORer interface {
CBORMarshaler
CBORUnmarshaler
}
// Wraps already-serialized bytes as CBOR-marshalable.
type CBORBytes []byte
func (b CBORBytes) MarshalCBOR(w io.Writer) error {
_, err := w.Write(b)
return err
}
func (b *CBORBytes) UnmarshalCBOR(r io.Reader) error {
var c bytes.Buffer
_, err := c.ReadFrom(r)
*b = c.Bytes()
return err
}
vm/runtime
implementation
#
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.RandomnessSeedAtEpoch(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)
}
Code Loading #
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)
}
Exit codes #
package exitcode
// Common error codes that may be shared by different actors.
// Actors may also define their own codes, including redefining these values.
const (
// Indicates a method parameter is invalid.
ErrIllegalArgument = FirstActorErrorCode + iota
// Indicates a requested resource does not exist.
ErrNotFound
// Indicates an action is disallowed.
ErrForbidden
// Indicates a balance of funds is insufficient.
ErrInsufficientFunds
// Indicates an actor's internal state is invalid.
ErrIllegalState
// Indicates de/serialization failure within actor code.
ErrSerialization
// Common error codes stop here. If you define a common error code above
// this value it will have conflicting interpretations
FirstActorSpecificExitCode = ExitCode(32)
// An error code intended to be replaced by different code structure or a more descriptive error.
ErrPlaceholder = ExitCode(1000)
)