diff --git a/api/client/builder/BUILD.bazel b/api/client/builder/BUILD.bazel index 7f4a10ca41..5c8dfe73da 100644 --- a/api/client/builder/BUILD.bazel +++ b/api/client/builder/BUILD.bazel @@ -13,6 +13,8 @@ go_library( "//consensus-types/primitives:go_default_library", "//encoding/bytesutil:go_default_library", "//monitoring/tracing:go_default_library", + "//network:go_default_library", + "//network/authorization:go_default_library", "//proto/engine/v1:go_default_library", "//proto/prysm/v1alpha1:go_default_library", "@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", diff --git a/api/client/builder/client.go b/api/client/builder/client.go index cf6b13327a..b2d9109872 100644 --- a/api/client/builder/client.go +++ b/api/client/builder/client.go @@ -15,6 +15,8 @@ import ( "github.com/pkg/errors" types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v3/monitoring/tracing" + "github.com/prysmaticlabs/prysm/v3/network" + "github.com/prysmaticlabs/prysm/v3/network/authorization" v1 "github.com/prysmaticlabs/prysm/v3/proto/engine/v1" ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1" log "github.com/sirupsen/logrus" @@ -83,6 +85,15 @@ func (*requestLogger) observe(r *http.Request) (e error) { var _ observer = &requestLogger{} +// BuilderClient provides a collection of helper methods for calling Builder API endpoints. +type BuilderClient interface { + NodeURL() string + GetHeader(ctx context.Context, slot types.Slot, parentHash [32]byte, pubkey [48]byte) (*ethpb.SignedBuilderBid, error) + RegisterValidator(ctx context.Context, svr []*ethpb.SignedValidatorRegistrationV1) error + SubmitBlindedBlock(ctx context.Context, sb *ethpb.SignedBlindedBeaconBlockBellatrix) (*v1.ExecutionPayload, error) + Status(ctx context.Context) error +} + // Client provides a collection of helper methods for calling Builder API endpoints. type Client struct { hc *http.Client @@ -94,7 +105,8 @@ type Client struct { // `host` is the base host + port used to construct request urls. This value can be // a URL string, or NewClient will assume an http endpoint if just `host:port` is used. func NewClient(host string, opts ...ClientOpt) (*Client, error) { - u, err := urlForHost(host) + endpoint := covertEndPoint(host) + u, err := urlForHost(endpoint.Url) if err != nil { return nil, err } @@ -304,3 +316,12 @@ func non200Err(response *http.Response) error { return errors.Wrap(ErrNotOK, fmt.Sprintf("unsupported error code: %d", response.StatusCode)) } } + +func covertEndPoint(ep string) network.Endpoint { + return network.Endpoint{ + Url: ep, + Auth: network.AuthorizationData{ // Auth is not used for builder. + Method: authorization.None, + Value: "", + }} +} diff --git a/api/client/builder/testing/BUILD.bazel b/api/client/builder/testing/BUILD.bazel new file mode 100644 index 0000000000..c62defced1 --- /dev/null +++ b/api/client/builder/testing/BUILD.bazel @@ -0,0 +1,14 @@ +load("@prysm//tools/go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["mock.go"], + importpath = "github.com/prysmaticlabs/prysm/v3/api/client/builder/testing", + visibility = ["//visibility:public"], + deps = [ + "//consensus-types/primitives:go_default_library", + "//encoding/bytesutil:go_default_library", + "//proto/engine/v1:go_default_library", + "//proto/prysm/v1alpha1:go_default_library", + ], +) diff --git a/api/client/builder/testing/mock.go b/api/client/builder/testing/mock.go new file mode 100644 index 0000000000..19bc0c36e4 --- /dev/null +++ b/api/client/builder/testing/mock.go @@ -0,0 +1,49 @@ +package testing + +import ( + "context" + + types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" + "github.com/prysmaticlabs/prysm/v3/encoding/bytesutil" + v1 "github.com/prysmaticlabs/prysm/v3/proto/engine/v1" + ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1" +) + +// MockClient is a mock implementation of BuilderClient. +type MockClient struct { + RegisteredVals map[[48]byte]bool +} + +// NewClient creates a new, correctly initialized mock. +func NewClient() MockClient { + return MockClient{RegisteredVals: map[[48]byte]bool{}} +} + +// NodeURL -- +func (MockClient) NodeURL() string { + return "" +} + +// GetHeader -- +func (MockClient) GetHeader(_ context.Context, _ types.Slot, _ [32]byte, _ [48]byte) (*ethpb.SignedBuilderBid, error) { + return nil, nil +} + +// RegisterValidator -- +func (m MockClient) RegisterValidator(_ context.Context, svr []*ethpb.SignedValidatorRegistrationV1) error { + for _, r := range svr { + b := bytesutil.ToBytes48(r.Message.Pubkey) + m.RegisteredVals[b] = true + } + return nil +} + +// SubmitBlindedBlock -- +func (MockClient) SubmitBlindedBlock(_ context.Context, _ *ethpb.SignedBlindedBeaconBlockBellatrix) (*v1.ExecutionPayload, error) { + return nil, nil +} + +// Status -- +func (MockClient) Status(_ context.Context) error { + return nil +} diff --git a/beacon-chain/builder/BUILD.bazel b/beacon-chain/builder/BUILD.bazel index 07d7f3a40b..3e5bc36777 100644 --- a/beacon-chain/builder/BUILD.bazel +++ b/beacon-chain/builder/BUILD.bazel @@ -1,4 +1,4 @@ -load("@prysm//tools/go:def.bzl", "go_library") +load("@prysm//tools/go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", @@ -16,8 +16,6 @@ go_library( "//cmd/beacon-chain/flags:go_default_library", "//consensus-types/primitives:go_default_library", "//encoding/bytesutil:go_default_library", - "//network:go_default_library", - "//network/authorization:go_default_library", "//proto/engine/v1:go_default_library", "//proto/prysm/v1alpha1:go_default_library", "@com_github_pkg_errors//:go_default_library", @@ -28,3 +26,18 @@ go_library( "@io_opencensus_go//trace:go_default_library", ], ) + +go_test( + name = "go_default_test", + srcs = ["service_test.go"], + embed = [":go_default_library"], + deps = [ + "//api/client/builder/testing:go_default_library", + "//beacon-chain/blockchain/testing:go_default_library", + "//beacon-chain/db/testing:go_default_library", + "//encoding/bytesutil:go_default_library", + "//proto/prysm/v1alpha1:go_default_library", + "//testing/assert:go_default_library", + "//testing/require:go_default_library", + ], +) diff --git a/beacon-chain/builder/option.go b/beacon-chain/builder/option.go index 2f747b4135..5eba6bac3c 100644 --- a/beacon-chain/builder/option.go +++ b/beacon-chain/builder/option.go @@ -1,11 +1,10 @@ package builder import ( + "github.com/prysmaticlabs/prysm/v3/api/client/builder" "github.com/prysmaticlabs/prysm/v3/beacon-chain/blockchain" "github.com/prysmaticlabs/prysm/v3/beacon-chain/db" "github.com/prysmaticlabs/prysm/v3/cmd/beacon-chain/flags" - "github.com/prysmaticlabs/prysm/v3/network" - "github.com/prysmaticlabs/prysm/v3/network/authorization" "github.com/urfave/cli/v2" ) @@ -14,22 +13,30 @@ type Option func(s *Service) error // FlagOptions for builder service flag configurations. func FlagOptions(c *cli.Context) ([]Option, error) { endpoint := c.String(flags.MevRelayEndpoint.Name) + var client *builder.Client + if endpoint != "" { + var err error + client, err = builder.NewClient(endpoint) + if err != nil { + return nil, err + } + } opts := []Option{ - WithBuilderEndpoints(endpoint), + WithBuilderClient(client), } return opts, nil } -// WithBuilderEndpoints sets the endpoint for the beacon chain builder service. -func WithBuilderEndpoints(endpoint string) Option { +// WithBuilderClient sets the builder client for the beacon chain builder service. +func WithBuilderClient(client builder.BuilderClient) Option { return func(s *Service) error { - s.cfg.builderEndpoint = covertEndPoint(endpoint) + s.cfg.builderClient = client return nil } } // WithHeadFetcher gets the head info from chain service. -func WithHeadFetcher(svc *blockchain.Service) Option { +func WithHeadFetcher(svc blockchain.HeadFetcher) Option { return func(s *Service) error { s.cfg.headFetcher = svc return nil @@ -43,12 +50,3 @@ func WithDatabase(beaconDB db.HeadAccessDatabase) Option { return nil } } - -func covertEndPoint(ep string) network.Endpoint { - return network.Endpoint{ - Url: ep, - Auth: network.AuthorizationData{ // Auth is not used for builder. - Method: authorization.None, - Value: "", - }} -} diff --git a/beacon-chain/builder/service.go b/beacon-chain/builder/service.go index 42860cec28..dc42b59673 100644 --- a/beacon-chain/builder/service.go +++ b/beacon-chain/builder/service.go @@ -2,6 +2,7 @@ package builder import ( "context" + "reflect" "time" "github.com/pkg/errors" @@ -10,7 +11,6 @@ import ( "github.com/prysmaticlabs/prysm/v3/beacon-chain/db" types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v3/encoding/bytesutil" - "github.com/prysmaticlabs/prysm/v3/network" v1 "github.com/prysmaticlabs/prysm/v3/proto/engine/v1" ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1" log "github.com/sirupsen/logrus" @@ -30,15 +30,15 @@ type BlockBuilder interface { // config defines a config struct for dependencies into the service. type config struct { - builderEndpoint network.Endpoint - beaconDB db.HeadAccessDatabase - headFetcher blockchain.HeadFetcher + builderClient builder.BuilderClient + beaconDB db.HeadAccessDatabase + headFetcher blockchain.HeadFetcher } // Service defines a service that provides a client for interacting with the beacon chain and MEV relay network. type Service struct { cfg *config - c *builder.Client + c builder.BuilderClient ctx context.Context cancel context.CancelFunc } @@ -56,18 +56,14 @@ func NewService(ctx context.Context, opts ...Option) (*Service, error) { return nil, err } } - if s.cfg.builderEndpoint.Url != "" { - c, err := builder.NewClient(s.cfg.builderEndpoint.Url) - if err != nil { - return nil, err - } - s.c = c + if s.cfg.builderClient != nil && !reflect.ValueOf(s.cfg.builderClient).IsNil() { + s.c = s.cfg.builderClient // Is the builder up? if err := s.c.Status(ctx); err != nil { log.WithError(err).Error("Failed to check builder status") } else { - log.WithField("endpoint", c.NodeURL()).Info("Builder has been configured") + log.WithField("endpoint", s.c.NodeURL()).Info("Builder has been configured") log.Warn("Outsourcing block construction to external builders adds non-trivial delay to block propagation time. " + "Builder-constructed blocks or fallback blocks may get orphaned. Use at your own risk!") } @@ -157,7 +153,7 @@ func (s *Service) RegisterValidator(ctx context.Context, reg []*ethpb.SignedVali return s.cfg.beaconDB.SaveRegistrationsByValidatorIDs(ctx, idxs, msgs) } -// Configured returns true if the user has input a builder URL. +// Configured returns true if the user has configured a builder client. func (s *Service) Configured() bool { - return s.cfg.builderEndpoint.Url != "" + return s.c != nil && !reflect.ValueOf(s.c).IsNil() } diff --git a/beacon-chain/builder/service_test.go b/beacon-chain/builder/service_test.go new file mode 100644 index 0000000000..84c08dfdbf --- /dev/null +++ b/beacon-chain/builder/service_test.go @@ -0,0 +1,39 @@ +package builder + +import ( + "context" + "testing" + + buildertesting "github.com/prysmaticlabs/prysm/v3/api/client/builder/testing" + blockchainTesting "github.com/prysmaticlabs/prysm/v3/beacon-chain/blockchain/testing" + dbtesting "github.com/prysmaticlabs/prysm/v3/beacon-chain/db/testing" + "github.com/prysmaticlabs/prysm/v3/encoding/bytesutil" + eth "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v3/testing/assert" + "github.com/prysmaticlabs/prysm/v3/testing/require" +) + +func Test_NewServiceWithBuilder(t *testing.T) { + s, err := NewService(context.Background(), WithBuilderClient(&buildertesting.MockClient{})) + require.NoError(t, err) + assert.Equal(t, true, s.Configured()) +} + +func Test_NewServiceWithoutBuilder(t *testing.T) { + s, err := NewService(context.Background()) + require.NoError(t, err) + assert.Equal(t, false, s.Configured()) +} + +func Test_RegisterValidator(t *testing.T) { + ctx := context.Background() + db := dbtesting.SetupDB(t) + headFetcher := &blockchainTesting.ChainService{} + builder := buildertesting.NewClient() + s, err := NewService(ctx, WithDatabase(db), WithHeadFetcher(headFetcher), WithBuilderClient(&builder)) + require.NoError(t, err) + pubkey := bytesutil.ToBytes48([]byte("pubkey")) + var feeRecipient [20]byte + require.NoError(t, s.RegisterValidator(ctx, []*eth.SignedValidatorRegistrationV1{{Message: ð.ValidatorRegistrationV1{Pubkey: pubkey[:], FeeRecipient: feeRecipient[:]}}})) + assert.Equal(t, true, builder.RegisteredVals[pubkey]) +}