Skip to content

chore: minor deduplication#3139

Open
tac0turtle wants to merge 3 commits intomainfrom
marko/simplify2
Open

chore: minor deduplication#3139
tac0turtle wants to merge 3 commits intomainfrom
marko/simplify2

Conversation

@tac0turtle
Copy link
Contributor

@tac0turtle tac0turtle commented Mar 7, 2026

Overview

this pr attempts a simple deduplication of some functions

Summary by CodeRabbit

  • Refactor

    • Consolidated metadata read/write handling for more consistent, maintainable behavior; no user-facing changes.
  • Tests

    • Standardized test setup to use a single in-memory store helper across suites.
  • Chores

    • CI build updated to include an additional application and expanded build context for its dependencies.
    • Linter configuration updated to exclude an additional rule.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 7, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5af550b0-8d1a-48bd-ac73-b324c73d32e7

📥 Commits

Reviewing files that changed from the base of the PR and between b4e9dbc and 2b3dcdb.

📒 Files selected for processing (1)
  • .golangci.yml

📝 Walkthrough

Walkthrough

Adds helpers to read/write 8-byte little-endian uint64 metadata and replaces manual encoding/decoding with them; test setups switch from memStore(t) to testMemStore(t); CI adds an ev-node-grpc app build and the gRPC Dockerfile copies core/go.sum as well.

Changes

Cohort / File(s) Summary
Metadata helpers & usages
block/internal/cache/manager.go, block/internal/submitting/submitter.go
Added getMetadataUint64(ctx, st, key) (uint64, bool) and putUint64Metadata(ctx, st, key, val); refactored DA height, header/data, and genesis metadata reads/writes to use these helpers instead of manual 8-byte encoding/decoding.
Test store initialization
block/internal/cache/manager_test.go, block/internal/cache/pending_data_test.go, block/internal/cache/pending_headers_test.go
Replaced memStore(t) with testMemStore(t) across tests and removed the old memStore helper; test logic unchanged.
CI workflow
.github/workflows/ci.yml
Added ev-node-grpc to the docker build apps list so apps/grpc/Dockerfile is included in CI builds.
gRPC Dockerfile
apps/grpc/Dockerfile
COPY updated to include core/go.sum alongside core/go.mod into the ./core/ build context.
Lint config
.golangci.yml
Added G118 to the gosec excludes list.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Suggested reviewers

  • Manav-Aggarwal
  • MSevey
  • tzdybal

Poem

🐇 I hopped through bytes both small and round,

Little-endian treasures safely found.
Tests now nest in a cleaner space,
CI builds a new gRPC place —
Hooray, the cache keeps bound and sound! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'chore: minor deduplication' accurately reflects the main changes: it's a refactoring PR that consolidates duplicated code by introducing helper functions (getMetadataUint64 and putUint64Metadata) to reduce code repetition across multiple files.
Description check ✅ Passed The PR description is minimal but directly addresses the PR objective. It states 'this pr attempts a simple deduplication of some functions,' which matches the intent described in the objectives. However, it lacks detailed context, rationale, and specific information about which functions are being deduplicated.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch marko/simplify2

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 7, 2026

