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

tvx simulate command; tvx extract --ignore-sanity-checks #4554

Merged
merged 3 commits into from
Oct 23, 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Comment on lines +16 to +22
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are now global vars in the main package initialized by the initialize cli.BeforeFunc.


// 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