2020-08-05 12:20:13 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"compress/gzip"
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2020-08-06 09:43:10 +00:00
|
|
|
"log"
|
2020-08-05 12:20:13 +00:00
|
|
|
"os"
|
|
|
|
|
|
|
|
"github.com/filecoin-project/specs-actors/actors/builtin"
|
|
|
|
"github.com/ipfs/go-cid"
|
|
|
|
"github.com/urfave/cli/v2"
|
|
|
|
|
|
|
|
"github.com/filecoin-project/oni/tvx/lotus"
|
|
|
|
"github.com/filecoin-project/oni/tvx/state"
|
|
|
|
)
|
|
|
|
|
|
|
|
var extractMsgFlags struct {
|
|
|
|
cid string
|
|
|
|
file string
|
|
|
|
}
|
|
|
|
|
|
|
|
var extractMsgCmd = &cli.Command{
|
|
|
|
Name: "extract-message",
|
|
|
|
Description: "generate a message-class test vector by extracting it from a network",
|
|
|
|
Action: runExtractMsg,
|
|
|
|
Flags: []cli.Flag{
|
|
|
|
&apiFlag,
|
|
|
|
&cli.StringFlag{
|
|
|
|
Name: "cid",
|
|
|
|
Usage: "message CID to generate test vector from",
|
|
|
|
Required: true,
|
|
|
|
Destination: &extractMsgFlags.cid,
|
|
|
|
},
|
|
|
|
&cli.StringFlag{
|
|
|
|
Name: "file",
|
|
|
|
Usage: "output file",
|
|
|
|
Required: true,
|
|
|
|
Destination: &extractMsgFlags.file,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
func runExtractMsg(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")
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
// get the output file.
|
|
|
|
if extractMsgFlags.file == "" {
|
|
|
|
return fmt.Errorf("output file required")
|
|
|
|
}
|
|
|
|
|
|
|
|
mid, err := cid.Decode(extractMsgFlags.cid)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make the client.
|
|
|
|
api, err := makeClient(c)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// locate the message.
|
|
|
|
msgInfo, err := api.StateSearchMsg(ctx, mid)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to locate message: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract the serialized message.
|
|
|
|
msg, err := api.ChainGetMessage(ctx, mid)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// create a read through store that uses ChainGetObject to fetch unknown CIDs.
|
|
|
|
pst := state.NewProxyingStore(ctx, api)
|
|
|
|
|
|
|
|
g := state.NewSurgeon(ctx, api, pst)
|
|
|
|
|
|
|
|
// Get actors accessed by message.
|
|
|
|
retain, err := g.GetAccessedActors(ctx, api, mid)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
retain = append(retain, builtin.RewardActorAddr)
|
|
|
|
|
|
|
|
fmt.Println("accessed actors:")
|
|
|
|
for _, k := range retain {
|
|
|
|
fmt.Println("\t", k.String())
|
|
|
|
}
|
|
|
|
|
2020-08-06 09:43:10 +00:00
|
|
|
// get the tipset on which this message was "executed".
|
|
|
|
// https://github.com/filecoin-project/lotus/issues/2847
|
|
|
|
execTs, err := api.ChainGetTipSet(ctx, msgInfo.TipSet)
|
2020-08-05 12:20:13 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-08-06 09:43:10 +00:00
|
|
|
// get the previous tipset, on which this message was mined.
|
|
|
|
includedTs, err := api.ChainGetTipSet(ctx, execTs.Parents())
|
2020-08-05 12:20:13 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-08-06 09:43:10 +00:00
|
|
|
// Warn if the block contains more messages from this sender, preceding the
|
|
|
|
// extracted message.
|
|
|
|
//
|
|
|
|
// TODO https://github.com/filecoin-project/oni/issues/195
|
|
|
|
for _, b := range includedTs.Blocks() {
|
|
|
|
messages, err := api.ChainGetBlockMessages(ctx, b.Cid())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, other := range messages.BlsMessages {
|
|
|
|
if other.Cid() == mid {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if other.From == msg.From && other.Nonce < msg.Nonce {
|
|
|
|
log.Printf("WARN: tipset includes preceding message with lower nonce from same sender; this extraction won't work")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, m := range messages.SecpkMessages {
|
|
|
|
if m.Message.Cid() == mid {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if m.Message.From == msg.From && m.Message.Nonce < msg.Nonce {
|
|
|
|
log.Printf("WARN: tipset includes preceding message with lower nonce from same sender; this extraction won't work")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-05 12:20:13 +00:00
|
|
|
fmt.Println("getting the _before_ filtered state tree")
|
2020-08-06 09:43:10 +00:00
|
|
|
preroot, err := g.GetMaskedStateTree(includedTs.Parents(), retain)
|
2020-08-05 12:20:13 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
driver := lotus.NewDriver(ctx)
|
|
|
|
|
2020-08-06 09:43:10 +00:00
|
|
|
_, postroot, err := driver.ExecuteMessage(msg, preroot, pst.Blockstore, execTs.Height())
|
2020-08-05 12:20:13 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to execute message: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
msgBytes, err := msg.Serialize()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-08-05 14:52:57 +00:00
|
|
|
out := new(bytes.Buffer)
|
|
|
|
gw := gzip.NewWriter(out)
|
|
|
|
if err := g.WriteCAR(gw, preroot, postroot); err != nil {
|
2020-08-05 12:20:13 +00:00
|
|
|
return err
|
|
|
|
}
|
2020-08-05 14:52:57 +00:00
|
|
|
if err = gw.Flush(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err = gw.Close(); err != nil {
|
2020-08-05 12:20:13 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
version, err := api.Version(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write out the test vector.
|
|
|
|
vector := TestVector{
|
|
|
|
Class: ClassMessage,
|
|
|
|
Selector: "",
|
|
|
|
Meta: &Metadata{
|
|
|
|
ID: "TK",
|
|
|
|
Version: "TK",
|
|
|
|
Gen: GenerationData{
|
|
|
|
Source: "TK",
|
|
|
|
Version: version.String(),
|
|
|
|
},
|
|
|
|
},
|
2020-08-05 14:52:57 +00:00
|
|
|
CAR: out.Bytes(),
|
2020-08-05 12:20:13 +00:00
|
|
|
Pre: &Preconditions{
|
2020-08-06 09:43:10 +00:00
|
|
|
Epoch: execTs.Height(),
|
2020-08-05 12:20:13 +00:00
|
|
|
StateTree: &StateTree{
|
2020-08-05 14:52:57 +00:00
|
|
|
RootCID: preroot,
|
2020-08-05 12:20:13 +00:00
|
|
|
},
|
|
|
|
},
|
2020-08-06 16:57:15 +00:00
|
|
|
ApplyMessages: []Message{{Bytes: msgBytes}},
|
2020-08-05 12:20:13 +00:00
|
|
|
Post: &Postconditions{
|
|
|
|
StateTree: &StateTree{
|
2020-08-05 14:52:57 +00:00
|
|
|
RootCID: postroot,
|
2020-08-05 12:20:13 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
file, err := os.Create(extractMsgFlags.file)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
enc := json.NewEncoder(file)
|
|
|
|
enc.SetIndent("", " ")
|
|
|
|
if err := enc.Encode(&vector); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|