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" ) 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 == build.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 > build.BlockGasLimit { return xerrors.Errorf("'GasLimit' field cannot be greater than a block's gas limit (%d > %d)", m.GasLimit, build.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