feat: events: version the MessageReceipt structure. (#9636)

This commit is contained in:
raulk 2022-11-15 12:00:39 +00:00 committed by GitHub
parent ae7847d097
commit c75d0c4bd1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 492 additions and 200 deletions

View File

@ -344,12 +344,8 @@ func fakeReceipt(tb testing.TB, rng *pseudo.Rand, st adt.Store, events []*types.
eventsRoot, err := arr.Root()
require.NoError(tb, err, "flush events amt")
return &types.MessageReceipt{
ExitCode: exitcode.Ok,
Return: randomBytes(32, rng),
GasUsed: rng.Int63(),
Events: eventsRoot,
}
rec := types.NewMessageReceiptV1(exitcode.Ok, randomBytes(32, rng), rng.Int63(), &eventsRoot)
return &rec
}
func fakeTipSet(tb testing.TB, rng *pseudo.Rand, h abi.ChainEpoch, parents []cid.Cid) *types.TipSet {

View File

@ -15,7 +15,6 @@ import (
address "github.com/filecoin-project/go-address"
abi "github.com/filecoin-project/go-state-types/abi"
crypto "github.com/filecoin-project/go-state-types/crypto"
exitcode "github.com/filecoin-project/go-state-types/exitcode"
proof "github.com/filecoin-project/go-state-types/proof"
)
@ -1289,189 +1288,6 @@ func (t *ActorV5) UnmarshalCBOR(r io.Reader) (err error) {
return nil
}
var lengthBufMessageReceipt = []byte{132}
func (t *MessageReceipt) MarshalCBOR(w io.Writer) error {
if t == nil {
_, err := w.Write(cbg.CborNull)
return err
}
cw := cbg.NewCborWriter(w)
if _, err := cw.Write(lengthBufMessageReceipt); err != nil {
return err
}
// t.ExitCode (exitcode.ExitCode) (int64)
if t.ExitCode >= 0 {
if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.ExitCode)); err != nil {
return err
}
} else {
if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.ExitCode-1)); err != nil {
return err
}
}
// t.Return ([]uint8) (slice)
if len(t.Return) > cbg.ByteArrayMaxLen {
return xerrors.Errorf("Byte array in field t.Return was too long")
}
if err := cw.WriteMajorTypeHeader(cbg.MajByteString, uint64(len(t.Return))); err != nil {
return err
}
if _, err := cw.Write(t.Return[:]); err != nil {
return err
}
// t.GasUsed (int64) (int64)
if t.GasUsed >= 0 {
if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.GasUsed)); err != nil {
return err
}
} else {
if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.GasUsed-1)); err != nil {
return err
}
}
// t.EventsRoot (cid.Cid) (struct)
if t.EventsRoot == nil {
if _, err := cw.Write(cbg.CborNull); err != nil {
return err
}
} else {
if err := cbg.WriteCid(cw, *t.EventsRoot); err != nil {
return xerrors.Errorf("failed to write cid field t.EventsRoot: %w", err)
}
}
return nil
}
func (t *MessageReceipt) UnmarshalCBOR(r io.Reader) (err error) {
*t = MessageReceipt{}
cr := cbg.NewCborReader(r)
maj, extra, err := cr.ReadHeader()
if err != nil {
return err
}
defer func() {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
}()
if maj != cbg.MajArray {
return fmt.Errorf("cbor input should be of type array")
}
if extra != 4 {
return fmt.Errorf("cbor input had wrong number of fields")
}
// t.ExitCode (exitcode.ExitCode) (int64)
{
maj, extra, err := cr.ReadHeader()
var extraI int64
if err != nil {
return err
}
switch maj {
case cbg.MajUnsignedInt:
extraI = int64(extra)
if extraI < 0 {
return fmt.Errorf("int64 positive overflow")
}
case cbg.MajNegativeInt:
extraI = int64(extra)
if extraI < 0 {
return fmt.Errorf("int64 negative oveflow")
}
extraI = -1 - extraI
default:
return fmt.Errorf("wrong type for int64 field: %d", maj)
}
t.ExitCode = exitcode.ExitCode(extraI)
}
// t.Return ([]uint8) (slice)
maj, extra, err = cr.ReadHeader()
if err != nil {
return err
}
if extra > cbg.ByteArrayMaxLen {
return fmt.Errorf("t.Return: byte array too large (%d)", extra)
}
if maj != cbg.MajByteString {
return fmt.Errorf("expected byte array")
}
if extra > 0 {
t.Return = make([]uint8, extra)
}
if _, err := io.ReadFull(cr, t.Return[:]); err != nil {
return err
}
// t.GasUsed (int64) (int64)
{
maj, extra, err := cr.ReadHeader()
var extraI int64
if err != nil {
return err
}
switch maj {
case cbg.MajUnsignedInt:
extraI = int64(extra)
if extraI < 0 {
return fmt.Errorf("int64 positive overflow")
}
case cbg.MajNegativeInt:
extraI = int64(extra)
if extraI < 0 {
return fmt.Errorf("int64 negative oveflow")
}
extraI = -1 - extraI
default:
return fmt.Errorf("wrong type for int64 field: %d", maj)
}
t.GasUsed = int64(extraI)
}
// t.EventsRoot (cid.Cid) (struct)
{
b, err := cr.ReadByte()
if err != nil {
return err
}
if b != cbg.CborNull[0] {
if err := cr.UnreadByte(); err != nil {
return err
}
c, err := cbg.ReadCid(cr)
if err != nil {
return xerrors.Errorf("failed to read cid field t.EventsRoot: %w", err)
}
t.EventsRoot = &c
}
}
return nil
}
var lengthBufBlockMsg = []byte{131}
func (t *BlockMsg) MarshalCBOR(w io.Writer) error {

View File

@ -8,14 +8,52 @@ import (
"github.com/filecoin-project/go-state-types/exitcode"
)
type MessageReceiptVersion byte
const (
// MessageReceiptV0 refers to pre FIP-0049 receipts.
MessageReceiptV0 MessageReceiptVersion = 0
// MessageReceiptV1 refers to post FIP-0049 receipts.
MessageReceiptV1 MessageReceiptVersion = 1
)
type MessageReceipt struct {
version MessageReceiptVersion
ExitCode exitcode.ExitCode
Return []byte
GasUsed int64
EventsRoot *cid.Cid // Root of Event AMT
}
// NewMessageReceiptV0 creates a new pre FIP-0049 receipt with no capability to
// convey events.
func NewMessageReceiptV0(exitcode exitcode.ExitCode, ret []byte, gasUsed int64) MessageReceipt {
return MessageReceipt{
version: MessageReceiptV0,
ExitCode: exitcode,
Return: ret,
GasUsed: gasUsed,
}
}
// NewMessageReceiptV1 creates a new pre FIP-0049 receipt with the ability to
// convey events.
func NewMessageReceiptV1(exitcode exitcode.ExitCode, ret []byte, gasUsed int64, eventsRoot *cid.Cid) MessageReceipt {
return MessageReceipt{
version: MessageReceiptV1,
ExitCode: exitcode,
Return: ret,
GasUsed: gasUsed,
EventsRoot: eventsRoot,
}
}
func (mr *MessageReceipt) Version() MessageReceiptVersion {
return mr.version
}
func (mr *MessageReceipt) Equals(o *MessageReceipt) bool {
return mr.ExitCode == o.ExitCode && bytes.Equal(mr.Return, o.Return) && mr.GasUsed == o.GasUsed &&
return mr.version == mr.version && mr.ExitCode == o.ExitCode && bytes.Equal(mr.Return, o.Return) && mr.GasUsed == o.GasUsed &&
(mr.EventsRoot == o.EventsRoot || (mr.EventsRoot != nil && o.EventsRoot != nil && *mr.EventsRoot == *o.EventsRoot))
}

View File

@ -0,0 +1,359 @@
package types
import (
"fmt"
"io"
cbg "github.com/whyrusleeping/cbor-gen"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-state-types/cbor"
"github.com/filecoin-project/go-state-types/exitcode"
)
// This file contains custom CBOR serde logic to deal with the new versioned
// MessageReceipt resulting from the introduction of actor events (FIP-0049).
type messageReceiptV0 struct{ *MessageReceipt }
type messageReceiptV1 struct{ *MessageReceipt }
func (mr *MessageReceipt) MarshalCBOR(w io.Writer) error {
if mr == nil {
_, err := w.Write(cbg.CborNull)
return err
}
var m cbor.Marshaler
switch mr.version {
case MessageReceiptV0:
m = &messageReceiptV0{mr}
case MessageReceiptV1:
m = &messageReceiptV1{mr}
default:
return xerrors.Errorf("invalid message receipt version: %d", mr.version)
}
return m.MarshalCBOR(w)
}
func (mr *MessageReceipt) UnmarshalCBOR(r io.Reader) (err error) {
*mr = MessageReceipt{}
cr := cbg.NewCborReader(r)
maj, extra, err := cr.ReadHeader()
if err != nil {
return err
}
defer func() {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
}()
if maj != cbg.MajArray {
return fmt.Errorf("cbor input should be of type array")
}
var u cbor.Unmarshaler
switch extra {
case 3:
mr.version = MessageReceiptV0
u = &messageReceiptV0{mr}
case 4:
mr.version = MessageReceiptV1
u = &messageReceiptV1{mr}
default:
return fmt.Errorf("cbor input had wrong number of fields")
}
// Ok to pass a CBOR reader since cbg.NewCborReader will return itself when
// already a CBOR reader.
return u.UnmarshalCBOR(cr)
}
var lengthBufAMessageReceiptV0 = []byte{131}
func (t *messageReceiptV0) MarshalCBOR(w io.Writer) error {
// eliding null check since nulls were already handled in the dispatcher
cw := cbg.NewCborWriter(w)
if _, err := cw.Write(lengthBufAMessageReceiptV0); err != nil {
return err
}
// t.ExitCode (exitcode.ExitCode) (int64)
if t.ExitCode >= 0 {
if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.ExitCode)); err != nil {
return err
}
} else {
if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.ExitCode-1)); err != nil {
return err
}
}
// t.Return ([]uint8) (slice)
if len(t.Return) > cbg.ByteArrayMaxLen {
return xerrors.Errorf("Byte array in field t.Return was too long")
}
if err := cw.WriteMajorTypeHeader(cbg.MajByteString, uint64(len(t.Return))); err != nil {
return err
}
if _, err := cw.Write(t.Return[:]); err != nil {
return err
}
// t.GasUsed (int64) (int64)
if t.GasUsed >= 0 {
if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.GasUsed)); err != nil {
return err
}
} else {
if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.GasUsed-1)); err != nil {
return err
}
}
return nil
}
func (t *messageReceiptV0) UnmarshalCBOR(r io.Reader) (err error) {
cr := cbg.NewCborReader(r)
// t.ExitCode (exitcode.ExitCode) (int64)
{
maj, extra, err := cr.ReadHeader()
var extraI int64
if err != nil {
return err
}
switch maj {
case cbg.MajUnsignedInt:
extraI = int64(extra)
if extraI < 0 {
return fmt.Errorf("int64 positive overflow")
}
case cbg.MajNegativeInt:
extraI = int64(extra)
if extraI < 0 {
return fmt.Errorf("int64 negative oveflow")
}
extraI = -1 - extraI
default:
return fmt.Errorf("wrong type for int64 field: %d", maj)
}
t.ExitCode = exitcode.ExitCode(extraI)
}
// t.Return ([]uint8) (slice)
maj, extra, err := cr.ReadHeader()
if err != nil {
return err
}
if extra > cbg.ByteArrayMaxLen {
return fmt.Errorf("t.Return: byte array too large (%d)", extra)
}
if maj != cbg.MajByteString {
return fmt.Errorf("expected byte array")
}
if extra > 0 {
t.Return = make([]uint8, extra)
}
if _, err := io.ReadFull(cr, t.Return[:]); err != nil {
return err
}
// t.GasUsed (int64) (int64)
{
maj, extra, err := cr.ReadHeader()
var extraI int64
if err != nil {
return err
}
switch maj {
case cbg.MajUnsignedInt:
extraI = int64(extra)
if extraI < 0 {
return fmt.Errorf("int64 positive overflow")
}
case cbg.MajNegativeInt:
extraI = int64(extra)
if extraI < 0 {
return fmt.Errorf("int64 negative oveflow")
}
extraI = -1 - extraI
default:
return fmt.Errorf("wrong type for int64 field: %d", maj)
}
t.GasUsed = extraI
}
return nil
}
var lengthBufBMessageReceiptV1 = []byte{132}
func (t *messageReceiptV1) MarshalCBOR(w io.Writer) error {
// eliding null check since nulls were already handled in the dispatcher
cw := cbg.NewCborWriter(w)
if _, err := cw.Write(lengthBufBMessageReceiptV1); err != nil {
return err
}
// t.ExitCode (exitcode.ExitCode) (int64)
if t.ExitCode >= 0 {
if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.ExitCode)); err != nil {
return err
}
} else {
if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.ExitCode-1)); err != nil {
return err
}
}
// t.Return ([]uint8) (slice)
if len(t.Return) > cbg.ByteArrayMaxLen {
return xerrors.Errorf("Byte array in field t.Return was too long")
}
if err := cw.WriteMajorTypeHeader(cbg.MajByteString, uint64(len(t.Return))); err != nil {
return err
}
if _, err := cw.Write(t.Return[:]); err != nil {
return err
}
// t.GasUsed (int64) (int64)
if t.GasUsed >= 0 {
if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.GasUsed)); err != nil {
return err
}
} else {
if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.GasUsed-1)); err != nil {
return err
}
}
// t.EventsRoot (cid.Cid) (struct)
if t.EventsRoot == nil {
if _, err := cw.Write(cbg.CborNull); err != nil {
return err
}
} else {
if err := cbg.WriteCid(cw, *t.EventsRoot); err != nil {
return xerrors.Errorf("failed to write cid field t.EventsRoot: %w", err)
}
}
return nil
}
func (t *messageReceiptV1) UnmarshalCBOR(r io.Reader) (err error) {
cr := cbg.NewCborReader(r)
// t.ExitCode (exitcode.ExitCode) (int64)
{
maj, extra, err := cr.ReadHeader()
var extraI int64
if err != nil {
return err
}
switch maj {
case cbg.MajUnsignedInt:
extraI = int64(extra)
if extraI < 0 {
return fmt.Errorf("int64 positive overflow")
}
case cbg.MajNegativeInt:
extraI = int64(extra)
if extraI < 0 {
return fmt.Errorf("int64 negative oveflow")
}
extraI = -1 - extraI
default:
return fmt.Errorf("wrong type for int64 field: %d", maj)
}
t.ExitCode = exitcode.ExitCode(extraI)
}
// t.Return ([]uint8) (slice)
maj, extra, err := cr.ReadHeader()
if err != nil {
return err
}
if extra > cbg.ByteArrayMaxLen {
return fmt.Errorf("t.Return: byte array too large (%d)", extra)
}
if maj != cbg.MajByteString {
return fmt.Errorf("expected byte array")
}
if extra > 0 {
t.Return = make([]uint8, extra)
}
if _, err := io.ReadFull(cr, t.Return[:]); err != nil {
return err
}
// t.GasUsed (int64) (int64)
{
maj, extra, err := cr.ReadHeader()
var extraI int64
if err != nil {
return err
}
switch maj {
case cbg.MajUnsignedInt:
extraI = int64(extra)
if extraI < 0 {
return fmt.Errorf("int64 positive overflow")
}
case cbg.MajNegativeInt:
extraI = int64(extra)
if extraI < 0 {
return fmt.Errorf("int64 negative oveflow")
}
extraI = -1 - extraI
default:
return fmt.Errorf("wrong type for int64 field: %d", maj)
}
t.GasUsed = extraI
}
// t.EventsRoot (cid.Cid) (struct)
{
b, err := cr.ReadByte()
if err != nil {
return err
}
if b != cbg.CborNull[0] {
if err := cr.UnreadByte(); err != nil {
return err
}
c, err := cbg.ReadCid(cr)
if err != nil {
return xerrors.Errorf("failed to read cid field t.EventsRoot: %w", err)
}
t.EventsRoot = &c
}
}
return nil
}

