diff --git a/beacon-chain/blockchain/service_test.go b/beacon-chain/blockchain/service_test.go index fb425bca98..f86f98bb65 100644 --- a/beacon-chain/blockchain/service_test.go +++ b/beacon-chain/blockchain/service_test.go @@ -90,7 +90,7 @@ func setupBeaconChain(t *testing.T, beaconDB db.Database) *Service { require.NoError(t, err) web3Service, err = powchain.NewService(ctx, &powchain.Web3ServiceConfig{ BeaconDB: beaconDB, - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, DepositContract: common.Address{}, }) require.NoError(t, err, "Unable to set up web3 service") diff --git a/beacon-chain/node/node.go b/beacon-chain/node/node.go index 40d37b8e60..c03169baba 100644 --- a/beacon-chain/node/node.go +++ b/beacon-chain/node/node.go @@ -436,7 +436,7 @@ func (b *BeaconNode) registerPOWChainService() error { } cfg := &powchain.Web3ServiceConfig{ - HTTPEndpoints: endpoints, + HttpEndpoints: endpoints, DepositContract: common.HexToAddress(depAddress), BeaconDB: b.db, DepositCache: b.depositCache, diff --git a/beacon-chain/powchain/BUILD.bazel b/beacon-chain/powchain/BUILD.bazel index f2f525383a..ce1a528cf8 100644 --- a/beacon-chain/powchain/BUILD.bazel +++ b/beacon-chain/powchain/BUILD.bazel @@ -9,6 +9,7 @@ go_library( "deposit.go", "log.go", "log_processing.go", + "provider.go", "service.go", ], importpath = "github.com/prysmaticlabs/prysm/beacon-chain/powchain", @@ -32,6 +33,8 @@ go_library( "//proto/beacon/db:go_default_library", "//shared/bytesutil:go_default_library", "//shared/hashutil:go_default_library", + "//shared/httputils:go_default_library", + "//shared/httputils/authorizationmethod:go_default_library", "//shared/logutil:go_default_library", "//shared/params:go_default_library", "//shared/timeutils:go_default_library", @@ -64,6 +67,7 @@ go_test( "init_test.go", "log_processing_test.go", "powchain_test.go", + "provider_test.go", "service_test.go", ], embed = [":go_default_library"], @@ -82,6 +86,8 @@ go_test( "//shared/bls:go_default_library", "//shared/bytesutil:go_default_library", "//shared/event:go_default_library", + "//shared/httputils:go_default_library", + "//shared/httputils/authorizationmethod:go_default_library", "//shared/params:go_default_library", "//shared/testutil:go_default_library", "//shared/testutil/assert:go_default_library", diff --git a/beacon-chain/powchain/block_reader_test.go b/beacon-chain/powchain/block_reader_test.go index 37e4e9904a..32c695ab92 100644 --- a/beacon-chain/powchain/block_reader_test.go +++ b/beacon-chain/powchain/block_reader_test.go @@ -32,7 +32,7 @@ func TestLatestMainchainInfo_OK(t *testing.T) { beaconDB := dbutil.SetupDB(t) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, DepositContract: testAcc.ContractAddr, BeaconDB: beaconDB, }) @@ -70,7 +70,7 @@ func TestLatestMainchainInfo_OK(t *testing.T) { func TestBlockHashByHeight_ReturnsHash(t *testing.T) { beaconDB := dbutil.SetupDB(t) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, BeaconDB: beaconDB, }) require.NoError(t, err, "unable to setup web3 ETH1.0 chain service") @@ -97,7 +97,7 @@ func TestBlockHashByHeight_ReturnsHash(t *testing.T) { func TestBlockHashByHeight_ReturnsError_WhenNoEth1Client(t *testing.T) { beaconDB := dbutil.SetupDB(t) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, BeaconDB: beaconDB, }) require.NoError(t, err, "unable to setup web3 ETH1.0 chain service") @@ -113,7 +113,7 @@ func TestBlockHashByHeight_ReturnsError_WhenNoEth1Client(t *testing.T) { func TestBlockExists_ValidHash(t *testing.T) { beaconDB := dbutil.SetupDB(t) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, BeaconDB: beaconDB, }) require.NoError(t, err, "unable to setup web3 ETH1.0 chain service") @@ -144,7 +144,7 @@ func TestBlockExists_ValidHash(t *testing.T) { func TestBlockExists_InvalidHash(t *testing.T) { beaconDB := dbutil.SetupDB(t) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, BeaconDB: beaconDB, }) require.NoError(t, err, "unable to setup web3 ETH1.0 chain service") @@ -158,7 +158,7 @@ func TestBlockExists_InvalidHash(t *testing.T) { func TestBlockExists_UsesCachedBlockInfo(t *testing.T) { beaconDB := dbutil.SetupDB(t) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, BeaconDB: beaconDB, }) require.NoError(t, err, "unable to setup web3 ETH1.0 chain service") @@ -181,7 +181,7 @@ func TestBlockExists_UsesCachedBlockInfo(t *testing.T) { func TestBlockExistsWithCache_UsesCachedHeaderInfo(t *testing.T) { beaconDB := dbutil.SetupDB(t) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, BeaconDB: beaconDB, }) require.NoError(t, err, "unable to setup web3 ETH1.0 chain service") @@ -202,7 +202,7 @@ func TestBlockExistsWithCache_UsesCachedHeaderInfo(t *testing.T) { func TestBlockExistsWithCache_HeaderNotCached(t *testing.T) { beaconDB := dbutil.SetupDB(t) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, BeaconDB: beaconDB, }) require.NoError(t, err, "unable to setup web3 ETH1.0 chain service") @@ -218,7 +218,7 @@ func TestService_BlockNumberByTimestamp(t *testing.T) { testAcc, err := contracts.Setup() require.NoError(t, err, "Unable to set up simulated backend") web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, BeaconDB: beaconDB, }) require.NoError(t, err) @@ -245,7 +245,7 @@ func TestService_BlockNumberByTimestampLessTargetTime(t *testing.T) { testAcc, err := contracts.Setup() require.NoError(t, err, "Unable to set up simulated backend") web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, BeaconDB: beaconDB, }) require.NoError(t, err) @@ -278,7 +278,7 @@ func TestService_BlockNumberByTimestampMoreTargetTime(t *testing.T) { testAcc, err := contracts.Setup() require.NoError(t, err, "Unable to set up simulated backend") web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, BeaconDB: beaconDB, }) require.NoError(t, err) @@ -309,7 +309,7 @@ func TestService_BlockNumberByTimestampMoreTargetTime(t *testing.T) { func TestService_BlockTimeByHeight_ReturnsError_WhenNoEth1Client(t *testing.T) { beaconDB := dbutil.SetupDB(t) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, BeaconDB: beaconDB, }) require.NoError(t, err, "unable to setup web3 ETH1.0 chain service") diff --git a/beacon-chain/powchain/deposit_test.go b/beacon-chain/powchain/deposit_test.go index 7d12a019cb..bc250c3656 100644 --- a/beacon-chain/powchain/deposit_test.go +++ b/beacon-chain/powchain/deposit_test.go @@ -24,7 +24,7 @@ const pubKeyErr = "could not convert bytes to public key" func TestProcessDeposit_OK(t *testing.T) { beaconDB := testDB.SetupDB(t) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, BeaconDB: beaconDB, }) require.NoError(t, err, "Unable to setup web3 ETH1.0 chain service") @@ -48,7 +48,7 @@ func TestProcessDeposit_OK(t *testing.T) { func TestProcessDeposit_InvalidMerkleBranch(t *testing.T) { beaconDB := testDB.SetupDB(t) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, BeaconDB: beaconDB, }) require.NoError(t, err, "unable to setup web3 ETH1.0 chain service") @@ -74,7 +74,7 @@ func TestProcessDeposit_InvalidPublicKey(t *testing.T) { hook := logTest.NewGlobal() beaconDB := testDB.SetupDB(t) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, BeaconDB: beaconDB, }) require.NoError(t, err, "unable to setup web3 ETH1.0 chain service") @@ -110,7 +110,7 @@ func TestProcessDeposit_InvalidSignature(t *testing.T) { hook := logTest.NewGlobal() beaconDB := testDB.SetupDB(t) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, BeaconDB: beaconDB, }) require.NoError(t, err, "unable to setup web3 ETH1.0 chain service") @@ -145,7 +145,7 @@ func TestProcessDeposit_UnableToVerify(t *testing.T) { hook := logTest.NewGlobal() beaconDB := testDB.SetupDB(t) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, BeaconDB: beaconDB, }) require.NoError(t, err, "unable to setup web3 ETH1.0 chain service") @@ -178,7 +178,7 @@ func TestProcessDeposit_UnableToVerify(t *testing.T) { func TestProcessDeposit_IncompleteDeposit(t *testing.T) { beaconDB := testDB.SetupDB(t) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, BeaconDB: beaconDB, }) require.NoError(t, err, "unable to setup web3 ETH1.0 chain service") @@ -239,7 +239,7 @@ func TestProcessDeposit_IncompleteDeposit(t *testing.T) { func TestProcessDeposit_AllDepositedSuccessfully(t *testing.T) { beaconDB := testDB.SetupDB(t) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, BeaconDB: beaconDB, }) require.NoError(t, err, "unable to setup web3 ETH1.0 chain service") diff --git a/beacon-chain/powchain/log_processing_test.go b/beacon-chain/powchain/log_processing_test.go index c73df69b2b..e4770a16af 100644 --- a/beacon-chain/powchain/log_processing_test.go +++ b/beacon-chain/powchain/log_processing_test.go @@ -37,7 +37,7 @@ func TestProcessDepositLog_OK(t *testing.T) { require.NoError(t, err) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, DepositContract: testAcc.ContractAddr, BeaconDB: beaconDB, DepositCache: depositCache, @@ -101,7 +101,7 @@ func TestProcessDepositLog_InsertsPendingDeposit(t *testing.T) { require.NoError(t, err) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, DepositContract: testAcc.ContractAddr, BeaconDB: beaconDB, DepositCache: depositCache, @@ -158,7 +158,7 @@ func TestUnpackDepositLogData_OK(t *testing.T) { require.NoError(t, err, "Unable to set up simulated backend") beaconDB := testDB.SetupDB(t) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, BeaconDB: beaconDB, DepositContract: testAcc.ContractAddr, }) @@ -209,7 +209,7 @@ func TestProcessETH2GenesisLog_8DuplicatePubkeys(t *testing.T) { require.NoError(t, err) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, DepositContract: testAcc.ContractAddr, BeaconDB: beaconDB, DepositCache: depositCache, @@ -280,7 +280,7 @@ func TestProcessETH2GenesisLog(t *testing.T) { require.NoError(t, err) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, DepositContract: testAcc.ContractAddr, BeaconDB: beaconDB, DepositCache: depositCache, @@ -367,7 +367,7 @@ func TestProcessETH2GenesisLog_CorrectNumOfDeposits(t *testing.T) { require.NoError(t, err) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, DepositContract: testAcc.ContractAddr, BeaconDB: kvStore, DepositCache: depositCache, @@ -460,7 +460,7 @@ func TestProcessETH2GenesisLog_LargePeriodOfNoLogs(t *testing.T) { require.NoError(t, err) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, DepositContract: testAcc.ContractAddr, BeaconDB: kvStore, DepositCache: depositCache, @@ -563,7 +563,7 @@ func TestWeb3ServiceProcessDepositLog_RequestMissedDeposits(t *testing.T) { require.NoError(t, err) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, DepositContract: testAcc.ContractAddr, BeaconDB: beaconDB, DepositCache: depositCache, @@ -655,7 +655,7 @@ func newPowchainService(t *testing.T, eth1Backend *contracts.TestAccount, beacon require.NoError(t, err) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, DepositContract: eth1Backend.ContractAddr, BeaconDB: beaconDB, DepositCache: depositCache, diff --git a/beacon-chain/powchain/provider.go b/beacon-chain/powchain/provider.go new file mode 100644 index 0000000000..b7e6b4278b --- /dev/null +++ b/beacon-chain/powchain/provider.go @@ -0,0 +1,49 @@ +package powchain + +import ( + "encoding/base64" + "strings" + + "github.com/prysmaticlabs/prysm/shared/httputils" + "github.com/prysmaticlabs/prysm/shared/httputils/authorizationmethod" +) + +// HttpEndpoint extracts an httputils.Endpoint from the provider parameter. +func HttpEndpoint(eth1Provider string) httputils.Endpoint { + endpoint := httputils.Endpoint{ + Url: "", + Auth: httputils.AuthorizationData{ + Method: authorizationmethod.None, + Value: "", + }} + + authValues := strings.Split(eth1Provider, ",") + endpoint.Url = strings.TrimSpace(authValues[0]) + if len(authValues) > 2 { + log.Errorf( + "ETH1 endpoint string can contain one comma for specifying the authorization header to access the provider."+ + " String contains too many commas: %d. Skipping authorization.", len(authValues)-1) + } else if len(authValues) == 2 { + switch httputils.Method(strings.TrimSpace(authValues[1])) { + case authorizationmethod.Basic: + basicAuthValues := strings.Split(strings.TrimSpace(authValues[1]), " ") + if len(basicAuthValues) != 2 { + log.Errorf("Basic Authentication has incorrect format. Skipping authorization.") + } else { + endpoint.Auth.Method = authorizationmethod.Basic + endpoint.Auth.Value = base64.StdEncoding.EncodeToString([]byte(basicAuthValues[1])) + } + case authorizationmethod.Bearer: + bearerAuthValues := strings.Split(strings.TrimSpace(authValues[1]), " ") + if len(bearerAuthValues) != 2 { + log.Errorf("Bearer Authentication has incorrect format. Skipping authorization.") + } else { + endpoint.Auth.Method = authorizationmethod.Bearer + endpoint.Auth.Value = bearerAuthValues[1] + } + case authorizationmethod.None: + log.Errorf("Authorization has incorrect format or authorization type is not supported.") + } + } + return endpoint +} diff --git a/beacon-chain/powchain/provider_test.go b/beacon-chain/powchain/provider_test.go new file mode 100644 index 0000000000..7833d604a9 --- /dev/null +++ b/beacon-chain/powchain/provider_test.go @@ -0,0 +1,74 @@ +package powchain + +import ( + "testing" + + "github.com/prysmaticlabs/prysm/shared/httputils/authorizationmethod" + "github.com/prysmaticlabs/prysm/shared/testutil/assert" + logTest "github.com/sirupsen/logrus/hooks/test" +) + +func TestHttpEndpoint(t *testing.T) { + hook := logTest.NewGlobal() + url := "http://test" + + t.Run("URL", func(t *testing.T) { + endpoint := HttpEndpoint(url) + assert.Equal(t, url, endpoint.Url) + assert.Equal(t, authorizationmethod.None, endpoint.Auth.Method) + }) + t.Run("URL with separator", func(t *testing.T) { + endpoint := HttpEndpoint(url + ",") + assert.Equal(t, url, endpoint.Url) + assert.Equal(t, authorizationmethod.None, endpoint.Auth.Method) + }) + t.Run("URL with whitespace", func(t *testing.T) { + endpoint := HttpEndpoint(" " + url + " ,") + assert.Equal(t, url, endpoint.Url) + assert.Equal(t, authorizationmethod.None, endpoint.Auth.Method) + }) + t.Run("Basic auth", func(t *testing.T) { + endpoint := HttpEndpoint(url + ",Basic username:password") + assert.Equal(t, url, endpoint.Url) + assert.Equal(t, authorizationmethod.Basic, endpoint.Auth.Method) + assert.Equal(t, "dXNlcm5hbWU6cGFzc3dvcmQ=", endpoint.Auth.Value) + }) + t.Run("Basic auth with whitespace", func(t *testing.T) { + endpoint := HttpEndpoint(url + ", Basic username:password ") + assert.Equal(t, url, endpoint.Url) + assert.Equal(t, authorizationmethod.Basic, endpoint.Auth.Method) + assert.Equal(t, "dXNlcm5hbWU6cGFzc3dvcmQ=", endpoint.Auth.Value) + }) + t.Run("Basic auth with incorrect format", func(t *testing.T) { + hook.Reset() + endpoint := HttpEndpoint(url + ",Basic username:password foo") + assert.Equal(t, url, endpoint.Url) + assert.Equal(t, authorizationmethod.None, endpoint.Auth.Method) + assert.LogsContain(t, hook, "Skipping authorization") + }) + t.Run("Bearer auth", func(t *testing.T) { + endpoint := HttpEndpoint(url + ",Bearer token") + assert.Equal(t, url, endpoint.Url) + assert.Equal(t, authorizationmethod.Bearer, endpoint.Auth.Method) + assert.Equal(t, "token", endpoint.Auth.Value) + }) + t.Run("Bearer auth with whitespace", func(t *testing.T) { + endpoint := HttpEndpoint(url + ", Bearer token ") + assert.Equal(t, url, endpoint.Url) + assert.Equal(t, authorizationmethod.Bearer, endpoint.Auth.Method) + assert.Equal(t, "token", endpoint.Auth.Value) + }) + t.Run("Bearer auth with incorrect format", func(t *testing.T) { + hook.Reset() + endpoint := HttpEndpoint(url + ",Bearer token foo") + assert.Equal(t, url, endpoint.Url) + assert.Equal(t, authorizationmethod.None, endpoint.Auth.Method) + assert.LogsContain(t, hook, "Skipping authorization") + }) + t.Run("Too many separators", func(t *testing.T) { + endpoint := HttpEndpoint(url + ",Bearer token,foo") + assert.Equal(t, url, endpoint.Url) + assert.Equal(t, authorizationmethod.None, endpoint.Auth.Method) + assert.LogsContain(t, hook, "Skipping authorization") + }) +} diff --git a/beacon-chain/powchain/service.go b/beacon-chain/powchain/service.go index b16de56668..821c8706f2 100644 --- a/beacon-chain/powchain/service.go +++ b/beacon-chain/powchain/service.go @@ -35,6 +35,8 @@ import ( contracts "github.com/prysmaticlabs/prysm/contracts/deposit-contract" protodb "github.com/prysmaticlabs/prysm/proto/beacon/db" "github.com/prysmaticlabs/prysm/shared/bytesutil" + "github.com/prysmaticlabs/prysm/shared/httputils" + "github.com/prysmaticlabs/prysm/shared/httputils/authorizationmethod" "github.com/prysmaticlabs/prysm/shared/logutil" "github.com/prysmaticlabs/prysm/shared/params" "github.com/prysmaticlabs/prysm/shared/timeutils" @@ -128,7 +130,8 @@ type Service struct { ctx context.Context cancel context.CancelFunc headTicker *time.Ticker - currHttpEndpoint string + httpEndpoints []httputils.Endpoint + currHttpEndpoint httputils.Endpoint httpLogger bind.ContractFilterer eth1DataFetcher RPCDataFetcher rpcClient RPCClient @@ -144,7 +147,7 @@ type Service struct { // Web3ServiceConfig defines a config struct for web3 service to use through its life cycle. type Web3ServiceConfig struct { - HTTPEndpoints []string + HttpEndpoints []string DepositContract common.Address BeaconDB db.HeadAccessDatabase DepositCache *depositcache.DepositCache @@ -172,16 +175,22 @@ func NewService(ctx context.Context, config *Web3ServiceConfig) (*Service, error config.Eth1HeaderReqLimit = defaultEth1HeaderReqLimit } - config.HTTPEndpoints = dedupEndpoints(config.HTTPEndpoints) + stringEndpoints := dedupEndpoints(config.HttpEndpoints) + endpoints := make([]httputils.Endpoint, len(stringEndpoints)) + for i, e := range stringEndpoints { + endpoints[i] = HttpEndpoint(e) + } + // Select first http endpoint in the provided list. - currEndpoint := "" - if len(config.HTTPEndpoints) > 0 { - currEndpoint = config.HTTPEndpoints[0] + var currEndpoint httputils.Endpoint + if len(config.HttpEndpoints) > 0 { + currEndpoint = endpoints[0] } s := &Service{ - cfg: config, ctx: ctx, cancel: cancel, + cfg: config, + httpEndpoints: endpoints, currHttpEndpoint: currEndpoint, latestEth1Data: &protodb.LatestETH1Data{ BlockHeight: 0, @@ -230,7 +239,7 @@ func NewService(ctx context.Context, config *Web3ServiceConfig) (*Service, error func (s *Service) Start() { // If the chain has not started already and we don't have access to eth1 nodes, we will not be // able to generate the genesis state. - if !s.chainStartData.Chainstarted && s.currHttpEndpoint == "" { + if !s.chainStartData.Chainstarted && s.currHttpEndpoint.Url == "" { // check for genesis state before shutting down the node, // if a genesis state exists, we can continue on. genState, err := s.cfg.BeaconDB.GenesisState(s.ctx) @@ -243,7 +252,7 @@ func (s *Service) Start() { } // Exit early if eth1 endpoint is not set. - if s.currHttpEndpoint == "" { + if s.currHttpEndpoint.Url == "" { return } go func() { @@ -375,11 +384,18 @@ func (s *Service) connectToPowChain() error { return nil } -func (s *Service) dialETH1Nodes(endpoint string) (*ethclient.Client, *gethRPC.Client, error) { - httpRPCClient, err := gethRPC.Dial(endpoint) +func (s *Service) dialETH1Nodes(endpoint httputils.Endpoint) (*ethclient.Client, *gethRPC.Client, error) { + httpRPCClient, err := gethRPC.Dial(endpoint.Url) if err != nil { return nil, nil, err } + if endpoint.Auth.Method != authorizationmethod.None { + header, err := endpoint.Auth.ToHeaderValue() + if err != nil { + return nil, nil, err + } + httpRPCClient.SetHeader("Authorization", header) + } httpClient := ethclient.NewClient(httpRPCClient) // Add a method to clean-up and close clients in the event // of any connection failure. @@ -451,7 +467,7 @@ func (s *Service) waitForConnection() { s.connectedETH1 = true s.runError = nil log.WithFields(logrus.Fields{ - "endpoint": logutil.MaskCredentialsLogging(s.currHttpEndpoint), + "endpoint": logutil.MaskCredentialsLogging(s.currHttpEndpoint.Url), }).Info("Connected to eth1 proof-of-work chain") return } @@ -480,7 +496,7 @@ func (s *Service) waitForConnection() { for { select { case <-ticker.C: - log.Debugf("Trying to dial endpoint: %s", logutil.MaskCredentialsLogging(s.currHttpEndpoint)) + log.Debugf("Trying to dial endpoint: %s", logutil.MaskCredentialsLogging(s.currHttpEndpoint.Url)) errConnect := s.connectToPowChain() if errConnect != nil { errorLogger(errConnect, "Could not connect to powchain endpoint") @@ -499,7 +515,7 @@ func (s *Service) waitForConnection() { s.connectedETH1 = true s.runError = nil log.WithFields(logrus.Fields{ - "endpoint": logutil.MaskCredentialsLogging(s.currHttpEndpoint), + "endpoint": logutil.MaskCredentialsLogging(s.currHttpEndpoint.Url), }).Info("Connected to eth1 proof-of-work chain") return } @@ -854,10 +870,10 @@ func (s *Service) determineEarliestVotingBlock(ctx context.Context, followBlock // is ready to serve we connect to it again. This method is only // relevant if we are on our backup endpoint. func (s *Service) checkDefaultEndpoint() { - primaryEndpoint := s.cfg.HTTPEndpoints[0] + primaryEndpoint := s.httpEndpoints[0] // Return early if we are running on our primary // endpoint. - if s.currHttpEndpoint == primaryEndpoint { + if s.currHttpEndpoint.Equals(primaryEndpoint) { return } @@ -885,10 +901,10 @@ func (s *Service) checkDefaultEndpoint() { func (s *Service) fallbackToNextEndpoint() { currEndpoint := s.currHttpEndpoint currIndex := 0 - totalEndpoints := len(s.cfg.HTTPEndpoints) + totalEndpoints := len(s.httpEndpoints) - for i, endpoint := range s.cfg.HTTPEndpoints { - if endpoint == currEndpoint { + for i, endpoint := range s.httpEndpoints { + if endpoint.Equals(currEndpoint) { currIndex = i break } @@ -901,8 +917,8 @@ func (s *Service) fallbackToNextEndpoint() { if nextIndex == currIndex { return } - s.currHttpEndpoint = s.cfg.HTTPEndpoints[nextIndex] - log.Infof("Falling back to alternative endpoint: %s", logutil.MaskCredentialsLogging(s.currHttpEndpoint)) + s.currHttpEndpoint = s.httpEndpoints[nextIndex] + log.Infof("Falling back to alternative endpoint: %s", logutil.MaskCredentialsLogging(s.currHttpEndpoint.Url)) } // validates the current powchain data saved and makes sure that any diff --git a/beacon-chain/powchain/service_test.go b/beacon-chain/powchain/service_test.go index bfa72d3e70..e71abe8817 100644 --- a/beacon-chain/powchain/service_test.go +++ b/beacon-chain/powchain/service_test.go @@ -22,6 +22,7 @@ import ( contracts "github.com/prysmaticlabs/prysm/contracts/deposit-contract" protodb "github.com/prysmaticlabs/prysm/proto/beacon/db" "github.com/prysmaticlabs/prysm/shared/event" + "github.com/prysmaticlabs/prysm/shared/httputils" "github.com/prysmaticlabs/prysm/shared/params" "github.com/prysmaticlabs/prysm/shared/testutil" "github.com/prysmaticlabs/prysm/shared/testutil/assert" @@ -124,7 +125,7 @@ func TestStart_OK(t *testing.T) { testAcc, err := contracts.Setup() require.NoError(t, err, "Unable to set up simulated backend") web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, DepositContract: testAcc.ContractAddr, BeaconDB: beaconDB, }) @@ -147,13 +148,13 @@ func TestStart_OK(t *testing.T) { web3Service.cancel() } -func TestStart_NoHTTPEndpointDefinedFails_WithoutChainStarted(t *testing.T) { +func TestStart_NoHttpEndpointDefinedFails_WithoutChainStarted(t *testing.T) { hook := logTest.NewGlobal() beaconDB := dbutil.SetupDB(t) testAcc, err := contracts.Setup() require.NoError(t, err, "Unable to set up simulated backend") s, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{""}, // No endpoint defined! + HttpEndpoints: []string{""}, // No endpoint defined! DepositContract: testAcc.ContractAddr, BeaconDB: beaconDB, }) @@ -182,7 +183,7 @@ func TestStart_NoHTTPEndpointDefinedFails_WithoutChainStarted(t *testing.T) { hook.Reset() } -func TestStart_NoHTTPEndpointDefinedSucceeds_WithGenesisState(t *testing.T) { +func TestStart_NoHttpEndpointDefinedSucceeds_WithGenesisState(t *testing.T) { hook := logTest.NewGlobal() beaconDB := dbutil.SetupDB(t) testAcc, err := contracts.Setup() @@ -196,7 +197,7 @@ func TestStart_NoHTTPEndpointDefinedSucceeds_WithGenesisState(t *testing.T) { depositCache, err := depositcache.New() require.NoError(t, err) s, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{""}, // No endpoint defined! + HttpEndpoints: []string{""}, // No endpoint defined! DepositContract: testAcc.ContractAddr, BeaconDB: beaconDB, DepositCache: depositCache, @@ -216,7 +217,7 @@ func TestStart_NoHTTPEndpointDefinedSucceeds_WithGenesisState(t *testing.T) { hook.Reset() } -func TestStart_NoHTTPEndpointDefinedSucceeds_WithChainStarted(t *testing.T) { +func TestStart_NoHttpEndpointDefinedSucceeds_WithChainStarted(t *testing.T) { hook := logTest.NewGlobal() beaconDB := dbutil.SetupDB(t) testAcc, err := contracts.Setup() @@ -227,7 +228,7 @@ func TestStart_NoHTTPEndpointDefinedSucceeds_WithChainStarted(t *testing.T) { Trie: &protodb.SparseMerkleTrie{}, })) s, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{""}, // No endpoint defined! + HttpEndpoints: []string{""}, // No endpoint defined! DepositContract: testAcc.ContractAddr, BeaconDB: beaconDB, }) @@ -244,7 +245,7 @@ func TestStop_OK(t *testing.T) { require.NoError(t, err, "Unable to set up simulated backend") beaconDB := dbutil.SetupDB(t) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, DepositContract: testAcc.ContractAddr, BeaconDB: beaconDB, }) @@ -269,7 +270,7 @@ func TestService_Eth1Synced(t *testing.T) { require.NoError(t, err, "Unable to set up simulated backend") beaconDB := dbutil.SetupDB(t) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, DepositContract: testAcc.ContractAddr, BeaconDB: beaconDB, }) @@ -290,7 +291,7 @@ func TestFollowBlock_OK(t *testing.T) { require.NoError(t, err, "Unable to set up simulated backend") beaconDB := dbutil.SetupDB(t) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, DepositContract: testAcc.ContractAddr, BeaconDB: beaconDB, }) @@ -363,7 +364,7 @@ func TestHandlePanic_OK(t *testing.T) { hook := logTest.NewGlobal() beaconDB := dbutil.SetupDB(t) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, BeaconDB: beaconDB, }) require.NoError(t, err, "unable to setup web3 ETH1.0 chain service") @@ -402,7 +403,7 @@ func TestLogTillGenesis_OK(t *testing.T) { require.NoError(t, err, "Unable to set up simulated backend") beaconDB := dbutil.SetupDB(t) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, DepositContract: testAcc.ContractAddr, BeaconDB: beaconDB, }) @@ -462,7 +463,7 @@ func TestNewService_EarliestVotingBlock(t *testing.T) { require.NoError(t, err, "Unable to set up simulated backend") beaconDB := dbutil.SetupDB(t) web3Service, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, DepositContract: testAcc.ContractAddr, BeaconDB: beaconDB, }) @@ -513,7 +514,7 @@ func TestNewService_Eth1HeaderRequLimit(t *testing.T) { beaconDB := dbutil.SetupDB(t) s1, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, DepositContract: testAcc.ContractAddr, BeaconDB: beaconDB, }) @@ -521,7 +522,7 @@ func TestNewService_Eth1HeaderRequLimit(t *testing.T) { assert.Equal(t, defaultEth1HeaderReqLimit, s1.cfg.Eth1HeaderReqLimit, "default eth1 header request limit not set") s2, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{endpoint}, + HttpEndpoints: []string{endpoint}, DepositContract: testAcc.ContractAddr, BeaconDB: beaconDB, Eth1HeaderReqLimit: uint64(150), @@ -539,36 +540,36 @@ func TestServiceFallbackCorrectly(t *testing.T) { beaconDB := dbutil.SetupDB(t) s1, err := NewService(context.Background(), &Web3ServiceConfig{ - HTTPEndpoints: []string{firstEndpoint}, + HttpEndpoints: []string{firstEndpoint}, DepositContract: testAcc.ContractAddr, BeaconDB: beaconDB, }) require.NoError(t, err) - assert.Equal(t, firstEndpoint, s1.currHttpEndpoint, "Unexpected http endpoint") + assert.Equal(t, firstEndpoint, s1.currHttpEndpoint.Url, "Unexpected http endpoint") // Stay at the first endpoint. s1.fallbackToNextEndpoint() - assert.Equal(t, firstEndpoint, s1.currHttpEndpoint, "Unexpected http endpoint") + assert.Equal(t, firstEndpoint, s1.currHttpEndpoint.Url, "Unexpected http endpoint") - s1.cfg.HTTPEndpoints = append(s1.cfg.HTTPEndpoints, secondEndpoint) + s1.httpEndpoints = append(s1.httpEndpoints, httputils.Endpoint{Url: secondEndpoint}) s1.fallbackToNextEndpoint() - assert.Equal(t, secondEndpoint, s1.currHttpEndpoint, "Unexpected http endpoint") + assert.Equal(t, secondEndpoint, s1.currHttpEndpoint.Url, "Unexpected http endpoint") thirdEndpoint := "C" fourthEndpoint := "D" - s1.cfg.HTTPEndpoints = append(s1.cfg.HTTPEndpoints, thirdEndpoint, fourthEndpoint) + s1.httpEndpoints = append(s1.httpEndpoints, httputils.Endpoint{Url: thirdEndpoint}, httputils.Endpoint{Url: fourthEndpoint}) s1.fallbackToNextEndpoint() - assert.Equal(t, thirdEndpoint, s1.currHttpEndpoint, "Unexpected http endpoint") + assert.Equal(t, thirdEndpoint, s1.currHttpEndpoint.Url, "Unexpected http endpoint") s1.fallbackToNextEndpoint() - assert.Equal(t, fourthEndpoint, s1.currHttpEndpoint, "Unexpected http endpoint") + assert.Equal(t, fourthEndpoint, s1.currHttpEndpoint.Url, "Unexpected http endpoint") // Rollover correctly back to the first endpoint s1.fallbackToNextEndpoint() - assert.Equal(t, firstEndpoint, s1.currHttpEndpoint, "Unexpected http endpoint") + assert.Equal(t, firstEndpoint, s1.currHttpEndpoint.Url, "Unexpected http endpoint") } func TestDedupEndpoints(t *testing.T) { diff --git a/cmd/beacon-chain/flags/base.go b/cmd/beacon-chain/flags/base.go index 7d13ddbbbd..fc10cb4f9e 100644 --- a/cmd/beacon-chain/flags/base.go +++ b/cmd/beacon-chain/flags/base.go @@ -11,11 +11,12 @@ var ( // HTTPWeb3ProviderFlag provides an HTTP access endpoint to an ETH 1.0 RPC. HTTPWeb3ProviderFlag = &cli.StringFlag{ Name: "http-web3provider", - Usage: "A mainchain web3 provider string http endpoint. This is our primary web3 provider", + Usage: "A mainchain web3 provider string http endpoint. Can contain auth header as well in the format --http-web3provider=\"https://goerli.infura.io/v3/xxxx,Basic xxx\" for project secret (base64 encoded) and --http-web3provider=\"https://goerli.infura.io/v3/xxxx,Bearer xxx\" for jwt use", + Value: "", } FallbackWeb3ProviderFlag = &cli.StringSliceFlag{ Name: "fallback-web3provider", - Usage: "A mainchain web3 provider string http endpoint. This is our fallback web3 provider, this flag maybe used multiple times.", + Usage: "A mainchain web3 provider string http endpoint. This is our fallback web3 provider, this flag may be used multiple times.", } // DepositContractFlag defines a flag for the deposit contract address. DepositContractFlag = &cli.StringFlag{ diff --git a/shared/httputils/BUILD.bazel b/shared/httputils/BUILD.bazel new file mode 100644 index 0000000000..d87847088d --- /dev/null +++ b/shared/httputils/BUILD.bazel @@ -0,0 +1,21 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_test") +load("@prysm//tools/go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["endpoint.go"], + importpath = "github.com/prysmaticlabs/prysm/shared/httputils", + visibility = ["//visibility:public"], + deps = ["//shared/httputils/authorizationmethod:go_default_library"], +) + +go_test( + name = "go_default_test", + srcs = ["endpoint_test.go"], + embed = [":go_default_library"], + deps = [ + "//shared/httputils/authorizationmethod:go_default_library", + "//shared/testutil/assert:go_default_library", + "//shared/testutil/require:go_default_library", + ], +) diff --git a/shared/httputils/authorizationmethod/BUILD.bazel b/shared/httputils/authorizationmethod/BUILD.bazel new file mode 100644 index 0000000000..713b7db417 --- /dev/null +++ b/shared/httputils/authorizationmethod/BUILD.bazel @@ -0,0 +1,8 @@ +load("@prysm//tools/go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["authorization_method.go"], + importpath = "github.com/prysmaticlabs/prysm/shared/httputils/authorizationmethod", + visibility = ["//visibility:public"], +) diff --git a/shared/httputils/authorizationmethod/authorization_method.go b/shared/httputils/authorizationmethod/authorization_method.go new file mode 100644 index 0000000000..a5f24bb5cc --- /dev/null +++ b/shared/httputils/authorizationmethod/authorization_method.go @@ -0,0 +1,13 @@ +package authorizationmethod + +// AuthorizationMethod is an authorization method such as 'Basic' or 'Bearer'. +type AuthorizationMethod uint8 + +const ( + // None represents no authorization method. + None AuthorizationMethod = iota + // Basic represents Basic Authentication. + Basic + // Bearer represents Bearer Authentication (token authentication). + Bearer +) diff --git a/shared/httputils/endpoint.go b/shared/httputils/endpoint.go new file mode 100644 index 0000000000..55e24f75fc --- /dev/null +++ b/shared/httputils/endpoint.go @@ -0,0 +1,53 @@ +package httputils + +import ( + "errors" + "strings" + + "github.com/prysmaticlabs/prysm/shared/httputils/authorizationmethod" +) + +// Endpoint is an endpoint with authorization data. +type Endpoint struct { + Url string + Auth AuthorizationData +} + +// AuthorizationData holds all information necessary to authorize with HTTP. +type AuthorizationData struct { + Method authorizationmethod.AuthorizationMethod + Value string +} + +func (e Endpoint) Equals(other Endpoint) bool { + return e.Url == other.Url && e.Auth.Equals(other.Auth) +} + +func (d AuthorizationData) Equals(other AuthorizationData) bool { + return d.Method == other.Method && d.Value == other.Value +} + +// ToHeaderValue retrieves the value of the authorization header from AuthorizationData. +func (d *AuthorizationData) ToHeaderValue() (string, error) { + switch d.Method { + case authorizationmethod.Basic: + return "Basic " + d.Value, nil + case authorizationmethod.Bearer: + return "Bearer " + d.Value, nil + case authorizationmethod.None: + return "", nil + } + + return "", errors.New("could not create HTTP header for unknown authorization method") +} + +// Method returns the authorizationmethod.AuthorizationMethod corresponding with the parameter value. +func Method(auth string) authorizationmethod.AuthorizationMethod { + if strings.HasPrefix(strings.ToLower(auth), "basic") { + return authorizationmethod.Basic + } + if strings.HasPrefix(strings.ToLower(auth), "bearer") { + return authorizationmethod.Bearer + } + return authorizationmethod.None +} diff --git a/shared/httputils/endpoint_test.go b/shared/httputils/endpoint_test.go new file mode 100644 index 0000000000..e8135ba071 --- /dev/null +++ b/shared/httputils/endpoint_test.go @@ -0,0 +1,142 @@ +package httputils + +import ( + "testing" + + "github.com/prysmaticlabs/prysm/shared/httputils/authorizationmethod" + "github.com/prysmaticlabs/prysm/shared/testutil/assert" + "github.com/prysmaticlabs/prysm/shared/testutil/require" +) + +func TestToHeaderValue(t *testing.T) { + t.Run("None", func(t *testing.T) { + data := &AuthorizationData{ + Method: authorizationmethod.None, + Value: "foo", + } + header, err := data.ToHeaderValue() + require.NoError(t, err) + assert.Equal(t, "", header) + }) + t.Run("Basic", func(t *testing.T) { + data := &AuthorizationData{ + Method: authorizationmethod.Basic, + Value: "foo", + } + header, err := data.ToHeaderValue() + require.NoError(t, err) + assert.Equal(t, "Basic foo", header) + }) + t.Run("Bearer", func(t *testing.T) { + data := &AuthorizationData{ + Method: authorizationmethod.Bearer, + Value: "foo", + } + header, err := data.ToHeaderValue() + require.NoError(t, err) + assert.Equal(t, "Bearer foo", header) + }) + t.Run("Unknown", func(t *testing.T) { + data := &AuthorizationData{ + Method: 99, + Value: "foo", + } + _, err := data.ToHeaderValue() + require.NotNil(t, err) + }) +} + +func TestMethod(t *testing.T) { + t.Run("None", func(t *testing.T) { + method := Method("") + assert.Equal(t, authorizationmethod.None, method) + method = Method("foo") + assert.Equal(t, authorizationmethod.None, method) + }) + t.Run("Basic", func(t *testing.T) { + method := Method("Basic") + assert.Equal(t, authorizationmethod.Basic, method) + }) + t.Run("Basic different text case", func(t *testing.T) { + method := Method("bAsIc") + assert.Equal(t, authorizationmethod.Basic, method) + }) + t.Run("Bearer", func(t *testing.T) { + method := Method("Bearer") + assert.Equal(t, authorizationmethod.Bearer, method) + }) + t.Run("Bearer different text case", func(t *testing.T) { + method := Method("bEaReR") + assert.Equal(t, authorizationmethod.Bearer, method) + }) +} + +func TestEndpointEquals(t *testing.T) { + e := Endpoint{ + Url: "Url", + Auth: AuthorizationData{ + Method: authorizationmethod.Basic, + Value: "Basic username:password", + }, + } + + t.Run("equal", func(t *testing.T) { + other := Endpoint{ + Url: "Url", + Auth: AuthorizationData{ + Method: authorizationmethod.Basic, + Value: "Basic username:password", + }, + } + assert.Equal(t, true, e.Equals(other)) + }) + t.Run("different URL", func(t *testing.T) { + other := Endpoint{ + Url: "Different", + Auth: AuthorizationData{ + Method: authorizationmethod.Basic, + Value: "Basic username:password", + }, + } + assert.Equal(t, false, e.Equals(other)) + }) + t.Run("different auth data", func(t *testing.T) { + other := Endpoint{ + Url: "Url", + Auth: AuthorizationData{ + Method: authorizationmethod.Bearer, + Value: "Bearer token", + }, + } + assert.Equal(t, false, e.Equals(other)) + }) +} + +func TestAuthorizationDataEquals(t *testing.T) { + d := AuthorizationData{ + Method: authorizationmethod.Basic, + Value: "username:password", + } + + t.Run("equal", func(t *testing.T) { + other := AuthorizationData{ + Method: authorizationmethod.Basic, + Value: "username:password", + } + assert.Equal(t, true, d.Equals(other)) + }) + t.Run("different method", func(t *testing.T) { + other := AuthorizationData{ + Method: authorizationmethod.None, + Value: "username:password", + } + assert.Equal(t, false, d.Equals(other)) + }) + t.Run("different value", func(t *testing.T) { + other := AuthorizationData{ + Method: authorizationmethod.Basic, + Value: "different:different", + } + assert.Equal(t, false, d.Equals(other)) + }) +}