Skip to content

Metadata Service postgres db implementation#4846

Open
sergekh2 wants to merge 15 commits into
mainfrom
serge/metadata-service-01
Open

Metadata Service postgres db implementation#4846
sergekh2 wants to merge 15 commits into
mainfrom
serge/metadata-service-01

Conversation

@sergekh2

@sergekh2 sergekh2 commented Jan 9, 2026

Copy link
Copy Markdown
Member

No description provided.

@sergekh2 sergekh2 requested a review from bas-vk as a code owner January 9, 2026 01:53
@vercel

vercel Bot commented Jan 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
river-sample-app Ready Ready Preview, Comment Jan 9, 2026 2:56am

@cursor

cursor Bot commented Jan 9, 2026

Copy link
Copy Markdown

PR Summary

Introduces a PostgreSQL-backed metadata store with block-ordered updates, notifications, and comprehensive tests.

  • Adds migrations defining md_stream_records, md_stream_placement (GIN index), md_blocks, and md_last_block with trigger notify_md_last_block publishing on md_record_block
  • Implements PostgresMetadataServiceStore with: paginated listing (optionally by node), BatchUpdateStreamRecords (insert/placement/miniblock) producing md_blocks with event masks and optional trimming, record getters/counts, block range queries, and LISTEN/NOTIFY for new blocks
  • Enforces validation (node indexes, replication_factor, sequencing, sealed), uses repeatable-read snapshots, and returns per-update errors
  • Adds thorough tests covering listing, counts, updates, error cases, trimming behavior, block queries, and notification handling

Written by Cursor Bugbot for commit 388106d. This will update automatically on new commits. Configure here.

@coderabbitai

coderabbitai Bot commented Jan 9, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds a PostgreSQL-backed metadata service: new SQL migrations for versioned, block-serialized metadata tables and notifications; a PostgresMetadataServiceStore implementing block-based updates, listing, counts, LISTEN/NOTIFY and validation; and comprehensive tests for store behavior.

Changes

Cohort / File(s) Summary
DB migrations
core/node/storage/metadata_migrations/000001_create_initial_schema.up.sql, core/node/storage/metadata_migrations/000001_create_initial_schema.down.sql
Creates md_stream_records, md_stream_placement (with GIN index), md_blocks, md_last_block; adds notify_md_last_block() function and notify_on_md_last_block trigger; inserts initial md_last_block row; down migration drops these objects.
Postgres metadata store implementation
core/node/storage/pg_metadata_service.go
Adds MetadataServiceStore interface, types (record, block, update variants), and PostgresMetadataServiceStore with SQL-backed implementations for listing, batch updates (block insertion/trimming), single-record retrieval, counts, GetRecordBlocks, LISTEN/NOTIFY handling, transactional helpers, and validation logic.
Unit tests
core/node/storage/pg_metadata_service_test.go
New tests covering listing/get/counts, BatchUpdateStreamRecords (success and error paths), block trimming semantics, GetRecordBlocks validation, and OnNewRecordBlock notification and lifecycle behavior.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • bas-vk
  • texuf
🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive No description provided; this is a completely empty description that conveys no meaningful information about the changeset. Add a brief description explaining the purpose of the Metadata Service implementation, key components (schema, store interface, tests), and any relevant context for reviewers.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly summarizes the main change: introducing a PostgreSQL implementation for the Metadata Service with schema, store, and tests.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch serge/metadata-service-01

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 40eb02d and 388106d.

📒 Files selected for processing (2)
  • core/node/storage/pg_metadata_service.go
  • core/node/storage/pg_metadata_service_test.go
🧰 Additional context used
📓 Path-based instructions (8)
core/**/*.go

📄 CodeRabbit inference engine (core/AGENTS.md)

core/**/*.go: Format all modified Go files using ./fmt.sh from the /core directory before committing
Run the linter using ./lint.sh from the /core directory to check code quality, which runs both golangci-lint and staticcheck
Use RiverError type from node/base/error.go for structured errors
Use RiverErrorWithBase to construct a river error derived from an error of arbitrary or unknown type
Wrap blockchain errors with context using crypto/ utilities
Log errors with structured fields using zap logger
Use node/crypto/blockchain.go for Ethereum client operations
Use node/crypto/chain_monitor.go for transaction monitoring
Use xchain/entitlement/ for cross-chain entitlement checks
Do not run ./fmt.sh and ./lint.sh at the same time; run them as separate commands
Use go.uber.org/zap for structured logging with custom extensions from node/logging/
Use go.opentelemetry.io for distributed tracing across the application
Use prometheus for OpenMetrics in application metrics collection

Files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
core/**/*_test.go

📄 CodeRabbit inference engine (core/AGENTS.md)

core/**/*_test.go: Use testify/require and testify/assert for test assertions in Go test files
Database tests should use testutils/dbtestutils for test DB lifecycle management
Do not set GOCACHE or GOMODCACHE when running Go test commands; run go commands directly without setting extra environment variables
Set RIVER_TEST_LOG=info and RIVER_TEST_PRINT=1 environment variables for debug output in tests
Mock implementations should be used from testutils/mocks/ in tests

Files:

  • core/node/storage/pg_metadata_service_test.go
core/node/storage/pg_*.go

📄 CodeRabbit inference engine (core/AGENTS.md)

core/node/storage/pg_*.go: Use PostgreSQL transactions for consistency in all storage operations
All storage operations should be in node/storage/pg_*.go files

Files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
core/node/storage/**/*.go

📄 CodeRabbit inference engine (core/AGENTS.md)

core/node/storage/**/*.go: Use PostgreSQL transactions in node/storage/ for all database operations to ensure consistency
Use jackc/pgx/v5 for PostgreSQL connection pooling and database driver operations

