Skip to content

Commit

Permalink
Merge pull request #4554 from filecoin-project/tvx-simulate-2
Browse files Browse the repository at this point in the history
tvx simulate command; tvx extract --ignore-sanity-checks
  • Loading branch information
magik6k authored Oct 23, 2020
2 parents 623ffeb + 39d9150 commit 91fae36
Show file tree
Hide file tree
Showing 4 changed files with 337 additions and 69 deletions.
106 changes: 52 additions & 54 deletions cmd/tvx/extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"github.com/filecoin-project/lotus/chain/actors/builtin/reward"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/vm"
lcli "github.com/filecoin-project/lotus/cli"
"github.com/filecoin-project/lotus/conformance"

"github.com/filecoin-project/test-vectors/schema"
Expand All @@ -34,13 +33,14 @@ const (
)

type extractOpts struct {
id string
block string
class string
cid string
file string
retain string
precursor string
id string
block string
class string
cid string
file string
retain string
precursor string
ignoreSanityChecks bool
}

var extractFlags extractOpts
Expand All @@ -49,6 +49,8 @@ var extractCmd = &cli.Command{
Name: "extract",
Description: "generate a test vector by extracting it from a live chain",
Action: runExtract,
Before: initialize,
After: destroy,
Flags: []cli.Flag{
&repoFlag,
&cli.StringFlag{
Expand Down Expand Up @@ -88,53 +90,42 @@ var extractCmd = &cli.Command{
},
&cli.StringFlag{
Name: "precursor-select",
Usage: "precursors to apply; values: 'all', 'sender'; 'all' selects all preceding" +
"messages in the canonicalised tipset, 'sender' selects only preceding messages from the same" +
"sender. Usually, 'sender' is a good tradeoff and gives you sufficient accuracy. If the receipt sanity" +
"check fails due to gas reasons, switch to 'all', as previous messages in the tipset may have" +
Usage: "precursors to apply; values: 'all', 'sender'; 'all' selects all preceding " +
"messages in the canonicalised tipset, 'sender' selects only preceding messages from the same " +
"sender. Usually, 'sender' is a good tradeoff and gives you sufficient accuracy. If the receipt sanity " +
"check fails due to gas reasons, switch to 'all', as previous messages in the tipset may have " +
"affected state in a disruptive way",
Value: "sender",
Destination: &extractFlags.precursor,
},
&cli.BoolFlag{
Name: "ignore-sanity-checks",
Usage: "generate vector even if sanity checks fail",
Value: false,
Destination: &extractFlags.ignoreSanityChecks,
},
},
}

func runExtract(c *cli.Context) error {
// LOTUS_DISABLE_VM_BUF disables what's called "VM state tree buffering",
// which stashes write operations in a BufferedBlockstore
// (https://github.com/filecoin-project/lotus/blob/b7a4dbb07fd8332b4492313a617e3458f8003b2a/lib/bufbstore/buf_bstore.go#L21)
// such that they're not written until the VM is actually flushed.
//
// For some reason, the standard behaviour was not working for me (raulk),
// and disabling it (such that the state transformations are written immediately
// to the blockstore) worked.
_ = os.Setenv("LOTUS_DISABLE_VM_BUF", "iknowitsabadidea")
func runExtract(_ *cli.Context) error {
return doExtract(extractFlags)
}

func doExtract(opts extractOpts) error {
ctx := context.Background()

// Make the API client.
fapi, closer, err := lcli.GetFullNodeAPI(c)
if err != nil {
return err
}
defer closer()

return doExtract(ctx, fapi, extractFlags)
}

func doExtract(ctx context.Context, fapi api.FullNode, opts extractOpts) error {
mcid, err := cid.Decode(opts.cid)
if err != nil {
return err
}

msg, execTs, incTs, err := resolveFromChain(ctx, fapi, mcid, opts.block)
msg, execTs, incTs, err := resolveFromChain(ctx, FullAPI, mcid, opts.block)
if err != nil {
return fmt.Errorf("failed to resolve message and tipsets from chain: %w", err)
}

// get the circulating supply before the message was executed.
circSupplyDetail, err := fapi.StateVMCirculatingSupplyInternal(ctx, incTs.Key())
circSupplyDetail, err := FullAPI.StateVMCirculatingSupplyInternal(ctx, incTs.Key())
if err != nil {
return fmt.Errorf("failed while fetching circulating supply: %w", err)
}
Expand All @@ -147,7 +138,7 @@ func doExtract(ctx context.Context, fapi api.FullNode, opts extractOpts) error {
log.Printf("finding precursor messages using mode: %s", opts.precursor)

// Fetch messages in canonical order from inclusion tipset.
msgs, err := fapi.ChainGetParentMessages(ctx, execTs.Blocks()[0].Cid())
msgs, err := FullAPI.ChainGetParentMessages(ctx, execTs.Blocks()[0].Cid())
if err != nil {
return fmt.Errorf("failed to fetch messages in canonical order from inclusion tipset: %w", err)
}
Expand All @@ -174,8 +165,8 @@ func doExtract(ctx context.Context, fapi api.FullNode, opts extractOpts) error {

var (
// create a read-through store that uses ChainGetObject to fetch unknown CIDs.
pst = NewProxyingStores(ctx, fapi)
g = NewSurgeon(ctx, fapi, pst)
pst = NewProxyingStores(ctx, FullAPI)
g = NewSurgeon(ctx, FullAPI, pst)
)

driver := conformance.NewDriver(ctx, schema.Selector{}, conformance.DriverOpts{
Expand All @@ -200,7 +191,7 @@ func doExtract(ctx context.Context, fapi api.FullNode, opts extractOpts) error {
CircSupply: circSupplyDetail.FilCirculating,
BaseFee: basefee,
// recorded randomness will be discarded.
Rand: conformance.NewRecordingRand(new(conformance.LogReporter), fapi),
Rand: conformance.NewRecordingRand(new(conformance.LogReporter), FullAPI),
})
if err != nil {
return fmt.Errorf("failed to execute precursor message: %w", err)
Expand All @@ -215,7 +206,7 @@ func doExtract(ctx context.Context, fapi api.FullNode, opts extractOpts) error {
retention = opts.retain

// recordingRand will record randomness so we can embed it in the test vector.
recordingRand = conformance.NewRecordingRand(new(conformance.LogReporter), fapi)
recordingRand = conformance.NewRecordingRand(new(conformance.LogReporter), FullAPI)
)

log.Printf("using state retention strategy: %s", retention)
Expand Down Expand Up @@ -248,7 +239,7 @@ func doExtract(ctx context.Context, fapi api.FullNode, opts extractOpts) error {
case "accessed-actors":
log.Printf("calculating accessed actors")
// get actors accessed by message.
retain, err := g.GetAccessedActors(ctx, fapi, mcid)
retain, err := g.GetAccessedActors(ctx, FullAPI, mcid)
if err != nil {
return fmt.Errorf("failed to calculate accessed actors: %w", err)
}
Expand Down Expand Up @@ -286,7 +277,7 @@ func doExtract(ctx context.Context, fapi api.FullNode, opts extractOpts) error {
// TODO sometimes this returns a nil receipt and no error ¯\_(ツ)_/¯
// ex: https://filfox.info/en/message/bafy2bzacebpxw3yiaxzy2bako62akig46x3imji7fewszen6fryiz6nymu2b2
// This code is lenient and skips receipt comparison in case of a nil receipt.
rec, err := fapi.StateGetReceipt(ctx, mcid, execTs.Key())
rec, err := FullAPI.StateGetReceipt(ctx, mcid, execTs.Key())
if err != nil {
return fmt.Errorf("failed to find receipt on chain: %w", err)
}
Expand All @@ -300,13 +291,20 @@ func doExtract(ctx context.Context, fapi api.FullNode, opts extractOpts) error {
ReturnValue: rec.Return,
GasUsed: rec.GasUsed,
}

reporter := new(conformance.LogReporter)
conformance.AssertMsgResult(reporter, receipt, applyret, "as locally executed")
if reporter.Failed() {
log.Println(color.RedString("receipt sanity check failed; aborting"))
return fmt.Errorf("vector generation aborted")
if opts.ignoreSanityChecks {
log.Println(color.YellowString("receipt sanity check failed; proceeding anyway"))
} else {
log.Println(color.RedString("receipt sanity check failed; aborting"))
return fmt.Errorf("vector generation aborted")
}
} else {
log.Println(color.GreenString("receipt sanity check succeeded"))
}
log.Println(color.GreenString("receipt sanity check succeeded"))

} else {
receipt = &schema.Receipt{
ExitCode: int64(applyret.ExitCode),
Expand Down Expand Up @@ -336,17 +334,17 @@ func doExtract(ctx context.Context, fapi api.FullNode, opts extractOpts) error {
return err
}

version, err := fapi.Version(ctx)
version, err := FullAPI.Version(ctx)
if err != nil {
return err
}

ntwkName, err := fapi.StateNetworkName(ctx)
ntwkName, err := FullAPI.StateNetworkName(ctx)
if err != nil {
return err
}

nv, err := fapi.StateNetworkVersion(ctx, execTs.Key())
nv, err := FullAPI.StateNetworkVersion(ctx, execTs.Key())
if err != nil {
return err
}
Expand Down Expand Up @@ -399,8 +397,12 @@ func doExtract(ctx context.Context, fapi api.FullNode, opts extractOpts) error {
},
}

return writeVector(vector, opts.file)
}

func writeVector(vector schema.TestVector, file string) (err error) {
output := io.WriteCloser(os.Stdout)
if file := opts.file; file != "" {
if file := file; file != "" {
dir := filepath.Dir(file)
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("unable to create directory %s: %w", dir, err)
Expand All @@ -415,11 +417,7 @@ func doExtract(ctx context.Context, fapi api.FullNode, opts extractOpts) error {

enc := json.NewEncoder(output)
enc.SetIndent("", " ")
if err := enc.Encode(&vector); err != nil {
return err
}

return nil
return enc.Encode(&vector)
}

// resolveFromChain queries the chain for the provided message, using the block CID to
Expand Down
19 changes: 5 additions & 14 deletions cmd/tvx/extract_many.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package main

import (
"context"
"encoding/csv"
"fmt"
"io"
Expand All @@ -20,7 +19,6 @@ import (
"github.com/urfave/cli/v2"

"github.com/filecoin-project/lotus/chain/stmgr"
lcli "github.com/filecoin-project/lotus/cli"
)

var extractManyFlags struct {
Expand All @@ -45,6 +43,8 @@ var extractManyCmd = &cli.Command{
after these compulsory seven.
`,
Action: runExtractMany,
Before: initialize,
After: destroy,
Flags: []cli.Flag{
&repoFlag,
&cli.StringFlag{
Expand Down Expand Up @@ -77,15 +77,6 @@ func runExtractMany(c *cli.Context) error {
// to the blockstore) worked.
_ = os.Setenv("LOTUS_DISABLE_VM_BUF", "iknowitsabadidea")

ctx := context.Background()

// Make the API client.
fapi, closer, err := lcli.GetFullNodeAPI(c)
if err != nil {
return err
}
defer closer()

var (
in = extractManyFlags.in
outdir = extractManyFlags.outdir
Expand Down Expand Up @@ -198,8 +189,8 @@ func runExtractMany(c *cli.Context) error {
precursor: PrecursorSelectSender,
}

if err := doExtract(ctx, fapi, opts); err != nil {
log.Println(color.RedString("failed to extract vector for message %s: %s; queuing for 'canonical' precursor selection", mcid, err))
if err := doExtract(opts); err != nil {
log.Println(color.RedString("failed to extract vector for message %s: %s; queuing for 'all' precursor selection", mcid, err))
retry = append(retry, opts)
continue
}
Expand All @@ -215,7 +206,7 @@ func runExtractMany(c *cli.Context) error {
log.Printf("retrying %s: %s", r.cid, r.id)

r.precursor = PrecursorSelectAll
if err := doExtract(ctx, fapi, r); err != nil {
if err := doExtract(r); err != nil {
merr = multierror.Append(merr, fmt.Errorf("failed to extract vector for message %s: %w", r.cid, err))
continue
}
Expand Down
46 changes: 45 additions & 1 deletion cmd/tvx/main.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
package main

import (
"fmt"
"log"
"os"
"sort"

"github.com/filecoin-project/go-jsonrpc"
"github.com/urfave/cli/v2"

"github.com/filecoin-project/lotus/api"
lcli "github.com/filecoin-project/lotus/cli"
)

// FullAPI is a JSON-RPC client targeting a full node. It's initialized in a
// cli.BeforeFunc.
var FullAPI api.FullNode

// Closer is the closer for the JSON-RPC client, which must be called on
// cli.AfterFunc.
var Closer jsonrpc.ClientCloser

// DefaultLotusRepoPath is where the fallback path where to look for a Lotus
// client repo. It is expanded with mitchellh/go-homedir, so it'll work with all
// OSes despite the Unix twiddle notation.
Expand All @@ -23,7 +36,7 @@ var repoFlag = cli.StringFlag{
func main() {
app := &cli.App{
Name: "tvx",
Description: `tvx is a tool for extracting and executing test vectors. It has three subcommands.
Description: `tvx is a tool for extracting and executing test vectors. It has four subcommands.
tvx extract extracts a test vector from a live network. It requires access to
a Filecoin client that exposes the standard JSON-RPC API endpoint. Only
Expand All @@ -35,6 +48,10 @@ func main() {
tvx extract-many performs a batch extraction of many messages, supplied in a
CSV file. Refer to the help of that subcommand for more info.
tvx simulate takes a raw message and simulates it on top of the supplied
epoch, reporting the result on stderr and writing a test vector on stdout
or into the specified file.
SETTING THE JSON-RPC API ENDPOINT
You can set the JSON-RPC API endpoint through one of the following methods.
Expand All @@ -57,6 +74,7 @@ func main() {
extractCmd,
execCmd,
extractManyCmd,
simulateCmd,
},
}

Expand All @@ -69,3 +87,29 @@ func main() {
log.Fatal(err)
}
}

func initialize(c *cli.Context) error {
// LOTUS_DISABLE_VM_BUF disables what's called "VM state tree buffering",
// which stashes write operations in a BufferedBlockstore
// (https://github.com/filecoin-project/lotus/blob/b7a4dbb07fd8332b4492313a617e3458f8003b2a/lib/bufbstore/buf_bstore.go#L21)
// such that they're not written until the VM is actually flushed.
//
// For some reason, the standard behaviour was not working for me (raulk),
// and disabling it (such that the state transformations are written immediately
// to the blockstore) worked.
_ = os.Setenv("LOTUS_DISABLE_VM_BUF", "iknowitsabadidea")

// Make the API client.
var err error
if FullAPI, Closer, err = lcli.GetFullNodeAPI(c); err != nil {
err = fmt.Errorf("failed to locate Lotus node; ")
}
return err
}

func destroy(_ *cli.Context) error {
if Closer != nil {
Closer()
}
return nil
}
Loading

0 comments on commit 91fae36

Please sign in to comment.