diff --git a/bitfield.go b/bitfield.go new file mode 100644 index 0000000..bf95329 --- /dev/null +++ b/bitfield.go @@ -0,0 +1,32 @@ +package statediff + +// This class provides a wrapper around bitfield.BitField +// which json Marshal's to include a hint at the type to allow +// clients to provide cleaner rendering of such. + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "github.com/filecoin-project/go-bitfield" +) + +type JSONBitField struct { + bitfield.BitField +} + +type jsonField struct { + T string `json:"_type"` + B string `json:"bytes"` +} + +func (j JSONBitField) MarshalJSON() ([]byte, error) { + b := bytes.NewBuffer([]byte{}) + if err := j.MarshalCBOR(b); err != nil { + return nil, err + } + return json.Marshal(jsonField{ + T: "bitfield", + B: hex.EncodeToString(b.Bytes()), + }) +} diff --git a/cmd/statediff/chain.go b/cmd/statediff/chain.go index 3911e16..c7719ab 100644 --- a/cmd/statediff/chain.go +++ b/cmd/statediff/chain.go @@ -12,6 +12,7 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/statediff" + "github.com/filecoin-project/statediff/lib" ) var chainCmd = &cli.Command{ @@ -19,7 +20,7 @@ var chainCmd = &cli.Command{ Description: "Examine the state delta of an API object", Action: runChainCmd, Flags: []cli.Flag{ - &apiFlag, + &lib.ApiFlag, &expandActorsFlag, }, } @@ -80,7 +81,7 @@ func runChainCmd(c *cli.Context) error { return fmt.Errorf("no descriptor provided") } - client, err := GetAPI(c) + client, err := lib.GetAPI(c) if err != nil { return err } diff --git a/cmd/statediff/main.go b/cmd/statediff/main.go index 3f30f02..5720a69 100644 --- a/cmd/statediff/main.go +++ b/cmd/statediff/main.go @@ -1,6 +1,3 @@ -//go:generate go run github.com/filecoin-project/statediff/build/gen "../../npm/app" static/app.js -//go:generate go run github.com/go-bindata/go-bindata/go-bindata -fs -prefix "static/" static/ - package main import ( @@ -26,7 +23,6 @@ func main() { vectorCmd, carCmd, chainCmd, - exploreCmd, }, } diff --git a/cmd/statediff/.gitignore b/cmd/stateexplorer/.gitignore similarity index 100% rename from cmd/statediff/.gitignore rename to cmd/stateexplorer/.gitignore diff --git a/cmd/statediff/explore.go b/cmd/stateexplorer/explore.go similarity index 97% rename from cmd/statediff/explore.go rename to cmd/stateexplorer/explore.go index 2cd8e73..f289a89 100644 --- a/cmd/statediff/explore.go +++ b/cmd/stateexplorer/explore.go @@ -9,6 +9,7 @@ import ( "path" "github.com/filecoin-project/statediff" + "github.com/filecoin-project/statediff/lib" "github.com/filecoin-project/statediff/build" "github.com/ipfs/go-cid" "github.com/urfave/cli/v2" @@ -31,14 +32,14 @@ var exploreCmd = &cli.Command{ Description: "Examine a state tree in a browser", Action: runExploreCmd, Flags: []cli.Flag{ - &apiFlag, + &lib.ApiFlag, &assetsFlag, &bindFlag, }, } func runExploreCmd(c *cli.Context) error { - client, err := GetAPI(c) + client, err := lib.GetAPI(c) if err != nil { return err } diff --git a/cmd/stateexplorer/main.go b/cmd/stateexplorer/main.go new file mode 100644 index 0000000..c674ee0 --- /dev/null +++ b/cmd/stateexplorer/main.go @@ -0,0 +1,33 @@ +//go:generate go run github.com/filecoin-project/statediff/build/gen "../../npm/app" static/app.js +//go:generate go run github.com/go-bindata/go-bindata/go-bindata -fs -prefix "static/" static/ + +package main + +import ( + "log" + "os" + "sort" + + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Name: "stateexplorer", + Usage: "State Explorer 🗺", + Description: "State Explorer 🗺", + Commands: []*cli.Command{ + exploreCmd, + }, + } + + 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) + } +} diff --git a/cmd/statediff/static/index.html b/cmd/stateexplorer/static/index.html similarity index 100% rename from cmd/statediff/static/index.html rename to cmd/stateexplorer/static/index.html diff --git a/cmd/statediff/static/styles.css b/cmd/stateexplorer/static/styles.css similarity index 100% rename from cmd/statediff/static/styles.css rename to cmd/stateexplorer/static/styles.css diff --git a/cmd/statediff/api.go b/lib/api.go similarity index 96% rename from cmd/statediff/api.go rename to lib/api.go index 32c2c0c..470b0f8 100644 --- a/cmd/statediff/api.go +++ b/lib/api.go @@ -1,6 +1,6 @@ // establish connection to a 'FullNode' filecoin API with cli configuration. // Mostly with an eye towards ease of use for lotus users. -package main +package lib import ( "fmt" @@ -17,7 +17,7 @@ import ( "github.com/urfave/cli/v2" ) -var apiFlag = cli.StringFlag{ +var ApiFlag = cli.StringFlag{ Name: "api", Usage: "api endpoint, formatted as token:multiaddr", Value: "", @@ -46,7 +46,7 @@ func tryGetAPIFromHomeDir() ([]string, error) { func GetAPI(c *cli.Context) (api.FullNode, error) { var err error - api := c.String(apiFlag.Name) + api := c.String(ApiFlag.Name) sp := strings.SplitN(api, ":", 2) if len(sp) != 2 { sp, err = tryGetAPIFromHomeDir() diff --git a/npm/app/components/jsonPrinter.js b/npm/app/components/jsonPrinter.js index 54b9d7d..f10aabf 100644 --- a/npm/app/components/jsonPrinter.js +++ b/npm/app/components/jsonPrinter.js @@ -50,18 +50,34 @@ class jsonPrinter { if (obj.length == 0) { return '[]'; } - str += "[\n"; - for (let i = 0; i < obj.length; i++) { - if (i > 0) { - str += ",\n"; + // if all entries are numbers, we'll skip returns. + if (obj.every(n => typeof n == "number")) { + str += "["; + for (let i = 0; i < obj.length; i++) { + if (i > 0) { + str += ", "; + } + str += this.stringify(obj[i], path + "[" + i + "]") + if (i > 100 && !full) { + str += ` ... and ${obj.length -i} more`; + break + } } - str += this.indent(this.stringify(obj[i], path + "[" + i + "]")); - if (i > 100 && !full) { - str += this.indent(` ... and ${obj.length - i} more`); - break; + str += "]"; + } else { + str += "[\n"; + for (let i = 0; i < obj.length; i++) { + if (i > 0) { + str += ",\n"; + } + str += this.indent(this.stringify(obj[i], path + "[" + i + "]")); + if (i > 100 && !full) { + str += this.indent(` ... and ${obj.length - i} more`); + break; + } } + str += "\n]"; } - str += "\n]"; } else if (Object.keys(obj).length == 1 && typeof obj["/"] == "string") { // cid special case. str += `${obj["/"]}`; diff --git a/transform.go b/transform.go index 01a7a06..7a0ebfe 100644 --- a/transform.go +++ b/transform.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "regexp" addr "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" @@ -55,6 +56,9 @@ const ( StorageMinerActorAllocatedSectors LotusType = "storageMinerActor.AllocatedSectors" StorageMinerActorSectors LotusType = "storageMinerActor.Sectors" StorageMinerActorDeadlines LotusType = "storageMinerActor.Deadlines" + StorageMinerActorDeadline LotusType = "storageMinerActor.Deadlines.Due" + StorageMinerActorDeadlinePartitions LotusType = "storageMinerActor.Deadlines.Due.Partitions" + StorageMinerActorDeadlineExpiry LotusType = "storageMinerActor.Deadlines.Due.ExpirationsEpochs" StoragePowerActorState LotusType = "storagePowerActor" StoragePowerActorCronEventQueue LotusType = "storagePowerCronEventQueue" StoragePowerActorClaims LotusType = "storagePowerClaims" @@ -65,8 +69,12 @@ const ( PaymentChannelActorState LotusType = "paymentChannelActor" ) +var simplifyingRe = regexp.MustCompile(`\[\d+\]`) + // Transform will unmarshal cbor data based on a provided type hint. func Transform(ctx context.Context, c cid.Cid, store blockstore.Blockstore, as string) (interface{}, error) { + as = string(simplifyingRe.ReplaceAll([]byte(as), []byte(""))) + // First select types which do their own store loading. switch LotusType(as) { case LotusTypeStateroot: @@ -75,8 +83,14 @@ func Transform(ctx context.Context, c cid.Cid, store blockstore.Blockstore, as s return transformInitActor(ctx, c, store) case StorageMinerActorPreCommittedSectors: return transformMinerActorPreCommittedSectors(ctx, c, store) + case StorageMinerActorPreCommittedSectorsExpiry: + return transformMinerActorPreCommittedSectorsExpiry(ctx, c, store) case StorageMinerActorSectors: return transformMinerActorSectors(ctx, c, store) + case StorageMinerActorDeadlinePartitions: + return transformMinerActorDeadlinePartitions(ctx, c, store) + case StorageMinerActorDeadlineExpiry: + return transformMinerActorDeadlineExpiry(ctx, c, store) case StoragePowerActorCronEventQueue: return transformPowerActorEventQueue(ctx, c, store) case StoragePowerActorClaims: @@ -148,11 +162,15 @@ func Transform(ctx context.Context, c cid.Cid, store blockstore.Blockstore, as s case StorageMinerActorAllocatedSectors: dest := bitfield.BitField{} err := cbor.DecodeInto(data, &dest) - return dest, err + return JSONBitField{dest}, err case StorageMinerActorDeadlines: dest := storageMinerActor.Deadlines{} err := cbor.DecodeInto(data, &dest) return dest, err + case StorageMinerActorDeadline: + dest := storageMinerActor.Deadline{} + err := cbor.DecodeInto(data, &dest) + return dest, err case StoragePowerActorState: dest := storagePowerActor.State{} err := cbor.DecodeInto(data, &dest) @@ -243,6 +261,24 @@ func transformMinerActorPreCommittedSectors(ctx context.Context, c cid.Cid, stor return m, nil } +func transformMinerActorPreCommittedSectorsExpiry(ctx context.Context, c cid.Cid, store blockstore.Blockstore) (interface{}, error) { + cborStore := cbor.NewCborStore(store) + list, err := adt.AsArray(adt.WrapStore(ctx, cborStore), c) + if err != nil { + return nil, err + } + + m := make(map[int64]JSONBitField) + value := bitfield.BitField{} + if err := list.ForEach(&value, func(k int64) error { + m[k] = JSONBitField{value} + return nil + }); err != nil { + return nil, err + } + return m, nil +} + func transformMinerActorSectors(ctx context.Context, c cid.Cid, store blockstore.Blockstore) (interface{}, error) { cborStore := cbor.NewCborStore(store) list, err := adt.AsArray(adt.WrapStore(ctx, cborStore), c) @@ -261,6 +297,42 @@ func transformMinerActorSectors(ctx context.Context, c cid.Cid, store blockstore return m, nil } +func transformMinerActorDeadlinePartitions(ctx context.Context, c cid.Cid, store blockstore.Blockstore) (interface{}, error) { + cborStore := cbor.NewCborStore(store) + list, err := adt.AsArray(adt.WrapStore(ctx, cborStore), c) + if err != nil { + return nil, err + } + + m := make(map[int64]storageMinerActor.Partition) + value := storageMinerActor.Partition{} + if err := list.ForEach(&value, func(k int64) error { + m[k] = value + return nil + }); err != nil { + return nil, err + } + return m, nil +} + +func transformMinerActorDeadlineExpiry(ctx context.Context, c cid.Cid, store blockstore.Blockstore) (interface{}, error) { + cborStore := cbor.NewCborStore(store) + list, err := adt.AsArray(adt.WrapStore(ctx, cborStore), c) + if err != nil { + return nil, err + } + + m := make(map[int64]JSONBitField) + value := bitfield.BitField{} + if err := list.ForEach(&value, func(k int64) error { + m[k] = JSONBitField{value} + return nil + }); err != nil { + return nil, err + } + return m, nil +} + func transformPowerActorEventQueue(ctx context.Context, c cid.Cid, store blockstore.Blockstore) (interface{}, error) { cborStore := cbor.NewCborStore(store) node, err := adt.AsMultimap(adt.WrapStore(ctx, cborStore), c)