Files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
**/core/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/core/**/*.go: Format all Go files using the fmt.sh script from the /core directory
Run the linter using the lint.sh script from the /core directory for all Go code changes
Use structured logging with zap for Go development
Implement proper error handling with RiverError types in Go code
Implement proper context cancellation for graceful shutdowns in Go code

Files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
**/core/**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

**/core/**/*_test.go: Use testify for test assertions in Go tests
Make tests re-runnable by using unique identifiers (e.g., UUIDs) instead of hardcoded values

Files:

  • core/node/storage/pg_metadata_service_test.go
**/core/node/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Use structured logging with zap in Go backend River nodes for observability

Files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
**/core/node/storage/**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

Storage tests should verify data persistence and constraints for database schema changes

Files:

  • core/node/storage/pg_metadata_service_test.go
🧠 Learnings (26)
📓 Common learnings
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Make changes to storage interfaces first, then implement PostgreSQL-specific logic, write comprehensive tests, benchmark critical paths, and update migrations if schema changes are needed
📚 Learning: 2025-12-17T02:40:57.825Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/AGENTS.md:0-0
Timestamp: 2025-12-17T02:40:57.825Z
Learning: Applies to core/node/storage/pg_*.go : All storage operations should be in node/storage/pg_*.go files

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
📚 Learning: 2026-01-08T18:27:30.866Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-08T18:27:30.866Z
Learning: Applies to **/core/node/storage/**/*_test.go : Storage tests should verify data persistence and constraints for database schema changes

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-12-17T02:40:57.825Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/AGENTS.md:0-0
Timestamp: 2025-12-17T02:40:57.825Z
Learning: Applies to core/node/storage/**/*.go : Use PostgreSQL transactions in node/storage/ for all database operations to ensure consistency

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-12-17T02:40:57.825Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/AGENTS.md:0-0
Timestamp: 2025-12-17T02:40:57.825Z
Learning: Applies to core/node/storage/pg_*.go : Use PostgreSQL transactions for consistency in all storage operations

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*_test.go : Implement table-driven tests when testing multiple scenarios to improve maintainability and coverage

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*_test.go : Use helper functions to generate test data consistently: `safeAddress(t)` for Ethereum addresses, `testAppMetadataWithName(name string)` for app metadata, `testutils.FakeStreamId(streamType)` for stream IDs

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*.go : Extract transaction logic into separate `*Tx` methods for better organization and testability in PostgreSQL storage implementations

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
📚 Learning: 2025-11-25T08:47:37.754Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/rpc/AGENTS.md:0-0
Timestamp: 2025-11-25T08:47:37.754Z
Learning: Applies to core/node/rpc/**/*_test.go : Generate test wallets and metadata using helper functions like `safeNewWallet()` and `appMetadataForBot()` for realistic test data

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*_test.go : Always test edge cases including: empty/nil values, non-existent entities (NOT_FOUND errors), duplicate entries (ALREADY_EXISTS errors), concurrent operations, and transaction rollbacks

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*_test.go : All storage tests should follow the consistent setup pattern using helper functions: `setupStreamStorageTest(t)`, `setupAppRegistryStorageTest(t)`, or `prepareNotificationsDB(ctx)` depending on storage type, with automatic cleanup via `t.Cleanup(params.closer)`

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*.go : Enforce miniblock requirement constraint: if a stream exists in the `es` (event streams) table, it MUST have at least one miniblock; empty streams are not allowed

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*_test.go : Use `require.New(t)` at the beginning of test functions and check error codes with `base.IsRiverErrorCode(err, code)` for specific error type verification

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*.go : Use `lockStream(ctx, tx, streamId, true)` with `FOR UPDATE` for write operations and `lockStream(ctx, tx, streamId, false)` with `FOR SHARE` for read operations to ensure proper row-level locking

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*.go : Always include stream ID in queries to leverage table partitioning (256 partitions by default)

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:47:37.754Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/rpc/AGENTS.md:0-0
Timestamp: 2025-11-25T08:47:37.754Z
Learning: Applies to core/node/rpc/**/*_test.go : Enable River logs with `enableRiverLogs: true` in tester options when debugging test failures

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
📚 Learning: 2025-12-17T02:40:57.825Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/AGENTS.md:0-0
Timestamp: 2025-12-17T02:40:57.825Z
Learning: Applies to core/node/storage/**/*.go : Use jackc/pgx/v5 for PostgreSQL connection pooling and database driver operations

Applied to files:

  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:47:01.034Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/app_registry/AGENTS.md:0-0
Timestamp: 2025-11-25T08:47:01.034Z
Learning: Applies to core/node/app_registry/**/app_registry/**/*.go : When adding storage methods, implement in three layers: AppRegistryStore interface, PostgresAppRegistryStore implementation, and CachedEncryptedMessageQueue delegation

Applied to files:

  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*.go : Use `COPY FROM` for atomic batch miniblock writes instead of individual insert statements in loops

Applied to files:

  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Make changes to storage interfaces first, then implement PostgreSQL-specific logic, write comprehensive tests, benchmark critical paths, and update migrations if schema changes are needed

Applied to files:

  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*.go : The `last_snapshot_miniblock_num` in the `es` table MUST always point to one of the miniblocks within the last continuous miniblock sequence, and the pointer only grows forward

Applied to files:

  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*.go : Enforce minipool generation sequencing constraint: the minipool generation number MUST always be set to the last miniblock number + 1

Applied to files:

  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*.go : Use the `txRunner` function for all database operations to provide automatic retry for serialization failures and deadlocks, exponential backoff with jitter, transaction tracking, debugging, and comprehensive metrics and logging

