From 15f779189a37f5f30cd17844e0ed36389b91c7d8 Mon Sep 17 00:00:00 2001 From: laizy Date: Mon, 26 Oct 2020 17:26:38 +0800 Subject: [PATCH] implement graphql api (#1274) --- cmd/config.go | 8 + cmd/usage.go | 8 + cmd/utils/flags.go | 20 +- common/common.go | 20 ++ common/config/config.go | 14 +- consensus/vbft/config/types.go | 15 +- go.mod | 1 + go.sum | 2 + http/graphql/scalar.go | 171 +++++++++++++ http/graphql/schema/bindata.go | 263 ++++++++++++++++++++ http/graphql/schema/generate.go | 20 ++ http/graphql/schema/schema.graphql | 119 +++++++++ http/graphql/service.go | 372 +++++++++++++++++++++++++++++ main.go | 15 ++ 14 files changed, 1032 insertions(+), 16 deletions(-) create mode 100644 http/graphql/scalar.go create mode 100644 http/graphql/schema/bindata.go create mode 100644 http/graphql/schema/generate.go create mode 100644 http/graphql/schema/schema.graphql create mode 100644 http/graphql/service.go diff --git a/cmd/config.go b/cmd/config.go index b53b252323..0e6c1de913 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -40,10 +40,12 @@ func SetOntologyConfig(ctx *cli.Context) (*config.OntologyConfig, error) { setP2PNodeConfig(ctx, cfg.P2PNode) setRpcConfig(ctx, cfg.Rpc) setRestfulConfig(ctx, cfg.Restful) + setGraphQLConfig(ctx, cfg.GraphQL) setWebSocketConfig(ctx, cfg.Ws) if cfg.Genesis.ConsensusType == config.CONSENSUS_TYPE_SOLO { cfg.Ws.EnableHttpWs = true cfg.Restful.EnableHttpRestful = true + cfg.GraphQL.EnableGraphQL = true cfg.Consensus.EnableConsensus = true cfg.P2PNode.NetworkId = config.NETWORK_ID_SOLO_NET cfg.P2PNode.NetworkName = config.GetNetworkName(cfg.P2PNode.NetworkId) @@ -187,6 +189,12 @@ func setRestfulConfig(ctx *cli.Context, cfg *config.RestfulConfig) { cfg.HttpMaxConnections = ctx.Uint(utils.GetFlagName(utils.RestfulMaxConnsFlag)) } +func setGraphQLConfig(ctx *cli.Context, cfg *config.GraphQLConfig) { + cfg.EnableGraphQL = ctx.Bool(utils.GetFlagName(utils.GraphQLEnableFlag)) + cfg.GraphQLPort = ctx.Uint(utils.GetFlagName(utils.GraphQLPortFlag)) + cfg.MaxConnections = ctx.Uint(utils.GetFlagName(utils.GraphQLMaxConnsFlag)) +} + func setWebSocketConfig(ctx *cli.Context, cfg *config.WebSocketConfig) { cfg.EnableHttpWs = ctx.Bool(utils.GetFlagName(utils.WsEnabledFlag)) cfg.HttpWsPort = ctx.Uint(utils.GetFlagName(utils.WsPortFlag)) diff --git a/cmd/usage.go b/cmd/usage.go index 62177473bb..5f3a8c896e 100644 --- a/cmd/usage.go +++ b/cmd/usage.go @@ -177,6 +177,14 @@ var AppHelpFlagGroups = []flagGroup{ utils.RestfulMaxConnsFlag, }, }, + { + Name: "GRAPHQL", + Flags: []cli.Flag{ + utils.GraphQLEnableFlag, + utils.GraphQLPortFlag, + utils.GraphQLMaxConnsFlag, + }, + }, { Name: "WEB SOCKET", Flags: []cli.Flag{ diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 5c90f187d9..58e2e918f6 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -82,7 +82,6 @@ var ( Usage: "Block data storage ``", Value: config.DEFAULT_DATA_DIR, } - //Consensus setting EnableConsensusFlag = cli.BoolFlag{ Name: "enable-consensus", @@ -103,7 +102,6 @@ var ( Usage: "Min gas price `` of transaction to be accepted by tx pool.", Value: config.DEFAULT_GAS_PRICE, } - //Test Mode setting EnableTestModeFlag = cli.BoolFlag{ Name: "testmode", @@ -199,7 +197,23 @@ var ( RestfulMaxConnsFlag = cli.UintFlag{ Name: "restmaxconns", Usage: "Restful server maximum connections ``", - Value: config.DEFAULT_REST_MAX_CONN, + Value: config.DEFAULT_HTTP_MAX_CONN, + } + + //GraphQL setting + GraphQLEnableFlag = cli.BoolFlag{ + Name: "graphql", + Usage: "Enable graphql api server", + } + GraphQLPortFlag = cli.UintFlag{ + Name: "graphql-port", + Usage: "GraphQL server listening port ``", + Value: config.DEFAULT_GRAPHQL_PORT, + } + GraphQLMaxConnsFlag = cli.UintFlag{ + Name: "graphql-max-connection", + Usage: "GraphQL server maximum connections ``", + Value: config.DEFAULT_HTTP_MAX_CONN, } //Account setting diff --git a/common/common.go b/common/common.go index c8a2f4023e..7101a5fb0e 100644 --- a/common/common.go +++ b/common/common.go @@ -20,8 +20,11 @@ package common import ( "encoding/hex" + "fmt" "math/rand" "os" + + "github.com/ontio/ontology-crypto/keypair" ) // GetNonce returns random nonce @@ -55,3 +58,20 @@ func FileExisted(filename string) bool { _, err := os.Stat(filename) return err == nil || os.IsExist(err) } + +func PubKeyToHex(pub keypair.PublicKey) string { + nodeid := hex.EncodeToString(keypair.SerializePublicKey(pub)) + return nodeid +} + +func PubKeyFromHex(nodeid string) (keypair.PublicKey, error) { + pubKey, err := hex.DecodeString(nodeid) + if err != nil { + return nil, err + } + pk, err := keypair.DeserializePublicKey(pubKey) + if err != nil { + return nil, fmt.Errorf("deserialize failed: %s", err) + } + return pk, err +} diff --git a/common/config/config.go b/common/config/config.go index 9809a1d441..044ba990b3 100644 --- a/common/config/config.go +++ b/common/config/config.go @@ -60,9 +60,10 @@ const ( DEFAULT_NODE_PORT = 20338 DEFAULT_RPC_PORT = 20336 DEFAULT_RPC_LOCAL_PORT = 20337 + DEFAULT_GRAPHQL_PORT = 20333 DEFAULT_REST_PORT = 20334 DEFAULT_WS_PORT = 20335 - DEFAULT_REST_MAX_CONN = 1024 + DEFAULT_HTTP_MAX_CONN = 1024 DEFAULT_MAX_CONN_IN_BOUND = 1024 DEFAULT_MAX_CONN_OUT_BOUND = 1024 DEFAULT_MAX_CONN_IN_BOUND_FOR_SINGLE_IP = 16 @@ -648,6 +649,12 @@ type RestfulConfig struct { HttpKeyPath string } +type GraphQLConfig struct { + EnableGraphQL bool + GraphQLPort uint + MaxConnections uint +} + type WebSocketConfig struct { EnableHttpWs bool HttpWsPort uint @@ -662,6 +669,7 @@ type OntologyConfig struct { P2PNode *P2PNodeConfig Rpc *RpcConfig Restful *RestfulConfig + GraphQL *GraphQLConfig Ws *WebSocketConfig } @@ -706,6 +714,10 @@ func NewOntologyConfig() *OntologyConfig { EnableHttpRestful: true, HttpRestPort: DEFAULT_REST_PORT, }, + GraphQL: &GraphQLConfig{ + EnableGraphQL: false, + GraphQLPort: DEFAULT_GRAPHQL_PORT, + }, Ws: &WebSocketConfig{ EnableHttpWs: true, HttpWsPort: DEFAULT_WS_PORT, diff --git a/consensus/vbft/config/types.go b/consensus/vbft/config/types.go index 3234b35f62..51b593f818 100644 --- a/consensus/vbft/config/types.go +++ b/consensus/vbft/config/types.go @@ -19,30 +19,21 @@ package vconfig import ( - "encoding/hex" "encoding/json" "fmt" "github.com/ontio/ontology-crypto/keypair" + "github.com/ontio/ontology/common" "github.com/ontio/ontology/core/types" ) // PubkeyID returns a marshaled representation of the given public key. func PubkeyID(pub keypair.PublicKey) string { - nodeid := hex.EncodeToString(keypair.SerializePublicKey(pub)) - return nodeid + return common.PubKeyToHex(pub) } func Pubkey(nodeid string) (keypair.PublicKey, error) { - pubKey, err := hex.DecodeString(nodeid) - if err != nil { - return nil, err - } - pk, err := keypair.DeserializePublicKey(pubKey) - if err != nil { - return nil, fmt.Errorf("deserialize failed: %s", err) - } - return pk, err + return common.PubKeyFromHex(nodeid) } func VbftBlock(header *types.Header) (*VbftBlockInfo, error) { diff --git a/go.mod b/go.mod index 55fc4b2205..db8e813165 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/gorilla/websocket v1.4.1 github.com/gosuri/uilive v0.0.3 // indirect github.com/gosuri/uiprogress v0.0.1 + github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 github.com/hashicorp/golang-lru v0.5.3 github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c github.com/itchyny/base58-go v0.1.0 diff --git a/go.sum b/go.sum index 027004bb5e..59d54c8936 100644 --- a/go.sum +++ b/go.sum @@ -102,6 +102,7 @@ github.com/gosuri/uilive v0.0.3 h1:kvo6aB3pez9Wbudij8srWo4iY6SFTTxTKOkb+uRCE8I= github.com/gosuri/uilive v0.0.3/go.mod h1:qkLSc0A5EXSP6B04TrN4oQoxqFI7A8XvoXSlJi8cwk8= github.com/gosuri/uiprogress v0.0.1 h1:0kpv/XY/qTmFWl/SkaJykZXrBBzwwadmW8fRb7RJSxw= github.com/gosuri/uiprogress v0.0.1/go.mod h1:C1RTYn4Sc7iEyf6j8ft5dyoZ4212h8G1ol9QQluh5+0= +github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 h1:E0whKxgp2ojts0FDgUA8dl62bmH0LxKanMoBr6MDTDM= github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk= @@ -157,6 +158,7 @@ github.com/ontio/ontology-eventbus v0.9.1 h1:nt3AXWx3gOyqtLiU4EwI92Yc4ik/pWHu9xR github.com/ontio/ontology-eventbus v0.9.1/go.mod h1:hCQIlbdPckcfykMeVUdWrqHZ8d30TBdmLfXCVWGkYhM= github.com/ontio/wagon v0.4.1 h1:3A8BxTMVGrQnyWxD1h8w5PLvN9GZMWjC75Jw+5Vgpe0= github.com/ontio/wagon v0.4.1/go.mod h1:oTPdgWT7WfPlEyzVaHSn1vQPMSbOpQPv+WphxibWlhg= +github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6 h1:lNCW6THrCKBiJBpz8kbVGjC7MgdCGKwuvBgc7LoD6sw= github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI= diff --git a/http/graphql/scalar.go b/http/graphql/scalar.go new file mode 100644 index 0000000000..637ec5a8ea --- /dev/null +++ b/http/graphql/scalar.go @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2018 The ontology Authors + * This file is part of The ontology library. + * + * The ontology is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ontology is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with The ontology. If not, see . + */ +package graphql + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" + + "github.com/ontio/ontology/common" +) + +type Uint32 uint32 + +func (Uint32) ImplementsGraphQLType(name string) bool { + return name == "Uint32" +} + +func (t *Uint32) UnmarshalGraphQL(input interface{}) error { + switch input := input.(type) { + case uint32: + *t = Uint32(input) + return nil + case int32: + *t = Uint32(input) + return nil + case int64: + *t = Uint32(input) + return nil + default: + return fmt.Errorf("wrong type for Uint32: %T", input) + } +} + +func (t Uint32) MarshalJSON() ([]byte, error) { + return json.Marshal(uint32(t)) +} + +type Uint64 uint64 + +func (Uint64) ImplementsGraphQLType(name string) bool { + return name == "Uint64" +} + +func (t *Uint64) UnmarshalGraphQL(input interface{}) error { + switch input := input.(type) { + case uint32: + *t = Uint64(input) + case int32: + *t = Uint64(input) + case int: + *t = Uint64(input) + case uint: + *t = Uint64(input) + case uint64: + *t = Uint64(input) + case int64: + *t = Uint64(input) + case string: + val, err := strconv.ParseUint(input, 10, 64) + if err != nil { + return fmt.Errorf("wrong type for Uint64: %T", input) + } + *t = Uint64(val) + default: + return fmt.Errorf("wrong type for Uint64: %T", input) + } + return nil +} + +func (t Uint64) MarshalJSON() ([]byte, error) { + return json.Marshal(strconv.FormatUint(uint64(t), 10)) +} + +type Addr struct { + common.Address +} + +func (Addr) ImplementsGraphQLType(name string) bool { + return name == "Address" +} + +func (t *Addr) UnmarshalGraphQL(input interface{}) error { + var err error + switch input := input.(type) { + case string: + if strings.HasPrefix(input, "0x") { + t.Address, err = common.AddressFromHexString(input[2:]) + } + + if err == nil { + t.Address, err = common.AddressFromBase58(input) + } + default: + return fmt.Errorf("wrong type for Address: %T", input) + } + return err +} + +func (t Addr) MarshalJSON() ([]byte, error) { + return json.Marshal(t.Address.ToBase58()) +} + +type H256 common.Uint256 + +func (H256) ImplementsGraphQLType(name string) bool { + return name == "H256" +} + +func (t *H256) UnmarshalGraphQL(input interface{}) error { + switch input := input.(type) { + case string: + if strings.HasPrefix(input, "0x") { + input = input[2:] + } + + hash, err := common.Uint256FromHexString(input) + if err != nil { + return err + } + *t = H256(hash) + return nil + default: + return fmt.Errorf("wrong type for H256: %T", input) + } +} + +func (t H256) MarshalJSON() ([]byte, error) { + hash := common.Uint256(t) + return json.Marshal(hash.ToHexString()) +} + +type PubKey string + +func (PubKey) ImplementsGraphQLType(name string) bool { + return name == "PubKey" +} + +func (key *PubKey) UnmarshalGraphQL(input interface{}) error { + switch input := input.(type) { + case string: + _, err := common.PubKeyFromHex(input) + if err != nil { + return err + } + *key = PubKey(input) + return nil + default: + return fmt.Errorf("wrong type for PubKey: %T", input) + } +} + +func (key PubKey) MarshalJSON() ([]byte, error) { + return json.Marshal(string(key)) +} diff --git a/http/graphql/schema/bindata.go b/http/graphql/schema/bindata.go new file mode 100644 index 0000000000..7edb879a88 --- /dev/null +++ b/http/graphql/schema/bindata.go @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2018 The ontology Authors + * This file is part of The ontology library. + * + * The ontology is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ontology is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with The ontology. If not, see . + */ + +// Code generated by go-bindata. (@generated) DO NOT EDIT. + +// Package schema generated by go-bindata.// sources: +// schema.graphql +package schema + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" +) + +func bindataRead(data []byte, name string) ([]byte, error) { + gz, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + return nil, fmt.Errorf("read %q: %v", name, err) + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, gz) + clErr := gz.Close() + + if err != nil { + return nil, fmt.Errorf("read %q: %v", name, err) + } + if clErr != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type asset struct { + bytes []byte + info os.FileInfo +} + +type bindataFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +// Name return file name +func (fi bindataFileInfo) Name() string { + return fi.name +} + +// Size return file size +func (fi bindataFileInfo) Size() int64 { + return fi.size +} + +// Mode return file mode +func (fi bindataFileInfo) Mode() os.FileMode { + return fi.mode +} + +// ModTime return file modify time +func (fi bindataFileInfo) ModTime() time.Time { + return fi.modTime +} + +// IsDir return file whether a directory +func (fi bindataFileInfo) IsDir() bool { + return fi.mode&os.ModeDir != 0 +} + +// Sys return file is sys mode +func (fi bindataFileInfo) Sys() interface{} { + return nil +} + +var _schemaGraphql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x94\x55\x4d\x6f\xdb\x30\x0c\xbd\xfb\x57\xb0\xc8\xa5\xbb\xf4\xd0\xb5\xc5\xe0\x5b\xb3\x16\x48\xd1\xaf\x6c\xc9\x36\x0c\x45\x31\x30\x32\x6b\x0b\xb1\x25\x4f\x94\xd3\x18\x45\xff\xfb\x20\xcb\x76\xe5\xc4\x1d\xd0\x53\x22\xea\xf1\xe9\x3d\x92\x92\x27\xb0\x42\xa6\xd3\x2f\xa0\x0d\x64\xb4\x05\xb6\x46\xaa\x14\x30\x49\x0c\x31\x47\x2c\x30\x47\x03\xe7\xed\x72\x12\x62\xf4\x13\x64\xc8\xd9\xf1\xe9\x59\x07\x9b\xb9\xff\xbb\x98\xb2\x5a\xe5\x52\xc0\x9a\xea\x0e\x36\xaf\x56\xd7\x54\x47\xdd\xf2\x87\x54\xf6\xf3\x71\x34\x81\x4a\x2a\x7b\x76\x02\xa4\x84\x4e\x28\x01\xe4\x96\x25\x04\x9e\x9d\x44\x11\xa9\xaa\x80\xe5\x76\x59\x97\x04\x2f\x11\x00\xc0\xd5\xdd\xcf\xfb\xeb\xcb\x3f\x77\x97\xf7\xe1\xf2\xd7\xf9\xe2\xb6\x59\x5f\x5c\xce\x6f\xee\x7f\xf7\xdb\xed\xb2\xd9\x7e\x8d\x22\xa9\x2c\x99\x27\x14\x04\x73\xac\x73\x8d\x49\x4b\xea\x54\x40\x0c\x8b\x46\xc3\x81\x43\x5a\x77\xe2\x95\xda\xe8\x35\x7d\x75\x9b\xb2\x28\x73\x2a\x48\x59\x1e\x49\xdd\xcf\xbc\xa0\x32\xd7\xf5\x47\x32\x5d\x64\x53\x38\xa3\xc3\x98\xc2\x62\x17\x45\x86\xa5\x56\xc3\x20\x56\x36\xd3\x66\x18\xa3\x02\x65\x3e\x0c\x25\xc4\x62\xa0\x76\x02\xd6\xa0\x62\x14\x56\x6a\xe5\x9a\x50\x09\x5b\x19\x72\xdd\xd4\xca\xea\x5c\xa7\xb5\x77\xb4\x0c\x60\x2f\x43\x1d\xbe\xab\xfe\x00\x37\x26\x71\x33\x1d\xad\x7c\xad\x04\x0d\x21\x76\xeb\x5d\xfa\xb6\xfa\x58\x8a\x3c\x37\x72\x17\x99\x22\xdf\xc8\x42\xda\x61\xb4\xc4\x9a\x4c\xdc\x0d\x6a\x1f\x73\x95\x8d\xbb\x12\xfb\x28\xcb\x94\x63\x78\x58\xc8\xf4\xe0\xf1\x20\xf2\xfa\x48\xa6\x59\x40\xd8\x35\x6c\x21\xd3\xd6\x16\xcb\xf4\x02\x2d\xba\x3c\x5f\xa6\xc7\xf6\x88\x66\x94\x1d\x9f\x1f\xea\x2e\x7e\x3b\x20\x9b\xc0\x34\xd7\x62\xfd\xbf\x4a\x7a\x80\x3f\x6c\x02\xcb\x8c\x20\x23\x4c\xc8\x38\xa4\xcd\x24\xc3\xca\x01\x8e\x5a\xb9\x6e\x27\x86\x59\xf3\xdb\x7a\xf0\x49\x41\xdf\x38\xc8\x03\xa9\x44\x5e\x25\x94\x78\x82\x10\x15\xc3\x43\xd0\x45\xa7\xbf\x11\xec\xb9\x41\x3a\x96\x50\x0b\x7a\x42\x2f\xba\x05\x85\xaa\xdb\xfe\xf7\xb2\x7d\xe6\xd1\xf8\x6c\x84\x6e\x91\xb3\xd1\xa4\x70\x76\x02\x7c\x69\x68\x23\x75\xd5\xf9\x73\x28\x8f\x77\x1b\xb3\xf1\x1c\x51\x19\x43\xca\x76\x29\x4d\xd3\x8f\x46\x07\x20\xac\xa8\x2c\x88\x2d\x16\x65\x58\xce\x94\x14\x19\xb4\x7d\x3d\x3b\xcc\x28\x43\x41\x66\x9d\xbb\xd6\x10\x81\xd1\xda\xc2\xb3\xb4\x19\xe4\x84\x1b\x62\x78\x32\xba\x68\xe8\xb8\x27\xb7\x7a\xaf\xe3\xcd\xdf\xef\x5a\xdb\x11\x57\x83\x96\x37\xfc\x23\x23\x63\xb7\xfc\x4e\xba\xd0\x8a\x49\x71\xc5\x90\xa0\xc5\xb1\xdc\x1e\xe1\x6f\x80\x7f\x85\x87\x0e\xab\xdc\xca\xee\x9b\xe1\x28\x84\x56\xba\x65\x55\x3a\x21\x86\xe7\x4c\x83\x40\xd5\x17\x0e\x14\x6d\x6d\x78\x88\x5b\x4f\xb5\x5e\xaf\x89\xca\xc1\x45\x1e\x4a\xdd\x67\xed\x19\xf7\x6a\xd6\xb3\x0d\xaf\x67\x40\xc8\x32\x55\xd8\xdd\xc7\x8f\xb1\x8f\x3d\x08\xdd\xb3\x31\xc5\x1c\x95\xe8\xbe\x4d\x5a\xd9\xb7\xa2\xf9\x40\x3a\x0c\xbc\xf7\xfc\x7c\xab\xc8\xd4\x2d\x4b\x4a\xb6\x79\x22\xa6\xf5\xac\x41\x1f\xee\x24\x7d\x8a\xfd\x13\xb2\x0b\x46\xce\x0e\x83\x2b\x34\x0a\xf3\xa0\x3d\xbe\xb7\xf7\x3a\x25\xbb\xdc\xee\xd0\x04\x8f\x46\x4f\xe6\x7d\x1f\xba\x41\x78\xeb\xa0\x3b\xd2\x6f\x34\xce\x58\x64\x54\x60\xeb\xea\xaf\x73\x18\x7b\xa3\xd1\x6b\xf4\x2f\x00\x00\xff\xff\x28\x4d\x93\xb3\x8d\x08\x00\x00") + +func schemaGraphqlBytes() ([]byte, error) { + return bindataRead( + _schemaGraphql, + "schema.graphql", + ) +} + +func schemaGraphql() (*asset, error) { + bytes, err := schemaGraphqlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "schema.graphql", size: 2189, mode: os.FileMode(438), modTime: time.Unix(1598341445, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +// Asset loads and returns the asset for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func Asset(name string) ([]byte, error) { + canonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[canonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) + } + return a.bytes, nil + } + return nil, fmt.Errorf("Asset %s not found", name) +} + +// MustAsset is like Asset but panics when Asset would return an error. +// It simplifies safe initialization of global variables. +func MustAsset(name string) []byte { + a, err := Asset(name) + if err != nil { + panic("asset: Asset(" + name + "): " + err.Error()) + } + + return a +} + +// AssetInfo loads and returns the asset info for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func AssetInfo(name string) (os.FileInfo, error) { + canonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[canonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) + } + return a.info, nil + } + return nil, fmt.Errorf("AssetInfo %s not found", name) +} + +// AssetNames returns the names of the assets. +func AssetNames() []string { + names := make([]string, 0, len(_bindata)) + for name := range _bindata { + names = append(names, name) + } + return names +} + +// _bindata is a table, holding each asset generator, mapped to its name. +var _bindata = map[string]func() (*asset, error){ + "schema.graphql": schemaGraphql, +} + +// AssetDir returns the file names below a certain +// directory embedded in the file by go-bindata. +// For example if you run go-bindata on data/... and data contains the +// following hierarchy: +// data/ +// foo.txt +// img/ +// a.png +// b.png +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("nonexistent") would return an error +// AssetDir("") will return []string{"data"}. +func AssetDir(name string) ([]string, error) { + node := _bintree + if len(name) != 0 { + canonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(canonicalName, "/") + for _, p := range pathList { + node = node.Children[p] + if node == nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + } + } + if node.Func != nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + rv := make([]string, 0, len(node.Children)) + for childName := range node.Children { + rv = append(rv, childName) + } + return rv, nil +} + +type bintree struct { + Func func() (*asset, error) + Children map[string]*bintree +} + +var _bintree = &bintree{nil, map[string]*bintree{ + "schema.graphql": &bintree{schemaGraphql, map[string]*bintree{}}, +}} + +// RestoreAsset restores an asset under the given directory +func RestoreAsset(dir, name string) error { + data, err := Asset(name) + if err != nil { + return err + } + info, err := AssetInfo(name) + if err != nil { + return err + } + err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) + if err != nil { + return err + } + err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) + if err != nil { + return err + } + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil +} + +// RestoreAssets restores an asset under the given directory recursively +func RestoreAssets(dir, name string) error { + children, err := AssetDir(name) + // File + if err != nil { + return RestoreAsset(dir, name) + } + // Dir + for _, child := range children { + err = RestoreAssets(dir, filepath.Join(name, child)) + if err != nil { + return err + } + } + return nil +} + +func _filePath(dir, name string) string { + canonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...) +} diff --git a/http/graphql/schema/generate.go b/http/graphql/schema/generate.go new file mode 100644 index 0000000000..c2c2e42a0c --- /dev/null +++ b/http/graphql/schema/generate.go @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2018 The ontology Authors + * This file is part of The ontology library. + * + * The ontology is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ontology is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with The ontology. If not, see . + */ + +//go:generate go-bindata -ignore=\.go -pkg=schema -o=bindata.go ./... +package schema diff --git a/http/graphql/schema/schema.graphql b/http/graphql/schema/schema.graphql new file mode 100644 index 0000000000..6e85d3204a --- /dev/null +++ b/http/graphql/schema/schema.graphql @@ -0,0 +1,119 @@ +# base58 or hex string address +scalar Address +# hex string of hash256 +scalar H256 +# hex string of public key +scalar PubKey + +scalar Uint32 +# uint64 encoded as string +scalar Uint64 + +enum TxType { + INVOKE_NEO + INVOKE_WASM + DEPLOY_NEO + DEPLOY_WASM +} + +interface Payload { + code : String! +} + +type InvokeCode implements Payload { + code: String! +} + +type DeployCode implements Payload { + code: String! + vmType: String! + name: String! + version: String! + author: String! + email: String! + desc: String! +} + +# transaction structure of ontology +type Transaction { + version: Uint32! + hash: H256! + nonce: Uint32! + txType: TxType! + gasPrice: Uint32! + gasLimit: Uint32! + payer: Address! + payload: Payload! + sigs: [Sig!]! + + height: Uint32! +} + +type Sig { + sigData: [String!]! + pubKeys: [PubKey!]! + M: Uint32! +} + +# Block structure of ontology +type Block { + # The header of this block. + header: Header! + + # The transactions this block included. + transactions: [Transaction!]! +} + +# Header is the header of a block +type Header { + # The version of this header. + version: Uint32! + + # The hash of this header. + hash: H256! + + # The previous block hash. + prevHash: H256! + + # The current block height. + height: Uint32! + + # The timestamp this block generated. + timestamp: Uint32! + + # The merkle tree root with leaves from genesis block to this block. + blockRoot: H256! + + # The transactions root of this block. + txsRoot: H256! + + # The consensus data of this block. + consensusData: Uint64! + + # The multi address of conosensus nodes who can generate next block. + nextBookkeeper: Address! + + # The conosensus nodes who generate this block. + bookkeepers: [PubKey!]! + + # The signature of conosensus nodes who generate this block. + sigData: [String!]! +} + +type Balance { + ont: Uint64! + ong: Uint64! + height: Uint32! +} + +type Query { + getBlockByHeight(height: Uint32!): Block + getBlockByHash(hash: H256!): Block + getBlockHash(height: Uint32!): H256! + getTx(hash: H256!): Transaction + getBalance(addr: Address!): Balance! +} + +schema { + query: Query +} diff --git a/http/graphql/service.go b/http/graphql/service.go new file mode 100644 index 0000000000..0128f04bd1 --- /dev/null +++ b/http/graphql/service.go @@ -0,0 +1,372 @@ +/* + * Copyright (C) 2018 The ontology Authors + * This file is part of The ontology library. + * + * The ontology is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ontology is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with The ontology. If not, see . + */ + +package graphql + +import ( + "net" + "net/http" + "strconv" + + "github.com/graph-gophers/graphql-go" + "github.com/graph-gophers/graphql-go/relay" + "github.com/ontio/ontology/common" + "github.com/ontio/ontology/common/config" + "github.com/ontio/ontology/common/log" + "github.com/ontio/ontology/core/payload" + "github.com/ontio/ontology/core/types" + "github.com/ontio/ontology/http/base/actor" + comm "github.com/ontio/ontology/http/base/common" + "github.com/ontio/ontology/http/graphql/schema" + "github.com/ontio/ontology/smartcontract/service/native/utils" + "golang.org/x/net/netutil" +) + +var ontSchema *graphql.Schema + +func init() { + resolver := &resolver{} + s, err := schema.Asset("schema.graphql") + if err != nil { + panic(err) + } + + ontSchema = graphql.MustParseSchema(string(s), resolver, graphql.UseFieldResolvers()) +} + +type resolver struct{} +type block struct { + Header *header + Transactions []*transaction +} + +type header struct { + Version Uint32 + Hash H256 + PrevHash H256 + Height Uint32 + Timestamp Uint32 + BlockRoot H256 + TxsRoot H256 + ConsensusData Uint64 + NextBookkeeper Addr + Bookkeepers []PubKey + SigData []string +} + +type invokeCodePayload struct { + Code string +} + +type deployCodePayload struct { + Code string + VmType string + Name string + Version string + Author string + Email string + Desc string +} + +type TxPayload struct { + pl interface{} +} + +func (self *TxPayload) ToInvokeCode() (*invokeCodePayload, bool) { + pl, ok := self.pl.(*invokeCodePayload) + return pl, ok +} + +func (self *TxPayload) ToDeployCode() (*deployCodePayload, bool) { + pl, ok := self.pl.(*deployCodePayload) + return pl, ok +} + +func (self *TxPayload) Code() string { + switch pd := self.pl.(type) { + case *invokeCodePayload: + return pd.Code + case *deployCodePayload: + return pd.Code + default: + panic("unreachable") + } +} + +func NewTxPayload(pl types.Payload) *TxPayload { + switch val := pl.(type) { + case *payload.InvokeCode: + return &TxPayload{pl: &invokeCodePayload{Code: common.ToHexString(val.Code)}} + case *payload.DeployCode: + vmty := "Neo" + if val.VmType() == payload.WASMVM_TYPE { + vmty = "Wasm" + } + dp := &deployCodePayload{ + Code: common.ToHexString(val.GetRawCode()), + VmType: vmty, + Name: val.Name, + Version: val.Version, + Author: val.Author, + Email: val.Email, + Desc: val.Description, + } + return &TxPayload{pl: dp} + default: + panic("unreachable") + } +} + +func NewTransaction(tx *types.Transaction, height uint32) *transaction { + ty := convTxType(tx) + var sigs []*Sig + for _, val := range tx.Sigs { + sig, err := val.GetSig() + if err == nil { + var sigdata []string + for _, data := range sig.SigData { + sigdata = append(sigdata, common.ToHexString(data)) + } + var pubkey []PubKey + for _, val := range sig.PubKeys { + pubkey = append(pubkey, PubKey(common.PubKeyToHex(val))) + } + sigs = append(sigs, &Sig{ + SigData: sigdata, + PubKeys: pubkey, + M: Uint32(sig.M), + }) + } + } + t := &transaction{ + Version: Uint32(tx.Version), + Hash: H256(tx.Hash()), + Nonce: Uint32(tx.Nonce), + TxType: ty, + GasPrice: Uint32(tx.GasPrice), + GasLimit: Uint32(tx.GasLimit), + Payer: Addr{tx.Payer}, + Payload: NewTxPayload(tx.Payload), + Sigs: sigs, + Height: Uint32(height), + } + + return t +} + +func NewBlock(b *types.Block) *block { + var txs []*transaction + for _, tx := range b.Transactions { + txs = append(txs, NewTransaction(tx, b.Header.Height)) + } + return &block{ + Header: NewHeader(b.Header), + Transactions: txs, + } +} + +func NewHeader(h *types.Header) *header { + var pubKeys []PubKey + for _, k := range h.Bookkeepers { + pubKeys = append(pubKeys, PubKey(common.PubKeyToHex(k))) + } + var sigData []string + for _, sig := range h.SigData { + sigData = append(sigData, common.ToHexString(sig)) + } + + hd := &header{ + Version: Uint32(h.Version), + Hash: H256(h.Hash()), + PrevHash: H256(h.PrevBlockHash), + Height: Uint32(h.Height), + Timestamp: Uint32(h.Timestamp), + BlockRoot: H256(h.BlockRoot), + TxsRoot: H256(h.TransactionsRoot), + ConsensusData: Uint64(h.ConsensusData), + NextBookkeeper: Addr{h.NextBookkeeper}, + Bookkeepers: pubKeys, + SigData: sigData, + } + + return hd +} + +func (self *resolver) GetBlockByHeight(args struct{ Height Uint32 }) (*block, error) { + b, err := actor.GetBlockByHeight(uint32(args.Height)) + if err != nil { + return nil, err + } + + return NewBlock(b), nil +} + +func (self *resolver) GetBlockByHash(args struct{ Hash H256 }) (*block, error) { + b, err := actor.GetBlockFromStore(common.Uint256(args.Hash)) + if err != nil { + return nil, err + } + + return NewBlock(b), nil +} + +func (self *resolver) GetBlockHash(args struct{ Height Uint32 }) H256 { + return H256(actor.GetBlockHashFromStore(uint32(args.Height))) +} + +type balance struct { + Ont Uint64 + Ong Uint64 + Height Uint32 +} + +type TxType string + +const INVOKE_NEO TxType = "INVOKE_NEO" +const INVOKE_WASM TxType = "INVOKE_WASM" +const DEPLOY_NEO TxType = "DEPLOY_NEO" +const DEPLOY_WASM TxType = "DEPLOY_WASM" + +func convTxType(tx *types.Transaction) TxType { + switch pl := tx.Payload.(type) { + case *payload.InvokeCode: + if tx.TxType == types.InvokeNeo { + return INVOKE_NEO + } else { + return INVOKE_WASM + } + case *payload.DeployCode: + switch pl.VmType() { + case payload.NEOVM_TYPE: + return DEPLOY_NEO + case payload.WASMVM_TYPE: + return DEPLOY_WASM + default: + panic("unreachable") + } + default: + panic("unreachable") + } +} + +type transaction struct { + Version Uint32 + Hash H256 + Nonce Uint32 + TxType TxType + GasPrice Uint32 + GasLimit Uint32 + Payer Addr + Payload *TxPayload + Sigs []*Sig + Height Uint32 +} + +type Sig struct { + SigData []string + PubKeys []PubKey + M Uint32 +} + +func (self *resolver) GetTx(args struct{ Hash H256 }) (*transaction, error) { + height, tx, err := actor.GetTxnWithHeightByTxHash(common.Uint256(args.Hash)) + if err != nil { + return nil, err + } + + return NewTransaction(tx, height), nil +} + +func (self *resolver) GetBalance(args struct{ Addr Addr }) (*balance, error) { + balances, height, err := comm.GetContractBalance(0, + []common.Address{utils.OntContractAddress, utils.OngContractAddress}, args.Addr.Address, true) + if err != nil { + return nil, err + } + + return &balance{ + Height: Uint32(height), + Ont: Uint64(balances[0]), + Ong: Uint64(balances[1]), + }, nil +} + +func StartServer(cfg *config.GraphQLConfig) { + if !cfg.EnableGraphQL || cfg.GraphQLPort == 0 { + return + } + + serverMut := http.NewServeMux() + serverMut.HandleFunc("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write(page) + })) + + serverMut.Handle("/query", &relay.Handler{Schema: ontSchema}) + + server := &http.Server{Handler: serverMut} + listener, err := net.Listen("tcp", ":"+strconv.Itoa(int(cfg.GraphQLPort))) + if err != nil { + log.Error("start graphql server error: %s", err) + return + } + if cfg.MaxConnections > 0 { + listener = netutil.LimitListener(listener, int(cfg.MaxConnections)) + } + + log.Infof("start GraphQL service on %d", cfg.GraphQLPort) + log.Error(server.Serve(listener)) +} + +var page = []byte(` + + + + + + + + + + + +
Loading...
+ + + +`) diff --git a/main.go b/main.go index 863784c639..293e710ab0 100644 --- a/main.go +++ b/main.go @@ -44,6 +44,7 @@ import ( "github.com/ontio/ontology/core/ledger" "github.com/ontio/ontology/events" bactor "github.com/ontio/ontology/http/base/actor" + "github.com/ontio/ontology/http/graphql" "github.com/ontio/ontology/http/jsonrpc" "github.com/ontio/ontology/http/localrpc" "github.com/ontio/ontology/http/nodeinfo" @@ -123,6 +124,10 @@ func setupAPP() *cli.App { utils.RestfulEnableFlag, utils.RestfulPortFlag, utils.RestfulMaxConnsFlag, + //graphql setting + utils.GraphQLEnableFlag, + utils.GraphQLPortFlag, + utils.GraphQLMaxConnsFlag, //ws setting utils.WsEnabledFlag, utils.WsPortFlag, @@ -189,6 +194,7 @@ func startOntology(ctx *cli.Context) { log.Errorf("initLocalRpc error: %s", err) return } + initGraphQL(ctx) initRestful(ctx) initWs(ctx) initNodeInfo(ctx, p2pSvr) @@ -388,6 +394,15 @@ func initLocalRpc(ctx *cli.Context) error { return nil } +func initGraphQL(ctx *cli.Context) { + if !config.DefConfig.GraphQL.EnableGraphQL { + return + } + go graphql.StartServer(config.DefConfig.GraphQL) + + log.Infof("GraphQL init success") +} + func initRestful(ctx *cli.Context) { if !config.DefConfig.Restful.EnableHttpRestful { return