VM Message - Actor Method Invocation #

A message is the unit of communication between two actors, and thus the primitive cause of changes in state. A message combines:

  • a token amount to be transferred from the sender to the receiver, and
  • a method with parameters to be invoked on the receiver (optional).

Actor code may send additional messages to other actors while processing a received message. Messages are processed synchronously: an actor waits for a sent message to complete before resuming control.

The processing of a message consumes units of computation and storage denominated in gas. A message’s gas limit provides an upper bound on its computation. The sender of a message pays for the gas units consumed by a message’s execution (including all nested messages) at a gas price they determine. A block producer chooses which messages to include in a block and is rewarded according to each message’s gas price and consumption, forming a market.

Message syntax validation #

A syntactically invalid message must not be transmitted, retained in a message pool, or included in a block.

A syntactically valid UnsignedMessage:

  • has a well-formed, non-empty To address,
  • has a well-formed, non-empty From address,
  • has a non-negative CallSeqNum,
  • has Value no less than zero and no greater than the total token supply (2e9 * 1e18), and
  • has a non-negative MethodNum,
  • has non-empty Params only if MethodNum is zero,
  • has non-negative GasPrice,
  • has GasLimit that is at least equal to the gas consumption associated with the message’s serialized bytes,
  • has GasLimit that is no greater than the block gas limit network parameter.

When transmitted individually (before inclusion in a block), a message is packaged as SignedMessage, regardless of signature scheme used. A valid signed message:

  • has a total serialized size no greater than message.MessageMaxSize.

Message semantic validation #

Semantic validation refers to validation requiring information outside of the message itself.

A semantically valid SignedMessage must carry a signature that verifies the payload as having been signed with the public key of the account actor identified by the From address. Note that when the From address is an ID-address, the public key must be looked up in the state of the sending account actor in the parent state identified by the block.

Note: the sending actor must exist in the parent state identified by the block that includes the message. This means that it is not valid for a single block to include a message that creates a new account actor and a message from that same actor. The first message from that actor must wait until a subsequent epoch. Message pools may exclude messages from an actor that is not yet present in the chain state.

There is no further semantic validation of a message that can cause a block including the message to be invalid. Every syntactically valid and correctly signed message can be included in a block and will produce a receipt from execution. However, a message may fail to execute to completion, in which case it will not effect the desired state change.

The reason for this “no message semantic validation” policy is that the state that a message will be applied to cannot be known before the message is executed as part of a tipset. A block producer does not know whether another block will precede it in the tipset, thus altering the state to which the block’s messages will apply from the declared parent 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 {
    // Version of this message (0 until we have to have a breaking change)
    Version     int64

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