Applied to files:

  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*.go : Keep transactions short-lived and use appropriate access modes (ReadOnly vs ReadWrite) in txRunner calls

Applied to files:

  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*.go : Add transaction tags for debugging in txRunner calls to improve transaction tracking and monitoring

Applied to files:

  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*.go : Handle context cancellation properly; disable context cancellation for write operations in txRunner

Applied to files:

  • core/node/storage/pg_metadata_service.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Common_CI
  • GitHub Check: Multinode
  • GitHub Check: Go_Tests
  • GitHub Check: Multinode_Ent_Legacy
  • GitHub Check: Multinode_Ent
  • GitHub Check: Cursor Bugbot
  • GitHub Check: XChain_Integration
🔇 Additional comments (20)
core/node/storage/pg_metadata_service.go (12)

1-38: LGTM!

Imports, constants, and event mask definitions are well-structured.


40-89: LGTM!

Type definitions are clear and well-organized. The discriminated union pattern using pointers for MetadataStreamRecordUpdate is appropriate.


91-151: LGTM!

Interface is well-documented with clear semantics for pagination, error handling, and async notifications.


153-202: LGTM!

Constructor and initialization follow established patterns. Proper use of AsRiverError for error wrapping.


204-349: LGTM!

List methods correctly use RepeatableRead isolation for snapshot consistency. Defensive copy of NodeIndexes at line 320 prevents aliasing issues.


351-432: LGTM!

Batch update correctly uses FOR UPDATE on md_last_block for serialized block assignment. Per-update error tracking and block trimming logic are correct.


434-479: LGTM!

Update dispatcher correctly validates exactly one operation type and routes appropriately.


481-596: LGTM!

Insert validation is thorough. Handles race condition by catching UniqueViolation as fallback to EXISTS check.


598-712: LGTM!

Placement update handles partial updates correctly, validating constraints against existing record when needed.


714-800: LGTM!

Miniblock update correctly validates hash chain continuity and sequential block numbers. Sealing is one-way as expected.


802-1009: LGTM!

Listener implementation handles connection lifecycle, reconnection with backoff, and graceful cancellation correctly. Defensive fallback to GetLastRecordBlockNum on parse errors is a nice touch.


1011-1257: LGTM!

GetRecordBlocks properly validates range and orders results. Helper functions follow established patterns with FOR UPDATE for write operations per coding guidelines.

core/node/storage/pg_metadata_service_test.go (8)

1-73: LGTM!

Test setup follows established patterns with proper cleanup via t.Cleanup.


75-84: LGTM!

Clean helpers for deterministic test data generation.


86-212: LGTM!

Comprehensive test covering list pagination, node filtering, record retrieval, counts, and event mask verification.


214-334: LGTM!

Good coverage of partial batch success, per-update error handling, and verification that failed updates don't create block entries.


336-401: LGTM!

Correctly verifies block trimming behavior - oldest block (0) deleted, keeping most recent (1, 2).


403-468: LGTM!

Correctly verifies that numBlocksToKeep=0 disables trimming.


470-481: LGTM!

Simple validation tests for edge cases.


483-536: LGTM!

Good coverage of LISTEN/NOTIFY functionality with proper timeout handling and channel cleanup verification after cancellation.


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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
core/node/storage/metadata_migrations/000001_create_initial_schema.up.sql (1)

27-47: LGTM!

Composite primary key (block_num, block_slot) is appropriate for ordered block retrieval. Comment at line 31-32 has a typo: "event not updated" should likely be "even if not updated".

core/node/storage/pg_metadata_service.go (1)

912-939: Variable shadowing at line 926.

blockNum is redeclared in the inner scope, which shadows the outer blockNum. While the behavior is correct (returns immediately), this can be confusing.

♻️ Suggested fix
 	blockNum, err := strconv.ParseInt(payload, 10, 64)
 	if err != nil {
-		blockNum, lastErr := s.GetLastRecordBlockNum(ctx)
+		fallbackBlockNum, lastErr := s.GetLastRecordBlockNum(ctx)
 		if lastErr != nil {
 			return 0, RiverErrorWithBase(
 				protocol.Err_INVALID_ARGUMENT,
 				"invalid block notification payload",
 				err,
 				"payload",
 				payload,
 			).Func("parseRecordBlockNotification")
 		}
-		return blockNum, nil
+		return fallbackBlockNum, nil
 	}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d81db9a and 2fcd2ec.

📒 Files selected for processing (4)
  • core/node/storage/metadata_migrations/000001_create_initial_schema.down.sql
  • core/node/storage/metadata_migrations/000001_create_initial_schema.up.sql
  • core/node/storage/pg_metadata_service.go
  • core/node/storage/pg_metadata_service_test.go
🧰 Additional context used
📓 Path-based instructions (8)
core/**/*.go

📄 CodeRabbit inference engine (core/AGENTS.md)

core/**/*.go: Format all modified Go files using ./fmt.sh from the /core directory before committing
Run the linter using ./lint.sh from the /core directory to check code quality, which runs both golangci-lint and staticcheck
Use RiverError type from node/base/error.go for structured errors
Use RiverErrorWithBase to construct a river error derived from an error of arbitrary or unknown type
Wrap blockchain errors with context using crypto/ utilities
Log errors with structured fields using zap logger
Use node/crypto/blockchain.go for Ethereum client operations
Use node/crypto/chain_monitor.go for transaction monitoring
Use xchain/entitlement/ for cross-chain entitlement checks
Do not run ./fmt.sh and ./lint.sh at the same time; run them as separate commands
Use go.uber.org/zap for structured logging with custom extensions from node/logging/
Use go.opentelemetry.io for distributed tracing across the application
Use prometheus for OpenMetrics in application metrics collection

