unified tvx
tool; end-to-end MVP for extracting+running msg-class vectors (#177)
Co-authored-by: Will Scott <will@cypherpunk.email> Co-authored-by: Anton Evangelatov <anton.evangelatov@gmail.com>
This commit is contained in:
parent
62a9982b76
commit
b7e3b4ff77
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,4 +1,4 @@
|
||||
lotus
|
||||
venv/
|
||||
__pycache__/
|
||||
.ipynb_checkpoints/
|
||||
tvx/tvx
|
||||
|
149
corpus/schema.json
Normal file
149
corpus/schema.json
Normal file
@ -0,0 +1,149 @@
|
||||
{
|
||||
"$id": "https://filecoin.io/oni/schemas/test-vector.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "a filecoin VM test vector",
|
||||
"type": "object",
|
||||
"definitions": {
|
||||
"hex": {
|
||||
"title": "hex value",
|
||||
"description": "a hex value prefixed with 0x, and accepting only lowercase characters; 0x represents an empty byte array",
|
||||
"type": "string",
|
||||
"pattern": "0x[0-9a-f]*",
|
||||
"examples": [
|
||||
"0xa1b2c3",
|
||||
"0x"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"title": "metadata",
|
||||
"description": "metadata about this test vector, such as its id, version, data about its generation, etc.",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"type": "string"
|
||||
},
|
||||
"gen": {
|
||||
"title": "generation metadata",
|
||||
"description": "metadata about how this test vector was generated",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"source": {
|
||||
"type": "string",
|
||||
"examples": [
|
||||
"lotus",
|
||||
"dsl"
|
||||
]
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"examples": [
|
||||
"0.4.1+git.27d74337+api0.8.1"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"preconditions": {
|
||||
"title": "execution preconditions",
|
||||
"description": "preconditions that need to be applied and satisfied before this test vector can be executed",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"state_tree": {
|
||||
"title": "state tree to seed",
|
||||
"description": "state tree to seed before applying this test vector; mapping of actor addresses => serialized state",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/hex"
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"t01": "0x0123456789abcdef",
|
||||
"t02": "0x0123456789abcdef",
|
||||
"t03": "0x0123456789abcdef"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"postconditions": {
|
||||
"title": "execution preconditions",
|
||||
"description": "postconditions that need to be satisfied after execution for this test vector to pass",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"state_tree": {
|
||||
"title": "state tree postconditions",
|
||||
"description": "state tree postconditions that must be true for this test vector to pass",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"car_bytes": {
|
||||
"title": "the hex-encoded CAR containing the full state tree",
|
||||
"description": "the hex-encoded CAR containing the full state tree, for debugging/diffing purposes",
|
||||
"$ref": "#/definitions/hex"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"apply_message": {
|
||||
"title": "message to apply, hex-encoded",
|
||||
"$ref": "#/definitions/hex"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"class"
|
||||
],
|
||||
"properties": {
|
||||
"class": {
|
||||
"title": "test vector class",
|
||||
"description": "test vector class; depending on the value, the apply_* property to provide (and its schema) will vary; the relevant apply property is apply_[class]",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"message",
|
||||
"block",
|
||||
"tipset",
|
||||
"chain"
|
||||
]
|
||||
},
|
||||
"selector": {
|
||||
"title": "selector the driver can use to determine if this test vector applies",
|
||||
"description": "format TBD",
|
||||
"type": "string"
|
||||
},
|
||||
"_meta": {
|
||||
"$ref": "#/definitions/meta"
|
||||
},
|
||||
"preconditions": {
|
||||
"$ref": "#/definitions/preconditions"
|
||||
},
|
||||
"postconditions": {
|
||||
"$ref": "#/definitions/postconditions"
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"class": {
|
||||
"const": "message"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"required": ["apply_message"],
|
||||
"properties": {
|
||||
"apply_message": {
|
||||
"$ref": "#/definitions/apply_message"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
29
corpus/single_message.json
Normal file
29
corpus/single_message.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"class": "message",
|
||||
"selector": "<some predicate for the driver to determine if this vector applies to the implementation, e.g. to deal with protocol upgrades>",
|
||||
|
||||
"_meta": {
|
||||
"id": "test_vector_id",
|
||||
"version": "version_id",
|
||||
"gen": {
|
||||
"source": "lotus",
|
||||
"version": "0.4.1+git.27d74337+api0.8.1"
|
||||
}
|
||||
},
|
||||
|
||||
"preconditions": {
|
||||
"state_tree": {
|
||||
"t01": "0x0123456789abcdef",
|
||||
"t02": "0x0123456789abcdef",
|
||||
"t03": "0x0123456789abcdef"
|
||||
}
|
||||
},
|
||||
|
||||
"apply_message": "0x89004300ba20583103b85358cc1c968d826c0a0efcde99b6e4db138edfc051f188a4471cfc23ccf7884adbbac912e45f7363774bf16f83ca8f1902b44800505e1442e66b744200011a09d225c80758c68219016f58c099a447a8c80988e1c061dd3a8dd674033c3a6083c4b55cf035e6e8de3baf09436ad00adf6eb14d58ae6691d22ce82255874047e7aa0b9a86bbf424afc6d89dc29f7b253dd04c88a625b86345f05aa0bdee503971c1e9735ed5b81c31ad41a1f10b387d96151d1cbd0c4286267daf04087fd8d5272f870994c3732f3fc4f54287a485d6c8dafb6056df3fc9233f849027b4c1272850435126fef65a8363768a858c0cc5f9d9114ff378ad50cab25ab0a2f685ca63d9f1f7727da4d06db5b80b5f",
|
||||
|
||||
"postconditions": {
|
||||
"state_tree": {
|
||||
"car_bytes": "0x890043009920583103b5b1fab6769fcb464146d192af785915d8e1dacff264b2be3bcb7bf4064a77cb3b13e27063ea4825dc48cd0a7df77e211916e748002740f0c493a2824200011a09d9c4dc0758c68219091c58c083e7c59e748e070b382a813f4ffe8ead2a090469aaa37f0664b82c092bc78b43fac300335cd2d6e4a8c71ce138003c0fa8ee1707b41aa87f35c811613d59ac53449aef8263aa51c876f2e67c185118ec9628f73442f58e093909ce952e431e770ac693cdbe99f2f9404ad3ca754196ab443c6c66afa41310e705e453496bdcedf0f271529dc7374021d7b80d343397f996d06bf11e753a49eaed7b3ce80a8e9bbaa48bf83223527bd8a2933f002949fdb8058103336a4a979375d5e963cdd7b1"
|
||||
}
|
||||
}
|
||||
}
|
112
tvx/examine.go
Normal file
112
tvx/examine.go
Normal file
@ -0,0 +1,112 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin"
|
||||
init_ "github.com/filecoin-project/specs-actors/actors/builtin/init"
|
||||
"github.com/filecoin-project/specs-actors/actors/util/adt"
|
||||
|
||||
"github.com/filecoin-project/oni/tvx/state"
|
||||
)
|
||||
|
||||
func trimQuotes(s string) string {
|
||||
if len(s) >= 2 {
|
||||
if s[0] == '"' && s[len(s)-1] == '"' {
|
||||
return s[1 : len(s)-1]
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
var examineFlags struct {
|
||||
file string
|
||||
pre bool
|
||||
post bool
|
||||
}
|
||||
|
||||
var examineCmd = &cli.Command{
|
||||
Name: "examine",
|
||||
Description: "examine an exported state root as represented in a test vector",
|
||||
Action: runExamineCmd,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "file",
|
||||
Usage: "test vector file",
|
||||
Required: true,
|
||||
Destination: &examineFlags.file,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "pre",
|
||||
Usage: "examine the precondition state tree",
|
||||
Destination: &examineFlags.pre,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "post",
|
||||
Usage: "examine the postcondition state tree",
|
||||
Destination: &examineFlags.post,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func runExamineCmd(_ *cli.Context) error {
|
||||
file, err := os.Open(examineFlags.file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var tv TestVector
|
||||
if err := json.NewDecoder(file).Decode(&tv); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
examine := func(encoded []byte) error {
|
||||
tree, err := state.RecoverStateTree(context.TODO(), encoded)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
initActor, err := tree.GetActor(builtin.InitActorAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot recover init actor: %w", err)
|
||||
}
|
||||
|
||||
var ias init_.State
|
||||
if err := tree.Store.Get(context.TODO(), initActor.Head, &ias); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
adtStore := adt.WrapStore(context.TODO(), tree.Store)
|
||||
m, err := adt.AsMap(adtStore, ias.AddressMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
actors, err := m.CollectKeys()
|
||||
for _, actor := range actors {
|
||||
fmt.Printf("%s\n", actor)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if examineFlags.pre {
|
||||
log.Print("examining precondition tree")
|
||||
if err := examine(tv.Pre.StateTree.CAR); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if examineFlags.post {
|
||||
log.Print("examining postcondition tree")
|
||||
if err := examine(tv.Post.StateTree.CAR); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
96
tvx/exec_lotus.go
Normal file
96
tvx/exec_lotus.go
Normal file
@ -0,0 +1,96 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/lib/blockstore"
|
||||
"github.com/ipld/go-car"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/filecoin-project/oni/tvx/lotus"
|
||||
)
|
||||
|
||||
var execLotusFlags struct {
|
||||
file string
|
||||
}
|
||||
|
||||
var execLotusCmd = &cli.Command{
|
||||
Name: "exec-lotus",
|
||||
Description: "execute a test vector against Lotus",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "file",
|
||||
Usage: "input file",
|
||||
Required: true,
|
||||
Destination: &execLotusFlags.file,
|
||||
},
|
||||
},
|
||||
Action: runExecLotus,
|
||||
}
|
||||
|
||||
func runExecLotus(_ *cli.Context) error {
|
||||
if execLotusFlags.file == "" {
|
||||
return fmt.Errorf("test vector file cannot be empty")
|
||||
}
|
||||
|
||||
file, err := os.Open(execLotusFlags.file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open test vector: %w", err)
|
||||
}
|
||||
|
||||
var (
|
||||
dec = json.NewDecoder(file)
|
||||
tv TestVector
|
||||
)
|
||||
|
||||
if err = dec.Decode(&tv); err != nil {
|
||||
return fmt.Errorf("failed to decode test vector: %w", err)
|
||||
}
|
||||
|
||||
switch tv.Class {
|
||||
case "message":
|
||||
var (
|
||||
ctx = context.Background()
|
||||
epoch = tv.Pre.Epoch
|
||||
)
|
||||
|
||||
bs := blockstore.NewTemporary()
|
||||
|
||||
buf := bytes.NewReader(tv.Pre.StateTree.CAR)
|
||||
gr, err := gzip.NewReader(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer gr.Close()
|
||||
|
||||
header, err := car.LoadCar(bs, gr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load state tree car from test vector: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("roots: ", header.Roots)
|
||||
|
||||
fmt.Println("decoding message")
|
||||
msg, err := types.DecodeMessage(tv.ApplyMessage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
driver := lotus.NewDriver(ctx)
|
||||
|
||||
fmt.Println("executing message")
|
||||
spew.Dump(driver.ExecuteMessage(msg, header.Roots[0], bs, epoch))
|
||||
|
||||
return nil
|
||||
|
||||
default:
|
||||
return fmt.Errorf("test vector class not supported")
|
||||
}
|
||||
}
|
204
tvx/extract_msg.go
Normal file
204
tvx/extract_msg.go
Normal file
@ -0,0 +1,204 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"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())
|
||||
}
|
||||
|
||||
// get the tipset on which this message was mined.
|
||||
ts, err := api.ChainGetTipSet(ctx, msgInfo.TipSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get the previous tipset, on top of which the message was executed.
|
||||
prevTs, err := api.ChainGetTipSet(ctx, ts.Parents())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("getting the _before_ filtered state tree")
|
||||
preroot, err := g.GetMaskedStateTree(prevTs.Parents(), retain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
driver := lotus.NewDriver(ctx)
|
||||
|
||||
_, postroot, err := driver.ExecuteMessage(msg, preroot, pst.Blockstore, ts.Height())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute message: %w", err)
|
||||
}
|
||||
|
||||
msgBytes, err := msg.Serialize()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
getZippedCAR := func(root cid.Cid) ([]byte, error) {
|
||||
out := new(bytes.Buffer)
|
||||
gw := gzip.NewWriter(out)
|
||||
if err := g.WriteCAR(gw, root); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = gw.Flush(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = gw.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return out.Bytes(), nil
|
||||
}
|
||||
|
||||
pretree, err := getZippedCAR(preroot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
posttree, err := getZippedCAR(postroot)
|
||||
if err != nil {
|
||||
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(),
|
||||
},
|
||||
},
|
||||
Pre: &Preconditions{
|
||||
Epoch: ts.Height(),
|
||||
StateTree: &StateTree{
|
||||
CAR: pretree,
|
||||
},
|
||||
},
|
||||
ApplyMessage: msgBytes,
|
||||
Post: &Postconditions{
|
||||
StateTree: &StateTree{
|
||||
CAR: posttree,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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
|
||||
}
|
29
tvx/go.mod
Normal file
29
tvx/go.mod
Normal file
@ -0,0 +1,29 @@
|
||||
module github.com/filecoin-project/oni/tvx
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/filecoin-project/go-address v0.0.2-0.20200504173055-8b6f2fb2b3ef
|
||||
github.com/filecoin-project/lotus v0.4.3-0.20200801235920-43491cb7edfd
|
||||
github.com/filecoin-project/sector-storage v0.0.0-20200730203805-7153e1dd05b5
|
||||
github.com/filecoin-project/specs-actors v0.8.6
|
||||
github.com/ipfs/go-block-format v0.0.2
|
||||
github.com/ipfs/go-blockservice v0.1.4-0.20200624145336-a978cec6e834
|
||||
github.com/ipfs/go-cid v0.0.7
|
||||
github.com/ipfs/go-datastore v0.4.4
|
||||
github.com/ipfs/go-hamt-ipld v0.1.1
|
||||
github.com/ipfs/go-ipfs-exchange-interface v0.0.1
|
||||
github.com/ipfs/go-ipfs-exchange-offline v0.0.1
|
||||
github.com/ipfs/go-ipld-cbor v0.0.5-0.20200428170625-a0bd04d3cbdf
|
||||
github.com/ipfs/go-ipld-format v0.2.0
|
||||
github.com/ipfs/go-merkledag v0.3.1
|
||||
github.com/ipld/go-car v0.1.1-0.20200526133713-1c7508d55aae
|
||||
github.com/multiformats/go-multiaddr v0.2.2
|
||||
github.com/multiformats/go-multiaddr-net v0.1.5
|
||||
github.com/multiformats/go-multihash v0.0.14
|
||||
github.com/urfave/cli/v2 v2.2.0
|
||||
github.com/whyrusleeping/cbor-gen v0.0.0-20200723185710-6a3894a6352b
|
||||
)
|
||||
|
||||
replace github.com/filecoin-project/filecoin-ffi => ../extra/filecoin-ffi
|
1794
tvx/go.sum
Normal file
1794
tvx/go.sum
Normal file
File diff suppressed because it is too large
Load Diff
58
tvx/list_accessed.go
Normal file
58
tvx/list_accessed.go
Normal file
@ -0,0 +1,58 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/filecoin-project/oni/tvx/state"
|
||||
)
|
||||
|
||||
var listAccessedFlags struct {
|
||||
cid string
|
||||
}
|
||||
|
||||
var listAccessedCmd = &cli.Command{
|
||||
Name: "list-accessed",
|
||||
Description: "extract actors accessed during the execution of a message",
|
||||
Action: runListAccessed,
|
||||
Flags: []cli.Flag{
|
||||
&apiFlag,
|
||||
&cli.StringFlag{
|
||||
Name: "cid",
|
||||
Usage: "message CID",
|
||||
Required: true,
|
||||
Destination: &listAccessedFlags.cid,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func runListAccessed(c *cli.Context) error {
|
||||
ctx := context.Background()
|
||||
|
||||
node, err := makeClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mid, err := cid.Decode(listAccessedFlags.cid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rtst := state.NewProxyingStore(ctx, node)
|
||||
|
||||
sg := state.NewSurgeon(ctx, node, rtst)
|
||||
|
||||
actors, err := sg.GetAccessedActors(context.TODO(), node, mid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for k := range actors {
|
||||
fmt.Printf("%v\n", k)
|
||||
}
|
||||
return nil
|
||||
}
|
57
tvx/lotus/driver.go
Normal file
57
tvx/lotus/driver.go
Normal file
@ -0,0 +1,57 @@
|
||||
package lotus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/state"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/chain/vm"
|
||||
"github.com/filecoin-project/lotus/lib/blockstore"
|
||||
"github.com/filecoin-project/sector-storage/ffiwrapper"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/ipfs/go-cid"
|
||||
cbor "github.com/ipfs/go-ipld-cbor"
|
||||
)
|
||||
|
||||
type Driver struct {
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func NewDriver(ctx context.Context) *Driver {
|
||||
return &Driver{ctx: ctx}
|
||||
}
|
||||
|
||||
func (d *Driver) ExecuteMessage(msg *types.Message, preroot cid.Cid, bs blockstore.Blockstore, epoch abi.ChainEpoch) (*vm.ApplyRet, cid.Cid, error) {
|
||||
fmt.Println("execution sanity check")
|
||||
cst := cbor.NewCborStore(bs)
|
||||
st, err := state.LoadStateTree(cst, preroot)
|
||||
if err != nil {
|
||||
return nil, cid.Undef, err
|
||||
}
|
||||
|
||||
actor, err := st.GetActor(msg.From)
|
||||
if err != nil {
|
||||
return nil, cid.Undef, err
|
||||
}
|
||||
|
||||
fmt.Println("from actor found: ", actor)
|
||||
|
||||
fmt.Println("creating vm")
|
||||
lvm, err := vm.NewVM(preroot, epoch, &vmRand{}, bs, mkFakedSigSyscalls(vm.Syscalls(ffiwrapper.ProofVerifier)), nil)
|
||||
if err != nil {
|
||||
return nil, cid.Undef, err
|
||||
}
|
||||
|
||||
fmt.Println("applying message")
|
||||
ret, err := lvm.ApplyMessage(d.ctx, msg)
|
||||
if err != nil {
|
||||
return nil, cid.Undef, err
|
||||
}
|
||||
|
||||
fmt.Printf("applied message: %+v\n", ret)
|
||||
|
||||
fmt.Println("flushing")
|
||||
root, err := lvm.Flush(d.ctx)
|
||||
return ret, root, err
|
||||
}
|
38
tvx/lotus/stubs.go
Normal file
38
tvx/lotus/stubs.go
Normal file
@ -0,0 +1,38 @@
|
||||
package lotus
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/lotus/chain/state"
|
||||
"github.com/filecoin-project/lotus/chain/vm"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/crypto"
|
||||
"github.com/filecoin-project/specs-actors/actors/runtime"
|
||||
|
||||
cbor "github.com/ipfs/go-ipld-cbor"
|
||||
)
|
||||
|
||||
type vmRand struct {
|
||||
}
|
||||
|
||||
func (*vmRand) GetRandomness(ctx context.Context, dst crypto.DomainSeparationTag, h abi.ChainEpoch, input []byte) ([]byte, error) {
|
||||
return []byte("i_am_random"), nil
|
||||
}
|
||||
|
||||
type fakedSigSyscalls struct {
|
||||
runtime.Syscalls
|
||||
}
|
||||
|
||||
func (fss *fakedSigSyscalls) VerifySignature(_ crypto.Signature, _ address.Address, plaintext []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func mkFakedSigSyscalls(base vm.SyscallBuilder) vm.SyscallBuilder {
|
||||
return func(ctx context.Context, cstate *state.StateTree, cst cbor.IpldStore) runtime.Syscalls {
|
||||
return &fakedSigSyscalls{
|
||||
base(ctx, cstate, cst),
|
||||
}
|
||||
}
|
||||
}
|
80
tvx/main.go
Normal file
80
tvx/main.go
Normal file
@ -0,0 +1,80 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/api/client"
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
manet "github.com/multiformats/go-multiaddr-net"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var apiFlag = cli.StringFlag{
|
||||
Name: "api",
|
||||
Usage: "api endpoint, formatted as token:multiaddr",
|
||||
Value: "",
|
||||
EnvVars: []string{"FULLNODE_API_INFO"},
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := &cli.App{
|
||||
Name: "tvx",
|
||||
Description: "a toolbox for managing test vectors",
|
||||
Usage: "a toolbox for managing test vectors",
|
||||
Commands: []*cli.Command{
|
||||
deltaCmd,
|
||||
listAccessedCmd,
|
||||
extractMsgCmd,
|
||||
execLotusCmd,
|
||||
examineCmd,
|
||||
},
|
||||
}
|
||||
|
||||
sort.Sort(cli.CommandsByName(app.Commands))
|
||||
for _, c := range app.Commands {
|
||||
sort.Sort(cli.FlagsByName(c.Flags))
|
||||
}
|
||||
|
||||
err := app.Run(os.Args)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func makeClient(c *cli.Context) (api.FullNode, error) {
|
||||
api := c.String(apiFlag.Name)
|
||||
sp := strings.SplitN(api, ":", 2)
|
||||
if len(sp) != 2 {
|
||||
return nil, fmt.Errorf("invalid api value, missing token or address: %s", api)
|
||||
}
|
||||
|
||||
// TODO: discovery from filesystem
|
||||
token := sp[0]
|
||||
ma, err := multiaddr.NewMultiaddr(sp[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse provided multiaddr: %w", err)
|
||||
}
|
||||
|
||||
_, dialAddr, err := manet.DialArgs(ma)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid api multiAddr: %w", err)
|
||||
}
|
||||
|
||||
addr := "ws://" + dialAddr + "/rpc/v0"
|
||||
headers := http.Header{}
|
||||
if len(token) != 0 {
|
||||
headers.Add("Authorization", "Bearer "+token)
|
||||
}
|
||||
|
||||
node, _, err := client.NewFullNodeRPC(addr, headers)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not connect to api: %w", err)
|
||||
}
|
||||
return node, nil
|
||||
}
|
90
tvx/schema.go
Normal file
90
tvx/schema.go
Normal file
@ -0,0 +1,90 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
)
|
||||
|
||||
// Class represents the type of test this instance is.
|
||||
type Class string
|
||||
|
||||
var (
|
||||
// ClassMessage tests the VM transition over a single message
|
||||
ClassMessage Class = "message"
|
||||
// ClassBlock tests the VM transition over a block of messages
|
||||
ClassBlock Class = "block"
|
||||
// ClassTipset tests the VM transition on a tipset update
|
||||
ClassTipset Class = "tipset"
|
||||
// ClassChain tests the VM transition across a chain segment
|
||||
ClassChain Class = "chain"
|
||||
)
|
||||
|
||||
// Selector provides a filter to indicate what implementations this test is relevant for
|
||||
type Selector string
|
||||
|
||||
// Metadata provides information on the generation of this test case
|
||||
type Metadata struct {
|
||||
ID string `json:"id"`
|
||||
Version string `json:"version"`
|
||||
Gen GenerationData `json:"gen"`
|
||||
}
|
||||
|
||||
// GenerationData tags the source of this test case
|
||||
type GenerationData struct {
|
||||
Source string `json:"source"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// StateTree represents a state tree within preconditions and postconditions.
|
||||
type StateTree struct {
|
||||
// CAR is the car representation of a state tree
|
||||
CAR HexEncodedBytes `json:"car_hex"`
|
||||
}
|
||||
|
||||
// HexEncodedBytes is a hex-encoded binary value.
|
||||
//
|
||||
// TODO may switch to base64 or base85 for efficiency.
|
||||
type HexEncodedBytes []byte
|
||||
|
||||
// Preconditions contain a representation of VM state at the beginning of the test
|
||||
type Preconditions struct {
|
||||
Epoch abi.ChainEpoch `json:"epoch"`
|
||||
StateTree *StateTree `json:"state_tree"`
|
||||
}
|
||||
|
||||
// Postconditions contain a representation of VM state at th end of the test
|
||||
type Postconditions struct {
|
||||
StateTree *StateTree `json:"state_tree"`
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshal for HexEncodedBytes
|
||||
func (heb HexEncodedBytes) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(hex.EncodeToString(heb))
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshal for HexEncodedBytes
|
||||
func (heb *HexEncodedBytes) UnmarshalJSON(v []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(v, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bytes, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*heb = bytes
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestVector is a single test case
|
||||
type TestVector struct {
|
||||
Class `json:"class"`
|
||||
Selector `json:"selector"`
|
||||
Meta *Metadata `json:"_meta"`
|
||||
Pre *Preconditions `json:"preconditions"`
|
||||
ApplyMessage HexEncodedBytes `json:"apply_message"`
|
||||
Post *Postconditions `json:"postconditions"`
|
||||
}
|
55
tvx/state/serialize.go
Normal file
55
tvx/state/serialize.go
Normal file
@ -0,0 +1,55 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/state"
|
||||
bs "github.com/filecoin-project/lotus/lib/blockstore"
|
||||
"github.com/ipfs/go-hamt-ipld"
|
||||
cbor "github.com/ipfs/go-ipld-cbor"
|
||||
"github.com/ipfs/go-ipld-format"
|
||||
"github.com/ipld/go-car"
|
||||
)
|
||||
|
||||
// RecoverStateTree parses a car encoding of a state tree back to a structured format
|
||||
func RecoverStateTree(ctx context.Context, raw []byte) (*state.StateTree, error) {
|
||||
buf := bytes.NewBuffer(raw)
|
||||
store := bs.NewTemporary()
|
||||
gr, err := gzip.NewReader(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer gr.Close()
|
||||
|
||||
ch, err := car.LoadCar(store, gr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ch.Roots) != 1 {
|
||||
return nil, fmt.Errorf("car should have 1 root, has %d", len(ch.Roots))
|
||||
}
|
||||
cborstore := cbor.NewCborStore(store)
|
||||
|
||||
fmt.Printf("root is %s\n", ch.Roots[0])
|
||||
|
||||
nd, err := hamt.LoadNode(ctx, cborstore, ch.Roots[0], hamt.UseTreeBitWidth(5))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := nd.ForEach(ctx, func(k string, val interface{}) error {
|
||||
n, ok := val.(format.Node)
|
||||
if !ok {
|
||||
fmt.Printf("hampt %s (not node): %+v\n", k, val)
|
||||
} else {
|
||||
fmt.Printf("%s: %#v\n", k, n)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return state.LoadStateTree(cborstore, ch.Roots[0])
|
||||
}
|
93
tvx/state/store.go
Normal file
93
tvx/state/store.go
Normal file
@ -0,0 +1,93 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/lib/blockstore"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/util/adt"
|
||||
|
||||
blocks "github.com/ipfs/go-block-format"
|
||||
"github.com/ipfs/go-blockservice"
|
||||
"github.com/ipfs/go-cid"
|
||||
ds "github.com/ipfs/go-datastore"
|
||||
exchange "github.com/ipfs/go-ipfs-exchange-interface"
|
||||
offline "github.com/ipfs/go-ipfs-exchange-offline"
|
||||
cbor "github.com/ipfs/go-ipld-cbor"
|
||||
format "github.com/ipfs/go-ipld-format"
|
||||
"github.com/ipfs/go-merkledag"
|
||||
)
|
||||
|
||||
// ProxyingStores implements the ipld store where unknown items are fetched over the node API.
|
||||
type ProxyingStores struct {
|
||||
CBORStore cbor.IpldStore
|
||||
ADTStore adt.Store
|
||||
Datastore ds.Batching
|
||||
Blockstore blockstore.Blockstore
|
||||
BlockService blockservice.BlockService
|
||||
Exchange exchange.Interface
|
||||
DAGService format.DAGService
|
||||
}
|
||||
|
||||
type proxyingBlockstore struct {
|
||||
ctx context.Context
|
||||
api api.FullNode
|
||||
|
||||
blockstore.Blockstore
|
||||
}
|
||||
|
||||
func (pb *proxyingBlockstore) Get(cid cid.Cid) (blocks.Block, error) {
|
||||
if block, err := pb.Blockstore.Get(cid); err == nil {
|
||||
return block, err
|
||||
}
|
||||
|
||||
// fmt.Printf("fetching cid via rpc: %v\n", cid)
|
||||
item, err := pb.api.ChainReadObj(pb.ctx, cid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
block, err := blocks.NewBlockWithCid(item, cid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = pb.Blockstore.Put(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return block, nil
|
||||
}
|
||||
|
||||
// NewProxyingStore is a blockstore that proxies get requests for unknown CIDs
|
||||
// to a Filecoin node, via the ChainReadObj RPC.
|
||||
//
|
||||
// It also contains all possible stores, services and gadget that IPLD
|
||||
// requires (quite a handful).
|
||||
func NewProxyingStore(ctx context.Context, api api.FullNode) *ProxyingStores {
|
||||
ds := ds.NewMapDatastore()
|
||||
|
||||
bs := &proxyingBlockstore{
|
||||
ctx: ctx,
|
||||
api: api,
|
||||
Blockstore: blockstore.NewBlockstore(ds),
|
||||
}
|
||||
|
||||
var (
|
||||
cborstore = cbor.NewCborStore(bs)
|
||||
offl = offline.Exchange(bs)
|
||||
blkserv = blockservice.New(bs, offl)
|
||||
dserv = merkledag.NewDAGService(blkserv)
|
||||
)
|
||||
|
||||
return &ProxyingStores{
|
||||
CBORStore: cborstore,
|
||||
ADTStore: adt.WrapStore(ctx, cborstore),
|
||||
Datastore: ds,
|
||||
Blockstore: bs,
|
||||
Exchange: offl,
|
||||
BlockService: blkserv,
|
||||
DAGService: dserv,
|
||||
}
|
||||
}
|
283
tvx/state/surgeon.go
Normal file
283
tvx/state/surgeon.go
Normal file
@ -0,0 +1,283 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/chain/vm"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin"
|
||||
init_ "github.com/filecoin-project/specs-actors/actors/builtin/init"
|
||||
"github.com/filecoin-project/specs-actors/actors/util/adt"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ipfs/go-ipld-format"
|
||||
"github.com/ipld/go-car"
|
||||
cbg "github.com/whyrusleeping/cbor-gen"
|
||||
)
|
||||
|
||||
// Surgeon is an object used to fetch and manipulate state.
|
||||
type Surgeon struct {
|
||||
ctx context.Context
|
||||
api api.FullNode
|
||||
stores *ProxyingStores
|
||||
}
|
||||
|
||||
// NewSurgeon returns a state surgeon, an object used to fetch and manipulate
|
||||
// state.
|
||||
func NewSurgeon(ctx context.Context, api api.FullNode, stores *ProxyingStores) *Surgeon {
|
||||
return &Surgeon{
|
||||
ctx: ctx,
|
||||
api: api,
|
||||
stores: stores,
|
||||
}
|
||||
}
|
||||
|
||||
// GetMaskedStateTree trims the state tree at the supplied tipset to contain
|
||||
// only the state of the actors in the retain set. It also "dives" into some
|
||||
// singleton system actors, like the init actor, to trim the state so as to
|
||||
// compute a minimal state tree. In the future, thid method will dive into
|
||||
// other system actors like the power actor and the market actor.
|
||||
func (sg *Surgeon) GetMaskedStateTree(tsk types.TipSetKey, retain []address.Address) (cid.Cid, error) {
|
||||
stateMap := adt.MakeEmptyMap(sg.stores.ADTStore)
|
||||
|
||||
initState, err := sg.loadInitActor(tsk)
|
||||
if err != nil {
|
||||
return cid.Undef, err
|
||||
}
|
||||
|
||||
resolved, err := sg.resolveAddresses(retain, initState)
|
||||
if err != nil {
|
||||
return cid.Undef, err
|
||||
}
|
||||
|
||||
initState, err = sg.retainInitEntries(initState, retain)
|
||||
if err != nil {
|
||||
return cid.Undef, err
|
||||
}
|
||||
|
||||
err = sg.saveInitActor(initState, stateMap)
|
||||
if err != nil {
|
||||
return cid.Undef, err
|
||||
}
|
||||
|
||||
err = sg.pluckActorStates(tsk, resolved, stateMap)
|
||||
if err != nil {
|
||||
return cid.Undef, err
|
||||
}
|
||||
|
||||
root, err := stateMap.Root()
|
||||
if err != nil {
|
||||
return cid.Undef, err
|
||||
}
|
||||
|
||||
return root, nil
|
||||
}
|
||||
|
||||
// GetAccessedActors identifies the actors that were accessed during the
|
||||
// execution of a message.
|
||||
func (sg *Surgeon) GetAccessedActors(ctx context.Context, a api.FullNode, mid cid.Cid) ([]address.Address, error) {
|
||||
log.Printf("calculating accessed actors during execution of message: %s", mid)
|
||||
msgInfo, err := a.StateSearchMsg(ctx, mid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ts, err := a.ChainGetTipSet(ctx, msgInfo.TipSet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
trace, err := a.StateReplay(ctx, ts.Parents(), mid)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not replay msg: %w", err)
|
||||
}
|
||||
|
||||
accessed := make(map[address.Address]struct{})
|
||||
var recur func(trace *types.ExecutionTrace)
|
||||
recur = func(trace *types.ExecutionTrace) {
|
||||
accessed[trace.Msg.To] = struct{}{}
|
||||
accessed[trace.Msg.From] = struct{}{}
|
||||
for _, s := range trace.Subcalls {
|
||||
recur(&s)
|
||||
}
|
||||
}
|
||||
|
||||
recur(&trace.ExecutionTrace)
|
||||
|
||||
ret := make([]address.Address, 0, len(accessed))
|
||||
for k := range accessed {
|
||||
ret = append(ret, k)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// WriteCAR recursively writes the tree referenced by the root as a CAR into the
|
||||
// supplied io.Writer.
|
||||
func (sg *Surgeon) WriteCAR(w io.Writer, root cid.Cid) error {
|
||||
carWalkFn := func(nd format.Node) (out []*format.Link, err error) {
|
||||
for _, link := range nd.Links() {
|
||||
if link.Cid.Prefix().Codec == cid.FilCommitmentSealed || link.Cid.Prefix().Codec == cid.FilCommitmentUnsealed {
|
||||
continue
|
||||
}
|
||||
out = append(out, link)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
return car.WriteCarWithWalker(sg.ctx, sg.stores.DAGService, []cid.Cid{root}, w, carWalkFn)
|
||||
}
|
||||
|
||||
// pluckActorStates plucks the state from the supplied actors at the given
|
||||
// tipset, and places it into the supplied state map.
|
||||
func (sg *Surgeon) pluckActorStates(tsk types.TipSetKey, pluck []address.Address, stateMap *adt.Map) error {
|
||||
for _, a := range pluck {
|
||||
actor, err := sg.api.StateGetActor(sg.ctx, a, tsk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = stateMap.Put(adt.AddrKey(a), actor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// recursive copy of the actor state so we can
|
||||
err = vm.Copy(sg.stores.Blockstore, sg.stores.Blockstore, actor.Head)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
actorState, err := sg.api.ChainReadObj(sg.ctx, actor.Head)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cid, err := sg.stores.CBORStore.Put(sg.ctx, &cbg.Deferred{Raw: actorState})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cid != actor.Head {
|
||||
panic("mismatched cids")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// saveInitActor saves the state of the init actor to the provided state map.
|
||||
func (sg *Surgeon) saveInitActor(initState *init_.State, stateMap *adt.Map) error {
|
||||
log.Printf("saving init actor into state tree")
|
||||
|
||||
// Store the state of the init actor.
|
||||
cid, err := sg.stores.CBORStore.Put(sg.ctx, initState)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
actor := &types.Actor{
|
||||
Code: builtin.InitActorCodeID,
|
||||
Head: cid,
|
||||
}
|
||||
|
||||
err = stateMap.Put(adt.AddrKey(builtin.InitActorAddr), actor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cid, _ = stateMap.Root()
|
||||
log.Printf("saved init actor into state tree; new root: %s", cid)
|
||||
return nil
|
||||
}
|
||||
|
||||
// retainInitEntries takes an old init actor state, and retains only the
|
||||
// entries in the retain set, returning a new init actor state.
|
||||
func (sg *Surgeon) retainInitEntries(oldState *init_.State, retain []address.Address) (*init_.State, error) {
|
||||
log.Printf("retaining init actor entries for addresses: %v", retain)
|
||||
|
||||
oldAddrs, err := adt.AsMap(sg.stores.ADTStore, oldState.AddressMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newAddrs := adt.MakeEmptyMap(sg.stores.ADTStore)
|
||||
for _, r := range retain {
|
||||
if r.Protocol() == address.ID {
|
||||
// skip over ID addresses; they don't need a mapping in the init actor.
|
||||
continue
|
||||
}
|
||||
|
||||
var d cbg.Deferred
|
||||
if _, err := oldAddrs.Get(adt.AddrKey(r), &d); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := newAddrs.Put(adt.AddrKey(r), &d); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
rootCid, err := newAddrs.Root()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &init_.State{
|
||||
NetworkName: oldState.NetworkName,
|
||||
NextID: oldState.NextID,
|
||||
AddressMap: rootCid,
|
||||
}
|
||||
|
||||
log.Printf("new init actor state: %+v", s)
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// resolveAddresses resolved the requested addresses from the provided
|
||||
// InitActor state, returning a slice of length len(orig), where each index
|
||||
// contains the resolved address.
|
||||
func (sg *Surgeon) resolveAddresses(orig []address.Address, ist *init_.State) (ret []address.Address, err error) {
|
||||
log.Printf("resolving addresses: %v", orig)
|
||||
|
||||
ret = make([]address.Address, len(orig))
|
||||
for i, addr := range orig {
|
||||
resolved, err := ist.ResolveAddress(sg.stores.ADTStore, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret[i] = resolved
|
||||
}
|
||||
|
||||
log.Printf("resolved addresses: %v", ret)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// loadInitActor loads the init actor state from a given tipset.
|
||||
func (sg *Surgeon) loadInitActor(tsk types.TipSetKey) (initState *init_.State, err error) {
|
||||
log.Printf("loading the init actor for tipset: %s", tsk)
|
||||
|
||||
actor, err := sg.api.StateGetActor(sg.ctx, builtin.InitActorAddr, tsk)
|
||||
if err != nil {
|
||||
return initState, err
|
||||
}
|
||||
|
||||
actorState, err := sg.api.ChainReadObj(sg.ctx, actor.Head)
|
||||
if err != nil {
|
||||
return initState, err
|
||||
}
|
||||
|
||||
initState = new(init_.State)
|
||||
err = initState.UnmarshalCBOR(bytes.NewReader(actorState))
|
||||
if err != nil {
|
||||
return initState, err
|
||||
}
|
||||
|
||||
log.Printf("loaded init actor state: %+v", initState)
|
||||
|
||||
return initState, nil
|
||||
}
|
97
tvx/state_delta.go
Normal file
97
tvx/state_delta.go
Normal file
@ -0,0 +1,97 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var deltaFlags struct {
|
||||
from string
|
||||
to string
|
||||
}
|
||||
|
||||
var deltaCmd = &cli.Command{
|
||||
Name: "state-delta",
|
||||
Description: "collect affected state between two tipsets, addressed by blocks",
|
||||
Action: runStateDelta,
|
||||
Flags: []cli.Flag{
|
||||
&apiFlag,
|
||||
&cli.StringFlag{
|
||||
Name: "from",
|
||||
Usage: "block CID of initial state",
|
||||
Required: true,
|
||||
Destination: &deltaFlags.from,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "to",
|
||||
Usage: "block CID of ending state",
|
||||
Required: true,
|
||||
Destination: &deltaFlags.to,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func runStateDelta(c *cli.Context) error {
|
||||
node, err := makeClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
from, err := cid.Decode(deltaFlags.from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
to, err := cid.Decode(deltaFlags.to)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currBlock, err := node.ChainGetBlock(context.TODO(), to)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
srcBlock, err := node.ChainGetBlock(context.TODO(), from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
allMsgs := make(map[uint64][]*types.Message)
|
||||
|
||||
epochs := currBlock.Height - srcBlock.Height - 1
|
||||
for epochs > 0 {
|
||||
msgs, err := node.ChainGetBlockMessages(context.TODO(), to)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allMsgs[uint64(currBlock.Height)] = msgs.BlsMessages
|
||||
currBlock, err = node.ChainGetBlock(context.TODO(), currBlock.Parents[0])
|
||||
epochs--
|
||||
}
|
||||
|
||||
if !hasParent(currBlock, from) {
|
||||
return fmt.Errorf("from block was not a parent of `to` as expected")
|
||||
}
|
||||
|
||||
m := 0
|
||||
for _, msgs := range allMsgs {
|
||||
m += len(msgs)
|
||||
}
|
||||
|
||||
fmt.Printf("messages: %d\n", m)
|
||||
fmt.Printf("initial state root: %v\n", currBlock.ParentStateRoot)
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasParent(block *types.BlockHeader, parent cid.Cid) bool {
|
||||
for _, p := range block.Parents {
|
||||
if p.Equals(parent) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
Loading…
Reference in New Issue
Block a user