Skip to content

Commit 1eab851

Browse files
author
swapna gupta
authored
Merge pull request #38 from akramhussein/config-skip-geth-admin
Add Configuration parameter 'SkipGethAdmin' to support skipping 'admin_peers' checks
2 parents 30d145a + b94019a commit 1eab851

File tree

6 files changed

+213
-21
lines changed

6 files changed

+213
-21
lines changed

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,12 @@ Running the following commands will start a Docker container in
5959
a data directory at `<working directory>/ethereum-data` and the Rosetta API accessible
6060
at port `8080`.
6161

62-
The `NETWORK` environment variable can be set to `MAINNET`, `ROPSTEN`, `RINKEBY`, `GOERLI` or `TESTNET` (which defaults to `ROPSTEN`).
63-
64-
_It is possible to run `rosetta-ethereum` using a remote node by adding
65-
`-e "GETH=<node url>"` to any online command._
62+
#### Configuration Environment Variables
63+
* `MODE` (required) - Determines if Rosetta can make outbound connections. Options: `ONLINE` or `OFFLINE`.
64+
* `NETWORK` (required) - Ethereum network to launch and/or communicate with. Options: `MAINNET`, `ROPSTEN`, `RINKEBY`, `GOERLI` or `TESTNET` (which defaults to `ROPSTEN` for backwards compatibility).
65+
* `PORT`(required) - Which port to use for Rosetta.
66+
* `GETH` (optional) - Point to a remote `geth` node instead of initializing one
67+
* `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.
6668

6769
#### Mainnet:Online
6870
```text

cmd/run.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ func runRunCmd(cmd *cobra.Command, args []string) error {
9191
}
9292

9393
var err error
94-
client, err = ethereum.NewClient(cfg.GethURL, cfg.Params)
94+
client, err = ethereum.NewClient(cfg.GethURL, cfg.Params, cfg.SkipGethAdmin)
9595
if err != nil {
9696
return fmt.Errorf("%w: cannot initialize ethereum client", err)
9797
}