Files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
core/**/*_test.go

📄 CodeRabbit inference engine (core/AGENTS.md)

core/**/*_test.go: Use testify/require and testify/assert for test assertions in Go test files
Database tests should use testutils/dbtestutils for test DB lifecycle management
Do not set GOCACHE or GOMODCACHE when running Go test commands; run go commands directly without setting extra environment variables
Set RIVER_TEST_LOG=info and RIVER_TEST_PRINT=1 environment variables for debug output in tests
Mock implementations should be used from testutils/mocks/ in tests

Files:

  • core/node/storage/pg_metadata_service_test.go
core/node/storage/pg_*.go

📄 CodeRabbit inference engine (core/AGENTS.md)

core/node/storage/pg_*.go: Use PostgreSQL transactions for consistency in all storage operations
All storage operations should be in node/storage/pg_*.go files

Files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
core/node/storage/**/*.go

📄 CodeRabbit inference engine (core/AGENTS.md)

core/node/storage/**/*.go: Use PostgreSQL transactions in node/storage/ for all database operations to ensure consistency
Use jackc/pgx/v5 for PostgreSQL connection pooling and database driver operations

Files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
**/core/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/core/**/*.go: Format all Go files using the fmt.sh script from the /core directory
Run the linter using the lint.sh script from the /core directory for all Go code changes
Use structured logging with zap for Go development
Implement proper error handling with RiverError types in Go code
Implement proper context cancellation for graceful shutdowns in Go code

Files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
**/core/**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

**/core/**/*_test.go: Use testify for test assertions in Go tests
Make tests re-runnable by using unique identifiers (e.g., UUIDs) instead of hardcoded values

Files:

  • core/node/storage/pg_metadata_service_test.go
**/core/node/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Use structured logging with zap in Go backend River nodes for observability

Files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
**/core/node/storage/**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

Storage tests should verify data persistence and constraints for database schema changes

Files:

  • core/node/storage/pg_metadata_service_test.go
🧠 Learnings (19)
📓 Common learnings
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Make changes to storage interfaces first, then implement PostgreSQL-specific logic, write comprehensive tests, benchmark critical paths, and update migrations if schema changes are needed
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/AGENTS.md:0-0
Timestamp: 2025-12-17T02:40:57.825Z
Learning: Applies to core/node/storage/**/*.go : Use PostgreSQL transactions in node/storage/ for all database operations to ensure consistency
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/AGENTS.md:0-0
Timestamp: 2025-12-17T02:40:57.825Z
Learning: Applies to core/node/storage/pg_*.go : Use PostgreSQL transactions for consistency in all storage operations
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/AGENTS.md:0-0
Timestamp: 2025-12-17T02:40:57.825Z
Learning: Applies to core/node/storage/pg_*.go : All storage operations should be in node/storage/pg_*.go files
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-08T18:27:30.866Z
Learning: Applies to **/core/node/storage/**/*_test.go : Storage tests should verify data persistence and constraints for database schema changes
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/app_registry/AGENTS.md:0-0
Timestamp: 2025-11-25T08:47:01.034Z
Learning: Applies to core/node/app_registry/**/app_registry/**/*.go : When adding storage methods, implement in three layers: AppRegistryStore interface, PostgresAppRegistryStore implementation, and CachedEncryptedMessageQueue delegation
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/AGENTS.md:0-0
Timestamp: 2025-12-17T02:40:57.825Z
Learning: Applies to core/node/storage/**/*.go : Use jackc/pgx/v5 for PostgreSQL connection pooling and database driver operations
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*.go : Use `COPY FROM` for atomic batch miniblock writes instead of individual insert statements in loops
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/migrations/*.sql : Migrations use golang-migrate with embedded SQL files; always test migrations on a copy of production data and consider partition count impacts
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*.go : Always include stream ID in queries to leverage table partitioning (256 partitions by default)
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Make changes to storage interfaces first, then implement PostgreSQL-specific logic, write comprehensive tests, benchmark critical paths, and update migrations if schema changes are needed

Applied to files:

  • core/node/storage/metadata_migrations/000001_create_initial_schema.up.sql
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*.go : Enforce miniblock requirement constraint: if a stream exists in the `es` (event streams) table, it MUST have at least one miniblock; empty streams are not allowed

Applied to files:

  • core/node/storage/metadata_migrations/000001_create_initial_schema.up.sql
  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*.go : Always include stream ID in queries to leverage table partitioning (256 partitions by default)

Applied to files:

  • core/node/storage/metadata_migrations/000001_create_initial_schema.up.sql
  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
📚 Learning: 2026-01-08T18:27:30.866Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-08T18:27:30.866Z
Learning: Applies to **/core/node/storage/**/*_test.go : Storage tests should verify data persistence and constraints for database schema changes

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*_test.go : Implement table-driven tests when testing multiple scenarios to improve maintainability and coverage

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
📚 Learning: 2025-12-17T02:40:57.825Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/AGENTS.md:0-0
Timestamp: 2025-12-17T02:40:57.825Z
Learning: Applies to core/node/storage/pg_*.go : All storage operations should be in node/storage/pg_*.go files

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-12-17T02:40:57.825Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/AGENTS.md:0-0
Timestamp: 2025-12-17T02:40:57.825Z
Learning: Applies to core/node/storage/**/*.go : Use PostgreSQL transactions in node/storage/ for all database operations to ensure consistency

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-12-17T02:40:57.825Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/AGENTS.md:0-0
Timestamp: 2025-12-17T02:40:57.825Z
Learning: Applies to core/node/storage/pg_*.go : Use PostgreSQL transactions for consistency in all storage operations

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*_test.go : Always test edge cases including: empty/nil values, non-existent entities (NOT_FOUND errors), duplicate entries (ALREADY_EXISTS errors), concurrent operations, and transaction rollbacks

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*_test.go : Use helper functions to generate test data consistently: `safeAddress(t)` for Ethereum addresses, `testAppMetadataWithName(name string)` for app metadata, `testutils.FakeStreamId(streamType)` for stream IDs

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*_test.go : All storage tests should follow the consistent setup pattern using helper functions: `setupStreamStorageTest(t)`, `setupAppRegistryStorageTest(t)`, or `prepareNotificationsDB(ctx)` depending on storage type, with automatic cleanup via `t.Cleanup(params.closer)`

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*.go : Use `lockStream(ctx, tx, streamId, true)` with `FOR UPDATE` for write operations and `lockStream(ctx, tx, streamId, false)` with `FOR SHARE` for read operations to ensure proper row-level locking

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*_test.go : Use `require.New(t)` at the beginning of test functions and check error codes with `base.IsRiverErrorCode(err, code)` for specific error type verification

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
📚 Learning: 2025-11-25T08:47:37.754Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/rpc/AGENTS.md:0-0
Timestamp: 2025-11-25T08:47:37.754Z
Learning: Applies to core/node/rpc/**/*_test.go : Enable River logs with `enableRiverLogs: true` in tester options when debugging test failures

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
📚 Learning: 2025-12-17T02:40:57.825Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/AGENTS.md:0-0
Timestamp: 2025-12-17T02:40:57.825Z
Learning: Applies to core/node/storage/**/*.go : Use jackc/pgx/v5 for PostgreSQL connection pooling and database driver operations

