Support authorised access to web 3 providers (#8075)

* jwt access token impl

* use secret or jwt

* rename

* separate method for splitting auth

* usage update

* Update beacon-chain/flags/base.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update beacon-chain/node/node.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* make things work

* removed unused code

* better, more flexible authorization

* move types and function to proper packages

* fix checking if endpoint is not set

* fix existing tests

* rename Endpoint field to Url

* Tests for HttpEndpoint

* better bearer auth

* tests for endpoint utils

* fix endpoint registration

* fix test build

* move endpoint parsing to powchain

* Revert "fix existing tests"

This reverts commit ceab192e6a.

* fix field name in tests

* gzl

* add httputils dependency

* remove httputils dependency

* fix compilation issue in blockchain service test

* correct endpoint fallback function and tests

* gzl

* remove pointer from currHttpEndpoint

* allow whitespace in auth string

* endpoint equality

* correct one auth data Equals test case

* remove pointer receiver

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>
This commit is contained in:
Shay Zluf
2021-04-15 14:02:02 +03:00
committed by GitHub
parent 2c6549b431
commit d77c298ec6
16 changed files with 461 additions and 77 deletions

View File

@@ -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")

View File

@@ -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,

View File

@@ -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",

View File

@@ -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")

View File

@@ -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")

View File

@@ -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,

View File

@@ -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
}

View File

@@ -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")
})
}

View File

@@ -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

View File

@@ -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) {

View File

@@ -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{

View File

@@ -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",
],
)

View File

@@ -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"],
)

View File

@@ -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
)

View File

@@ -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
}

View File

@@ -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))
})
}