The latest Buf updates on your PR. Results from workflow CI / buf-check (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed⏩ skipped✅ passed✅ passedMar 8, 2026, 7:34 AM

@claude
Copy link
Contributor

claude bot commented Mar 7, 2026

Claude finished @tac0turtle's task in 1m 27s —— View job


Code Review

  • Gather context and read changed files
  • Analyze code quality and architecture
  • Review error handling and resilience
  • Check security concerns
  • Evaluate performance and testing
  • Post final review

Overall, this is a clean deduplication PR. The helper functions are a good idea. However, there are two substantive bugs and one missed deduplication that are worth addressing.


Critical: Persist before advancing in-memory state (submitter.go:352-363)

The current ordering in processDAInclusionLoop is:

// 1. Advance in-memory state
s.SetDAIncludedHeight(nextHeight)
currentDAIncluded = nextHeight

// 2. Persist (may fail!)
if err := putUint64Metadata(s.ctx, s.store, store.DAIncludedHeightKey, nextHeight); err != nil {
    s.logger.Error().Err(err).Uint64("height", nextHeight).Msg("failed to persist DA included height")
}

// 3. Delete cache
s.cache.DeleteHeight(nextHeight)

If putUint64Metadata fails, in-memory state has advanced but the store still reflects the old height. Then s.cache.DeleteHeight(nextHeight) removes the cache entries. On restart, the node reads the stale height from store, tries to re-process nextHeight, but finds the cache empty — the data needed to reconstruct inclusion is gone. This is a correctness bug.

The fix is to persist first, then update in-memory state and delete the cache only on success (as CodeRabbit flagged). Fix this →


Major: getMetadataUint64 silently swallows real store errors (manager.go:392-398)

func getMetadataUint64(ctx context.Context, st store.Store, key string) (uint64, bool) {
    b, err := st.GetMetadata(ctx, key)
    if err != nil || len(b) != 8 {  // real errors collapsed into "not found"
        return 0, false
    }
    return binary.LittleEndian.Uint64(b), true
}

A transient I/O error is indistinguishable from a legitimately absent key. initDAHeightFromStore then silently skips seeding DaHeight(), so on restart the node can reset its DA height to 0 with no diagnostic output — precisely what this function was added to prevent.

Recommend returning (uint64, bool, error) with ok=false only for genuine not-found, and a non-nil error for backend failures or malformed values. Then initDAHeightFromStore can log real errors distinctly. (Also flagged by CodeRabbit.) Fix this →


Minor: Missed deduplication in initializeDAIncludedHeight (submitter.go:409-413)

initializeDAIncludedHeight still manually decodes the uint64 instead of using the new getMetadataUint64 helper:

func (s *Submitter) initializeDAIncludedHeight(ctx context.Context) error {
    if height, err := s.store.GetMetadata(ctx, store.DAIncludedHeightKey); err == nil && len(height) == 8 {
        s.SetDAIncludedHeight(binary.LittleEndian.Uint64(height))
    }
    return nil
}

This is the exact pattern that was extracted into a helper — it was missed here. Once getMetadataUint64 returns an error value, this can use it directly.


Minor: Inconsistent test conversion (pending_data_test.go:183-186)

TestPendingData_InitFromMetadata was not converted to use testMemStore(t):

// still uses the old pattern
ds, err := store.NewTestInMemoryKVStore()
require.NoError(t, err)
store := store.New(ds)

All other tests in the file use testMemStore(t). This also shadows the imported store package name with a local variable, which is a minor code smell.


Observation: Helper placement asymmetry

getMetadataUint64 lives in block/internal/cache/manager.go while putUint64Metadata lives in block/internal/submitting/submitter.go. They are symmetric operations doing the same encode/decode pattern. Ideally both helpers would live in the same package (e.g., the store package, or a shared internal utility) to avoid future drift. Not a blocker for this PR, but worth noting as a follow-up.


What's good

  • The deduplication intent is clear and the helpers are well-named.
  • Test standardization on testMemStore(t) is a nice cleanup.
  • The Dockerfile fix to include core/go.sum is a real correctness fix.
  • CI addition of ev-node-grpc to build matrix is straightforward.

@tac0turtle tac0turtle marked this pull request as ready for review March 7, 2026 15:52
@tac0turtle tac0turtle requested a review from julienrbrt March 7, 2026 15:52
@codecov
Copy link

codecov bot commented Mar 7, 2026

Codecov Report

❌ Patch coverage is 81.81818% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 61.12%. Comparing base (a96974b) to head (2b3dcdb).

Files with missing lines Patch % Lines
block/internal/submitting/submitter.go 63.63% 0 Missing and 4 partials ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3139   +/-   ##
=======================================
  Coverage   61.12%   61.12%           
=======================================
  Files         113      113           
  Lines       11676    11674    -2     
=======================================
- Hits         7137     7136    -1     
- Misses       3737     3738    +1     
+ Partials      802      800    -2     
Flag Coverage Δ
combined 61.12% <81.81%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@block/internal/cache/manager.go`:
- Around line 390-405: getMetadataUint64 currently collapses backend errors and
malformed values into a missing result; change its signature to return (uint64,
bool, error) so it returns the raw error when st.GetMetadata fails or when
len(b) != 8 (e.g., return an fmt.Errorf describing the malformed length), and
only use ok==false for the explicit not-found case; then update
initDAHeightFromStore to call value, ok, err := getMetadataUint64(ctx, m.store,
store.DAIncludedHeightKey), handle/log err distinctly (e.g.,
m.logger.Error/return) and only treat the not-found (ok==false) path as a
harmless skip, preserving transient read failures for diagnostics.

In `@block/internal/submitting/submitter.go`:
- Around line 357-359: The DA included height is advanced and the per-height
cache is deleted even when putUint64Metadata(s.ctx, s.store,
store.DAIncludedHeightKey, nextHeight) fails; modify the flow so you only update
s.daIncludedHeight and remove the per-height cache after putUint64Metadata
returns nil. Concretely, check the error from putUint64Metadata and on non-nil
return/propagate the error (or retry) without mutating s.daIncludedHeight or
performing the per-height cache deletion; only after a successful
putUint64Metadata call set s.daIncludedHeight = nextHeight and then delete the
per-height cache. Ensure you log the failure as you do now via
s.logger.Error().Err(err)... but avoid dropping in-memory state when persistence
fails.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c63884b7-8812-49b2-aa3e-9a1c928062f8

📥 Commits

Reviewing files that changed from the base of the PR and between a96974b and 2025893.

📒 Files selected for processing (5)
  • block/internal/cache/manager.go
  • block/internal/cache/manager_test.go
  • block/internal/cache/pending_data_test.go
  • block/internal/cache/pending_headers_test.go
  • block/internal/submitting/submitter.go

Comment on lines +390 to +405
// getMetadataUint64 reads an 8-byte little-endian uint64 from store metadata.
// Returns 0, false if the key is absent, errors, or the value is not 8 bytes.
func getMetadataUint64(ctx context.Context, st store.Store, key string) (uint64, bool) {
b, err := st.GetMetadata(ctx, key)
if err != nil || len(b) != 8 {
return 0, false
}
return binary.LittleEndian.Uint64(b), true
}

// initDAHeightFromStore seeds maxDAHeight from the HeightToDAHeight metadata
// written by the submitter for the last finalized block. This ensures
// DaHeight() is non-zero on restart even when the in-flight snapshot is empty.
func (m *implementation) initDAHeightFromStore(ctx context.Context) {
daIncludedBytes, err := m.store.GetMetadata(ctx, store.DAIncludedHeightKey)
if err != nil || len(daIncludedBytes) != 8 {
return
}
daIncludedHeight := binary.LittleEndian.Uint64(daIncludedBytes)
if daIncludedHeight == 0 {
daIncludedHeight, ok := getMetadataUint64(ctx, m.store, store.DAIncludedHeightKey)
if !ok || daIncludedHeight == 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Preserve metadata read failures here instead of treating them as “missing”.

getMetadataUint64 now maps not-found, backend read errors, and malformed values to the same false result. initDAHeightFromStore then silently skips seeding DaHeight(), so a transient store failure can look like “no metadata” and reset startup state with no diagnostic.

Suggested fix
-// getMetadataUint64 reads an 8-byte little-endian uint64 from store metadata.
-// Returns 0, false if the key is absent, errors, or the value is not 8 bytes.
-func getMetadataUint64(ctx context.Context, st store.Store, key string) (uint64, bool) {
+// getMetadataUint64 reads an 8-byte little-endian uint64 from store metadata.
+// Returns ok=false only when the key is genuinely absent.
+func getMetadataUint64(ctx context.Context, st store.Store, key string) (uint64, bool, error) {
 	b, err := st.GetMetadata(ctx, key)
-	if err != nil || len(b) != 8 {
-		return 0, false
+	if err != nil {
+		return 0, false, fmt.Errorf("read metadata %q: %w", key, err)
 	}
+	if len(b) != 8 {
+		return 0, false, fmt.Errorf("invalid metadata length for %q: %d", key, len(b))
+	}
-	return binary.LittleEndian.Uint64(b), true
+	return binary.LittleEndian.Uint64(b), true, nil
 }

Then have initDAHeightFromStore log/handle the error separately and only ignore the real not-found case.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@block/internal/cache/manager.go` around lines 390 - 405, getMetadataUint64
currently collapses backend errors and malformed values into a missing result;
change its signature to return (uint64, bool, error) so it returns the raw error
when st.GetMetadata fails or when len(b) != 8 (e.g., return an fmt.Errorf
describing the malformed length), and only use ok==false for the explicit
not-found case; then update initDAHeightFromStore to call value, ok, err :=
getMetadataUint64(ctx, m.store, store.DAIncludedHeightKey), handle/log err
distinctly (e.g., m.logger.Error/return) and only treat the not-found
(ok==false) path as a harmless skip, preserving transient read failures for
diagnostics.

Comment on lines +357 to 359
if err := putUint64Metadata(s.ctx, s.store, store.DAIncludedHeightKey, nextHeight); err != nil {
s.logger.Error().Err(err).Uint64("height", nextHeight).Msg("failed to persist DA included height")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't advance past this height unless DAIncludedHeightKey was persisted.

Line 363 still deletes the per-height cache even when this write fails, but s.daIncludedHeight has already been advanced. That leaves durable state behind memory, and on restart the node can no longer reconstruct inclusion from the cache it just dropped.

Suggested fix
-				// Update DA included height
-				s.SetDAIncludedHeight(nextHeight)
-				currentDAIncluded = nextHeight
-
-				// Persist DA included height
-				if err := putUint64Metadata(s.ctx, s.store, store.DAIncludedHeightKey, nextHeight); err != nil {
+				// Persist DA included height before advancing in-memory state
+				if err := putUint64Metadata(s.ctx, s.store, store.DAIncludedHeightKey, nextHeight); err != nil {
 					s.logger.Error().Err(err).Uint64("height", nextHeight).Msg("failed to persist DA included height")
+					break
 				}
 
+				// Update DA included height
+				s.SetDAIncludedHeight(nextHeight)
+				currentDAIncluded = nextHeight
+
 				// Delete height cache for that height
 				// This can only be performed after the height has been persisted to store
 				s.cache.DeleteHeight(nextHeight)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@block/internal/submitting/submitter.go` around lines 357 - 359, The DA
included height is advanced and the per-height cache is deleted even when
putUint64Metadata(s.ctx, s.store, store.DAIncludedHeightKey, nextHeight) fails;
modify the flow so you only update s.daIncludedHeight and remove the per-height
cache after putUint64Metadata returns nil. Concretely, check the error from
putUint64Metadata and on non-nil return/propagate the error (or retry) without
mutating s.daIncludedHeight or performing the per-height cache deletion; only
after a successful putUint64Metadata call set s.daIncludedHeight = nextHeight
and then delete the per-height cache. Ensure you log the failure as you do now
via s.logger.Error().Err(err)... but avoid dropping in-memory state when
persistence fails.

Copy link
Member

@julienrbrt julienrbrt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants