Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions cmd/rpcdaemon/cli/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import (
"google.golang.org/grpc/health/grpc_health_v1"

"github.com/ledgerwatch/erigon/cmd/rpcdaemon/cli/httpcfg"
"github.com/ledgerwatch/erigon/cmd/rpcdaemon/graphql"
"github.com/ledgerwatch/erigon/cmd/rpcdaemon/health"
"github.com/ledgerwatch/erigon/cmd/rpcdaemon/rpcservices"
"github.com/ledgerwatch/erigon/cmd/utils"
Expand Down Expand Up @@ -81,6 +82,7 @@ func RootCommand() (*cobra.Command, *httpcfg.HttpCfg) {
cfg := &httpcfg.HttpCfg{Enabled: true, StateCache: kvcache.DefaultCoherentConfig}
rootCmd.PersistentFlags().StringVar(&cfg.PrivateApiAddr, "private.api.addr", "127.0.0.1:9090", "Erigon's components (txpool, rpcdaemon, sentry, downloader, ...) can be deployed as independent Processes on same/another server. Then components will connect to erigon by this internal grpc API. Example: 127.0.0.1:9090")
rootCmd.PersistentFlags().StringVar(&cfg.DataDir, "datadir", "", "path to Erigon working directory")
rootCmd.PersistentFlags().BoolVar(&cfg.GraphQLEnabled, "graphql", false, "enables graphql endpoint (disabled by default)")
rootCmd.PersistentFlags().StringVar(&cfg.HttpListenAddress, "http.addr", nodecfg.DefaultHTTPHost, "HTTP-RPC server listening interface")
rootCmd.PersistentFlags().StringVar(&cfg.TLSCertfile, "tls.cert", "", "certificate for client side TLS handshake")
rootCmd.PersistentFlags().StringVar(&cfg.TLSKeyFile, "tls.key", "", "key file for client side TLS handshake")
Expand Down Expand Up @@ -539,7 +541,9 @@ func startRegularRpcServer(ctx context.Context, cfg httpcfg.HttpCfg, rpcAPI []rp
wsHandler = srv.WebsocketHandler([]string{"*"}, nil, cfg.WebsocketCompression)
}

apiHandler, err := createHandler(cfg, defaultAPIList, httpHandler, wsHandler, nil)
graphQLHandler := graphql.CreateHandler(defaultAPIList)

apiHandler, err := createHandler(cfg, defaultAPIList, httpHandler, wsHandler, graphQLHandler, nil)
if err != nil {
return err
}
Expand Down Expand Up @@ -682,8 +686,12 @@ func obtainJWTSecret(cfg httpcfg.HttpCfg) ([]byte, error) {
return jwtSecret, nil
}

func createHandler(cfg httpcfg.HttpCfg, apiList []rpc.API, httpHandler http.Handler, wsHandler http.Handler, jwtSecret []byte) (http.Handler, error) {
func createHandler(cfg httpcfg.HttpCfg, apiList []rpc.API, httpHandler http.Handler, wsHandler http.Handler, graphQLHandler http.Handler, jwtSecret []byte) (http.Handler, error) {
var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if cfg.GraphQLEnabled && graphql.ProcessGraphQLcheckIfNeeded(graphQLHandler, w, r) {
return
}

// adding a healthcheck here
if health.ProcessHealthcheckIfNeeded(w, r, apiList) {
return
Expand Down Expand Up @@ -721,7 +729,9 @@ func createEngineListener(cfg httpcfg.HttpCfg, engineApi []rpc.API) (*http.Serve

engineHttpHandler := node.NewHTTPHandlerStack(engineSrv, nil /* authCors */, cfg.AuthRpcVirtualHost, cfg.HttpCompression)

engineApiHandler, err := createHandler(cfg, engineApi, engineHttpHandler, wsHandler, jwtSecret)
graphQLHandler := graphql.CreateHandler(engineApi)

engineApiHandler, err := createHandler(cfg, engineApi, engineHttpHandler, wsHandler, graphQLHandler, jwtSecret)
if err != nil {
return nil, nil, "", err
}
Expand Down
1 change: 1 addition & 0 deletions cmd/rpcdaemon/cli/httpcfg/http_cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
type HttpCfg struct {
Enabled bool
PrivateApiAddr string
GraphQLEnabled bool
WithDatadir bool // Erigon's database can be read by separated processes on same machine - in read-only mode - with full support of transactions. It will share same "OS PageCache" with Erigon process.
DataDir string
Dirs datadir.Dirs
Expand Down
10 changes: 10 additions & 0 deletions cmd/rpcdaemon/commands/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ func APIList(db kv.RoDB, borDb kv.RoDB, eth rpchelper.ApiBackend, txPool txpool.
parityImpl := NewParityAPIImpl(db)
borImpl := NewBorAPI(base, db, borDb) // bor (consensus) specific
otsImpl := NewOtterscanAPI(base, db)
gqlImpl := NewGraphQLAPI(base, db)

if cfg.GraphQLEnabled {
list = append(list, rpc.API{
Namespace: "graphql",
Public: true,
Service: GraphQLAPI(gqlImpl),
Version: "1.0",
})
}

for _, enabledAPI := range cfg.API {
switch enabledAPI {
Expand Down
127 changes: 127 additions & 0 deletions cmd/rpcdaemon/commands/graphql_api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package commands

import (
"context"
"fmt"
"math/big"

"github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon-lib/kv"
"github.com/ledgerwatch/erigon/common/hexutil"
"github.com/ledgerwatch/erigon/core/rawdb"
"github.com/ledgerwatch/erigon/core/types"
"github.com/ledgerwatch/erigon/rpc"
"github.com/ledgerwatch/erigon/turbo/adapter/ethapi"
"github.com/ledgerwatch/erigon/turbo/rpchelper"
)

type GraphQLAPI interface {
GetBlockDetails(ctx context.Context, number rpc.BlockNumber) (map[string]interface{}, error)
GetChainID(ctx context.Context) (*big.Int, error)
}

type GraphQLAPIImpl struct {
*BaseAPI
db kv.RoDB
}

func NewGraphQLAPI(base *BaseAPI, db kv.RoDB) *GraphQLAPIImpl {
return &GraphQLAPIImpl{
BaseAPI: base,
db: db,
}
}

func (api *GraphQLAPIImpl) GetChainID(ctx context.Context) (*big.Int, error) {
tx, err := api.db.BeginRo(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()

response, err := api.chainConfig(tx)
if err != nil {
return nil, err
}

return response.ChainID, nil
}

func (api *GraphQLAPIImpl) GetBlockDetails(ctx context.Context, blockNumber rpc.BlockNumber) (map[string]interface{}, error) {
tx, err := api.db.BeginRo(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()

block, senders, err := api.getBlockWithSenders(ctx, blockNumber, tx)
if err != nil {
return nil, err
}
if block == nil {
return nil, nil
}

getBlockRes, err := api.delegateGetBlockByNumber(tx, block, blockNumber, false)
if err != nil {
return nil, err
}

chainConfig, err := api.chainConfig(tx)
if err != nil {
return nil, err
}

receipts, err := api.getReceipts(ctx, tx, chainConfig, block, senders)
if err != nil {
return nil, fmt.Errorf("getReceipts error: %w", err)
}
result := make([]map[string]interface{}, 0, len(receipts))
for _, receipt := range receipts {
txn := block.Transactions()[receipt.TransactionIndex]
result = append(result, marshalReceipt(receipt, txn, chainConfig, block.HeaderNoCopy(), txn.Hash(), true))
}

response := map[string]interface{}{}
response["block"] = getBlockRes
response["receipts"] = result

return response, nil
}

func (api *GraphQLAPIImpl) getBlockWithSenders(ctx context.Context, number rpc.BlockNumber, tx kv.Tx) (*types.Block, []common.Address, error) {
if number == rpc.PendingBlockNumber {
return api.pendingBlock(), nil, nil
}

blockHeight, blockHash, _, err := rpchelper.GetBlockNumber(rpc.BlockNumberOrHashWithNumber(number), tx, api.filters)
if err != nil {
return nil, nil, err
}

block, senders, err := api._blockReader.BlockWithSenders(ctx, tx, blockHash, blockHeight)
return block, senders, err
}

func (api *GraphQLAPIImpl) delegateGetBlockByNumber(tx kv.Tx, b *types.Block, number rpc.BlockNumber, inclTx bool) (map[string]interface{}, error) {
td, err := rawdb.ReadTd(tx, b.Hash(), b.NumberU64())
if err != nil {
return nil, err
}
additionalFields := make(map[string]interface{})
response, err := ethapi.RPCMarshalBlock(b, inclTx, inclTx, additionalFields)
if !inclTx {
delete(response, "transactions") // workaround for https://github.com/ledgerwatch/erigon/issues/4989#issuecomment-1218415666
}
response["totalDifficulty"] = (*hexutil.Big)(td)
response["transactionCount"] = b.Transactions().Len()

if err == nil && number == rpc.PendingBlockNumber {
// Pending blocks need to nil out a few fields
for _, field := range []string{"hash", "nonce", "miner"} {
response[field] = nil
}
}

return response, err
}
Loading