Applied to files:

  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*.go : Use `COPY FROM` for atomic batch miniblock writes instead of individual insert statements in loops

Applied to files:

  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:47:01.034Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/app_registry/AGENTS.md:0-0
Timestamp: 2025-11-25T08:47:01.034Z
Learning: Applies to core/node/app_registry/**/app_registry/**/*.go : When adding storage methods, implement in three layers: AppRegistryStore interface, PostgresAppRegistryStore implementation, and CachedEncryptedMessageQueue delegation

Applied to files:

  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*.go : Use the `txRunner` function for all database operations to provide automatic retry for serialization failures and deadlocks, exponential backoff with jitter, transaction tracking, debugging, and comprehensive metrics and logging

Applied to files:

  • core/node/storage/pg_metadata_service.go
🧬 Code graph analysis (1)
core/node/storage/pg_metadata_service_test.go (7)
core/node/storage/pg_metadata_service.go (3)
  • PostgresMetadataServiceStore (149-153)
  • PostgresMetadataServiceStore (941-949)
  • NewPostgresMetadataServiceStore (161-172)
core/config/config.go (1)
  • DatabaseConfig (259-287)
core/node/base/test/context.go (1)
  • NewTestContext (15-23)
core/node/testutils/dbtestutils/db.go (1)
  • ConfigureDbWithPrefix (60-70)
core/node/storage/pg_storage.go (1)
  • CreateAndValidatePgxPool (243-254)
core/node/shared/stream_id.go (2)
  • STREAM_CHANNEL_BIN (16-16)
  • StreamId (41-41)
core/node/protocol/protocol.pb.go (1)
  • Err_BAD_PREV_MINIBLOCK_HASH (457-457)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Multinode_Ent
  • GitHub Check: Multinode_Ent_Legacy
  • GitHub Check: Multinode
  • GitHub Check: Go_Tests
  • GitHub Check: Common_CI
  • GitHub Check: Cursor Bugbot
  • GitHub Check: XChain_Integration
🔇 Additional comments (27)
core/node/storage/pg_metadata_service_test.go (7)

30-72: LGTM!

Test setup follows established patterns: uses require.New(t), dbtestutils.ConfigureDbWithPrefix, proper cleanup via t.Cleanup. Based on learnings, this aligns with storage test conventions.


78-207: LGTM!

Comprehensive test covering initial state, batch inserts, pagination, node filtering, record retrieval, counts, and block retrieval with event mask validation.


209-333: LGTM!

Good coverage of mixed update types, partial batch success with per-update errors, and EventMask composition verification.


335-402: LGTM!

Correctly validates block trimming behavior with numBlocksToKeep=2.


404-471: LGTM!

Correctly validates that numBlocksToKeep=0 preserves all blocks.


473-484: LGTM!

Input validation tests for GetRecordBlocks are appropriate.


486-540: LGTM!

Good coverage of OnNewRecordBlock notification behavior and channel lifecycle. The 50ms sleep for listener setup is acceptable for test synchronization.

core/node/storage/metadata_migrations/000001_create_initial_schema.up.sql (4)

1-14: LGTM!

Good schema with appropriate constraints for stream_id length, hash length, and non-negative miniblock numbers.


16-25: LGTM!

Denormalization for placement is a good design choice to avoid GIN index updates on frequent hash changes.


49-60: LGTM!

Singleton pattern with CHECK (singleton_key = TRUE) is a clean approach for the global block counter and serialization lock.


62-79: LGTM!

Trigger function and trigger setup are correct. DROP TRIGGER IF EXISTS before CREATE TRIGGER ensures migration idempotency.

core/node/storage/pg_metadata_service.go (16)

30-85: LGTM!

Clean type definitions with proper bit flags for event masks and clear separation of insert/update operations.


87-147: LGTM!

Interface is well-documented with clear contracts for pagination, batch updates with per-update errors, and async notifications.


161-198: LGTM!

Constructor and initialization correctly embed migrations and use AsRiverError for error wrapping.


