Skip to content

Commit

Permalink
feat(header forwarding): forward headers to native node requests (#133)
Browse files Browse the repository at this point in the history
* add header forwarding and associated plumbing

* import [email protected]

* run make add-license

* fix linting

* bootstrap automatically creates the right client
  • Loading branch information
potterbm-cb authored Oct 21, 2024
1 parent 5f86092 commit 9d1c0df
Show file tree
Hide file tree
Showing 10 changed files with 151 additions and 27 deletions.
19 changes: 17 additions & 2 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"
"log"
"math/big"
"net/http"
"strconv"

"github.com/coinbase/rosetta-geth-sdk/configuration"
Expand Down Expand Up @@ -58,9 +59,13 @@ type SDKClient struct {
skipAdminCalls bool
}

type ReplaceableRPCClient interface {
WithRPCTransport(string, http.RoundTripper) (ReplaceableRPCClient, error)
}

// NewClient creates a client that connects to the network.
func NewClient(cfg *configuration.Configuration, rpcClient *RPCClient) (*SDKClient, error) {
c, err := NewRPCClient(cfg.GethURL)
func NewClient(cfg *configuration.Configuration, rpcClient *RPCClient, transport http.RoundTripper) (*SDKClient, error) {
c, err := NewRPCClient(cfg.GethURL, transport)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -96,6 +101,16 @@ func NewClient(cfg *configuration.Configuration, rpcClient *RPCClient) (*SDKClie
}, nil
}

func (ec *SDKClient) WithRPCTransport(endpoint string, transport http.RoundTripper) (ReplaceableRPCClient, error) {
newClient, err := NewRPCClient(endpoint, transport)
if err != nil {
return ec, err
}

ec.RPCClient = newClient
return ec, nil
}

func (ec *SDKClient) PopulateCrossChainTransactions(
*EthTypes.Block,
[]*LoadedTransaction,
Expand Down
30 changes: 30 additions & 0 deletions client/default.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2024 Coinbase, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package client

import "net/http"

func NewDefaultHTTPTransport() http.RoundTripper {
// Override transport idle connection settings
//
// See this conversation around why `.Clone()` is used here:
// https://github.com/golang/go/issues/26013
defaultTransport := http.DefaultTransport.(*http.Transport).Clone()
defaultTransport.IdleConnTimeout = DefaultIdleConnTimeout
defaultTransport.MaxIdleConns = DefaultMaxConnections
defaultTransport.MaxIdleConnsPerHost = DefaultMaxConnections

return defaultTransport
}
21 changes: 7 additions & 14 deletions client/rpc_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,29 +66,22 @@ const (
)

// NewRPCClient connects a SDKClient to the given URL.
func NewRPCClient(endpoint string) (*RPCClient, error) {
// Override transport idle connection settings
//
// See this conversation around why `.Clone()` is used here:
// https://github.com/golang/go/issues/26013
defaultTransport := http.DefaultTransport.(*http.Transport).Clone()
defaultTransport.IdleConnTimeout = DefaultIdleConnTimeout
defaultTransport.MaxIdleConns = DefaultMaxConnections
defaultTransport.MaxIdleConnsPerHost = DefaultMaxConnections
func NewRPCClient(endpoint string, transport http.RoundTripper) (*RPCClient, error) {
if transport == nil {
transport = NewDefaultHTTPTransport()
}

clientOptions := rpc.WithHTTPClient(&http.Client{
Timeout: gethHTTPTimeout,
Transport: defaultTransport,
Transport: transport,
})
ctx := context.Background()

client, err := rpc.DialOptions(ctx, endpoint, clientOptions)
/*client, err := rpc.DialHTTPWithClient(endpoint, &http.Client{
Timeout: gethHTTPTimeout,
Transport: defaultTransport,
})*/
if err != nil {
return nil, fmt.Errorf("unable to dial node: %w", err)
}

return &RPCClient{client}, nil
}

Expand Down
7 changes: 7 additions & 0 deletions configuration/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,13 @@ type RosettaConfig struct {

// SupportCustomizedBlockBody indicates if the blockchain supports customized block body
SupportCustomizedBlockBody bool

// SupportHeaderForwarding indicates if rosetta should forward rosetta request headers to the
// native node, and forward native node response headers to the rosetta caller
SupportHeaderForwarding bool

// ForwardHeaders is the list of headers to forward to and from the native node
ForwardHeaders []string
}

type Token struct {
Expand Down
2 changes: 1 addition & 1 deletion examples/ethereum/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ func (c *EthereumClient) GetNativeTransferGasLimit(ctx context.Context, toAddres
// Ethereum network.
func NewEthereumClient(cfg *configuration.Configuration) (*EthereumClient, error) {
// Use SDK to quickly create a client that support JSON RPC calls
evmClient, err := evmClient.NewClient(cfg, nil)
evmClient, err := evmClient.NewClient(cfg, nil, nil)

if err != nil {
log.Fatalln("cannot initialize client: %w", err)
Expand Down
7 changes: 4 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ module github.com/coinbase/rosetta-geth-sdk
go 1.20

require (
github.com/coinbase/rosetta-sdk-go v0.8.2
github.com/coinbase/rosetta-sdk-go v0.8.6
github.com/coinbase/rosetta-sdk-go/types v1.0.0
github.com/ethereum/go-ethereum v1.13.8
github.com/neilotoole/errgroup v0.1.6
github.com/stretchr/testify v1.8.4
Expand Down Expand Up @@ -44,7 +45,7 @@ require (
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/holiman/bloomfilter/v2 v2.0.3 // indirect
Expand All @@ -58,7 +59,7 @@ require (
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mmcloughlin/addchain v0.4.0 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
Expand Down
14 changes: 8 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,10 @@ github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2/go.mod h1:8BT+cPK6xvFOcRlk
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo=
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
github.com/coinbase/rosetta-sdk-go v0.8.2 h1:+sNgMUPpntOsYLy5aRsHqBY6I0MTxZkS4JXV1Un3DKc=
github.com/coinbase/rosetta-sdk-go v0.8.2/go.mod h1:tXPR6AIW9ogsH4tYIaFOKOgfJNanCvcyl7JKLd4DToc=
github.com/coinbase/rosetta-sdk-go v0.8.6 h1:FQ8ApWTZIsung1AsTv+DjXR7RwKnS6MQXzLcXtBNyjc=
github.com/coinbase/rosetta-sdk-go v0.8.6/go.mod h1:LD9qAq1m+pwAqEBrzNTjDy9VTYEDgeHiiltVTVyHfXI=
github.com/coinbase/rosetta-sdk-go/types v1.0.0 h1:jpVIwLcPoOeCR6o1tU+Xv7r5bMONNbHU7MuEHboiFuA=
github.com/coinbase/rosetta-sdk-go/types v1.0.0/go.mod h1:eq7W2TMRH22GTW0N0beDnN931DW0/WOI1R2sdHNHG4c=
github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ=
github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI=
github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M=
Expand Down Expand Up @@ -242,8 +244,8 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
Expand Down Expand Up @@ -340,8 +342,8 @@ github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQ
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A=
github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY=
github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU=
Expand Down
29 changes: 29 additions & 0 deletions headers/headers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright 2024 Coinbase, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package headers

import (
"context"
"net/http"

"github.com/ethereum/go-ethereum/rpc"
)

// TODO: filter headers by "interesting headers"
func ContextWithHeaders(r *http.Request) context.Context {
ctx := r.Context()
ctxWithHeaders := rpc.NewContextWithHeaders(ctx, r.Header)
return ctxWithHeaders
}
14 changes: 13 additions & 1 deletion services/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
package services

import (
"context"
"net/http"

"github.com/coinbase/rosetta-geth-sdk/configuration"
"github.com/coinbase/rosetta-geth-sdk/headers"
AssetTypes "github.com/coinbase/rosetta-geth-sdk/types"
"github.com/coinbase/rosetta-sdk-go/types"

Expand All @@ -33,43 +35,53 @@ func NewBlockchainRouter(
types *AssetTypes.Types,
errors []*types.Error,
client construction.Client,

asserter *asserter.Asserter,
) http.Handler {
var contextFromRequest func(r *http.Request) context.Context = nil
if config.RosettaCfg.SupportHeaderForwarding {
contextFromRequest = headers.ContextWithHeaders
}

networkAPIService := NewNetworkAPIService(config, types, errors, client)
networkAPIController := server.NewNetworkAPIController(
networkAPIService,
asserter,
contextFromRequest,
)

accountAPIService := NewAccountAPIService(config, types, errors, client)
accountAPIController := server.NewAccountAPIController(
accountAPIService,
asserter,
contextFromRequest,
)

blockAPIService := NewBlockAPIService(config, client)
blockAPIController := server.NewBlockAPIController(
blockAPIService,
asserter,
contextFromRequest,
)

constructionAPIService := construction.NewAPIService(config, types, errors, client)
constructionAPIController := server.NewConstructionAPIController(
constructionAPIService,
asserter,
contextFromRequest,
)

// mempoolAPIService := NewMempoolAPIService()
// mempoolAPIController := server.NewMempoolAPIController(
// mempoolAPIService,
// asserter,
// contextFromRequest,
// )

// callAPIService := NewCallAPIService(config, client)
// callAPIController := server.NewCallAPIController(
// callAPIService,
// asserter,
// contextFromRequest,
// )

return server.NewRouter(
Expand Down
35 changes: 35 additions & 0 deletions utils/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ import (
"net/http"
"time"

gethSdkClient "github.com/coinbase/rosetta-geth-sdk/client"
"github.com/coinbase/rosetta-geth-sdk/configuration"
"github.com/coinbase/rosetta-geth-sdk/services"
"github.com/coinbase/rosetta-geth-sdk/services/construction"

AssetTypes "github.com/coinbase/rosetta-geth-sdk/types"

"github.com/coinbase/rosetta-sdk-go/asserter"
"github.com/coinbase/rosetta-sdk-go/headerforwarder"
"github.com/coinbase/rosetta-sdk-go/server"
RosettaTypes "github.com/coinbase/rosetta-sdk-go/types"
"github.com/neilotoole/errgroup"
Expand Down Expand Up @@ -58,9 +60,42 @@ func BootStrap(
return fmt.Errorf("could not initialize server asserter: %w", err)
}

// If header forwarding is turned on, initialize a new client
var headerForwarder *headerforwarder.HeaderForwarder
if cfg.RosettaCfg.SupportHeaderForwarding {
replaceableClient, isReplaceable := client.(gethSdkClient.ReplaceableRPCClient)
if !isReplaceable {
return fmt.Errorf("SupportHeaderForwarding enabled, but client does not implement ReplaceableRPCClient")
}

headerForwarder = headerforwarder.NewHeaderForwarder(
cfg.RosettaCfg.ForwardHeaders,
gethSdkClient.NewDefaultHTTPTransport(),
)

replacedClient, err := replaceableClient.WithRPCTransport(cfg.GethURL, headerForwarder)
if err != nil {
return fmt.Errorf("SupportHeaderForwarding enabled, but client replacement failed: %e", err)
}

convertedClient, ok := replacedClient.(construction.Client)
if !ok {
return fmt.Errorf("SupportHeaderForwarding enabled, but converting replaced client type failed")
}

client = convertedClient
}

router := services.NewBlockchainRouter(cfg, types, errors, client, asserter)

if cfg.RosettaCfg.SupportHeaderForwarding {
router = headerForwarder.HeaderForwarderHandler(router)
}

// Add this middleware last so that it executes first
loggedRouter := server.LoggerMiddleware(router)
corsRouter := server.CorsMiddleware(loggedRouter)

server := &http.Server{
Addr: fmt.Sprintf(":%d", cfg.Port),
Handler: corsRouter,
Expand Down

0 comments on commit 9d1c0df

Please sign in to comment.