From 4498ed2f55628b9f62ead9a5e8094eea351c0f3c Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Tue, 9 Sep 2025 17:19:04 -0700 Subject: [PATCH 01/34] init --- cl/singlenode/follower/follower.go | 232 +++++++++++++++++++++++++++++ cl/singlenode/follower/store.go | 60 ++++++++ 2 files changed, 292 insertions(+) create mode 100644 cl/singlenode/follower/follower.go create mode 100644 cl/singlenode/follower/store.go diff --git a/cl/singlenode/follower/follower.go b/cl/singlenode/follower/follower.go new file mode 100644 index 000000000..39d22f7f3 --- /dev/null +++ b/cl/singlenode/follower/follower.go @@ -0,0 +1,232 @@ +package follower + +import ( + "context" + "database/sql" + "errors" + "log/slog" + "time" + + "github.com/primev/mev-commit/cl/types" + "golang.org/x/sync/errgroup" +) + +type Follower struct { + logger *slog.Logger + sharedDB types.PayloadRepository + SyncBatchSize uint64 + CaughtUpThreshold uint64 + store *Store + payloadCh chan *types.PayloadInfo +} + +const ( + payloadBufferSize = 100 + defaultBackoff = 200 * time.Millisecond +) + +func NewFollower( + logger *slog.Logger, + payloadRepo types.PayloadRepository, + syncBatchSize uint64, + caughtUpThreshold uint64, + store *Store, +) (*Follower, error) { + if payloadRepo == nil { + return nil, errors.New("payload repository not provided") + } + if syncBatchSize == 0 { + return nil, errors.New("sync batch size must be greater than 0") + } + if caughtUpThreshold == 0 { + return nil, errors.New("caught up threshold must be greater than 0") + } + if caughtUpThreshold > syncBatchSize { + return nil, errors.New("caught up threshold must be less than sync batch size") + } + return &Follower{ + logger: logger, + sharedDB: payloadRepo, + SyncBatchSize: syncBatchSize, + CaughtUpThreshold: caughtUpThreshold, + store: store, + payloadCh: make(chan *types.PayloadInfo, payloadBufferSize), + }, nil +} + +func (f *Follower) Start(ctx context.Context) <-chan struct{} { + + done := make(chan struct{}) + eg, egCtx := errgroup.WithContext(ctx) + eg.Go(func() error { + f.handlePayloads(egCtx) + return nil + }) + + eg.Go(func() error { + f.logger.Info("Starting initial sync from shared DB") + if err := f.syncFromSharedDB(egCtx); err != nil { + f.logger.Error("Failed during initial sync", "error", err) + return err + } + f.logger.Info("Entering steady-state querying of shared DB") + f.queryPayloadsFromSharedDB(egCtx) + return nil + }) + + go func() { + defer close(done) + if err := eg.Wait(); err != nil { + f.logger.Error("follower failed, exiting", "error", err) + } + }() + + return done +} + +func (f *Follower) syncFromSharedDB(ctx context.Context) error { + lastProcessedBlock, err := f.store.GetLastProcessed() + if err != nil { + return err + } + var targetBlock uint64 + + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + // TODO: confirm this will always return valid nonzero payload + latestPayload, err := f.getLatestPayloadWithBackoff(ctx) + if err != nil { + return err + } + targetBlock = latestPayload.BlockHeight + + // TODO: confirm no off-by-one issue + if targetBlock-lastProcessedBlock <= f.CaughtUpThreshold { + f.logger.Info("Sync complete", "last_processed", lastProcessedBlock, "target", targetBlock) + return nil + } + + limit := min(f.SyncBatchSize, targetBlock-lastProcessedBlock) + + innerCtx, innerCancel := context.WithTimeout(ctx, 10*time.Second) + payloads, err := f.sharedDB.GetPayloadsSince(innerCtx, lastProcessedBlock+1, int(limit)) // TODO: confirm no off-by-one issue + innerCancel() + if err != nil { + return err + } + if len(payloads) == 0 { + return errors.New("no payloads returned from valid query") + } + + for _, p := range payloads { + f.payloadCh <- &p + } + } +} + +func (f *Follower) getLatestPayloadWithBackoff(ctx context.Context) (*types.PayloadInfo, error) { + const maxRetries = 10 + const base = 5 * time.Second + for attempt := range maxRetries { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + lctx, cancel := context.WithTimeout(ctx, base) + latest, err := f.sharedDB.GetLatestPayload(lctx) + cancel() + if err == nil { + return latest, nil + } + if err != sql.ErrNoRows { + return nil, err + } + + backoff := base * time.Duration(attempt+1) + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-time.After(backoff): + } + } + return nil, errors.New("failed to get latest payload after retries") +} + +func (f *Follower) queryPayloadsFromSharedDB(ctx context.Context) { + for { + lastProcessed, err := f.store.GetLastProcessed() + if err != nil { + f.logger.Error("Failed to read last processed height", "error", err) + time.Sleep(defaultBackoff) + continue + } + + lctx, cancel := context.WithTimeout(ctx, 5*time.Second) + payload, err := f.sharedDB.GetPayloadByHeight(lctx, lastProcessed+1) + cancel() + + if err != nil { + if err == sql.ErrNoRows { + time.Sleep(time.Millisecond) // New payload will likely be available within milliseconds + continue + } + f.logger.Error("Failed to fetch next payload by height with unexpected error", "height", lastProcessed+1, "error", err) + time.Sleep(defaultBackoff) + continue + } + + if payload == nil { + f.logger.Error("Received nil payload from valid query") + time.Sleep(defaultBackoff) + continue + } + + select { + case <-ctx.Done(): + return + case f.payloadCh <- payload: + f.logger.Debug("Sent payload to channel", "height", lastProcessed+1) + default: + f.logger.Error("Payload channel buffer is full", "height", lastProcessed+1) + time.Sleep(defaultBackoff) + continue + } + } +} + +func (f *Follower) handlePayloads(ctx context.Context) { + for { + select { + case <-ctx.Done(): + return + case p := <-f.payloadCh: + if err := f.handlePayload(ctx, p); err != nil { + f.logger.Error("Failed to process payload", "height", p.BlockHeight, "error", err) + continue + } + if err := f.store.SetLastProcessed(p.BlockHeight); err != nil { + f.logger.Error("Failed to persist last processed height", "height", p.BlockHeight, "error", err) + continue + } + } + } +} + +// TODO: confirm nothing would be broken if the service crashes mid processing of a payload. +// How does the node recover from this w.r.t engine api? Might need to save FSM state in additon to block number in kv store. + +func (f *Follower) handlePayload(ctx context.Context, payload *types.PayloadInfo) error { + // TODO: Apply the payload to follower's EL via Engine API in later steps. + f.logger.Info("Processing payload", + "payload_id", payload.PayloadID, + "block_height", payload.BlockHeight, + ) + return nil +} diff --git a/cl/singlenode/follower/store.go b/cl/singlenode/follower/store.go new file mode 100644 index 000000000..c0cf1d29b --- /dev/null +++ b/cl/singlenode/follower/store.go @@ -0,0 +1,60 @@ +package follower + +import ( + "encoding/binary" + "errors" + "fmt" + "log/slog" + + "github.com/primev/mev-commit/p2p/pkg/storage" +) + +type Store struct { + logger *slog.Logger + kv storage.Storage +} + +const ( + progressKey = "follower/last_block" +) + +func NewStore(logger *slog.Logger, kv storage.Storage) *Store { + return &Store{ + logger: logger.With("component", "FollowerStore"), + kv: kv, + } +} + +func (s *Store) GetLastProcessed() (uint64, error) { + if s.kv == nil { + return 0, errors.New("kv is nil") + } + buf, err := s.kv.Get(progressKey) + switch { + case err == nil: + if len(buf) != 8 { + return 0, fmt.Errorf("invalid %q length: got %d, want 8", progressKey, len(buf)) + } + return binary.BigEndian.Uint64(buf), nil + case errors.Is(err, storage.ErrKeyNotFound): + return 0, nil + default: + return 0, err + } +} + +func (s *Store) SetLastProcessed(height uint64) error { + if s.kv == nil { + return errors.New("kv is nil") + } + var b [8]byte + binary.BigEndian.PutUint64(b[:], height) + return s.kv.Put(progressKey, b[:]) +} + +func (s *Store) Close() error { + if s.kv != nil { + return s.kv.Close() + } + return nil +} From 4262039bf750fe5f6b79117a3b8efad05bf619e2 Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Tue, 9 Sep 2025 17:30:18 -0700 Subject: [PATCH 02/34] address todo --- cl/singlenode/follower/follower.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/cl/singlenode/follower/follower.go b/cl/singlenode/follower/follower.go index 39d22f7f3..9fa3dc1e2 100644 --- a/cl/singlenode/follower/follower.go +++ b/cl/singlenode/follower/follower.go @@ -98,7 +98,6 @@ func (f *Follower) syncFromSharedDB(ctx context.Context) error { default: } - // TODO: confirm this will always return valid nonzero payload latestPayload, err := f.getLatestPayloadWithBackoff(ctx) if err != nil { return err @@ -133,17 +132,14 @@ func (f *Follower) getLatestPayloadWithBackoff(ctx context.Context) (*types.Payl const maxRetries = 10 const base = 5 * time.Second for attempt := range maxRetries { - select { - case <-ctx.Done(): - return nil, ctx.Err() - default: - } - lctx, cancel := context.WithTimeout(ctx, base) latest, err := f.sharedDB.GetLatestPayload(lctx) cancel() if err == nil { - return latest, nil + if latest != nil { + return latest, nil + } + return nil, errors.New("nil payload returned") } if err != sql.ErrNoRows { return nil, err From ed55b2e97c49b38c26ce2a14f3a46718d213933c Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Tue, 9 Sep 2025 18:48:31 -0700 Subject: [PATCH 03/34] wip --- cl/singlenode/follower/export.go | 15 ++++ cl/singlenode/follower/follower.go | 53 +++++++----- cl/singlenode/follower/follower_test.go | 102 ++++++++++++++++++++++++ cl/singlenode/payloadstore/postgres.go | 20 +++++ 4 files changed, 168 insertions(+), 22 deletions(-) create mode 100644 cl/singlenode/follower/export.go create mode 100644 cl/singlenode/follower/follower_test.go diff --git a/cl/singlenode/follower/export.go b/cl/singlenode/follower/export.go new file mode 100644 index 000000000..2e4bbc004 --- /dev/null +++ b/cl/singlenode/follower/export.go @@ -0,0 +1,15 @@ +package follower + +import ( + "context" + + "github.com/primev/mev-commit/cl/types" +) + +func (f *Follower) PayloadCh() <-chan *types.PayloadInfo { + return f.payloadCh +} + +func (f *Follower) SyncFromSharedDB(ctx context.Context) error { + return f.syncFromSharedDB(ctx) +} diff --git a/cl/singlenode/follower/follower.go b/cl/singlenode/follower/follower.go index 9fa3dc1e2..2573e34b1 100644 --- a/cl/singlenode/follower/follower.go +++ b/cl/singlenode/follower/follower.go @@ -13,9 +13,9 @@ import ( type Follower struct { logger *slog.Logger - sharedDB types.PayloadRepository - SyncBatchSize uint64 - CaughtUpThreshold uint64 + sharedDB payloadDB + syncBatchSize uint64 + caughtUpThreshold uint64 store *Store payloadCh chan *types.PayloadInfo } @@ -25,14 +25,20 @@ const ( defaultBackoff = 200 * time.Millisecond ) +type payloadDB interface { + GetPayloadsSince(ctx context.Context, sinceHeight uint64, limit int) ([]types.PayloadInfo, error) + GetPayloadByHeight(ctx context.Context, height uint64) (*types.PayloadInfo, error) + GetLatestHeight(ctx context.Context) (*uint64, error) +} + func NewFollower( logger *slog.Logger, - payloadRepo types.PayloadRepository, + sharedDB payloadDB, syncBatchSize uint64, caughtUpThreshold uint64, store *Store, ) (*Follower, error) { - if payloadRepo == nil { + if sharedDB == nil { return nil, errors.New("payload repository not provided") } if syncBatchSize == 0 { @@ -46,9 +52,9 @@ func NewFollower( } return &Follower{ logger: logger, - sharedDB: payloadRepo, - SyncBatchSize: syncBatchSize, - CaughtUpThreshold: caughtUpThreshold, + sharedDB: sharedDB, + syncBatchSize: syncBatchSize, + caughtUpThreshold: caughtUpThreshold, store: store, payloadCh: make(chan *types.PayloadInfo, payloadBufferSize), }, nil @@ -89,7 +95,7 @@ func (f *Follower) syncFromSharedDB(ctx context.Context) error { if err != nil { return err } - var targetBlock uint64 + lastSignalledBlock := lastProcessedBlock for { select { @@ -98,19 +104,19 @@ func (f *Follower) syncFromSharedDB(ctx context.Context) error { default: } - latestPayload, err := f.getLatestPayloadWithBackoff(ctx) + targetBlock, err := f.getLatestHeightWithBackoff(ctx) if err != nil { return err } - targetBlock = latestPayload.BlockHeight - // TODO: confirm no off-by-one issue - if targetBlock-lastProcessedBlock <= f.CaughtUpThreshold { + blocksRemaining := targetBlock - lastSignalledBlock + + if blocksRemaining <= f.caughtUpThreshold { f.logger.Info("Sync complete", "last_processed", lastProcessedBlock, "target", targetBlock) return nil } - limit := min(f.SyncBatchSize, targetBlock-lastProcessedBlock) + limit := min(f.syncBatchSize, blocksRemaining) innerCtx, innerCancel := context.WithTimeout(ctx, 10*time.Second) payloads, err := f.sharedDB.GetPayloadsSince(innerCtx, lastProcessedBlock+1, int(limit)) // TODO: confirm no off-by-one issue @@ -123,36 +129,37 @@ func (f *Follower) syncFromSharedDB(ctx context.Context) error { } for _, p := range payloads { - f.payloadCh <- &p + f.payloadCh <- &p // Non-blocking up to payloadBufferSize + lastSignalledBlock = p.BlockHeight } } } -func (f *Follower) getLatestPayloadWithBackoff(ctx context.Context) (*types.PayloadInfo, error) { +func (f *Follower) getLatestHeightWithBackoff(ctx context.Context) (uint64, error) { const maxRetries = 10 const base = 5 * time.Second for attempt := range maxRetries { lctx, cancel := context.WithTimeout(ctx, base) - latest, err := f.sharedDB.GetLatestPayload(lctx) + latest, err := f.sharedDB.GetLatestHeight(lctx) cancel() if err == nil { if latest != nil { - return latest, nil + return *latest, nil } - return nil, errors.New("nil payload returned") + return 0, errors.New("nil height returned") } if err != sql.ErrNoRows { - return nil, err + return 0, err } backoff := base * time.Duration(attempt+1) select { case <-ctx.Done(): - return nil, ctx.Err() + return 0, ctx.Err() case <-time.After(backoff): } } - return nil, errors.New("failed to get latest payload after retries") + return 0, errors.New("failed to get latest payload after retries") } func (f *Follower) queryPayloadsFromSharedDB(ctx context.Context) { @@ -218,6 +225,8 @@ func (f *Follower) handlePayloads(ctx context.Context) { // TODO: confirm nothing would be broken if the service crashes mid processing of a payload. // How does the node recover from this w.r.t engine api? Might need to save FSM state in additon to block number in kv store. +// TODO: Or w.r.t above could the engine api just handle 1-2 duplicate calls and gracefully continue? Need to confirm. + func (f *Follower) handlePayload(ctx context.Context, payload *types.PayloadInfo) error { // TODO: Apply the payload to follower's EL via Engine API in later steps. f.logger.Info("Processing payload", diff --git a/cl/singlenode/follower/follower_test.go b/cl/singlenode/follower/follower_test.go new file mode 100644 index 000000000..c6f7ea72e --- /dev/null +++ b/cl/singlenode/follower/follower_test.go @@ -0,0 +1,102 @@ +package follower_test + +import ( + "context" + "fmt" + "io" + "testing" + "time" + + "github.com/primev/mev-commit/cl/singlenode/follower" + "github.com/primev/mev-commit/cl/types" + inmemstorage "github.com/primev/mev-commit/p2p/pkg/storage/inmem" + "github.com/primev/mev-commit/x/util" +) + +type mockPayloadDB struct { + GetPayloadsSinceFunc func(ctx context.Context, sinceHeight uint64, limit int) ([]types.PayloadInfo, error) + GetLatestHeightFunc func(ctx context.Context) (*uint64, error) + GetPayloadByHeightFunc func(ctx context.Context, height uint64) (*types.PayloadInfo, error) +} + +func (m *mockPayloadDB) GetPayloadsSince(ctx context.Context, sinceHeight uint64, limit int) ([]types.PayloadInfo, error) { + return m.GetPayloadsSinceFunc(ctx, sinceHeight, limit) +} + +func (m *mockPayloadDB) GetLatestHeight(ctx context.Context) (*uint64, error) { + return m.GetLatestHeightFunc(ctx) +} + +func (m *mockPayloadDB) GetPayloadByHeight(ctx context.Context, height uint64) (*types.PayloadInfo, error) { + return m.GetPayloadByHeightFunc(ctx, height) +} + +func TestFollower_syncFromSharedDB(t *testing.T) { + t.Parallel() + + lastProcessed := uint64(500) + latest := uint64(550) + + logger := util.NewTestLogger(io.Discard) + payloadRepo := &mockPayloadDB{ + GetLatestHeightFunc: func(ctx context.Context) (*uint64, error) { + return &latest, nil + }, + GetPayloadsSinceFunc: func(ctx context.Context, sinceHeight uint64, limit int) ([]types.PayloadInfo, error) { + if sinceHeight != 501 { + t.Fatal("unexpected sinceHeight", sinceHeight) + } + toReturn := []types.PayloadInfo{} + for i := sinceHeight; i <= latest; i++ { + toReturn = append(toReturn, types.PayloadInfo{BlockHeight: i}) + } + return toReturn, nil + }, + } + syncBatchSize := uint64(100) + caughtUpThreshold := uint64(5) + st := follower.NewStore(logger, inmemstorage.New()) + + follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, caughtUpThreshold, st) + if err != nil { + t.Fatal(err) + } + fmt.Println(follower) + + st.SetLastProcessed(lastProcessed) + + go func() { + follower.SyncFromSharedDB(context.Background()) + }() + + payloadCh := follower.PayloadCh() + + // expect all 50 payload signals to be sent + for i := 501; i <= 550; i++ { + select { + case p := <-payloadCh: + if p == nil { + t.Fatalf("received nil payload at %d", i) + } + if p.BlockHeight != uint64(i) { + t.Fatalf("expected payload height %d, got %d", i, p.BlockHeight) + } + if err := st.SetLastProcessed(p.BlockHeight); err != nil { + t.Fatalf("failed to set last processed: %v", err) + } + case <-time.After(1 * time.Second): + t.Fatalf("timeout waiting for payload %d", i) + } + } + + // No more than 50 + select { + case <-payloadCh: + t.Fatal("received unexpected payload") + case <-time.After(1 * time.Second): + } +} + +// TODO: test where channel becomes full and the sync thread blocks.. testing "// Non-blocking up to payloadBufferSize" + +// TODO: test threshold diff --git a/cl/singlenode/payloadstore/postgres.go b/cl/singlenode/payloadstore/postgres.go index c0389cf3d..010b1ad39 100644 --- a/cl/singlenode/payloadstore/postgres.go +++ b/cl/singlenode/payloadstore/postgres.go @@ -256,6 +256,26 @@ func (r *PostgresRepository) GetLatestPayload(ctx context.Context) (*types.Paylo return &payload, nil } +func (r *PostgresRepository) GetLatestHeight(ctx context.Context) (*uint64, error) { + query := ` + SELECT MAX(block_height) FROM execution_payloads; + ` + + queryCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + var n sql.NullInt64 + if err := r.db.QueryRowContext(queryCtx, query).Scan(&n); err != nil { + return nil, fmt.Errorf("failed to query latest height: %w", err) + } + if !n.Valid { + return nil, sql.ErrNoRows + } + + h := uint64(n.Int64) + return &h, nil +} + // Close closes the database connection. func (r *PostgresRepository) Close() error { if r.db != nil { From 7d6417e28132d69530208c7633842f40629926c1 Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Tue, 9 Sep 2025 22:46:42 -0700 Subject: [PATCH 04/34] prog --- cl/singlenode/follower/export.go | 6 +- cl/singlenode/follower/follower.go | 60 ++++++++-------- cl/singlenode/follower/follower_test.go | 91 +++++++++++++++++++++++-- 3 files changed, 120 insertions(+), 37 deletions(-) diff --git a/cl/singlenode/follower/export.go b/cl/singlenode/follower/export.go index 2e4bbc004..bf8bb9afa 100644 --- a/cl/singlenode/follower/export.go +++ b/cl/singlenode/follower/export.go @@ -6,10 +6,14 @@ import ( "github.com/primev/mev-commit/cl/types" ) -func (f *Follower) PayloadCh() <-chan *types.PayloadInfo { +func (f *Follower) PayloadCh() <-chan types.PayloadInfo { return f.payloadCh } func (f *Follower) SyncFromSharedDB(ctx context.Context) error { return f.syncFromSharedDB(ctx) } + +func (f *Follower) LastSignalledBlock() uint64 { + return f.lastSignalledBlock +} diff --git a/cl/singlenode/follower/follower.go b/cl/singlenode/follower/follower.go index 2573e34b1..cef33996c 100644 --- a/cl/singlenode/follower/follower.go +++ b/cl/singlenode/follower/follower.go @@ -12,12 +12,13 @@ import ( ) type Follower struct { - logger *slog.Logger - sharedDB payloadDB - syncBatchSize uint64 - caughtUpThreshold uint64 - store *Store - payloadCh chan *types.PayloadInfo + logger *slog.Logger + sharedDB payloadDB + syncBatchSize uint64 + caughtUpThreshold uint64 + store *Store + payloadCh chan types.PayloadInfo + lastSignalledBlock uint64 // Last block num signalled through payloadCh } const ( @@ -56,7 +57,7 @@ func NewFollower( syncBatchSize: syncBatchSize, caughtUpThreshold: caughtUpThreshold, store: store, - payloadCh: make(chan *types.PayloadInfo, payloadBufferSize), + payloadCh: make(chan types.PayloadInfo, payloadBufferSize), }, nil } @@ -91,13 +92,15 @@ func (f *Follower) Start(ctx context.Context) <-chan struct{} { } func (f *Follower) syncFromSharedDB(ctx context.Context) error { - lastProcessedBlock, err := f.store.GetLastProcessed() + var err error + // lastSignalledBlock is only set from disk here + f.lastSignalledBlock, err = f.store.GetLastProcessed() if err != nil { return err } - lastSignalledBlock := lastProcessedBlock for { + f.logger.Debug("Syncing from shared DB", "lastSignalledBlock", f.lastSignalledBlock) select { case <-ctx.Done(): return ctx.Err() @@ -109,17 +112,17 @@ func (f *Follower) syncFromSharedDB(ctx context.Context) error { return err } - blocksRemaining := targetBlock - lastSignalledBlock + blocksRemaining := targetBlock - f.lastSignalledBlock if blocksRemaining <= f.caughtUpThreshold { - f.logger.Info("Sync complete", "last_processed", lastProcessedBlock, "target", targetBlock) + f.logger.Info("Sync complete") return nil } limit := min(f.syncBatchSize, blocksRemaining) innerCtx, innerCancel := context.WithTimeout(ctx, 10*time.Second) - payloads, err := f.sharedDB.GetPayloadsSince(innerCtx, lastProcessedBlock+1, int(limit)) // TODO: confirm no off-by-one issue + payloads, err := f.sharedDB.GetPayloadsSince(innerCtx, f.lastSignalledBlock+1, int(limit)) innerCancel() if err != nil { return err @@ -128,18 +131,18 @@ func (f *Follower) syncFromSharedDB(ctx context.Context) error { return errors.New("no payloads returned from valid query") } - for _, p := range payloads { - f.payloadCh <- &p // Non-blocking up to payloadBufferSize - lastSignalledBlock = p.BlockHeight + for i := range payloads { + p := payloads[i] + f.payloadCh <- p // Non-blocking up to payloadBufferSize + f.lastSignalledBlock = p.BlockHeight } } } func (f *Follower) getLatestHeightWithBackoff(ctx context.Context) (uint64, error) { const maxRetries = 10 - const base = 5 * time.Second for attempt := range maxRetries { - lctx, cancel := context.WithTimeout(ctx, base) + lctx, cancel := context.WithTimeout(ctx, time.Second) latest, err := f.sharedDB.GetLatestHeight(lctx) cancel() if err == nil { @@ -152,7 +155,7 @@ func (f *Follower) getLatestHeightWithBackoff(ctx context.Context) (uint64, erro return 0, err } - backoff := base * time.Duration(attempt+1) + backoff := defaultBackoff * time.Duration(attempt+1) select { case <-ctx.Done(): return 0, ctx.Err() @@ -164,15 +167,8 @@ func (f *Follower) getLatestHeightWithBackoff(ctx context.Context) (uint64, erro func (f *Follower) queryPayloadsFromSharedDB(ctx context.Context) { for { - lastProcessed, err := f.store.GetLastProcessed() - if err != nil { - f.logger.Error("Failed to read last processed height", "error", err) - time.Sleep(defaultBackoff) - continue - } - lctx, cancel := context.WithTimeout(ctx, 5*time.Second) - payload, err := f.sharedDB.GetPayloadByHeight(lctx, lastProcessed+1) + payload, err := f.sharedDB.GetPayloadByHeight(lctx, f.lastSignalledBlock+1) cancel() if err != nil { @@ -180,7 +176,7 @@ func (f *Follower) queryPayloadsFromSharedDB(ctx context.Context) { time.Sleep(time.Millisecond) // New payload will likely be available within milliseconds continue } - f.logger.Error("Failed to fetch next payload by height with unexpected error", "height", lastProcessed+1, "error", err) + f.logger.Error("Failed to fetch next payload by height with unexpected error", "height", f.lastSignalledBlock+1, "error", err) time.Sleep(defaultBackoff) continue } @@ -194,10 +190,12 @@ func (f *Follower) queryPayloadsFromSharedDB(ctx context.Context) { select { case <-ctx.Done(): return - case f.payloadCh <- payload: - f.logger.Debug("Sent payload to channel", "height", lastProcessed+1) + case f.payloadCh <- *payload: + f.logger.Debug("Sent payload to channel", "height", f.lastSignalledBlock+1) + f.lastSignalledBlock = payload.BlockHeight + default: - f.logger.Error("Payload channel buffer is full", "height", lastProcessed+1) + f.logger.Error("Payload channel buffer is full", "height", f.lastSignalledBlock+1) time.Sleep(defaultBackoff) continue } @@ -227,7 +225,7 @@ func (f *Follower) handlePayloads(ctx context.Context) { // TODO: Or w.r.t above could the engine api just handle 1-2 duplicate calls and gracefully continue? Need to confirm. -func (f *Follower) handlePayload(ctx context.Context, payload *types.PayloadInfo) error { +func (f *Follower) handlePayload(ctx context.Context, payload types.PayloadInfo) error { // TODO: Apply the payload to follower's EL via Engine API in later steps. f.logger.Info("Processing payload", "payload_id", payload.PayloadID, diff --git a/cl/singlenode/follower/follower_test.go b/cl/singlenode/follower/follower_test.go index c6f7ea72e..9dcc8e5e5 100644 --- a/cl/singlenode/follower/follower_test.go +++ b/cl/singlenode/follower/follower_test.go @@ -2,6 +2,7 @@ package follower_test import ( "context" + "database/sql" "fmt" "io" "testing" @@ -65,8 +66,10 @@ func TestFollower_syncFromSharedDB(t *testing.T) { st.SetLastProcessed(lastProcessed) + errCh := make(chan error, 1) + go func() { - follower.SyncFromSharedDB(context.Background()) + errCh <- follower.SyncFromSharedDB(context.Background()) }() payloadCh := follower.PayloadCh() @@ -74,16 +77,17 @@ func TestFollower_syncFromSharedDB(t *testing.T) { // expect all 50 payload signals to be sent for i := 501; i <= 550; i++ { select { + case err := <-errCh: + if err != nil { + t.Fatalf("follower failed, exiting: %v", err) + } case p := <-payloadCh: - if p == nil { + if p == (types.PayloadInfo{}) { t.Fatalf("received nil payload at %d", i) } if p.BlockHeight != uint64(i) { t.Fatalf("expected payload height %d, got %d", i, p.BlockHeight) } - if err := st.SetLastProcessed(p.BlockHeight); err != nil { - t.Fatalf("failed to set last processed: %v", err) - } case <-time.After(1 * time.Second): t.Fatalf("timeout waiting for payload %d", i) } @@ -91,12 +95,89 @@ func TestFollower_syncFromSharedDB(t *testing.T) { // No more than 50 select { + case err := <-errCh: + if err != nil { + t.Fatalf("follower failed, exiting: %v", err) + } case <-payloadCh: t.Fatal("received unexpected payload") case <-time.After(1 * time.Second): } } +func TestFollower_syncFromSharedDB_NoRows(t *testing.T) { + t.Parallel() + + attempts := 0 + logger := util.NewTestLogger(io.Discard) + payloadRepo := &mockPayloadDB{ + GetLatestHeightFunc: func(ctx context.Context) (*uint64, error) { + if attempts < 3 { + attempts++ + return nil, sql.ErrNoRows + } + block15 := uint64(15) + return &block15, nil + }, + GetPayloadsSinceFunc: func(ctx context.Context, sinceHeight uint64, limit int) ([]types.PayloadInfo, error) { + if sinceHeight != 1 { + return nil, fmt.Errorf("unexpected sinceHeight %d", sinceHeight) + } + if limit != 15 { + return nil, fmt.Errorf("unexpected limit %d", limit) + } + toReturn := []types.PayloadInfo{} + for i := 1; i <= 15; i++ { + toReturn = append(toReturn, types.PayloadInfo{BlockHeight: uint64(i)}) + } + return toReturn, nil + }, + GetPayloadByHeightFunc: func(ctx context.Context, height uint64) (*types.PayloadInfo, error) { + return nil, nil + }, + } + syncBatchSize := uint64(100) + caughtUpThreshold := uint64(5) + st := follower.NewStore(logger, inmemstorage.New()) + + follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, caughtUpThreshold, st) + if err != nil { + t.Fatal(err) + } + + errCh := make(chan error, 1) + + go func() { + errCh <- follower.SyncFromSharedDB(context.Background()) + }() + + payloadCh := follower.PayloadCh() + + // expect 15 payloads + for i := 1; i <= 15; i++ { + select { + case err := <-errCh: + if err != nil { + t.Fatalf("follower failed, exiting: %v", err) + } + case p := <-payloadCh: + if p == (types.PayloadInfo{}) { + t.Fatalf("received nil payload at %d", i) + } + if p.BlockHeight != uint64(i) { + t.Fatalf("expected payload height %d, got %d", i, p.BlockHeight) + } + case <-time.After(10 * time.Second): + t.Fatalf("timeout waiting for payload %d", i) + } + } +} + +// TODO: test with two for loop iterations to test last processed vs last signalled block + +// TODO: test syncFromSharedDB leading into steady state with sql.ErrNoRows returned a couple times. +// When it does come online.. test from block 1, simulating new chain. + // TODO: test where channel becomes full and the sync thread blocks.. testing "// Non-blocking up to payloadBufferSize" // TODO: test threshold From 39e8fb0fd6b85253984e3aef6200614a5547aaab Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Tue, 9 Sep 2025 23:01:17 -0700 Subject: [PATCH 05/34] fix tests --- cl/singlenode/follower/follower_test.go | 53 ++++++++++++++++++++----- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/cl/singlenode/follower/follower_test.go b/cl/singlenode/follower/follower_test.go index 9dcc8e5e5..3395d84f5 100644 --- a/cl/singlenode/follower/follower_test.go +++ b/cl/singlenode/follower/follower_test.go @@ -74,22 +74,32 @@ func TestFollower_syncFromSharedDB(t *testing.T) { payloadCh := follower.PayloadCh() - // expect all 50 payload signals to be sent - for i := 501; i <= 550; i++ { + // expect 50 payloads + received := 0 + expectedBlockHeight := uint64(501) + numErrSignals := 0 + for received < 50 { select { case err := <-errCh: if err != nil { t.Fatalf("follower failed, exiting: %v", err) } + if numErrSignals > 1 { + t.Fatalf("SyncFromSharedDB should only signal nil error once") + } + numErrSignals++ + continue case p := <-payloadCh: if p == (types.PayloadInfo{}) { - t.Fatalf("received nil payload at %d", i) + t.Fatalf("received zero payload for expected block height %d", expectedBlockHeight) } - if p.BlockHeight != uint64(i) { - t.Fatalf("expected payload height %d, got %d", i, p.BlockHeight) + if p.BlockHeight != expectedBlockHeight { + t.Fatalf("expected payload height %d, got %d", expectedBlockHeight, p.BlockHeight) } + expectedBlockHeight++ + received++ case <-time.After(1 * time.Second): - t.Fatalf("timeout waiting for payload %d", i) + t.Fatalf("timeout waiting for payload for expected block height %d", expectedBlockHeight) } } @@ -154,22 +164,43 @@ func TestFollower_syncFromSharedDB_NoRows(t *testing.T) { payloadCh := follower.PayloadCh() // expect 15 payloads - for i := 1; i <= 15; i++ { + received := 0 + expectedBlockHeight := uint64(1) + numErrSignals := 0 + for received < 15 { select { case err := <-errCh: if err != nil { t.Fatalf("follower failed, exiting: %v", err) } + if numErrSignals > 1 { + t.Fatalf("SyncFromSharedDB should only signal nil error once") + } + numErrSignals++ + continue case p := <-payloadCh: if p == (types.PayloadInfo{}) { - t.Fatalf("received nil payload at %d", i) + t.Fatalf("received zero payload at %d", expectedBlockHeight) } - if p.BlockHeight != uint64(i) { - t.Fatalf("expected payload height %d, got %d", i, p.BlockHeight) + if p.BlockHeight != expectedBlockHeight { + t.Fatalf("expected payload height %d, got %d", expectedBlockHeight, p.BlockHeight) } + expectedBlockHeight++ + received++ case <-time.After(10 * time.Second): - t.Fatalf("timeout waiting for payload %d", i) + t.Fatalf("timeout waiting for payload %d", expectedBlockHeight) + } + } + + // No more than 15 + select { + case err := <-errCh: + if err != nil { + t.Fatalf("follower failed, exiting: %v", err) } + case <-payloadCh: + t.Fatal("received unexpected payload") + case <-time.After(1 * time.Second): } } From c39866d28a0d3272b3763dad1eafe724a66df98b Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Tue, 9 Sep 2025 23:02:38 -0700 Subject: [PATCH 06/34] Update follower_test.go --- cl/singlenode/follower/follower_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/cl/singlenode/follower/follower_test.go b/cl/singlenode/follower/follower_test.go index 3395d84f5..303418c27 100644 --- a/cl/singlenode/follower/follower_test.go +++ b/cl/singlenode/follower/follower_test.go @@ -142,9 +142,6 @@ func TestFollower_syncFromSharedDB_NoRows(t *testing.T) { } return toReturn, nil }, - GetPayloadByHeightFunc: func(ctx context.Context, height uint64) (*types.PayloadInfo, error) { - return nil, nil - }, } syncBatchSize := uint64(100) caughtUpThreshold := uint64(5) From d493d479e9a538ccc1a5730707915cfed87b6c39 Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Tue, 9 Sep 2025 23:04:27 -0700 Subject: [PATCH 07/34] Update follower_test.go --- cl/singlenode/follower/follower_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cl/singlenode/follower/follower_test.go b/cl/singlenode/follower/follower_test.go index 303418c27..8a7ec80ba 100644 --- a/cl/singlenode/follower/follower_test.go +++ b/cl/singlenode/follower/follower_test.go @@ -102,6 +102,9 @@ func TestFollower_syncFromSharedDB(t *testing.T) { t.Fatalf("timeout waiting for payload for expected block height %d", expectedBlockHeight) } } + if numErrSignals != 1 { + t.Fatalf("SyncFromSharedDB should signal nil error once, got %d", numErrSignals) + } // No more than 50 select { @@ -188,6 +191,9 @@ func TestFollower_syncFromSharedDB_NoRows(t *testing.T) { t.Fatalf("timeout waiting for payload %d", expectedBlockHeight) } } + if numErrSignals != 1 { + t.Fatalf("SyncFromSharedDB should signal nil error once, got %d", numErrSignals) + } // No more than 15 select { From 4dfae98e635010ea9779da705013ddf0027ffaff Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Tue, 9 Sep 2025 23:50:58 -0700 Subject: [PATCH 08/34] TestFollower_syncFromSharedDB_MultipleIterations --- cl/singlenode/follower/follower_test.go | 127 +++++++++++++++++++++++- 1 file changed, 122 insertions(+), 5 deletions(-) diff --git a/cl/singlenode/follower/follower_test.go b/cl/singlenode/follower/follower_test.go index 8a7ec80ba..888e15ccf 100644 --- a/cl/singlenode/follower/follower_test.go +++ b/cl/singlenode/follower/follower_test.go @@ -62,7 +62,6 @@ func TestFollower_syncFromSharedDB(t *testing.T) { if err != nil { t.Fatal(err) } - fmt.Println(follower) st.SetLastProcessed(lastProcessed) @@ -105,6 +104,9 @@ func TestFollower_syncFromSharedDB(t *testing.T) { if numErrSignals != 1 { t.Fatalf("SyncFromSharedDB should signal nil error once, got %d", numErrSignals) } + if received != 50 { + t.Fatalf("expected 50 payloads, got %d", received) + } // No more than 50 select { @@ -194,6 +196,9 @@ func TestFollower_syncFromSharedDB_NoRows(t *testing.T) { if numErrSignals != 1 { t.Fatalf("SyncFromSharedDB should signal nil error once, got %d", numErrSignals) } + if received != 15 { + t.Fatalf("expected 15 payloads, got %d", received) + } // No more than 15 select { @@ -207,11 +212,123 @@ func TestFollower_syncFromSharedDB_NoRows(t *testing.T) { } } -// TODO: test with two for loop iterations to test last processed vs last signalled block +func TestFollower_syncFromSharedDB_MultipleIterations(t *testing.T) { + t.Parallel() + + lastProcessed := uint64(200) + latest := uint64(250) + + logger := util.NewTestLogger(io.Discard) + + numGetLatestHeightCalls := 0 + numGetPayloadsCalls := 0 + payloadRepo := &mockPayloadDB{ + GetLatestHeightFunc: func(ctx context.Context) (*uint64, error) { + numGetLatestHeightCalls++ + toReturn := latest + uint64(numGetLatestHeightCalls) + return &toReturn, nil + }, + GetPayloadsSinceFunc: func(ctx context.Context, sinceHeight uint64, limit int) ([]types.PayloadInfo, error) { + numGetPayloadsCalls++ + switch numGetPayloadsCalls { + case 1: + if sinceHeight != 201 { + t.Fatal("unexpected sinceHeight", sinceHeight) + } + if limit != 20 { + t.Fatal("unexpected limit", limit) + } + case 2: + if sinceHeight != 221 { + t.Fatal("unexpected sinceHeight", sinceHeight) + } + if limit != 20 { + t.Fatal("unexpected limit", limit) + } + case 3: + if sinceHeight != 241 { + t.Fatal("unexpected sinceHeight", sinceHeight) + } + if limit != 13 { + t.Fatal("unexpected limit", limit) + } + default: + t.Fatal("unexpected numGetPayloadsCalls", numGetPayloadsCalls) + return nil, nil + } + toReturn := []types.PayloadInfo{} + for i := sinceHeight; i < sinceHeight+uint64(limit); i++ { + toReturn = append(toReturn, types.PayloadInfo{BlockHeight: i}) + } + return toReturn, nil + }, + } + syncBatchSize := uint64(20) + caughtUpThreshold := uint64(10) + st := follower.NewStore(logger, inmemstorage.New()) -// TODO: test syncFromSharedDB leading into steady state with sql.ErrNoRows returned a couple times. -// When it does come online.. test from block 1, simulating new chain. + follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, caughtUpThreshold, st) + if err != nil { + t.Fatal(err) + } + + st.SetLastProcessed(lastProcessed) + + errCh := make(chan error, 1) + + go func() { + errCh <- follower.SyncFromSharedDB(context.Background()) + }() + + payloadCh := follower.PayloadCh() + + // expect payloads up to 250+3 + received := 0 + expectedBlockHeight := uint64(201) + numErrSignals := 0 + for received < 53 { + select { + case err := <-errCh: + if err != nil { + t.Fatalf("follower failed, exiting: %v", err) + } + if numErrSignals > 1 { + t.Fatalf("SyncFromSharedDB should only signal nil error once") + } + numErrSignals++ + continue + case p := <-payloadCh: + if p == (types.PayloadInfo{}) { + t.Fatalf("received zero payload at %d", expectedBlockHeight) + } + if p.BlockHeight != expectedBlockHeight { + t.Fatalf("expected payload height %d, got %d", expectedBlockHeight, p.BlockHeight) + } + expectedBlockHeight++ + received++ + case <-time.After(10 * time.Second): + t.Fatalf("timeout waiting for payload %d", expectedBlockHeight) + } + } + if numErrSignals != 1 { + t.Fatalf("SyncFromSharedDB should signal nil error once, got %d", numErrSignals) + } + if received != 53 { + t.Fatalf("expected 53 payloads, got %d", received) + } -// TODO: test where channel becomes full and the sync thread blocks.. testing "// Non-blocking up to payloadBufferSize" + // No more than 53 + select { + case err := <-errCh: + if err != nil { + t.Fatalf("follower failed, exiting: %v", err) + } + case <-payloadCh: + t.Fatal("received unexpected payload") + case <-time.After(1 * time.Second): + } +} // TODO: test threshold + +// TODO: Simulate new chain with Start() func where sql.ErrNoRows is returned a couple times, then first row in DB has block 1. From 701825e227c00300754e14801e3d1b77f7f4660e Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Wed, 10 Sep 2025 00:07:03 -0700 Subject: [PATCH 09/34] Update follower_test.go --- cl/singlenode/follower/follower_test.go | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/cl/singlenode/follower/follower_test.go b/cl/singlenode/follower/follower_test.go index 888e15ccf..f56b33128 100644 --- a/cl/singlenode/follower/follower_test.go +++ b/cl/singlenode/follower/follower_test.go @@ -101,9 +101,6 @@ func TestFollower_syncFromSharedDB(t *testing.T) { t.Fatalf("timeout waiting for payload for expected block height %d", expectedBlockHeight) } } - if numErrSignals != 1 { - t.Fatalf("SyncFromSharedDB should signal nil error once, got %d", numErrSignals) - } if received != 50 { t.Fatalf("expected 50 payloads, got %d", received) } @@ -193,9 +190,6 @@ func TestFollower_syncFromSharedDB_NoRows(t *testing.T) { t.Fatalf("timeout waiting for payload %d", expectedBlockHeight) } } - if numErrSignals != 1 { - t.Fatalf("SyncFromSharedDB should signal nil error once, got %d", numErrSignals) - } if received != 15 { t.Fatalf("expected 15 payloads, got %d", received) } @@ -232,6 +226,7 @@ func TestFollower_syncFromSharedDB_MultipleIterations(t *testing.T) { numGetPayloadsCalls++ switch numGetPayloadsCalls { case 1: + // First iteration should request payloads from 201 to 220 if sinceHeight != 201 { t.Fatal("unexpected sinceHeight", sinceHeight) } @@ -239,6 +234,7 @@ func TestFollower_syncFromSharedDB_MultipleIterations(t *testing.T) { t.Fatal("unexpected limit", limit) } case 2: + // Second iteration should request payloads from 221 to 240 if sinceHeight != 221 { t.Fatal("unexpected sinceHeight", sinceHeight) } @@ -246,6 +242,7 @@ func TestFollower_syncFromSharedDB_MultipleIterations(t *testing.T) { t.Fatal("unexpected limit", limit) } case 3: + // Third iteration should request payloads from 241 to 253 if sinceHeight != 241 { t.Fatal("unexpected sinceHeight", sinceHeight) } @@ -282,7 +279,7 @@ func TestFollower_syncFromSharedDB_MultipleIterations(t *testing.T) { payloadCh := follower.PayloadCh() - // expect payloads up to 250+3 + // expect payloads up to 253 received := 0 expectedBlockHeight := uint64(201) numErrSignals := 0 @@ -310,9 +307,6 @@ func TestFollower_syncFromSharedDB_MultipleIterations(t *testing.T) { t.Fatalf("timeout waiting for payload %d", expectedBlockHeight) } } - if numErrSignals != 1 { - t.Fatalf("SyncFromSharedDB should signal nil error once, got %d", numErrSignals) - } if received != 53 { t.Fatalf("expected 53 payloads, got %d", received) } From 68ea80a78d105824b75d1fba08fb8102baef6078 Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Wed, 10 Sep 2025 15:24:40 -0700 Subject: [PATCH 10/34] TestFollower_queryPayloadsFromSharedDB --- cl/singlenode/follower/export.go | 8 ++++ cl/singlenode/follower/follower_test.go | 57 ++++++++++++++++++++++++- 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/cl/singlenode/follower/export.go b/cl/singlenode/follower/export.go index bf8bb9afa..367a1a638 100644 --- a/cl/singlenode/follower/export.go +++ b/cl/singlenode/follower/export.go @@ -17,3 +17,11 @@ func (f *Follower) SyncFromSharedDB(ctx context.Context) error { func (f *Follower) LastSignalledBlock() uint64 { return f.lastSignalledBlock } + +func (f *Follower) SetLastSignalledBlock(block uint64) { + f.lastSignalledBlock = block +} + +func (f *Follower) QueryPayloadsFromSharedDB(ctx context.Context) { + f.queryPayloadsFromSharedDB(ctx) +} diff --git a/cl/singlenode/follower/follower_test.go b/cl/singlenode/follower/follower_test.go index f56b33128..c5bf21ad4 100644 --- a/cl/singlenode/follower/follower_test.go +++ b/cl/singlenode/follower/follower_test.go @@ -323,6 +323,61 @@ func TestFollower_syncFromSharedDB_MultipleIterations(t *testing.T) { } } -// TODO: test threshold +func TestFollower_queryPayloadsFromSharedDB(t *testing.T) { + t.Parallel() + + lastSignalledBlock := uint64(100) + numCalls := 0 + payloadRepo := &mockPayloadDB{ + GetPayloadByHeightFunc: func(ctx context.Context, height uint64) (*types.PayloadInfo, error) { + numCalls++ + if numCalls > 50 { // Simulate catching up to latest block after 50 calls + if numCalls == 60 { + // 60th call returns a payload again + return &types.PayloadInfo{BlockHeight: lastSignalledBlock + 51}, nil + } + return nil, sql.ErrNoRows + } + return &types.PayloadInfo{BlockHeight: lastSignalledBlock + uint64(numCalls)}, nil + }, + } + + syncBatchSize := uint64(20) + caughtUpThreshold := uint64(10) + logger := util.NewTestLogger(io.Discard) + st := follower.NewStore(logger, inmemstorage.New()) + follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, caughtUpThreshold, st) + if err != nil { + t.Fatal(err) + } + + follower.SetLastSignalledBlock(lastSignalledBlock) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go func() { + follower.QueryPayloadsFromSharedDB(ctx) + }() + + // All 51 payloads should be received in little time + received := 0 + payloadCh := follower.PayloadCh() + deadline := time.Now().Add(time.Second) + for received < 51 { + select { + case p := <-payloadCh: + received++ + if p == (types.PayloadInfo{}) { + t.Fatalf("received zero payload at %d", p.BlockHeight) + } + if p.BlockHeight != lastSignalledBlock+uint64(received) { + t.Fatalf("expected payload height %d, got %d", lastSignalledBlock+uint64(received), p.BlockHeight) + } + case <-time.After(time.Until(deadline)): + t.Fatalf("timeout waiting for payload") + case <-ctx.Done(): + } + } +} // TODO: Simulate new chain with Start() func where sql.ErrNoRows is returned a couple times, then first row in DB has block 1. From 40c536124e0d59345b5a33155b6fffcf7d9db752 Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Wed, 10 Sep 2025 15:27:43 -0700 Subject: [PATCH 11/34] go mod sync --- cl/go.mod | 21 +++++++++----------- cl/go.sum | 58 +++---------------------------------------------------- 2 files changed, 12 insertions(+), 67 deletions(-) diff --git a/cl/go.mod b/cl/go.mod index 1f8c77bed..567531835 100644 --- a/cl/go.mod +++ b/cl/go.mod @@ -9,18 +9,19 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.1 github.com/golang/mock v1.6.0 github.com/lib/pq v1.10.9 + github.com/primev/mev-commit/p2p v0.0.1 github.com/redis/go-redis/v9 v9.6.1 github.com/urfave/cli/v2 v2.27.5 + golang.org/x/sync v0.11.0 golang.org/x/tools v0.29.0 ) require ( github.com/BurntSushi/toml v1.4.0 // indirect - github.com/DataDog/zstd v1.5.5 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/StackExchange/wmi v1.2.1 // indirect + github.com/armon/go-radix v1.0.0 // indirect github.com/bits-and-blooms/bitset v1.20.0 // indirect - github.com/cockroachdb/fifo v0.0.0-20240616162244-4768e80dfb9a // indirect github.com/consensys/bavard v0.1.27 // indirect github.com/consensys/gnark-crypto v0.16.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect @@ -30,7 +31,6 @@ require ( github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/ethereum/c-kzg-4844/v2 v2.1.0 // indirect github.com/ethereum/go-verkle v0.2.2 // indirect - github.com/getsentry/sentry-go v0.28.1 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/holiman/uint256 v1.3.2 // indirect @@ -40,9 +40,6 @@ require ( github.com/nxadm/tail v1.4.11 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/onsi/gomega v1.30.0 // indirect - github.com/pion/dtls/v2 v2.2.11 // indirect - github.com/pion/transport/v2 v2.2.5 // indirect - github.com/pion/transport/v3 v3.0.2 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect @@ -53,7 +50,6 @@ require ( github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect golang.org/x/mod v0.22.0 // indirect - golang.org/x/sync v0.11.0 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) @@ -64,19 +60,15 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/ethereum/go-ethereum v1.15.11 - github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/heyvito/go-leader v0.1.0 - github.com/klauspost/compress v1.17.9 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/primev/mev-commit/x v0.0.0-20241029202458-b151c03fa49e + github.com/primev/mev-commit/x v0.0.1 github.com/prometheus/client_golang v1.19.1 github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect - github.com/rs/cors v1.8.3 // indirect github.com/stretchr/testify v1.10.0 github.com/vmihailenco/msgpack/v5 v5.4.1 golang.org/x/crypto v0.35.0 // indirect @@ -84,3 +76,8 @@ require ( google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace ( + github.com/primev/mev-commit/p2p => ../p2p + github.com/primev/mev-commit/x => ../x +) diff --git a/cl/go.sum b/cl/go.sum index d406a3e90..bf5f267b4 100644 --- a/cl/go.sum +++ b/cl/go.sum @@ -8,6 +8,8 @@ github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDO github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3MdfoPyRVU= @@ -44,8 +46,6 @@ github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOV github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM= github.com/crate-crypto/go-kzg-4844 v1.1.0 h1:EN/u9k2TF6OWSHrCCDBBU6GLNMq88OspHHlMnHfoyU4= github.com/crate-crypto/go-kzg-4844 v1.1.0/go.mod h1:JolLjpSff1tCCJKaJx4psrlEdlXuJEC996PL3tTAFks= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= @@ -143,20 +143,17 @@ github.com/pion/dtls/v2 v2.2.11 h1:9U/dpCYl1ySttROPWJgqWKEylUdT0fXp/xst6JwY5Ks= github.com/pion/dtls/v2 v2.2.11/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4= github.com/pion/stun/v2 v2.0.0 h1:A5+wXKLAypxQri59+tmQKVs7+l6mMM+3d+eER9ifRU0= github.com/pion/stun/v2 v2.0.0/go.mod h1:22qRSh08fSEttYUmJZGlriq9+03jtVmXNODgLccj8GQ= -github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= github.com/pion/transport/v2 v2.2.5 h1:iyi25i/21gQck4hfRhomF6SktmUQjRsRW4WJdhfc3Kc= github.com/pion/transport/v2 v2.2.5/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= github.com/pion/transport/v3 v3.0.2 h1:r+40RJR25S9w3jbA6/5uEPTzcdn7ncyU44RWCbHkLg4= github.com/pion/transport/v3 v3.0.2/go.mod h1:nIToODoOlb5If2jF9y2Igfx3PFYWfuXi37m0IlWa/D0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/primev/mev-commit/x v0.0.0-20241029202458-b151c03fa49e h1:4UCC8PDllbWQNGuliOw3rxcmH79CUn6+xZhVGx3qZnQ= -github.com/primev/mev-commit/x v0.0.0-20241029202458-b151c03fa49e/go.mod h1:EEJMyKLa7ZLqTghwo1PlvHJPDW4MVl5WzBnhE6f5jDM= github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= @@ -178,15 +175,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf 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= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/supranational/blst v0.3.14 h1:xNMoHRJOTwMn63ip6qoWJ2Ymgvj7E2b9jY2FAwY+qRo= @@ -206,40 +196,22 @@ github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= 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.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -248,34 +220,13 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/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-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.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.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.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.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= -golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/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.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= @@ -283,8 +234,6 @@ golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -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/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -301,7 +250,6 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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= rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= From 587778501c226661d3c32ffa08007a978aa3ed7b Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Wed, 10 Sep 2025 15:32:39 -0700 Subject: [PATCH 12/34] Update follower_test.go --- cl/singlenode/follower/follower_test.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cl/singlenode/follower/follower_test.go b/cl/singlenode/follower/follower_test.go index c5bf21ad4..5d866e039 100644 --- a/cl/singlenode/follower/follower_test.go +++ b/cl/singlenode/follower/follower_test.go @@ -63,7 +63,10 @@ func TestFollower_syncFromSharedDB(t *testing.T) { t.Fatal(err) } - st.SetLastProcessed(lastProcessed) + err = st.SetLastProcessed(lastProcessed) + if err != nil { + t.Fatal(err) + } errCh := make(chan error, 1) @@ -269,7 +272,10 @@ func TestFollower_syncFromSharedDB_MultipleIterations(t *testing.T) { t.Fatal(err) } - st.SetLastProcessed(lastProcessed) + err = st.SetLastProcessed(lastProcessed) + if err != nil { + t.Fatal(err) + } errCh := make(chan error, 1) From 205c84de75b1a03044c85996e5ccad457580670e Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Wed, 10 Sep 2025 15:59:24 -0700 Subject: [PATCH 13/34] sleepRespectingContext --- cl/singlenode/follower/follower.go | 19 ++++--- cl/singlenode/follower/follower_test.go | 70 ++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 8 deletions(-) diff --git a/cl/singlenode/follower/follower.go b/cl/singlenode/follower/follower.go index cef33996c..9e597ad3d 100644 --- a/cl/singlenode/follower/follower.go +++ b/cl/singlenode/follower/follower.go @@ -173,35 +173,40 @@ func (f *Follower) queryPayloadsFromSharedDB(ctx context.Context) { if err != nil { if err == sql.ErrNoRows { - time.Sleep(time.Millisecond) // New payload will likely be available within milliseconds + f.sleepRespectingContext(ctx, time.Millisecond) // New payload will likely be available within milliseconds continue } f.logger.Error("Failed to fetch next payload by height with unexpected error", "height", f.lastSignalledBlock+1, "error", err) - time.Sleep(defaultBackoff) + f.sleepRespectingContext(ctx, defaultBackoff) continue } if payload == nil { f.logger.Error("Received nil payload from valid query") - time.Sleep(defaultBackoff) + f.sleepRespectingContext(ctx, defaultBackoff) continue } select { - case <-ctx.Done(): - return case f.payloadCh <- *payload: f.logger.Debug("Sent payload to channel", "height", f.lastSignalledBlock+1) f.lastSignalledBlock = payload.BlockHeight - default: f.logger.Error("Payload channel buffer is full", "height", f.lastSignalledBlock+1) - time.Sleep(defaultBackoff) + f.sleepRespectingContext(ctx, defaultBackoff) continue } } } +func (f *Follower) sleepRespectingContext(ctx context.Context, duration time.Duration) { + select { + case <-ctx.Done(): + return + case <-time.After(duration): + } +} + func (f *Follower) handlePayloads(ctx context.Context) { for { select { diff --git a/cl/singlenode/follower/follower_test.go b/cl/singlenode/follower/follower_test.go index 5d866e039..5b3e5a7bc 100644 --- a/cl/singlenode/follower/follower_test.go +++ b/cl/singlenode/follower/follower_test.go @@ -386,4 +386,72 @@ func TestFollower_queryPayloadsFromSharedDB(t *testing.T) { } } -// TODO: Simulate new chain with Start() func where sql.ErrNoRows is returned a couple times, then first row in DB has block 1. +func TestFollower_Start_SimulateNewChain(t *testing.T) { + t.Parallel() + + logger := util.NewTestLogger(io.Discard) + + getLatestCalls := 0 + getByHeightCalls := 0 + + payloadRepo := &mockPayloadDB{ + GetLatestHeightFunc: func(ctx context.Context) (*uint64, error) { + getLatestCalls++ + if getLatestCalls <= 3 { + return nil, sql.ErrNoRows + } + h := uint64(1) + return &h, nil + }, + GetPayloadsSinceFunc: func(ctx context.Context, sinceHeight uint64, limit int) ([]types.PayloadInfo, error) { + t.Fatalf("GetPayloadsSince should not be called, got sinceHeight=%d limit=%d", sinceHeight, limit) + return nil, nil + }, + GetPayloadByHeightFunc: func(ctx context.Context, height uint64) (*types.PayloadInfo, error) { + getByHeightCalls++ + if getByHeightCalls > 10 { + return nil, sql.ErrNoRows + } + return &types.PayloadInfo{BlockHeight: uint64(getByHeightCalls)}, nil + }, + } + + syncBatchSize := uint64(100) + caughtUpThreshold := uint64(5) + st := follower.NewStore(logger, inmemstorage.New()) + + f, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, caughtUpThreshold, st) + if err != nil { + t.Fatal(err) + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + done := f.Start(ctx) + + deadline := time.Now().Add(5 * time.Second) + for { + lp, err := st.GetLastProcessed() + if err != nil { + t.Fatal(err) + } + if lp >= 10 { + break + } + if time.Now().After(deadline) { + t.Fatalf("timeout waiting for first block to be processed") + } + time.Sleep(10 * time.Millisecond) + } + + if f.LastSignalledBlock() != 10 { + t.Fatalf("expected last signalled block to be 10, got %d", f.LastSignalledBlock()) + } + + cancel() + select { + case <-done: + case <-time.After(2 * time.Second): + t.Fatal("timeout waiting for follower to stop") + } +} From afa6e588b31e35d07ff1e424fc4110d8bbe70a3a Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Wed, 10 Sep 2025 16:00:44 -0700 Subject: [PATCH 14/34] fix TestFollower_Start_SimulateNewChain --- cl/singlenode/follower/follower.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cl/singlenode/follower/follower.go b/cl/singlenode/follower/follower.go index 9e597ad3d..45f272012 100644 --- a/cl/singlenode/follower/follower.go +++ b/cl/singlenode/follower/follower.go @@ -167,6 +167,12 @@ func (f *Follower) getLatestHeightWithBackoff(ctx context.Context) (uint64, erro func (f *Follower) queryPayloadsFromSharedDB(ctx context.Context) { for { + select { + case <-ctx.Done(): + return + default: + } + lctx, cancel := context.WithTimeout(ctx, 5*time.Second) payload, err := f.sharedDB.GetPayloadByHeight(lctx, f.lastSignalledBlock+1) cancel() From 4a3aeabd432f1522c6fa000ef6a770518c78c2d5 Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Wed, 10 Sep 2025 16:26:01 -0700 Subject: [PATCH 15/34] TestFollower_Start_SyncExistingChain --- cl/singlenode/follower/follower_test.go | 70 +++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/cl/singlenode/follower/follower_test.go b/cl/singlenode/follower/follower_test.go index 5b3e5a7bc..4e7819577 100644 --- a/cl/singlenode/follower/follower_test.go +++ b/cl/singlenode/follower/follower_test.go @@ -455,3 +455,73 @@ func TestFollower_Start_SimulateNewChain(t *testing.T) { t.Fatal("timeout waiting for follower to stop") } } + +func TestFollower_Start_SyncExistingChain(t *testing.T) { + t.Parallel() + + logger := util.NewTestLogger(io.Discard) + + lastProcessed := uint64(450) + latest := uint64(700) + + payloadRepo := &mockPayloadDB{ + GetLatestHeightFunc: func(ctx context.Context) (*uint64, error) { + return &latest, nil + }, + GetPayloadsSinceFunc: func(ctx context.Context, sinceHeight uint64, limit int) ([]types.PayloadInfo, error) { + toReturn := make([]types.PayloadInfo, 0, limit) + for i := uint64(0); i < uint64(limit); i++ { + toReturn = append(toReturn, types.PayloadInfo{BlockHeight: sinceHeight + i}) + } + return toReturn, nil + }, + GetPayloadByHeightFunc: func(ctx context.Context, height uint64) (*types.PayloadInfo, error) { + if height > latest { + return nil, sql.ErrNoRows + } + return &types.PayloadInfo{BlockHeight: height}, nil + }, + } + + syncBatchSize := uint64(20) + caughtUpThreshold := uint64(10) + st := follower.NewStore(logger, inmemstorage.New()) + + if err := st.SetLastProcessed(lastProcessed); err != nil { + t.Fatal(err) + } + + f, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, caughtUpThreshold, st) + if err != nil { + t.Fatal(err) + } + + ctx, cancel := context.WithCancel(context.Background()) + done := f.Start(ctx) + + deadline := time.Now().Add(5 * time.Second) + for { + lp, err := st.GetLastProcessed() + if err != nil { + t.Fatal(err) + } + if lp >= 700 { + break + } + if time.Now().After(deadline) { + t.Fatalf("timeout waiting for sync + steady-state; last processed: %d", lp) + } + time.Sleep(10 * time.Millisecond) + } + + if f.LastSignalledBlock() != 700 { + t.Fatalf("expected last signalled to be %d, got %d", 700, f.LastSignalledBlock()) + } + + cancel() + select { + case <-done: + case <-time.After(2 * time.Second): + t.Fatal("timeout waiting for follower to stop") + } +} From c44bd1c90af26b6d244b4133d43c26fd36311dd3 Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Wed, 10 Sep 2025 16:53:33 -0700 Subject: [PATCH 16/34] fix races --- cl/singlenode/follower/export.go | 12 ++++++-- cl/singlenode/follower/follower.go | 39 +++++++++++++++++-------- cl/singlenode/follower/follower_test.go | 28 +++++++++--------- 3 files changed, 51 insertions(+), 28 deletions(-) diff --git a/cl/singlenode/follower/export.go b/cl/singlenode/follower/export.go index 367a1a638..e2b408d4f 100644 --- a/cl/singlenode/follower/export.go +++ b/cl/singlenode/follower/export.go @@ -15,13 +15,21 @@ func (f *Follower) SyncFromSharedDB(ctx context.Context) error { } func (f *Follower) LastSignalledBlock() uint64 { - return f.lastSignalledBlock + return f.lastSignalledBlock.Load() } func (f *Follower) SetLastSignalledBlock(block uint64) { - f.lastSignalledBlock = block + f.lastSignalledBlock.Store(block) } func (f *Follower) QueryPayloadsFromSharedDB(ctx context.Context) { f.queryPayloadsFromSharedDB(ctx) } + +func (f *Follower) GetLastProcessed(ctx context.Context) (uint64, error) { + return f.getLastProcessed(ctx) +} + +func (f *Follower) SetLastProcessed(ctx context.Context, height uint64) error { + return f.setLastProcessed(ctx, height) +} diff --git a/cl/singlenode/follower/follower.go b/cl/singlenode/follower/follower.go index 45f272012..a0a7da482 100644 --- a/cl/singlenode/follower/follower.go +++ b/cl/singlenode/follower/follower.go @@ -5,6 +5,8 @@ import ( "database/sql" "errors" "log/slog" + "sync" + "sync/atomic" "time" "github.com/primev/mev-commit/cl/types" @@ -17,8 +19,9 @@ type Follower struct { syncBatchSize uint64 caughtUpThreshold uint64 store *Store + storeMu sync.RWMutex payloadCh chan types.PayloadInfo - lastSignalledBlock uint64 // Last block num signalled through payloadCh + lastSignalledBlock atomic.Uint64 // Last block num signalled through payloadCh } const ( @@ -94,13 +97,13 @@ func (f *Follower) Start(ctx context.Context) <-chan struct{} { func (f *Follower) syncFromSharedDB(ctx context.Context) error { var err error // lastSignalledBlock is only set from disk here - f.lastSignalledBlock, err = f.store.GetLastProcessed() + lastProcessed, err := f.getLastProcessed(ctx) if err != nil { return err } + f.lastSignalledBlock.Store(lastProcessed) for { - f.logger.Debug("Syncing from shared DB", "lastSignalledBlock", f.lastSignalledBlock) select { case <-ctx.Done(): return ctx.Err() @@ -112,7 +115,7 @@ func (f *Follower) syncFromSharedDB(ctx context.Context) error { return err } - blocksRemaining := targetBlock - f.lastSignalledBlock + blocksRemaining := targetBlock - f.lastSignalledBlock.Load() if blocksRemaining <= f.caughtUpThreshold { f.logger.Info("Sync complete") @@ -122,7 +125,7 @@ func (f *Follower) syncFromSharedDB(ctx context.Context) error { limit := min(f.syncBatchSize, blocksRemaining) innerCtx, innerCancel := context.WithTimeout(ctx, 10*time.Second) - payloads, err := f.sharedDB.GetPayloadsSince(innerCtx, f.lastSignalledBlock+1, int(limit)) + payloads, err := f.sharedDB.GetPayloadsSince(innerCtx, f.lastSignalledBlock.Load()+1, int(limit)) innerCancel() if err != nil { return err @@ -134,7 +137,7 @@ func (f *Follower) syncFromSharedDB(ctx context.Context) error { for i := range payloads { p := payloads[i] f.payloadCh <- p // Non-blocking up to payloadBufferSize - f.lastSignalledBlock = p.BlockHeight + f.lastSignalledBlock.Store(p.BlockHeight) } } } @@ -174,7 +177,7 @@ func (f *Follower) queryPayloadsFromSharedDB(ctx context.Context) { } lctx, cancel := context.WithTimeout(ctx, 5*time.Second) - payload, err := f.sharedDB.GetPayloadByHeight(lctx, f.lastSignalledBlock+1) + payload, err := f.sharedDB.GetPayloadByHeight(lctx, f.lastSignalledBlock.Load()+1) cancel() if err != nil { @@ -182,7 +185,7 @@ func (f *Follower) queryPayloadsFromSharedDB(ctx context.Context) { f.sleepRespectingContext(ctx, time.Millisecond) // New payload will likely be available within milliseconds continue } - f.logger.Error("Failed to fetch next payload by height with unexpected error", "height", f.lastSignalledBlock+1, "error", err) + f.logger.Error("Failed to fetch next payload by height with unexpected error", "height", f.lastSignalledBlock.Load()+1, "error", err) f.sleepRespectingContext(ctx, defaultBackoff) continue } @@ -195,10 +198,10 @@ func (f *Follower) queryPayloadsFromSharedDB(ctx context.Context) { select { case f.payloadCh <- *payload: - f.logger.Debug("Sent payload to channel", "height", f.lastSignalledBlock+1) - f.lastSignalledBlock = payload.BlockHeight + f.logger.Debug("Sent payload to channel", "height", payload.BlockHeight) + f.lastSignalledBlock.Store(payload.BlockHeight) default: - f.logger.Error("Payload channel buffer is full", "height", f.lastSignalledBlock+1) + f.logger.Error("Payload channel buffer is full", "height", payload.BlockHeight) f.sleepRespectingContext(ctx, defaultBackoff) continue } @@ -223,7 +226,7 @@ func (f *Follower) handlePayloads(ctx context.Context) { f.logger.Error("Failed to process payload", "height", p.BlockHeight, "error", err) continue } - if err := f.store.SetLastProcessed(p.BlockHeight); err != nil { + if err := f.setLastProcessed(ctx, p.BlockHeight); err != nil { f.logger.Error("Failed to persist last processed height", "height", p.BlockHeight, "error", err) continue } @@ -244,3 +247,15 @@ func (f *Follower) handlePayload(ctx context.Context, payload types.PayloadInfo) ) return nil } + +func (f *Follower) getLastProcessed(ctx context.Context) (uint64, error) { + f.storeMu.RLock() + defer f.storeMu.RUnlock() + return f.store.GetLastProcessed() +} + +func (f *Follower) setLastProcessed(ctx context.Context, height uint64) error { + f.storeMu.Lock() + defer f.storeMu.Unlock() + return f.store.SetLastProcessed(height) +} diff --git a/cl/singlenode/follower/follower_test.go b/cl/singlenode/follower/follower_test.go index 4e7819577..c404e4f1f 100644 --- a/cl/singlenode/follower/follower_test.go +++ b/cl/singlenode/follower/follower_test.go @@ -63,7 +63,7 @@ func TestFollower_syncFromSharedDB(t *testing.T) { t.Fatal(err) } - err = st.SetLastProcessed(lastProcessed) + err = follower.SetLastProcessed(context.Background(), lastProcessed) if err != nil { t.Fatal(err) } @@ -272,7 +272,7 @@ func TestFollower_syncFromSharedDB_MultipleIterations(t *testing.T) { t.Fatal(err) } - err = st.SetLastProcessed(lastProcessed) + err = follower.SetLastProcessed(context.Background(), lastProcessed) if err != nil { t.Fatal(err) } @@ -420,18 +420,18 @@ func TestFollower_Start_SimulateNewChain(t *testing.T) { caughtUpThreshold := uint64(5) st := follower.NewStore(logger, inmemstorage.New()) - f, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, caughtUpThreshold, st) + follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, caughtUpThreshold, st) if err != nil { t.Fatal(err) } ctx, cancel := context.WithCancel(context.Background()) defer cancel() - done := f.Start(ctx) + done := follower.Start(ctx) deadline := time.Now().Add(5 * time.Second) for { - lp, err := st.GetLastProcessed() + lp, err := follower.GetLastProcessed(context.Background()) if err != nil { t.Fatal(err) } @@ -444,8 +444,8 @@ func TestFollower_Start_SimulateNewChain(t *testing.T) { time.Sleep(10 * time.Millisecond) } - if f.LastSignalledBlock() != 10 { - t.Fatalf("expected last signalled block to be 10, got %d", f.LastSignalledBlock()) + if follower.LastSignalledBlock() != 10 { + t.Fatalf("expected last signalled block to be 10, got %d", follower.LastSignalledBlock()) } cancel() @@ -487,21 +487,21 @@ func TestFollower_Start_SyncExistingChain(t *testing.T) { caughtUpThreshold := uint64(10) st := follower.NewStore(logger, inmemstorage.New()) - if err := st.SetLastProcessed(lastProcessed); err != nil { + follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, caughtUpThreshold, st) + if err != nil { t.Fatal(err) } - f, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, caughtUpThreshold, st) - if err != nil { + if err := follower.SetLastProcessed(context.Background(), lastProcessed); err != nil { t.Fatal(err) } ctx, cancel := context.WithCancel(context.Background()) - done := f.Start(ctx) + done := follower.Start(ctx) deadline := time.Now().Add(5 * time.Second) for { - lp, err := st.GetLastProcessed() + lp, err := follower.GetLastProcessed(context.Background()) if err != nil { t.Fatal(err) } @@ -514,8 +514,8 @@ func TestFollower_Start_SyncExistingChain(t *testing.T) { time.Sleep(10 * time.Millisecond) } - if f.LastSignalledBlock() != 700 { - t.Fatalf("expected last signalled to be %d, got %d", 700, f.LastSignalledBlock()) + if follower.LastSignalledBlock() != 700 { + t.Fatalf("expected last signalled to be %d, got %d", 700, follower.LastSignalledBlock()) } cancel() From 564ae7696872d6b973bf3f7559c7871a5e2042db Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Wed, 10 Sep 2025 18:04:01 -0700 Subject: [PATCH 17/34] init engine api --- cl/singlenode/follower/follower.go | 32 ++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/cl/singlenode/follower/follower.go b/cl/singlenode/follower/follower.go index a0a7da482..bf78fb2ed 100644 --- a/cl/singlenode/follower/follower.go +++ b/cl/singlenode/follower/follower.go @@ -3,12 +3,16 @@ package follower import ( "context" "database/sql" + "encoding/hex" "errors" + "fmt" "log/slog" "sync" "sync/atomic" "time" + "github.com/primev/mev-commit/cl/blockbuilder" + "github.com/primev/mev-commit/cl/ethclient" "github.com/primev/mev-commit/cl/types" "golang.org/x/sync/errgroup" ) @@ -22,6 +26,7 @@ type Follower struct { storeMu sync.RWMutex payloadCh chan types.PayloadInfo lastSignalledBlock atomic.Uint64 // Last block num signalled through payloadCh + bb *blockbuilder.BlockBuilder } const ( @@ -64,6 +69,19 @@ func NewFollower( }, nil } +func (f *Follower) InitEngineAPI(ctx context.Context, ethAuthClientURL string, jwtSecret string) error { + jwtBytes, err := hex.DecodeString(jwtSecret) + if err != nil { + return fmt.Errorf("failed to decode JWT secret: %w", err) + } + engineClient, err := ethclient.NewAuthClient(ctx, ethAuthClientURL, jwtBytes) + if err != nil { + return fmt.Errorf("failed to create Ethereum engine client: %w", err) + } + f.bb = blockbuilder.NewMemberBlockBuilder(engineClient, f.logger.With("component", "BlockBuilder")) + return nil +} + func (f *Follower) Start(ctx context.Context) <-chan struct{} { done := make(chan struct{}) @@ -234,17 +252,11 @@ func (f *Follower) handlePayloads(ctx context.Context) { } } -// TODO: confirm nothing would be broken if the service crashes mid processing of a payload. -// How does the node recover from this w.r.t engine api? Might need to save FSM state in additon to block number in kv store. - -// TODO: Or w.r.t above could the engine api just handle 1-2 duplicate calls and gracefully continue? Need to confirm. - func (f *Follower) handlePayload(ctx context.Context, payload types.PayloadInfo) error { - // TODO: Apply the payload to follower's EL via Engine API in later steps. - f.logger.Info("Processing payload", - "payload_id", payload.PayloadID, - "block_height", payload.BlockHeight, - ) + if f.bb != nil { + return f.bb.FinalizeBlock(ctx, payload.PayloadID, payload.ExecutionPayload, "") + } + f.logger.Warn("Engine API has not been initialized, payload will not be applied") return nil } From 39d1b6cb9015efb1912fecd5b8385d64e5143d5f Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Wed, 10 Sep 2025 18:21:13 -0700 Subject: [PATCH 18/34] integrate block builder --- cl/blockbuilder/blockbuilder.go | 4 +-- cl/singlenode/follower/follower.go | 35 ++++++++------------ cl/singlenode/follower/follower_test.go | 44 +++++++++++++++++++++---- 3 files changed, 54 insertions(+), 29 deletions(-) diff --git a/cl/blockbuilder/blockbuilder.go b/cl/blockbuilder/blockbuilder.go index 78378287a..87a0766b7 100644 --- a/cl/blockbuilder/blockbuilder.go +++ b/cl/blockbuilder/blockbuilder.go @@ -121,7 +121,7 @@ func (bb *BlockBuilder) GetPayload(ctx context.Context) error { if bb.executionHead == nil { bb.logger.Info("executionHead is nil, it'll be set by RPC. CL is likely being restarted") err = util.RetryWithBackoff(ctx, maxAttempts, bb.logger, func() error { - innerErr := bb.setExecutionHeadFromRPC(ctx) + innerErr := bb.SetExecutionHeadFromRPC(ctx) if innerErr != nil { bb.logger.Warn( "Failed to set execution head from rpc, retrying...", @@ -502,7 +502,7 @@ func (bb *BlockBuilder) updateForkChoice(ctx context.Context, fcs engine.Forkcho }) } -func (bb *BlockBuilder) setExecutionHeadFromRPC(ctx context.Context) error { +func (bb *BlockBuilder) SetExecutionHeadFromRPC(ctx context.Context) error { header, err := bb.engineCl.HeaderByNumber(ctx, nil) // nil for the latest block if err != nil { return fmt.Errorf("failed to get the latest block header: %w", err) diff --git a/cl/singlenode/follower/follower.go b/cl/singlenode/follower/follower.go index bf78fb2ed..6f8462097 100644 --- a/cl/singlenode/follower/follower.go +++ b/cl/singlenode/follower/follower.go @@ -3,7 +3,6 @@ package follower import ( "context" "database/sql" - "encoding/hex" "errors" "fmt" "log/slog" @@ -11,8 +10,6 @@ import ( "sync/atomic" "time" - "github.com/primev/mev-commit/cl/blockbuilder" - "github.com/primev/mev-commit/cl/ethclient" "github.com/primev/mev-commit/cl/types" "golang.org/x/sync/errgroup" ) @@ -26,7 +23,7 @@ type Follower struct { storeMu sync.RWMutex payloadCh chan types.PayloadInfo lastSignalledBlock atomic.Uint64 // Last block num signalled through payloadCh - bb *blockbuilder.BlockBuilder + bb blockBuilder } const ( @@ -40,12 +37,19 @@ type payloadDB interface { GetLatestHeight(ctx context.Context) (*uint64, error) } +type blockBuilder interface { + GetExecutionHead() *types.ExecutionHead + FinalizeBlock(ctx context.Context, payloadIDStr, executionPayloadStr, msgID string) error + SetExecutionHeadFromRPC(ctx context.Context) error +} + func NewFollower( logger *slog.Logger, sharedDB payloadDB, syncBatchSize uint64, caughtUpThreshold uint64, store *Store, + bb blockBuilder, ) (*Follower, error) { if sharedDB == nil { return nil, errors.New("payload repository not provided") @@ -66,22 +70,10 @@ func NewFollower( caughtUpThreshold: caughtUpThreshold, store: store, payloadCh: make(chan types.PayloadInfo, payloadBufferSize), + bb: bb, }, nil } -func (f *Follower) InitEngineAPI(ctx context.Context, ethAuthClientURL string, jwtSecret string) error { - jwtBytes, err := hex.DecodeString(jwtSecret) - if err != nil { - return fmt.Errorf("failed to decode JWT secret: %w", err) - } - engineClient, err := ethclient.NewAuthClient(ctx, ethAuthClientURL, jwtBytes) - if err != nil { - return fmt.Errorf("failed to create Ethereum engine client: %w", err) - } - f.bb = blockbuilder.NewMemberBlockBuilder(engineClient, f.logger.With("component", "BlockBuilder")) - return nil -} - func (f *Follower) Start(ctx context.Context) <-chan struct{} { done := make(chan struct{}) @@ -253,11 +245,12 @@ func (f *Follower) handlePayloads(ctx context.Context) { } func (f *Follower) handlePayload(ctx context.Context, payload types.PayloadInfo) error { - if f.bb != nil { - return f.bb.FinalizeBlock(ctx, payload.PayloadID, payload.ExecutionPayload, "") + if f.bb.GetExecutionHead() == nil { + if err := f.bb.SetExecutionHeadFromRPC(ctx); err != nil { + return fmt.Errorf("failed to set execution head from rpc: %w", err) + } } - f.logger.Warn("Engine API has not been initialized, payload will not be applied") - return nil + return f.bb.FinalizeBlock(ctx, payload.PayloadID, payload.ExecutionPayload, "") } func (f *Follower) getLastProcessed(ctx context.Context) (uint64, error) { diff --git a/cl/singlenode/follower/follower_test.go b/cl/singlenode/follower/follower_test.go index c404e4f1f..9bd9536a0 100644 --- a/cl/singlenode/follower/follower_test.go +++ b/cl/singlenode/follower/follower_test.go @@ -32,6 +32,38 @@ func (m *mockPayloadDB) GetPayloadByHeight(ctx context.Context, height uint64) ( return m.GetPayloadByHeightFunc(ctx, height) } +type mockBlockBuilder struct { + GetExecutionHeadFunc func() *types.ExecutionHead + SetExecutionHeadFromRPCFunc func(ctx context.Context) error + FinalizeBlockFunc func(ctx context.Context, payloadIDStr, executionPayloadStr, msgID string) error +} + +func (m *mockBlockBuilder) GetExecutionHead() *types.ExecutionHead { + return m.GetExecutionHeadFunc() +} + +func (m *mockBlockBuilder) SetExecutionHeadFromRPC(ctx context.Context) error { + return m.SetExecutionHeadFromRPCFunc(ctx) +} + +func (m *mockBlockBuilder) FinalizeBlock(ctx context.Context, payloadIDStr, executionPayloadStr, msgID string) error { + return m.FinalizeBlockFunc(ctx, payloadIDStr, executionPayloadStr, msgID) +} + +func newNoopBlockBuilder() *mockBlockBuilder { + return &mockBlockBuilder{ + GetExecutionHeadFunc: func() *types.ExecutionHead { + return nil + }, + SetExecutionHeadFromRPCFunc: func(ctx context.Context) error { + return nil + }, + FinalizeBlockFunc: func(ctx context.Context, payloadIDStr, executionPayloadStr, msgID string) error { + return nil + }, + } +} + func TestFollower_syncFromSharedDB(t *testing.T) { t.Parallel() @@ -58,7 +90,7 @@ func TestFollower_syncFromSharedDB(t *testing.T) { caughtUpThreshold := uint64(5) st := follower.NewStore(logger, inmemstorage.New()) - follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, caughtUpThreshold, st) + follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, caughtUpThreshold, st, newNoopBlockBuilder()) if err != nil { t.Fatal(err) } @@ -152,7 +184,7 @@ func TestFollower_syncFromSharedDB_NoRows(t *testing.T) { caughtUpThreshold := uint64(5) st := follower.NewStore(logger, inmemstorage.New()) - follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, caughtUpThreshold, st) + follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, caughtUpThreshold, st, newNoopBlockBuilder()) if err != nil { t.Fatal(err) } @@ -267,7 +299,7 @@ func TestFollower_syncFromSharedDB_MultipleIterations(t *testing.T) { caughtUpThreshold := uint64(10) st := follower.NewStore(logger, inmemstorage.New()) - follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, caughtUpThreshold, st) + follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, caughtUpThreshold, st, newNoopBlockBuilder()) if err != nil { t.Fatal(err) } @@ -352,7 +384,7 @@ func TestFollower_queryPayloadsFromSharedDB(t *testing.T) { caughtUpThreshold := uint64(10) logger := util.NewTestLogger(io.Discard) st := follower.NewStore(logger, inmemstorage.New()) - follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, caughtUpThreshold, st) + follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, caughtUpThreshold, st, newNoopBlockBuilder()) if err != nil { t.Fatal(err) } @@ -420,7 +452,7 @@ func TestFollower_Start_SimulateNewChain(t *testing.T) { caughtUpThreshold := uint64(5) st := follower.NewStore(logger, inmemstorage.New()) - follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, caughtUpThreshold, st) + follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, caughtUpThreshold, st, newNoopBlockBuilder()) if err != nil { t.Fatal(err) } @@ -487,7 +519,7 @@ func TestFollower_Start_SyncExistingChain(t *testing.T) { caughtUpThreshold := uint64(10) st := follower.NewStore(logger, inmemstorage.New()) - follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, caughtUpThreshold, st) + follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, caughtUpThreshold, st, newNoopBlockBuilder()) if err != nil { t.Fatal(err) } From 71333e2a7f37cdc4aa56e75e11555c1ecbdc02a1 Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Wed, 10 Sep 2025 18:50:32 -0700 Subject: [PATCH 19/34] Update main.go --- cl/cmd/singlenode/main.go | 144 +++++++++++++++++++++++--------------- 1 file changed, 89 insertions(+), 55 deletions(-) diff --git a/cl/cmd/singlenode/main.go b/cl/cmd/singlenode/main.go index 3d4e1b306..01450a3ec 100644 --- a/cl/cmd/singlenode/main.go +++ b/cl/cmd/singlenode/main.go @@ -13,8 +13,12 @@ import ( "syscall" "time" + "github.com/primev/mev-commit/cl/blockbuilder" + "github.com/primev/mev-commit/cl/ethclient" "github.com/primev/mev-commit/cl/singlenode" - "github.com/primev/mev-commit/cl/singlenode/membernode" + "github.com/primev/mev-commit/cl/singlenode/follower" + "github.com/primev/mev-commit/cl/singlenode/payloadstore" + pebblestorage "github.com/primev/mev-commit/p2p/pkg/storage/pebble" "github.com/primev/mev-commit/x/util" "github.com/urfave/cli/v2" "github.com/urfave/cli/v2/altsrc" @@ -211,29 +215,24 @@ var ( Value: 5 * time.Millisecond, }) - // Member node specific flags - leaderAPIURLFlag = altsrc.NewStringFlag(&cli.StringFlag{ - Name: "leader-api-url", - Usage: "Leader node API URL for member nodes (e.g., 'http://leader:9090')", - EnvVars: []string{"MEMBER_LEADER_API_URL"}, - Category: categoryMember, - Action: func(_ *cli.Context, s string) error { - if s == "" { - return nil // Will be validated in member command - } - if _, err := url.Parse(s); err != nil { - return fmt.Errorf("invalid leader-api-url: %v", err) - } - return nil - }, + // Follower node specific flags + followerDataDirFlag = altsrc.NewStringFlag(&cli.StringFlag{ + Name: "data-dir", + Usage: "Directory for follower node pebble database", + EnvVars: []string{"FOLLOWER_DATA_DIR"}, + Value: "", }) - - pollIntervalFlag = altsrc.NewDurationFlag(&cli.DurationFlag{ - Name: "poll-interval", - Usage: "Interval for polling leader node for new payloads (e.g., '1s')", - EnvVars: []string{"MEMBER_POLL_INTERVAL"}, - Value: 1 * time.Second, - Category: categoryMember, + syncBatchSizeFlag = altsrc.NewUint64Flag(&cli.Uint64Flag{ + Name: "sync-batch-size", + Usage: "Number of payloads per request to the EL during sync", + EnvVars: []string{"FOLLOWER_SYNC_BATCH_SIZE"}, + Value: 100, + }) + caughtUpThresholdFlag = altsrc.NewUint64Flag(&cli.Uint64Flag{ + Name: "caught-up-threshold", + Usage: "Syncing is complete when the difference between the latest block and the last processed block is less than this threshold", + EnvVars: []string{"FOLLOWER_CAUGHT_UP_THRESHOLD"}, + Value: 10, }) ) @@ -256,7 +255,7 @@ func main() { txPoolPollingIntervalFlag, } - memberFlags := []cli.Flag{ + followerFlags := []cli.Flag{ configFlag, instanceIDFlag, ethClientURLFlag, @@ -265,8 +264,10 @@ func main() { logLevelFlag, logTagsFlag, healthAddrPortFlag, - leaderAPIURLFlag, - pollIntervalFlag, + postgresDSNFlag, + followerDataDirFlag, + syncBatchSizeFlag, + caughtUpThresholdFlag, } app := &cli.App{ @@ -290,10 +291,10 @@ func main() { }, }, { - Name: "member", + Name: "follower", Usage: "Start as member node (follows leader)", - Flags: memberFlags, - Before: altsrc.InitInputSourceWithContext(memberFlags, + Flags: followerFlags, + Before: altsrc.InitInputSourceWithContext(followerFlags, func(c *cli.Context) (altsrc.InputSourceContext, error) { configFile := c.String(configFlag.Name) if configFile != "" { @@ -302,7 +303,7 @@ func main() { return &altsrc.MapInputSource{}, nil }), Action: func(c *cli.Context) error { - return startMemberNode(c) + return startFollowerNode(c) }, }, // Keep the old "start" command for backward compatibility @@ -380,12 +381,7 @@ func startLeaderNode(c *cli.Context) error { return nil } -func startMemberNode(c *cli.Context) error { - leaderURL := c.String(leaderAPIURLFlag.Name) - if leaderURL == "" { - return fmt.Errorf("leader-api-url is required for member nodes") - } - +func startFollowerNode(c *cli.Context) error { logger, err := util.NewLogger( c.String(logLevelFlag.Name), c.String(logFmtFlag.Name), @@ -395,36 +391,74 @@ func startMemberNode(c *cli.Context) error { if err != nil { return fmt.Errorf("failed to create logger: %w", err) } - logger = logger.With("app", "snode", "role", "member") - - cfg := membernode.Config{ - InstanceID: c.String(instanceIDFlag.Name), - LeaderAPIURL: leaderURL, - EthClientURL: c.String(ethClientURLFlag.Name), - JWTSecret: c.String(jwtSecretFlag.Name), - HealthAddr: c.String(healthAddrPortFlag.Name), - PollInterval: c.Duration(pollIntervalFlag.Name), - } + logger = logger.With("app", "snode", "role", "follower") - logger.Info("Starting member node", "config", cfg) + logger.Info("Starting follower node") - // Create a root context that can be cancelled for graceful shutdown rootCtx, rootCancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer rootCancel() - memberNode, err := membernode.NewMemberNodeApp(rootCtx, cfg, logger) + postgresDSN := c.String(postgresDSNFlag.Name) + if postgresDSN == "" { + return fmt.Errorf("postgresDSN is required") + } + repo, err := payloadstore.NewPostgresRepository(rootCtx, postgresDSN, logger) if err != nil { - logger.Error("Failed to initialize MemberNodeApp", "error", err) + return fmt.Errorf("failed to initialize payload repository: %w", err) + } + syncBatchSize := c.Uint64(syncBatchSizeFlag.Name) + if syncBatchSize == 0 { + return fmt.Errorf("sync-batch-size is required") + } + caughtUpThreshold := c.Uint64(caughtUpThresholdFlag.Name) + if caughtUpThreshold == 0 { + return fmt.Errorf("caught-up-threshold is required") + } + dataDir := c.String(followerDataDirFlag.Name) + if dataDir == "" { + return fmt.Errorf("data-dir is required") + } + pebbleStore, err := pebblestorage.New(dataDir) + if err != nil { + return fmt.Errorf("failed to create storage: %w", err) + } + store := follower.NewStore(logger, pebbleStore) + + ethClientURL := c.String(ethClientURLFlag.Name) + if ethClientURL == "" { + return fmt.Errorf("eth-client-url is required") + } + jwtSecret := c.String(jwtSecretFlag.Name) + if jwtSecret == "" { + return fmt.Errorf("jwt-secret is required") + } + jwtBytes, err := hex.DecodeString(jwtSecret) + if err != nil { + return fmt.Errorf("failed to decode JWT secret: %w", err) + } + engineCL, err := ethclient.NewAuthClient(rootCtx, ethClientURL, jwtBytes) + if err != nil { + return fmt.Errorf("failed to create Ethereum engine client: %w", err) + } + bb := blockbuilder.NewMemberBlockBuilder(engineCL, logger) + + followerNode, err := follower.NewFollower( + logger, + repo, + 100, // syncBatchSize + 10, // caughtUpThreshold + store, + bb, + ) + if err != nil { + logger.Error("Failed to initialize Follower", "error", err) return err } - memberNode.Start() + followerNode.Start(rootCtx) <-rootCtx.Done() - logger.Info("Shutdown signal received, stopping member node...") - memberNode.Stop() - - logger.Info("Member node shutdown completed.") + logger.Info("Follower node shutdown completed.") return nil } From 3d50c899b8132adb50149533d796a0b17b45e11d Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Wed, 10 Sep 2025 18:56:58 -0700 Subject: [PATCH 20/34] go mod tidy --- cl/go.mod | 17 ++++++++++++++++- cl/go.sum | 26 ++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/cl/go.mod b/cl/go.mod index 567531835..cd1422f96 100644 --- a/cl/go.mod +++ b/cl/go.mod @@ -18,10 +18,17 @@ require ( require ( github.com/BurntSushi/toml v1.4.0 // indirect + github.com/DataDog/zstd v1.5.5 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/StackExchange/wmi v1.2.1 // indirect github.com/armon/go-radix v1.0.0 // indirect github.com/bits-and-blooms/bitset v1.20.0 // indirect + github.com/cockroachdb/errors v1.11.3 // indirect + github.com/cockroachdb/fifo v0.0.0-20240616162244-4768e80dfb9a // indirect + github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect + github.com/cockroachdb/pebble v1.1.2 // indirect + github.com/cockroachdb/redact v1.1.5 // indirect + github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect github.com/consensys/bavard v0.1.27 // indirect github.com/consensys/gnark-crypto v0.16.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect @@ -31,16 +38,22 @@ require ( github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/ethereum/c-kzg-4844/v2 v2.1.0 // indirect github.com/ethereum/go-verkle v0.2.2 // indirect + github.com/getsentry/sentry-go v0.28.1 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/gofrs/flock v0.8.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect github.com/holiman/uint256 v1.3.2 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/nxadm/tail v1.4.11 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/onsi/gomega v1.30.0 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect github.com/stretchr/objx v0.5.2 // indirect @@ -49,7 +62,9 @@ require ( github.com/tklauser/numcpus v0.7.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/mod v0.22.0 // indirect + golang.org/x/text v0.22.0 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/cl/go.sum b/cl/go.sum index bf5f267b4..ef772ef8b 100644 --- a/cl/go.sum +++ b/cl/go.sum @@ -22,6 +22,8 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= github.com/cockroachdb/fifo v0.0.0-20240616162244-4768e80dfb9a h1:f52TdbU4D5nozMAhO9TvTJ2ZMCXtN4VIAmfrrZ0JXQ4= @@ -46,6 +48,7 @@ github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOV github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM= github.com/crate-crypto/go-kzg-4844 v1.1.0 h1:EN/u9k2TF6OWSHrCCDBBU6GLNMq88OspHHlMnHfoyU4= github.com/crate-crypto/go-kzg-4844 v1.1.0/go.mod h1:JolLjpSff1tCCJKaJx4psrlEdlXuJEC996PL3tTAFks= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= @@ -62,11 +65,12 @@ github.com/ethereum/go-ethereum v1.15.11 h1:JK73WKeu0WC0O1eyX+mdQAVHUV+UR1a9VB/d github.com/ethereum/go-ethereum v1.15.11/go.mod h1:mf8YiHIb0GR4x4TipcvBUPxJLw1mFdmxzoDi11sDRoI= github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/getsentry/sentry-go v0.28.1 h1:zzaSm/vHmGllRM6Tpx1492r0YDzauArdBfkJRtY6P5k= github.com/getsentry/sentry-go v0.28.1/go.mod h1:1fQZ+7l7eeJ3wYi82q5Hg8GqAPgefRq+FP/QhafYVgg= +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-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= @@ -103,6 +107,8 @@ github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -139,6 +145,8 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pion/dtls/v2 v2.2.11 h1:9U/dpCYl1ySttROPWJgqWKEylUdT0fXp/xst6JwY5Ks= github.com/pion/dtls/v2 v2.2.11/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= @@ -150,6 +158,7 @@ github.com/pion/transport/v2 v2.2.5 h1:iyi25i/21gQck4hfRhomF6SktmUQjRsRW4WJdhfc3 github.com/pion/transport/v2 v2.2.5/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= github.com/pion/transport/v3 v3.0.2 h1:r+40RJR25S9w3jbA6/5uEPTzcdn7ncyU44RWCbHkLg4= github.com/pion/transport/v3 v3.0.2/go.mod h1:nIToODoOlb5If2jF9y2Igfx3PFYWfuXi37m0IlWa/D0= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= @@ -167,6 +176,7 @@ github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/cors v1.8.3 h1:O+qNyWn7Z+F9M0ILBHgMVPuB1xTOucVd5gtaYyXBpRo= @@ -195,32 +205,41 @@ github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAh github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +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.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +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.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 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-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -233,11 +252,14 @@ golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= 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= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= From f3ad52e9f9586f93ec012007396452400dabed5a Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Thu, 11 Sep 2025 13:21:03 -0700 Subject: [PATCH 21/34] single function for reading db --- cl/cmd/singlenode/main.go | 12 -- cl/singlenode/follower/export.go | 8 +- cl/singlenode/follower/follower.go | 101 ++++---------- cl/singlenode/follower/follower_test.go | 172 ++++-------------------- 4 files changed, 59 insertions(+), 234 deletions(-) diff --git a/cl/cmd/singlenode/main.go b/cl/cmd/singlenode/main.go index 01450a3ec..7a236b5dc 100644 --- a/cl/cmd/singlenode/main.go +++ b/cl/cmd/singlenode/main.go @@ -228,12 +228,6 @@ var ( EnvVars: []string{"FOLLOWER_SYNC_BATCH_SIZE"}, Value: 100, }) - caughtUpThresholdFlag = altsrc.NewUint64Flag(&cli.Uint64Flag{ - Name: "caught-up-threshold", - Usage: "Syncing is complete when the difference between the latest block and the last processed block is less than this threshold", - EnvVars: []string{"FOLLOWER_CAUGHT_UP_THRESHOLD"}, - Value: 10, - }) ) func main() { @@ -267,7 +261,6 @@ func main() { postgresDSNFlag, followerDataDirFlag, syncBatchSizeFlag, - caughtUpThresholdFlag, } app := &cli.App{ @@ -410,10 +403,6 @@ func startFollowerNode(c *cli.Context) error { if syncBatchSize == 0 { return fmt.Errorf("sync-batch-size is required") } - caughtUpThreshold := c.Uint64(caughtUpThresholdFlag.Name) - if caughtUpThreshold == 0 { - return fmt.Errorf("caught-up-threshold is required") - } dataDir := c.String(followerDataDirFlag.Name) if dataDir == "" { return fmt.Errorf("data-dir is required") @@ -446,7 +435,6 @@ func startFollowerNode(c *cli.Context) error { logger, repo, 100, // syncBatchSize - 10, // caughtUpThreshold store, bb, ) diff --git a/cl/singlenode/follower/export.go b/cl/singlenode/follower/export.go index e2b408d4f..deb8d6a28 100644 --- a/cl/singlenode/follower/export.go +++ b/cl/singlenode/follower/export.go @@ -10,8 +10,8 @@ func (f *Follower) PayloadCh() <-chan types.PayloadInfo { return f.payloadCh } -func (f *Follower) SyncFromSharedDB(ctx context.Context) error { - return f.syncFromSharedDB(ctx) +func (f *Follower) SyncFromSharedDB(ctx context.Context) { + f.syncFromSharedDB(ctx) } func (f *Follower) LastSignalledBlock() uint64 { @@ -22,10 +22,6 @@ func (f *Follower) SetLastSignalledBlock(block uint64) { f.lastSignalledBlock.Store(block) } -func (f *Follower) QueryPayloadsFromSharedDB(ctx context.Context) { - f.queryPayloadsFromSharedDB(ctx) -} - func (f *Follower) GetLastProcessed(ctx context.Context) (uint64, error) { return f.getLastProcessed(ctx) } diff --git a/cl/singlenode/follower/follower.go b/cl/singlenode/follower/follower.go index 6f8462097..e3978f409 100644 --- a/cl/singlenode/follower/follower.go +++ b/cl/singlenode/follower/follower.go @@ -18,7 +18,6 @@ type Follower struct { logger *slog.Logger sharedDB payloadDB syncBatchSize uint64 - caughtUpThreshold uint64 store *Store storeMu sync.RWMutex payloadCh chan types.PayloadInfo @@ -33,7 +32,6 @@ const ( type payloadDB interface { GetPayloadsSince(ctx context.Context, sinceHeight uint64, limit int) ([]types.PayloadInfo, error) - GetPayloadByHeight(ctx context.Context, height uint64) (*types.PayloadInfo, error) GetLatestHeight(ctx context.Context) (*uint64, error) } @@ -47,7 +45,6 @@ func NewFollower( logger *slog.Logger, sharedDB payloadDB, syncBatchSize uint64, - caughtUpThreshold uint64, store *Store, bb blockBuilder, ) (*Follower, error) { @@ -57,20 +54,13 @@ func NewFollower( if syncBatchSize == 0 { return nil, errors.New("sync batch size must be greater than 0") } - if caughtUpThreshold == 0 { - return nil, errors.New("caught up threshold must be greater than 0") - } - if caughtUpThreshold > syncBatchSize { - return nil, errors.New("caught up threshold must be less than sync batch size") - } return &Follower{ - logger: logger, - sharedDB: sharedDB, - syncBatchSize: syncBatchSize, - caughtUpThreshold: caughtUpThreshold, - store: store, - payloadCh: make(chan types.PayloadInfo, payloadBufferSize), - bb: bb, + logger: logger, + sharedDB: sharedDB, + syncBatchSize: syncBatchSize, + store: store, + payloadCh: make(chan types.PayloadInfo, payloadBufferSize), + bb: bb, }, nil } @@ -84,13 +74,8 @@ func (f *Follower) Start(ctx context.Context) <-chan struct{} { }) eg.Go(func() error { - f.logger.Info("Starting initial sync from shared DB") - if err := f.syncFromSharedDB(egCtx); err != nil { - f.logger.Error("Failed during initial sync", "error", err) - return err - } - f.logger.Info("Entering steady-state querying of shared DB") - f.queryPayloadsFromSharedDB(egCtx) + f.logger.Info("Starting sync from shared DB") + f.syncFromSharedDB(egCtx) return nil }) @@ -104,32 +89,34 @@ func (f *Follower) Start(ctx context.Context) <-chan struct{} { return done } -func (f *Follower) syncFromSharedDB(ctx context.Context) error { +func (f *Follower) syncFromSharedDB(ctx context.Context) { var err error // lastSignalledBlock is only set from disk here lastProcessed, err := f.getLastProcessed(ctx) if err != nil { - return err + f.logger.Error("failed to get last processed block", "error", err) + return } f.lastSignalledBlock.Store(lastProcessed) for { select { case <-ctx.Done(): - return ctx.Err() + return default: } targetBlock, err := f.getLatestHeightWithBackoff(ctx) if err != nil { - return err + f.sleepRespectingContext(ctx, defaultBackoff) + continue } blocksRemaining := targetBlock - f.lastSignalledBlock.Load() - if blocksRemaining <= f.caughtUpThreshold { - f.logger.Info("Sync complete") - return nil + if blocksRemaining == 0 { + f.sleepRespectingContext(ctx, time.Millisecond) // New payload will likely be available within milliseconds + continue } limit := min(f.syncBatchSize, blocksRemaining) @@ -138,15 +125,23 @@ func (f *Follower) syncFromSharedDB(ctx context.Context) error { payloads, err := f.sharedDB.GetPayloadsSince(innerCtx, f.lastSignalledBlock.Load()+1, int(limit)) innerCancel() if err != nil { - return err + f.logger.Error("failed to get payloads since", "error", err) + f.sleepRespectingContext(ctx, defaultBackoff) + continue } if len(payloads) == 0 { - return errors.New("no payloads returned from valid query") + f.logger.Error("no payloads returned from valid query") + f.sleepRespectingContext(ctx, defaultBackoff) + continue } for i := range payloads { p := payloads[i] - f.payloadCh <- p // Non-blocking up to payloadBufferSize + select { + case <-ctx.Done(): + return + case f.payloadCh <- p: + } f.lastSignalledBlock.Store(p.BlockHeight) } } @@ -178,46 +173,6 @@ func (f *Follower) getLatestHeightWithBackoff(ctx context.Context) (uint64, erro return 0, errors.New("failed to get latest payload after retries") } -func (f *Follower) queryPayloadsFromSharedDB(ctx context.Context) { - for { - select { - case <-ctx.Done(): - return - default: - } - - lctx, cancel := context.WithTimeout(ctx, 5*time.Second) - payload, err := f.sharedDB.GetPayloadByHeight(lctx, f.lastSignalledBlock.Load()+1) - cancel() - - if err != nil { - if err == sql.ErrNoRows { - f.sleepRespectingContext(ctx, time.Millisecond) // New payload will likely be available within milliseconds - continue - } - f.logger.Error("Failed to fetch next payload by height with unexpected error", "height", f.lastSignalledBlock.Load()+1, "error", err) - f.sleepRespectingContext(ctx, defaultBackoff) - continue - } - - if payload == nil { - f.logger.Error("Received nil payload from valid query") - f.sleepRespectingContext(ctx, defaultBackoff) - continue - } - - select { - case f.payloadCh <- *payload: - f.logger.Debug("Sent payload to channel", "height", payload.BlockHeight) - f.lastSignalledBlock.Store(payload.BlockHeight) - default: - f.logger.Error("Payload channel buffer is full", "height", payload.BlockHeight) - f.sleepRespectingContext(ctx, defaultBackoff) - continue - } - } -} - func (f *Follower) sleepRespectingContext(ctx context.Context, duration time.Duration) { select { case <-ctx.Done(): diff --git a/cl/singlenode/follower/follower_test.go b/cl/singlenode/follower/follower_test.go index 9bd9536a0..36e079de1 100644 --- a/cl/singlenode/follower/follower_test.go +++ b/cl/singlenode/follower/follower_test.go @@ -15,9 +15,8 @@ import ( ) type mockPayloadDB struct { - GetPayloadsSinceFunc func(ctx context.Context, sinceHeight uint64, limit int) ([]types.PayloadInfo, error) - GetLatestHeightFunc func(ctx context.Context) (*uint64, error) - GetPayloadByHeightFunc func(ctx context.Context, height uint64) (*types.PayloadInfo, error) + GetPayloadsSinceFunc func(ctx context.Context, sinceHeight uint64, limit int) ([]types.PayloadInfo, error) + GetLatestHeightFunc func(ctx context.Context) (*uint64, error) } func (m *mockPayloadDB) GetPayloadsSince(ctx context.Context, sinceHeight uint64, limit int) ([]types.PayloadInfo, error) { @@ -28,10 +27,6 @@ func (m *mockPayloadDB) GetLatestHeight(ctx context.Context) (*uint64, error) { return m.GetLatestHeightFunc(ctx) } -func (m *mockPayloadDB) GetPayloadByHeight(ctx context.Context, height uint64) (*types.PayloadInfo, error) { - return m.GetPayloadByHeightFunc(ctx, height) -} - type mockBlockBuilder struct { GetExecutionHeadFunc func() *types.ExecutionHead SetExecutionHeadFromRPCFunc func(ctx context.Context) error @@ -87,10 +82,9 @@ func TestFollower_syncFromSharedDB(t *testing.T) { }, } syncBatchSize := uint64(100) - caughtUpThreshold := uint64(5) st := follower.NewStore(logger, inmemstorage.New()) - follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, caughtUpThreshold, st, newNoopBlockBuilder()) + follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, st, newNoopBlockBuilder()) if err != nil { t.Fatal(err) } @@ -100,10 +94,10 @@ func TestFollower_syncFromSharedDB(t *testing.T) { t.Fatal(err) } - errCh := make(chan error, 1) - + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() go func() { - errCh <- follower.SyncFromSharedDB(context.Background()) + follower.SyncFromSharedDB(ctx) }() payloadCh := follower.PayloadCh() @@ -111,18 +105,8 @@ func TestFollower_syncFromSharedDB(t *testing.T) { // expect 50 payloads received := 0 expectedBlockHeight := uint64(501) - numErrSignals := 0 for received < 50 { select { - case err := <-errCh: - if err != nil { - t.Fatalf("follower failed, exiting: %v", err) - } - if numErrSignals > 1 { - t.Fatalf("SyncFromSharedDB should only signal nil error once") - } - numErrSignals++ - continue case p := <-payloadCh: if p == (types.PayloadInfo{}) { t.Fatalf("received zero payload for expected block height %d", expectedBlockHeight) @@ -142,10 +126,6 @@ func TestFollower_syncFromSharedDB(t *testing.T) { // No more than 50 select { - case err := <-errCh: - if err != nil { - t.Fatalf("follower failed, exiting: %v", err) - } case <-payloadCh: t.Fatal("received unexpected payload") case <-time.After(1 * time.Second): @@ -181,18 +161,17 @@ func TestFollower_syncFromSharedDB_NoRows(t *testing.T) { }, } syncBatchSize := uint64(100) - caughtUpThreshold := uint64(5) st := follower.NewStore(logger, inmemstorage.New()) - follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, caughtUpThreshold, st, newNoopBlockBuilder()) + follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, st, newNoopBlockBuilder()) if err != nil { t.Fatal(err) } - errCh := make(chan error, 1) - + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() go func() { - errCh <- follower.SyncFromSharedDB(context.Background()) + follower.SyncFromSharedDB(ctx) }() payloadCh := follower.PayloadCh() @@ -200,18 +179,8 @@ func TestFollower_syncFromSharedDB_NoRows(t *testing.T) { // expect 15 payloads received := 0 expectedBlockHeight := uint64(1) - numErrSignals := 0 for received < 15 { select { - case err := <-errCh: - if err != nil { - t.Fatalf("follower failed, exiting: %v", err) - } - if numErrSignals > 1 { - t.Fatalf("SyncFromSharedDB should only signal nil error once") - } - numErrSignals++ - continue case p := <-payloadCh: if p == (types.PayloadInfo{}) { t.Fatalf("received zero payload at %d", expectedBlockHeight) @@ -231,10 +200,6 @@ func TestFollower_syncFromSharedDB_NoRows(t *testing.T) { // No more than 15 select { - case err := <-errCh: - if err != nil { - t.Fatalf("follower failed, exiting: %v", err) - } case <-payloadCh: t.Fatal("received unexpected payload") case <-time.After(1 * time.Second): @@ -254,6 +219,10 @@ func TestFollower_syncFromSharedDB_MultipleIterations(t *testing.T) { payloadRepo := &mockPayloadDB{ GetLatestHeightFunc: func(ctx context.Context) (*uint64, error) { numGetLatestHeightCalls++ + if numGetLatestHeightCalls > 3 { + toReturn := uint64(253) // Simulate that DB has only been updated up to block 253 + return &toReturn, nil + } toReturn := latest + uint64(numGetLatestHeightCalls) return &toReturn, nil }, @@ -296,10 +265,9 @@ func TestFollower_syncFromSharedDB_MultipleIterations(t *testing.T) { }, } syncBatchSize := uint64(20) - caughtUpThreshold := uint64(10) st := follower.NewStore(logger, inmemstorage.New()) - follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, caughtUpThreshold, st, newNoopBlockBuilder()) + follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, st, newNoopBlockBuilder()) if err != nil { t.Fatal(err) } @@ -309,10 +277,10 @@ func TestFollower_syncFromSharedDB_MultipleIterations(t *testing.T) { t.Fatal(err) } - errCh := make(chan error, 1) - + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() go func() { - errCh <- follower.SyncFromSharedDB(context.Background()) + follower.SyncFromSharedDB(ctx) }() payloadCh := follower.PayloadCh() @@ -320,18 +288,8 @@ func TestFollower_syncFromSharedDB_MultipleIterations(t *testing.T) { // expect payloads up to 253 received := 0 expectedBlockHeight := uint64(201) - numErrSignals := 0 for received < 53 { select { - case err := <-errCh: - if err != nil { - t.Fatalf("follower failed, exiting: %v", err) - } - if numErrSignals > 1 { - t.Fatalf("SyncFromSharedDB should only signal nil error once") - } - numErrSignals++ - continue case p := <-payloadCh: if p == (types.PayloadInfo{}) { t.Fatalf("received zero payload at %d", expectedBlockHeight) @@ -351,80 +309,18 @@ func TestFollower_syncFromSharedDB_MultipleIterations(t *testing.T) { // No more than 53 select { - case err := <-errCh: - if err != nil { - t.Fatalf("follower failed, exiting: %v", err) - } case <-payloadCh: t.Fatal("received unexpected payload") case <-time.After(1 * time.Second): } } -func TestFollower_queryPayloadsFromSharedDB(t *testing.T) { - t.Parallel() - - lastSignalledBlock := uint64(100) - numCalls := 0 - payloadRepo := &mockPayloadDB{ - GetPayloadByHeightFunc: func(ctx context.Context, height uint64) (*types.PayloadInfo, error) { - numCalls++ - if numCalls > 50 { // Simulate catching up to latest block after 50 calls - if numCalls == 60 { - // 60th call returns a payload again - return &types.PayloadInfo{BlockHeight: lastSignalledBlock + 51}, nil - } - return nil, sql.ErrNoRows - } - return &types.PayloadInfo{BlockHeight: lastSignalledBlock + uint64(numCalls)}, nil - }, - } - - syncBatchSize := uint64(20) - caughtUpThreshold := uint64(10) - logger := util.NewTestLogger(io.Discard) - st := follower.NewStore(logger, inmemstorage.New()) - follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, caughtUpThreshold, st, newNoopBlockBuilder()) - if err != nil { - t.Fatal(err) - } - - follower.SetLastSignalledBlock(lastSignalledBlock) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - go func() { - follower.QueryPayloadsFromSharedDB(ctx) - }() - - // All 51 payloads should be received in little time - received := 0 - payloadCh := follower.PayloadCh() - deadline := time.Now().Add(time.Second) - for received < 51 { - select { - case p := <-payloadCh: - received++ - if p == (types.PayloadInfo{}) { - t.Fatalf("received zero payload at %d", p.BlockHeight) - } - if p.BlockHeight != lastSignalledBlock+uint64(received) { - t.Fatalf("expected payload height %d, got %d", lastSignalledBlock+uint64(received), p.BlockHeight) - } - case <-time.After(time.Until(deadline)): - t.Fatalf("timeout waiting for payload") - case <-ctx.Done(): - } - } -} - func TestFollower_Start_SimulateNewChain(t *testing.T) { t.Parallel() logger := util.NewTestLogger(io.Discard) getLatestCalls := 0 - getByHeightCalls := 0 payloadRepo := &mockPayloadDB{ GetLatestHeightFunc: func(ctx context.Context) (*uint64, error) { @@ -436,23 +332,20 @@ func TestFollower_Start_SimulateNewChain(t *testing.T) { return &h, nil }, GetPayloadsSinceFunc: func(ctx context.Context, sinceHeight uint64, limit int) ([]types.PayloadInfo, error) { - t.Fatalf("GetPayloadsSince should not be called, got sinceHeight=%d limit=%d", sinceHeight, limit) - return nil, nil - }, - GetPayloadByHeightFunc: func(ctx context.Context, height uint64) (*types.PayloadInfo, error) { - getByHeightCalls++ - if getByHeightCalls > 10 { - return nil, sql.ErrNoRows + if sinceHeight != 1 { + t.Fatalf("unexpected sinceHeight %d", sinceHeight) } - return &types.PayloadInfo{BlockHeight: uint64(getByHeightCalls)}, nil + if limit != 1 { + t.Fatalf("unexpected limit %d", limit) + } + return []types.PayloadInfo{{BlockHeight: 1}}, nil }, } syncBatchSize := uint64(100) - caughtUpThreshold := uint64(5) st := follower.NewStore(logger, inmemstorage.New()) - follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, caughtUpThreshold, st, newNoopBlockBuilder()) + follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, st, newNoopBlockBuilder()) if err != nil { t.Fatal(err) } @@ -467,7 +360,7 @@ func TestFollower_Start_SimulateNewChain(t *testing.T) { if err != nil { t.Fatal(err) } - if lp >= 10 { + if lp >= 1 { break } if time.Now().After(deadline) { @@ -476,8 +369,8 @@ func TestFollower_Start_SimulateNewChain(t *testing.T) { time.Sleep(10 * time.Millisecond) } - if follower.LastSignalledBlock() != 10 { - t.Fatalf("expected last signalled block to be 10, got %d", follower.LastSignalledBlock()) + if follower.LastSignalledBlock() != 1 { + t.Fatalf("expected last signalled block to be 1, got %d", follower.LastSignalledBlock()) } cancel() @@ -507,19 +400,12 @@ func TestFollower_Start_SyncExistingChain(t *testing.T) { } return toReturn, nil }, - GetPayloadByHeightFunc: func(ctx context.Context, height uint64) (*types.PayloadInfo, error) { - if height > latest { - return nil, sql.ErrNoRows - } - return &types.PayloadInfo{BlockHeight: height}, nil - }, } syncBatchSize := uint64(20) - caughtUpThreshold := uint64(10) st := follower.NewStore(logger, inmemstorage.New()) - follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, caughtUpThreshold, st, newNoopBlockBuilder()) + follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, st, newNoopBlockBuilder()) if err != nil { t.Fatal(err) } From 66f7004a10d22165c6097d02edbfa242fc4969ac Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Thu, 11 Sep 2025 15:57:00 -0700 Subject: [PATCH 22/34] rm lastProcessed --- cl/singlenode/follower/export.go | 16 ---- cl/singlenode/follower/follower.go | 65 ++++---------- cl/singlenode/follower/follower_test.go | 113 ++++++++++++++++-------- 3 files changed, 91 insertions(+), 103 deletions(-) diff --git a/cl/singlenode/follower/export.go b/cl/singlenode/follower/export.go index deb8d6a28..fa57dda72 100644 --- a/cl/singlenode/follower/export.go +++ b/cl/singlenode/follower/export.go @@ -13,19 +13,3 @@ func (f *Follower) PayloadCh() <-chan types.PayloadInfo { func (f *Follower) SyncFromSharedDB(ctx context.Context) { f.syncFromSharedDB(ctx) } - -func (f *Follower) LastSignalledBlock() uint64 { - return f.lastSignalledBlock.Load() -} - -func (f *Follower) SetLastSignalledBlock(block uint64) { - f.lastSignalledBlock.Store(block) -} - -func (f *Follower) GetLastProcessed(ctx context.Context) (uint64, error) { - return f.getLastProcessed(ctx) -} - -func (f *Follower) SetLastProcessed(ctx context.Context, height uint64) error { - return f.setLastProcessed(ctx, height) -} diff --git a/cl/singlenode/follower/follower.go b/cl/singlenode/follower/follower.go index e3978f409..3b679ce0b 100644 --- a/cl/singlenode/follower/follower.go +++ b/cl/singlenode/follower/follower.go @@ -4,10 +4,7 @@ import ( "context" "database/sql" "errors" - "fmt" "log/slog" - "sync" - "sync/atomic" "time" "github.com/primev/mev-commit/cl/types" @@ -15,14 +12,11 @@ import ( ) type Follower struct { - logger *slog.Logger - sharedDB payloadDB - syncBatchSize uint64 - store *Store - storeMu sync.RWMutex - payloadCh chan types.PayloadInfo - lastSignalledBlock atomic.Uint64 // Last block num signalled through payloadCh - bb blockBuilder + logger *slog.Logger + sharedDB payloadDB + syncBatchSize uint64 + payloadCh chan types.PayloadInfo + bb blockBuilder } const ( @@ -45,7 +39,6 @@ func NewFollower( logger *slog.Logger, sharedDB payloadDB, syncBatchSize uint64, - store *Store, bb blockBuilder, ) (*Follower, error) { if sharedDB == nil { @@ -58,7 +51,6 @@ func NewFollower( logger: logger, sharedDB: sharedDB, syncBatchSize: syncBatchSize, - store: store, payloadCh: make(chan types.PayloadInfo, payloadBufferSize), bb: bb, }, nil @@ -90,14 +82,14 @@ func (f *Follower) Start(ctx context.Context) <-chan struct{} { } func (f *Follower) syncFromSharedDB(ctx context.Context) { - var err error - // lastSignalledBlock is only set from disk here - lastProcessed, err := f.getLastProcessed(ctx) - if err != nil { - f.logger.Error("failed to get last processed block", "error", err) - return + if f.bb.GetExecutionHead() == nil { + if err := f.bb.SetExecutionHeadFromRPC(ctx); err != nil { + f.logger.Error("failed to set execution head from rpc", "error", err) + return + } } - f.lastSignalledBlock.Store(lastProcessed) + + lastSignalledBlock := f.bb.GetExecutionHead().BlockHeight for { select { @@ -112,7 +104,7 @@ func (f *Follower) syncFromSharedDB(ctx context.Context) { continue } - blocksRemaining := targetBlock - f.lastSignalledBlock.Load() + blocksRemaining := targetBlock - lastSignalledBlock if blocksRemaining == 0 { f.sleepRespectingContext(ctx, time.Millisecond) // New payload will likely be available within milliseconds @@ -122,7 +114,7 @@ func (f *Follower) syncFromSharedDB(ctx context.Context) { limit := min(f.syncBatchSize, blocksRemaining) innerCtx, innerCancel := context.WithTimeout(ctx, 10*time.Second) - payloads, err := f.sharedDB.GetPayloadsSince(innerCtx, f.lastSignalledBlock.Load()+1, int(limit)) + payloads, err := f.sharedDB.GetPayloadsSince(innerCtx, lastSignalledBlock+1, int(limit)) innerCancel() if err != nil { f.logger.Error("failed to get payloads since", "error", err) @@ -141,8 +133,8 @@ func (f *Follower) syncFromSharedDB(ctx context.Context) { case <-ctx.Done(): return case f.payloadCh <- p: + lastSignalledBlock = p.BlockHeight } - f.lastSignalledBlock.Store(p.BlockHeight) } } } @@ -187,35 +179,10 @@ func (f *Follower) handlePayloads(ctx context.Context) { case <-ctx.Done(): return case p := <-f.payloadCh: - if err := f.handlePayload(ctx, p); err != nil { + if err := f.bb.FinalizeBlock(ctx, p.PayloadID, p.ExecutionPayload, ""); err != nil { f.logger.Error("Failed to process payload", "height", p.BlockHeight, "error", err) continue } - if err := f.setLastProcessed(ctx, p.BlockHeight); err != nil { - f.logger.Error("Failed to persist last processed height", "height", p.BlockHeight, "error", err) - continue - } - } - } -} - -func (f *Follower) handlePayload(ctx context.Context, payload types.PayloadInfo) error { - if f.bb.GetExecutionHead() == nil { - if err := f.bb.SetExecutionHeadFromRPC(ctx); err != nil { - return fmt.Errorf("failed to set execution head from rpc: %w", err) } } - return f.bb.FinalizeBlock(ctx, payload.PayloadID, payload.ExecutionPayload, "") -} - -func (f *Follower) getLastProcessed(ctx context.Context) (uint64, error) { - f.storeMu.RLock() - defer f.storeMu.RUnlock() - return f.store.GetLastProcessed() -} - -func (f *Follower) setLastProcessed(ctx context.Context, height uint64) error { - f.storeMu.Lock() - defer f.storeMu.Unlock() - return f.store.SetLastProcessed(height) } diff --git a/cl/singlenode/follower/follower_test.go b/cl/singlenode/follower/follower_test.go index 36e079de1..75b015b20 100644 --- a/cl/singlenode/follower/follower_test.go +++ b/cl/singlenode/follower/follower_test.go @@ -5,12 +5,12 @@ import ( "database/sql" "fmt" "io" + "strconv" "testing" "time" "github.com/primev/mev-commit/cl/singlenode/follower" "github.com/primev/mev-commit/cl/types" - inmemstorage "github.com/primev/mev-commit/p2p/pkg/storage/inmem" "github.com/primev/mev-commit/x/util" ) @@ -28,13 +28,13 @@ func (m *mockPayloadDB) GetLatestHeight(ctx context.Context) (*uint64, error) { } type mockBlockBuilder struct { - GetExecutionHeadFunc func() *types.ExecutionHead + executionHead *types.ExecutionHead SetExecutionHeadFromRPCFunc func(ctx context.Context) error FinalizeBlockFunc func(ctx context.Context, payloadIDStr, executionPayloadStr, msgID string) error } func (m *mockBlockBuilder) GetExecutionHead() *types.ExecutionHead { - return m.GetExecutionHeadFunc() + return m.executionHead } func (m *mockBlockBuilder) SetExecutionHeadFromRPC(ctx context.Context) error { @@ -45,11 +45,9 @@ func (m *mockBlockBuilder) FinalizeBlock(ctx context.Context, payloadIDStr, exec return m.FinalizeBlockFunc(ctx, payloadIDStr, executionPayloadStr, msgID) } -func newNoopBlockBuilder() *mockBlockBuilder { +func newMockBlockBuilder() *mockBlockBuilder { return &mockBlockBuilder{ - GetExecutionHeadFunc: func() *types.ExecutionHead { - return nil - }, + executionHead: nil, SetExecutionHeadFromRPCFunc: func(ctx context.Context) error { return nil }, @@ -82,16 +80,16 @@ func TestFollower_syncFromSharedDB(t *testing.T) { }, } syncBatchSize := uint64(100) - st := follower.NewStore(logger, inmemstorage.New()) - follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, st, newNoopBlockBuilder()) + bb := newMockBlockBuilder() + follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, bb) if err != nil { t.Fatal(err) } - err = follower.SetLastProcessed(context.Background(), lastProcessed) - if err != nil { - t.Fatal(err) + bb.SetExecutionHeadFromRPCFunc = func(ctx context.Context) error { + bb.executionHead = &types.ExecutionHead{BlockHeight: lastProcessed} + return nil } ctx, cancel := context.WithCancel(context.Background()) @@ -161,13 +159,18 @@ func TestFollower_syncFromSharedDB_NoRows(t *testing.T) { }, } syncBatchSize := uint64(100) - st := follower.NewStore(logger, inmemstorage.New()) - follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, st, newNoopBlockBuilder()) + bb := newMockBlockBuilder() + follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, bb) if err != nil { t.Fatal(err) } + bb.SetExecutionHeadFromRPCFunc = func(ctx context.Context) error { + bb.executionHead = &types.ExecutionHead{BlockHeight: 0} // Only genesis block is available + return nil + } + ctx, cancel := context.WithCancel(context.Background()) defer cancel() go func() { @@ -265,16 +268,16 @@ func TestFollower_syncFromSharedDB_MultipleIterations(t *testing.T) { }, } syncBatchSize := uint64(20) - st := follower.NewStore(logger, inmemstorage.New()) - follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, st, newNoopBlockBuilder()) + bb := newMockBlockBuilder() + follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, bb) if err != nil { t.Fatal(err) } - err = follower.SetLastProcessed(context.Background(), lastProcessed) - if err != nil { - t.Fatal(err) + bb.SetExecutionHeadFromRPCFunc = func(ctx context.Context) error { + bb.executionHead = &types.ExecutionHead{BlockHeight: lastProcessed} + return nil } ctx, cancel := context.WithCancel(context.Background()) @@ -343,24 +346,34 @@ func TestFollower_Start_SimulateNewChain(t *testing.T) { } syncBatchSize := uint64(100) - st := follower.NewStore(logger, inmemstorage.New()) - follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, st, newNoopBlockBuilder()) + bb := newMockBlockBuilder() + follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, bb) if err != nil { t.Fatal(err) } + bb.SetExecutionHeadFromRPCFunc = func(ctx context.Context) error { + bb.executionHead = &types.ExecutionHead{BlockHeight: 0} // Only genesis block is available + return nil + } + + bb.FinalizeBlockFunc = func(ctx context.Context, payloadIDStr, executionPayloadStr, msgID string) error { + bb.executionHead = &types.ExecutionHead{BlockHeight: 1} + return nil + } + ctx, cancel := context.WithCancel(context.Background()) defer cancel() done := follower.Start(ctx) deadline := time.Now().Add(5 * time.Second) for { - lp, err := follower.GetLastProcessed(context.Background()) - if err != nil { - t.Fatal(err) + lp := bb.GetExecutionHead() + if lp == nil { + continue } - if lp >= 1 { + if lp.BlockHeight >= 1 { break } if time.Now().After(deadline) { @@ -369,8 +382,12 @@ func TestFollower_Start_SimulateNewChain(t *testing.T) { time.Sleep(10 * time.Millisecond) } - if follower.LastSignalledBlock() != 1 { - t.Fatalf("expected last signalled block to be 1, got %d", follower.LastSignalledBlock()) + finalExecutionHead := bb.GetExecutionHead() + if finalExecutionHead == nil { + t.Fatal("execution head is nil") + } + if finalExecutionHead.BlockHeight != 1 { + t.Fatalf("expected execution head block height to be 1, got %d", finalExecutionHead.BlockHeight) } cancel() @@ -396,34 +413,50 @@ func TestFollower_Start_SyncExistingChain(t *testing.T) { GetPayloadsSinceFunc: func(ctx context.Context, sinceHeight uint64, limit int) ([]types.PayloadInfo, error) { toReturn := make([]types.PayloadInfo, 0, limit) for i := uint64(0); i < uint64(limit); i++ { - toReturn = append(toReturn, types.PayloadInfo{BlockHeight: sinceHeight + i}) + toReturn = append(toReturn, types.PayloadInfo{ + BlockHeight: sinceHeight + i, + // Encode just the block height + ExecutionPayload: fmt.Sprintf("%d", sinceHeight+i), + }) } return toReturn, nil }, } syncBatchSize := uint64(20) - st := follower.NewStore(logger, inmemstorage.New()) - follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, st, newNoopBlockBuilder()) + bb := newMockBlockBuilder() + follower, err := follower.NewFollower(logger, payloadRepo, syncBatchSize, bb) if err != nil { t.Fatal(err) } - if err := follower.SetLastProcessed(context.Background(), lastProcessed); err != nil { - t.Fatal(err) + bb.SetExecutionHeadFromRPCFunc = func(ctx context.Context) error { + bb.executionHead = &types.ExecutionHead{BlockHeight: lastProcessed} + return nil + } + + bb.FinalizeBlockFunc = func(ctx context.Context, payloadIDStr, executionPayloadStr, msgID string) error { + // decode block num from executionPayloadStr + blockNum, err := strconv.ParseUint(executionPayloadStr, 10, 64) + if err != nil { + return err + } + bb.executionHead = &types.ExecutionHead{BlockHeight: blockNum} + return nil } ctx, cancel := context.WithCancel(context.Background()) + defer cancel() done := follower.Start(ctx) deadline := time.Now().Add(5 * time.Second) for { - lp, err := follower.GetLastProcessed(context.Background()) - if err != nil { - t.Fatal(err) + lp := bb.GetExecutionHead() + if lp == nil { + continue } - if lp >= 700 { + if lp.BlockHeight >= 700 { break } if time.Now().After(deadline) { @@ -432,8 +465,12 @@ func TestFollower_Start_SyncExistingChain(t *testing.T) { time.Sleep(10 * time.Millisecond) } - if follower.LastSignalledBlock() != 700 { - t.Fatalf("expected last signalled to be %d, got %d", 700, follower.LastSignalledBlock()) + finalExecutionHead := bb.GetExecutionHead() + if finalExecutionHead == nil { + t.Fatal("execution head is nil") + } + if finalExecutionHead.BlockHeight != 700 { + t.Fatalf("expected execution head block height to be 700, got %d", finalExecutionHead.BlockHeight) } cancel() From 9728b3f2b4a54b99f3d6c1cf49c8430247f1ecbc Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Thu, 11 Sep 2025 16:40:38 -0700 Subject: [PATCH 23/34] GetLatestHeight doesnt return pointer --- cl/singlenode/follower/follower.go | 44 +++++++------------------ cl/singlenode/follower/follower_test.go | 35 +++++++++----------- cl/singlenode/payloadstore/postgres.go | 13 ++++---- 3 files changed, 33 insertions(+), 59 deletions(-) diff --git a/cl/singlenode/follower/follower.go b/cl/singlenode/follower/follower.go index 3b679ce0b..54eb75693 100644 --- a/cl/singlenode/follower/follower.go +++ b/cl/singlenode/follower/follower.go @@ -2,7 +2,6 @@ package follower import ( "context" - "database/sql" "errors" "log/slog" "time" @@ -26,7 +25,7 @@ const ( type payloadDB interface { GetPayloadsSince(ctx context.Context, sinceHeight uint64, limit int) ([]types.PayloadInfo, error) - GetLatestHeight(ctx context.Context) (*uint64, error) + GetLatestHeight(ctx context.Context) (uint64, error) } type blockBuilder interface { @@ -98,12 +97,19 @@ func (f *Follower) syncFromSharedDB(ctx context.Context) { default: } - targetBlock, err := f.getLatestHeightWithBackoff(ctx) + cctx, cancel := context.WithTimeout(ctx, 5*time.Second) + targetBlock, err := f.sharedDB.GetLatestHeight(cctx) + cancel() if err != nil { f.sleepRespectingContext(ctx, defaultBackoff) continue } + if lastSignalledBlock > targetBlock { + f.logger.Error("internal invariant has been broken. Follower EL is ahead of signer") + return + } + blocksRemaining := targetBlock - lastSignalledBlock if blocksRemaining == 0 { @@ -113,9 +119,9 @@ func (f *Follower) syncFromSharedDB(ctx context.Context) { limit := min(f.syncBatchSize, blocksRemaining) - innerCtx, innerCancel := context.WithTimeout(ctx, 10*time.Second) - payloads, err := f.sharedDB.GetPayloadsSince(innerCtx, lastSignalledBlock+1, int(limit)) - innerCancel() + cctx, cancel = context.WithTimeout(ctx, 5*time.Second) + payloads, err := f.sharedDB.GetPayloadsSince(cctx, lastSignalledBlock+1, int(limit)) + cancel() if err != nil { f.logger.Error("failed to get payloads since", "error", err) f.sleepRespectingContext(ctx, defaultBackoff) @@ -139,32 +145,6 @@ func (f *Follower) syncFromSharedDB(ctx context.Context) { } } -func (f *Follower) getLatestHeightWithBackoff(ctx context.Context) (uint64, error) { - const maxRetries = 10 - for attempt := range maxRetries { - lctx, cancel := context.WithTimeout(ctx, time.Second) - latest, err := f.sharedDB.GetLatestHeight(lctx) - cancel() - if err == nil { - if latest != nil { - return *latest, nil - } - return 0, errors.New("nil height returned") - } - if err != sql.ErrNoRows { - return 0, err - } - - backoff := defaultBackoff * time.Duration(attempt+1) - select { - case <-ctx.Done(): - return 0, ctx.Err() - case <-time.After(backoff): - } - } - return 0, errors.New("failed to get latest payload after retries") -} - func (f *Follower) sleepRespectingContext(ctx context.Context, duration time.Duration) { select { case <-ctx.Done(): diff --git a/cl/singlenode/follower/follower_test.go b/cl/singlenode/follower/follower_test.go index 75b015b20..874dd21da 100644 --- a/cl/singlenode/follower/follower_test.go +++ b/cl/singlenode/follower/follower_test.go @@ -2,7 +2,6 @@ package follower_test import ( "context" - "database/sql" "fmt" "io" "strconv" @@ -16,14 +15,14 @@ import ( type mockPayloadDB struct { GetPayloadsSinceFunc func(ctx context.Context, sinceHeight uint64, limit int) ([]types.PayloadInfo, error) - GetLatestHeightFunc func(ctx context.Context) (*uint64, error) + GetLatestHeightFunc func(ctx context.Context) (uint64, error) } func (m *mockPayloadDB) GetPayloadsSince(ctx context.Context, sinceHeight uint64, limit int) ([]types.PayloadInfo, error) { return m.GetPayloadsSinceFunc(ctx, sinceHeight, limit) } -func (m *mockPayloadDB) GetLatestHeight(ctx context.Context) (*uint64, error) { +func (m *mockPayloadDB) GetLatestHeight(ctx context.Context) (uint64, error) { return m.GetLatestHeightFunc(ctx) } @@ -65,8 +64,8 @@ func TestFollower_syncFromSharedDB(t *testing.T) { logger := util.NewTestLogger(io.Discard) payloadRepo := &mockPayloadDB{ - GetLatestHeightFunc: func(ctx context.Context) (*uint64, error) { - return &latest, nil + GetLatestHeightFunc: func(ctx context.Context) (uint64, error) { + return latest, nil }, GetPayloadsSinceFunc: func(ctx context.Context, sinceHeight uint64, limit int) ([]types.PayloadInfo, error) { if sinceHeight != 501 { @@ -136,13 +135,12 @@ func TestFollower_syncFromSharedDB_NoRows(t *testing.T) { attempts := 0 logger := util.NewTestLogger(io.Discard) payloadRepo := &mockPayloadDB{ - GetLatestHeightFunc: func(ctx context.Context) (*uint64, error) { + GetLatestHeightFunc: func(ctx context.Context) (uint64, error) { if attempts < 3 { attempts++ - return nil, sql.ErrNoRows + return 0, nil } - block15 := uint64(15) - return &block15, nil + return 15, nil }, GetPayloadsSinceFunc: func(ctx context.Context, sinceHeight uint64, limit int) ([]types.PayloadInfo, error) { if sinceHeight != 1 { @@ -220,14 +218,12 @@ func TestFollower_syncFromSharedDB_MultipleIterations(t *testing.T) { numGetLatestHeightCalls := 0 numGetPayloadsCalls := 0 payloadRepo := &mockPayloadDB{ - GetLatestHeightFunc: func(ctx context.Context) (*uint64, error) { + GetLatestHeightFunc: func(ctx context.Context) (uint64, error) { numGetLatestHeightCalls++ if numGetLatestHeightCalls > 3 { - toReturn := uint64(253) // Simulate that DB has only been updated up to block 253 - return &toReturn, nil + return 253, nil // Simulate that DB has only been updated up to block 253 } - toReturn := latest + uint64(numGetLatestHeightCalls) - return &toReturn, nil + return latest + uint64(numGetLatestHeightCalls), nil }, GetPayloadsSinceFunc: func(ctx context.Context, sinceHeight uint64, limit int) ([]types.PayloadInfo, error) { numGetPayloadsCalls++ @@ -326,13 +322,12 @@ func TestFollower_Start_SimulateNewChain(t *testing.T) { getLatestCalls := 0 payloadRepo := &mockPayloadDB{ - GetLatestHeightFunc: func(ctx context.Context) (*uint64, error) { + GetLatestHeightFunc: func(ctx context.Context) (uint64, error) { getLatestCalls++ if getLatestCalls <= 3 { - return nil, sql.ErrNoRows + return 0, nil } - h := uint64(1) - return &h, nil + return 1, nil }, GetPayloadsSinceFunc: func(ctx context.Context, sinceHeight uint64, limit int) ([]types.PayloadInfo, error) { if sinceHeight != 1 { @@ -407,8 +402,8 @@ func TestFollower_Start_SyncExistingChain(t *testing.T) { latest := uint64(700) payloadRepo := &mockPayloadDB{ - GetLatestHeightFunc: func(ctx context.Context) (*uint64, error) { - return &latest, nil + GetLatestHeightFunc: func(ctx context.Context) (uint64, error) { + return latest, nil }, GetPayloadsSinceFunc: func(ctx context.Context, sinceHeight uint64, limit int) ([]types.PayloadInfo, error) { toReturn := make([]types.PayloadInfo, 0, limit) diff --git a/cl/singlenode/payloadstore/postgres.go b/cl/singlenode/payloadstore/postgres.go index 010b1ad39..075fa1b0d 100644 --- a/cl/singlenode/payloadstore/postgres.go +++ b/cl/singlenode/payloadstore/postgres.go @@ -256,24 +256,23 @@ func (r *PostgresRepository) GetLatestPayload(ctx context.Context) (*types.Paylo return &payload, nil } -func (r *PostgresRepository) GetLatestHeight(ctx context.Context) (*uint64, error) { +func (r *PostgresRepository) GetLatestHeight(ctx context.Context) (uint64, error) { query := ` SELECT MAX(block_height) FROM execution_payloads; ` - queryCtx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() var n sql.NullInt64 if err := r.db.QueryRowContext(queryCtx, query).Scan(&n); err != nil { - return nil, fmt.Errorf("failed to query latest height: %w", err) + // MAX should never return sql.ErrNoRow, always bubble errors + return 0, err } if !n.Valid { - return nil, sql.ErrNoRows + // Empty table -> new chain + return 0, nil } - - h := uint64(n.Int64) - return &h, nil + return uint64(n.Int64), nil } // Close closes the database connection. From 3b531612750467f8c69596fa6244893c61211098 Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Thu, 11 Sep 2025 16:49:04 -0700 Subject: [PATCH 24/34] cleanup main.go --- cl/cmd/singlenode/main.go | 36 ++++++++++-------------------------- 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/cl/cmd/singlenode/main.go b/cl/cmd/singlenode/main.go index 7a236b5dc..c66404eba 100644 --- a/cl/cmd/singlenode/main.go +++ b/cl/cmd/singlenode/main.go @@ -18,7 +18,6 @@ import ( "github.com/primev/mev-commit/cl/singlenode" "github.com/primev/mev-commit/cl/singlenode/follower" "github.com/primev/mev-commit/cl/singlenode/payloadstore" - pebblestorage "github.com/primev/mev-commit/p2p/pkg/storage/pebble" "github.com/primev/mev-commit/x/util" "github.com/urfave/cli/v2" "github.com/urfave/cli/v2/altsrc" @@ -216,12 +215,6 @@ var ( }) // Follower node specific flags - followerDataDirFlag = altsrc.NewStringFlag(&cli.StringFlag{ - Name: "data-dir", - Usage: "Directory for follower node pebble database", - EnvVars: []string{"FOLLOWER_DATA_DIR"}, - Value: "", - }) syncBatchSizeFlag = altsrc.NewUint64Flag(&cli.Uint64Flag{ Name: "sync-batch-size", Usage: "Number of payloads per request to the EL during sync", @@ -259,7 +252,6 @@ func main() { logTagsFlag, healthAddrPortFlag, postgresDSNFlag, - followerDataDirFlag, syncBatchSizeFlag, } @@ -403,16 +395,6 @@ func startFollowerNode(c *cli.Context) error { if syncBatchSize == 0 { return fmt.Errorf("sync-batch-size is required") } - dataDir := c.String(followerDataDirFlag.Name) - if dataDir == "" { - return fmt.Errorf("data-dir is required") - } - pebbleStore, err := pebblestorage.New(dataDir) - if err != nil { - return fmt.Errorf("failed to create storage: %w", err) - } - store := follower.NewStore(logger, pebbleStore) - ethClientURL := c.String(ethClientURLFlag.Name) if ethClientURL == "" { return fmt.Errorf("eth-client-url is required") @@ -434,8 +416,7 @@ func startFollowerNode(c *cli.Context) error { followerNode, err := follower.NewFollower( logger, repo, - 100, // syncBatchSize - store, + syncBatchSize, bb, ) if err != nil { @@ -443,10 +424,13 @@ func startFollowerNode(c *cli.Context) error { return err } - followerNode.Start(rootCtx) - - <-rootCtx.Done() - - logger.Info("Follower node shutdown completed.") - return nil + done := followerNode.Start(rootCtx) + select { + case <-done: + logger.Info("Follower node shutdown completed.") + return nil + case <-rootCtx.Done(): + logger.Info("Follower node shutdown completed.") + return nil + } } From 589edba61d770070cc37bf5df4c5e16dd113aace Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Thu, 11 Sep 2025 16:55:30 -0700 Subject: [PATCH 25/34] rm channel buffer --- cl/singlenode/follower/follower.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cl/singlenode/follower/follower.go b/cl/singlenode/follower/follower.go index 54eb75693..fe76a5900 100644 --- a/cl/singlenode/follower/follower.go +++ b/cl/singlenode/follower/follower.go @@ -19,8 +19,7 @@ type Follower struct { } const ( - payloadBufferSize = 100 - defaultBackoff = 200 * time.Millisecond + defaultBackoff = 200 * time.Millisecond ) type payloadDB interface { @@ -50,7 +49,7 @@ func NewFollower( logger: logger, sharedDB: sharedDB, syncBatchSize: syncBatchSize, - payloadCh: make(chan types.PayloadInfo, payloadBufferSize), + payloadCh: make(chan types.PayloadInfo), bb: bb, }, nil } From decdcef59cda09e1185e83740deff62e36e493a6 Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Thu, 11 Sep 2025 16:56:20 -0700 Subject: [PATCH 26/34] go mod tidy --- cl/go.mod | 18 +----------------- cl/go.sum | 28 ++-------------------------- 2 files changed, 3 insertions(+), 43 deletions(-) diff --git a/cl/go.mod b/cl/go.mod index cd1422f96..2b207c911 100644 --- a/cl/go.mod +++ b/cl/go.mod @@ -18,17 +18,9 @@ require ( require ( github.com/BurntSushi/toml v1.4.0 // indirect - github.com/DataDog/zstd v1.5.5 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/StackExchange/wmi v1.2.1 // indirect - github.com/armon/go-radix v1.0.0 // indirect github.com/bits-and-blooms/bitset v1.20.0 // indirect - github.com/cockroachdb/errors v1.11.3 // indirect - github.com/cockroachdb/fifo v0.0.0-20240616162244-4768e80dfb9a // indirect - github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect - github.com/cockroachdb/pebble v1.1.2 // indirect - github.com/cockroachdb/redact v1.1.5 // indirect - github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect github.com/consensys/bavard v0.1.27 // indirect github.com/consensys/gnark-crypto v0.16.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect @@ -38,22 +30,16 @@ require ( github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/ethereum/c-kzg-4844/v2 v2.1.0 // indirect github.com/ethereum/go-verkle v0.2.2 // indirect - github.com/getsentry/sentry-go v0.28.1 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/gofrs/flock v0.8.1 // indirect - github.com/gogo/protobuf v1.3.2 // indirect github.com/holiman/uint256 v1.3.2 // indirect - github.com/klauspost/compress v1.17.9 // indirect - github.com/kr/pretty v0.3.1 // indirect - github.com/kr/text v0.2.0 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/nxadm/tail v1.4.11 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/onsi/gomega v1.30.0 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect github.com/stretchr/objx v0.5.2 // indirect @@ -62,9 +48,7 @@ require ( github.com/tklauser/numcpus v0.7.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/mod v0.22.0 // indirect - golang.org/x/text v0.22.0 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/cl/go.sum b/cl/go.sum index ef772ef8b..c931508f8 100644 --- a/cl/go.sum +++ b/cl/go.sum @@ -8,8 +8,6 @@ github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDO github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= -github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= -github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3MdfoPyRVU= @@ -22,8 +20,6 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= -github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= github.com/cockroachdb/fifo v0.0.0-20240616162244-4768e80dfb9a h1:f52TdbU4D5nozMAhO9TvTJ2ZMCXtN4VIAmfrrZ0JXQ4= @@ -48,7 +44,6 @@ github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOV github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM= github.com/crate-crypto/go-kzg-4844 v1.1.0 h1:EN/u9k2TF6OWSHrCCDBBU6GLNMq88OspHHlMnHfoyU4= github.com/crate-crypto/go-kzg-4844 v1.1.0/go.mod h1:JolLjpSff1tCCJKaJx4psrlEdlXuJEC996PL3tTAFks= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= @@ -65,12 +60,11 @@ github.com/ethereum/go-ethereum v1.15.11 h1:JK73WKeu0WC0O1eyX+mdQAVHUV+UR1a9VB/d github.com/ethereum/go-ethereum v1.15.11/go.mod h1:mf8YiHIb0GR4x4TipcvBUPxJLw1mFdmxzoDi11sDRoI= github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/getsentry/sentry-go v0.28.1 h1:zzaSm/vHmGllRM6Tpx1492r0YDzauArdBfkJRtY6P5k= github.com/getsentry/sentry-go v0.28.1/go.mod h1:1fQZ+7l7eeJ3wYi82q5Hg8GqAPgefRq+FP/QhafYVgg= -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-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= @@ -107,8 +101,6 @@ github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -145,8 +137,6 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= -github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= -github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pion/dtls/v2 v2.2.11 h1:9U/dpCYl1ySttROPWJgqWKEylUdT0fXp/xst6JwY5Ks= github.com/pion/dtls/v2 v2.2.11/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= @@ -158,7 +148,6 @@ github.com/pion/transport/v2 v2.2.5 h1:iyi25i/21gQck4hfRhomF6SktmUQjRsRW4WJdhfc3 github.com/pion/transport/v2 v2.2.5/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= github.com/pion/transport/v3 v3.0.2 h1:r+40RJR25S9w3jbA6/5uEPTzcdn7ncyU44RWCbHkLg4= github.com/pion/transport/v3 v3.0.2/go.mod h1:nIToODoOlb5If2jF9y2Igfx3PFYWfuXi37m0IlWa/D0= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= @@ -176,7 +165,6 @@ github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/cors v1.8.3 h1:O+qNyWn7Z+F9M0ILBHgMVPuB1xTOucVd5gtaYyXBpRo= @@ -205,41 +193,32 @@ github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAh github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= -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.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -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.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 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-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -252,14 +231,11 @@ golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= 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= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= From 3ba9a0da40c2a76057f93906c32e3475e343a47d Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Thu, 11 Sep 2025 17:17:39 -0700 Subject: [PATCH 27/34] Update follower.go --- cl/singlenode/follower/follower.go | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/cl/singlenode/follower/follower.go b/cl/singlenode/follower/follower.go index fe76a5900..04502713c 100644 --- a/cl/singlenode/follower/follower.go +++ b/cl/singlenode/follower/follower.go @@ -4,6 +4,7 @@ import ( "context" "errors" "log/slog" + "sync" "time" "github.com/primev/mev-commit/cl/types" @@ -15,6 +16,7 @@ type Follower struct { sharedDB payloadDB syncBatchSize uint64 payloadCh chan types.PayloadInfo + bbMutex sync.RWMutex bb blockBuilder } @@ -80,14 +82,14 @@ func (f *Follower) Start(ctx context.Context) <-chan struct{} { } func (f *Follower) syncFromSharedDB(ctx context.Context) { - if f.bb.GetExecutionHead() == nil { - if err := f.bb.SetExecutionHeadFromRPC(ctx); err != nil { + if f.GetExecutionHead() == nil { + if err := f.SetExecutionHeadFromRPC(ctx); err != nil { f.logger.Error("failed to set execution head from rpc", "error", err) return } } - lastSignalledBlock := f.bb.GetExecutionHead().BlockHeight + lastSignalledBlock := f.GetExecutionHead().BlockHeight for { select { @@ -158,10 +160,28 @@ func (f *Follower) handlePayloads(ctx context.Context) { case <-ctx.Done(): return case p := <-f.payloadCh: - if err := f.bb.FinalizeBlock(ctx, p.PayloadID, p.ExecutionPayload, ""); err != nil { + if err := f.FinalizeBlock(ctx, p.PayloadID, p.ExecutionPayload, ""); err != nil { f.logger.Error("Failed to process payload", "height", p.BlockHeight, "error", err) continue } } } } + +func (f *Follower) GetExecutionHead() *types.ExecutionHead { + f.bbMutex.RLock() + defer f.bbMutex.RUnlock() + return f.bb.GetExecutionHead() +} + +func (f *Follower) SetExecutionHeadFromRPC(ctx context.Context) error { + f.bbMutex.Lock() + defer f.bbMutex.Unlock() + return f.bb.SetExecutionHeadFromRPC(ctx) +} + +func (f *Follower) FinalizeBlock(ctx context.Context, payloadIDStr, executionPayloadStr, msgID string) error { + f.bbMutex.Lock() + defer f.bbMutex.Unlock() + return f.bb.FinalizeBlock(ctx, payloadIDStr, executionPayloadStr, msgID) +} From 51265eed6d94c4e8e89066a45ab2b2263f0c0ed4 Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Thu, 11 Sep 2025 17:30:55 -0700 Subject: [PATCH 28/34] sync test --- cl/singlenode/follower/export.go | 4 ++++ cl/singlenode/follower/follower.go | 14 +++++++------- cl/singlenode/follower/follower_test.go | 8 ++++---- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/cl/singlenode/follower/export.go b/cl/singlenode/follower/export.go index fa57dda72..1be1aee9e 100644 --- a/cl/singlenode/follower/export.go +++ b/cl/singlenode/follower/export.go @@ -13,3 +13,7 @@ func (f *Follower) PayloadCh() <-chan types.PayloadInfo { func (f *Follower) SyncFromSharedDB(ctx context.Context) { f.syncFromSharedDB(ctx) } + +func (f *Follower) GetExecutionHead() *types.ExecutionHead { + return f.getExecutionHead() +} diff --git a/cl/singlenode/follower/follower.go b/cl/singlenode/follower/follower.go index 04502713c..8cab788fe 100644 --- a/cl/singlenode/follower/follower.go +++ b/cl/singlenode/follower/follower.go @@ -82,14 +82,14 @@ func (f *Follower) Start(ctx context.Context) <-chan struct{} { } func (f *Follower) syncFromSharedDB(ctx context.Context) { - if f.GetExecutionHead() == nil { - if err := f.SetExecutionHeadFromRPC(ctx); err != nil { + if f.getExecutionHead() == nil { + if err := f.setExecutionHeadFromRPC(ctx); err != nil { f.logger.Error("failed to set execution head from rpc", "error", err) return } } - lastSignalledBlock := f.GetExecutionHead().BlockHeight + lastSignalledBlock := f.getExecutionHead().BlockHeight for { select { @@ -160,7 +160,7 @@ func (f *Follower) handlePayloads(ctx context.Context) { case <-ctx.Done(): return case p := <-f.payloadCh: - if err := f.FinalizeBlock(ctx, p.PayloadID, p.ExecutionPayload, ""); err != nil { + if err := f.finalizeBlock(ctx, p.PayloadID, p.ExecutionPayload, ""); err != nil { f.logger.Error("Failed to process payload", "height", p.BlockHeight, "error", err) continue } @@ -168,19 +168,19 @@ func (f *Follower) handlePayloads(ctx context.Context) { } } -func (f *Follower) GetExecutionHead() *types.ExecutionHead { +func (f *Follower) getExecutionHead() *types.ExecutionHead { f.bbMutex.RLock() defer f.bbMutex.RUnlock() return f.bb.GetExecutionHead() } -func (f *Follower) SetExecutionHeadFromRPC(ctx context.Context) error { +func (f *Follower) setExecutionHeadFromRPC(ctx context.Context) error { f.bbMutex.Lock() defer f.bbMutex.Unlock() return f.bb.SetExecutionHeadFromRPC(ctx) } -func (f *Follower) FinalizeBlock(ctx context.Context, payloadIDStr, executionPayloadStr, msgID string) error { +func (f *Follower) finalizeBlock(ctx context.Context, payloadIDStr, executionPayloadStr, msgID string) error { f.bbMutex.Lock() defer f.bbMutex.Unlock() return f.bb.FinalizeBlock(ctx, payloadIDStr, executionPayloadStr, msgID) diff --git a/cl/singlenode/follower/follower_test.go b/cl/singlenode/follower/follower_test.go index 874dd21da..0ccac169d 100644 --- a/cl/singlenode/follower/follower_test.go +++ b/cl/singlenode/follower/follower_test.go @@ -364,7 +364,7 @@ func TestFollower_Start_SimulateNewChain(t *testing.T) { deadline := time.Now().Add(5 * time.Second) for { - lp := bb.GetExecutionHead() + lp := follower.GetExecutionHead() if lp == nil { continue } @@ -377,7 +377,7 @@ func TestFollower_Start_SimulateNewChain(t *testing.T) { time.Sleep(10 * time.Millisecond) } - finalExecutionHead := bb.GetExecutionHead() + finalExecutionHead := follower.GetExecutionHead() if finalExecutionHead == nil { t.Fatal("execution head is nil") } @@ -447,7 +447,7 @@ func TestFollower_Start_SyncExistingChain(t *testing.T) { deadline := time.Now().Add(5 * time.Second) for { - lp := bb.GetExecutionHead() + lp := follower.GetExecutionHead() if lp == nil { continue } @@ -460,7 +460,7 @@ func TestFollower_Start_SyncExistingChain(t *testing.T) { time.Sleep(10 * time.Millisecond) } - finalExecutionHead := bb.GetExecutionHead() + finalExecutionHead := follower.GetExecutionHead() if finalExecutionHead == nil { t.Fatal("execution head is nil") } From ba862f4b5e089609a9ce0ec5c0eb3a60849c35e1 Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Thu, 11 Sep 2025 18:37:02 -0700 Subject: [PATCH 29/34] dont reinit postgres --- cl/cmd/singlenode/main.go | 2 +- cl/singlenode/payloadstore/postgres.go | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/cl/cmd/singlenode/main.go b/cl/cmd/singlenode/main.go index c66404eba..5b80eb79c 100644 --- a/cl/cmd/singlenode/main.go +++ b/cl/cmd/singlenode/main.go @@ -387,7 +387,7 @@ func startFollowerNode(c *cli.Context) error { if postgresDSN == "" { return fmt.Errorf("postgresDSN is required") } - repo, err := payloadstore.NewPostgresRepository(rootCtx, postgresDSN, logger) + repo, err := payloadstore.NewPostgresFollower(rootCtx, postgresDSN, logger) if err != nil { return fmt.Errorf("failed to initialize payload repository: %w", err) } diff --git a/cl/singlenode/payloadstore/postgres.go b/cl/singlenode/payloadstore/postgres.go index 075fa1b0d..bf11aaf4e 100644 --- a/cl/singlenode/payloadstore/postgres.go +++ b/cl/singlenode/payloadstore/postgres.go @@ -71,6 +71,25 @@ func NewPostgresRepository(ctx context.Context, dsn string, logger *slog.Logger) return &PostgresRepository{db: db, logger: l}, nil } +func NewPostgresFollower(ctx context.Context, dsn string, logger *slog.Logger) (*PostgresRepository, error) { + l := logger.With("component", "postgresFollower") + + db, err := sql.Open("postgres", dsn) + if err != nil { + return nil, fmt.Errorf("failed to open postgres connection: %w", err) + } + + pingCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + if err := db.PingContext(pingCtx); err != nil { + _ = db.Close() + return nil, fmt.Errorf("failed to ping postgres: %w", err) + } + + l.Info("Connected to PostgreSQL") + return &PostgresRepository{db: db, logger: l}, nil +} + // SavePayload saves the payload information to the database. func (r *PostgresRepository) SavePayload(ctx context.Context, info *types.PayloadInfo) error { query := ` From 1b7064346f1c1518379b433ca5a415cc8707b61a Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Thu, 11 Sep 2025 18:45:20 -0700 Subject: [PATCH 30/34] logs --- cl/cmd/singlenode/main.go | 2 +- cl/singlenode/follower/follower.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cl/cmd/singlenode/main.go b/cl/cmd/singlenode/main.go index 5b80eb79c..dc1386aa3 100644 --- a/cl/cmd/singlenode/main.go +++ b/cl/cmd/singlenode/main.go @@ -411,7 +411,7 @@ func startFollowerNode(c *cli.Context) error { if err != nil { return fmt.Errorf("failed to create Ethereum engine client: %w", err) } - bb := blockbuilder.NewMemberBlockBuilder(engineCL, logger) + bb := blockbuilder.NewMemberBlockBuilder(engineCL, logger.With("component", "BlockBuilder")) followerNode, err := follower.NewFollower( logger, diff --git a/cl/singlenode/follower/follower.go b/cl/singlenode/follower/follower.go index 8cab788fe..914014bd2 100644 --- a/cl/singlenode/follower/follower.go +++ b/cl/singlenode/follower/follower.go @@ -164,6 +164,7 @@ func (f *Follower) handlePayloads(ctx context.Context) { f.logger.Error("Failed to process payload", "height", p.BlockHeight, "error", err) continue } + f.logger.Info("Successfully processed payload", "height", p.BlockHeight) } } } From fc1fd84ec29248addb3b43030e045830802f3c17 Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Thu, 11 Sep 2025 19:02:31 -0700 Subject: [PATCH 31/34] debugs --- cl/singlenode/follower/follower.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cl/singlenode/follower/follower.go b/cl/singlenode/follower/follower.go index 914014bd2..aa13ac95a 100644 --- a/cl/singlenode/follower/follower.go +++ b/cl/singlenode/follower/follower.go @@ -87,9 +87,11 @@ func (f *Follower) syncFromSharedDB(ctx context.Context) { f.logger.Error("failed to set execution head from rpc", "error", err) return } + f.logger.Debug("set execution head from rpc") } lastSignalledBlock := f.getExecutionHead().BlockHeight + f.logger.Debug("lastSignalledBlock set from execution head", "block height", lastSignalledBlock) for { select { @@ -112,6 +114,7 @@ func (f *Follower) syncFromSharedDB(ctx context.Context) { } blocksRemaining := targetBlock - lastSignalledBlock + f.logger.Debug("blocksRemaining", "blocksRemaining", blocksRemaining) if blocksRemaining == 0 { f.sleepRespectingContext(ctx, time.Millisecond) // New payload will likely be available within milliseconds @@ -133,6 +136,7 @@ func (f *Follower) syncFromSharedDB(ctx context.Context) { f.sleepRespectingContext(ctx, defaultBackoff) continue } + f.logger.Debug("number of payloads returned", "number of payloads", len(payloads)) for i := range payloads { p := payloads[i] From 1319bad6f1d2d0f90befa395ed3326bcdb41c58a Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Thu, 11 Sep 2025 19:06:59 -0700 Subject: [PATCH 32/34] Update follower.go --- cl/singlenode/follower/follower.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cl/singlenode/follower/follower.go b/cl/singlenode/follower/follower.go index aa13ac95a..6274efa7f 100644 --- a/cl/singlenode/follower/follower.go +++ b/cl/singlenode/follower/follower.go @@ -114,12 +114,12 @@ func (f *Follower) syncFromSharedDB(ctx context.Context) { } blocksRemaining := targetBlock - lastSignalledBlock - f.logger.Debug("blocksRemaining", "blocksRemaining", blocksRemaining) if blocksRemaining == 0 { f.sleepRespectingContext(ctx, time.Millisecond) // New payload will likely be available within milliseconds continue } + f.logger.Debug("non-zero blocksRemaining", "blocksRemaining", blocksRemaining) limit := min(f.syncBatchSize, blocksRemaining) From 7dd4ac35dfa27fdb831e0532139fc23b20abba92 Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Fri, 12 Sep 2025 09:52:47 -0700 Subject: [PATCH 33/34] remove stale store code --- cl/go.mod | 16 ++++++--- cl/go.sum | 54 ++++++++++++++++++++++++++++- cl/singlenode/follower/store.go | 60 --------------------------------- 3 files changed, 64 insertions(+), 66 deletions(-) delete mode 100644 cl/singlenode/follower/store.go diff --git a/cl/go.mod b/cl/go.mod index 2b207c911..3325d7dd5 100644 --- a/cl/go.mod +++ b/cl/go.mod @@ -9,7 +9,6 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.1 github.com/golang/mock v1.6.0 github.com/lib/pq v1.10.9 - github.com/primev/mev-commit/p2p v0.0.1 github.com/redis/go-redis/v9 v9.6.1 github.com/urfave/cli/v2 v2.27.5 golang.org/x/sync v0.11.0 @@ -18,9 +17,11 @@ require ( require ( github.com/BurntSushi/toml v1.4.0 // indirect + github.com/DataDog/zstd v1.5.5 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/StackExchange/wmi v1.2.1 // indirect github.com/bits-and-blooms/bitset v1.20.0 // indirect + github.com/cockroachdb/fifo v0.0.0-20240616162244-4768e80dfb9a // indirect github.com/consensys/bavard v0.1.27 // indirect github.com/consensys/gnark-crypto v0.16.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect @@ -30,16 +31,23 @@ require ( github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/ethereum/c-kzg-4844/v2 v2.1.0 // indirect github.com/ethereum/go-verkle v0.2.2 // indirect + github.com/getsentry/sentry-go v0.28.1 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/holiman/uint256 v1.3.2 // indirect + github.com/klauspost/compress v1.17.9 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nxadm/tail v1.4.11 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/onsi/gomega v1.30.0 // indirect + github.com/pion/dtls/v2 v2.2.11 // indirect + github.com/pion/transport/v2 v2.2.5 // indirect + github.com/pion/transport/v3 v3.0.2 // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/rs/cors v1.8.3 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect github.com/stretchr/objx v0.5.2 // indirect @@ -48,6 +56,7 @@ require ( github.com/tklauser/numcpus v0.7.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/mod v0.22.0 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) @@ -76,7 +85,4 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -replace ( - github.com/primev/mev-commit/p2p => ../p2p - github.com/primev/mev-commit/x => ../x -) +replace github.com/primev/mev-commit/x => ../x diff --git a/cl/go.sum b/cl/go.sum index c931508f8..f3f5a9ab3 100644 --- a/cl/go.sum +++ b/cl/go.sum @@ -44,6 +44,8 @@ github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOV github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM= github.com/crate-crypto/go-kzg-4844 v1.1.0 h1:EN/u9k2TF6OWSHrCCDBBU6GLNMq88OspHHlMnHfoyU4= github.com/crate-crypto/go-kzg-4844 v1.1.0/go.mod h1:JolLjpSff1tCCJKaJx4psrlEdlXuJEC996PL3tTAFks= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= @@ -141,15 +143,16 @@ github.com/pion/dtls/v2 v2.2.11 h1:9U/dpCYl1ySttROPWJgqWKEylUdT0fXp/xst6JwY5Ks= github.com/pion/dtls/v2 v2.2.11/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= -github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4= github.com/pion/stun/v2 v2.0.0 h1:A5+wXKLAypxQri59+tmQKVs7+l6mMM+3d+eER9ifRU0= github.com/pion/stun/v2 v2.0.0/go.mod h1:22qRSh08fSEttYUmJZGlriq9+03jtVmXNODgLccj8GQ= +github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= github.com/pion/transport/v2 v2.2.5 h1:iyi25i/21gQck4hfRhomF6SktmUQjRsRW4WJdhfc3Kc= github.com/pion/transport/v2 v2.2.5/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= github.com/pion/transport/v3 v3.0.2 h1:r+40RJR25S9w3jbA6/5uEPTzcdn7ncyU44RWCbHkLg4= github.com/pion/transport/v3 v3.0.2/go.mod h1:nIToODoOlb5If2jF9y2Igfx3PFYWfuXi37m0IlWa/D0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= @@ -173,8 +176,15 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf 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= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/supranational/blst v0.3.14 h1:xNMoHRJOTwMn63ip6qoWJ2Ymgvj7E2b9jY2FAwY+qRo= @@ -194,22 +204,40 @@ github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= 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.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -218,13 +246,34 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/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-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.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.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.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.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/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.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= @@ -232,6 +281,8 @@ golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +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/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -248,6 +299,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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= rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= diff --git a/cl/singlenode/follower/store.go b/cl/singlenode/follower/store.go deleted file mode 100644 index c0cf1d29b..000000000 --- a/cl/singlenode/follower/store.go +++ /dev/null @@ -1,60 +0,0 @@ -package follower - -import ( - "encoding/binary" - "errors" - "fmt" - "log/slog" - - "github.com/primev/mev-commit/p2p/pkg/storage" -) - -type Store struct { - logger *slog.Logger - kv storage.Storage -} - -const ( - progressKey = "follower/last_block" -) - -func NewStore(logger *slog.Logger, kv storage.Storage) *Store { - return &Store{ - logger: logger.With("component", "FollowerStore"), - kv: kv, - } -} - -func (s *Store) GetLastProcessed() (uint64, error) { - if s.kv == nil { - return 0, errors.New("kv is nil") - } - buf, err := s.kv.Get(progressKey) - switch { - case err == nil: - if len(buf) != 8 { - return 0, fmt.Errorf("invalid %q length: got %d, want 8", progressKey, len(buf)) - } - return binary.BigEndian.Uint64(buf), nil - case errors.Is(err, storage.ErrKeyNotFound): - return 0, nil - default: - return 0, err - } -} - -func (s *Store) SetLastProcessed(height uint64) error { - if s.kv == nil { - return errors.New("kv is nil") - } - var b [8]byte - binary.BigEndian.PutUint64(b[:], height) - return s.kv.Put(progressKey, b[:]) -} - -func (s *Store) Close() error { - if s.kv != nil { - return s.kv.Close() - } - return nil -} From ccd85cc57291013df961569773a681cb254e9d61 Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Fri, 12 Sep 2025 10:01:45 -0700 Subject: [PATCH 34/34] return errors --- cl/singlenode/follower/export.go | 4 ++-- cl/singlenode/follower/follower.go | 22 +++++++++----------- cl/singlenode/follower/follower_test.go | 27 ++++++++++++++++++++++--- 3 files changed, 36 insertions(+), 17 deletions(-) diff --git a/cl/singlenode/follower/export.go b/cl/singlenode/follower/export.go index 1be1aee9e..e32be7cdb 100644 --- a/cl/singlenode/follower/export.go +++ b/cl/singlenode/follower/export.go @@ -10,8 +10,8 @@ func (f *Follower) PayloadCh() <-chan types.PayloadInfo { return f.payloadCh } -func (f *Follower) SyncFromSharedDB(ctx context.Context) { - f.syncFromSharedDB(ctx) +func (f *Follower) SyncFromSharedDB(ctx context.Context) error { + return f.syncFromSharedDB(ctx) } func (f *Follower) GetExecutionHead() *types.ExecutionHead { diff --git a/cl/singlenode/follower/follower.go b/cl/singlenode/follower/follower.go index 6274efa7f..12bd5bbb7 100644 --- a/cl/singlenode/follower/follower.go +++ b/cl/singlenode/follower/follower.go @@ -3,6 +3,7 @@ package follower import ( "context" "errors" + "fmt" "log/slog" "sync" "time" @@ -61,14 +62,12 @@ func (f *Follower) Start(ctx context.Context) <-chan struct{} { done := make(chan struct{}) eg, egCtx := errgroup.WithContext(ctx) eg.Go(func() error { - f.handlePayloads(egCtx) - return nil + return f.handlePayloads(egCtx) }) eg.Go(func() error { f.logger.Info("Starting sync from shared DB") - f.syncFromSharedDB(egCtx) - return nil + return f.syncFromSharedDB(egCtx) }) go func() { @@ -81,11 +80,11 @@ func (f *Follower) Start(ctx context.Context) <-chan struct{} { return done } -func (f *Follower) syncFromSharedDB(ctx context.Context) { +func (f *Follower) syncFromSharedDB(ctx context.Context) error { if f.getExecutionHead() == nil { if err := f.setExecutionHeadFromRPC(ctx); err != nil { f.logger.Error("failed to set execution head from rpc", "error", err) - return + return err } f.logger.Debug("set execution head from rpc") } @@ -96,7 +95,7 @@ func (f *Follower) syncFromSharedDB(ctx context.Context) { for { select { case <-ctx.Done(): - return + return ctx.Err() default: } @@ -109,8 +108,7 @@ func (f *Follower) syncFromSharedDB(ctx context.Context) { } if lastSignalledBlock > targetBlock { - f.logger.Error("internal invariant has been broken. Follower EL is ahead of signer") - return + return fmt.Errorf("internal invariant has been broken. Follower EL is ahead of signer") } blocksRemaining := targetBlock - lastSignalledBlock @@ -142,7 +140,7 @@ func (f *Follower) syncFromSharedDB(ctx context.Context) { p := payloads[i] select { case <-ctx.Done(): - return + return ctx.Err() case f.payloadCh <- p: lastSignalledBlock = p.BlockHeight } @@ -158,11 +156,11 @@ func (f *Follower) sleepRespectingContext(ctx context.Context, duration time.Dur } } -func (f *Follower) handlePayloads(ctx context.Context) { +func (f *Follower) handlePayloads(ctx context.Context) error { for { select { case <-ctx.Done(): - return + return ctx.Err() case p := <-f.payloadCh: if err := f.finalizeBlock(ctx, p.PayloadID, p.ExecutionPayload, ""); err != nil { f.logger.Error("Failed to process payload", "height", p.BlockHeight, "error", err) diff --git a/cl/singlenode/follower/follower_test.go b/cl/singlenode/follower/follower_test.go index 0ccac169d..046132365 100644 --- a/cl/singlenode/follower/follower_test.go +++ b/cl/singlenode/follower/follower_test.go @@ -91,10 +91,15 @@ func TestFollower_syncFromSharedDB(t *testing.T) { return nil } + errCh := make(chan error) + ctx, cancel := context.WithCancel(context.Background()) defer cancel() go func() { - follower.SyncFromSharedDB(ctx) + err := follower.SyncFromSharedDB(ctx) + if err != nil { + errCh <- err + } }() payloadCh := follower.PayloadCh() @@ -123,6 +128,8 @@ func TestFollower_syncFromSharedDB(t *testing.T) { // No more than 50 select { + case err := <-errCh: + t.Fatal(err) case <-payloadCh: t.Fatal("received unexpected payload") case <-time.After(1 * time.Second): @@ -169,10 +176,15 @@ func TestFollower_syncFromSharedDB_NoRows(t *testing.T) { return nil } + errCh := make(chan error) + ctx, cancel := context.WithCancel(context.Background()) defer cancel() go func() { - follower.SyncFromSharedDB(ctx) + err := follower.SyncFromSharedDB(ctx) + if err != nil { + errCh <- err + } }() payloadCh := follower.PayloadCh() @@ -201,6 +213,8 @@ func TestFollower_syncFromSharedDB_NoRows(t *testing.T) { // No more than 15 select { + case err := <-errCh: + t.Fatal(err) case <-payloadCh: t.Fatal("received unexpected payload") case <-time.After(1 * time.Second): @@ -276,10 +290,15 @@ func TestFollower_syncFromSharedDB_MultipleIterations(t *testing.T) { return nil } + errCh := make(chan error) + ctx, cancel := context.WithCancel(context.Background()) defer cancel() go func() { - follower.SyncFromSharedDB(ctx) + err := follower.SyncFromSharedDB(ctx) + if err != nil { + errCh <- err + } }() payloadCh := follower.PayloadCh() @@ -308,6 +327,8 @@ func TestFollower_syncFromSharedDB_MultipleIterations(t *testing.T) { // No more than 53 select { + case err := <-errCh: + t.Fatal(err) case <-payloadCh: t.Fatal("received unexpected payload") case <-time.After(1 * time.Second):