diff --git a/chaos/actor.go b/chaos/actor.go index a52ab012..1e71e507 100644 --- a/chaos/actor.go +++ b/chaos/actor.go @@ -61,6 +61,9 @@ const ( // MethodMutateState is the identifier for the method that attempts to mutate // a state value in the actor. MethodMutateState + // MethodAbort is the identifier for the method that panics optionally with + // a passed exit code. + MethodAbort ) // Exports defines the methods this actor exposes publicly. @@ -73,6 +76,7 @@ func (a Actor) Exports() []interface{} { MethodDeleteActor: a.DeleteActor, MethodSend: a.Send, MethodMutateState: a.MutateState, + MethodAbort: a.Abort, } } @@ -233,3 +237,21 @@ func (a Actor) MutateState(rt runtime.Runtime, args *MutateStateArgs) *adt.Empty } return nil } + +// AbortArgs are the arguments to the abort method, specifying the exit code to +// (optionally) abort with and the message. +type AbortArgs struct { + Code exitcode.ExitCode + NoCode bool + Message string +} + +// Abort simply causes a panic or abort with the passed exit code. +func (a Actor) Abort(rt runtime.Runtime, args *AbortArgs) *adt.EmptyValue { + if args.NoCode { // no code, just plain old panic + panic(args.Message) + } else { + rt.Abortf(args.Code, args.Message) + } + return nil +} diff --git a/chaos/cbor_gen.go b/chaos/cbor_gen.go index 410d3d4a..7ad3c47b 100644 --- a/chaos/cbor_gen.go +++ b/chaos/cbor_gen.go @@ -599,3 +599,119 @@ func (t *MutateStateArgs) UnmarshalCBOR(r io.Reader) error { } return nil } + +var lengthBufAbortArgs = []byte{131} + +func (t *AbortArgs) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + if _, err := w.Write(lengthBufAbortArgs); err != nil { + return err + } + + scratch := make([]byte, 9) + + // t.Code (exitcode.ExitCode) (int64) + if t.Code >= 0 { + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Code)); err != nil { + return err + } + } else { + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajNegativeInt, uint64(-t.Code-1)); err != nil { + return err + } + } + + // t.NoCode (bool) (bool) + if err := cbg.WriteBool(w, t.NoCode); err != nil { + return err + } + + // t.Message (string) (string) + if len(t.Message) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.Message was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len(t.Message))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.Message)); err != nil { + return err + } + return nil +} + +func (t *AbortArgs) UnmarshalCBOR(r io.Reader) error { + *t = AbortArgs{} + + br := cbg.GetPeeker(r) + scratch := make([]byte, 8) + + maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 3 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.Code (exitcode.ExitCode) (int64) + { + maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + 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.Code = exitcode.ExitCode(extraI) + } + // t.NoCode (bool) (bool) + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajOther { + return fmt.Errorf("booleans must be major type 7") + } + switch extra { + case 20: + t.NoCode = false + case 21: + t.NoCode = true + default: + return fmt.Errorf("booleans are either major type 7, value 20 or 21 (got %d)", extra) + } + // t.Message (string) (string) + + { + sval, err := cbg.ReadStringBuf(br, scratch) + if err != nil { + return err + } + + t.Message = string(sval) + } + return nil +} diff --git a/chaos/gen/gen.go b/chaos/gen/gen.go index 1e08b170..6fa56a6c 100644 --- a/chaos/gen/gen.go +++ b/chaos/gen/gen.go @@ -14,6 +14,7 @@ func main() { chaos.SendArgs{}, chaos.SendReturn{}, chaos.MutateStateArgs{}, + chaos.AbortArgs{}, ); err != nil { panic(err) } diff --git a/gen/suites/vm_violations/actor_abort.go b/gen/suites/vm_violations/actor_abort.go new file mode 100644 index 00000000..6621aa73 --- /dev/null +++ b/gen/suites/vm_violations/actor_abort.go @@ -0,0 +1,32 @@ +package main + +import ( + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/specs-actors/actors/abi" + "github.com/filecoin-project/specs-actors/actors/abi/big" + "github.com/filecoin-project/specs-actors/actors/runtime/exitcode" + "github.com/filecoin-project/test-vectors/chaos" + + . "github.com/filecoin-project/test-vectors/gen/builders" +) + +func abort(args chaos.AbortArgs, expectedCode exitcode.ExitCode) func(*MessageVectorBuilder) { + return func(v *MessageVectorBuilder) { + v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200)) + + sender := v.Actors.Account(address.SECP256K1, abi.NewTokenAmount(1_000_000_000_000)) + v.CommitPreconditions() + + v.Messages.Raw( + sender.ID, + chaos.Address, + chaos.MethodAbort, + MustSerialize(&args), + Value(big.Zero()), + Nonce(0), + ) + v.CommitApplies() + + v.Assert.LastMessageResultSatisfies(ExitCode(expectedCode)) + } +} diff --git a/gen/suites/vm_violations/main.go b/gen/suites/vm_violations/main.go index 5cc4f0d1..92950853 100644 --- a/gen/suites/vm_violations/main.go +++ b/gen/suites/vm_violations/main.go @@ -228,4 +228,29 @@ func main() { MessageFunc: mutateState(valPfx+"after-transaction", chaos.MutateAfterTransaction, exitcode.SysErrorIllegalActor), }, ) + + g.Group("actor_abort", + &VectorDef{ + Metadata: &Metadata{ + ID: "no-exit-code", + Version: "v1", + Desc: "no exit code provided, just panic and let the runtime return the error", + }, + Selector: map[string]string{"chaos_actor": "true"}, + Mode: ModeLenientAssertions, + Hints: []string{schema.HintIncorrect, schema.HintNegate}, + MessageFunc: abort(chaos.AbortArgs{NoCode: true, Message: "no exit code abort"}, exitcode.FirstActorSpecificExitCode), + }, + &VectorDef{ + Metadata: &Metadata{ + ID: "system-exit-code", + Version: "v1", + Desc: "actors should not return system exit codes", + }, + Selector: map[string]string{"chaos_actor": "true"}, + Mode: ModeLenientAssertions, + Hints: []string{schema.HintIncorrect, schema.HintNegate}, + MessageFunc: abort(chaos.AbortArgs{Code: exitcode.SysErrInsufficientFunds, Message: "system exit code abort"}, exitcode.SysErrorIllegalActor), + }, + ) }