200-262: LGTM!

Correct use of RepeatableRead isolation for snapshot consistency. Cursor-based pagination is efficient.


264-367: LGTM!

Query variants are correctly structured with parameterized queries and proper ordering.


369-448: LGTM!

Serialization via FOR UPDATE on singleton row is correct. Per-update error handling allows partial batch success. Trimming logic is sound.


450-483: LGTM!

Clean validation ensuring exactly one operation per update and proper dispatch.


485-582: LGTM!

Thorough input validation and defensive handling of unique violations. Correctly inserts into both md_stream_records and md_stream_placement.


584-671: LGTM!

Correctly handles all placement update combinations and only updates md_stream_placement when nodes actually change.


673-740: LGTM!

Correct precondition checks for miniblock updates: sealed stream check, hash chain validation, and sequential block number enforcement.


742-799: LGTM!

Correct use of transactions for read consistency with appropriate logging options.


818-879: LGTM!

Well-structured async listener with proper cleanup, backoff retry for resilience, and correct connection release before reconnection.


881-910: LGTM!

Correct backoff pattern with proper resource cleanup on partial failures.


951-1040: LGTM!

Correct range validation, query construction, and ordered block retrieval.


1071-1099: LGTM!

Correct use of FOR UPDATE for write operations as per coding guidelines.


1174-1210: LGTM!

Error classification and node index validation are well-implemented.

Comment thread core/node/storage/pg_metadata_service.go Outdated
if err := s.insertRecordBlockTx(ctx, tx, blockNum, blockSlot, mask, record); err != nil {
return err
}
blockSlot++

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale errors retained in errs slice after transaction retry

Medium Severity

The errs slice is created once at line 377 outside the txRunner callback, but the callback can be retried on serialization failures or deadlocks. When a retry occurs, the errs slice is not reset. If update i fails with a validation error on attempt 1 (e.g., Err_NOT_FOUND) but succeeds on retry attempt 2, errs[i] will still contain the stale error from attempt 1. The caller would incorrectly believe the update failed when it actually succeeded. The fix would be to reset the errs slice at the start of the callback or explicitly set errs[i] = nil on successful updates.

🔬 Verification Test

Why verification test was not possible: This bug requires triggering a PostgreSQL serialization failure or deadlock during the transaction, followed by a successful retry where a previously failed update now succeeds. This requires coordinating concurrent database transactions with precise timing to induce the serialization conflict, which cannot be reliably reproduced in a unit test without mocking the database layer or using specialized concurrency testing tools. The bug is evident from code inspection: the errs slice is captured by closure and never cleared inside the callback that txRunner may retry.

Fix in Cursor Fix in Web

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
core/node/storage/pg_metadata_service_test.go (1)

490-493: Potential test flakiness with sleep-based synchronization.

The 50ms sleep assumes the listener goroutine has started and subscribed. Under CI load, this could occasionally fail. Consider adding a ready signal or retry logic.

core/node/storage/pg_metadata_service.go (2)

272-293: Consider cursor-based pagination for large datasets.

Currently loads all records into memory before paginating. For very large stream counts, this could cause memory pressure. Database-level pagination with LIMIT/OFFSET or cursors would be more scalable.


832-858: Silent fallback on parse error may hide issues.

If notification payload is malformed, the code falls back to GetLastRecordBlockNum without logging. Consider logging a warning for unexpected payload formats to aid debugging.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d7387a9 and 40eb02d.

📒 Files selected for processing (3)
  • core/node/storage/metadata_migrations/000001_create_initial_schema.up.sql
  • core/node/storage/pg_metadata_service.go
  • core/node/storage/pg_metadata_service_test.go
🧰 Additional context used
📓 Path-based instructions (8)
core/**/*.go

📄 CodeRabbit inference engine (core/AGENTS.md)

core/**/*.go: Format all modified Go files using ./fmt.sh from the /core directory before committing
Run the linter using ./lint.sh from the /core directory to check code quality, which runs both golangci-lint and staticcheck
Use RiverError type from node/base/error.go for structured errors
Use RiverErrorWithBase to construct a river error derived from an error of arbitrary or unknown type
Wrap blockchain errors with context using crypto/ utilities
Log errors with structured fields using zap logger
Use node/crypto/blockchain.go for Ethereum client operations
Use node/crypto/chain_monitor.go for transaction monitoring
Use xchain/entitlement/ for cross-chain entitlement checks
Do not run ./fmt.sh and ./lint.sh at the same time; run them as separate commands
Use go.uber.org/zap for structured logging with custom extensions from node/logging/
Use go.opentelemetry.io for distributed tracing across the application
Use prometheus for OpenMetrics in application metrics collection

Files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
core/**/*_test.go

📄 CodeRabbit inference engine (core/AGENTS.md)

core/**/*_test.go: Use testify/require and testify/assert for test assertions in Go test files
Database tests should use testutils/dbtestutils for test DB lifecycle management
Do not set GOCACHE or GOMODCACHE when running Go test commands; run go commands directly without setting extra environment variables
Set RIVER_TEST_LOG=info and RIVER_TEST_PRINT=1 environment variables for debug output in tests
Mock implementations should be used from testutils/mocks/ in tests

Files:

  • core/node/storage/pg_metadata_service_test.go
core/node/storage/pg_*.go

📄 CodeRabbit inference engine (core/AGENTS.md)

core/node/storage/pg_*.go: Use PostgreSQL transactions for consistency in all storage operations
All storage operations should be in node/storage/pg_*.go files

Files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
core/node/storage/**/*.go

📄 CodeRabbit inference engine (core/AGENTS.md)

