VM Message - Actor Method Invocation
-
State
reliable
-
Theory Audit
wip
-
Edit this section
-
section-systems.filecoin_vm.message
-
State
reliable
-
Theory Audit
wip
- Edit this section
-
section-systems.filecoin_vm.message
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/where applicable).
Actor code may send additional messages to other actors while processing a received message. Messages are processed synchronously, that is, an actor waits for a sent message to complete before resuming control.
The processing of a message consumes units of computation and storage, both of which are denominated in gas. A message’s gas limit provides an upper bound on the computation required to process it. 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
-
State
reliable
-
Theory Audit
wip
-
Edit this section
-
section-systems.filecoin_vm.message.message-syntax-validation
-
State
reliable
-
Theory Audit
wip
- Edit this section
-
section-systems.filecoin_vm.message.message-syntax-validation
A syntactically invalid message must not be transmitted, retained in a message pool, or included in a block. If an invalid message is received, it should be dropped and not propagated further.
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
.
type SignedMessage struct {
Message Message
Signature crypto.Signature
}
A syntactically valid UnsignedMessage
:
- has a well-formed, non-empty
To
address, - has a well-formed, non-empty
From
address, - has
Value
no less than zero and no greater than the total token supply (2e9 * 1e18
), and - 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.
type Message struct {
// Version of this message (has to be non-negative)
Version uint64
// Address of the receiving actor.
To address.Address
// Address of the sending actor.
From address.Address
CallSeqNum uint64
// Value to transfer from sender's to receiver's balance.
Value BigInt
// GasPrice is a Gas-to-FIL cost
GasPrice BigInt
// Maximum Gas to be spent on the processing of this message
GasLimit int64
// Optional method to invoke on receiver, zero for a plain value transfer.
Method abi.MethodNum
//Serialized parameters to the method.
Params []byte
}
There should be several functions able to extract information from the Message struct
, such as the sender and recipient addresses, the value to be transferred, the required funds to execute the message and the CID of the message.
Given that Messages should eventually be included in a Block and added to the blockchain, the validity of the message should be checked with regard to the sender and the receiver of the message, the value (which should be non-negative and always smaller than the circulating supply), the gas price (which again should be non-negative) and the BlockGasLimit
which should not be greater than the block’s gas limit.
Message semantic validation
-
State
reliable
-
Theory Audit
wip
-
Edit this section
-
section-systems.filecoin_vm.message.message-semantic-validation
-
State
reliable
-
Theory Audit
wip
- Edit this section
-
section-systems.filecoin_vm.message.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. The MessageReceipt sturct
includes the following:
type MessageReceipt struct {
ExitCode exitcode.ExitCode
Return []byte
GasUsed int64
}
However, a message may fail to execute to completion, in which case it will not trigger 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.
package types
import (
"bytes"
"encoding/json"
"fmt"
block "github.com/ipfs/go-block-format"
"github.com/ipfs/go-cid"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
"github.com/filecoin-project/go-state-types/network"
"github.com/filecoin-project/lotus/build/buildconstants"
)
const MessageVersion = 0
type ChainMsg interface {
Cid() cid.Cid
VMMessage() *Message
ToStorageBlock() (block.Block, error)
// FIXME: This is the *message* length, this name is misleading.
ChainLength() int
}
type Message struct {
Version uint64
To address.Address
From address.Address
Nonce uint64
Value abi.TokenAmount
GasLimit int64
GasFeeCap abi.TokenAmount
GasPremium abi.TokenAmount
Method abi.MethodNum
Params []byte
}
func (m *Message) Caller() address.Address {
return m.From
}
func (m *Message) Receiver() address.Address {
return m.To
}
func (m *Message) ValueReceived() abi.TokenAmount {
return m.Value
}
func DecodeMessage(b []byte) (*Message, error) {
var msg Message
if err := msg.UnmarshalCBOR(bytes.NewReader(b)); err != nil {
return nil, err
}
if msg.Version != MessageVersion {
return nil, fmt.Errorf("decoded message had incorrect version (%d)", msg.Version)
}
return &msg, nil
}
func (m *Message) Serialize() ([]byte, error) {
buf := new(bytes.Buffer)
if err := m.MarshalCBOR(buf); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func (m *Message) ChainLength() int {
ser, err := m.Serialize()
if err != nil {
panic(err)
}
return len(ser)
}
func (m *Message) ToStorageBlock() (block.Block, error) {
data, err := m.Serialize()
if err != nil {
return nil, err
}
c, err := abi.CidBuilder.Sum(data)
if err != nil {
return nil, err
}
return block.NewBlockWithCid(data, c)
}
func (m *Message) Cid() cid.Cid {
b, err := m.ToStorageBlock()
if err != nil {
panic(fmt.Sprintf("failed to marshal message: %s", err)) // I think this is maybe sketchy, what happens if we try to serialize a message with an undefined address in it?
}
return b.Cid()
}
type mCid struct {
*RawMessage
CID cid.Cid
}
type RawMessage Message
func (m *Message) MarshalJSON() ([]byte, error) {
return json.Marshal(&mCid{
RawMessage: (*RawMessage)(m),
CID: m.Cid(),
})
}
func (m *Message) RequiredFunds() BigInt {
return BigMul(m.GasFeeCap, NewInt(uint64(m.GasLimit)))
}
func (m *Message) VMMessage() *Message {
return m
}
func (m *Message) Equals(o *Message) bool {
return m.Cid() == o.Cid()
}
func (m *Message) EqualCall(o *Message) bool {
m1 := *m
m2 := *o
m1.GasLimit, m2.GasLimit = 0, 0
m1.GasFeeCap, m2.GasFeeCap = big.Zero(), big.Zero()
m1.GasPremium, m2.GasPremium = big.Zero(), big.Zero()
return (&m1).Equals(&m2)
}
func (m *Message) ValidForBlockInclusion(minGas int64, version network.Version) error {
if m.Version != 0 {
return xerrors.New("'Version' unsupported")
}
if m.To == address.Undef {
return xerrors.New("'To' address cannot be empty")
}
if m.To == buildconstants.ZeroAddress && version >= network.Version7 {
return xerrors.New("invalid 'To' address")
}
if !abi.AddressValidForNetworkVersion(m.To, version) {
return xerrors.New("'To' address protocol unsupported for network version")
}
if m.From == address.Undef {
return xerrors.New("'From' address cannot be empty")
}
if !abi.AddressValidForNetworkVersion(m.From, version) {
return xerrors.New("'From' address protocol unsupported for network version")
}
if m.Value.Int == nil {
return xerrors.New("'Value' cannot be nil")
}
if m.Value.LessThan(big.Zero()) {
return xerrors.New("'Value' field cannot be negative")
}
if m.Value.GreaterThan(TotalFilecoinInt) {
return xerrors.New("'Value' field cannot be greater than total filecoin supply")
}
if m.GasFeeCap.Int == nil {
return xerrors.New("'GasFeeCap' cannot be nil")
}
if m.GasFeeCap.LessThan(big.Zero()) {
return xerrors.New("'GasFeeCap' field cannot be negative")
}
if m.GasPremium.Int == nil {
return xerrors.New("'GasPremium' cannot be nil")
}
if m.GasPremium.LessThan(big.Zero()) {
return xerrors.New("'GasPremium' field cannot be negative")
}
if m.GasPremium.GreaterThan(m.GasFeeCap) {
return xerrors.New("'GasFeeCap' less than 'GasPremium'")
}
if m.GasLimit > buildconstants.BlockGasLimit {
return xerrors.Errorf("'GasLimit' field cannot be greater than a block's gas limit (%d > %d)", m.GasLimit, buildconstants.BlockGasLimit)
}
if m.GasLimit <= 0 {
return xerrors.Errorf("'GasLimit' field %d must be positive", m.GasLimit)
}
// since prices might vary with time, this is technically semantic validation
if m.GasLimit < minGas {
return xerrors.Errorf("'GasLimit' field cannot be less than the cost of storing a message on chain %d < %d", m.GasLimit, minGas)
}
return nil
}
// EffectiveGasPremium returns the effective gas premium claimable by the miner
// given the supplied base fee. This method is not used anywhere except the Eth API.
//
// Filecoin clamps the gas premium at GasFeeCap - BaseFee, if lower than the
// specified premium. Returns 0 if GasFeeCap is less than BaseFee.
func (m *Message) EffectiveGasPremium(baseFee abi.TokenAmount) abi.TokenAmount {
available := big.Sub(m.GasFeeCap, baseFee)
// It's possible that storage providers may include messages with gasFeeCap less than the baseFee
// In such cases, their reward should be viewed as zero
if available.LessThan(big.NewInt(0)) {
available = big.NewInt(0)
}
if big.Cmp(m.GasPremium, available) <= 0 {
return m.GasPremium
}
return available
}
const TestGasLimit = 100e6