diff --git a/cmd/tvx/codenames.go b/cmd/tvx/codenames.go new file mode 100644 index 00000000000..851e9d841aa --- /dev/null +++ b/cmd/tvx/codenames.go @@ -0,0 +1,37 @@ +package main + +import ( + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/build" +) + +// ProtocolCodenames is a table that summarises the protocol codenames that +// will be set on extracted vectors, depending on the original execution height. +// +// Implementers rely on these names to filter the vectors they can run through +// their implementations, based on their support level +var ProtocolCodenames = []struct { + firstEpoch abi.ChainEpoch + name string +}{ + {0, "genesis"}, + {build.UpgradeBreezeHeight + 1, "breeze"}, + {build.UpgradeSmokeHeight + 1, "smoke"}, + {build.UpgradeIgnitionHeight + 1, "ignition"}, + {build.UpgradeRefuelHeight + 1, "refuel"}, + {build.UpgradeActorsV2Height + 1, "actorsv2"}, + {build.UpgradeTapeHeight + 1, "tape"}, + {build.UpgradeLiftoffHeight + 1, "liftoff"}, +} + +// GetProtocolCodename gets the protocol codename associated with a height. +func GetProtocolCodename(height abi.ChainEpoch) string { + for i, v := range ProtocolCodenames { + if height < v.firstEpoch { + // found the cutoff, return previous. + return ProtocolCodenames[i-1].name + } + } + return ProtocolCodenames[len(ProtocolCodenames)-1].name +} diff --git a/cmd/tvx/codenames_test.go b/cmd/tvx/codenames_test.go new file mode 100644 index 00000000000..00d1077072d --- /dev/null +++ b/cmd/tvx/codenames_test.go @@ -0,0 +1,28 @@ +package main + +import ( + "math" + "testing" + + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/build" +) + +func TestProtocolCodenames(t *testing.T) { + if height := abi.ChainEpoch(100); GetProtocolCodename(height) != "genesis" { + t.Fatal("expected genesis codename") + } + + if height := abi.ChainEpoch(build.UpgradeBreezeHeight + 1); GetProtocolCodename(height) != "breeze" { + t.Fatal("expected breeze codename") + } + + if height := build.UpgradeActorsV2Height + 1; GetProtocolCodename(height) != "actorsv2" { + t.Fatal("expected actorsv2 codename") + } + + if height := abi.ChainEpoch(math.MaxInt64); GetProtocolCodename(height) != ProtocolCodenames[len(ProtocolCodenames)-1].name { + t.Fatal("expected last codename") + } +} diff --git a/cmd/tvx/exec.go b/cmd/tvx/exec.go index 9ec6f9e2bc7..89ad2391351 100644 --- a/cmd/tvx/exec.go +++ b/cmd/tvx/exec.go @@ -72,20 +72,24 @@ func runExecLotus(_ *cli.Context) error { func executeTestVector(tv schema.TestVector) error { log.Println("executing test vector:", tv.Meta.ID) - r := new(conformance.LogReporter) - switch class := tv.Class; class { - case "message": - conformance.ExecuteMessageVector(r, &tv) - case "tipset": - conformance.ExecuteTipsetVector(r, &tv) - default: - return fmt.Errorf("test vector class %s not supported", class) - } - if r.Failed() { - log.Println(color.HiRedString("❌ test vector failed")) - } else { - log.Println(color.GreenString("✅ test vector succeeded")) + for _, v := range tv.Pre.Variants { + r := new(conformance.LogReporter) + + switch class, v := tv.Class, v; class { + case "message": + conformance.ExecuteMessageVector(r, &tv, &v) + case "tipset": + conformance.ExecuteTipsetVector(r, &tv, &v) + default: + return fmt.Errorf("test vector class %s not supported", class) + } + + if r.Failed() { + log.Println(color.HiRedString("❌ test vector failed for variant %s", v.ID)) + } else { + log.Println(color.GreenString("✅ test vector succeeded for variant %s", v.ID)) + } } return nil diff --git a/cmd/tvx/extract.go b/cmd/tvx/extract.go index 4bb391af6a0..4a8dd162cd1 100644 --- a/cmd/tvx/extract.go +++ b/cmd/tvx/extract.go @@ -347,6 +347,13 @@ func doExtract(ctx context.Context, fapi api.FullNode, opts extractOpts) error { return err } + nv, err := fapi.StateNetworkVersion(ctx, execTs.Key()) + if err != nil { + return err + } + + codename := GetProtocolCodename(execTs.Height()) + // Write out the test vector. vector := schema.TestVector{ Class: schema.ClassMessage, @@ -363,10 +370,15 @@ func doExtract(ctx context.Context, fapi api.FullNode, opts extractOpts) error { {Source: fmt.Sprintf("execution_tipset:%s", execTs.Key().String())}, {Source: "github.com/filecoin-project/lotus", Version: version.String()}}, }, + Selector: schema.Selector{ + schema.SelectorMinProtocolVersion: codename, + }, Randomness: recordingRand.Recorded(), CAR: out.Bytes(), Pre: &schema.Preconditions{ - Epoch: int64(execTs.Height()), + Variants: []schema.Variant{ + {ID: codename, Epoch: int64(execTs.Height()), NetworkVersion: uint(nv)}, + }, CircSupply: circSupply.Int, BaseFee: basefee.Int, StateTree: &schema.StateTree{ diff --git a/conformance/corpus_test.go b/conformance/corpus_test.go index 3d447570d05..a09f9a8d336 100644 --- a/conformance/corpus_test.go +++ b/conformance/corpus_test.go @@ -11,6 +11,11 @@ import ( "github.com/filecoin-project/test-vectors/schema" ) +var invokees = map[schema.Class]func(Reporter, *schema.TestVector, *schema.Variant){ + schema.ClassMessage: ExecuteMessageVector, + schema.ClassTipset: ExecuteTipsetVector, +} + const ( // EnvSkipConformance, if 1, skips the conformance test suite. EnvSkipConformance = "SKIP_CONFORMANCE" @@ -120,13 +125,16 @@ func TestConformance(t *testing.T) { } // dispatch the execution depending on the vector class. - switch vector.Class { - case "message": - ExecuteMessageVector(t, &vector) - case "tipset": - ExecuteTipsetVector(t, &vector) - default: - t.Fatalf("test vector class not supported: %s", vector.Class) + invokee, ok := invokees[vector.Class] + if !ok { + t.Fatalf("unsupported test vector class: %s", vector.Class) + } + + for _, variant := range vector.Pre.Variants { + variant := variant + t.Run(variant.ID, func(t *testing.T) { + invokee(t, &vector, &variant) + }) } }) } diff --git a/conformance/driver.go b/conformance/driver.go index f49022b9c8c..fbb7dda6d24 100644 --- a/conformance/driver.go +++ b/conformance/driver.go @@ -82,7 +82,7 @@ type ExecuteTipsetResult struct { // This method returns the the receipts root, the poststate root, and the VM // message results. The latter _include_ implicit messages, such as cron ticks // and reward withdrawal per miner. -func (d *Driver) ExecuteTipset(bs blockstore.Blockstore, ds ds.Batching, preroot cid.Cid, parentEpoch abi.ChainEpoch, tipset *schema.Tipset) (*ExecuteTipsetResult, error) { +func (d *Driver) ExecuteTipset(bs blockstore.Blockstore, ds ds.Batching, preroot cid.Cid, parentEpoch abi.ChainEpoch, tipset *schema.Tipset, execEpoch abi.ChainEpoch) (*ExecuteTipsetResult, error) { var ( syscalls = vm.Syscalls(ffiwrapper.ProofVerifier) vmRand = NewFixedRand() @@ -121,11 +121,10 @@ func (d *Driver) ExecuteTipset(bs blockstore.Blockstore, ds ds.Batching, preroot messages []*types.Message results []*vm.ApplyRet - epoch = abi.ChainEpoch(tipset.Epoch) basefee = abi.NewTokenAmount(tipset.BaseFee.Int64()) ) - postcid, receiptsroot, err := sm.ApplyBlocks(context.Background(), parentEpoch, preroot, blocks, epoch, vmRand, func(_ cid.Cid, msg *types.Message, ret *vm.ApplyRet) error { + postcid, receiptsroot, err := sm.ApplyBlocks(context.Background(), parentEpoch, preroot, blocks, execEpoch, vmRand, func(_ cid.Cid, msg *types.Message, ret *vm.ApplyRet) error { messages = append(messages, msg) results = append(results, ret) return nil diff --git a/conformance/runner.go b/conformance/runner.go index d489ac2888f..6f9d73305a3 100644 --- a/conformance/runner.go +++ b/conformance/runner.go @@ -30,11 +30,11 @@ import ( ) // ExecuteMessageVector executes a message-class test vector. -func ExecuteMessageVector(r Reporter, vector *schema.TestVector) { +func ExecuteMessageVector(r Reporter, vector *schema.TestVector, variant *schema.Variant) { var ( - ctx = context.Background() - epoch = vector.Pre.Epoch - root = vector.Pre.StateTree.RootCID + ctx = context.Background() + baseEpoch = variant.Epoch + root = vector.Pre.StateTree.RootCID ) // Load the CAR into a new temporary Blockstore. @@ -53,16 +53,16 @@ func ExecuteMessageVector(r Reporter, vector *schema.TestVector) { r.Fatalf("failed to deserialize message: %s", err) } - // add an epoch if one's set. - if m.Epoch != nil { - epoch = *m.Epoch + // add the epoch offset if one is set. + if m.EpochOffset != nil { + baseEpoch += *m.EpochOffset } // Execute the message. var ret *vm.ApplyRet ret, root, err = driver.ExecuteMessage(bs, ExecuteMessageParams{ Preroot: root, - Epoch: abi.ChainEpoch(epoch), + Epoch: abi.ChainEpoch(baseEpoch), Message: msg, BaseFee: BaseFeeOrDefault(vector.Pre.BaseFee), CircSupply: CircSupplyOrDefault(vector.Pre.CircSupply), @@ -86,10 +86,10 @@ func ExecuteMessageVector(r Reporter, vector *schema.TestVector) { } // ExecuteTipsetVector executes a tipset-class test vector. -func ExecuteTipsetVector(r Reporter, vector *schema.TestVector) { +func ExecuteTipsetVector(r Reporter, vector *schema.TestVector, variant *schema.Variant) { var ( ctx = context.Background() - prevEpoch = vector.Pre.Epoch + baseEpoch = abi.ChainEpoch(variant.Epoch) root = vector.Pre.StateTree.RootCID tmpds = ds.NewMapDatastore() ) @@ -105,9 +105,11 @@ func ExecuteTipsetVector(r Reporter, vector *schema.TestVector) { // Apply every tipset. var receiptsIdx int + var prevEpoch = baseEpoch for i, ts := range vector.ApplyTipsets { ts := ts // capture - ret, err := driver.ExecuteTipset(bs, tmpds, root, abi.ChainEpoch(prevEpoch), &ts) + execEpoch := baseEpoch + abi.ChainEpoch(ts.EpochOffset) + ret, err := driver.ExecuteTipset(bs, tmpds, root, prevEpoch, &ts, execEpoch) if err != nil { r.Fatalf("failed to apply tipset %d message: %s", i, err) } @@ -122,7 +124,7 @@ func ExecuteTipsetVector(r Reporter, vector *schema.TestVector) { r.Errorf("post receipts root doesn't match; expected: %s, was: %s", expected, actual) } - prevEpoch = ts.Epoch + prevEpoch = execEpoch root = ret.PostStateRoot } diff --git a/extern/test-vectors b/extern/test-vectors index a8f968adeba..d9a75a7873a 160000 --- a/extern/test-vectors +++ b/extern/test-vectors @@ -1 +1 @@ -Subproject commit a8f968adeba1995f161f7be0048188affc425079 +Subproject commit d9a75a7873aee0db28b87e3970d2ea16a2f37c6a diff --git a/go.mod b/go.mod index c3935be199c..a6a074e4985 100644 --- a/go.mod +++ b/go.mod @@ -41,7 +41,7 @@ require ( github.com/filecoin-project/specs-actors v0.9.12 github.com/filecoin-project/specs-actors/v2 v2.1.0 github.com/filecoin-project/specs-storage v0.1.1-0.20200907031224-ed2e5cd13796 - github.com/filecoin-project/test-vectors/schema v0.0.4 + github.com/filecoin-project/test-vectors/schema v0.0.5 github.com/gbrlsnchs/jwt/v3 v3.0.0-beta.1 github.com/go-kit/kit v0.10.0 github.com/go-ole/go-ole v1.2.4 // indirect diff --git a/go.sum b/go.sum index feff6abbd08..4cf7b362844 100644 --- a/go.sum +++ b/go.sum @@ -282,8 +282,8 @@ github.com/filecoin-project/specs-actors/v2 v2.1.0 h1:ocEuGz8DG2cUWw32c/tvF8D6xT github.com/filecoin-project/specs-actors/v2 v2.1.0/go.mod h1:E7fAX4CZkDVQvDNRCxfq+hc3nx56KcCKyuZf0hlQJ20= github.com/filecoin-project/specs-storage v0.1.1-0.20200907031224-ed2e5cd13796 h1:dJsTPWpG2pcTeojO2pyn0c6l+x/3MZYCBgo/9d11JEk= github.com/filecoin-project/specs-storage v0.1.1-0.20200907031224-ed2e5cd13796/go.mod h1:nJRRM7Aa9XVvygr3W9k6xGF46RWzr2zxF/iGoAIfA/g= -github.com/filecoin-project/test-vectors/schema v0.0.4 h1:QTRd0gb/NP4ZOTM7Dib5U3xE1/ToGDKnYLfxkC3t/m8= -github.com/filecoin-project/test-vectors/schema v0.0.4/go.mod h1:iQ9QXLpYWL3m7warwvK1JC/pTri8mnfEmKygNDqqY6E= +github.com/filecoin-project/test-vectors/schema v0.0.5 h1:w3zHQhzM4pYxJDl21avXjOKBLF8egrvwUwjpT8TquDg= +github.com/filecoin-project/test-vectors/schema v0.0.5/go.mod h1:iQ9QXLpYWL3m7warwvK1JC/pTri8mnfEmKygNDqqY6E= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6 h1:u/UEqS66A5ckRmS4yNpjmVH56sVtS/RfclBAYocb4as= github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6/go.mod h1:1i71OnUq3iUe1ma7Lr6yG6/rjvM3emb6yoL7xLFzcVQ=