View File

@ -0,0 +1,75 @@
package types
import (
"bytes"
"encoding/hex"
"testing"
"github.com/ipfs/go-cid"
"github.com/stretchr/testify/assert"
)
func TestMessageReceiptSerdeRoundrip(t *testing.T) {
var (
assert = assert.New(t)
buf = new(bytes.Buffer)
err error
)
randomCid, err := cid.Decode("bafy2bzacecu7n7wbtogznrtuuvf73dsz7wasgyneqasksdblxupnyovmtwxxu")
assert.NoError(err)
//
// Version 0
//
mr := NewMessageReceiptV0(0, []byte{0x00, 0x01, 0x02, 0x04}, 42)
// marshal
err = mr.MarshalCBOR(buf)
assert.NoError(err)
t.Logf("version 0: %s\n", hex.EncodeToString(buf.Bytes()))
// unmarshal
var mr2 MessageReceipt
err = mr2.UnmarshalCBOR(buf)
assert.NoError(err)
assert.Equal(mr, mr2)
// version 0 with an events root -- should not serialize the events root!
mr.EventsRoot = &randomCid
buf.Reset()
// marshal
err = mr.MarshalCBOR(buf)
assert.NoError(err)
t.Logf("version 0 (with root): %s\n", hex.EncodeToString(buf.Bytes()))
// unmarshal
mr2 = MessageReceipt{}
err = mr2.UnmarshalCBOR(buf)
assert.NoError(err)
assert.NotEqual(mr, mr2)
assert.Nil(mr2.EventsRoot)
//
// Version 1
//
buf.Reset()
mr = NewMessageReceiptV1(0, []byte{0x00, 0x01, 0x02, 0x04}, 42, &randomCid)
// marshal
err = mr.MarshalCBOR(buf)
assert.NoError(err)
t.Logf("version 1: %s\n", hex.EncodeToString(buf.Bytes()))
// unmarshal
mr2 = MessageReceipt{}
err = mr2.UnmarshalCBOR(buf)
assert.NoError(err)
assert.Equal(mr, mr2)
assert.NotNil(mr2.EventsRoot)
}

