Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tipset-class test vector driver (take 2) #3485

Merged
merged 2 commits into from
Sep 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
96 changes: 89 additions & 7 deletions conformance/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package conformance

import (
"context"
"fmt"

"github.com/filecoin-project/lotus/chain/stmgr"
"github.com/filecoin-project/lotus/chain/store"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/vm"
"github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper"
Expand All @@ -14,7 +17,10 @@ import (
"github.com/filecoin-project/test-vectors/chaos"
"github.com/filecoin-project/test-vectors/schema"

"github.com/filecoin-project/go-address"

"github.com/ipfs/go-cid"
ds "github.com/ipfs/go-datastore"
)

var (
Expand All @@ -24,16 +30,92 @@ var (
)

type Driver struct {
ctx context.Context
vector *schema.TestVector
ctx context.Context
selector schema.Selector
}

func NewDriver(ctx context.Context, selector schema.Selector) *Driver {
return &Driver{ctx: ctx, selector: selector}
}

type ExecuteTipsetResult struct {
ReceiptsRoot cid.Cid
PostStateRoot cid.Cid

// AppliedMessages stores the messages that were applied, in the order they
// were applied. It includes implicit messages (cron, rewards).
AppliedMessages []*types.Message
// AppliedResults stores the results of AppliedMessages, in the same order.
AppliedResults []*vm.ApplyRet
}

func NewDriver(ctx context.Context, vector *schema.TestVector) *Driver {
return &Driver{ctx: ctx, vector: vector}
// ExecuteTipset executes the supplied tipset on top of the state represented
// by the preroot CID.
//
// parentEpoch is the last epoch in which an actual tipset was processed. This
// is used by Lotus for null block counting and cron firing.
//
// 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) {
var (
syscalls = mkFakedSigSyscalls(vm.Syscalls(ffiwrapper.ProofVerifier))
vmRand = new(testRand)

cs = store.NewChainStore(bs, ds, syscalls)
sm = stmgr.NewStateManager(cs)
)

blocks := make([]store.BlockMessages, 0, len(tipset.Blocks))
for _, b := range tipset.Blocks {
sb := store.BlockMessages{
Miner: b.MinerAddr,
WinCount: b.WinCount,
}
for _, m := range b.Messages {
msg, err := types.DecodeMessage(m)
if err != nil {
return nil, err
}
switch msg.From.Protocol() {
case address.SECP256K1:
sb.SecpkMessages = append(sb.SecpkMessages, msg)
case address.BLS:
sb.BlsMessages = append(sb.BlsMessages, msg)
default:
return nil, fmt.Errorf("from account is not secpk nor bls: %s", msg.From)
}
}
blocks = append(blocks, sb)
}

var (
messages []*types.Message
results []*vm.ApplyRet
)

postcid, receiptsroot, err := sm.ApplyBlocks(context.Background(), parentEpoch, preroot, blocks, tipset.Epoch, vmRand, func(_ cid.Cid, msg *types.Message, ret *vm.ApplyRet) error {
messages = append(messages, msg)
results = append(results, ret)
return nil
}, tipset.BaseFee)

if err != nil {
return nil, err
}

ret := &ExecuteTipsetResult{
ReceiptsRoot: receiptsroot,
PostStateRoot: postcid,
AppliedMessages: messages,
AppliedResults: results,
}
return ret, nil
}

// ExecuteMessage executes a conformance test vector message in a temporary VM.
func (d *Driver) ExecuteMessage(msg *types.Message, preroot cid.Cid, bs blockstore.Blockstore, epoch abi.ChainEpoch) (*vm.ApplyRet, cid.Cid, error) {
func (d *Driver) ExecuteMessage(bs blockstore.Blockstore, preroot cid.Cid, epoch abi.ChainEpoch, msg *types.Message) (*vm.ApplyRet, cid.Cid, error) {
vmOpts := &vm.VMOpts{
StateBase: preroot,
Epoch: epoch,
Expand All @@ -52,10 +134,10 @@ func (d *Driver) ExecuteMessage(msg *types.Message, preroot cid.Cid, bs blocksto
invoker := vm.NewInvoker()

// add support for the puppet and chaos actors.
if puppetOn, ok := d.vector.Selector["puppet_actor"]; ok && puppetOn == "true" {
if puppetOn, ok := d.selector["puppet_actor"]; ok && puppetOn == "true" {
invoker.Register(puppet.PuppetActorCodeID, puppet.Actor{}, puppet.State{})
}
if chaosOn, ok := d.vector.Selector["chaos_actor"]; ok && chaosOn == "true" {
if chaosOn, ok := d.selector["chaos_actor"]; ok && chaosOn == "true" {
invoker.Register(chaos.ChaosActorCodeCID, chaos.Actor{}, chaos.State{})
}

Expand Down
156 changes: 113 additions & 43 deletions conformance/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"testing"

"github.com/ipfs/go-cid"
ds "github.com/ipfs/go-datastore"

"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/vm"
"github.com/filecoin-project/lotus/lib/blockstore"
Expand Down Expand Up @@ -135,6 +140,8 @@ func TestConformance(t *testing.T) {
switch vector.Class {
case "message":
executeMessageVector(t, &vector)
case "tipset":
executeTipsetVector(t, &vector)
default:
t.Fatalf("test vector class not supported: %s", vector.Class)
}
Expand All @@ -150,24 +157,11 @@ func executeMessageVector(t *testing.T, vector *schema.TestVector) {
root = vector.Pre.StateTree.RootCID
)

bs := blockstore.NewTemporary()

// Read the base64-encoded CAR from the vector, and inflate the gzip.
buf := bytes.NewReader(vector.CAR)
r, err := gzip.NewReader(buf)
if err != nil {
t.Fatalf("failed to inflate gzipped CAR: %s", err)
}
defer r.Close() // nolint

// Load the CAR embedded in the test vector into the Blockstore.
_, err = car.LoadCar(bs, r)
if err != nil {
t.Fatalf("failed to load state tree car from test vector: %s", err)
}
// Load the CAR into a new temporary Blockstore.
bs := loadCAR(t, vector.CAR)

// Create a new Driver.
driver := NewDriver(ctx, vector)
driver := NewDriver(ctx, vector.Selector)

// Apply every message.
for i, m := range vector.ApplyMessages {
Expand All @@ -183,52 +177,128 @@ func executeMessageVector(t *testing.T, vector *schema.TestVector) {

// Execute the message.
var ret *vm.ApplyRet
ret, root, err = driver.ExecuteMessage(msg, root, bs, epoch)
ret, root, err = driver.ExecuteMessage(bs, root, epoch, msg)
if err != nil {
t.Fatalf("fatal failure when executing message: %s", err)
}

// Assert that the receipt matches what the test vector expects.
receipt := vector.Post.Receipts[i]
if expected, actual := receipt.ExitCode, ret.ExitCode; expected != actual {
t.Errorf("exit code of msg %d did not match; expected: %s, got: %s", i, expected, actual)
assertMsgResult(t, vector.Post.Receipts[i], ret, strconv.Itoa(i))
}

// Once all messages are applied, assert that the final state root matches
// the expected postcondition root.
if root != vector.Post.StateTree.RootCID {
dumpThreeWayStateDiff(t, vector, bs, root)
}
}

// executeTipsetVector executes a tipset-class test vector.
func executeTipsetVector(t *testing.T, vector *schema.TestVector) {
var (
ctx = context.Background()
prevEpoch = vector.Pre.Epoch
root = vector.Pre.StateTree.RootCID
tmpds = ds.NewMapDatastore()
)

// Load the CAR into a new temporary Blockstore.
bs := loadCAR(t, vector.CAR)

// Create a new Driver.
driver := NewDriver(ctx, vector.Selector)

// Apply every tipset.
var receiptsIdx int
for i, ts := range vector.ApplyTipsets {
ts := ts // capture
ret, err := driver.ExecuteTipset(bs, tmpds, root, prevEpoch, &ts)
if err != nil {
t.Fatalf("failed to apply tipset %d message: %s", i, err)
}
if expected, actual := receipt.GasUsed, ret.GasUsed; expected != actual {
t.Errorf("gas used of msg %d did not match; expected: %d, got: %d", i, expected, actual)

for j, v := range ret.AppliedResults {
assertMsgResult(t, vector.Post.Receipts[receiptsIdx], v, fmt.Sprintf("%d of tipset %d", j, i))
receiptsIdx++
}
if expected, actual := []byte(receipt.ReturnValue), ret.Return; !bytes.Equal(expected, actual) {
t.Errorf("return value of msg %d did not match; expected: %s, got: %s", i, base64.StdEncoding.EncodeToString(expected), base64.StdEncoding.EncodeToString(actual))

// Compare the receipts root.
if expected, actual := vector.Post.ReceiptsRoots[i], ret.ReceiptsRoot; expected != actual {
t.Errorf("post receipts root doesn't match; expected: %s, was: %s", expected, actual)
}

prevEpoch = ts.Epoch
root = ret.PostStateRoot
}

// Once all messages are applied, assert that the final state root matches
// the expected postcondition root.
if root != vector.Post.StateTree.RootCID {
color.NoColor = false // enable colouring.
dumpThreeWayStateDiff(t, vector, bs, root)
}
}

t.Errorf("wrong post root cid; expected %v, but got %v", vector.Post.StateTree.RootCID, root)
// assertMsgResult compares a message result. It takes the expected receipt
// encoded in the vector, the actual receipt returned by Lotus, and a message
// label to log in the assertion failure message to facilitate debugging.
func assertMsgResult(t *testing.T, expected *schema.Receipt, actual *vm.ApplyRet, label string) {
t.Helper()

var (
a = color.New(color.FgMagenta, color.Bold).Sprint("(A) expected final state")
b = color.New(color.FgYellow, color.Bold).Sprint("(B) actual final state")
c = color.New(color.FgCyan, color.Bold).Sprint("(C) initial state")
d1 = color.New(color.FgGreen, color.Bold).Sprint("[Δ1]")
d2 = color.New(color.FgGreen, color.Bold).Sprint("[Δ2]")
d3 = color.New(color.FgGreen, color.Bold).Sprint("[Δ3]")
)
if expected, actual := expected.ExitCode, actual.ExitCode; expected != actual {
t.Errorf("exit code of msg %s did not match; expected: %s, got: %s", label, expected, actual)
}
if expected, actual := expected.GasUsed, actual.GasUsed; expected != actual {
t.Errorf("gas used of msg %s did not match; expected: %d, got: %d", label, expected, actual)
}
if expected, actual := []byte(expected.ReturnValue), actual.Return; !bytes.Equal(expected, actual) {
t.Errorf("return value of msg %s did not match; expected: %s, got: %s", label, base64.StdEncoding.EncodeToString(expected), base64.StdEncoding.EncodeToString(actual))
}
}

func dumpThreeWayStateDiff(t *testing.T, vector *schema.TestVector, bs blockstore.Blockstore, actual cid.Cid) {
color.NoColor = false // enable colouring.

bold := color.New(color.Bold).SprintfFunc()
t.Errorf("wrong post root cid; expected %v, but got %v", vector.Post.StateTree.RootCID, actual)

// run state diffs.
t.Log(bold("=== dumping 3-way diffs between %s, %s, %s ===", a, b, c))
var (
a = color.New(color.FgMagenta, color.Bold).Sprint("(A) expected final state")
b = color.New(color.FgYellow, color.Bold).Sprint("(B) actual final state")
c = color.New(color.FgCyan, color.Bold).Sprint("(C) initial state")
d1 = color.New(color.FgGreen, color.Bold).Sprint("[Δ1]")
d2 = color.New(color.FgGreen, color.Bold).Sprint("[Δ2]")
d3 = color.New(color.FgGreen, color.Bold).Sprint("[Δ3]")
)

t.Log(bold("--- %s left: %s; right: %s ---", d1, a, b))
t.Log(statediff.Diff(context.Background(), bs, vector.Post.StateTree.RootCID, root))
bold := color.New(color.Bold).SprintfFunc()

t.Log(bold("--- %s left: %s; right: %s ---", d2, c, b))
t.Log(statediff.Diff(context.Background(), bs, vector.Pre.StateTree.RootCID, root))
// run state diffs.
t.Log(bold("=== dumping 3-way diffs between %s, %s, %s ===", a, b, c))

t.Log(bold("--- %s left: %s; right: %s ---", d3, c, a))
t.Log(statediff.Diff(context.Background(), bs, vector.Pre.StateTree.RootCID, vector.Post.StateTree.RootCID))
t.Log(bold("--- %s left: %s; right: %s ---", d1, a, b))
t.Log(statediff.Diff(context.Background(), bs, vector.Post.StateTree.RootCID, actual))

t.Log(bold("--- %s left: %s; right: %s ---", d2, c, b))
t.Log(statediff.Diff(context.Background(), bs, vector.Pre.StateTree.RootCID, actual))

t.Log(bold("--- %s left: %s; right: %s ---", d3, c, a))
t.Log(statediff.Diff(context.Background(), bs, vector.Pre.StateTree.RootCID, vector.Post.StateTree.RootCID))
}

func loadCAR(t *testing.T, vectorCAR schema.Base64EncodedBytes) blockstore.Blockstore {
bs := blockstore.NewTemporary()

// Read the base64-encoded CAR from the vector, and inflate the gzip.
buf := bytes.NewReader(vectorCAR)
r, err := gzip.NewReader(buf)
if err != nil {
t.Fatalf("failed to inflate gzipped CAR: %s", err)
}
defer r.Close() // nolint

// Load the CAR embedded in the test vector into the Blockstore.
_, err = car.LoadCar(bs, r)
if err != nil {
t.Fatalf("failed to load state tree car from test vector: %s", err)
}
return bs
}
2 changes: 1 addition & 1 deletion extern/test-vectors
Submodule test-vectors updated 56 files
+3 −0 .github/labels.yml
+19 −0 chaos/actor.go
+70 −0 chaos/cbor_gen.go
+1 −0 chaos/gen/gen.go
+203 −0 corpus/reward/reward--ok-miners-awarded-no-premiums.json
+58 −0 corpus/vm_violations/address_resolution--resolve-address-bad-id-identity.json
+58 −0 corpus/vm_violations/address_resolution--resolve-address-bls-lookup.json
+58 −0 corpus/vm_violations/address_resolution--resolve-address-id-identity.json
+58 −0 corpus/vm_violations/address_resolution--resolve-address-nonexistant.json
+58 −0 corpus/vm_violations/address_resolution--resolve-address-secp-lookup.json
+51 −65 gen/builders/actors.go
+57 −16 gen/builders/asserter.go
+47 −216 gen/builders/builder.go
+160 −0 gen/builders/builder_message.go
+259 −0 gen/builders/builder_tipset.go
+43 −0 gen/builders/car.go
+4 −0 gen/builders/diagnostics.go
+1 −1 gen/builders/gas.go
+39 −10 gen/builders/generator.go
+24 −7 gen/builders/messages.go
+16 −1 gen/builders/predicates.go
+58 −0 gen/builders/reward.go
+132 −0 gen/builders/state_tracker.go
+4 −4 gen/builders/state_zero.go
+95 −0 gen/builders/tipsets.go
+2 −2 gen/builders/wallet.go
+1 −1 gen/suites/actor_creation/addresses.go
+23 −23 gen/suites/actor_creation/main.go
+2 −2 gen/suites/actor_creation/on_tranfer.go
+2 −2 gen/suites/actor_creation/params.go
+1 −1 gen/suites/msg_application/actor_exec.go
+3 −3 gen/suites/msg_application/gas_cost.go
+2 −2 gen/suites/msg_application/invalid_msgs.go
+21 −22 gen/suites/msg_application/main.go
+2 −2 gen/suites/msg_application/unknown_actors.go
+10 −11 gen/suites/multisig/main.go
+13 −13 gen/suites/multisig/ok.go
+31 −32 gen/suites/nested/main.go
+28 −28 gen/suites/nested/nested.go
+8 −9 gen/suites/paych/main.go
+6 −6 gen/suites/paych/ok.go
+27 −0 gen/suites/reward/main.go
+68 −0 gen/suites/reward/miner.go
+2 −2 gen/suites/transfer/basic.go
+35 −35 gen/suites/transfer/main.go
+2 −2 gen/suites/transfer/self_transfer.go
+3 −4 gen/suites/transfer/system_receiver.go
+2 −2 gen/suites/transfer/unknown.go
+2 −2 gen/suites/vm_violations/actor_creation.go
+80 −0 gen/suites/vm_violations/address_resolution.go
+2 −2 gen/suites/vm_violations/caller_validation.go
+86 −39 gen/suites/vm_violations/main.go
+2 −2 go.mod
+85 −13 go.sum
+100 −23 schema.json
+35 −15 schema/schema.go
5 changes: 1 addition & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@ replace github.com/supranational/blst => github.com/filecoin-project/blst v0.1.2
require (
contrib.go.opencensus.io/exporter/jaeger v0.1.0
contrib.go.opencensus.io/exporter/prometheus v0.1.0
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
github.com/BurntSushi/toml v0.3.1
github.com/GeertJohan/go.rice v1.0.0
github.com/Gurpartap/async v0.0.0-20180927173644-4f7f499dd9ee
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
github.com/buger/goterm v0.0.0-20200322175922-2f3e71b85129
github.com/coreos/go-systemd/v22 v22.0.0
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect
github.com/detailyang/go-fallocate v0.0.0-20180908115635-432fa640bd2e
github.com/dgraph-io/badger/v2 v2.0.3
github.com/docker/go-units v0.4.0
Expand All @@ -41,7 +39,7 @@ require (
github.com/filecoin-project/specs-actors v0.9.3
github.com/filecoin-project/specs-storage v0.1.1-0.20200730063404-f7db367e9401
github.com/filecoin-project/statediff v0.0.1
github.com/filecoin-project/test-vectors v0.0.0-20200826113833-9ffe6524729d
github.com/filecoin-project/test-vectors v0.0.0-20200902131127-9806d09b005d
github.com/gbrlsnchs/jwt/v3 v3.0.0-beta.1
github.com/go-kit/kit v0.10.0
github.com/google/uuid v1.1.1
Expand Down Expand Up @@ -119,7 +117,6 @@ require (
github.com/whyrusleeping/pubsub v0.0.0-20131020042734-02de8aa2db3d
github.com/xorcare/golden v0.6.1-0.20191112154924-b87f686d7542
go.opencensus.io v0.22.4
go.uber.org/dig v1.10.0 // indirect
go.uber.org/fx v1.9.0
go.uber.org/multierr v1.5.0
go.uber.org/zap v1.15.0
Expand Down