Skip to content

Commit

Permalink
Merge pull request #38 from akramhussein/config-skip-geth-admin
Browse files Browse the repository at this point in the history
Add Configuration parameter 'SkipGethAdmin' to support skipping 'admin_peers' checks
  • Loading branch information
swapna gupta authored Jun 18, 2021
2 parents 30d145a + b94019a commit 1eab851
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 21 deletions.
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,12 @@ Running the following commands will start a Docker container in
a data directory at `<working directory>/ethereum-data` and the Rosetta API accessible
at port `8080`.

The `NETWORK` environment variable can be set to `MAINNET`, `ROPSTEN`, `RINKEBY`, `GOERLI` or `TESTNET` (which defaults to `ROPSTEN`).

_It is possible to run `rosetta-ethereum` using a remote node by adding
`-e "GETH=<node url>"` to any online command._
#### Configuration Environment Variables
* `MODE` (required) - Determines if Rosetta can make outbound connections. Options: `ONLINE` or `OFFLINE`.
* `NETWORK` (required) - Ethereum network to launch and/or communicate with. Options: `MAINNET`, `ROPSTEN`, `RINKEBY`, `GOERLI` or `TESTNET` (which defaults to `ROPSTEN` for backwards compatibility).
* `PORT`(required) - Which port to use for Rosetta.
* `GETH` (optional) - Point to a remote `geth` node instead of initializing one
* `SKIP_GETH_ADMIN` (optional, default: `FALSE`) - Instruct Rosetta to not use the `geth` `admin` RPC calls. This is typically disabled by hosted blockchain node services.

