From 721df5c8a4727e9d9f30b93e1cb8ec388bd6a6a8 Mon Sep 17 00:00:00 2001 From: halo3mic Date: Wed, 26 Jun 2024 00:40:45 +0000 Subject: [PATCH 1/2] add beacon sidecar --- cmd/geth/main.go | 6 ++ cmd/utils/flags.go | 23 +++++ eth/backend.go | 8 +- eth/ethconfig/config.go | 4 + go.mod | 2 + go.sum | 5 + miner/builder.go | 37 ++++--- suave/builder/api/api.go | 1 + suave/builder/beacon_sidecar/engine.go | 80 +++++++++++++++ suave/builder/beacon_sidecar/types.go | 130 +++++++++++++++++++++++++ suave/builder/beacon_sidecar/utils.go | 121 +++++++++++++++++++++++ suave/builder/session_manager.go | 95 ++++++++++++++---- suave/builder/session_manager_test.go | 2 +- 13 files changed, 477 insertions(+), 37 deletions(-) create mode 100644 suave/builder/beacon_sidecar/engine.go create mode 100644 suave/builder/beacon_sidecar/types.go create mode 100644 suave/builder/beacon_sidecar/utils.go diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 8ec70aedf93e..1e9eb4579dee 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -207,6 +207,11 @@ var ( utils.MetricsInfluxDBBucketFlag, utils.MetricsInfluxDBOrganizationFlag, } + + suaveFlags = []cli.Flag{ + utils.SuaveBeaconRpcFlag, + utils.SuaveBoostRelayUrlFlag, + } ) var app = flags.NewApp("the go-ethereum command line interface") @@ -258,6 +263,7 @@ func init() { consoleFlags, debug.Flags, metricsFlags, + suaveFlags, ) flags.AutoEnvVars(app.Flags, "GETH") diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index a7a65fbef00f..eca1044d6500 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -954,6 +954,20 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server. Value: metrics.DefaultConfig.InfluxDBOrganization, Category: flags.MetricsCategory, } + + // SUAVE Flags + SuaveBoostRelayUrlFlag = &cli.StringFlag{ + Name: "suave.eth.boost_relay_url", + EnvVars: []string{"SUAVE_BOOST_RELAY_URL"}, + Usage: "API endpoint for the boost relay", + Category: flags.SuaveCategory, + } + SuaveBeaconRpcFlag = &cli.StringFlag{ + Name: "suave.eth.beacon_rpc", + EnvVars: []string{"BEACON_RPC"}, + Usage: "RPC endpoint for a beacon node", + Category: flags.SuaveCategory, + } ) var ( @@ -1785,6 +1799,15 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { cfg.EthDiscoveryURLs = SplitAndTrim(urls) } } + + // SUAVE Related + if ctx.IsSet(SuaveBoostRelayUrlFlag.Name) { + cfg.BoostRelayUrl = ctx.String(SuaveBoostRelayUrlFlag.Name) + } + if ctx.IsSet(SuaveBeaconRpcFlag.Name) { + cfg.BeaconRpc = ctx.String(SuaveBeaconRpcFlag.Name) + } + // Override any default configs for hard coded networks. switch { case ctx.Bool(MainnetFlag.Name): diff --git a/eth/backend.go b/eth/backend.go index 2c8ab1a1c7ab..9ac18ad4b2b6 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -60,6 +60,7 @@ import ( "github.com/ethereum/go-ethereum/suave/backends" suave_builder "github.com/ethereum/go-ethereum/suave/builder" suave_builder_api "github.com/ethereum/go-ethereum/suave/builder/api" + beacon_sidecar "github.com/ethereum/go-ethereum/suave/builder/beacon_sidecar" ) // Config contains the configuration options of the ETH protocol. @@ -329,7 +330,12 @@ func (s *Ethereum) APIs() []rpc.API { Service: backends.NewEthBackendServer(s.APIBackend), }) - sessionManager := suave_builder.NewSessionManager(s.blockchain, s.txPool, &suave_builder.Config{}) + beaconSidecar := &beacon_sidecar.BeaconSidecar{} + if s.config.BeaconRpc != "" && s.config.BoostRelayUrl != "" { + beaconSidecar = beacon_sidecar.NewBeaconSidecar(s.config.BeaconRpc, s.config.BoostRelayUrl) + } + + sessionManager := suave_builder.NewSessionManager(s.blockchain, s.txPool, &suave_builder.Config{}, beaconSidecar) apis = append(apis, rpc.API{ Namespace: "suavex", diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index fef7f29f4e28..25285f981e56 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -163,6 +163,10 @@ type Config struct { // OverrideVerkle (TODO: remove after the fork) OverrideVerkle *uint64 `toml:",omitempty"` + + // SUAVE Config + BoostRelayUrl string + BeaconRpc string } // CreateConsensusEngine creates a consensus engine for the given chain config. diff --git a/go.mod b/go.mod index 0a22b79ba985..334f755c9310 100644 --- a/go.mod +++ b/go.mod @@ -138,6 +138,7 @@ require ( github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.10.1 // indirect github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7 // indirect + github.com/r3labs/sse v0.0.0-20210224172625-26fe804710bc // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect @@ -149,6 +150,7 @@ require ( golang.org/x/net v0.21.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/go.sum b/go.sum index 039647af5aa8..ebb127262c64 100644 --- a/go.sum +++ b/go.sum @@ -348,6 +348,8 @@ github.com/protolambda/ztyp v0.2.2 h1:rVcL3vBu9W/aV646zF6caLS/dyn9BN8NYiuJzicLNy github.com/protolambda/ztyp v0.2.2/go.mod h1:9bYgKGqg3wJqT9ac1gI2hnVb0STQq7p/1lapqrqY1dU= github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7 h1:0tVE4tdWQK9ZpYygoV7+vS6QkDvQVySboMVEIxBJmXw= github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7/go.mod h1:wmuf/mdK4VMD+jA9ThwcUKjg3a2XWM9cVfFYjDyY4j4= +github.com/r3labs/sse v0.0.0-20210224172625-26fe804710bc h1:zAsgcP8MhzAbhMnB1QQ2O7ZhWYVGYSR2iVcjzQuPV+o= +github.com/r3labs/sse v0.0.0-20210224172625-26fe804710bc/go.mod h1:S8xSOnV3CgpNrWd0GQ/OoQfMtlg2uPRSuTzcSGrzwK8= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -414,6 +416,7 @@ golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191116160921-f9c825593386/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -504,6 +507,8 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/cenkalti/backoff.v1 v1.1.0 h1:Arh75ttbsvlpVA7WtVpH4u9h6Zl46xuptxqLxPiSo4Y= +gopkg.in/cenkalti/backoff.v1 v1.1.0/go.mod h1:J6Vskwqd+OMVJl8C33mmtxTBs2gyzfv7UDAkHu8BrjI= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/miner/builder.go b/miner/builder.go index 7465b4f9e34b..e265f7b74ef1 100644 --- a/miner/builder.go +++ b/miner/builder.go @@ -19,7 +19,6 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/internal/ethapi" - "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" suavextypes "github.com/ethereum/go-ethereum/suave/builder/api" "github.com/flashbots/go-boost-utils/ssz" @@ -42,11 +41,17 @@ type BuilderConfig struct { } type BuilderArgs struct { - ParentHash common.Hash - FeeRecipient common.Address - ProposerPubkey []byte - Extra []byte - Slot uint64 + Slot uint64 + ProposerPubkey []byte + ParentHash common.Hash + Timestamp uint64 + FeeRecipient common.Address + GasLimit uint64 + Random common.Hash + Withdrawals []*types.Withdrawal + ParentBlockRoot common.Hash + + Extra []byte } type Builder struct { @@ -72,10 +77,14 @@ func NewBuilder(config *BuilderConfig, args *BuilderArgs) (*Builder, error) { } workerParams := &generateParams{ - parentHash: args.ParentHash, - forceTime: false, - coinbase: args.FeeRecipient, - extra: args.Extra, + timestamp: args.Timestamp, + forceTime: false, + parentHash: args.ParentHash, + coinbase: args.FeeRecipient, + random: args.Random, + withdrawals: args.Withdrawals, + beaconRoot: &args.ParentBlockRoot, + extra: args.Extra, } env, err := b.wrk.prepareWork(workerParams) if err != nil { @@ -184,18 +193,14 @@ type ChainContextDummy struct { } func (c *ChainContextDummy) Engine() consensus.Engine { - log.Error("Engine not implemented for ChainContextDummy") - return nil + panic("not implemented") } func (c *ChainContextDummy) GetHeader(common.Hash, uint64) *types.Header { - log.Error("GetHeader not implemented for ChainContextDummy") - return nil + panic("not implemented") } func (b *Builder) Call(args *ethapi.TransactionArgs) ([]byte, error) { - // TODO: Figure out the timeout situation - error := args.CallDefaults(0, common.Big0, b.wrk.chainConfig.ChainID) if error != nil { return nil, error diff --git a/suave/builder/api/api.go b/suave/builder/api/api.go index db171f586655..2d7ed9f48fb5 100644 --- a/suave/builder/api/api.go +++ b/suave/builder/api/api.go @@ -42,6 +42,7 @@ type BuildBlockArgs struct { GasLimit uint64 `json:"gasLimit"` Random common.Hash `json:"random"` Withdrawals []*types.Withdrawal `json:"withdrawals"` + BeaconRoot common.Hash `json:"beaconRoot"` Extra []byte `json:"extra"` } diff --git a/suave/builder/beacon_sidecar/engine.go b/suave/builder/beacon_sidecar/engine.go new file mode 100644 index 000000000000..ab37d612ff7f --- /dev/null +++ b/suave/builder/beacon_sidecar/engine.go @@ -0,0 +1,80 @@ +package beacon_sidecar + +import ( + "context" + "encoding/json" + "sync" + "time" + + "github.com/ethereum/go-ethereum/log" +) + +type BeaconSidecar struct { + latestBeaconBuildBlockArgs BeaconBuildBlockArgs + latestTimestamp uint64 + mu sync.Mutex + cancel context.CancelFunc + wg sync.WaitGroup +} + +func NewBeaconSidecar(beaconRpc string, boostRelayUrl string) *BeaconSidecar { + log.Info("Starting BeaconSidecar", "beaconRpc", beaconRpc, "boostRelayUrl", boostRelayUrl) + ctx, cancel := context.WithCancel(context.Background()) + sidecar := &BeaconSidecar{ + cancel: cancel, + } + go sidecar.startSyncing(ctx, beaconRpc, boostRelayUrl) + return sidecar +} + +func (bs *BeaconSidecar) GetLatestBeaconBuildBlockArgs() BeaconBuildBlockArgs { + bs.mu.Lock() + defer bs.mu.Unlock() + return bs.latestBeaconBuildBlockArgs.Copy() +} + +func (bs *BeaconSidecar) GetLatestTimestamp() uint64 { + bs.mu.Lock() + defer bs.mu.Unlock() + return bs.latestTimestamp +} + +func (bs *BeaconSidecar) Stop() { + bs.cancel() + bs.wg.Wait() +} + +func (bs *BeaconSidecar) startSyncing(ctx context.Context, beaconRpc string, boostRelayUrl string) { + defer bs.wg.Done() + defer bs.cancel() + + bs.wg.Add(1) + payloadAttrC := make(chan PayloadAttributesEvent) + go SubscribeToPayloadAttributesEvents(ctx, beaconRpc, payloadAttrC) + + for paEvent := range payloadAttrC { + log.Debug("New PA event", "data", paEvent.Data) + + validatorData, err := getValidatorForSlot(ctx, boostRelayUrl, paEvent.Data.ProposalSlot) + if err != nil { + log.Warn("could not get validator", "slot", paEvent.Data.ProposalSlot, "err", err) + continue + } + res, err := json.Marshal(validatorData) + if err != nil { + log.Error("could not marshal validator data", "err", err) + continue + } + log.Debug("New validator data", "data", string(res)) + + beaconBuildBlockArgs := NewBeaconBuildBlockArgsFromValAndPAEvent(validatorData, paEvent) + bs.update(beaconBuildBlockArgs) + } +} + +func (bs *BeaconSidecar) update(beaconBuildBlockArgs BeaconBuildBlockArgs) { + bs.mu.Lock() + defer bs.mu.Unlock() + bs.latestBeaconBuildBlockArgs = beaconBuildBlockArgs + bs.latestTimestamp = uint64(time.Now().Unix()) +} diff --git a/suave/builder/beacon_sidecar/types.go b/suave/builder/beacon_sidecar/types.go new file mode 100644 index 000000000000..b0f7992e0c73 --- /dev/null +++ b/suave/builder/beacon_sidecar/types.go @@ -0,0 +1,130 @@ +package beacon_sidecar + +import ( + "encoding/json" + + "github.com/attestantio/go-eth2-client/spec/capella" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/suave/builder/api" +) + +type BeaconBuildBlockArgs struct { + Slot uint64 + ProposerPubkey []byte + Parent common.Hash + Timestamp uint64 + FeeRecipient common.Address + GasLimit uint64 + Random common.Hash + Withdrawals []*types.Withdrawal + ParentBlockRoot common.Hash +} + +func NewBeaconBuildBlockArgsFromValAndPAEvent(valData ValidatorData, paEvent PayloadAttributesEvent) BeaconBuildBlockArgs { + beaconBuildBlockArgs := BeaconBuildBlockArgs{ + Slot: paEvent.Data.ProposalSlot, + ProposerPubkey: hexutil.MustDecode(valData.Pubkey), + Parent: paEvent.Data.ParentBlockHash, + Timestamp: paEvent.Data.PayloadAttributes.Timestamp, + Random: paEvent.Data.PayloadAttributes.PrevRandao, + FeeRecipient: valData.FeeRecipient, + GasLimit: valData.GasLimit, + ParentBlockRoot: paEvent.Data.PayloadAttributes.ParentBeaconBlockRoot, + } + + for _, w := range paEvent.Data.PayloadAttributes.Withdrawals { + withdrawal := types.Withdrawal{ + Index: uint64(w.Index), + Validator: uint64(w.ValidatorIndex), + Address: common.Address(w.Address), + Amount: uint64(w.Amount), + } + beaconBuildBlockArgs.Withdrawals = append(beaconBuildBlockArgs.Withdrawals, &withdrawal) + } + return beaconBuildBlockArgs +} + +func (b *BeaconBuildBlockArgs) Copy() BeaconBuildBlockArgs { + deepCopy := BeaconBuildBlockArgs{ + Slot: b.Slot, + ProposerPubkey: make([]byte, len(b.ProposerPubkey)), + Parent: b.Parent, + Timestamp: b.Timestamp, + Random: b.Random, + FeeRecipient: b.FeeRecipient, + GasLimit: b.GasLimit, + ParentBlockRoot: b.ParentBlockRoot, + Withdrawals: make([]*types.Withdrawal, len(b.Withdrawals)), + } + + copy(deepCopy.ProposerPubkey, b.ProposerPubkey) + for i, w := range b.Withdrawals { + deepCopy.Withdrawals[i] = &types.Withdrawal{ + Index: w.Index, + Validator: w.Validator, + Address: w.Address, + Amount: w.Amount, + } + } + + return deepCopy +} + +func (b *BeaconBuildBlockArgs) ToBuildBlockArgs() api.BuildBlockArgs { + return api.BuildBlockArgs{ + Slot: b.Slot, + ProposerPubkey: b.ProposerPubkey, + Parent: b.Parent, + Timestamp: b.Timestamp, + FeeRecipient: b.FeeRecipient, + GasLimit: b.GasLimit, + Random: b.Random, + Withdrawals: b.Withdrawals, + BeaconRoot: b.ParentBlockRoot, + } + +} + +func (b BeaconBuildBlockArgs) Bytes() ([]byte, error) { + return json.Marshal(b) +} + +type PayloadAttributesEvent struct { + Version string `json:"version"` + Data PayloadAttributesEventData `json:"data"` +} + +type PayloadAttributesEventData struct { + ProposalSlot uint64 `json:"proposal_slot,string"` + ParentBlockHash common.Hash `json:"parent_block_hash"` + PayloadAttributes PayloadAttributes `json:"payload_attributes"` +} + +type PayloadAttributes struct { + Timestamp uint64 `json:"timestamp,string"` + PrevRandao common.Hash `json:"prev_randao"` + SuggestedFeeRecipient common.Address `json:"suggested_fee_recipient"` + ParentBeaconBlockRoot common.Hash `json:"parent_beacon_block_root"` + Withdrawals []*capella.Withdrawal `json:"withdrawals"` +} + +type ValidatorData struct { + Pubkey string + FeeRecipient common.Address + GasLimit uint64 +} + +type GetValidatorRelayResponse []struct { + Slot uint64 `json:"slot,string"` + Entry struct { + Message struct { + FeeRecipient string `json:"fee_recipient"` + GasLimit uint64 `json:"gas_limit,string"` + Timestamp uint64 `json:"timestamp,string"` + Pubkey string `json:"pubkey"` + } `json:"message"` + Signature string `json:"signature"` + } `json:"entry"` +} diff --git a/suave/builder/beacon_sidecar/utils.go b/suave/builder/beacon_sidecar/utils.go new file mode 100644 index 000000000000..026b32fc30ac --- /dev/null +++ b/suave/builder/beacon_sidecar/utils.go @@ -0,0 +1,121 @@ +// Based on https://github.com/flashbots/suave-geth/blob/892e2e11ba2735cdbc7d7ef694b8942dadaf0bdd/suave/cmd/suavecli/boost_utils.go + +package beacon_sidecar + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strings" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/r3labs/sse" +) + +func SubscribeToPayloadAttributesEvents(ctx context.Context, endpoint string, payloadAttrC chan<- PayloadAttributesEvent) { + retries := 100 // todo: make this configurable + eventsURL := fmt.Sprintf("%s/eth/v1/events?topics=payload_attributes", endpoint) + + client := sse.NewClient(eventsURL) + for i := 0; i < retries; i++ { + select { + case <-ctx.Done(): + log.Info("Stopping subscription to payload_attributes events") + return + default: + err := client.SubscribeRawWithContext(ctx, func(msg *sse.Event) { + var payloadAttributesResp PayloadAttributesEvent + if len(msg.Data) == 0 { + log.Warn("Empty payload_attributes event") + return + } + err := json.Unmarshal(msg.Data, &payloadAttributesResp) + if err != nil { + log.Error("Could not unmarshal payload_attributes event", "err", err) + return + } + + select { + case payloadAttrC <- payloadAttributesResp: + case <-ctx.Done(): + log.Info("Context completed during event processing") + return + } + }) + + if err != nil { + log.Error("Failed to subscribe to payload_attributes events", "err", err) + if ctx.Err() != nil { + log.Info("Context error detected, stopping retries") + return + } + // Wait before retrying to avoid hammering the server on immediate reconnects + time.Sleep(1 * time.Second) + continue + } + } + + log.Warn("SubscribeRawWithContext ended unexpectedly, reconnecting") + } + log.Error("Failed to subscribe to payload_attributes events after retries") +} + +func getValidatorForSlot(ctx context.Context, relayUrl string, nextSlot uint64) (ValidatorData, error) { + endpoint := relayUrl + "/relay/v1/builder/validators" + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return ValidatorData{}, fmt.Errorf("could not prepare request: %w", err) + } + + // Execute request + resp, err := http.DefaultClient.Do(req) + if err != nil { + return ValidatorData{}, err + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusNoContent { + return ValidatorData{}, errors.New("nothing returned from the boost relay") + } + + if resp.StatusCode > 299 { + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return ValidatorData{}, fmt.Errorf("could not read error response body for status code %d: %w", resp.StatusCode, err) + } + return ValidatorData{}, fmt.Errorf("http error: %d / %s", resp.StatusCode, string(bodyBytes)) + } + + var dst GetValidatorRelayResponse + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return ValidatorData{}, fmt.Errorf("could not read response body: %w", err) + } + + if err := json.Unmarshal(bodyBytes, &dst); err != nil { + return ValidatorData{}, fmt.Errorf("could not unmarshal response %s: %w", string(bodyBytes), err) + } + + res := make(map[uint64]ValidatorData) + for _, data := range dst { + feeRecipient := common.HexToAddress(data.Entry.Message.FeeRecipient) + pubkeyHex := strings.ToLower(data.Entry.Message.Pubkey) + + res[data.Slot] = ValidatorData{ + Pubkey: pubkeyHex, + FeeRecipient: feeRecipient, + GasLimit: data.Entry.Message.GasLimit, + } + } + v, found := res[nextSlot] + if !found { + return ValidatorData{}, errors.New("validator not found") + } + + return v, nil +} diff --git a/suave/builder/session_manager.go b/suave/builder/session_manager.go index 214fe3908dc0..d9506972f2ea 100644 --- a/suave/builder/session_manager.go +++ b/suave/builder/session_manager.go @@ -2,6 +2,7 @@ package builder import ( "context" + "encoding/json" "fmt" "math/big" "sync" @@ -14,9 +15,11 @@ import ( "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/suave/builder/api" + bs "github.com/ethereum/go-ethereum/suave/builder/beacon_sidecar" "github.com/google/uuid" ) @@ -27,16 +30,17 @@ type Config struct { } type SessionManager struct { - sem chan struct{} - sessions map[string]*miner.Builder - sessionTimers map[string]*time.Timer - sessionsLock sync.RWMutex - blockchain *core.BlockChain - pool *txpool.TxPool - config *Config + sem chan struct{} + sessions map[string]*miner.Builder + sessionTimers map[string]*time.Timer + sessionsLock sync.RWMutex + blockchain *core.BlockChain + pool *txpool.TxPool + config *Config + beacon_sidecar *bs.BeaconSidecar } -func NewSessionManager(blockchain *core.BlockChain, pool *txpool.TxPool, config *Config) *SessionManager { +func NewSessionManager(blockchain *core.BlockChain, pool *txpool.TxPool, config *Config, bs *bs.BeaconSidecar) *SessionManager { if config.GasCeil == 0 { config.GasCeil = 1000000000000000000 } @@ -53,12 +57,13 @@ func NewSessionManager(blockchain *core.BlockChain, pool *txpool.TxPool, config } s := &SessionManager{ - sem: sem, - sessions: make(map[string]*miner.Builder), - sessionTimers: make(map[string]*time.Timer), - blockchain: blockchain, - config: config, - pool: pool, + sem: sem, + sessions: make(map[string]*miner.Builder), + sessionTimers: make(map[string]*time.Timer), + blockchain: blockchain, + config: config, + pool: pool, + beacon_sidecar: bs, } return s } @@ -79,14 +84,22 @@ func (s *SessionManager) newBuilder(args *api.BuildBlockArgs) (*miner.Builder, e EthBackend: s, GasCeil: s.config.GasCeil, } + args = s.builderArgsFromBeaconSidecar(args) builderArgs := &miner.BuilderArgs{ - ParentHash: args.Parent, - FeeRecipient: args.FeeRecipient, - ProposerPubkey: args.ProposerPubkey, - Extra: args.Extra, - Slot: args.Slot, + ParentHash: args.Parent, + FeeRecipient: args.FeeRecipient, + ProposerPubkey: args.ProposerPubkey, + Extra: args.Extra, + Slot: args.Slot, + Timestamp: args.Timestamp, + GasLimit: args.GasLimit, + Random: args.Random, + Withdrawals: args.Withdrawals, + ParentBlockRoot: args.BeaconRoot, } + jsonArgs, _ := json.Marshal(builderArgs) + log.Info("Creating new builder session", "args", string(jsonArgs)) session, err := miner.NewBuilder(builderCfg, builderArgs) if err != nil { @@ -95,6 +108,50 @@ func (s *SessionManager) newBuilder(args *api.BuildBlockArgs) (*miner.Builder, e return session, nil } +// todo: avoid unnecessary and inefficient copying of args among four different BuilderArgs structs +func (s *SessionManager) builderArgsFromBeaconSidecar(overrides *api.BuildBlockArgs) *api.BuildBlockArgs { + if s.beacon_sidecar == nil { + return overrides + } + + args := s.beacon_sidecar.GetLatestBeaconBuildBlockArgs() + if overrides == nil { + args := args.ToBuildBlockArgs() + return &args + } + + if overrides.Slot == 0 { + overrides.Slot = args.Slot + } + + if len(overrides.ProposerPubkey) == 0 { + overrides.ProposerPubkey = args.ProposerPubkey + } + if overrides.Parent == (common.Hash{}) { + overrides.Parent = args.Parent + } + if overrides.Timestamp == 0 { + overrides.Timestamp = args.Timestamp + } + if overrides.FeeRecipient == (common.Address{}) { + overrides.FeeRecipient = args.FeeRecipient + } + if overrides.GasLimit == 0 { + overrides.GasLimit = args.GasLimit + } + if overrides.Random == (common.Hash{}) { + overrides.Random = args.Random + } + if len(overrides.Withdrawals) == 0 { + overrides.Withdrawals = args.Withdrawals + } + if overrides.BeaconRoot == (common.Hash{}) { + overrides.BeaconRoot = args.ParentBlockRoot + } + + return overrides +} + // NewSession creates a new builder session and returns the session id func (s *SessionManager) NewSession(ctx context.Context, args *api.BuildBlockArgs) (string, error) { if args == nil { diff --git a/suave/builder/session_manager_test.go b/suave/builder/session_manager_test.go index fd69268b9168..4110faec694e 100644 --- a/suave/builder/session_manager_test.go +++ b/suave/builder/session_manager_test.go @@ -127,7 +127,7 @@ func newSessionManager(t *testing.T, cfg *Config) (*SessionManager, *testBackend if cfg == nil { cfg = &Config{} } - return NewSessionManager(backend.chain, backend.pool, cfg), backend + return NewSessionManager(backend.chain, backend.pool, cfg, nil), backend } var ( From e54edf25e4c2a41bf409d4d69b08c0c1f2376edc Mon Sep 17 00:00:00 2001 From: halo3mic Date: Tue, 13 Aug 2024 17:54:45 +0000 Subject: [PATCH 2/2] use go-eth2-client to listen to beacon events --- go.mod | 22 ++++-- go.sum | 37 ++++++++++ suave/builder/beacon_sidecar/engine.go | 7 +- suave/builder/beacon_sidecar/types.go | 35 +++------- suave/builder/beacon_sidecar/utils.go | 97 +++++++++++++------------- 5 files changed, 113 insertions(+), 85 deletions(-) diff --git a/go.mod b/go.mod index 334f755c9310..f63525a1fc76 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/Microsoft/go-winio v0.6.1 github.com/VictoriaMetrics/fastcache v1.12.1 github.com/attestantio/go-builder-client v0.4.3 - github.com/attestantio/go-eth2-client v0.19.10 + github.com/attestantio/go-eth2-client v0.21.10 github.com/aws/aws-sdk-go-v2 v1.21.2 github.com/aws/aws-sdk-go-v2/config v1.18.45 github.com/aws/aws-sdk-go-v2/credentials v1.13.43 @@ -69,10 +69,10 @@ require ( github.com/tyler-smith/go-bip39 v1.1.0 github.com/urfave/cli/v2 v2.25.7 go.uber.org/automaxprocs v1.5.2 - golang.org/x/crypto v0.21.0 + golang.org/x/crypto v0.23.0 golang.org/x/sync v0.5.0 - golang.org/x/sys v0.18.0 - golang.org/x/text v0.14.0 + golang.org/x/sys v0.20.0 + golang.org/x/text v0.15.0 golang.org/x/time v0.3.0 golang.org/x/tools v0.15.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0 @@ -107,6 +107,8 @@ require ( github.com/dlclark/regexp2 v1.7.0 // indirect github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 // indirect github.com/getsentry/sentry-go v0.18.0 // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect github.com/goccy/go-json v0.10.2 // indirect @@ -116,11 +118,12 @@ require ( github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.4 // indirect + github.com/huandu/go-clone v1.6.0 // indirect github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kilic/bls12-381 v0.1.0 // indirect github.com/klauspost/compress v1.15.15 // indirect - github.com/klauspost/cpuid/v2 v2.2.6 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect @@ -131,25 +134,32 @@ require ( github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/naoina/go-stringutil v0.1.0 // indirect github.com/opentracing/opentracing-go v1.1.0 // indirect + github.com/pk910/dynamic-ssz v0.0.3 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.16.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.10.1 // indirect - github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7 // indirect + github.com/prysmaticlabs/go-bitfield v0.0.0-20240328144219-a1caa50c3a1e // indirect github.com/r3labs/sse v0.0.0-20210224172625-26fe804710bc // indirect + github.com/r3labs/sse/v2 v2.10.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/rs/zerolog v1.32.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect + go.opentelemetry.io/otel v1.16.0 // indirect + go.opentelemetry.io/otel/metric v1.16.0 // indirect + go.opentelemetry.io/otel/trace v1.16.0 // indirect golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.21.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/Knetic/govaluate.v3 v3.0.0 // indirect gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect rsc.io/tmplfunc v0.0.3 // indirect diff --git a/go.sum b/go.sum index ebb127262c64..e3080bb69234 100644 --- a/go.sum +++ b/go.sum @@ -26,6 +26,8 @@ github.com/attestantio/go-builder-client v0.4.3 h1:K1m/PTqY01mfAPc0h+9iR2OivY3LO github.com/attestantio/go-builder-client v0.4.3/go.mod h1:yeJANU1O5P3b/4+iwShz9JMcgUnZABCh5RJBtZnLiDo= github.com/attestantio/go-eth2-client v0.19.10 h1:NLs9mcBvZpBTZ3du7Ey2NHQoj8d3UePY7pFBXX6C6qs= github.com/attestantio/go-eth2-client v0.19.10/go.mod h1:TTz7YF6w4z6ahvxKiHuGPn6DbQn7gH6HPuWm/DEQeGE= +github.com/attestantio/go-eth2-client v0.21.10 h1:1DWn42WKjk8mR8jKkjbaDCGNMVnh2IfAWRUmt7iemRo= +github.com/attestantio/go-eth2-client v0.21.10/go.mod h1:d7ZPNrMX8jLfIgML5u7QZxFo2AukLM+5m08iMaLdqb8= github.com/aws/aws-sdk-go-v2 v1.21.2 h1:+LXZ0sgo8quN9UOKXXzAWRT3FWd4NxeXWOZom9pE7GA= github.com/aws/aws-sdk-go-v2 v1.21.2/go.mod h1:ErQhvNuEMhJjweavOYhxVkn2RUx7kQXVATHrjKtxIpM= github.com/aws/aws-sdk-go-v2/config v1.18.45 h1:Aka9bI7n8ysuwPeFdm77nfbyHCAKQ3z9ghB3S/38zes= @@ -85,6 +87,7 @@ github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/Yj github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 h1:d28BXYi+wUpz1KBmiF9bWrjEMacUEREV6MBi2ODnrfQ= @@ -146,6 +149,11 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= @@ -163,6 +171,7 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-yaml v1.11.2 h1:joq77SxuyIs9zzxEjgyLBugMQ9NEgTWxXfz2wVqwAaQ= github.com/goccy/go-yaml v1.11.2/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -221,6 +230,7 @@ github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25 github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U= github.com/huandu/go-clone v1.6.0 h1:HMo5uvg4wgfiy5FoGOqlFLQED/VGRm2D9Pi8g1FXPGc= github.com/huandu/go-clone v1.6.0/go.mod h1:ReGivhG6op3GYr+UY3lS6mxjKp7MIGTknuU5TbTVaXE= github.com/huandu/go-clone/generic v1.6.0 h1:Wgmt/fUZ28r16F2Y3APotFD59sHk1p78K0XLdbUYN5U= @@ -254,6 +264,8 @@ github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7y github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= @@ -283,6 +295,7 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= @@ -322,6 +335,8 @@ github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQm github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pk910/dynamic-ssz v0.0.3 h1:fCWzFowq9P6SYCc7NtJMkZcIHk+r5hSVD+32zVi6Aio= +github.com/pk910/dynamic-ssz v0.0.3/go.mod h1:b6CrLaB2X7pYA+OSEEbkgXDEcRnjLOZIxZTsMuO/Y9c= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -348,8 +363,12 @@ github.com/protolambda/ztyp v0.2.2 h1:rVcL3vBu9W/aV646zF6caLS/dyn9BN8NYiuJzicLNy github.com/protolambda/ztyp v0.2.2/go.mod h1:9bYgKGqg3wJqT9ac1gI2hnVb0STQq7p/1lapqrqY1dU= github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7 h1:0tVE4tdWQK9ZpYygoV7+vS6QkDvQVySboMVEIxBJmXw= github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7/go.mod h1:wmuf/mdK4VMD+jA9ThwcUKjg3a2XWM9cVfFYjDyY4j4= +github.com/prysmaticlabs/go-bitfield v0.0.0-20240328144219-a1caa50c3a1e h1:ATgOe+abbzfx9kCPeXIW4fiWyDdxlwHw07j8UGhdTd4= +github.com/prysmaticlabs/go-bitfield v0.0.0-20240328144219-a1caa50c3a1e/go.mod h1:wmuf/mdK4VMD+jA9ThwcUKjg3a2XWM9cVfFYjDyY4j4= github.com/r3labs/sse v0.0.0-20210224172625-26fe804710bc h1:zAsgcP8MhzAbhMnB1QQ2O7ZhWYVGYSR2iVcjzQuPV+o= github.com/r3labs/sse v0.0.0-20210224172625-26fe804710bc/go.mod h1:S8xSOnV3CgpNrWd0GQ/OoQfMtlg2uPRSuTzcSGrzwK8= +github.com/r3labs/sse/v2 v2.10.0 h1:hFEkLLFY4LDifoHdiCN/LlGBAdVJYsANaLqNYa1l/v0= +github.com/r3labs/sse/v2 v2.10.0/go.mod h1:Igau6Whc+F17QUgML1fYe1VPZzTV6EMCnYktEmkNJ7I= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -358,6 +377,9 @@ github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDN github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= +github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= @@ -396,6 +418,12 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsr github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= +go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= +go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo= +go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4= +go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= +go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME= go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -406,6 +434,8 @@ golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -467,8 +497,11 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -481,6 +514,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= @@ -507,6 +542,8 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/Knetic/govaluate.v3 v3.0.0 h1:18mUyIt4ZlRlFZAAfVetz4/rzlJs9yhN+U02F4u1AOc= +gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E= gopkg.in/cenkalti/backoff.v1 v1.1.0 h1:Arh75ttbsvlpVA7WtVpH4u9h6Zl46xuptxqLxPiSo4Y= gopkg.in/cenkalti/backoff.v1 v1.1.0/go.mod h1:J6Vskwqd+OMVJl8C33mmtxTBs2gyzfv7UDAkHu8BrjI= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/suave/builder/beacon_sidecar/engine.go b/suave/builder/beacon_sidecar/engine.go index ab37d612ff7f..147e03ed94e4 100644 --- a/suave/builder/beacon_sidecar/engine.go +++ b/suave/builder/beacon_sidecar/engine.go @@ -6,6 +6,7 @@ import ( "sync" "time" + eth2apiv1 "github.com/attestantio/go-eth2-client/api/v1" "github.com/ethereum/go-ethereum/log" ) @@ -49,13 +50,13 @@ func (bs *BeaconSidecar) startSyncing(ctx context.Context, beaconRpc string, boo defer bs.cancel() bs.wg.Add(1) - payloadAttrC := make(chan PayloadAttributesEvent) - go SubscribeToPayloadAttributesEvents(ctx, beaconRpc, payloadAttrC) + payloadAttrC := make(chan eth2apiv1.PayloadAttributesEvent) + go subscribeToPayloadAttributesEvents(ctx, beaconRpc, payloadAttrC) for paEvent := range payloadAttrC { log.Debug("New PA event", "data", paEvent.Data) - validatorData, err := getValidatorForSlot(ctx, boostRelayUrl, paEvent.Data.ProposalSlot) + validatorData, err := getValidatorForSlot(ctx, boostRelayUrl, uint64(paEvent.Data.ProposalSlot)) if err != nil { log.Warn("could not get validator", "slot", paEvent.Data.ProposalSlot, "err", err) continue diff --git a/suave/builder/beacon_sidecar/types.go b/suave/builder/beacon_sidecar/types.go index b0f7992e0c73..501071486629 100644 --- a/suave/builder/beacon_sidecar/types.go +++ b/suave/builder/beacon_sidecar/types.go @@ -3,7 +3,7 @@ package beacon_sidecar import ( "encoding/json" - "github.com/attestantio/go-eth2-client/spec/capella" + eth2apiv1 "github.com/attestantio/go-eth2-client/api/v1" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" @@ -22,19 +22,19 @@ type BeaconBuildBlockArgs struct { ParentBlockRoot common.Hash } -func NewBeaconBuildBlockArgsFromValAndPAEvent(valData ValidatorData, paEvent PayloadAttributesEvent) BeaconBuildBlockArgs { +func NewBeaconBuildBlockArgsFromValAndPAEvent(valData ValidatorData, paEvent eth2apiv1.PayloadAttributesEvent) BeaconBuildBlockArgs { beaconBuildBlockArgs := BeaconBuildBlockArgs{ - Slot: paEvent.Data.ProposalSlot, + Slot: uint64(paEvent.Data.ProposalSlot), ProposerPubkey: hexutil.MustDecode(valData.Pubkey), - Parent: paEvent.Data.ParentBlockHash, - Timestamp: paEvent.Data.PayloadAttributes.Timestamp, - Random: paEvent.Data.PayloadAttributes.PrevRandao, + Parent: common.Hash(paEvent.Data.ParentBlockHash), + Timestamp: paEvent.Data.V3.Timestamp, + Random: paEvent.Data.V3.PrevRandao, FeeRecipient: valData.FeeRecipient, GasLimit: valData.GasLimit, - ParentBlockRoot: paEvent.Data.PayloadAttributes.ParentBeaconBlockRoot, + ParentBlockRoot: common.Hash(paEvent.Data.V3.ParentBeaconBlockRoot), } - for _, w := range paEvent.Data.PayloadAttributes.Withdrawals { + for _, w := range paEvent.Data.V3.Withdrawals { withdrawal := types.Withdrawal{ Index: uint64(w.Index), Validator: uint64(w.ValidatorIndex), @@ -91,25 +91,6 @@ func (b BeaconBuildBlockArgs) Bytes() ([]byte, error) { return json.Marshal(b) } -type PayloadAttributesEvent struct { - Version string `json:"version"` - Data PayloadAttributesEventData `json:"data"` -} - -type PayloadAttributesEventData struct { - ProposalSlot uint64 `json:"proposal_slot,string"` - ParentBlockHash common.Hash `json:"parent_block_hash"` - PayloadAttributes PayloadAttributes `json:"payload_attributes"` -} - -type PayloadAttributes struct { - Timestamp uint64 `json:"timestamp,string"` - PrevRandao common.Hash `json:"prev_randao"` - SuggestedFeeRecipient common.Address `json:"suggested_fee_recipient"` - ParentBeaconBlockRoot common.Hash `json:"parent_beacon_block_root"` - Withdrawals []*capella.Withdrawal `json:"withdrawals"` -} - type ValidatorData struct { Pubkey string FeeRecipient common.Address diff --git a/suave/builder/beacon_sidecar/utils.go b/suave/builder/beacon_sidecar/utils.go index 026b32fc30ac..354bae73a3cf 100644 --- a/suave/builder/beacon_sidecar/utils.go +++ b/suave/builder/beacon_sidecar/utils.go @@ -1,4 +1,4 @@ -// Based on https://github.com/flashbots/suave-geth/blob/892e2e11ba2735cdbc7d7ef694b8942dadaf0bdd/suave/cmd/suavecli/boost_utils.go +// Partially based on https://github.com/flashbots/suave-geth/blob/892e2e11ba2735cdbc7d7ef694b8942dadaf0bdd/suave/cmd/suavecli/boost_utils.go package beacon_sidecar @@ -14,55 +14,30 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" - "github.com/r3labs/sse" -) + "github.com/rs/zerolog" -func SubscribeToPayloadAttributesEvents(ctx context.Context, endpoint string, payloadAttrC chan<- PayloadAttributesEvent) { - retries := 100 // todo: make this configurable - eventsURL := fmt.Sprintf("%s/eth/v1/events?topics=payload_attributes", endpoint) - - client := sse.NewClient(eventsURL) - for i := 0; i < retries; i++ { - select { - case <-ctx.Done(): - log.Info("Stopping subscription to payload_attributes events") - return - default: - err := client.SubscribeRawWithContext(ctx, func(msg *sse.Event) { - var payloadAttributesResp PayloadAttributesEvent - if len(msg.Data) == 0 { - log.Warn("Empty payload_attributes event") - return - } - err := json.Unmarshal(msg.Data, &payloadAttributesResp) - if err != nil { - log.Error("Could not unmarshal payload_attributes event", "err", err) - return - } - - select { - case payloadAttrC <- payloadAttributesResp: - case <-ctx.Done(): - log.Info("Context completed during event processing") - return - } - }) - - if err != nil { - log.Error("Failed to subscribe to payload_attributes events", "err", err) - if ctx.Err() != nil { - log.Info("Context error detected, stopping retries") - return - } - // Wait before retrying to avoid hammering the server on immediate reconnects - time.Sleep(1 * time.Second) - continue - } - } + eth2client "github.com/attestantio/go-eth2-client" + eth2apiv1 "github.com/attestantio/go-eth2-client/api/v1" + eth2http "github.com/attestantio/go-eth2-client/http" +) - log.Warn("SubscribeRawWithContext ended unexpectedly, reconnecting") +func subscribeToPayloadAttributesEvents( + ctx context.Context, + endpoint string, + payloadAttrC chan eth2apiv1.PayloadAttributesEvent, +) error { + provider, err := newEth2EventsProvider(ctx, endpoint) + if err != nil { + return err } - log.Error("Failed to subscribe to payload_attributes events after retries") + log.Debug("Subscribing to payload_attributes events") + return provider.Events(ctx, []string{"payload_attributes"}, func(event *eth2apiv1.Event) { + if data, ok := event.Data.(*eth2apiv1.PayloadAttributesEvent); ok { + payloadAttrC <- *data + } else { + log.Error("Unexpected data type", "type", fmt.Sprintf("%T", event.Data)) + } + }) } func getValidatorForSlot(ctx context.Context, relayUrl string, nextSlot uint64) (ValidatorData, error) { @@ -72,8 +47,10 @@ func getValidatorForSlot(ctx context.Context, relayUrl string, nextSlot uint64) return ValidatorData{}, fmt.Errorf("could not prepare request: %w", err) } - // Execute request - resp, err := http.DefaultClient.Do(req) + client := &http.Client{ + Timeout: 10 * time.Second, + } + resp, err := client.Do(req) if err != nil { return ValidatorData{}, err } @@ -119,3 +96,25 @@ func getValidatorForSlot(ctx context.Context, relayUrl string, nextSlot uint64) return v, nil } + +func newEth2EventsProvider(ctx context.Context, endpoint string) (eth2client.EventsProvider, error) { + client, err := newEth2HttpClient(ctx, endpoint) + if err != nil { + return nil, err + } + if provider, isProvider := client.(eth2client.EventsProvider); isProvider { + return provider, nil + } + return nil, errors.New("client does not support event subscriptions") +} + +func newEth2HttpClient(ctx context.Context, endpoint string) (eth2client.Service, error) { + client, err := eth2http.New(ctx, + eth2http.WithAddress(endpoint), + eth2http.WithLogLevel(zerolog.WarnLevel), + ) + if err != nil { + return nil, fmt.Errorf("failed to create client: %w", err) + } + return client, nil +}