View File

@ -23,6 +23,7 @@ import (
"github.com/filecoin-project/go-state-types/abi"
actorstypes "github.com/filecoin-project/go-state-types/actors"
"github.com/filecoin-project/go-state-types/exitcode"
"github.com/filecoin-project/go-state-types/network"
"github.com/filecoin-project/lotus/blockstore"
"github.com/filecoin-project/lotus/build"
@ -275,6 +276,7 @@ func (x *FvmExtern) workerKeyAtLookback(ctx context.Context, minerId address.Add
type FVM struct {
fvm *ffi.FVM
nv network.Version
}
func defaultFVMOpts(ctx context.Context, opts *VMOpts) (*ffi.FVMOpts, error) {
@ -337,6 +339,7 @@ func NewFVM(ctx context.Context, opts *VMOpts) (*FVM, error) {
return &FVM{
fvm: fvm,
nv: opts.NetworkVersion,
}, nil
}
@ -439,6 +442,7 @@ func NewDebugFVM(ctx context.Context, opts *VMOpts) (*FVM, error) {
return &FVM{
fvm: fvm,
nv: opts.NetworkVersion,
}, nil
}
@ -457,10 +461,12 @@ func (vm *FVM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet
}
duration := time.Since(start)
receipt := types.MessageReceipt{
Return: ret.Return,
ExitCode: exitcode.ExitCode(ret.ExitCode),
GasUsed: ret.GasUsed,
var receipt types.MessageReceipt
if vm.nv >= network.Version18 {
receipt = types.NewMessageReceiptV1(exitcode.ExitCode(ret.ExitCode), ret.Return, ret.GasUsed, ret.EventsRoot)
} else {
receipt = types.NewMessageReceiptV0(exitcode.ExitCode(ret.ExitCode), ret.Return, ret.GasUsed)
}
var aerr aerrors.ActorError
@ -521,10 +527,12 @@ func (vm *FVM) ApplyImplicitMessage(ctx context.Context, cmsg *types.Message) (*
}
duration := time.Since(start)
receipt := types.MessageReceipt{
Return: ret.Return,
ExitCode: exitcode.ExitCode(ret.ExitCode),
GasUsed: ret.GasUsed,
var receipt types.MessageReceipt
if vm.nv >= network.Version18 {
receipt = types.NewMessageReceiptV1(exitcode.ExitCode(ret.ExitCode), ret.Return, ret.GasUsed, ret.EventsRoot)
} else {
receipt = types.NewMessageReceiptV0(exitcode.ExitCode(ret.ExitCode), ret.Return, ret.GasUsed)
}
var aerr aerrors.ActorError

View File

@ -28,7 +28,7 @@ func main() {
types.MsgMeta{},
types.ActorV4{},
types.ActorV5{},
types.MessageReceipt{},
// types.MessageReceipt{}, // Custom serde to deal with versioning.
types.BlockMsg{},
types.ExpTipSet{},
types.BeaconEntry{},