#### Mainnet:Online
```text
Expand Down
2 changes: 1 addition & 1 deletion cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func runRunCmd(cmd *cobra.Command, args []string) error {
}

var err error
client, err = ethereum.NewClient(cfg.GethURL, cfg.Params)
client, err = ethereum.NewClient(cfg.GethURL, cfg.Params, cfg.SkipGethAdmin)
if err != nil {
return fmt.Errorf("%w: cannot initialize ethereum client", err)
}
Expand Down
16 changes: 16 additions & 0 deletions configuration/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ const (
// when GethEnv is not populated.
DefaultGethURL = "http://localhost:8545"

// SkipGethAdminEnv is an optional environment variable
// to skip geth `admin` calls which are typically not supported
// by hosted node services. When not set, defaults to false.
SkipGethAdminEnv = "SKIP_GETH_ADMIN"

// MiddlewareVersion is the version of rosetta-ethereum.
MiddlewareVersion = "0.0.4"
)
Expand All @@ -94,6 +99,7 @@ type Configuration struct {
RemoteGeth bool
Port int
GethArguments string
SkipGethAdmin bool

// Block Reward Data
Params *params.ChainConfig
Expand Down Expand Up @@ -163,6 +169,16 @@ func LoadConfiguration() (*Configuration, error) {
config.GethURL = envGethURL
}

config.SkipGethAdmin = false
envSkipGethAdmin := os.Getenv(SkipGethAdminEnv)
if len(envSkipGethAdmin) > 0 {
val, err := strconv.ParseBool(envSkipGethAdmin)
if err != nil {
return nil, fmt.Errorf("%w: unable to parse SKIP_GETH_ADMIN %s", err, envSkipGethAdmin)
}
config.SkipGethAdmin = val
}

portValue := os.Getenv(PortEnv)
if len(portValue) == 0 {
return nil, errors.New("PORT must be populated")
Expand Down
36 changes: 22 additions & 14 deletions configuration/configuration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ import (

func TestLoadConfiguration(t *testing.T) {
tests := map[string]struct {
Mode string
Network string
Port string
Geth string
Mode string
Network string
Port string
Geth string
SkipGethAdmin string

cfg *Configuration
err error
Expand All @@ -49,9 +50,10 @@ func TestLoadConfiguration(t *testing.T) {
err: errors.New("PORT must be populated"),
},
"all set (mainnet)": {
Mode: string(Online),
Network: Mainnet,
Port: "1000",
Mode: string(Online),
Network: Mainnet,
Port: "1000",
SkipGethAdmin: "FALSE",
cfg: &Configuration{
Mode: Online,
Network: &types.NetworkIdentifier{
Expand All @@ -63,13 +65,15 @@ func TestLoadConfiguration(t *testing.T) {
Port: 1000,
GethURL: DefaultGethURL,
GethArguments: ethereum.MainnetGethArguments,
SkipGethAdmin: false,
},
},
"all set (mainnet) + geth": {
Mode: string(Online),
Network: Mainnet,
Port: "1000",
Geth: "http://blah",
Mode: string(Online),
Network: Mainnet,
Port: "1000",
Geth: "http://blah",
SkipGethAdmin: "TRUE",
cfg: &Configuration{
Mode: Online,
Network: &types.NetworkIdentifier{
Expand All @@ -82,6 +86,7 @@ func TestLoadConfiguration(t *testing.T) {
GethURL: "http://blah",
RemoteGeth: true,
GethArguments: ethereum.MainnetGethArguments,
SkipGethAdmin: true,
},
},
"all set (ropsten)": {
Expand Down Expand Up @@ -136,9 +141,10 @@ func TestLoadConfiguration(t *testing.T) {
},
},
"all set (testnet)": {
Mode: string(Online),
Network: Testnet,
Port: "1000",
Mode: string(Online),
Network: Testnet,
Port: "1000",
SkipGethAdmin: "TRUE",
cfg: &Configuration{
Mode: Online,
Network: &types.NetworkIdentifier{
Expand All @@ -150,6 +156,7 @@ func TestLoadConfiguration(t *testing.T) {
Port: 1000,
GethURL: DefaultGethURL,
GethArguments: ethereum.RopstenGethArguments,
SkipGethAdmin: true,
},
},
"invalid mode": {
Expand Down Expand Up @@ -178,6 +185,7 @@ func TestLoadConfiguration(t *testing.T) {
os.Setenv(NetworkEnv, test.Network)
os.Setenv(PortEnv, test.Port)
os.Setenv(GethEnv, test.Geth)
os.Setenv(SkipGethAdminEnv, test.SkipGethAdmin)

cfg, err := LoadConfiguration()
if test.err != nil {
Expand Down
11 changes: 9 additions & 2 deletions ethereum/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,12 @@ type Client struct {
g GraphQL

traceSemaphore *semaphore.Weighted

skipAdminCalls bool
}

// NewClient creates a Client that from the provided url and params.
func NewClient(url string, params *params.ChainConfig) (*Client, error) {
func NewClient(url string, params *params.ChainConfig, skipAdminCalls bool) (*Client, error) {
c, err := rpc.DialHTTPWithClient(url, &http.Client{
Timeout: gethHTTPTimeout,
})
Expand All @@ -81,7 +83,7 @@ func NewClient(url string, params *params.ChainConfig) (*Client, error) {
return nil, fmt.Errorf("%w: unable to create GraphQL client", err)
}

return &Client{params, tc, c, g, semaphore.NewWeighted(maxTraceConcurrency)}, nil
return &Client{params, tc, c, g, semaphore.NewWeighted(maxTraceConcurrency), skipAdminCalls}, nil
}

// Close shuts down the RPC client connection.
Expand Down Expand Up @@ -155,6 +157,11 @@ func (ec *Client) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
// Peers retrieves all peers of the node.
func (ec *Client) peers(ctx context.Context) ([]*RosettaTypes.Peer, error) {
var info []*p2p.PeerInfo

if ec.skipAdminCalls {
return []*RosettaTypes.Peer{}, nil
}

if err := ec.c.CallContext(ctx, &info, "admin_peers"); err != nil {
return nil, err
}
Expand Down
159 changes: 159 additions & 0 deletions ethereum/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,83 @@ func TestStatus_NotSyncing(t *testing.T) {
mockGraphQL.AssertExpectations(t)
}

func TestStatus_NotSyncing_SkipAdminCalls(t *testing.T) {
mockJSONRPC := &mocks.JSONRPC{}
mockGraphQL := &mocks.GraphQL{}

c := &Client{
c: mockJSONRPC,
g: mockGraphQL,
traceSemaphore: semaphore.NewWeighted(100),
skipAdminCalls: true,
}

ctx := context.Background()
mockJSONRPC.On(
"CallContext",
ctx,
mock.Anything,
"eth_getBlockByNumber",
"latest",
false,
).Return(
nil,
).Run(
func(args mock.Arguments) {
header := args.Get(1).(**types.Header)
file, err := ioutil.ReadFile("testdata/basic_header.json")
assert.NoError(t, err)

*header = new(types.Header)

assert.NoError(t, (*header).UnmarshalJSON(file))
},
).Once()

mockJSONRPC.On(
"CallContext",
ctx,
mock.Anything,
"eth_syncing",
).Return(
nil,
).Run(
func(args mock.Arguments) {
status := args.Get(1).(*json.RawMessage)

*status = json.RawMessage("false")
},
).Once()

adminPeersSkipped := true
mockJSONRPC.On(
"CallContext",
ctx,
mock.Anything,
"admin_peers",
).Return(
nil,
).Run(
func(args mock.Arguments) {
adminPeersSkipped = false
},
).Maybe()

block, timestamp, syncStatus, peers, err := c.Status(ctx)
assert.True(t, adminPeersSkipped)
assert.Equal(t, &RosettaTypes.BlockIdentifier{
Hash: "0x48269a339ce1489cff6bab70eff432289c4f490b81dbd00ff1f81c68de06b842",
Index: 8916656,
}, block)
assert.Equal(t, int64(1603225195000), timestamp)
assert.Nil(t, syncStatus)
assert.Equal(t, []*RosettaTypes.Peer{}, peers)
assert.NoError(t, err)

mockJSONRPC.AssertExpectations(t)
mockGraphQL.AssertExpectations(t)
}

func TestStatus_Syncing(t *testing.T) {
mockJSONRPC := &mocks.JSONRPC{}
mockGraphQL := &mocks.GraphQL{}
Expand Down Expand Up @@ -317,6 +394,88 @@ func TestStatus_Syncing(t *testing.T) {
mockGraphQL.AssertExpectations(t)
}

func TestStatus_Syncing_SkipAdminCalls(t *testing.T) {
mockJSONRPC := &mocks.JSONRPC{}
mockGraphQL := &mocks.GraphQL{}

c := &Client{
c: mockJSONRPC,
g: mockGraphQL,
traceSemaphore: semaphore.NewWeighted(100),
skipAdminCalls: true,
}

ctx := context.Background()
mockJSONRPC.On(
"CallContext",
ctx,
mock.Anything,
"eth_getBlockByNumber",
"latest",
false,
).Return(
nil,
).Run(
func(args mock.Arguments) {
header := args.Get(1).(**types.Header)
file, err := ioutil.ReadFile("testdata/basic_header.json")
assert.NoError(t, err)

*header = new(types.Header)

assert.NoError(t, (*header).UnmarshalJSON(file))
},
).Once()

mockJSONRPC.On(
"CallContext",
ctx,
mock.Anything,
"eth_syncing",
).Return(
nil,
).Run(
func(args mock.Arguments) {
progress := args.Get(1).(*json.RawMessage)
file, err := ioutil.ReadFile("testdata/syncing_info.json")
assert.NoError(t, err)

*progress = json.RawMessage(file)
},
).Once()

adminPeersSkipped := true
mockJSONRPC.On(
"CallContext",
ctx,
mock.Anything,
"admin_peers",
).Return(
nil,
).Run(
func(args mock.Arguments) {
adminPeersSkipped = false
},
).Maybe()

block, timestamp, syncStatus, peers, err := c.Status(ctx)
assert.True(t, adminPeersSkipped)
assert.Equal(t, &RosettaTypes.BlockIdentifier{
Hash: "0x48269a339ce1489cff6bab70eff432289c4f490b81dbd00ff1f81c68de06b842",
Index: 8916656,
}, block)
assert.Equal(t, int64(1603225195000), timestamp)
assert.Equal(t, &RosettaTypes.SyncStatus{
CurrentIndex: RosettaTypes.Int64(25),
TargetIndex: RosettaTypes.Int64(8916760),
}, syncStatus)
assert.Equal(t, []*RosettaTypes.Peer{}, peers)
assert.NoError(t, err)

mockJSONRPC.AssertExpectations(t)
mockGraphQL.AssertExpectations(t)
}

func TestBalance(t *testing.T) {
mockJSONRPC := &mocks.JSONRPC{}
mockGraphQL := &mocks.GraphQL{}
Expand Down

0 comments on commit 1eab851

Please sign in to comment.