diff --git a/chain/events/filter/event_test.go b/chain/events/filter/event_test.go index 76cad096e..1ada8a81f 100644 --- a/chain/events/filter/event_test.go +++ b/chain/events/filter/event_test.go @@ -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 { diff --git a/chain/types/cbor_gen.go b/chain/types/cbor_gen.go index 2fcad68fd..91126c573 100644 --- a/chain/types/cbor_gen.go +++ b/chain/types/cbor_gen.go @@ -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 { diff --git a/chain/types/message_receipt.go b/chain/types/message_receipt.go index 69d338f13..d133c84ce 100644 --- a/chain/types/message_receipt.go +++ b/chain/types/message_receipt.go @@ -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)) } diff --git a/chain/types/message_receipt_cbor.go b/chain/types/message_receipt_cbor.go new file mode 100644 index 000000000..e1364e654 --- /dev/null +++ b/chain/types/message_receipt_cbor.go @@ -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 +} diff --git a/chain/types/message_receipt_test.go b/chain/types/message_receipt_test.go new file mode 100644 index 000000000..f0b341f55 --- /dev/null +++ b/chain/types/message_receipt_test.go @@ -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) +} diff --git a/chain/vm/fvm.go b/chain/vm/fvm.go index 29ef223d5..df715ee7a 100644 --- a/chain/vm/fvm.go +++ b/chain/vm/fvm.go @@ -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 diff --git a/gen/main.go b/gen/main.go index 125db293e..f6a373e55 100644 --- a/gen/main.go +++ b/gen/main.go @@ -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{},