From 43d9cc36a4ea3f7b5c83a5efd63bb4eea1fc6b02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 19 Mar 2021 19:22:46 +0100 Subject: [PATCH] OpenRPC Support (#5843) * main: init implement rpc.Discover RPC method This implement the basic functionality for the method over HTTP RPC. Signed-off-by: meows * main,go.mod,go.sum: init example with go-openrpc-reflect lib Signed-off-by: meows Conflicts: go.mod go.sum * main: make variable name human-friendly Signed-off-by: meows * main,go.mod,go.sum: init impl of go-openrp-reflect printing document Signed-off-by: meows Conflicts: go.mod go.sum * go.mod,go.sum: use go-openrpc-reflect and open-rpc/meta-schema hackforks This is for development only. Versions need to be bumped when they're ready for use as canonical remotes. Signed-off-by: meows * main,openrpc,main: refactor openrpc supporting code to own package This eliminates code duplication. Signed-off-by: meows * main: add rpc.Discover to openrpc document Signed-off-by: meows * openrpc: fix rpc.discover method name casing Also fixes casing stuff for the rest of Filecoin. methods. Signed-off-by: meows * Revert "main: add rpc.Discover to openrpc document" This reverts commit 116898efb10f33e405ac74acb1aa6daefcd46a62. * main: fix document creation method name This fixes an issue caused with the latest reverting commit. Signed-off-by: meows * main,docgen,openrpc: refactor to share api parsing, etc as docgen exported stuff Signed-off-by: meows Makefile: fix docgen refactoring for makefile use of command Signed-off-by: meows * openrpc: add schema.examples to app reflector There are quite of few of these already registered for the docgen command, so it makes sense to use those! Signed-off-by: meows * openrpc: init method pairing examples Signed-off-by: meows * go.mod,go.sum: bump go.mod to use latest meta-schema and openrpc-reflect versions Signed-off-by: meows * openrpc: init SchemaType mapper function This function will handle the manual configurations for app-specific data types w/r/t their json schema representation. This is useful for cases where the reflect library is unable to provide a sufficient representation automatically. Provided in this commit is an initial implementation for the integerD type (assuming number are represented in the API as hexs), and a commonly used cid.Cid type. Signed-off-by: meows * go.mod,go.sum: tame dependencies by bumping etclabscore/go-openrpc-reflect This removes a problematic dependency on github.com/ethereum/go-ethereum, which was imported as a dependency for a couple github.com/etclabscore/go-openrpc-reflect tests. etclabscore/go-openrpc-reflect v0.0.36 has removed this dependency, so this commit is the result of bumping that version and then running 'go mod tidy' This is in response to a review at https://github.com/filecoin-project/lotus/pull/4711#pullrequestreview-535686205 Date: 2020-11-21 06:52:48-06:00 Signed-off-by: meows * main: add 'miner' arg to openrpc gen cmd This allows the command to EITHER generate the doc for Full or Miner APIs. See comment for usage. Date: 2020-11-21 07:48:05-06:00 Signed-off-by: meows * docgen: add missing examples for Miner API Generating the Miner API OpenRPC doc (via 'go run ./api/openrpc/cmd miner') caused the example logic to panic because some types were missing. This commit adds those missing types, although I'm not an expert in the API so I can't suggest that the example values provided are ideal or well representative. Date: 2020-11-21 07:50:21-06:00 Signed-off-by: meows * build/openrpc/full.json,build/openrpc/miner.json: add build/openrpc/[full/miner].json docs These will be used as static documents provided by the rpc.discover method. Date: 2020-11-21 07:51:39-06:00 Signed-off-by: meows * build: init go-rice openrpc static assets Date: 2020-11-21 08:23:06-06:00 Signed-off-by: meows * main: remove rpc.discover implementation from runtime plugin Instead of generating the doc on the fly, we're going to serve a static asset. Rel https://github.com/filecoin-project/lotus/pull/4711#pullrequestreview-535686205 This removes the runtime implementation from the RPC server construction. Date: 2020-11-21 08:41:20-06:00 Signed-off-by: meows * api,apistruct,common: add Discover(ctx) method to CommonAPI interface and structs Date: 2020-11-21 08:41:56-06:00 Signed-off-by: meows * main: use rpc server method aliasing for rpc.discover This depends on a currently-forked change at filecoin-project/go-jsonrpc 8350f9463ee451b187d35c492e32f1b999e80210 which establishes this new method RPCServer.AliasMethod. This solves the problem that the OpenRPC spec says that the document should be served at the system extension-prefixed endpoing rpc.discover (not Filecoin.Discover). In fact, the document will be available at BOTH endpoints, but that duplicity is harmless. Date: 2020-11-21 09:18:26-06:00 Signed-off-by: meows * api,apistruct,build,common: rpc.discover: return json object instead of string Instead of casting the JSON asset from bytes to string, unmarshal it to a map[string]interface{} so the server will provide it as a JSON object. Date: 2020-11-21 09:27:11-06:00 Signed-off-by: meows * Makefile: merge resolve: docsgen command path Date: 2020-11-22 07:19:36-06:00 Signed-off-by: meows * apistruct,main,docgen,openrpc: merge resolve: fix func exporteds, signatures Date: 2020-11-22 07:31:03-06:00 Signed-off-by: meows * go.mod,go.sum: 'get get' auto-bumps version Date: 2020-11-22 07:31:44-06:00 Signed-off-by: meows * Makefile,docgen,main,build/openrpc: refactor openrpc documentation generation This creates Makefile command docsgen-openrpc-json, and refactors the docsgen command to generate both the markdown and openrpc json documents, redirecting the output of the openrpc json documentation to the build/openrpc/ directory, where those json files will be compiled as static assets via go-rice boxes. The api/openrpc/cmd now uses usage argumentation congruent to that of the docgen command (switching on API context). Date: 2020-11-22 08:01:18-06:00 Signed-off-by: meows * main,docgen_openrpc: rename api/openrpc -> api/docgen-openrpc Renames the package as well. This is intended to parallel the existing docgen package and command namespacing. Date: 2020-11-22 10:34:46-06:00 Signed-off-by: meows * api,apistruct,docgen,build,build/openrpc: use typed Discover response Instead of using a map[string]interface{}, use a typed response for the Discover method implementation. This avoids having to set a docgen Example for the generic map[string]interface{} (as an openrpc document) which both pollutes the generic type and lacks useful information for the Discover method example. Date: 2020-11-22 08:31:16-06:00 Signed-off-by: meows * apistruct,build,main,impl: implement Discover method for Worker and StorageMiner APIs Methods return static compiled assets respective to the APIs. Date: 2020-11-22 08:57:18-06:00 Signed-off-by: meows * docgen_openrpc,build/openrpc: remove timestamping from openrpc doc info This should allow openrpc docs generated at different times to be equal. This is important because the CI (Circle) runs the docgen command and tests that the output and the source are unchanged (via git diff). Date: 2020-11-22 10:47:07-06:00 Signed-off-by: meows * main,docgen_openrpc,main,build: fix lint issues Fixes goimports, staticcheck, golint issues. Date: 2020-11-22 11:06:46-06:00 Signed-off-by: meows * docgenopenrpc: fix: don't use an underscore in package name (golint) Date: 2020-11-22 11:07:53-06:00 Signed-off-by: meows * go.sum: fix: mod-tidy-check (run 'go mod tidy') Date: 2020-11-22 11:09:48-06:00 Signed-off-by: meows * go.mod,go.sum: bump filecoin-project/go-jsonrpc dep to latest This version includes the necessary RPCServer.AliasMethod method. Date: 2020-11-23 12:16:15-06:00 Signed-off-by: meows * Makefile,main,build,build/openrpc: init gzipped openrpc static docs Date: 2020-11-24 06:15:06-06:00 Signed-off-by: meows * build: refactor gzip reading Date: 2020-11-24 06:18:34-06:00 Signed-off-by: meows * build: add basic test for openrpc doc from static assets Date: 2020-11-24 06:30:23-06:00 Signed-off-by: meows * build: handle reader Close error This keeps the errcheck linter happy. Date: 2020-11-24 06:33:14-06:00 Signed-off-by: meows * go.sum: run 'go mod tidy' Date: 2020-11-24 06:36:07-06:00 Signed-off-by: meows * go.mod,go.sum: go mod tidy Tidying up after resolving the merge conflicts with master at go.mod Date: 2020-11-24 06:40:45-06:00 Signed-off-by: meows * go.mod,go.sum: bump filecoin-project/go-jsonrpc to latest This is a repeat of 76e6fd2, since the latest merge to master seems to have reverted this. Date: 2020-11-24 06:42:30-06:00 Signed-off-by: meows * docgenopenrpc,build/openrpc: remove method example pairings, improve schema examples Removing method example pairings since they were redundant to schema examples and were not implemented well. Improved schema examples by using the ExampleValue method instead of the map lookup. Made a note in the comment here that this is not ideal, since we have to make a shortcut assumption /workaround by using 'unknown' as the method name and the typea as its own parent. Luckily these values aren't heavily used by the method logic. Date: 2020-11-27 12:57:36-06:00 Signed-off-by: meows * docgenopenrpc: use generic number jsonschema for number types Previously used an integer schema assuming hex encoding. It appears, based on review some of the examples, that this may not be the case. Obvioussly this schema could be more descriptive, but just shooting for mostly likely to be not wrong at this point. Date: 2020-12-15 14:44:37-06:00 Signed-off-by: meows * cmd/lotus,go.mod,go.sum: maybe fix straggling merge resolution conflicts Date: 2021-01-19 12:30:42-06:00 Signed-off-by: meows * build/openrpc/full.json.gz,build/openrpc/miner.json.gz,build/openrpc/worker.json.gz: run 'make docsgen' Date: 2021-01-19 12:33:55-06:00 Signed-off-by: meows * api/apistruct,node/impl: (lint) gofmt Date: 2021-01-19 12:39:48-06:00 Signed-off-by: meows * api/docgen: maybe fix parse error: open ./api: no such file or directory Date: 2021-01-19 12:52:04-06:00 Signed-off-by: meows * api/docgen,build/openrpc: maybe fix no such file error and run 'make docsgen' Date: 2021-01-19 12:55:52-06:00 Signed-off-by: meows * api/docgen: return if AST comment/groupdoc parsing encounters any error This will returns empty comments/docs maps. This should fix issues like: https://app.circleci.com/pipelines/github/filecoin-project/lotus/12445/workflows/4ebadce9-a298-4ad1-939b-f19ef4c0a5bf/jobs/107218 where the environment makes file lookups hard or impossible. Date: 2021-01-19 13:04:58-06:00 Signed-off-by: meows * api: Don't depend on build/ * make: support parallel docsgen * openrpc gen: Use simple build version * methodgen * goimports Co-authored-by: meows --- .gitignore | 2 + Makefile | 30 +++- api/api_common.go | 5 + api/api_test.go | 12 ++ api/apistruct/struct.go | 22 ++- api/docgen-openrpc/cmd/docgen_openrpc.go | 77 +++++++++ api/docgen-openrpc/openrpc.go | 172 +++++++++++++++++++ api/docgen/cmd/docgen.go | 137 +++++++++++++++ api/docgen/docgen.go | 207 ++++++----------------- api/mocks/mock_full.go | 16 ++ api/types/openrpc.go | 3 + build/openrpc.go | 43 +++++ build/openrpc/full.json.gz | Bin 0 -> 22794 bytes build/openrpc/miner.json.gz | Bin 0 -> 8259 bytes build/openrpc/worker.json.gz | Bin 0 -> 2914 bytes build/openrpc_test.go | 23 +++ cmd/lotus-seal-worker/rpc.go | 6 + cmd/lotus/rpc.go | 1 + documentation/en/api-methods-miner.md | 20 +++ documentation/en/api-methods.md | 20 +++ go.mod | 7 +- go.sum | 63 ++++++- node/impl/common/common.go | 5 + node/impl/storminer.go | 6 + 24 files changed, 712 insertions(+), 165 deletions(-) create mode 100644 api/docgen-openrpc/cmd/docgen_openrpc.go create mode 100644 api/docgen-openrpc/openrpc.go create mode 100644 api/docgen/cmd/docgen.go create mode 100644 api/types/openrpc.go create mode 100644 build/openrpc.go create mode 100644 build/openrpc/full.json.gz create mode 100644 build/openrpc/miner.json.gz create mode 100644 build/openrpc/worker.json.gz create mode 100644 build/openrpc_test.go diff --git a/.gitignore b/.gitignore index 23eca7e42..e34ebb935 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,8 @@ /lotus-pcr /lotus-wallet /lotus-keygen +/docgen-md +/docgen-openrpc /bench.json /lotuspond/front/node_modules /lotuspond/front/build diff --git a/Makefile b/Makefile index abc01385a..8cd770906 100644 --- a/Makefile +++ b/Makefile @@ -327,10 +327,32 @@ method-gen: gen: type-gen method-gen -docsgen: - go run ./api/docgen "api/api_full.go" "FullNode" > documentation/en/api-methods.md - go run ./api/docgen "api/api_storage.go" "StorageMiner" > documentation/en/api-methods-miner.md - go run ./api/docgen "api/api_worker.go" "WorkerAPI" > documentation/en/api-methods-worker.md +docsgen: docsgen-md docsgen-openrpc + +docsgen-md-bin: + go build $(GOFLAGS) -o docgen-md ./api/docgen/cmd +docsgen-openrpc-bin: + go build $(GOFLAGS) -o docgen-openrpc ./api/docgen-openrpc/cmd + +docsgen-md: docsgen-md-full docsgen-md-storage docsgen-md-worker + +docsgen-md-full: docsgen-md-bin + ./docgen-md "api/api_full.go" "FullNode" > documentation/en/api-methods.md +docsgen-md-storage: docsgen-md-bin + ./docgen-md "api/api_storage.go" "StorageMiner" > documentation/en/api-methods-miner.md +docsgen-md-worker: docsgen-md-bin + ./docgen-md "api/api_worker.go" "WorkerAPI" > documentation/en/api-methods-worker.md + +docsgen-openrpc: docsgen-openrpc-full docsgen-openrpc-storage docsgen-openrpc-worker + +docsgen-openrpc-full: docsgen-openrpc-bin + ./docgen-openrpc "api/api_full.go" "FullNode" -gzip > build/openrpc/full.json.gz +docsgen-openrpc-storage: docsgen-openrpc-bin + ./docgen-openrpc "api/api_storage.go" "StorageMiner" -gzip > build/openrpc/miner.json.gz +docsgen-openrpc-worker: docsgen-openrpc-bin + ./docgen-openrpc "api/api_worker.go" "WorkerAPI" -gzip > build/openrpc/worker.json.gz + +.PHONY: docsgen docsgen-md-bin docsgen-openrpc-bin print-%: @echo $*=$($*) diff --git a/api/api_common.go b/api/api_common.go index fc89f11cd..a0726528d 100644 --- a/api/api_common.go +++ b/api/api_common.go @@ -11,6 +11,8 @@ import ( "github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/peer" protocol "github.com/libp2p/go-libp2p-core/protocol" + + apitypes "github.com/filecoin-project/lotus/api/types" ) type Common interface { @@ -52,6 +54,9 @@ type Common interface { // MethodGroup: Common + // Discover returns an OpenRPC document describing an RPC API. + Discover(ctx context.Context) (apitypes.OpenRPCDocument, error) + // ID returns peerID of libp2p node backing this API ID(context.Context) (peer.ID, error) diff --git a/api/api_test.go b/api/api_test.go index 34c47f432..e4010a471 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -37,6 +37,18 @@ func TestDoesntDependOnFFI(t *testing.T) { } } +func TestDoesntDependOnBuild(t *testing.T) { + deps, err := exec.Command(goCmd(), "list", "-deps", "github.com/filecoin-project/lotus/api").Output() + if err != nil { + t.Fatal(err) + } + for _, pkg := range strings.Fields(string(deps)) { + if pkg == "github.com/filecoin-project/build" { + t.Fatal("api depends on filecoin-ffi") + } + } +} + func TestReturnTypes(t *testing.T) { errType := reflect.TypeOf(new(error)).Elem() bareIface := reflect.TypeOf(new(interface{})).Elem() diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index 34b18cd41..56ead4b10 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -5,6 +5,8 @@ import ( "io" "time" + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-bitfield" "github.com/google/uuid" "github.com/ipfs/go-cid" metrics "github.com/libp2p/go-libp2p-core/metrics" @@ -12,8 +14,6 @@ import ( "github.com/libp2p/go-libp2p-core/peer" protocol "github.com/libp2p/go-libp2p-core/protocol" - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-bitfield" datatransfer "github.com/filecoin-project/go-data-transfer" "github.com/filecoin-project/go-fil-markets/piecestore" "github.com/filecoin-project/go-fil-markets/retrievalmarket" @@ -33,6 +33,7 @@ import ( "github.com/filecoin-project/specs-storage/storage" "github.com/filecoin-project/lotus/api" + apitypes "github.com/filecoin-project/lotus/api/types" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/actors/builtin/paych" "github.com/filecoin-project/lotus/chain/types" @@ -63,6 +64,7 @@ type CommonStruct struct { NetBlockAdd func(ctx context.Context, acl api.NetBlockList) error `perm:"admin"` NetBlockRemove func(ctx context.Context, acl api.NetBlockList) error `perm:"admin"` NetBlockList func(ctx context.Context) (api.NetBlockList, error) `perm:"read"` + Discover func(ctx context.Context) (map[string]interface{}, error) `perm:"read"` ID func(context.Context) (peer.ID, error) `perm:"read"` Version func(context.Context) (api.APIVersion, error) `perm:"read"` @@ -382,6 +384,8 @@ type StorageMinerStruct struct { CreateBackup func(ctx context.Context, fpath string) error `perm:"admin"` CheckProvable func(ctx context.Context, pp abi.RegisteredPoStProof, sectors []storage.SectorRef, expensive bool) (map[abi.SectorNumber]string, error) `perm:"admin"` + + Discover func(ctx context.Context) (apitypes.OpenRPCDocument, error) `perm:"read"` } } @@ -420,6 +424,8 @@ type WorkerStruct struct { ProcessSession func(context.Context) (uuid.UUID, error) `perm:"admin"` Session func(context.Context) (uuid.UUID, error) `perm:"admin"` + + Discover func(ctx context.Context) (apitypes.OpenRPCDocument, error) `perm:"read"` } } @@ -544,6 +550,10 @@ func (c *CommonStruct) NetPeerInfo(ctx context.Context, p peer.ID) (*api.Extende return c.Internal.NetPeerInfo(ctx, p) } +func (c *CommonStruct) Discover(ctx context.Context) (apitypes.OpenRPCDocument, error) { + return c.Internal.Discover(ctx) +} + // ID implements API.ID func (c *CommonStruct) ID(ctx context.Context) (peer.ID, error) { return c.Internal.ID(ctx) @@ -1612,6 +1622,10 @@ func (c *StorageMinerStruct) CheckProvable(ctx context.Context, pp abi.Registere return c.Internal.CheckProvable(ctx, pp, sectors, expensive) } +func (c *StorageMinerStruct) Discover(ctx context.Context) (apitypes.OpenRPCDocument, error) { + return c.Internal.Discover(ctx) +} + // WorkerStruct func (w *WorkerStruct) Version(ctx context.Context) (api.Version, error) { @@ -1710,6 +1724,10 @@ func (w *WorkerStruct) Session(ctx context.Context) (uuid.UUID, error) { return w.Internal.Session(ctx) } +func (c *WorkerStruct) Discover(ctx context.Context) (apitypes.OpenRPCDocument, error) { + return c.Internal.Discover(ctx) +} + func (g GatewayStruct) ChainGetBlockMessages(ctx context.Context, c cid.Cid) (*api.BlockMessages, error) { return g.Internal.ChainGetBlockMessages(ctx, c) } diff --git a/api/docgen-openrpc/cmd/docgen_openrpc.go b/api/docgen-openrpc/cmd/docgen_openrpc.go new file mode 100644 index 000000000..a1a039b40 --- /dev/null +++ b/api/docgen-openrpc/cmd/docgen_openrpc.go @@ -0,0 +1,77 @@ +package main + +import ( + "compress/gzip" + "encoding/json" + "io" + "log" + "os" + + "github.com/filecoin-project/lotus/api/apistruct" + docgen_openrpc "github.com/filecoin-project/lotus/api/docgen-openrpc" +) + +/* +main defines a small program that writes an OpenRPC document describing +a Lotus API to stdout. + +If the first argument is "miner", the document will describe the StorageMiner API. +If not (no, or any other args), the document will describe the Full API. + +Use: + + go run ./api/openrpc/cmd ["api/api_full.go"|"api/api_storage.go"|"api/api_worker.go"] ["FullNode"|"StorageMiner"|"WorkerAPI"] + + With gzip compression: a '-gzip' flag is made available as an optional third argument. Note that position matters. + + go run ./api/openrpc/cmd ["api/api_full.go"|"api/api_storage.go"|"api/api_worker.go"] ["FullNode"|"StorageMiner"|"WorkerAPI"] -gzip + +*/ + +func main() { + doc := docgen_openrpc.NewLotusOpenRPCDocument() + + switch os.Args[2] { + case "FullNode": + doc.RegisterReceiverName("Filecoin", &apistruct.FullNodeStruct{}) + case "StorageMiner": + doc.RegisterReceiverName("Filecoin", &apistruct.StorageMinerStruct{}) + case "WorkerAPI": + doc.RegisterReceiverName("Filecoin", &apistruct.WorkerStruct{}) + } + + out, err := doc.Discover() + if err != nil { + log.Fatalln(err) + } + + var jsonOut []byte + var writer io.WriteCloser + + // Use os.Args to handle a somewhat hacky flag for the gzip option. + // Could use flags package to handle this more cleanly, but that requires changes elsewhere + // the scope of which just isn't warranted by this one use case which will usually be run + // programmatically anyways. + if len(os.Args) > 3 && os.Args[3] == "-gzip" { + jsonOut, err = json.Marshal(out) + if err != nil { + log.Fatalln(err) + } + writer = gzip.NewWriter(os.Stdout) + } else { + jsonOut, err = json.MarshalIndent(out, "", " ") + if err != nil { + log.Fatalln(err) + } + writer = os.Stdout + } + + _, err = writer.Write(jsonOut) + if err != nil { + log.Fatalln(err) + } + err = writer.Close() + if err != nil { + log.Fatalln(err) + } +} diff --git a/api/docgen-openrpc/openrpc.go b/api/docgen-openrpc/openrpc.go new file mode 100644 index 000000000..507ad3cb1 --- /dev/null +++ b/api/docgen-openrpc/openrpc.go @@ -0,0 +1,172 @@ +package docgenopenrpc + +import ( + "encoding/json" + "go/ast" + "net" + "os" + "reflect" + + "github.com/alecthomas/jsonschema" + go_openrpc_reflect "github.com/etclabscore/go-openrpc-reflect" + "github.com/filecoin-project/lotus/api/docgen" + "github.com/filecoin-project/lotus/build" + "github.com/ipfs/go-cid" + meta_schema "github.com/open-rpc/meta-schema" +) + +// Comments holds API method comments collected by AST parsing. +var Comments map[string]string + +// GroupDocs holds documentation for documentation groups. +var GroupDocs map[string]string + +func init() { + Comments, GroupDocs = docgen.ParseApiASTInfo(os.Args[1], os.Args[2]) +} + +// schemaDictEntry represents a type association passed to the jsonschema reflector. +type schemaDictEntry struct { + example interface{} + rawJson string +} + +const integerD = `{ + "title": "number", + "type": "number", + "description": "Number is a number" + }` + +const cidCidD = `{"title": "Content Identifier", "type": "string", "description": "Cid represents a self-describing content addressed identifier. It is formed by a Version, a Codec (which indicates a multicodec-packed content type) and a Multihash."}` + +func OpenRPCSchemaTypeMapper(ty reflect.Type) *jsonschema.Type { + unmarshalJSONToJSONSchemaType := func(input string) *jsonschema.Type { + var js jsonschema.Type + err := json.Unmarshal([]byte(input), &js) + if err != nil { + panic(err) + } + return &js + } + + if ty.Kind() == reflect.Ptr { + ty = ty.Elem() + } + + if ty == reflect.TypeOf((*interface{})(nil)).Elem() { + return &jsonschema.Type{Type: "object", AdditionalProperties: []byte("true")} + } + + // Second, handle other types. + // Use a slice instead of a map because it preserves order, as a logic safeguard/fallback. + dict := []schemaDictEntry{ + {cid.Cid{}, cidCidD}, + } + + for _, d := range dict { + if reflect.TypeOf(d.example) == ty { + tt := unmarshalJSONToJSONSchemaType(d.rawJson) + + return tt + } + } + + // Handle primitive types in case there are generic cases + // specific to our services. + switch ty.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + // Return all integer types as the hex representation integer schemea. + ret := unmarshalJSONToJSONSchemaType(integerD) + return ret + case reflect.Uintptr: + return &jsonschema.Type{Type: "number", Title: "uintptr-title"} + case reflect.Struct: + case reflect.Map: + case reflect.Slice, reflect.Array: + case reflect.Float32, reflect.Float64: + case reflect.Bool: + case reflect.String: + case reflect.Ptr, reflect.Interface: + default: + } + + return nil +} + +// NewLotusOpenRPCDocument defines application-specific documentation and configuration for its OpenRPC document. +func NewLotusOpenRPCDocument() *go_openrpc_reflect.Document { + d := &go_openrpc_reflect.Document{} + + // Register "Meta" document fields. + // These include getters for + // - Servers object + // - Info object + // - ExternalDocs object + // + // These objects represent server-specific data that cannot be + // reflected. + d.WithMeta(&go_openrpc_reflect.MetaT{ + GetServersFn: func() func(listeners []net.Listener) (*meta_schema.Servers, error) { + return func(listeners []net.Listener) (*meta_schema.Servers, error) { + return nil, nil + } + }, + GetInfoFn: func() (info *meta_schema.InfoObject) { + info = &meta_schema.InfoObject{} + title := "Lotus RPC API" + info.Title = (*meta_schema.InfoObjectProperties)(&title) + + version := build.BuildVersion + info.Version = (*meta_schema.InfoObjectVersion)(&version) + return info + }, + GetExternalDocsFn: func() (exdocs *meta_schema.ExternalDocumentationObject) { + return nil // FIXME + }, + }) + + // Use a provided Ethereum default configuration as a base. + appReflector := &go_openrpc_reflect.EthereumReflectorT{} + + // Install overrides for the json schema->type map fn used by the jsonschema reflect package. + appReflector.FnSchemaTypeMap = func() func(ty reflect.Type) *jsonschema.Type { + return OpenRPCSchemaTypeMapper + } + + appReflector.FnIsMethodEligible = func(m reflect.Method) bool { + for i := 0; i < m.Func.Type().NumOut(); i++ { + if m.Func.Type().Out(i).Kind() == reflect.Chan { + return false + } + } + return go_openrpc_reflect.EthereumReflector.IsMethodEligible(m) + } + appReflector.FnGetMethodName = func(moduleName string, r reflect.Value, m reflect.Method, funcDecl *ast.FuncDecl) (string, error) { + if m.Name == "ID" { + return moduleName + "_ID", nil + } + if moduleName == "rpc" && m.Name == "Discover" { + return "rpc.discover", nil + } + + return moduleName + "." + m.Name, nil + } + + appReflector.FnGetMethodSummary = func(r reflect.Value, m reflect.Method, funcDecl *ast.FuncDecl) (string, error) { + if v, ok := Comments[m.Name]; ok { + return v, nil + } + return "", nil // noComment + } + + appReflector.FnSchemaExamples = func(ty reflect.Type) (examples *meta_schema.Examples, err error) { + v := docgen.ExampleValue("unknown", ty, ty) // This isn't ideal, but seems to work well enough. + return &meta_schema.Examples{ + meta_schema.AlwaysTrue(v), + }, nil + } + + // Finally, register the configured reflector to the document. + d.WithReflector(appReflector) + return d +} diff --git a/api/docgen/cmd/docgen.go b/api/docgen/cmd/docgen.go new file mode 100644 index 000000000..e4c415015 --- /dev/null +++ b/api/docgen/cmd/docgen.go @@ -0,0 +1,137 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + "reflect" + "sort" + "strings" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/api/apistruct" + "github.com/filecoin-project/lotus/api/docgen" +) + +func main() { + comments, groupComments := docgen.ParseApiASTInfo(os.Args[1], os.Args[2]) + + groups := make(map[string]*docgen.MethodGroup) + + var t reflect.Type + var permStruct, commonPermStruct reflect.Type + + switch os.Args[2] { + case "FullNode": + t = reflect.TypeOf(new(struct{ api.FullNode })).Elem() + permStruct = reflect.TypeOf(apistruct.FullNodeStruct{}.Internal) + commonPermStruct = reflect.TypeOf(apistruct.CommonStruct{}.Internal) + case "StorageMiner": + t = reflect.TypeOf(new(struct{ api.StorageMiner })).Elem() + permStruct = reflect.TypeOf(apistruct.StorageMinerStruct{}.Internal) + commonPermStruct = reflect.TypeOf(apistruct.CommonStruct{}.Internal) + case "WorkerAPI": + t = reflect.TypeOf(new(struct{ api.WorkerAPI })).Elem() + permStruct = reflect.TypeOf(apistruct.WorkerStruct{}.Internal) + commonPermStruct = reflect.TypeOf(apistruct.WorkerStruct{}.Internal) + default: + panic("unknown type") + } + + for i := 0; i < t.NumMethod(); i++ { + m := t.Method(i) + + groupName := docgen.MethodGroupFromName(m.Name) + + g, ok := groups[groupName] + if !ok { + g = new(docgen.MethodGroup) + g.Header = groupComments[groupName] + g.GroupName = groupName + groups[groupName] = g + } + + var args []interface{} + ft := m.Func.Type() + for j := 2; j < ft.NumIn(); j++ { + inp := ft.In(j) + args = append(args, docgen.ExampleValue(m.Name, inp, nil)) + } + + v, err := json.MarshalIndent(args, "", " ") + if err != nil { + panic(err) + } + + outv := docgen.ExampleValue(m.Name, ft.Out(0), nil) + + ov, err := json.MarshalIndent(outv, "", " ") + if err != nil { + panic(err) + } + + g.Methods = append(g.Methods, &docgen.Method{ + Name: m.Name, + Comment: comments[m.Name], + InputExample: string(v), + ResponseExample: string(ov), + }) + } + + var groupslice []*docgen.MethodGroup + for _, g := range groups { + groupslice = append(groupslice, g) + } + + sort.Slice(groupslice, func(i, j int) bool { + return groupslice[i].GroupName < groupslice[j].GroupName + }) + + fmt.Printf("# Groups\n") + + for _, g := range groupslice { + fmt.Printf("* [%s](#%s)\n", g.GroupName, g.GroupName) + for _, method := range g.Methods { + fmt.Printf(" * [%s](#%s)\n", method.Name, method.Name) + } + } + + for _, g := range groupslice { + g := g + fmt.Printf("## %s\n", g.GroupName) + fmt.Printf("%s\n\n", g.Header) + + sort.Slice(g.Methods, func(i, j int) bool { + return g.Methods[i].Name < g.Methods[j].Name + }) + + for _, m := range g.Methods { + fmt.Printf("### %s\n", m.Name) + fmt.Printf("%s\n\n", m.Comment) + + meth, ok := permStruct.FieldByName(m.Name) + if !ok { + meth, ok = commonPermStruct.FieldByName(m.Name) + if !ok { + panic("no perms for method: " + m.Name) + } + } + + perms := meth.Tag.Get("perm") + + fmt.Printf("Perms: %s\n\n", perms) + + if strings.Count(m.InputExample, "\n") > 0 { + fmt.Printf("Inputs:\n```json\n%s\n```\n\n", m.InputExample) + } else { + fmt.Printf("Inputs: `%s`\n\n", m.InputExample) + } + + if strings.Count(m.ResponseExample, "\n") > 0 { + fmt.Printf("Response:\n```json\n%s\n```\n\n", m.ResponseExample) + } else { + fmt.Printf("Response: `%s`\n\n", m.ResponseExample) + } + } + } +} diff --git a/api/docgen/docgen.go b/api/docgen/docgen.go index 7b6a2725b..23caa3a8f 100644 --- a/api/docgen/docgen.go +++ b/api/docgen/docgen.go @@ -1,18 +1,18 @@ -package main +package docgen import ( - "encoding/json" "fmt" "go/ast" "go/parser" "go/token" - "os" + "path/filepath" "reflect" - "sort" "strings" "time" "unicode" + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-bitfield" "github.com/google/uuid" "github.com/ipfs/go-cid" "github.com/ipfs/go-filestore" @@ -23,8 +23,6 @@ import ( pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/multiformats/go-multiaddr" - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-bitfield" datatransfer "github.com/filecoin-project/go-data-transfer" filestore2 "github.com/filecoin-project/go-fil-markets/filestore" "github.com/filecoin-project/go-fil-markets/retrievalmarket" @@ -36,7 +34,7 @@ import ( "github.com/filecoin-project/go-state-types/exitcode" "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/api/apistruct" + apitypes "github.com/filecoin-project/lotus/api/types" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/extern/sector-storage/sealtasks" @@ -89,6 +87,8 @@ func init() { addExample(pid) addExample(&pid) + multistoreIDExample := multistore.StoreID(50) + addExample(bitfield.NewFromSet([]uint64{5})) addExample(abi.RegisteredSealProof_StackedDrg32GiBV1_1) addExample(abi.RegisteredPoStProof_StackedDrgWindow32GiBV1) @@ -118,7 +118,8 @@ func init() { addExample(time.Minute) addExample(datatransfer.TransferID(3)) addExample(datatransfer.Ongoing) - addExample(multistore.StoreID(50)) + addExample(multistoreIDExample) + addExample(&multistoreIDExample) addExample(retrievalmarket.ClientEventDealAccepted) addExample(retrievalmarket.DealStatusNew) addExample(network.ReachabilityPublic) @@ -126,17 +127,17 @@ func init() { addExample(map[string]int{"name": 42}) addExample(map[string]time.Time{"name": time.Unix(1615243938, 0).UTC()}) addExample(&types.ExecutionTrace{ - Msg: exampleValue("init", reflect.TypeOf(&types.Message{}), nil).(*types.Message), - MsgRct: exampleValue("init", reflect.TypeOf(&types.MessageReceipt{}), nil).(*types.MessageReceipt), + Msg: ExampleValue("init", reflect.TypeOf(&types.Message{}), nil).(*types.Message), + MsgRct: ExampleValue("init", reflect.TypeOf(&types.MessageReceipt{}), nil).(*types.MessageReceipt), }) addExample(map[string]types.Actor{ - "t01236": exampleValue("init", reflect.TypeOf(types.Actor{}), nil).(types.Actor), + "t01236": ExampleValue("init", reflect.TypeOf(types.Actor{}), nil).(types.Actor), }) addExample(map[string]api.MarketDeal{ - "t026363": exampleValue("init", reflect.TypeOf(api.MarketDeal{}), nil).(api.MarketDeal), + "t026363": ExampleValue("init", reflect.TypeOf(api.MarketDeal{}), nil).(api.MarketDeal), }) addExample(map[string]api.MarketBalance{ - "t026363": exampleValue("init", reflect.TypeOf(api.MarketBalance{}), nil).(api.MarketBalance), + "t026363": ExampleValue("init", reflect.TypeOf(api.MarketBalance{}), nil).(api.MarketBalance), }) addExample(map[string]*pubsub.TopicScoreSnapshot{ "/blocks": { @@ -251,9 +252,17 @@ func init() { sealtasks.TTPreCommit2: {}, }) addExample(sealtasks.TTCommit2) + addExample(apitypes.OpenRPCDocument{ + "openrpc": "1.2.6", + "info": map[string]interface{}{ + "title": "Lotus RPC API", + "version": "1.2.1/generated=2020-11-22T08:22:42-06:00", + }, + "methods": []interface{}{}}, + ) } -func exampleValue(method string, t, parent reflect.Type) interface{} { +func ExampleValue(method string, t, parent reflect.Type) interface{} { v, ok := ExampleValues[t] if ok { return v @@ -262,10 +271,10 @@ func exampleValue(method string, t, parent reflect.Type) interface{} { switch t.Kind() { case reflect.Slice: out := reflect.New(t).Elem() - reflect.Append(out, reflect.ValueOf(exampleValue(method, t.Elem(), t))) + reflect.Append(out, reflect.ValueOf(ExampleValue(method, t.Elem(), t))) return out.Interface() case reflect.Chan: - return exampleValue(method, t.Elem(), nil) + return ExampleValue(method, t.Elem(), nil) case reflect.Struct: es := exampleStruct(method, t, parent) v := reflect.ValueOf(es).Elem().Interface() @@ -274,7 +283,7 @@ func exampleValue(method string, t, parent reflect.Type) interface{} { case reflect.Array: out := reflect.New(t).Elem() for i := 0; i < t.Len(); i++ { - out.Index(i).Set(reflect.ValueOf(exampleValue(method, t.Elem(), t))) + out.Index(i).Set(reflect.ValueOf(ExampleValue(method, t.Elem(), t))) } return out.Interface() @@ -299,7 +308,7 @@ func exampleStruct(method string, t, parent reflect.Type) interface{} { continue } if strings.Title(f.Name) == f.Name { - ns.Elem().Field(i).Set(reflect.ValueOf(exampleValue(method, f.Type, t))) + ns.Elem().Field(i).Set(reflect.ValueOf(ExampleValue(method, f.Type, t))) } } @@ -331,13 +340,24 @@ func (v *Visitor) Visit(node ast.Node) ast.Visitor { return v } -const noComment = "There are not yet any comments for this method." +const NoComment = "There are not yet any comments for this method." -func parseApiASTInfo(apiFile, iface string) (map[string]string, map[string]string) { //nolint:golint +func ParseApiASTInfo(apiFile, iface string) (comments map[string]string, groupDocs map[string]string) { //nolint:golint fset := token.NewFileSet() - pkgs, err := parser.ParseDir(fset, "./api", nil, parser.AllErrors|parser.ParseComments) + apiDir, err := filepath.Abs("./api") + if err != nil { + fmt.Println("./api filepath absolute error: ", err) + return + } + apiFile, err = filepath.Abs(apiFile) + if err != nil { + fmt.Println("filepath absolute error: ", err, "file:", apiFile) + return + } + pkgs, err := parser.ParseDir(fset, apiDir, nil, parser.AllErrors|parser.ParseComments) if err != nil { fmt.Println("parse error: ", err) + return } ap := pkgs["api"] @@ -349,14 +369,14 @@ func parseApiASTInfo(apiFile, iface string) (map[string]string, map[string]strin v := &Visitor{iface, make(map[string]ast.Node)} ast.Walk(v, pkgs["api"]) - groupDocs := make(map[string]string) - out := make(map[string]string) + comments = make(map[string]string) + groupDocs = make(map[string]string) for mn, node := range v.Methods { - cs := cmap.Filter(node).Comments() - if len(cs) == 0 { - out[mn] = noComment + filteredComments := cmap.Filter(node).Comments() + if len(filteredComments) == 0 { + comments[mn] = NoComment } else { - for _, c := range cs { + for _, c := range filteredComments { if strings.HasPrefix(c.Text(), "MethodGroup:") { parts := strings.Split(c.Text(), "\n") groupName := strings.TrimSpace(parts[0][12:]) @@ -367,15 +387,15 @@ func parseApiASTInfo(apiFile, iface string) (map[string]string, map[string]strin } } - last := cs[len(cs)-1].Text() + last := filteredComments[len(filteredComments)-1].Text() if !strings.HasPrefix(last, "MethodGroup:") { - out[mn] = last + comments[mn] = last } else { - out[mn] = noComment + comments[mn] = NoComment } } } - return out, groupDocs + return comments, groupDocs } type MethodGroup struct { @@ -391,7 +411,7 @@ type Method struct { ResponseExample string } -func methodGroupFromName(mn string) string { +func MethodGroupFromName(mn string) string { i := strings.IndexFunc(mn[1:], func(r rune) bool { return unicode.IsUpper(r) }) @@ -400,126 +420,3 @@ func methodGroupFromName(mn string) string { } return mn[:i+1] } - -func main() { - comments, groupComments := parseApiASTInfo(os.Args[1], os.Args[2]) - - groups := make(map[string]*MethodGroup) - - var t reflect.Type - var permStruct, commonPermStruct reflect.Type - - switch os.Args[2] { - case "FullNode": - t = reflect.TypeOf(new(struct{ api.FullNode })).Elem() - permStruct = reflect.TypeOf(apistruct.FullNodeStruct{}.Internal) - commonPermStruct = reflect.TypeOf(apistruct.CommonStruct{}.Internal) - case "StorageMiner": - t = reflect.TypeOf(new(struct{ api.StorageMiner })).Elem() - permStruct = reflect.TypeOf(apistruct.StorageMinerStruct{}.Internal) - commonPermStruct = reflect.TypeOf(apistruct.CommonStruct{}.Internal) - case "WorkerAPI": - t = reflect.TypeOf(new(struct{ api.WorkerAPI })).Elem() - permStruct = reflect.TypeOf(apistruct.WorkerStruct{}.Internal) - commonPermStruct = reflect.TypeOf(apistruct.WorkerStruct{}.Internal) - default: - panic("unknown type") - } - - for i := 0; i < t.NumMethod(); i++ { - m := t.Method(i) - - groupName := methodGroupFromName(m.Name) - - g, ok := groups[groupName] - if !ok { - g = new(MethodGroup) - g.Header = groupComments[groupName] - g.GroupName = groupName - groups[groupName] = g - } - - var args []interface{} - ft := m.Func.Type() - for j := 2; j < ft.NumIn(); j++ { - inp := ft.In(j) - args = append(args, exampleValue(m.Name, inp, nil)) - } - - v, err := json.MarshalIndent(args, "", " ") - if err != nil { - panic(err) - } - - outv := exampleValue(m.Name, ft.Out(0), nil) - - ov, err := json.MarshalIndent(outv, "", " ") - if err != nil { - panic(err) - } - - g.Methods = append(g.Methods, &Method{ - Name: m.Name, - Comment: comments[m.Name], - InputExample: string(v), - ResponseExample: string(ov), - }) - } - - var groupslice []*MethodGroup - for _, g := range groups { - groupslice = append(groupslice, g) - } - - sort.Slice(groupslice, func(i, j int) bool { - return groupslice[i].GroupName < groupslice[j].GroupName - }) - - fmt.Printf("# Groups\n") - - for _, g := range groupslice { - fmt.Printf("* [%s](#%s)\n", g.GroupName, g.GroupName) - for _, method := range g.Methods { - fmt.Printf(" * [%s](#%s)\n", method.Name, method.Name) - } - } - - for _, g := range groupslice { - g := g - fmt.Printf("## %s\n", g.GroupName) - fmt.Printf("%s\n\n", g.Header) - - sort.Slice(g.Methods, func(i, j int) bool { - return g.Methods[i].Name < g.Methods[j].Name - }) - - for _, m := range g.Methods { - fmt.Printf("### %s\n", m.Name) - fmt.Printf("%s\n\n", m.Comment) - - meth, ok := permStruct.FieldByName(m.Name) - if !ok { - meth, ok = commonPermStruct.FieldByName(m.Name) - if !ok { - panic("no perms for method: " + m.Name) - } - } - - perms := meth.Tag.Get("perm") - - fmt.Printf("Perms: %s\n\n", perms) - - if strings.Count(m.InputExample, "\n") > 0 { - fmt.Printf("Inputs:\n```json\n%s\n```\n\n", m.InputExample) - } else { - fmt.Printf("Inputs: `%s`\n\n", m.InputExample) - } - - if strings.Count(m.ResponseExample, "\n") > 0 { - fmt.Printf("Response:\n```json\n%s\n```\n\n", m.ResponseExample) - } else { - fmt.Printf("Response: `%s`\n\n", m.ResponseExample) - } - } - } -} diff --git a/api/mocks/mock_full.go b/api/mocks/mock_full.go index 0b76c784d..8fd646d9a 100644 --- a/api/mocks/mock_full.go +++ b/api/mocks/mock_full.go @@ -18,6 +18,7 @@ import ( dline "github.com/filecoin-project/go-state-types/dline" network "github.com/filecoin-project/go-state-types/network" api "github.com/filecoin-project/lotus/api" + apitypes "github.com/filecoin-project/lotus/api/types" miner "github.com/filecoin-project/lotus/chain/actors/builtin/miner" types "github.com/filecoin-project/lotus/chain/types" marketevents "github.com/filecoin-project/lotus/markets/loggers" @@ -783,6 +784,21 @@ func (mr *MockFullNodeMockRecorder) CreateBackup(arg0, arg1 interface{}) *gomock return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateBackup", reflect.TypeOf((*MockFullNode)(nil).CreateBackup), arg0, arg1) } +// Discover mocks base method +func (m *MockFullNode) Discover(arg0 context.Context) (apitypes.OpenRPCDocument, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Discover", arg0) + ret0, _ := ret[0].(apitypes.OpenRPCDocument) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Discover indicates an expected call of Discover +func (mr *MockFullNodeMockRecorder) Discover(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Discover", reflect.TypeOf((*MockFullNode)(nil).Discover), arg0) +} + // GasEstimateFeeCap mocks base method func (m *MockFullNode) GasEstimateFeeCap(arg0 context.Context, arg1 *types.Message, arg2 int64, arg3 types.TipSetKey) (big.Int, error) { m.ctrl.T.Helper() diff --git a/api/types/openrpc.go b/api/types/openrpc.go new file mode 100644 index 000000000..7d65cbde6 --- /dev/null +++ b/api/types/openrpc.go @@ -0,0 +1,3 @@ +package apitypes + +type OpenRPCDocument map[string]interface{} diff --git a/build/openrpc.go b/build/openrpc.go new file mode 100644 index 000000000..0f514c8aa --- /dev/null +++ b/build/openrpc.go @@ -0,0 +1,43 @@ +package build + +import ( + "bytes" + "compress/gzip" + "encoding/json" + + rice "github.com/GeertJohan/go.rice" + + apitypes "github.com/filecoin-project/lotus/api/types" +) + +func mustReadGzippedOpenRPCDocument(data []byte) apitypes.OpenRPCDocument { + zr, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + log.Fatal(err) + } + m := apitypes.OpenRPCDocument{} + err = json.NewDecoder(zr).Decode(&m) + if err != nil { + log.Fatal(err) + } + err = zr.Close() + if err != nil { + log.Fatal(err) + } + return m +} + +func OpenRPCDiscoverJSON_Full() apitypes.OpenRPCDocument { + data := rice.MustFindBox("openrpc").MustBytes("full.json.gz") + return mustReadGzippedOpenRPCDocument(data) +} + +func OpenRPCDiscoverJSON_Miner() apitypes.OpenRPCDocument { + data := rice.MustFindBox("openrpc").MustBytes("miner.json.gz") + return mustReadGzippedOpenRPCDocument(data) +} + +func OpenRPCDiscoverJSON_Worker() apitypes.OpenRPCDocument { + data := rice.MustFindBox("openrpc").MustBytes("worker.json.gz") + return mustReadGzippedOpenRPCDocument(data) +} diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..bd38746b0f8413c4f8f433a0129952177255f897 GIT binary patch literal 22794 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 literal 0 HcmV?d00001 diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..23501e9118ab6ca00ed613185faf0ee69442af41 GIT binary patch literal 8259 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 ./ diff --git a/go.sum b/go.sum index d8175785c..e4b3d7d0e 100644 --- a/go.sum +++ b/go.sum @@ -42,8 +42,15 @@ github.com/Gurpartap/async v0.0.0-20180927173644-4f7f499dd9ee h1:8doiS7ib3zi6/K1 github.com/Gurpartap/async v0.0.0-20180927173644-4f7f499dd9ee/go.mod h1:W0GbEAA4uFNYOGG2cJpmFJ04E6SD1NLELPYZB57/7AY= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= +github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic= +github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= @@ -58,6 +65,8 @@ github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBA github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/akavel/rsrc v0.8.0 h1:zjWn7ukO9Kc5Q62DOJCcxGpXC18RawVtYAGdz2aLlfw= github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= +github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 h1:T3+cD5fYvuH36h7EZq+TDpm+d8a6FSD4pQsbmuGGQ8o= +github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921/go.mod h1:/n6+1/DWPltRLWL/VKyUxg6tzsl5kHUCcraimt4vr60= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -140,6 +149,7 @@ github.com/cockroachdb/redact v0.0.0-20200622112456-cd282804bbd3 h1:2+dpIJzYMSbL github.com/cockroachdb/redact v0.0.0-20200622112456-cd282804bbd3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/codegangsta/cli v1.20.0/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkbQ3slBdOA= github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327 h1:7grrpcfCtbZLsjtB0DgMuzs1umsJmpzaHMZ6cO6iAWw= github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -155,6 +165,7 @@ github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7 github.com/coreos/go-systemd/v22 v22.1.0 h1:kq/SbG2BCKLkDKkjQf5OWwKWUKj1lgs3lFI4PxnR5lg= github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/corpix/uarand v0.1.1/go.mod h1:SFKZvkcRoLqVRFZ4u25xPmp6m9ktANfbpXZ7SJ0/FNU= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -223,6 +234,10 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/etclabscore/go-jsonschema-walk v0.0.6 h1:DrNzoKWKd8f8XB5nFGBY00IcjakRE22OTI12k+2LkyY= +github.com/etclabscore/go-jsonschema-walk v0.0.6/go.mod h1:VdfDY72AFAiUhy0ZXEaWSpveGjMT5JcDIm903NGqFwQ= +github.com/etclabscore/go-openrpc-reflect v0.0.36 h1:kSqNB2U8RVoW4si+4fsv13NGNkRAQ5j78zTUx1qiehk= +github.com/etclabscore/go-openrpc-reflect v0.0.36/go.mod h1:0404Ky3igAasAOpyj1eESjstTyneBAIk5PgJFbK4s5E= github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5 h1:BBso6MBKW8ncyZLv37o+KNyy0HrrHgfnOaGQC2qvN+A= github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5/go.mod h1:JpoxHjuQauoxiFMl1ie8Xc/7TfLuMZ5eOCONd1sUBHg= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -333,6 +348,21 @@ github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.4 h1:3Vw+rh13uq2JFNxgnMTGE1rnoieU9FmyE1gvnyylsYg= +github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/spec v0.19.7/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/spec v0.19.11 h1:ogU5q8dtp3MMPn59a9VRrPKVxvJHEs5P7yNMR5sNnis= +github.com/go-openapi/spec v0.19.11/go.mod h1:vqK/dIdLGCosfvYsQV3WfC7N3TiZSnGY2RZKoFK7X28= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.8/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= +github.com/go-openapi/swag v0.19.11 h1:RFTu/dlFySpyVvJDfp/7674JY4SDglYWKztbiIGFpmc= +github.com/go-openapi/swag v0.19.11/go.mod h1:Uc0gKkdR+ojzsEpjh39QChyu92vPgIr72POcgHMAgSY= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= @@ -366,6 +396,7 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -484,7 +515,11 @@ github.com/huin/goupnp v0.0.0-20180415215157-1395d1447324/go.mod h1:MZ2ZmwcBpvOo github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= +github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= +github.com/iancoleman/orderedmap v0.1.0 h1:2orAxZBJsvimgEBmMWfXaFlzSG2fbQil5qzP3F6cCkg= +github.com/iancoleman/orderedmap v0.1.0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428/go.mod h1:uhpZMVGznybq1itEKXj6RYw9I71qK4kH+OGMjRC4KEo= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d h1:/WZQPMZNsjZ7IlCpsLGdQBINg5bxKQ1K1sh6awxLtkA= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= @@ -710,6 +745,8 @@ github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.1.1-0.20190114141812-62fb9bc030d1 h1:qBCV/RLV02TSfQa7tFmxTihnG+u+7JXByOkhlkR5rmQ= github.com/jonboulle/clockwork v0.1.1-0.20190114141812-62fb9bc030d1/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= @@ -1078,6 +1115,11 @@ github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0Q github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/marten-seemann/qpack v0.1.0/go.mod h1:LFt1NU/Ptjip0C2CPkhimBz5CGE3WGDAUWqna+CNTrI= github.com/marten-seemann/qpack v0.2.0/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk= @@ -1212,6 +1254,7 @@ github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/ngdinhtoan/glide-cleanup v0.2.0/go.mod h1:UQzsmiDOb8YV3nOsCxK/c9zPpCZVNoHScRE3EO9pVMM= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nikkolasg/hexjson v0.0.0-20181101101858-78e39397e00c h1:5bFTChQxSKNwy8ALwOebjekYExl9HTT9urdawqC95tA= @@ -1239,6 +1282,8 @@ github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoT github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333 h1:CznVS40zms0Dj5he4ERo+fRPtO0qxUk8lA8Xu3ddet0= +github.com/open-rpc/meta-schema v0.0.0-20201029221707-1b72ef2ea333/go.mod h1:Ag6rSXkHIckQmjFBCweJEEt1mrTPBv8b9W4aU/NQWfI= github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opentracing-contrib/go-grpc v0.0.0-20180928155321-4b5a12d3ff02/go.mod h1:JNdpVEzCpXBgIiv4ds+TzhN1hrtxq6ClLrTlT9OQRSc= @@ -1406,10 +1451,12 @@ github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3 github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -1420,6 +1467,12 @@ github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFd github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/texttheater/golang-levenshtein v0.0.0-20180516184445-d188e65d659e/go.mod h1:XDKHRm5ThF8YJjx001LtgelzsoaEcvnA7lVWz9EeX3g= +github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc= +github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= +github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc= +github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tj/go-spin v1.1.0 h1:lhdWZsvImxvZ3q1C5OIB7d72DuOwP4O2NdBg9PyzNds= github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -1664,13 +1717,15 @@ golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200519113804-d87ec0cfa476/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201022231255-08b38378de70 h1:Z6x4N9mAi4oF0TbHweCsH618MO6OI6UFgV0FP5n0wBY= +golang.org/x/net v0.0.0-20201022231255-08b38378de70/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1798,6 +1853,7 @@ golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -1928,8 +1984,9 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M= howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= modernc.org/cc v1.0.0 h1:nPibNuDEx6tvYrUAtvDTTw98rx5juGsa5zuDnKwEEQQ= diff --git a/node/impl/common/common.go b/node/impl/common/common.go index 389e2fbc6..7d99fb42a 100644 --- a/node/impl/common/common.go +++ b/node/impl/common/common.go @@ -24,6 +24,7 @@ import ( "github.com/filecoin-project/go-jsonrpc/auth" "github.com/filecoin-project/lotus/api" + apitypes "github.com/filecoin-project/lotus/api/types" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/node/modules/dtypes" "github.com/filecoin-project/lotus/node/modules/lp2p" @@ -207,6 +208,10 @@ func (a *CommonAPI) NetBandwidthStatsByProtocol(ctx context.Context) (map[protoc return a.Reporter.GetBandwidthByProtocol(), nil } +func (a *CommonAPI) Discover(ctx context.Context) (apitypes.OpenRPCDocument, error) { + return build.OpenRPCDiscoverJSON_Full(), nil +} + func (a *CommonAPI) ID(context.Context) (peer.ID, error) { return a.Host.ID(), nil } diff --git a/node/impl/storminer.go b/node/impl/storminer.go index cde168bea..e81560059 100644 --- a/node/impl/storminer.go +++ b/node/impl/storminer.go @@ -8,6 +8,7 @@ import ( "strconv" "time" + "github.com/filecoin-project/lotus/build" "github.com/google/uuid" "github.com/ipfs/go-cid" "github.com/libp2p/go-libp2p-core/host" @@ -31,6 +32,7 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/api/apistruct" + apitypes "github.com/filecoin-project/lotus/api/types" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/markets/storageadapter" "github.com/filecoin-project/lotus/miner" @@ -690,4 +692,8 @@ func (sm *StorageMinerAPI) ActorAddressConfig(ctx context.Context) (api.AddressC return sm.AddrSel.AddressConfig, nil } +func (sm *StorageMinerAPI) Discover(ctx context.Context) (apitypes.OpenRPCDocument, error) { + return build.OpenRPCDiscoverJSON_Miner(), nil +} + var _ api.StorageMiner = &StorageMinerAPI{}