Skip to content

Commit

Permalink
Merge pull request #3485 from filecoin-project/tipset-vector
Browse files Browse the repository at this point in the history
tipset-class test vector driver (take 2)
  • Loading branch information
magik6k authored Sep 2, 2020
2 parents b7f2487 + 7cc9c83 commit edc2a28
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 65 deletions.
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
Loading

0 comments on commit edc2a28

Please sign in to comment.