From 61397506c4eea1d835d371a8cbfcfee9d2072e1d Mon Sep 17 00:00:00 2001 From: Sam Calder-Mason Date: Tue, 20 Feb 2024 11:42:37 +1000 Subject: [PATCH 01/13] fix: Add additional serving bundle checks --- .gitignore | 2 +- pkg/api/handler.go | 2 +- pkg/beacon/default.go | 107 ++++++++++++++++++++++++++++++++------ pkg/beacon/download.go | 16 +++++- pkg/beacon/store/block.go | 1 - 5 files changed, 106 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index 407ef681..132749c9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,6 @@ bin .vscode config.yaml config2.yaml -__debug_bin +__debug_bin* dist/ checkpointz diff --git a/pkg/api/handler.go b/pkg/api/handler.go index a219b04c..29bfda1f 100644 --- a/pkg/api/handler.go +++ b/pkg/api/handler.go @@ -95,7 +95,7 @@ func (h *Handler) wrappedHandler(handler func(ctx context.Context, r *http.Reque "path": r.URL.Path, "content_type": contentType, "accept": r.Header.Get("Accept"), - }).Debug("Handling request") + }).Trace("Handling request") h.metrics.ObserveRequest(r.Method, registeredPath) diff --git a/pkg/beacon/default.go b/pkg/beacon/default.go index e8dceb6c..d19046fc 100644 --- a/pkg/beacon/default.go +++ b/pkg/beacon/default.go @@ -4,19 +4,23 @@ import ( "context" "errors" "fmt" + "sync" "time" v1 "github.com/attestantio/go-eth2-client/api/v1" "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/chuckpreslar/emission" + "github.com/ethpandaops/beacon/pkg/beacon" "github.com/ethpandaops/beacon/pkg/beacon/api/types" "github.com/ethpandaops/beacon/pkg/beacon/state" "github.com/ethpandaops/checkpointz/pkg/beacon/checkpoints" "github.com/ethpandaops/checkpointz/pkg/beacon/node" "github.com/ethpandaops/checkpointz/pkg/beacon/store" "github.com/ethpandaops/checkpointz/pkg/eth" + "github.com/ethpandaops/ethwallclock" "github.com/go-co-op/gocron" + perrors "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -40,6 +44,10 @@ type Default struct { historicalSlotFailures map[phase0.Slot]int + servingMutex sync.Mutex + historicalMutex sync.Mutex + majorityMutex sync.Mutex + metrics *Metrics } @@ -72,6 +80,10 @@ func NewDefaultProvider(namespace string, log logrus.FieldLogger, nodes []node.C states: store.NewBeaconState(log, config.Caches.States, namespace), depositSnapshots: store.NewDepositSnapshot(log, config.Caches.DepositSnapshots, namespace), + servingMutex: sync.Mutex{}, + historicalMutex: sync.Mutex{}, + majorityMutex: sync.Mutex{}, + metrics: NewMetrics(namespace + "_beacon"), } } @@ -100,17 +112,46 @@ func (d *Default) Start(ctx context.Context) error { d.log.WithError(err).Fatal("Failed to start crons") } + // Check for new serving checkpoints after each epoch + node, err := d.nodes.Healthy(ctx).NotSyncing(ctx).RandomNode(ctx) + if err != nil { + d.log.WithError(err).Fatal("Failed to get a healthy, non-syncing node to subscribe to wallclock events") + } + + node.Beacon.Wallclock().OnEpochChanged(func(epoch ethwallclock.Epoch) { + // Sleep for a bit to allow the beacon nodes to run their epoch transition. + time.Sleep(time.Second * 30) + + if err := d.checkForNewServingCheckpoint(ctx); err != nil { + d.log.WithError(err).Error("Failed to check for new serving checkpoint after epoch change") + } + }) + break } }() + // Subscribe to the nodes' finality updates. + for _, node := range d.nodes { + node.Beacon.OnFinalityCheckpointUpdated(ctx, func(ctx context.Context, checkpoint *beacon.FinalityCheckpointUpdated) error { + // Check if we have a new majority finality. + if err := d.checkFinality(ctx); err != nil { + d.log.WithError(err).Error("Failed to check finality") + + return err + } + + return d.checkForNewServingCheckpoint(ctx) + }) + } + return nil } func (d *Default) startCrons(ctx context.Context) error { s := gocron.NewScheduler(time.Local) - if _, err := s.Every("5s").Do(func() { + if _, err := s.Every("30s").Do(func() { if err := d.checkFinality(ctx); err != nil { d.log.WithError(err).Error("Failed to check finality") } @@ -210,13 +251,17 @@ func (d *Default) startHistoricalLoop(ctx context.Context) error { } func (d *Default) startServingLoop(ctx context.Context) error { + if err := d.checkForNewServingCheckpoint(ctx); err != nil { + d.log.WithError(err).Error("Failed to check for serving checkpoint") + } + for { select { - case <-time.After(time.Second * 1): + case <-time.After(time.Second * 5): if err := d.checkForNewServingCheckpoint(ctx); err != nil { d.log.WithError(err).Error("Failed to check for new serving checkpoint") - time.Sleep(time.Second * 30) + time.Sleep(time.Second * 15) } case <-ctx.Done(): return ctx.Err() @@ -225,22 +270,44 @@ func (d *Default) startServingLoop(ctx context.Context) error { } func (d *Default) checkForNewServingCheckpoint(ctx context.Context) error { + d.servingMutex.Lock() + defer d.servingMutex.Unlock() + // Don't bother checking if we don't know the head yet. if d.head == nil { - return nil + return errors.New("head finality is unknown") } if d.head.Finalized == nil { - return nil + return errors.New("head finalized checkpoint is unknown") } - // If head == serving, we're done. - if d.servingBundle != nil && d.servingBundle.Finalized != nil && d.servingBundle.Finalized.Epoch == d.head.Finalized.Epoch { - return nil + logCtx := d.log.WithFields(logrus.Fields{ + "head_epoch": d.head.Finalized.Epoch, + "head_root": fmt.Sprintf("%#x", d.head.Finalized.Root), + }) + + // If we don't have a serving bundle already, download one. + if d.servingBundle == nil { + logCtx.Info("No serving bundle available, downloading") + + return d.downloadServingCheckpoint(ctx, d.head) } - if err := d.downloadServingCheckpoint(ctx, d.head); err != nil { - return err + if d.servingBundle.Finalized == nil { + logCtx.Info("Serving bundle is unknown, downloading") + + return d.downloadServingCheckpoint(ctx, d.head) + } + + // If the head has moved on, download a new serving bundle. + if d.servingBundle.Finalized.Epoch != d.head.Finalized.Epoch { + logCtx. + WithField("serving_epoch", d.servingBundle.Finalized.Epoch). + WithField("serving_root", fmt.Sprintf("%#x", d.servingBundle.Finalized.Root)). + Info("Head finality has advanced, downloading new serving bundle") + + return d.downloadServingCheckpoint(ctx, d.head) } return nil @@ -336,6 +403,9 @@ func (d *Default) shouldDownloadStates() bool { } func (d *Default) checkFinality(ctx context.Context) error { + d.majorityMutex.Lock() + defer d.majorityMutex.Unlock() + aggFinality := []*v1.Finality{} readyNodes := d.nodes.Ready(ctx) @@ -350,19 +420,22 @@ func (d *Default) checkFinality(ctx context.Context) error { aggFinality = append(aggFinality, finality) } - Default, err := checkpoints.NewMajorityDecider().Decide(aggFinality) + majority, err := checkpoints.NewMajorityDecider().Decide(aggFinality) if err != nil { - return err + return perrors.Wrap(err, "failed to decide majority finality") } - if d.head == nil || d.head.Finalized == nil || d.head.Finalized.Root != Default.Finalized.Root { - d.head = Default + if d.head == nil || d.head.Finalized == nil || d.head.Finalized.Root != majority.Finalized.Root { + d.head = majority - d.publishFinalityCheckpointHeadUpdated(ctx, Default) + d.publishFinalityCheckpointHeadUpdated(ctx, majority) - d.log.WithField("epoch", Default.Finalized.Epoch).WithField("root", fmt.Sprintf("%#x", Default.Finalized.Root)).Info("New finalized head checkpoint") + d.log. + WithField("epoch", majority.Finalized.Epoch). + WithField("root", fmt.Sprintf("%#x", majority.Finalized.Root)). + Info("New finalized head checkpoint") - d.metrics.ObserveHeadEpoch(Default.Finalized.Epoch) + d.metrics.ObserveHeadEpoch(majority.Finalized.Epoch) } return nil diff --git a/pkg/beacon/download.go b/pkg/beacon/download.go index ff0d4be2..d4af983c 100644 --- a/pkg/beacon/download.go +++ b/pkg/beacon/download.go @@ -10,22 +10,31 @@ import ( "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/ethpandaops/checkpointz/pkg/eth" + perrors "github.com/pkg/errors" "github.com/sirupsen/logrus" ) func (d *Default) downloadServingCheckpoint(ctx context.Context, checkpoint *v1.Finality) error { + if checkpoint == nil { + return errors.New("checkpoint is nil") + } + + if checkpoint.Finalized == nil { + return errors.New("finalized checkpoint is nil") + } + upstream, err := d.nodes. Ready(ctx). DataProviders(ctx). PastFinalizedCheckpoint(ctx, checkpoint). // Ensure we attempt to fetch the bundle from a node that knows about the checkpoint. RandomNode(ctx) if err != nil { - return err + return perrors.Wrap(err, "no data provider node available") } block, err := d.fetchBundle(ctx, checkpoint.Finalized.Root, upstream) if err != nil { - return err + return perrors.Wrap(err, "failed to fetch bundle") } // Validate that everything is ok to serve. @@ -117,6 +126,9 @@ func (d *Default) checkGenesis(ctx context.Context) error { } func (d *Default) fetchHistoricalCheckpoints(ctx context.Context, checkpoint *v1.Finality) error { + d.historicalMutex.Lock() + defer d.historicalMutex.Unlock() + if d.spec == nil { return errors.New("beacon spec unavailable") } diff --git a/pkg/beacon/store/block.go b/pkg/beacon/store/block.go index a7d1bba2..62e0578f 100644 --- a/pkg/beacon/store/block.go +++ b/pkg/beacon/store/block.go @@ -35,7 +35,6 @@ func NewBlock(log logrus.FieldLogger, config Config, namespace string) *Block { block, ok := value.(*spec.VersionedSignedBeaconBlock) if !ok { c.log.WithField("block_root", key).Error("Invalid block type when cleaning up block cache") - return } From 5a181c6322df54918514c017c386081f9d05552a Mon Sep 17 00:00:00 2001 From: Sam Calder-Mason Date: Tue, 20 Feb 2024 11:58:04 +1000 Subject: [PATCH 02/13] feat: Subscribe to node events --- go.mod | 25 ++++++++-------- go.sum | 70 +++++++++++++++++-------------------------- pkg/beacon/default.go | 29 ++++++++++++++++-- 3 files changed, 67 insertions(+), 57 deletions(-) diff --git a/go.mod b/go.mod index aba3edad..50668a7b 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,11 @@ module github.com/ethpandaops/checkpointz go 1.17 require ( - github.com/attestantio/go-eth2-client v0.18.2 + github.com/attestantio/go-eth2-client v0.19.9 github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9 github.com/creasty/defaults v1.6.0 - github.com/ethpandaops/beacon v0.28.0 + github.com/ethpandaops/beacon v0.35.0 + github.com/ethpandaops/ethwallclock v0.2.0 github.com/go-co-op/gocron v1.18.0 github.com/julienschmidt/httprouter v1.3.0 github.com/nanmu42/gzip v1.2.0 @@ -20,8 +21,7 @@ require ( require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/ethpandaops/ethwallclock v0.2.0 // indirect - github.com/fatih/color v1.13.0 // indirect + github.com/fatih/color v1.16.0 // indirect github.com/ferranbt/fastssz v0.1.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-gonic/gin v1.7.4 // indirect @@ -32,16 +32,17 @@ require ( github.com/go-playground/validator/v10 v10.9.0 // indirect github.com/goccy/go-yaml v1.9.5 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/holiman/uint256 v1.2.2 // indirect + github.com/holiman/uint256 v1.2.4 // indirect + github.com/huandu/go-clone v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.13.6 // indirect - github.com/klauspost/cpuid/v2 v2.1.2 // indirect + github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/minio/sha256-simd v1.0.0 // indirect + github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -59,12 +60,12 @@ require ( 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/crypto v0.10.0 // indirect + golang.org/x/crypto v0.18.0 // indirect golang.org/x/net v0.10.0 // indirect golang.org/x/sync v0.2.0 // indirect - golang.org/x/sys v0.9.0 // indirect - golang.org/x/text v0.10.0 // indirect - golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect ) diff --git a/go.sum b/go.sum index 5171b5d2..0c6cdff4 100644 --- a/go.sum +++ b/go.sum @@ -41,9 +41,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= -github.com/attestantio/go-eth2-client v0.15.7/go.mod h1:/Oh6YTuHmHhgLN/ZnQRKHGc7HdIzGlDkI2vjNZvOsvA= -github.com/attestantio/go-eth2-client v0.18.2 h1:oZk+NEGqTVl5hIxtVQwwLYXzD+tIcDEuS9MjYtMS/js= -github.com/attestantio/go-eth2-client v0.18.2/go.mod h1:KSVlZSW1A3jUg5H8O89DLtqxgJprRfTtI7k89fLdhu0= +github.com/attestantio/go-eth2-client v0.19.9 h1:g5LLX3X7cLC0KS0oai/MtxBOZz3U3QPIX5qryYMxgVE= +github.com/attestantio/go-eth2-client v0.19.9/go.mod h1:TTz7YF6w4z6ahvxKiHuGPn6DbQn7gH6HPuWm/DEQeGE= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -60,8 +59,6 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -74,14 +71,13 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ethpandaops/beacon v0.28.0 h1:9qaJbCDc0QKN8pA2hwTBJuvFv3vjpBLVH+4PjuR7BAI= -github.com/ethpandaops/beacon v0.28.0/go.mod h1:p+PMyAYXro2hgNEMNbCtzLOvzHund9Q9BTPATvf8/SA= +github.com/ethpandaops/beacon v0.35.0 h1:ZkHfxm41N0wkv503Xdb6rFxLuEnIonClUQWUPFHS5VU= +github.com/ethpandaops/beacon v0.35.0/go.mod h1:B+SLxj1gnDd/Ia7cl/uuhzo1wyVf2p2puL6lmzPdPro= github.com/ethpandaops/ethwallclock v0.2.0 h1:EeFKtZ7v6TAdn/oAh0xaPujD7N4amjBxrWIByraUfLM= github.com/ethpandaops/ethwallclock v0.2.0/go.mod h1:y0Cu+mhGLlem19vnAV2x0hpFS5KZ7oOi2SWYayv9l24= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= -github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/ferranbt/fastssz v0.1.2/go.mod h1:X5UPrE2u1UJjxHA8X54u04SBwdAQjG2sFtWs39YxyWs= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/ferranbt/fastssz v0.1.3 h1:ZI+z3JH05h4kgmFXdHuR1aWYsgrg7o+Fw7/NCzM16Mo= github.com/ferranbt/fastssz v0.1.3/go.mod h1:0Y9TEd/9XuFlh7mskMPfXiI2Dkw4Ddg9EyXt1W7MRvE= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= @@ -189,8 +185,9 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/holiman/uint256 v1.2.2 h1:TXKcSGc2WaxPD2+bmzAsVthL4+pEN0YwXcL5qED83vk= -github.com/holiman/uint256 v1.2.2/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= +github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= +github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c= 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= @@ -216,13 +213,13 @@ github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQ github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.1.2 h1:XhdX4fqAJUA0yj+kUwMavO0hHrSPAecYdYf1ZmxHvak= -github.com/klauspost/cpuid/v2 v2.1.2/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +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/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -235,19 +232,20 @@ github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgx github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= +github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= +github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -274,7 +272,6 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= @@ -301,8 +298,6 @@ github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+Pymzi github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= 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/gohashtree v0.0.1-alpha.0.20220714111606-acbb2962fb48/go.mod h1:4pWaT30XoEx1j8KNJf3TV+E3mQkaufn7mf+jRNb/Fuk= -github.com/r3labs/sse/v2 v2.7.4/go.mod h1:hUrYMKfu9WquG9MyI0r6TKiNH+6Sw/QPKm2YbNbU5g8= 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/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= @@ -313,10 +308,7 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= -github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U= github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc= github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -361,7 +353,6 @@ github.com/xhit/go-str2duration v1.2.0/go.mod h1:3cPSlfZlUHVlneIVfePFWcJZsuwf+P1 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -383,10 +374,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210915214749-c084706c2272/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= -golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -417,7 +406,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= @@ -452,8 +440,6 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -523,7 +509,6 @@ golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -533,16 +518,17 @@ golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= -golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -552,8 +538,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= -golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +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/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -597,15 +583,14 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -708,7 +693,6 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/pkg/beacon/default.go b/pkg/beacon/default.go index d19046fc..fb766ba9 100644 --- a/pkg/beacon/default.go +++ b/pkg/beacon/default.go @@ -133,16 +133,41 @@ func (d *Default) Start(ctx context.Context) error { // Subscribe to the nodes' finality updates. for _, node := range d.nodes { - node.Beacon.OnFinalityCheckpointUpdated(ctx, func(ctx context.Context, checkpoint *beacon.FinalityCheckpointUpdated) error { + logCtx := d.log.WithFields(logrus.Fields{ + "node": node.Config.Name, + }) + + node.Beacon.OnFinalityCheckpointUpdated(ctx, func(ctx context.Context, event *beacon.FinalityCheckpointUpdated) error { + logCtx.WithFields(logrus.Fields{ + "epoch": event.Finality.Finalized.Epoch, + "root": fmt.Sprintf("%#x", event.Finality.Finalized.Root), + }).Info("Node has a new finalized checkpoint") + // Check if we have a new majority finality. if err := d.checkFinality(ctx); err != nil { - d.log.WithError(err).Error("Failed to check finality") + logCtx.WithError(err).Error("Failed to check finality") return err } return d.checkForNewServingCheckpoint(ctx) }) + + node.Beacon.OnReady(ctx, func(ctx context.Context, _ *beacon.ReadyEvent) error { + node.Beacon.Wallclock().OnEpochChanged(func(epoch ethwallclock.Epoch) { + time.Sleep(time.Second * 15) + + if _, err := node.Beacon.FetchFinality(ctx, "head"); err != nil { + logCtx.WithError(err).Error("Failed to fetch finality after epoch transition") + } + + if err := d.checkFinality(ctx); err != nil { + logCtx.WithError(err).Error("Failed to check finality") + } + }) + + return nil + }) } return nil From 3f4b04d6acf40f71e544819f93cfa6c003a98473 Mon Sep 17 00:00:00 2001 From: Sam Calder-Mason Date: Tue, 20 Feb 2024 12:05:35 +1000 Subject: [PATCH 03/13] feat: Subscribe to node events --- pkg/beacon/default.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/beacon/default.go b/pkg/beacon/default.go index fb766ba9..41e96ea7 100644 --- a/pkg/beacon/default.go +++ b/pkg/beacon/default.go @@ -155,7 +155,7 @@ func (d *Default) Start(ctx context.Context) error { node.Beacon.OnReady(ctx, func(ctx context.Context, _ *beacon.ReadyEvent) error { node.Beacon.Wallclock().OnEpochChanged(func(epoch ethwallclock.Epoch) { - time.Sleep(time.Second * 15) + time.Sleep(time.Second * 5) if _, err := node.Beacon.FetchFinality(ctx, "head"); err != nil { logCtx.WithError(err).Error("Failed to fetch finality after epoch transition") @@ -164,6 +164,10 @@ func (d *Default) Start(ctx context.Context) error { if err := d.checkFinality(ctx); err != nil { logCtx.WithError(err).Error("Failed to check finality") } + + if err := d.checkForNewServingCheckpoint(ctx); err != nil { + logCtx.WithError(err).Error("Failed to check for new serving checkpoint after epoch change") + } }) return nil From c17d72d51fc6af693b874894ed7fc0a9373625f3 Mon Sep 17 00:00:00 2001 From: Sam Calder-Mason Date: Tue, 20 Feb 2024 13:17:28 +1000 Subject: [PATCH 04/13] fix: Panic on unhealthy node without an initialized wallclock --- pkg/beacon/default.go | 25 ++++++------------------- pkg/beacon/download.go | 6 +++++- pkg/beacon/nodes.go | 3 ++- 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/pkg/beacon/default.go b/pkg/beacon/default.go index 41e96ea7..05b3b057 100644 --- a/pkg/beacon/default.go +++ b/pkg/beacon/default.go @@ -112,32 +112,19 @@ func (d *Default) Start(ctx context.Context) error { d.log.WithError(err).Fatal("Failed to start crons") } - // Check for new serving checkpoints after each epoch - node, err := d.nodes.Healthy(ctx).NotSyncing(ctx).RandomNode(ctx) - if err != nil { - d.log.WithError(err).Fatal("Failed to get a healthy, non-syncing node to subscribe to wallclock events") - } - - node.Beacon.Wallclock().OnEpochChanged(func(epoch ethwallclock.Epoch) { - // Sleep for a bit to allow the beacon nodes to run their epoch transition. - time.Sleep(time.Second * 30) - - if err := d.checkForNewServingCheckpoint(ctx); err != nil { - d.log.WithError(err).Error("Failed to check for new serving checkpoint after epoch change") - } - }) - break } }() // Subscribe to the nodes' finality updates. for _, node := range d.nodes { + n := node + logCtx := d.log.WithFields(logrus.Fields{ - "node": node.Config.Name, + "node": n.Config.Name, }) - node.Beacon.OnFinalityCheckpointUpdated(ctx, func(ctx context.Context, event *beacon.FinalityCheckpointUpdated) error { + n.Beacon.OnFinalityCheckpointUpdated(ctx, func(ctx context.Context, event *beacon.FinalityCheckpointUpdated) error { logCtx.WithFields(logrus.Fields{ "epoch": event.Finality.Finalized.Epoch, "root": fmt.Sprintf("%#x", event.Finality.Finalized.Root), @@ -153,8 +140,8 @@ func (d *Default) Start(ctx context.Context) error { return d.checkForNewServingCheckpoint(ctx) }) - node.Beacon.OnReady(ctx, func(ctx context.Context, _ *beacon.ReadyEvent) error { - node.Beacon.Wallclock().OnEpochChanged(func(epoch ethwallclock.Epoch) { + n.Beacon.OnReady(ctx, func(ctx context.Context, _ *beacon.ReadyEvent) error { + n.Beacon.Wallclock().OnEpochChanged(func(epoch ethwallclock.Epoch) { time.Sleep(time.Second * 5) if _, err := node.Beacon.FetchFinality(ctx, "head"); err != nil { diff --git a/pkg/beacon/download.go b/pkg/beacon/download.go index d4af983c..1224f084 100644 --- a/pkg/beacon/download.go +++ b/pkg/beacon/download.go @@ -257,6 +257,7 @@ func (d *Default) downloadBlock(ctx context.Context, slot phase0.Slot, upstream "slot": slot, "root": eth.RootAsString(root), "state_root": eth.RootAsString(stateRoot), + "node": upstream.Config.Name, }). Infof("Downloaded and stored block for slot %d", slot) @@ -377,7 +378,10 @@ func (d *Default) downloadAndStoreDepositSnapshot(ctx context.Context, epoch pha } d.log. - WithFields(logrus.Fields{"epoch": epoch}). + WithFields(logrus.Fields{ + "epoch": epoch, + "node": node.Config.Name, + }). Infof("Downloaded and stored deposit snapshot for epoch %d", epoch) return nil diff --git a/pkg/beacon/nodes.go b/pkg/beacon/nodes.go index 496c1fdb..a98e0a15 100644 --- a/pkg/beacon/nodes.go +++ b/pkg/beacon/nodes.go @@ -4,6 +4,7 @@ import ( "context" "errors" "math/rand" + "strings" "time" v1 "github.com/attestantio/go-eth2-client/api/v1" @@ -25,7 +26,7 @@ func NewNodesFromConfig(log logrus.FieldLogger, configs []node.Config, namespace for i, config := range configs { sconfig := &sbeacon.Config{ Name: config.Name, - Addr: config.Address, + Addr: strings.TrimRight(config.Address, "/"), Headers: config.Headers, } From 34aceb8a68063512b157cf802ec8feae9a9c0bb6 Mon Sep 17 00:00:00 2001 From: Sam Calder-Mason Date: Tue, 20 Feb 2024 14:03:01 +1000 Subject: [PATCH 05/13] Fix: Reduce checkpointz status edge caching time --- pkg/api/handler.go | 6 +++--- pkg/beacon/expire_test.go | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/api/handler.go b/pkg/api/handler.go index 29bfda1f..d88aa4b4 100644 --- a/pkg/api/handler.go +++ b/pkg/api/handler.go @@ -439,7 +439,7 @@ func (h *Handler) handleCheckpointzStatus(ctx context.Context, r *http.Request, }, }) - rsp.SetCacheControl("public, s-max-age=30") + rsp.SetCacheControl("public, s-max-age=5") return rsp, nil } @@ -483,7 +483,7 @@ func (h *Handler) handleCheckpointzBeaconSlots(ctx context.Context, r *http.Requ }, }) - rsp.SetCacheControl("public, s-max-age=30") + rsp.SetCacheControl("public, s-max-age=5") return rsp, nil } @@ -509,7 +509,7 @@ func (h *Handler) handleCheckpointzBeaconSlot(ctx context.Context, r *http.Reque }, }) - rsp.SetCacheControl("public, s-max-age=60") + rsp.SetCacheControl("public, s-max-age=5") return rsp, nil } diff --git a/pkg/beacon/expire_test.go b/pkg/beacon/expire_test.go index 8d753bae..9909ac0e 100644 --- a/pkg/beacon/expire_test.go +++ b/pkg/beacon/expire_test.go @@ -67,8 +67,11 @@ func TestExpireMultiple(t *testing.T) { for _, test := range tests { t.Run(fmt.Sprintf("%v", test.slot), func(t *testing.T) { test := test + t.Parallel() + expirySlot := CalculateSlotExpiration(test.slot, slotsOfHistory) + expiryTime := GetSlotTime(expirySlot, defaultSecondsPerSlot, genesis) if expiryTime.String() != test.expect { From 99324a648291a7981acfe261fd64392f20616935 Mon Sep 17 00:00:00 2001 From: Sam Calder-Mason Date: Tue, 20 Feb 2024 15:03:03 +1000 Subject: [PATCH 06/13] feat: Serve block_sidecars route --- go.mod | 4 + pkg/api/handler.go | 32 +++++++ pkg/beacon/config.go | 6 +- pkg/beacon/default.go | 7 ++ pkg/beacon/download.go | 52 ++++++++++++ pkg/beacon/finality_provider.go | 3 + pkg/beacon/store/blob_sidecars.go | 63 ++++++++++++++ pkg/beacon/store/blob_sidecars_test.go | 47 +++++++++++ pkg/service/eth/eth.go | 110 +++++++++++++++++++++++++ 9 files changed, 322 insertions(+), 2 deletions(-) create mode 100644 pkg/beacon/store/blob_sidecars.go create mode 100644 pkg/beacon/store/blob_sidecars_test.go diff --git a/go.mod b/go.mod index 50668a7b..d81ff424 100644 --- a/go.mod +++ b/go.mod @@ -15,12 +15,14 @@ require ( github.com/prometheus/client_golang v1.16.0 github.com/sirupsen/logrus v1.9.1 github.com/spf13/cobra v1.6.1 + github.com/stretchr/testify v1.8.4 gopkg.in/yaml.v2 v2.4.0 ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.16.0 // indirect github.com/ferranbt/fastssz v0.1.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect @@ -46,6 +48,7 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pmezard/go-difflib v1.0.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 @@ -68,4 +71,5 @@ require ( golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/pkg/api/handler.go b/pkg/api/handler.go index d88aa4b4..319600f3 100644 --- a/pkg/api/handler.go +++ b/pkg/api/handler.go @@ -51,6 +51,7 @@ func (h *Handler) Register(ctx context.Context, router *httprouter.Router) error router.GET("/eth/v1/beacon/blocks/:block_id/root", h.wrappedHandler(h.handleEthV1BeaconBlocksRoot)) router.GET("/eth/v1/beacon/states/:state_id/finality_checkpoints", h.wrappedHandler(h.handleEthV1BeaconStatesFinalityCheckpoints)) router.GET("/eth/v1/beacon/deposit_snapshot", h.wrappedHandler(h.handleEthV1BeaconDepositSnapshot)) + router.GET("/eth/v1/beacon/blob_sidecars/:block_id", h.wrappedHandler(h.handleEthV1BeaconBlobSidecars)) router.GET("/eth/v1/config/spec", h.wrappedHandler(h.handleEthV1ConfigSpec)) router.GET("/eth/v1/config/deposit_contract", h.wrappedHandler(h.handleEthV1ConfigDepositContract)) @@ -587,3 +588,34 @@ func (h *Handler) handleEthV1BeaconDepositSnapshot(ctx context.Context, r *http. }, }), nil } + +func (h *Handler) handleEthV1BeaconBlobSidecars(ctx context.Context, r *http.Request, p httprouter.Params, contentType ContentType) (*HTTPResponse, error) { + if err := ValidateContentType(contentType, []ContentType{ContentTypeJSON}); err != nil { + return NewUnsupportedMediaTypeResponse(nil), err + } + + id, err := eth.NewBlockIdentifier(p.ByName("block_id")) + if err != nil { + return NewBadRequestResponse(nil), err + } + + sidecars, err := h.eth.BlobSidecars(ctx, id) + if err != nil { + return NewInternalServerErrorResponse(nil), err + } + + rsp := NewSuccessResponse(ContentTypeResolvers{ + ContentTypeJSON: func() ([]byte, error) { + return json.Marshal(sidecars) + }, + }) + + switch id.Type() { + case eth.BlockIDFinalized, eth.BlockIDRoot: + rsp.SetCacheControl("public, s-max-age=6000") + default: + rsp.SetCacheControl("public, s-max-age=15") + } + + return rsp, nil +} diff --git a/pkg/beacon/config.go b/pkg/beacon/config.go index 453de0f3..d818eb9b 100644 --- a/pkg/beacon/config.go +++ b/pkg/beacon/config.go @@ -24,11 +24,13 @@ type Config struct { // Cache configuration holds configuration for the caches. type CacheConfig struct { // Blocks holds the block cache configuration. - Blocks store.Config `yaml:"blocks" default:"{\"MaxItems\": 200}"` + Blocks store.Config `yaml:"blocks" default:"{\"MaxItems\": 30}"` // States holds the state cache configuration. States store.Config `yaml:"states" default:"{\"MaxItems\": 5}"` // DepositSnapshots holds the deposit snapshot cache configuration. - DepositSnapshots store.Config `yaml:"deposit_snapshots" default:"{\"MaxItems\": 50}"` + DepositSnapshots store.Config `yaml:"deposit_snapshots" default:"{\"MaxItems\": 30}"` + // BlobSidecars holds the blob sidecar cache configuration. + BlobSidecars store.Config `yaml:"blob_sidecars" default:"{\"MaxItems\": 30}"` } type FrontendConfig struct { diff --git a/pkg/beacon/default.go b/pkg/beacon/default.go index 05b3b057..a201c8ed 100644 --- a/pkg/beacon/default.go +++ b/pkg/beacon/default.go @@ -9,6 +9,7 @@ import ( v1 "github.com/attestantio/go-eth2-client/api/v1" "github.com/attestantio/go-eth2-client/spec" + "github.com/attestantio/go-eth2-client/spec/deneb" "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/chuckpreslar/emission" "github.com/ethpandaops/beacon/pkg/beacon" @@ -38,6 +39,7 @@ type Default struct { blocks *store.Block states *store.BeaconState depositSnapshots *store.DepositSnapshot + blobSidecars *store.BlobSidecar spec *state.Spec genesis *v1.Genesis @@ -79,6 +81,7 @@ func NewDefaultProvider(namespace string, log logrus.FieldLogger, nodes []node.C blocks: store.NewBlock(log, config.Caches.Blocks, namespace), states: store.NewBeaconState(log, config.Caches.States, namespace), depositSnapshots: store.NewDepositSnapshot(log, config.Caches.DepositSnapshots, namespace), + blobSidecars: store.NewBlobSidecar(log, config.Caches.BlobSidecars, namespace), servingMutex: sync.Mutex{}, historicalMutex: sync.Mutex{}, @@ -560,6 +563,10 @@ func (d *Default) GetBlockByStateRoot(ctx context.Context, stateRoot phase0.Root return block, nil } +func (d *Default) GetBlobSidecarsBySlot(ctx context.Context, slot phase0.Slot) ([]*deneb.BlobSidecar, error) { + return d.blobSidecars.GetBySlot(slot) +} + func (d *Default) GetBeaconStateBySlot(ctx context.Context, slot phase0.Slot) (*[]byte, error) { block, err := d.GetBlockBySlot(ctx, slot) if err != nil { diff --git a/pkg/beacon/download.go b/pkg/beacon/download.go index 1224f084..2f1ab2e8 100644 --- a/pkg/beacon/download.go +++ b/pkg/beacon/download.go @@ -345,6 +345,24 @@ func (d *Default) fetchBundle(ctx context.Context, root phase0.Root, upstream *N if err := d.downloadAndStoreDepositSnapshot(ctx, epoch, upstream); err != nil { return nil, fmt.Errorf("failed to download and store deposit snapshot: %w", err) } + + spec, err := upstream.Beacon.Spec() + if err != nil { + return nil, fmt.Errorf("failed to fetch spec from upstream node: %w", err) + } + + denebFork, err := spec.ForkEpochs.GetByName("DENEB") + if err != nil { + return nil, fmt.Errorf("failed to fetch deneb fork: %w", err) + } + + if denebFork.Active(slot, spec.SlotsPerEpoch) { + // Download and store blob sidecars + if err := d.downloadAndStoreBlobSidecars(ctx, slot, upstream); err != nil { + return nil, fmt.Errorf("failed to download and store blob sidecars: %w", err) + } + } + } d.log.Infof("Successfully fetched bundle from %s", upstream.Config.Name) @@ -386,3 +404,37 @@ func (d *Default) downloadAndStoreDepositSnapshot(ctx context.Context, epoch pha return nil } + +func (d *Default) downloadAndStoreBlobSidecars(ctx context.Context, slot phase0.Slot, node *Node) error { + // Check if we already have the deposit snapshot. + if _, err := d.blobSidecars.GetBySlot(slot); err == nil { + return nil + } + + // Download the blob sidecars from our upstream. + blobSidecars, err := node.Beacon.FetchBeaconBlockBlobs(ctx, eth.SlotAsString(slot)) + if err != nil { + return err + } + + if blobSidecars == nil { + return errors.New("invalid blob sidecars") + } + + // Store for the FinalityHaltedServingPeriod to ensure we have them in case of non-finality. + // We'll let the store handle purging old items. + expiresAt := time.Now().Add(FinalityHaltedServingPeriod) + + if err := d.blobSidecars.Add(slot, blobSidecars, expiresAt); err != nil { + return fmt.Errorf("failed to store blob sidecars: %w", err) + } + + d.log. + WithFields(logrus.Fields{ + "slot": slot, + "node": node.Config.Name, + }). + Infof("Downloaded and stored blob sidecar for slot %d", slot) + + return nil +} diff --git a/pkg/beacon/finality_provider.go b/pkg/beacon/finality_provider.go index 8d26db22..f3eb0c88 100644 --- a/pkg/beacon/finality_provider.go +++ b/pkg/beacon/finality_provider.go @@ -5,6 +5,7 @@ import ( v1 "github.com/attestantio/go-eth2-client/api/v1" "github.com/attestantio/go-eth2-client/spec" + "github.com/attestantio/go-eth2-client/spec/deneb" "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/ethpandaops/beacon/pkg/beacon/api/types" "github.com/ethpandaops/beacon/pkg/beacon/state" @@ -47,6 +48,8 @@ type FinalityProvider interface { GetBeaconStateByStateRoot(ctx context.Context, root phase0.Root) (*[]byte, error) // GetBeaconStateByRoot returns the beacon sate with the given root. GetBeaconStateByRoot(ctx context.Context, root phase0.Root) (*[]byte, error) + // GetBlobSidecarsBySlot returns the blob sidecars for the given slot. + GetBlobSidecarsBySlot(ctx context.Context, slot phase0.Slot) ([]*deneb.BlobSidecar, error) // ListFinalizedSlots returns a slice of finalized slots. ListFinalizedSlots(ctx context.Context) ([]phase0.Slot, error) // GetEpochBySlot returns the epoch for the given slot. diff --git a/pkg/beacon/store/blob_sidecars.go b/pkg/beacon/store/blob_sidecars.go new file mode 100644 index 00000000..d0ed611d --- /dev/null +++ b/pkg/beacon/store/blob_sidecars.go @@ -0,0 +1,63 @@ +package store + +import ( + "errors" + "time" + + "github.com/attestantio/go-eth2-client/spec/deneb" + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/ethpandaops/checkpointz/pkg/cache" + "github.com/ethpandaops/checkpointz/pkg/eth" + "github.com/sirupsen/logrus" +) + +type BlobSidecar struct { + store *cache.TTLMap + log logrus.FieldLogger +} + +func NewBlobSidecar(log logrus.FieldLogger, config Config, namespace string) *BlobSidecar { + d := &BlobSidecar{ + log: log.WithField("component", "beacon/store/blob_sidecar"), + store: cache.NewTTLMap(config.MaxItems, "blob_sidecar", namespace), + } + + d.store.OnItemDeleted(func(key string, value interface{}, expiredAt time.Time) { + d.log.WithField("key", key).WithField("expired_at", expiredAt.String()).Debug("Blob sidecar was deleted from the cache") + }) + + d.store.EnableMetrics(namespace) + + return d +} + +func (d *BlobSidecar) Add(slot phase0.Slot, sidecars []*deneb.BlobSidecar, expiresAt time.Time) error { + d.store.Add(eth.SlotAsString(slot), sidecars, expiresAt, false) + + d.log.WithFields( + logrus.Fields{ + "slot": eth.SlotAsString(slot), + "expires_at": expiresAt.String(), + }, + ).Debug("Added blob sidecar") + + return nil +} + +func (d *BlobSidecar) GetBySlot(slot phase0.Slot) ([]*deneb.BlobSidecar, error) { + data, _, err := d.store.Get(eth.SlotAsString(slot)) + if err != nil { + return nil, err + } + + return d.parseSidecar(data) +} + +func (d *BlobSidecar) parseSidecar(data interface{}) ([]*deneb.BlobSidecar, error) { + sidecar, ok := data.([]*deneb.BlobSidecar) + if !ok { + return nil, errors.New("invalid blob sidecar type") + } + + return sidecar, nil +} diff --git a/pkg/beacon/store/blob_sidecars_test.go b/pkg/beacon/store/blob_sidecars_test.go new file mode 100644 index 00000000..4a488842 --- /dev/null +++ b/pkg/beacon/store/blob_sidecars_test.go @@ -0,0 +1,47 @@ +package store + +import ( + "testing" + "time" + + "github.com/attestantio/go-eth2-client/spec/deneb" + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/sirupsen/logrus/hooks/test" + "github.com/stretchr/testify/assert" +) + +func TestBlobSidecarAddAndGet(t *testing.T) { + logger, _ := test.NewNullLogger() + config := Config{MaxItems: 10} + namespace := "test" + blobSidecarStore := NewBlobSidecar(logger, config, namespace) + + slot := phase0.Slot(100) + expiresAt := time.Now().Add(10 * time.Minute) + sidecars := []*deneb.BlobSidecar{ + { + Blob: deneb.Blob{}, + }, + } + + err := blobSidecarStore.Add(slot, sidecars, expiresAt) + assert.NoError(t, err) + + retrievedSidecars, err := blobSidecarStore.GetBySlot(slot) + assert.NoError(t, err) + assert.NotNil(t, retrievedSidecars) + assert.Equal(t, sidecars, retrievedSidecars) +} + +func TestBlobSidecarGetBySlotNotFound(t *testing.T) { + logger, _ := test.NewNullLogger() + config := Config{MaxItems: 10} + namespace := "test" + blobSidecarStore := NewBlobSidecar(logger, config, namespace) + + slot := phase0.Slot(200) + + retrievedSidecars, err := blobSidecarStore.GetBySlot(slot) + assert.Error(t, err) + assert.Nil(t, retrievedSidecars) +} diff --git a/pkg/service/eth/eth.go b/pkg/service/eth/eth.go index f1981f9f..ad726944 100644 --- a/pkg/service/eth/eth.go +++ b/pkg/service/eth/eth.go @@ -6,6 +6,7 @@ import ( v1 "github.com/attestantio/go-eth2-client/api/v1" "github.com/attestantio/go-eth2-client/spec" + "github.com/attestantio/go-eth2-client/spec/deneb" "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/ethpandaops/beacon/pkg/beacon/api/types" "github.com/ethpandaops/beacon/pkg/beacon/state" @@ -432,3 +433,112 @@ func (h *Handler) BlockRoot(ctx context.Context, blockID BlockIdentifier) (phase return phase0.Root{}, fmt.Errorf("invalid block id: %v", blockID.String()) } } + +// BlobSidecars returns the blob sidecars for the given block ID. +func (h *Handler) BlobSidecars(ctx context.Context, blockID BlockIdentifier) ([]*deneb.BlobSidecar, error) { + var err error + + const call = "blob_sidecars" + + h.metrics.ObserveCall(call, blockID.Type().String()) + + defer func() { + if err != nil { + h.metrics.ObserveErrorCall(call, blockID.Type().String()) + } + }() + + slot := phase0.Slot(0) + + switch blockID.Type() { + case BlockIDGenesis: + block, err := h.provider.GetBlockBySlot(ctx, phase0.Slot(0)) + if err != nil { + return nil, err + } + + if block == nil { + return nil, fmt.Errorf("no genesis block") + } + + sl, err := block.Slot() + if err != nil { + return nil, err + } + + slot = sl + case BlockIDSlot: + sslot, err := NewSlotFromString(blockID.Value()) + if err != nil { + return nil, err + } + + block, err := h.provider.GetBlockBySlot(ctx, sslot) + if err != nil { + return nil, err + } + + if block == nil { + return nil, fmt.Errorf("no block for slot %v", sslot) + } + + sl, err := block.Slot() + if err != nil { + return nil, err + } + + slot = sl + case BlockIDRoot: + root, err := blockID.AsRoot() + if err != nil { + return nil, err + } + + block, err := h.provider.GetBlockByRoot(ctx, root) + if err != nil { + return nil, err + } + + if block == nil { + return nil, fmt.Errorf("no block for root %v", root) + } + + sl, err := block.Slot() + if err != nil { + return nil, err + } + + slot = sl + case BlockIDFinalized: + finality, err := h.provider.Finalized(ctx) + if err != nil { + return nil, err + } + + if finality == nil || finality.Finalized == nil { + return nil, fmt.Errorf("no finality") + } + + block, err := h.provider.GetBlockByRoot(ctx, finality.Finalized.Root) + if err != nil { + return nil, err + } + + if block == nil { + return nil, fmt.Errorf("no block for finalized root %v", finality.Finalized.Root) + } + + sl, err := block.Slot() + if err != nil { + return nil, err + } + + slot = sl + default: + return nil, fmt.Errorf("invalid block id: %v", blockID.String()) + } + + h.log.WithField("slot", slot).Debug("Getting blob sidecars") + + return h.provider.GetBlobSidecarsBySlot(ctx, slot) +} From da8335d35ff0055670137bee657ed2a8011f8455 Mon Sep 17 00:00:00 2001 From: Sam Calder-Mason Date: Tue, 20 Feb 2024 15:08:25 +1000 Subject: [PATCH 07/13] Merge master --- pkg/beacon/default.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/beacon/default.go b/pkg/beacon/default.go index 74905789..a201c8ed 100644 --- a/pkg/beacon/default.go +++ b/pkg/beacon/default.go @@ -87,10 +87,6 @@ func NewDefaultProvider(namespace string, log logrus.FieldLogger, nodes []node.C historicalMutex: sync.Mutex{}, majorityMutex: sync.Mutex{}, - servingMutex: sync.Mutex{}, - historicalMutex: sync.Mutex{}, - majorityMutex: sync.Mutex{}, - metrics: NewMetrics(namespace + "_beacon"), } } From 72f2fd922f8da758a0189c5b476814dfad3139ac Mon Sep 17 00:00:00 2001 From: Sam Calder-Mason Date: Tue, 20 Feb 2024 15:10:41 +1000 Subject: [PATCH 08/13] fix: typo --- pkg/beacon/download.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/beacon/download.go b/pkg/beacon/download.go index 2f1ab2e8..4b779ed5 100644 --- a/pkg/beacon/download.go +++ b/pkg/beacon/download.go @@ -406,7 +406,7 @@ func (d *Default) downloadAndStoreDepositSnapshot(ctx context.Context, epoch pha } func (d *Default) downloadAndStoreBlobSidecars(ctx context.Context, slot phase0.Slot, node *Node) error { - // Check if we already have the deposit snapshot. + // Check if we already have the blob sidecars. if _, err := d.blobSidecars.GetBySlot(slot); err == nil { return nil } From e05677e984e300b66581c4658822d07fc495d22b Mon Sep 17 00:00:00 2001 From: Sam Calder-Mason Date: Tue, 20 Feb 2024 15:11:46 +1000 Subject: [PATCH 09/13] fix: Remove debug logs --- pkg/service/eth/eth.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/service/eth/eth.go b/pkg/service/eth/eth.go index ad726944..113f7c78 100644 --- a/pkg/service/eth/eth.go +++ b/pkg/service/eth/eth.go @@ -538,7 +538,5 @@ func (h *Handler) BlobSidecars(ctx context.Context, blockID BlockIdentifier) ([] return nil, fmt.Errorf("invalid block id: %v", blockID.String()) } - h.log.WithField("slot", slot).Debug("Getting blob sidecars") - return h.provider.GetBlobSidecarsBySlot(ctx, slot) } From c900bf1685935ef6eac1c82214d79d574804790f Mon Sep 17 00:00:00 2001 From: Sam Calder-Mason Date: Tue, 20 Feb 2024 15:13:31 +1000 Subject: [PATCH 10/13] chore: linting --- pkg/beacon/download.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pkg/beacon/download.go b/pkg/beacon/download.go index 4b779ed5..90990143 100644 --- a/pkg/beacon/download.go +++ b/pkg/beacon/download.go @@ -346,23 +346,22 @@ func (d *Default) fetchBundle(ctx context.Context, root phase0.Root, upstream *N return nil, fmt.Errorf("failed to download and store deposit snapshot: %w", err) } - spec, err := upstream.Beacon.Spec() + sp, err := upstream.Beacon.Spec() if err != nil { return nil, fmt.Errorf("failed to fetch spec from upstream node: %w", err) } - denebFork, err := spec.ForkEpochs.GetByName("DENEB") + denebFork, err := sp.ForkEpochs.GetByName("DENEB") if err != nil { return nil, fmt.Errorf("failed to fetch deneb fork: %w", err) } - if denebFork.Active(slot, spec.SlotsPerEpoch) { + if denebFork.Active(slot, sp.SlotsPerEpoch) { // Download and store blob sidecars if err := d.downloadAndStoreBlobSidecars(ctx, slot, upstream); err != nil { return nil, fmt.Errorf("failed to download and store blob sidecars: %w", err) } } - } d.log.Infof("Successfully fetched bundle from %s", upstream.Config.Name) From 4f0baebb4108a88e488a0468b7b1c931843bb73d Mon Sep 17 00:00:00 2001 From: Sam Calder-Mason Date: Tue, 20 Feb 2024 15:35:10 +1000 Subject: [PATCH 11/13] feat: Handle no DENEB fork definition --- pkg/beacon/download.go | 71 ++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 33 deletions(-) diff --git a/pkg/beacon/download.go b/pkg/beacon/download.go index 90990143..25d3dabf 100644 --- a/pkg/beacon/download.go +++ b/pkg/beacon/download.go @@ -311,30 +311,9 @@ func (d *Default) fetchBundle(ctx context.Context, root phase0.Root, upstream *N } if d.shouldDownloadStates() { - // If the state already exists, don't bother downloading it again. - existingState, err := d.states.GetByStateRoot(stateRoot) - if err == nil && existingState != nil { - d.log.Infof("Successfully fetched bundle from %s", upstream.Config.Name) - - return block, nil - } - - beaconState, err := upstream.Beacon.FetchRawBeaconState(ctx, eth.SlotAsString(slot), "application/octet-stream") - if err != nil { - return nil, fmt.Errorf("failed to fetch beacon state: %w", err) - } - - if beaconState == nil { - return nil, errors.New("beacon state is nil") - } - - expiresAt := time.Now().Add(FinalityHaltedServingPeriod) - if slot == phase0.Slot(0) { - expiresAt = time.Now().Add(999999 * time.Hour) - } - - if err := d.states.Add(stateRoot, &beaconState, expiresAt, slot); err != nil { - return nil, fmt.Errorf("failed to store beacon state: %w", err) + // Download and store beacon state + if err := d.downloadAndStoreBeaconState(ctx, stateRoot, slot, upstream); err != nil { + return nil, fmt.Errorf("failed to download and store beacon state: %w", err) } } @@ -345,17 +324,15 @@ func (d *Default) fetchBundle(ctx context.Context, root phase0.Root, upstream *N if err := d.downloadAndStoreDepositSnapshot(ctx, epoch, upstream); err != nil { return nil, fmt.Errorf("failed to download and store deposit snapshot: %w", err) } + } - sp, err := upstream.Beacon.Spec() - if err != nil { - return nil, fmt.Errorf("failed to fetch spec from upstream node: %w", err) - } - - denebFork, err := sp.ForkEpochs.GetByName("DENEB") - if err != nil { - return nil, fmt.Errorf("failed to fetch deneb fork: %w", err) - } + sp, err := upstream.Beacon.Spec() + if err != nil { + return nil, fmt.Errorf("failed to fetch spec from upstream node: %w", err) + } + denebFork, err := sp.ForkEpochs.GetByName("DENEB") + if err == nil && denebFork != nil { if denebFork.Active(slot, sp.SlotsPerEpoch) { // Download and store blob sidecars if err := d.downloadAndStoreBlobSidecars(ctx, slot, upstream); err != nil { @@ -369,6 +346,34 @@ func (d *Default) fetchBundle(ctx context.Context, root phase0.Root, upstream *N return block, nil } +func (d *Default) downloadAndStoreBeaconState(ctx context.Context, stateRoot phase0.Root, slot phase0.Slot, node *Node) error { + // If the state already exists, don't bother downloading it again. + existingState, err := d.states.GetByStateRoot(stateRoot) + if err == nil && existingState != nil { + return nil + } + + beaconState, err := node.Beacon.FetchRawBeaconState(ctx, eth.SlotAsString(slot), "application/octet-stream") + if err != nil { + return fmt.Errorf("failed to fetch beacon state: %w", err) + } + + if beaconState == nil { + return errors.New("beacon state is nil") + } + + expiresAt := time.Now().Add(FinalityHaltedServingPeriod) + if slot == phase0.Slot(0) { + expiresAt = time.Now().Add(999999 * time.Hour) + } + + if err := d.states.Add(stateRoot, &beaconState, expiresAt, slot); err != nil { + return fmt.Errorf("failed to store beacon state: %w", err) + } + + return nil +} + func (d *Default) downloadAndStoreDepositSnapshot(ctx context.Context, epoch phase0.Epoch, node *Node) error { // Check if we already have the deposit snapshot. if _, err := d.depositSnapshots.GetByEpoch(epoch); err == nil { From 599b95ea2bd7c6776346fb62bc9305b6521b4293 Mon Sep 17 00:00:00 2001 From: Sam Calder-Mason Date: Tue, 20 Feb 2024 15:38:38 +1000 Subject: [PATCH 12/13] fix: Fix assignment in error handling condition --- pkg/beacon/download.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/beacon/download.go b/pkg/beacon/download.go index 25d3dabf..7074c020 100644 --- a/pkg/beacon/download.go +++ b/pkg/beacon/download.go @@ -312,7 +312,7 @@ func (d *Default) fetchBundle(ctx context.Context, root phase0.Root, upstream *N if d.shouldDownloadStates() { // Download and store beacon state - if err := d.downloadAndStoreBeaconState(ctx, stateRoot, slot, upstream); err != nil { + if err = d.downloadAndStoreBeaconState(ctx, stateRoot, slot, upstream); err != nil { return nil, fmt.Errorf("failed to download and store beacon state: %w", err) } } @@ -321,7 +321,7 @@ func (d *Default) fetchBundle(ctx context.Context, root phase0.Root, upstream *N epoch := phase0.Epoch(slot / d.spec.SlotsPerEpoch) // Download and store deposit snapshots - if err := d.downloadAndStoreDepositSnapshot(ctx, epoch, upstream); err != nil { + if err = d.downloadAndStoreDepositSnapshot(ctx, epoch, upstream); err != nil { return nil, fmt.Errorf("failed to download and store deposit snapshot: %w", err) } } From dedc1e59fd1f80958709b311bfff3f201309e890 Mon Sep 17 00:00:00 2001 From: Sam Calder-Mason Date: Tue, 20 Feb 2024 16:48:46 +1000 Subject: [PATCH 13/13] fix: duplicate metrics --- pkg/beacon/store/blob_sidecars_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/beacon/store/blob_sidecars_test.go b/pkg/beacon/store/blob_sidecars_test.go index 4a488842..18bc9434 100644 --- a/pkg/beacon/store/blob_sidecars_test.go +++ b/pkg/beacon/store/blob_sidecars_test.go @@ -13,7 +13,7 @@ import ( func TestBlobSidecarAddAndGet(t *testing.T) { logger, _ := test.NewNullLogger() config := Config{MaxItems: 10} - namespace := "test" + namespace := "test_a" blobSidecarStore := NewBlobSidecar(logger, config, namespace) slot := phase0.Slot(100) @@ -36,7 +36,7 @@ func TestBlobSidecarAddAndGet(t *testing.T) { func TestBlobSidecarGetBySlotNotFound(t *testing.T) { logger, _ := test.NewNullLogger() config := Config{MaxItems: 10} - namespace := "test" + namespace := "test_b" blobSidecarStore := NewBlobSidecar(logger, config, namespace) slot := phase0.Slot(200)