package types import ( "bytes" "fmt" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/specs-actors/actors/abi" "github.com/filecoin-project/specs-actors/actors/abi/big" block "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" "github.com/multiformats/go-multihash" xerrors "golang.org/x/xerrors" "github.com/filecoin-project/go-address" ) 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 int64 To address.Address From address.Address Nonce uint64 Value BigInt GasPrice BigInt GasLimit int64 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 } pref := cid.NewPrefixV1(cid.DagCBOR, multihash.BLAKE2B_MIN+31) c, err := pref.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() } func (m *Message) RequiredFunds() BigInt { return BigAdd( m.Value, BigMul(m.GasPrice, 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) ValidForBlockInclusion(minGas int64) 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.From == address.Undef { return xerrors.New("'From' address cannot be empty") } 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.GasPrice.LessThan(big.Zero()) { return xerrors.New("'GasPrice' field cannot be negative") } if m.GasLimit > build.BlockGasLimit { return xerrors.New("'GasLimit' field cannot be greater than a block's gas limit") } // since prices might vary with time, this is technically semantic validation if m.GasLimit < minGas { return xerrors.New("'GasLimit' field cannot be less than the cost of storing a message on chain") } return nil } const TestGasLimit = 100e6