From 3a4da60e659fb9feb94e9b52913354ed6f79b9ce Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 20 Sep 2022 10:28:56 +0200 Subject: [PATCH 1/6] cmd/geth, cmd/utils: make it possible to use geth attach with custom headers --- cmd/geth/consolecmd.go | 23 ++++++++++++++++++----- cmd/utils/flags.go | 7 +++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index 87bbe24b977a..fcec45fb5912 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -17,7 +17,9 @@ package main import ( + "context" "fmt" + "net/http" "strings" "github.com/ethereum/go-ethereum/cmd/utils" @@ -47,7 +49,7 @@ See https://geth.ethereum.org/docs/interface/javascript-console.`, Name: "attach", Usage: "Start an interactive JavaScript environment (connect to node)", ArgsUsage: "[endpoint]", - Flags: flags.Merge([]cli.Flag{utils.DataDirFlag}, consoleFlags), + Flags: flags.Merge([]cli.Flag{utils.DataDirFlag, utils.HttpHeaderFlag}, consoleFlags), Description: ` The Geth console is an interactive shell for the JavaScript runtime environment which exposes a node admin interface as well as the Ðapp JavaScript API. @@ -118,14 +120,25 @@ func remoteConsole(ctx *cli.Context) error { if ctx.Args().Len() > 1 { utils.Fatalf("invalid command-line: too many arguments") } - + var opts []rpc.ClientOption + if ctx.IsSet(utils.HttpHeaderFlag.Name) { + var customHeaders = make(http.Header) + for _, h := range ctx.StringSlice(utils.HttpHeaderFlag.Name) { + kv := strings.Split(h, ":") + if len(kv) != 2 { + utils.Fatalf("invalid http header directive: %q", h) + } + customHeaders.Add(kv[0], kv[1]) + } + opts = append(opts, rpc.WithHeaders(customHeaders)) + } endpoint := ctx.Args().First() if endpoint == "" { cfg := defaultNodeConfig() utils.SetDataDir(ctx, &cfg) endpoint = cfg.IPCEndpoint() } - client, err := dialRPC(endpoint) + client, err := dialRPC(endpoint, opts) if err != nil { utils.Fatalf("Unable to attach to remote geth: %v", err) } @@ -168,7 +181,7 @@ geth --exec "%s" console`, b.String()) // dialRPC returns a RPC client which connects to the given endpoint. // The check for empty endpoint implements the defaulting logic // for "geth attach" with no argument. -func dialRPC(endpoint string) (*rpc.Client, error) { +func dialRPC(endpoint string, opts []rpc.ClientOption) (*rpc.Client, error) { if endpoint == "" { endpoint = node.DefaultIPCEndpoint(clientIdentifier) } else if strings.HasPrefix(endpoint, "rpc:") || strings.HasPrefix(endpoint, "ipc:") { @@ -176,5 +189,5 @@ func dialRPC(endpoint string) (*rpc.Client, error) { // these prefixes. endpoint = endpoint[4:] } - return rpc.Dial(endpoint) + return rpc.DialOptions(context.Background(), endpoint, opts...) } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 745b9f088eb3..a8a04e5c405b 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -976,6 +976,13 @@ var ( Value: metrics.DefaultConfig.InfluxDBOrganization, Category: flags.MetricsCategory, } + + HttpHeaderFlag = &cli.StringSliceFlag{ + Name: "header", + Aliases: []string{"H"}, + Usage: "Pass custom header(s) to server", + Category: flags.NetworkingCategory, + } ) var ( From c5080fcbc5da9b4d5f548b4de9cc6c1eb131933e Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 20 Sep 2022 11:44:39 +0200 Subject: [PATCH 2/6] ethdb/remotedb: support custom headers for remotedb --- cmd/utils/flags.go | 5 +++-- ethdb/remotedb/remotedb.go | 23 +++++++++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index a8a04e5c405b..201dbb5ff784 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1002,6 +1002,7 @@ var ( DataDirFlag, AncientFlag, RemoteDBFlag, + HttpHeaderFlag, } ) @@ -2132,8 +2133,8 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly bool) ethdb. ) switch { case ctx.IsSet(RemoteDBFlag.Name): - log.Info("Using remote db", "url", ctx.String(RemoteDBFlag.Name)) - chainDb, err = remotedb.New(ctx.String(RemoteDBFlag.Name)) + log.Info("Using remote db", "url", ctx.String(RemoteDBFlag.Name), "headers", len(ctx.StringSlice(HttpHeaderFlag.Name))) + chainDb, err = remotedb.New(ctx.String(RemoteDBFlag.Name), ctx.StringSlice(HttpHeaderFlag.Name)) case ctx.String(SyncModeFlag.Name) == "light": chainDb, err = stack.OpenDatabase("lightchaindata", cache, handles, "", readonly) default: diff --git a/ethdb/remotedb/remotedb.go b/ethdb/remotedb/remotedb.go index 59a570bb5e96..9a29f80477e5 100644 --- a/ethdb/remotedb/remotedb.go +++ b/ethdb/remotedb/remotedb.go @@ -22,7 +22,10 @@ package remotedb import ( + "context" "errors" + "fmt" + "net/http" "strings" "github.com/ethereum/go-ethereum/common/hexutil" @@ -150,7 +153,7 @@ func (db *Database) Close() error { return nil } -func dialRPC(endpoint string) (*rpc.Client, error) { +func dialRPC(endpoint string, headers []string) (*rpc.Client, error) { if endpoint == "" { return nil, errors.New("endpoint must be specified") } @@ -159,11 +162,23 @@ func dialRPC(endpoint string) (*rpc.Client, error) { // these prefixes. endpoint = endpoint[4:] } - return rpc.Dial(endpoint) + var opts []rpc.ClientOption + if len(headers) > 0 { + var customHeaders = make(http.Header) + for _, h := range headers { + kv := strings.Split(h, ":") + if len(kv) != 2 { + return nil, fmt.Errorf("invalid http header directive: %q", h) + } + customHeaders.Add(kv[0], kv[1]) + } + opts = append(opts, rpc.WithHeaders(customHeaders)) + } + return rpc.DialOptions(context.Background(), endpoint, opts...) } -func New(endpoint string) (ethdb.Database, error) { - client, err := dialRPC(endpoint) +func New(endpoint string, headers []string) (ethdb.Database, error) { + client, err := dialRPC(endpoint, headers) if err != nil { return nil, err } From 71163ce92ad6dcac338647b5065a77c4db631635 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 20 Sep 2022 15:23:36 +0200 Subject: [PATCH 3/6] cmd/geth, cmd/utils, ethdb/remotedb: refactor a bit --- cmd/geth/consolecmd.go | 32 +------------------------------- cmd/utils/flags.go | 33 ++++++++++++++++++++++++++++++++- ethdb/remotedb/remotedb.go | 38 ++------------------------------------ 3 files changed, 35 insertions(+), 68 deletions(-) diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index fcec45fb5912..83c6b66a8a60 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -17,16 +17,12 @@ package main import ( - "context" "fmt" - "net/http" "strings" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/console" "github.com/ethereum/go-ethereum/internal/flags" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/rpc" "github.com/urfave/cli/v2" ) @@ -120,25 +116,13 @@ func remoteConsole(ctx *cli.Context) error { if ctx.Args().Len() > 1 { utils.Fatalf("invalid command-line: too many arguments") } - var opts []rpc.ClientOption - if ctx.IsSet(utils.HttpHeaderFlag.Name) { - var customHeaders = make(http.Header) - for _, h := range ctx.StringSlice(utils.HttpHeaderFlag.Name) { - kv := strings.Split(h, ":") - if len(kv) != 2 { - utils.Fatalf("invalid http header directive: %q", h) - } - customHeaders.Add(kv[0], kv[1]) - } - opts = append(opts, rpc.WithHeaders(customHeaders)) - } endpoint := ctx.Args().First() if endpoint == "" { cfg := defaultNodeConfig() utils.SetDataDir(ctx, &cfg) endpoint = cfg.IPCEndpoint() } - client, err := dialRPC(endpoint, opts) + client, err := utils.DialRPCWithHeaders(endpoint, ctx.StringSlice(utils.HttpHeaderFlag.Name)) if err != nil { utils.Fatalf("Unable to attach to remote geth: %v", err) } @@ -177,17 +161,3 @@ func ephemeralConsole(ctx *cli.Context) error { geth --exec "%s" console`, b.String()) return nil } - -// dialRPC returns a RPC client which connects to the given endpoint. -// The check for empty endpoint implements the defaulting logic -// for "geth attach" with no argument. -func dialRPC(endpoint string, opts []rpc.ClientOption) (*rpc.Client, error) { - if endpoint == "" { - endpoint = node.DefaultIPCEndpoint(clientIdentifier) - } else if strings.HasPrefix(endpoint, "rpc:") || strings.HasPrefix(endpoint, "ipc:") { - // Backwards compatibility with geth < 1.5 which required - // these prefixes. - endpoint = endpoint[4:] - } - return rpc.DialOptions(context.Background(), endpoint, opts...) -} diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 201dbb5ff784..3d3f4a86cf90 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -18,10 +18,13 @@ package utils import ( + "context" "crypto/ecdsa" + "errors" "fmt" "math" "math/big" + "net/http" "os" "path/filepath" godebug "runtime/debug" @@ -2134,7 +2137,11 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly bool) ethdb. switch { case ctx.IsSet(RemoteDBFlag.Name): log.Info("Using remote db", "url", ctx.String(RemoteDBFlag.Name), "headers", len(ctx.StringSlice(HttpHeaderFlag.Name))) - chainDb, err = remotedb.New(ctx.String(RemoteDBFlag.Name), ctx.StringSlice(HttpHeaderFlag.Name)) + client, err := DialRPCWithHeaders(ctx.String(RemoteDBFlag.Name), ctx.StringSlice(HttpHeaderFlag.Name)) + if err != nil { + break + } + chainDb = remotedb.New(client) case ctx.String(SyncModeFlag.Name) == "light": chainDb, err = stack.OpenDatabase("lightchaindata", cache, handles, "", readonly) default: @@ -2156,6 +2163,30 @@ func IsNetworkPreset(ctx *cli.Context) bool { return false } +func DialRPCWithHeaders(endpoint string, headers []string) (*rpc.Client, error) { + if endpoint == "" { + return nil, errors.New("endpoint must be specified") + } + if strings.HasPrefix(endpoint, "rpc:") || strings.HasPrefix(endpoint, "ipc:") { + // Backwards compatibility with geth < 1.5 which required + // these prefixes. + endpoint = endpoint[4:] + } + var opts []rpc.ClientOption + if len(headers) > 0 { + var customHeaders = make(http.Header) + for _, h := range headers { + kv := strings.Split(h, ":") + if len(kv) != 2 { + return nil, fmt.Errorf("invalid http header directive: %q", h) + } + customHeaders.Add(kv[0], kv[1]) + } + opts = append(opts, rpc.WithHeaders(customHeaders)) + } + return rpc.DialOptions(context.Background(), endpoint, opts...) +} + func MakeGenesis(ctx *cli.Context) *core.Genesis { var genesis *core.Genesis switch { diff --git a/ethdb/remotedb/remotedb.go b/ethdb/remotedb/remotedb.go index 9a29f80477e5..9ce657d78026 100644 --- a/ethdb/remotedb/remotedb.go +++ b/ethdb/remotedb/remotedb.go @@ -22,12 +22,6 @@ package remotedb import ( - "context" - "errors" - "fmt" - "net/http" - "strings" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/rpc" @@ -153,36 +147,8 @@ func (db *Database) Close() error { return nil } -func dialRPC(endpoint string, headers []string) (*rpc.Client, error) { - if endpoint == "" { - return nil, errors.New("endpoint must be specified") - } - if strings.HasPrefix(endpoint, "rpc:") || strings.HasPrefix(endpoint, "ipc:") { - // Backwards compatibility with geth < 1.5 which required - // these prefixes. - endpoint = endpoint[4:] - } - var opts []rpc.ClientOption - if len(headers) > 0 { - var customHeaders = make(http.Header) - for _, h := range headers { - kv := strings.Split(h, ":") - if len(kv) != 2 { - return nil, fmt.Errorf("invalid http header directive: %q", h) - } - customHeaders.Add(kv[0], kv[1]) - } - opts = append(opts, rpc.WithHeaders(customHeaders)) - } - return rpc.DialOptions(context.Background(), endpoint, opts...) -} - -func New(endpoint string, headers []string) (ethdb.Database, error) { - client, err := dialRPC(endpoint, headers) - if err != nil { - return nil, err - } +func New(client *rpc.Client) ethdb.Database { return &Database{ remote: client, - }, nil + } } From 858732707f29c4fec471fdb8ca500c185ab98e67 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 20 Sep 2022 18:41:09 +0200 Subject: [PATCH 4/6] cmd/geth: add test for custom headers --- cmd/geth/attach_test.go | 67 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 cmd/geth/attach_test.go diff --git a/cmd/geth/attach_test.go b/cmd/geth/attach_test.go new file mode 100644 index 000000000000..f885ae0ce633 --- /dev/null +++ b/cmd/geth/attach_test.go @@ -0,0 +1,67 @@ +package main + +import ( + "fmt" + "net" + "net/http" + "sync/atomic" + "testing" +) + +type testHandler struct { + body func(http.ResponseWriter, *http.Request) +} + +func (t *testHandler) ServeHTTP(out http.ResponseWriter, in *http.Request) { + t.body(out, in) +} + +// TestAttachWithHeaders tests that 'geth attach' with custom headers works, i.e +// that custom headers are forwarded to the target. +func TestAttachWithHeaders(t *testing.T) { + t.Parallel() + ln, err := net.Listen("tcp", "localhost:0") + if err != nil { + t.Fatal(err) + } + port := ln.Addr().(*net.TCPAddr).Port + testReceiveHeaders(t, ln, "attach", "-H", "first: one", "-H", "second: two", fmt.Sprintf("http://localhost:%d", port)) + // This way to do it fails due to flag ordering: + // + // testReceiveHeaders(t, ln, "-H", "first: one", "-H", "second: two", "attach", fmt.Sprintf("http://localhost:%d", port)) + // This is fixed in a follow-up PR. +} + +// TestAttachWithHeaders tests that 'geth db --remotedb' with custom headers works, i.e +// that custom headers are forwarded to the target. +func TestRemoteDbWithHeaders(t *testing.T) { + t.Parallel() + ln, err := net.Listen("tcp", "localhost:0") + if err != nil { + t.Fatal(err) + } + port := ln.Addr().(*net.TCPAddr).Port + testReceiveHeaders(t, ln, "db", "metadata", "--remotedb", fmt.Sprintf("http://localhost:%d", port), "-H", "first: one", "-H", "second: two") +} + +func testReceiveHeaders(t *testing.T, ln net.Listener, gethArgs ...string) { + var ok uint32 + server := &http.Server{ + Addr: "localhost:0", + Handler: &testHandler{func(w http.ResponseWriter, r *http.Request) { + // We expect two headers + if have, want := r.Header.Get("first"), "one"; have != want { + t.Fatalf("missing header, have %v want %v", have, want) + } + if have, want := r.Header.Get("second"), "two"; have != want { + t.Fatalf("missing header, have %v want %v", have, want) + } + atomic.StoreUint32(&ok, 1) + }}} + go server.Serve(ln) + defer server.Close() + runGeth(t, gethArgs...).WaitExit() + if atomic.LoadUint32(&ok) != 1 { + t.Fatal("Test fail, expected invocation to succeed") + } +} From 935a30bd1bac261e476016e10c9eb4748f227f80 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 29 Sep 2022 20:22:36 +0200 Subject: [PATCH 5/6] Update flags.go --- cmd/utils/flags.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 3d3f4a86cf90..ca6ded475668 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -983,7 +983,7 @@ var ( HttpHeaderFlag = &cli.StringSliceFlag{ Name: "header", Aliases: []string{"H"}, - Usage: "Pass custom header(s) to server", + Usage: "Pass custom headers to the RPC server wheng using --" + RemoteDBFlag.Name + " or the geth attach console.", Category: flags.NetworkingCategory, } ) From 8ff002c7c4cd217c0a63e691120d137ccc939465 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 30 Sep 2022 11:28:46 +0200 Subject: [PATCH 6/6] cmd/geth: license header --- cmd/geth/attach_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/cmd/geth/attach_test.go b/cmd/geth/attach_test.go index f885ae0ce633..7c5f951750fb 100644 --- a/cmd/geth/attach_test.go +++ b/cmd/geth/attach_test.go @@ -1,3 +1,19 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library 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 go-ethereum library 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 go-ethereum library. If not, see . + package main import (