core/node/storage/**/*.go: Use PostgreSQL transactions in node/storage/ for all database operations to ensure consistency
Use jackc/pgx/v5 for PostgreSQL connection pooling and database driver operations

Files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
**/core/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/core/**/*.go: Format all Go files using the fmt.sh script from the /core directory
Run the linter using the lint.sh script from the /core directory for all Go code changes
Use structured logging with zap for Go development
Implement proper error handling with RiverError types in Go code
Implement proper context cancellation for graceful shutdowns in Go code

Files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
**/core/**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

**/core/**/*_test.go: Use testify for test assertions in Go tests
Make tests re-runnable by using unique identifiers (e.g., UUIDs) instead of hardcoded values

Files:

  • core/node/storage/pg_metadata_service_test.go
**/core/node/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Use structured logging with zap in Go backend River nodes for observability

Files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
**/core/node/storage/**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

Storage tests should verify data persistence and constraints for database schema changes

Files:

  • core/node/storage/pg_metadata_service_test.go
🧠 Learnings (27)
📓 Common learnings
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Make changes to storage interfaces first, then implement PostgreSQL-specific logic, write comprehensive tests, benchmark critical paths, and update migrations if schema changes are needed
📚 Learning: 2025-12-17T02:40:57.825Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/AGENTS.md:0-0
Timestamp: 2025-12-17T02:40:57.825Z
Learning: Applies to core/node/storage/pg_*.go : All storage operations should be in node/storage/pg_*.go files

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
📚 Learning: 2026-01-08T18:27:30.866Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-08T18:27:30.866Z
Learning: Applies to **/core/node/storage/**/*_test.go : Storage tests should verify data persistence and constraints for database schema changes

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-12-17T02:40:57.825Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/AGENTS.md:0-0
Timestamp: 2025-12-17T02:40:57.825Z
Learning: Applies to core/node/storage/**/*.go : Use PostgreSQL transactions in node/storage/ for all database operations to ensure consistency

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-12-17T02:40:57.825Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/AGENTS.md:0-0
Timestamp: 2025-12-17T02:40:57.825Z
Learning: Applies to core/node/storage/pg_*.go : Use PostgreSQL transactions for consistency in all storage operations

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*_test.go : Implement table-driven tests when testing multiple scenarios to improve maintainability and coverage

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*_test.go : Use helper functions to generate test data consistently: `safeAddress(t)` for Ethereum addresses, `testAppMetadataWithName(name string)` for app metadata, `testutils.FakeStreamId(streamType)` for stream IDs

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*_test.go : Always test edge cases including: empty/nil values, non-existent entities (NOT_FOUND errors), duplicate entries (ALREADY_EXISTS errors), concurrent operations, and transaction rollbacks

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:47:37.754Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/rpc/AGENTS.md:0-0
Timestamp: 2025-11-25T08:47:37.754Z
Learning: Applies to core/node/rpc/**/*_test.go : Generate test wallets and metadata using helper functions like `safeNewWallet()` and `appMetadataForBot()` for realistic test data

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*.go : Extract transaction logic into separate `*Tx` methods for better organization and testability in PostgreSQL storage implementations

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*_test.go : Use standard imports in storage tests: context, testing, github.com/ethereum/go-ethereum/common, github.com/stretchr/testify/require, and relevant River protocol packages

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*.go : Enforce miniblock requirement constraint: if a stream exists in the `es` (event streams) table, it MUST have at least one miniblock; empty streams are not allowed

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/metadata_migrations/000001_create_initial_schema.up.sql
  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*_test.go : All storage tests should follow the consistent setup pattern using helper functions: `setupStreamStorageTest(t)`, `setupAppRegistryStorageTest(t)`, or `prepareNotificationsDB(ctx)` depending on storage type, with automatic cleanup via `t.Cleanup(params.closer)`

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*_test.go : Use `require.New(t)` at the beginning of test functions and check error codes with `base.IsRiverErrorCode(err, code)` for specific error type verification

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*.go : Use `lockStream(ctx, tx, streamId, true)` with `FOR UPDATE` for write operations and `lockStream(ctx, tx, streamId, false)` with `FOR SHARE` for read operations to ensure proper row-level locking

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*.go : Always include stream ID in queries to leverage table partitioning (256 partitions by default)

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:47:37.754Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/rpc/AGENTS.md:0-0
Timestamp: 2025-11-25T08:47:37.754Z
Learning: Applies to core/node/rpc/**/*_test.go : Enable River logs with `enableRiverLogs: true` in tester options when debugging test failures

Applied to files:

  • core/node/storage/pg_metadata_service_test.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Make changes to storage interfaces first, then implement PostgreSQL-specific logic, write comprehensive tests, benchmark critical paths, and update migrations if schema changes are needed

Applied to files:

  • core/node/storage/metadata_migrations/000001_create_initial_schema.up.sql
  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:47:01.034Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/app_registry/AGENTS.md:0-0
Timestamp: 2025-11-25T08:47:01.034Z
Learning: Applies to core/node/app_registry/**/app_registry/**/*.go : When adding storage methods, implement in three layers: AppRegistryStore interface, PostgresAppRegistryStore implementation, and CachedEncryptedMessageQueue delegation

Applied to files:

  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-12-17T02:40:57.825Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/AGENTS.md:0-0
Timestamp: 2025-12-17T02:40:57.825Z
Learning: Applies to core/node/storage/**/*.go : Use jackc/pgx/v5 for PostgreSQL connection pooling and database driver operations

Applied to files:

  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*.go : Use `COPY FROM` for atomic batch miniblock writes instead of individual insert statements in loops

Applied to files:

  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*.go : The `last_snapshot_miniblock_num` in the `es` table MUST always point to one of the miniblocks within the last continuous miniblock sequence, and the pointer only grows forward