configuration/configuration.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ const (
8181
// when GethEnv is not populated.
8282
DefaultGethURL = "http://localhost:8545"
8383

84+
// SkipGethAdminEnv is an optional environment variable
85+
// to skip geth `admin` calls which are typically not supported
86+
// by hosted node services. When not set, defaults to false.
87+
SkipGethAdminEnv = "SKIP_GETH_ADMIN"
88+
8489
// MiddlewareVersion is the version of rosetta-ethereum.
8590
MiddlewareVersion = "0.0.4"
8691
)
@@ -94,6 +99,7 @@ type Configuration struct {
9499
RemoteGeth bool
95100
Port int
96101
GethArguments string
102+
SkipGethAdmin bool
97103

98104
// Block Reward Data
99105
Params *params.ChainConfig
@@ -163,6 +169,16 @@ func LoadConfiguration() (*Configuration, error) {
163169
config.GethURL = envGethURL
164170
}
165171

172+
config.SkipGethAdmin = false
173+
envSkipGethAdmin := os.Getenv(SkipGethAdminEnv)
174+
if len(envSkipGethAdmin) > 0 {
175+
val, err := strconv.ParseBool(envSkipGethAdmin)
176+
if err != nil {
177+
return nil, fmt.Errorf("%w: unable to parse SKIP_GETH_ADMIN %s", err, envSkipGethAdmin)
178+
}
179+
config.SkipGethAdmin = val
180+
}
181+
166182
portValue := os.Getenv(PortEnv)
167183
if len(portValue) == 0 {
168184
return nil, errors.New("PORT must be populated")

configuration/configuration_test.go

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@ import (
2828

2929
func TestLoadConfiguration(t *testing.T) {
3030
tests := map[string]struct {
31-
Mode string
32-
Network string
33-
Port string
34-
Geth string
31+
Mode string
32+
Network string
33+
Port string
34+
Geth string
35+
SkipGethAdmin string
3536

3637
cfg *Configuration
3738
err error
@@ -49,9 +50,10 @@ func TestLoadConfiguration(t *testing.T) {
4950
err: errors.New("PORT must be populated"),
5051
},
5152
"all set (mainnet)": {
52-
Mode: string(Online),
53-
Network: Mainnet,
54-
Port: "1000",
53+
Mode: string(Online),
54+
Network: Mainnet,
55+
Port: "1000",
56+
SkipGethAdmin: "FALSE",
5557
cfg: &Configuration{
5658
Mode: Online,
5759
Network: &types.NetworkIdentifier{
@@ -63,13 +65,15 @@ func TestLoadConfiguration(t *testing.T) {
6365
Port: 1000,
6466
GethURL: DefaultGethURL,
6567
GethArguments: ethereum.MainnetGethArguments,
68+
SkipGethAdmin: false,
6669
},
6770
},
6871
"all set (mainnet) + geth": {
69-
Mode: string(Online),
70-
Network: Mainnet,
71-
Port: "1000",
72-
Geth: "http://blah",
72+
Mode: string(Online),
73+
Network: Mainnet,
74+
Port: "1000",
75+
Geth: "http://blah",
76+
SkipGethAdmin: "TRUE",
7377
cfg: &Configuration{
7478
Mode: Online,
7579
Network: &types.NetworkIdentifier{
@@ -82,6 +86,7 @@ func TestLoadConfiguration(t *testing.T) {
8286
GethURL: "http://blah",
8387
RemoteGeth: true,
8488
GethArguments: ethereum.MainnetGethArguments,
89+
SkipGethAdmin: true,
8590
},
8691
},
8792
"all set (ropsten)": {
@@ -136,9 +141,10 @@ func TestLoadConfiguration(t *testing.T) {
136141
},
137142
},
138143
"all set (testnet)": {
139-
Mode: string(Online),
140-
Network: Testnet,
141-
Port: "1000",
144+
Mode: string(Online),
145+
Network: Testnet,
146+
Port: "1000",
147+
SkipGethAdmin: "TRUE",
142148
cfg: &Configuration{
143149
Mode: Online,
144150
Network: &types.NetworkIdentifier{
@@ -150,6 +156,7 @@ func TestLoadConfiguration(t *testing.T) {
150156
Port: 1000,
151157
GethURL: DefaultGethURL,
152158
GethArguments: ethereum.RopstenGethArguments,
159+
SkipGethAdmin: true,
153160
},
154161
},
155162
"invalid mode": {
@@ -178,6 +185,7 @@ func TestLoadConfiguration(t *testing.T) {
178185
os.Setenv(NetworkEnv, test.Network)
179186
os.Setenv(PortEnv, test.Port)
180187
os.Setenv(GethEnv, test.Geth)
188+
os.Setenv(SkipGethAdminEnv, test.SkipGethAdmin)
181189

182190
cfg, err := LoadConfiguration()
183191
if test.err != nil {

ethereum/client.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,12 @@ type Client struct {
6060
g GraphQL
6161

6262
traceSemaphore *semaphore.Weighted
63+
64+
skipAdminCalls bool
6365
}
6466

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

84-
return &Client{params, tc, c, g, semaphore.NewWeighted(maxTraceConcurrency)}, nil
86+
return &Client{params, tc, c, g, semaphore.NewWeighted(maxTraceConcurrency), skipAdminCalls}, nil
8587
}
8688

8789
// Close shuts down the RPC client connection.
@@ -155,6 +157,11 @@ func (ec *Client) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
155157
// Peers retrieves all peers of the node.
156158
func (ec *Client) peers(ctx context.Context) ([]*RosettaTypes.Peer, error) {
157159
var info []*p2p.PeerInfo
160+
161+
if ec.skipAdminCalls {
162+
return []*RosettaTypes.Peer{}, nil
163+
}
164+
158165
if err := ec.c.CallContext(ctx, &info, "admin_peers"); err != nil {
159166
return nil, err
160167
}

ethereum/client_test.go

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,83 @@ func TestStatus_NotSyncing(t *testing.T) {
192192
mockGraphQL.AssertExpectations(t)
193193
}
194194

195+
func TestStatus_NotSyncing_SkipAdminCalls(t *testing.T) {
196+
mockJSONRPC := &mocks.JSONRPC{}
197+
mockGraphQL := &mocks.GraphQL{}
198+
199+
c := &Client{
200+
c: mockJSONRPC,
201+
g: mockGraphQL,
202+
traceSemaphore: semaphore.NewWeighted(100),
203+
skipAdminCalls: true,
204+
}
205+
206+
ctx := context.Background()
207+
mockJSONRPC.On(
208+
"CallContext",
209+
ctx,
210+
mock.Anything,
211+
"eth_getBlockByNumber",
212+
"latest",
213+
false,
214+
).Return(
215+
nil,
216+
).Run(
217+
func(args mock.Arguments) {
218+
header := args.Get(1).(**types.Header)
219+
file, err := ioutil.ReadFile("testdata/basic_header.json")
220+
assert.NoError(t, err)
221+
222+
*header = new(types.Header)
223+
224+
assert.NoError(t, (*header).UnmarshalJSON(file))
225+
},
226+
).Once()
227+
228+
mockJSONRPC.On(
229+
"CallContext",
230+
ctx,
231+
mock.Anything,
232+
"eth_syncing",
233+
).Return(
234+
nil,
235+
).Run(
236+
func(args mock.Arguments) {
237+
status := args.Get(1).(*json.RawMessage)
238+
239+
*status = json.RawMessage("false")
240+
},
241+
).Once()
242+
243+
adminPeersSkipped := true
244+
mockJSONRPC.On(
245+
"CallContext",
246+
ctx,
247+
mock.Anything,
248+
"admin_peers",
249+
).Return(
250+
nil,
251+
).Run(
252+
func(args mock.Arguments) {
253+
adminPeersSkipped = false
254+
},
255+
).Maybe()
256+
257+
block, timestamp, syncStatus, peers, err := c.Status(ctx)
258+
assert.True(t, adminPeersSkipped)
259+
assert.Equal(t, &RosettaTypes.BlockIdentifier{
260+
Hash: "0x48269a339ce1489cff6bab70eff432289c4f490b81dbd00ff1f81c68de06b842",
261+
Index: 8916656,
262+
}, block)
263+
assert.Equal(t, int64(1603225195000), timestamp)
264+
assert.Nil(t, syncStatus)
265+
assert.Equal(t, []*RosettaTypes.Peer{}, peers)
266+
assert.NoError(t, err)
267+
268+
mockJSONRPC.AssertExpectations(t)
269+
mockGraphQL.AssertExpectations(t)
270+
}
271+
195272
func TestStatus_Syncing(t *testing.T) {
196273
mockJSONRPC := &mocks.JSONRPC{}
197274
mockGraphQL := &mocks.GraphQL{}
@@ -317,6 +394,88 @@ func TestStatus_Syncing(t *testing.T) {
317394
mockGraphQL.AssertExpectations(t)
318395
}
319396

397+
func TestStatus_Syncing_SkipAdminCalls(t *testing.T) {
398+
mockJSONRPC := &mocks.JSONRPC{}
399+
mockGraphQL := &mocks.GraphQL{}
400+
401+
c := &Client{
402+
c: mockJSONRPC,
403+
g: mockGraphQL,
404+
traceSemaphore: semaphore.NewWeighted(100),
405+
skipAdminCalls: true,
406+
}
407+
408+
ctx := context.Background()
409+
mockJSONRPC.On(
410+
"CallContext",
411+
ctx,
412+
mock.Anything,
413+
"eth_getBlockByNumber",
414+
"latest",
415+
false,
416+
).Return(
417+
nil,
418+
).Run(
419+
func(args mock.Arguments) {
420+
header := args.Get(1).(**types.Header)
421+
file, err := ioutil.ReadFile("testdata/basic_header.json")
422+
assert.NoError(t, err)
423+
424+
*header = new(types.Header)
425+
426+
assert.NoError(t, (*header).UnmarshalJSON(file))
427+
},
428+
).Once()
429+
430+
mockJSONRPC.On(
431+
"CallContext",
432+
ctx,
433+
mock.Anything,
434+
"eth_syncing",
435+
).Return(
436+
nil,
437+
).Run(
438+
func(args mock.Arguments) {
439+
progress := args.Get(1).(*json.RawMessage)
440+
file, err := ioutil.ReadFile("testdata/syncing_info.json")
441+
assert.NoError(t, err)
442+
443+
*progress = json.RawMessage(file)
444+
},
445+
).Once()
446+
447+
adminPeersSkipped := true
448+
mockJSONRPC.On(
449+
"CallContext",
450+
ctx,
451+
mock.Anything,
452+
"admin_peers",
453+
).Return(
454+
nil,
455+
).Run(
456+
func(args mock.Arguments) {
457+
adminPeersSkipped = false
458+
},
459+
).Maybe()
460+
461+
block, timestamp, syncStatus, peers, err := c.Status(ctx)
462+
assert.True(t, adminPeersSkipped)
463+
assert.Equal(t, &RosettaTypes.BlockIdentifier{
464+
Hash: "0x48269a339ce1489cff6bab70eff432289c4f490b81dbd00ff1f81c68de06b842",
465+
Index: 8916656,
466+
}, block)
467+
assert.Equal(t, int64(1603225195000), timestamp)
468+
assert.Equal(t, &RosettaTypes.SyncStatus{
469+
CurrentIndex: RosettaTypes.Int64(25),
470+
TargetIndex: RosettaTypes.Int64(8916760),
471+
}, syncStatus)
472+
assert.Equal(t, []*RosettaTypes.Peer{}, peers)
473+
assert.NoError(t, err)
474+
475+
mockJSONRPC.AssertExpectations(t)
476+
mockGraphQL.AssertExpectations(t)
477+
}
478+
320479
func TestBalance(t *testing.T) {
321480
mockJSONRPC := &mocks.JSONRPC{}
322481
mockGraphQL := &mocks.GraphQL{}

0 commit comments

Comments
 (0)