Applied to files:

  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*.go : Enforce minipool generation sequencing constraint: the minipool generation number MUST always be set to the last miniblock number + 1

Applied to files:

  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*.go : Use the `txRunner` function for all database operations to provide automatic retry for serialization failures and deadlocks, exponential backoff with jitter, transaction tracking, debugging, and comprehensive metrics and logging

Applied to files:

  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*.go : Keep transactions short-lived and use appropriate access modes (ReadOnly vs ReadWrite) in txRunner calls

Applied to files:

  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*.go : Add transaction tags for debugging in txRunner calls to improve transaction tracking and monitoring

Applied to files:

  • core/node/storage/pg_metadata_service.go
📚 Learning: 2025-11-25T08:48:08.084Z
Learnt from: CR
Repo: towns-protocol/towns PR: 0
File: core/node/storage/AGENTS.md:0-0
Timestamp: 2025-11-25T08:48:08.084Z
Learning: Applies to core/node/storage/storage/**/*.go : Handle context cancellation properly; disable context cancellation for write operations in txRunner

Applied to files:

  • core/node/storage/pg_metadata_service.go
🧬 Code graph analysis (2)
core/node/storage/pg_metadata_service_test.go (8)
core/node/storage/pg_metadata_service.go (3)
  • PostgresMetadataServiceStore (145-149)
  • PostgresMetadataServiceStore (861-869)
  • NewPostgresMetadataServiceStore (157-168)
core/config/config.go (1)
  • DatabaseConfig (259-287)
core/node/base/test/context.go (1)
  • NewTestContext (15-23)
core/node/testutils/dbtestutils/db.go (1)
  • ConfigureDbWithPrefix (60-70)
core/node/storage/pg_storage.go (1)
  • CreateAndValidatePgxPool (243-254)
core/node/shared/stream_id.go (2)
  • STREAM_CHANNEL_BIN (16-16)
  • StreamId (41-41)
core/node/protocol/protocol.pb.go (1)
  • Err_BAD_PREV_MINIBLOCK_HASH (457-457)
core/node/base/error.go (1)
  • AsRiverError (273-355)
core/node/storage/pg_metadata_service.go (4)
core/node/shared/stream_id.go (2)
  • StreamId (41-41)
  • StreamIdFromBytes (77-85)
core/node/base/error.go (2)
  • RiverError (74-86)
  • RiverErrorWithBase (88-94)
core/node/protocol/protocol.pb.go (6)
  • Err_INVALID_ARGUMENT (407-407)
  • Err_ALREADY_EXISTS (416-416)
  • Err_FAILED_PRECONDITION (426-426)
  • Err_BAD_PREV_MINIBLOCK_HASH (457-457)
  • Err_BAD_BLOCK_NUMBER (473-473)
  • Err_NOT_FOUND (413-413)
core/node/base/backoff_tracker.go (1)
  • BackoffTracker (11-34)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Common_CI
  • GitHub Check: Go_Tests
  • GitHub Check: Multinode_Ent_Legacy
  • GitHub Check: Multinode
  • GitHub Check: Multinode_Ent
  • GitHub Check: Cursor Bugbot
  • GitHub Check: XChain_Integration
🔇 Additional comments (20)
core/node/storage/pg_metadata_service_test.go (7)

31-73: LGTM!

Setup helper follows established patterns with proper cleanup registration and unique DB prefix.


75-84: LGTM!

Clean test data generators.


86-211: LGTM!

Comprehensive test covering listing, pagination, filtering by node, counting, and block retrieval with proper event mask validation.


213-333: LGTM!

Good coverage of partial batch failures, error code mapping, and event mask differentiation between update types.


335-400: LGTM!

Properly validates block trimming behavior with numBlocksToKeep=2.


402-467: LGTM!

Confirms zero-trim parameter preserves all blocks.


469-480: LGTM!

Validates error handling for invalid block range inputs.

core/node/storage/metadata_migrations/000001_create_initial_schema.up.sql (3)

1-60: LGTM!

Well-structured schema with proper constraints. The denormalized placement table for GIN index efficiency is a sensible optimization.


62-77: LGTM!

Clean NOTIFY trigger implementation. The DROP IF EXISTS ensures idempotent migrations.


79-79: LGTM!

Initial block_num of -1 correctly indicates no updates have occurred.

core/node/storage/pg_metadata_service.go (10)

30-82: LGTM!

Types are well-structured. The event mask bits and update union pattern are clean.


83-143: LGTM!

Well-documented interface with clear contracts.


145-194: LGTM!

Initialization follows the established pattern with embedded PostgresEventStore.


301-380: LGTM!

Batch update with proper serialization via row lock, partial failure handling, and correct trimming logic.


382-415: LGTM!

Clean union validation and dispatch.


417-509: LGTM!

Thorough validation with proper duplicate handling via both pre-check and UniqueViolation.


511-598: LGTM!

Placement update handles all field combinations correctly with proper constraint validation.


600-660: LGTM!

Strict miniblock sequence validation with proper sealed state handling.


871-962: LGTM!

Proper range validation and ordered block retrieval.


964-1138: LGTM!

Helper functions are well-organized with proper locking differentiation and comprehensive validation.

@blacksmith-sh

blacksmith-sh Bot commented Jan 9, 2026

Copy link
Copy Markdown

Found 2 test failures on Blacksmith runners:

Failures

Test View Logs
node/rpc/TestAppRegistry_MessageForwardSettings View Logs
node/rpc/TestAppRegistry_MessageForwardSettings/NO_MESSAGES View Logs

Fix in Cursor

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.

1 participant