Skip to content

Migrate hold module from KV store to collections.#2554

Open
nagarajdivine wants to merge 4 commits into
mainfrom
feat/hold-module-collections
Open

Migrate hold module from KV store to collections.#2554
nagarajdivine wants to merge 4 commits into
mainfrom
feat/hold-module-collections

Conversation

@nagarajdivine

@nagarajdivine nagarajdivine commented Dec 3, 2025

Copy link
Copy Markdown
Contributor

Description


Before we can merge this PR, please make sure that all the following items have been
checked off. If any of the checklist items are not applicable, please leave them but
write a little note why.

  • Targeted PR against correct branch (see CONTRIBUTING.md).
  • Linked to Github issue with discussion and accepted design OR link to spec that describes this work.
  • Wrote unit and integration tests
  • Updated relevant documentation (docs/) or specification (x/<module>/spec/).
  • Added relevant godoc comments.
  • Added relevant changelog entries under .changelog/unreleased (see Adding Changes).
  • Re-reviewed Files changed in the Github PR explorer.
  • Review Codecov Report in the comment section below once CI passes.

Summary by CodeRabbit

  • Refactor
    • Migrated the hold module to collections-backed, schema-driven storage and adjusted keeper initialization and public keeper fields.
  • New Features
    • Added new serialization codecs for hold keys and values to support address, denom, and integer handling.
  • Bug Fixes
    • Better error handling during iteration; hold lookups now return zero balances when absent.
  • Performance / Behavior
    • Reworked query pagination with consistent sort order, reliable next-key computation, and unified in-memory processing.
  • Tests
    • Updated tests and helpers to align with the new storage/service patterns.
  • Documentation
    • Added an unreleased changelog entry for the migration.

@nagarajdivine nagarajdivine requested a review from a team as a code owner December 3, 2025 15:35
@coderabbitai

coderabbitai Bot commented Dec 3, 2025

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The hold module was migrated from direct KVStore access to collections-based storage: new codecs and a Schema-backed Map were added, the Keeper now accepts a KVStoreService and exposes Holds map, gRPC pagination was reworked to use in-memory collection processing, and tests/initializers were updated to the new store types and APIs.

Changes

Cohort / File(s) Summary
Changelog & App Init
\.changelog/unreleased/features/2419-hold-module-collections.md, app/app.go
Adds changelog entry; updates HoldKeeper initialization to pass a store.KVStoreService (via runtime.NewKVStoreService) instead of a raw store key.
Codecs
x/hold/codec.go
Adds IntValueCodec, AddressKeyCodec, and DenomKeyCodec with binary/JSON encode/decode, sizing, stringify and non-terminal variants for composite key support.
Keeper Core
x/hold/keeper/keeper.go
Replaces direct KVStore with store.KVStoreService, collections.Schema, and a collections.Map (Holds) keyed by (addr, denom); constructor and all hold operations (get/set/add/release/iterate) updated to context-aware map APIs and pair-key usage.
gRPC Query & Pagination
x/hold/keeper/grpc_query.go
Rewrites GetAllHolds to build in-memory entries (entryWithKey), adds compareKeys, unified offset/key pagination, NextKey encoding, reverse handling, total counting, and aggregated per-entry error reporting.
Tests & Test Helpers
x/hold/keeper/*.go
x/hold/keeper/export_test.go, genesis_test.go, grpc_query_test.go, invariants_test.go, keeper_test.go
Switches imports from cosmossdk.io/store/typescosmossdk.io/core/store, updates test signatures to use store.KVStore, adapts setups to open stores via StoreService/OpenKVStore, changes SetHoldCoinAmount to accept ctx, and updates iterator/error handling in tests.
Internal Helpers
x/hold/keeper/grpc_query.go
Adds unexported types/helpers: entryWithKey and compareKeys for ordering/pagination boundary logic.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant GRPC as gRPC Server
    participant Keeper
    participant StoreSvc as KVStoreService
    participant Holds as Collections.Map

    Client->>GRPC: GetAllHolds(request + pagination)
    GRPC->>Keeper: GetAllHolds(ctx, req)
    Keeper->>StoreSvc: open map / use Schema
    StoreSvc->>Holds: access map API
    Keeper->>Holds: iterate all entries (collect into memory)
    Holds-->>Keeper: entries (addr, denom, amount) / per-entry errors
    Keeper->>Keeper: apply offset/limit or startKey, compute NextKey
    Keeper-->>GRPC: response(hands[], next_key, total, status)
    GRPC-->>Client: response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • SpicyLemon
  • nullpointer0x00

Poem

🐰
I hopped from stores of old to maps so neat,
With codecs snug and schemas tidy-sweet.
Holds now collected, paged, and set with care,
I nibble edges, sort the keys with flair.
A carrot crunch — state storage light as air!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: migrating the hold module from KV store to collections for state storage.
Linked Issues check ✅ Passed The PR fulfills issue #2491 requirements by migrating x/hold module to collections for state storage without requiring a migration.
Out of Scope Changes check ✅ Passed All changes are scoped to migrating the hold module to collections; no unrelated modifications detected beyond the required scope.

✏️ 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 feat/hold-module-collections

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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
x/hold/keeper/keeper_test.go (1)

182-195: Error from store.Iterator is not checked before use.

The error returned from store.Iterator is captured but not checked before entering the loop. If iterator creation fails, iter could be nil, causing a panic when accessing iter.Valid().

Apply this diff to check the error:

 	iter, err := store.Iterator(nil, nil)
+	s.Require().NoError(err, "store.Iterator(nil, nil)")
 	defer func() {
 		if iter != nil {
 			iter.Close()
 		}
 	}()
🧹 Nitpick comments (3)
x/hold/codec.go (1)

159-168: Non-terminal methods for DenomKeyCodec are not safe if used non-terminally.

Since DenomKeyCodec uses raw bytes without a length prefix, the DecodeNonTerminal method will consume the entire remaining buffer. This only works correctly if the denom is always the last (terminal) part of a composite key.

If the composite key structure is (Address, Denom) where Address is first and Denom is terminal, this is fine. However, the NonTerminal methods as implemented could be misleading or cause issues if someone attempts to use DenomKeyCodec as a non-terminal key component in the future.

Consider either:

  1. Adding a comment clarifying this codec must only be used as the terminal key component, or
  2. Having EncodeNonTerminal/DecodeNonTerminal return an error or panic to prevent misuse.
x/hold/keeper/grpc_query.go (1)

168-173: Manual reversal adds O(n) time complexity.

Consider using a reverse iterator if the collections library supports it (e.g., IterateReverse or passing a range with reverse ordering) instead of collecting all entries and reversing in-memory.

x/hold/keeper/keeper.go (1)

34-34: Use camelCase for parameter names.

Go convention is to use camelCase for parameter names. store_service should be storeService.

-func NewKeeper(cdc codec.BinaryCodec, store_service store.KVStoreService, accountKeeper hold.AccountKeeper, bankKeeper hold.BankKeeper) Keeper {
+func NewKeeper(cdc codec.BinaryCodec, storeService store.KVStoreService, accountKeeper hold.AccountKeeper, bankKeeper hold.BankKeeper) Keeper {

Also update the usage on line 35 and 48:

-	sb := collections.NewSchemaBuilder(store_service)
+	sb := collections.NewSchemaBuilder(storeService)
-		StoreService:  store_service,
+		StoreService:  storeService,
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f06387c and 20e108e.

📒 Files selected for processing (10)
  • .changelog/unreleased/features/2419-hold-module-collections.md (1 hunks)
  • app/app.go (1 hunks)
  • x/hold/codec.go (2 hunks)
  • x/hold/keeper/export_test.go (1 hunks)
  • x/hold/keeper/genesis_test.go (5 hunks)
  • x/hold/keeper/grpc_query.go (2 hunks)
  • x/hold/keeper/grpc_query_test.go (6 hunks)
  • x/hold/keeper/invariants_test.go (19 hunks)
  • x/hold/keeper/keeper.go (6 hunks)
  • x/hold/keeper/keeper_test.go (12 hunks)
🧰 Additional context used
🧠 Learnings (22)
📓 Common learnings
Learnt from: nullpointer0x00
Repo: provenance-io/provenance PR: 0
File: :0-0
Timestamp: 2024-06-10T19:25:28.209Z
Learning: A GitHub issue was created to track the migration of parameters for the metadata, marker, name, and msgFees modules as indicated by the TODO comments in the codebase. The issue is linked to PR #1932 and can be found at https://github.com/provenance-io/provenance/issues/1935.
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2422
File: app/upgrades.go:52-53
Timestamp: 2025-09-05T19:28:28.271Z
Learning: The asset module in the Provenance blockchain doesn't maintain its own state storage and therefore doesn't require a StoreKey in upgrade configurations. It delegates all storage operations to the NFT module, unlike other modules like ledger and registry which have their own state stores.
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2422
File: x/asset/simulation/decoder.go:8-14
Timestamp: 2025-08-15T20:19:31.726Z
Learning: The asset module in Provenance doesn't use collections-based storage like ledger/registry modules. Instead, it delegates storage to the NFT module, so a collections-based store decoder isn't applicable here. The module should use its own types.StoreKey rather than exchange.StoreKey in RegisterStoreDecoder.
📚 Learning: 2024-06-10T19:25:28.209Z
Learnt from: nullpointer0x00
Repo: provenance-io/provenance PR: 0
File: :0-0
Timestamp: 2024-06-10T19:25:28.209Z
Learning: A GitHub issue was created to track the migration of parameters for the metadata, marker, name, and msgFees modules as indicated by the TODO comments in the codebase. The issue is linked to PR #1932 and can be found at https://github.com/provenance-io/provenance/issues/1935.

Applied to files:

  • .changelog/unreleased/features/2419-hold-module-collections.md
📚 Learning: 2025-08-15T20:59:09.909Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2422
File: x/ledger/keeper/expected_keeper.go:11-19
Timestamp: 2025-08-15T20:59:09.909Z
Learning: The BankKeeper interface methods in Cosmos SDK v0.50.x and Provenance use `context.Context` as the context parameter type, not `sdk.Context`. This applies to methods like SendCoins, HasBalance, GetBalance, SpendableCoin, HasSupply, etc. The expected keeper interfaces should consistently use `context.Context` to match the actual Cosmos SDK bank keeper signatures.

Applied to files:

  • x/hold/keeper/genesis_test.go
  • x/hold/keeper/grpc_query.go
  • x/hold/keeper/invariants_test.go
  • x/hold/keeper/keeper_test.go
  • x/hold/keeper/grpc_query_test.go
  • x/hold/keeper/keeper.go
  • x/hold/keeper/export_test.go
📚 Learning: 2024-10-08T18:12:51.936Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2140
File: x/metadata/keeper/migrations_v4_test.go:253-479
Timestamp: 2024-10-08T18:12:51.936Z
Learning: In Go tests within this project, it's acceptable to have similar helper functions like `newUUID`, `newScopeID`, `newSpecID`, and `newScope` defined within each test. Tests may tailor these functions to specific needs rather than sharing them, to avoid breaking other tests when changes are made and to keep them non-public.

Applied to files:

  • x/hold/keeper/genesis_test.go
📚 Learning: 2024-10-08T18:12:51.935Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 1993
File: x/marker/client/cli/cli_test.go:1090-1090
Timestamp: 2024-10-08T18:12:51.935Z
Learning: SpicyLemon prefers allowing tests to fail naturally if the setup is incorrect rather than explicitly checking and handling such errors within the test functions. They also recommend using `s.Require().NotNil(...)` instead of `s.T().Fatal(...)` for assertions in test functions.

Applied to files:

  • x/hold/keeper/genesis_test.go
  • x/hold/keeper/invariants_test.go
  • x/hold/keeper/grpc_query_test.go
📚 Learning: 2024-10-08T18:12:51.936Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2140
File: x/metadata/types/signer_utils_test.go:1837-1889
Timestamp: 2024-10-08T18:12:51.936Z
Learning: In Go tests where test cases are defined within a test case slice and iterated with `t.Run`, instances defined in the test case struct are not shared between subtests, ensuring test independence.

Applied to files:

  • x/hold/keeper/genesis_test.go
📚 Learning: 2024-10-02T03:49:31.991Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2140
File: x/metadata/types/address_test.go:69-80
Timestamp: 2024-10-02T03:49:31.991Z
Learning: In the `subSet` function used in unit tests, it's acceptable for it to panic if an out-of-bounds index is provided, as this indicates a test case was set up incorrectly.

Applied to files:

  • x/hold/keeper/genesis_test.go
  • x/hold/keeper/grpc_query_test.go
📚 Learning: 2024-10-02T02:51:17.452Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2140
File: x/metadata/keeper/bank_test.go:81-91
Timestamp: 2024-10-02T02:51:17.452Z
Learning: In test helper functions like `setBalances` in `x/metadata/keeper/bank_test.go`, it's acceptable to handle zero or negative amounts without raising an error, as it suits the test's purposes.

Applied to files:

  • x/hold/keeper/genesis_test.go
  • x/hold/keeper/grpc_query.go
  • x/hold/keeper/invariants_test.go
  • x/hold/keeper/keeper_test.go
  • x/hold/keeper/grpc_query_test.go
  • x/hold/keeper/keeper.go
  • x/hold/keeper/export_test.go
📚 Learning: 2024-10-08T18:12:51.935Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2140
File: x/metadata/keeper/mocks_test.go:579-589
Timestamp: 2024-10-08T18:12:51.935Z
Learning: When mocking methods like `MintCoins` and `BurnCoins` in tests, using slices to store error strings and consuming them sequentially is the preferred approach, as it offers precise control and simplicity without unnecessary complexity.

Applied to files:

  • x/hold/keeper/genesis_test.go
  • x/hold/keeper/invariants_test.go
  • x/hold/keeper/keeper_test.go
  • x/hold/keeper/grpc_query_test.go
  • x/hold/keeper/keeper.go
📚 Learning: 2025-08-15T22:52:24.461Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2422
File: x/ledger/keeper/keeper.go:74-173
Timestamp: 2025-08-15T22:52:24.461Z
Learning: In the ledger keeper NewKeeper function in x/ledger/keeper/keeper.go, SpicyLemon prefers to allow bankKeeper and registryKeeper dependencies to be nil rather than adding validation that would panic on nil values. The design intentionally supports nil keepers for flexibility.

Applied to files:

  • app/app.go
  • x/hold/keeper/grpc_query.go
  • x/hold/keeper/grpc_query_test.go
  • x/hold/keeper/keeper.go
  • x/hold/keeper/export_test.go
📚 Learning: 2024-10-02T01:43:01.360Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2140
File: x/marker/types/send_restrictions.go:11-11
Timestamp: 2024-10-02T01:43:01.360Z
Learning: In `x/marker/types/send_restrictions.go`, the `transferAgentKey` is only used for storing data in ephemeral contexts, so changing its value does not impact backward compatibility.

Applied to files:

  • app/app.go
  • x/hold/keeper/export_test.go
📚 Learning: 2024-06-10T19:25:28.209Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 0
File: :0-0
Timestamp: 2024-06-10T19:25:28.209Z
Learning: The `GetLockedCoins` function in `x/hold/keeper/locked_coins.go` must conform to the `banktypes.GetLockedCoinsFn` type, which does not allow returning an error. In cases where `k.GetHoldCoins` encounters an error, using `panic` is considered appropriate due to the severity of such errors.

Applied to files:

  • x/hold/keeper/grpc_query.go
  • x/hold/keeper/invariants_test.go
  • x/hold/keeper/keeper_test.go
  • x/hold/keeper/keeper.go
  • x/hold/keeper/export_test.go
📚 Learning: 2025-08-15T20:59:09.909Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2422
File: x/ledger/keeper/expected_keeper.go:11-19
Timestamp: 2025-08-15T20:59:09.909Z
Learning: In Cosmos SDK v0.50.x, the bank keeper interface methods use `context.Context` rather than `sdk.Context`. The BankKeeper interface should use `context.Context` for methods like SendCoins, HasBalance, GetBalance, etc.

Applied to files:

  • x/hold/keeper/grpc_query.go
  • x/hold/keeper/invariants_test.go
  • x/hold/keeper/keeper_test.go
  • x/hold/keeper/grpc_query_test.go
  • x/hold/keeper/keeper.go
  • x/hold/keeper/export_test.go
📚 Learning: 2024-10-02T16:55:10.172Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2140
File: x/metadata/keeper/mocks_test.go:425-427
Timestamp: 2024-10-02T16:55:10.172Z
Learning: In this codebase, when using `sdk.AccAddress` as map keys, casting to `string` is preferred over calling `.String()` for performance reasons.

Applied to files:

  • x/hold/codec.go
  • x/hold/keeper/keeper_test.go
📚 Learning: 2025-08-15T20:34:01.110Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2422
File: x/asset/types/codec.go:9-10
Timestamp: 2025-08-15T20:34:01.110Z
Learning: In Go Cosmos SDK projects, sdk.Msg interface embeds proto.Message interface, making it valid to copy elements from []sdk.Msg to []proto.Message even though the slice types are different. The pattern of declaring AllRequestMsgs as []sdk.Msg for type assertions while copying to []proto.Message for RegisterImplementations is a common and correct pattern.

Applied to files:

  • x/hold/codec.go
📚 Learning: 2025-08-15T20:34:01.110Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2422
File: x/asset/types/codec.go:9-10
Timestamp: 2025-08-15T20:34:01.110Z
Learning: The pattern of declaring AllRequestMsgs as []sdk.Msg and then copying to []proto.Message for RegisterImplementations is a standard, correct pattern used throughout the Provenance codebase. This works because sdk.Msg interface embeds proto.Message interface, making the copy operation valid. This pattern appears consistently across 15+ modules and compiles without errors.

Applied to files:

  • x/hold/codec.go
📚 Learning: 2024-10-10T23:08:15.256Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2175
File: .golangci.yml:87-87
Timestamp: 2024-10-10T23:08:15.256Z
Learning: The `depguard` definitions in `.golangci.yml` are prefixes (unless they end with `$`), so the entry `- cosmossdk.io/client/v2` covers imports from `cosmossdk.io/client/v2` and its subpackages like `autocli`. Changing imports from `cosmossdk.io/client/v2/autocli` to `cosmossdk.io/client/v2` in `app/app.go` would result in unresolved references because the required code resides in the `autocli` package.

Applied to files:

  • x/hold/keeper/invariants_test.go
  • x/hold/keeper/grpc_query_test.go
  • x/hold/keeper/keeper.go
📚 Learning: 2024-10-02T03:37:18.486Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2140
File: x/metadata/keeper/msg_server_test.go:1225-1228
Timestamp: 2024-10-02T03:37:18.486Z
Learning: When test cases are run using `s.T().Run` instead of `s.Run`, use `require` with `t` instead of `s.Require()`, to ensure that assertions apply to the correct test instance.

Applied to files:

  • x/hold/keeper/invariants_test.go
📚 Learning: 2024-10-02T02:29:56.018Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2140
File: x/metadata/types/signer_utils.go:138-143
Timestamp: 2024-10-02T02:29:56.018Z
Learning: In this codebase, methods like `GetAcc()` and `GetSignerAcc()` are intended to return `nil` if the address is invalid, and address validation is performed elsewhere. These methods should return a single value without an error.

Applied to files:

  • x/hold/keeper/invariants_test.go
  • x/hold/keeper/keeper_test.go
  • x/hold/keeper/keeper.go
📚 Learning: 2024-06-10T19:25:28.209Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 0
File: :0-0
Timestamp: 2024-06-10T19:25:28.209Z
Learning: The `WithVestingLockedBypass` function in `x/hold/keeper/invariants.go` is specifically used to handle scenarios involving vesting and locked coins deep within third-party library code. It follows a pattern used by other injectable functions to allow bypassing specific portions of those functions.

Applied to files:

  • x/hold/keeper/invariants_test.go
  • x/hold/keeper/keeper.go
📚 Learning: 2024-10-08T18:12:51.936Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 1868
File: x/exchange/keeper/payments.go:24-34
Timestamp: 2024-10-08T18:12:51.936Z
Learning: The `parsePaymentStoreValue` function in `x/exchange/keeper/payments.go` returns `nil, nil` when the input byte slice is empty, as a design decision to simplify handling for the caller. This behavior is based on the rationale provided by a user, emphasizing ease of use over explicit error signaling for empty inputs.

Applied to files:

  • x/hold/keeper/keeper_test.go
  • x/hold/keeper/export_test.go
📚 Learning: 2024-10-08T18:12:51.935Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2140
File: x/metadata/keeper/account_data_test.go:11-11
Timestamp: 2024-10-08T18:12:51.935Z
Learning: Do not raise issues about the `keeper` package still being in use after import removal in test files when there are no unresolved references.

Applied to files:

  • x/hold/keeper/grpc_query_test.go
🧬 Code graph analysis (6)
x/hold/keeper/genesis_test.go (1)
x/hold/keeper/keeper_test.go (1)
  • TestSuite (33-50)
app/app.go (12)
x/marker/types/key.go (1)
  • StoreKey (17-17)
x/ibchooks/types/keys.go (1)
  • StoreKey (5-5)
x/quarantine/keys.go (1)
  • StoreKey (18-18)
x/msgfees/types/keys.go (1)
  • StoreKey (8-8)
x/name/types/keys.go (1)
  • StoreKey (17-17)
x/attribute/types/keys.go (1)
  • StoreKey (19-19)
x/sanction/keys.go (1)
  • StoreKey (8-8)
x/ibcratelimit/keys.go (1)
  • StoreKey (8-8)
x/oracle/types/keys.go (1)
  • StoreKey (10-10)
x/exchange/keys.go (1)
  • StoreKey (16-16)
x/hold/keys.go (1)
  • StoreKey (8-8)
x/trigger/types/keys.go (1)
  • StoreKey (15-15)
x/hold/keeper/grpc_query.go (5)
internal/provutils/pair.go (1)
  • Pair (6-9)
x/hold/query.pb.go (6)
  • GetAllHoldsRequest (115-118)
  • GetAllHoldsRequest (122-122)
  • GetAllHoldsRequest (123-125)
  • GetAllHoldsResponse (154-159)
  • GetAllHoldsResponse (163-163)
  • GetAllHoldsResponse (164-166)
x/hold/keeper/keeper.go (1)
  • Keeper (24-32)
x/hold/hold.pb.go (3)
  • AccountHold (30-35)
  • AccountHold (39-39)
  • AccountHold (40-42)
x/hold/codec.go (22)
  • AddressKeyCodec (56-56)
  • AddressKeyCodec (58-71)
  • AddressKeyCodec (73-84)
  • AddressKeyCodec (86-88)
  • AddressKeyCodec (90-92)
  • AddressKeyCodec (94-100)
  • AddressKeyCodec (102-104)
  • AddressKeyCodec (106-108)
  • AddressKeyCodec (110-112)
  • AddressKeyCodec (114-116)
  • AddressKeyCodec (118-120)
  • DenomKeyCodec (123-123)
  • DenomKeyCodec (125-131)
  • DenomKeyCodec (133-135)
  • DenomKeyCodec (137-139)
  • DenomKeyCodec (141-143)
  • DenomKeyCodec (145-149)
  • DenomKeyCodec (151-153)
  • DenomKeyCodec (155-157)
  • DenomKeyCodec (159-161)
  • DenomKeyCodec (163-165)
  • DenomKeyCodec (167-169)
x/hold/keeper/invariants_test.go (1)
x/hold/keeper/keeper_test.go (1)
  • TestSuite (33-50)
x/hold/keeper/keeper_test.go (1)
testutil/assertions/general.go (1)
  • RequireNotPanicsNoErrorf (55-57)
x/hold/keeper/export_test.go (1)
x/hold/keeper/keeper.go (1)
  • Keeper (24-32)
⏰ 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). (24)
  • GitHub Check: go-test-single-db (benchmark-invariants, ubuntu-latest)
  • GitHub Check: runsim (multi-seed-short, goleveldb, ubuntu-latest)
  • GitHub Check: go-test-single-db (nondeterminism, ubuntu-latest)
  • GitHub Check: runsim (after-import, goleveldb, ubuntu-latest)
  • GitHub Check: go-test-multi-db (simple, goleveldb, ubuntu-latest)
  • GitHub Check: go-test-multi-db (benchmark, goleveldb, ubuntu-latest)
  • GitHub Check: runsim (import-export, goleveldb, ubuntu-latest)
  • GitHub Check: tests (02)
  • GitHub Check: tests (01)
  • GitHub Check: tests (00)
  • GitHub Check: tests (04)
  • GitHub Check: tests (03)
  • GitHub Check: test-race (03)
  • GitHub Check: test-race (04)
  • GitHub Check: test-race (01)
  • GitHub Check: test-race (02)
  • GitHub Check: test-race (00)
  • GitHub Check: Build OSX
  • GitHub Check: Build Linux
  • GitHub Check: golangci-lint
  • GitHub Check: Analyze (go)
  • GitHub Check: heighliner-docker
  • GitHub Check: docker
  • GitHub Check: ghcr-publish
🔇 Additional comments (21)
.changelog/unreleased/features/2419-hold-module-collections.md (1)

1-1: Verify the correct issue number reference.

The PR description states this closes issue #2491, but the changelog entry and filename reference issue #2419. Please verify which issue number is correct and update accordingly.

x/hold/codec.go (2)

22-53: LGTM!

The IntValueCodec implementation correctly handles encoding/decoding of sdkmath.Int values. The empty byte check in Decode returning ZeroInt() is a good defensive measure.


55-120: LGTM!

The AddressKeyCodec implementation is correct. Using a 1-byte length prefix makes the encoding self-delimiting, which allows the NonTerminal variants to correctly delegate to the terminal implementations.

app/app.go (1)

615-617: LGTM!

The HoldKeeper initialization correctly wraps the store key with runtime.NewKVStoreService(), consistent with other keepers in this file that have migrated to collections-based storage.

x/hold/keeper/genesis_test.go (2)

6-6: LGTM!

The import change from storetypes to cosmossdk.io/core/store aligns with the collections migration pattern.


153-153: LGTM!

The setup function signature change to use store.KVStore is consistent across all test cases and matches the new storage interface.

x/hold/keeper/invariants_test.go (2)

7-8: LGTM!

The import addition for cosmossdk.io/core/store correctly supports the updated KVStore interface used in the test setup functions.


49-49: LGTM!

The setup function signature consistently uses store.KVStore across all test cases, maintaining uniformity with the collections migration.

x/hold/keeper/export_test.go (1)

28-31: LGTM!

The signature change from store storetypes.KVStore to ctx sdk.Context correctly aligns with the new collections-based storage pattern in the keeper. The internal call now properly delegates to the context-aware setHoldCoinAmount method.

x/hold/keeper/grpc_query_test.go (2)

3-10: LGTM!

The import path update from cosmossdk.io/store/types to cosmossdk.io/core/store correctly aligns with the new KVStoreService-based access pattern used throughout the keeper migration.


107-118: LGTM!

The standardSetup function and all test case setup closures correctly use the updated store.KVStore type, maintaining consistency with the new storage access pattern.

x/hold/keeper/grpc_query.go (3)

292-310: LGTM!

The compareKeys helper correctly implements lexicographic comparison: addresses are compared byte-wise first, then denominations are compared as strings. This matches the expected key ordering for the collections map.


98-122: LGTM!

The pagination key decoding using pairCodec correctly validates that all bytes were consumed (bytesRead != len(pageKey)), which prevents malformed keys from being accepted.


124-166: Address-based pagination requires loading all entries into memory; this is an intentional design trade-off.

The implementation loads all hold entries into memory because the response must group entries by address and aggregate coins per denom. Since the underlying data structure stores individual (address, denom) pairs, standard iterator-based pagination that counts entries cannot be used for address-level pagination. The code comment at line 71 documents this decision: "It was easier to do it this way than shoehorn a solution into a call to FilteredPaginate."

This approach has O(n) memory complexity and could be a concern for chains with very large numbers of holds, but it's a documented architectural trade-off required by the address-grouping requirement, not an oversight.

x/hold/keeper/keeper_test.go (2)

148-162: LGTM!

The test helpers are correctly updated to use the new StoreService.OpenKVStore(ctx) pattern and pass ctx to SetHoldCoinAmount. The setHoldCoinAmountRaw helper still uses direct store access for injecting malformed test data, which is appropriate for testing error handling paths.


226-342: LGTM!

The test cases are correctly updated to use the new store.KVStore type in setup closures and the context-based SetHoldCoinAmount call. Test coverage for various scenarios (add, update, delete, negative amounts, empty denoms) is maintained.

x/hold/keeper/keeper.go (5)

58-65: LGTM!

Panicking on schema build failure during initialization is acceptable since this indicates a programming error that should be caught during development. The AppendLockedCoinsGetter call correctly registers the hold module's locked coins function with the bank keeper.


71-94: LGTM!

The setHoldCoinAmount and getHoldCoinAmount methods correctly use collections.Join for key construction and properly handle the collections.ErrNotFound case by returning zero values, maintaining backward compatibility with the previous behavior.


253-277: LGTM!

The GetHoldCoins implementation correctly uses collections.NewPrefixedPairRange to iterate only over entries for the given address, which is more efficient than scanning all entries. Error handling for individual entry read failures is properly accumulated and returned.


317-344: LGTM!

The IterateAllHolds implementation correctly iterates over all entries with nil range, handles per-entry errors gracefully by continuing iteration and accumulating errors, and respects the process callback's stop signal.


38-55: Key encoding is compatible with existing state.

The custom AddressKeyCodec and DenomKeyCodec produce byte-for-byte identical output to the original CreateHoldCoinKey function:

  • AddressKeyCodec encodes with a 1-byte length prefix followed by address bytes, matching address.MustLengthPrefix()
  • DenomKeyCodec encodes denom as raw bytes without a length prefix
  • PairKeyCodec combines them sequentially with KeyPrefixHoldCoin (0x00) as the store prefix

No state migration is required—the PR objective is valid.

Comment thread x/hold/keeper/keeper_test.go Outdated

@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: 1

♻️ Duplicate comments (1)
x/hold/keeper/keeper_test.go (1)

182-195: Error from store.Iterator should be checked before use.

The error returned by store.Iterator(nil, nil) on line 182 is assigned to err but not checked before the loop starts. If iterator creation fails, iter could be nil, causing a panic when iter.Valid() is called.

Apply this diff to handle the error:

 	iter, err := store.Iterator(nil, nil)
+	s.Require().NoError(err, "store.Iterator(nil, nil)")
 	defer func() {
 		if iter != nil {
 			iter.Close()
 		}
 	}()
🧹 Nitpick comments (1)
x/hold/keeper/keeper_test.go (1)

153-157: Unused store parameter should be removed.

The store parameter is no longer used since the function now calls s.keeper.SetHoldCoinAmount(s.ctx, ...) which uses the keeper's internal store service.

Apply this diff to remove the unused parameter:

-func (s *TestSuite) requireSetHoldCoinAmount(store store.KVStore, addr sdk.AccAddress, denom string, amount sdkmath.Int) {
+func (s *TestSuite) requireSetHoldCoinAmount(addr sdk.AccAddress, denom string, amount sdkmath.Int) {
 	assertions.RequireNotPanicsNoErrorf(s.T(), func() error {
 		return s.keeper.SetHoldCoinAmount(s.ctx, addr, denom, amount)
 	}, "setHoldCoinAmount(%s, %s%s)", s.getAddrName(addr), amount, denom)
 }

This will also require updating all call sites (e.g., lines 250, 263, 276, etc.) to remove the store argument.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 20e108e and 3868110.

📒 Files selected for processing (2)
  • x/hold/keeper/keeper.go (6 hunks)
  • x/hold/keeper/keeper_test.go (12 hunks)
🧰 Additional context used
🧠 Learnings (12)
📓 Common learnings
Learnt from: nullpointer0x00
Repo: provenance-io/provenance PR: 0
File: :0-0
Timestamp: 2024-06-10T19:25:28.209Z
Learning: A GitHub issue was created to track the migration of parameters for the metadata, marker, name, and msgFees modules as indicated by the TODO comments in the codebase. The issue is linked to PR #1932 and can be found at https://github.com/provenance-io/provenance/issues/1935.
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2422
File: x/asset/simulation/decoder.go:8-14
Timestamp: 2025-08-15T20:19:31.726Z
Learning: The asset module in Provenance doesn't use collections-based storage like ledger/registry modules. Instead, it delegates storage to the NFT module, so a collections-based store decoder isn't applicable here. The module should use its own types.StoreKey rather than exchange.StoreKey in RegisterStoreDecoder.
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2422
File: app/upgrades.go:52-53
Timestamp: 2025-09-05T19:28:28.271Z
Learning: The asset module in the Provenance blockchain doesn't maintain its own state storage and therefore doesn't require a StoreKey in upgrade configurations. It delegates all storage operations to the NFT module, unlike other modules like ledger and registry which have their own state stores.
📚 Learning: 2025-08-15T20:59:09.909Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2422
File: x/ledger/keeper/expected_keeper.go:11-19
Timestamp: 2025-08-15T20:59:09.909Z
Learning: The BankKeeper interface methods in Cosmos SDK v0.50.x and Provenance use `context.Context` as the context parameter type, not `sdk.Context`. This applies to methods like SendCoins, HasBalance, GetBalance, SpendableCoin, HasSupply, etc. The expected keeper interfaces should consistently use `context.Context` to match the actual Cosmos SDK bank keeper signatures.

Applied to files:

  • x/hold/keeper/keeper_test.go
  • x/hold/keeper/keeper.go
📚 Learning: 2024-10-02T02:51:17.452Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2140
File: x/metadata/keeper/bank_test.go:81-91
Timestamp: 2024-10-02T02:51:17.452Z
Learning: In test helper functions like `setBalances` in `x/metadata/keeper/bank_test.go`, it's acceptable to handle zero or negative amounts without raising an error, as it suits the test's purposes.

Applied to files:

  • x/hold/keeper/keeper_test.go
  • x/hold/keeper/keeper.go
📚 Learning: 2025-08-15T20:59:09.909Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2422
File: x/ledger/keeper/expected_keeper.go:11-19
Timestamp: 2025-08-15T20:59:09.909Z
Learning: In Cosmos SDK v0.50.x, the bank keeper interface methods use `context.Context` rather than `sdk.Context`. The BankKeeper interface should use `context.Context` for methods like SendCoins, HasBalance, GetBalance, etc.

Applied to files:

  • x/hold/keeper/keeper_test.go
  • x/hold/keeper/keeper.go
📚 Learning: 2024-06-10T19:25:28.209Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 0
File: :0-0
Timestamp: 2024-06-10T19:25:28.209Z
Learning: The `GetLockedCoins` function in `x/hold/keeper/locked_coins.go` must conform to the `banktypes.GetLockedCoinsFn` type, which does not allow returning an error. In cases where `k.GetHoldCoins` encounters an error, using `panic` is considered appropriate due to the severity of such errors.

Applied to files:

  • x/hold/keeper/keeper_test.go
  • x/hold/keeper/keeper.go
📚 Learning: 2025-08-15T22:52:24.461Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2422
File: x/ledger/keeper/keeper.go:74-173
Timestamp: 2025-08-15T22:52:24.461Z
Learning: In the ledger keeper NewKeeper function in x/ledger/keeper/keeper.go, SpicyLemon prefers to allow bankKeeper and registryKeeper dependencies to be nil rather than adding validation that would panic on nil values. The design intentionally supports nil keepers for flexibility.

Applied to files:

  • x/hold/keeper/keeper_test.go
  • x/hold/keeper/keeper.go
📚 Learning: 2024-10-08T18:12:51.936Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2140
File: x/metadata/keeper/migrations_v4.go:162-166
Timestamp: 2024-10-08T18:12:51.936Z
Learning: In Go, iterating over a `nil` slice with `range` is safe and behaves like iterating over an empty slice; no nil check is needed before iterating over a slice.

Applied to files:

  • x/hold/keeper/keeper_test.go
📚 Learning: 2024-10-08T18:12:51.935Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2140
File: x/metadata/keeper/mocks_test.go:579-589
Timestamp: 2024-10-08T18:12:51.935Z
Learning: When mocking methods like `MintCoins` and `BurnCoins` in tests, using slices to store error strings and consuming them sequentially is the preferred approach, as it offers precise control and simplicity without unnecessary complexity.

Applied to files:

  • x/hold/keeper/keeper_test.go
  • x/hold/keeper/keeper.go
📚 Learning: 2024-10-02T02:29:56.018Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2140
File: x/metadata/types/signer_utils.go:138-143
Timestamp: 2024-10-02T02:29:56.018Z
Learning: In this codebase, methods like `GetAcc()` and `GetSignerAcc()` are intended to return `nil` if the address is invalid, and address validation is performed elsewhere. These methods should return a single value without an error.

Applied to files:

  • x/hold/keeper/keeper_test.go
  • x/hold/keeper/keeper.go
📚 Learning: 2024-10-02T16:55:10.172Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2140
File: x/metadata/keeper/mocks_test.go:425-427
Timestamp: 2024-10-02T16:55:10.172Z
Learning: In this codebase, when using `sdk.AccAddress` as map keys, casting to `string` is preferred over calling `.String()` for performance reasons.

Applied to files:

  • x/hold/keeper/keeper_test.go
📚 Learning: 2024-10-08T18:12:51.936Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 1868
File: x/exchange/keeper/payments.go:24-34
Timestamp: 2024-10-08T18:12:51.936Z
Learning: The `parsePaymentStoreValue` function in `x/exchange/keeper/payments.go` returns `nil, nil` when the input byte slice is empty, as a design decision to simplify handling for the caller. This behavior is based on the rationale provided by a user, emphasizing ease of use over explicit error signaling for empty inputs.

Applied to files:

  • x/hold/keeper/keeper_test.go
📚 Learning: 2024-10-10T23:08:15.256Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2175
File: .golangci.yml:87-87
Timestamp: 2024-10-10T23:08:15.256Z
Learning: The `depguard` definitions in `.golangci.yml` are prefixes (unless they end with `$`), so the entry `- cosmossdk.io/client/v2` covers imports from `cosmossdk.io/client/v2` and its subpackages like `autocli`. Changing imports from `cosmossdk.io/client/v2/autocli` to `cosmossdk.io/client/v2` in `app/app.go` would result in unresolved references because the required code resides in the `autocli` package.

Applied to files:

  • x/hold/keeper/keeper.go
🧬 Code graph analysis (1)
x/hold/keeper/keeper.go (4)
internal/provutils/pair.go (1)
  • Pair (6-9)
x/hold/codec.go (22)
  • AddressKeyCodec (56-56)
  • AddressKeyCodec (58-71)
  • AddressKeyCodec (73-84)
  • AddressKeyCodec (86-88)
  • AddressKeyCodec (90-92)
  • AddressKeyCodec (94-100)
  • AddressKeyCodec (102-104)
  • AddressKeyCodec (106-108)
  • AddressKeyCodec (110-112)
  • AddressKeyCodec (114-116)
  • AddressKeyCodec (118-120)
  • DenomKeyCodec (123-123)
  • DenomKeyCodec (125-131)
  • DenomKeyCodec (133-135)
  • DenomKeyCodec (137-139)
  • DenomKeyCodec (141-143)
  • DenomKeyCodec (145-149)
  • DenomKeyCodec (151-153)
  • DenomKeyCodec (155-157)
  • DenomKeyCodec (159-161)
  • DenomKeyCodec (163-165)
  • DenomKeyCodec (167-169)
x/hold/keys.go (1)
  • ModuleName (5-5)
x/hold/keeper/keys.go (1)
  • KeyPrefixHoldCoin (17-17)
⏰ 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). (17)
  • GitHub Check: test-race (00)
  • GitHub Check: test-race (03)
  • GitHub Check: tests (02)
  • GitHub Check: test-race (02)
  • GitHub Check: tests (04)
  • GitHub Check: tests (03)
  • GitHub Check: test-race (04)
  • GitHub Check: tests (00)
  • GitHub Check: test-race (01)
  • GitHub Check: tests (01)
  • GitHub Check: build-linux
  • GitHub Check: Build OSX
  • GitHub Check: Analyze (go)
  • GitHub Check: ghcr-publish
  • GitHub Check: docker
  • GitHub Check: heighliner-docker
  • GitHub Check: golangci-lint
🔇 Additional comments (10)
x/hold/keeper/keeper.go (6)

71-84: LGTM!

The migration from direct store access to collections-based storage is implemented correctly. The function properly uses collections.Join for composite key creation and handles zero amounts by removing the entry.


87-94: LGTM!

The function correctly handles collections.ErrNotFound by returning zero, preserving the expected behavior where a missing entry means no hold exists.


238-250: LGTM!

The function correctly distinguishes between "not found" (returns zero coin, no error) and actual errors (returns zero coin with wrapped error), maintaining the expected API contract.


253-277: LGTM!

The iteration logic with NewPrefixedPairRange correctly scopes to a single address. The error handling approach—continuing iteration while collecting errors—is appropriate for this use case.


283-311: LGTM!

The iteration pattern with early termination support is correctly implemented. The error handling properly collects errors while allowing iteration to continue.


317-344: LGTM!

The full iteration implementation is correct. Using nil range for full iteration and extracting both address and denom for error context provides good debugging information.

x/hold/keeper/keeper_test.go (4)

213-215: LGTM!

The error handling for store.Iterator is correctly implemented here, properly asserting no error before proceeding.


329-344: LGTM!

The test execution correctly uses the new context-based API s.keeper.SetHoldCoinAmount(s.ctx, ...) instead of the old store-based approach.


148-150: LGTM!

The helper correctly uses the new StoreService.OpenKVStore(ctx) pattern for accessing the KV store.


160-162: LGTM!

This helper correctly uses direct store access to set raw byte values, which is necessary for testing error handling with malformed data like "badvalue".

Comment thread x/hold/keeper/keeper.go
@nagarajdivine nagarajdivine force-pushed the feat/hold-module-collections branch from 3868110 to 90390cb Compare December 11, 2025 18:20

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
x/hold/keeper/keeper_test.go (1)

178-200: Check iterator creation error before using iterator.

The error from store.Iterator(nil, nil) at Line 182 is captured but never checked before calling iter.Valid() at Line 189. If iterator creation fails and returns a nil iterator, this will panic.

Apply this diff:

 func (s *TestSuite) clearHoldState() {
 	store := s.getStore()
 	var keys [][]byte
 
 	iter, err := store.Iterator(nil, nil)
+	s.Require().NoError(err, "store.Iterator(nil, nil)")
 	defer func() {
 		if iter != nil {
 			iter.Close()
 		}
 	}()
 
 	for ; iter.Valid(); iter.Next() {
 		s.Require().NoError(iter.Error(), "iter.Error()")
 		keys = append(keys, iter.Key())
 	}
 	err = iter.Close()
 	iter = nil
 	s.Require().NoError(err, "iter.Close()")
 
 	for _, key := range keys {
 		store.Delete(key)
 	}
 }
♻️ Duplicate comments (1)
x/hold/keeper/keeper.go (1)

34-66: Potential nil pointer dereference when bankKeeper is nil.

Line 64 calls bankKeeper.AppendLockedCoinsGetter(...) without checking if bankKeeper is nil. Based on learnings, nil keepers are intentionally allowed for flexibility in this codebase, so this call could panic.

Apply this diff to add a nil check:

 	k.Schema = schema
 
+	if bankKeeper != nil {
 		bankKeeper.AppendLockedCoinsGetter(k.GetLockedCoins)
+	}
 	return k
 }
🧹 Nitpick comments (2)
x/hold/keeper/grpc_query.go (1)

124-166: Consider memory implications of loading all entries into memory.

The current implementation loads all hold entries into allEntries before applying pagination. For chains with many accounts holding funds, this could consume significant memory.

Consider if there's a way to avoid loading all entries when not using reverse pagination - forward iteration with early termination would be more memory-efficient for the common case.

x/hold/codec.go (1)

125-135: Consider adding length prefix for non-terminal encoding.

EncodeNonTerminal should ideally use a length-prefixed encoding (similar to AddressKeyCodec) so that the decoder knows where the denom ends when it's not the final component. Currently it delegates to Encode which writes raw bytes without a delimiter.

If the denom is always the terminal component in practice, this works correctly. But the NonTerminal method name implies it should support non-terminal usage.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3868110 and 90390cb.

📒 Files selected for processing (10)
  • .changelog/unreleased/features/2419-hold-module-collections.md (1 hunks)
  • app/app.go (1 hunks)
  • x/hold/codec.go (2 hunks)
  • x/hold/keeper/export_test.go (1 hunks)
  • x/hold/keeper/genesis_test.go (5 hunks)
  • x/hold/keeper/grpc_query.go (2 hunks)
  • x/hold/keeper/grpc_query_test.go (6 hunks)
  • x/hold/keeper/invariants_test.go (19 hunks)
  • x/hold/keeper/keeper.go (6 hunks)
  • x/hold/keeper/keeper_test.go (12 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • .changelog/unreleased/features/2419-hold-module-collections.md
  • x/hold/keeper/grpc_query_test.go
  • x/hold/keeper/export_test.go
🧰 Additional context used
🧠 Learnings (21)
📓 Common learnings
Learnt from: nullpointer0x00
Repo: provenance-io/provenance PR: 0
File: :0-0
Timestamp: 2024-06-10T19:25:28.209Z
Learning: A GitHub issue was created to track the migration of parameters for the metadata, marker, name, and msgFees modules as indicated by the TODO comments in the codebase. The issue is linked to PR #1932 and can be found at https://github.com/provenance-io/provenance/issues/1935.
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2422
File: x/asset/simulation/decoder.go:8-14
Timestamp: 2025-08-15T20:19:31.726Z
Learning: The asset module in Provenance doesn't use collections-based storage like ledger/registry modules. Instead, it delegates storage to the NFT module, so a collections-based store decoder isn't applicable here. The module should use its own types.StoreKey rather than exchange.StoreKey in RegisterStoreDecoder.
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2422
File: app/upgrades.go:52-53
Timestamp: 2025-09-05T19:28:28.271Z
Learning: The asset module in the Provenance blockchain doesn't maintain its own state storage and therefore doesn't require a StoreKey in upgrade configurations. It delegates all storage operations to the NFT module, unlike other modules like ledger and registry which have their own state stores.
📚 Learning: 2025-08-15T20:59:09.909Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2422
File: x/ledger/keeper/expected_keeper.go:11-19
Timestamp: 2025-08-15T20:59:09.909Z
Learning: The BankKeeper interface methods in Cosmos SDK v0.50.x and Provenance use `context.Context` as the context parameter type, not `sdk.Context`. This applies to methods like SendCoins, HasBalance, GetBalance, SpendableCoin, HasSupply, etc. The expected keeper interfaces should consistently use `context.Context` to match the actual Cosmos SDK bank keeper signatures.

Applied to files:

  • x/hold/keeper/invariants_test.go
  • x/hold/keeper/genesis_test.go
  • x/hold/keeper/keeper.go
  • app/app.go
  • x/hold/keeper/grpc_query.go
  • x/hold/keeper/keeper_test.go
📚 Learning: 2025-08-15T20:59:09.909Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2422
File: x/ledger/keeper/expected_keeper.go:11-19
Timestamp: 2025-08-15T20:59:09.909Z
Learning: In Cosmos SDK v0.50.x, the bank keeper interface methods use `context.Context` rather than `sdk.Context`. The BankKeeper interface should use `context.Context` for methods like SendCoins, HasBalance, GetBalance, etc.

Applied to files:

  • x/hold/keeper/invariants_test.go
  • x/hold/keeper/keeper.go
  • app/app.go
  • x/hold/keeper/grpc_query.go
  • x/hold/keeper/keeper_test.go
📚 Learning: 2024-10-02T02:51:17.452Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2140
File: x/metadata/keeper/bank_test.go:81-91
Timestamp: 2024-10-02T02:51:17.452Z
Learning: In test helper functions like `setBalances` in `x/metadata/keeper/bank_test.go`, it's acceptable to handle zero or negative amounts without raising an error, as it suits the test's purposes.

Applied to files:

  • x/hold/keeper/invariants_test.go
  • x/hold/keeper/genesis_test.go
  • x/hold/keeper/keeper.go
  • x/hold/keeper/grpc_query.go
  • x/hold/keeper/keeper_test.go
📚 Learning: 2024-10-10T23:08:15.256Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2175
File: .golangci.yml:87-87
Timestamp: 2024-10-10T23:08:15.256Z
Learning: The `depguard` definitions in `.golangci.yml` are prefixes (unless they end with `$`), so the entry `- cosmossdk.io/client/v2` covers imports from `cosmossdk.io/client/v2` and its subpackages like `autocli`. Changing imports from `cosmossdk.io/client/v2/autocli` to `cosmossdk.io/client/v2` in `app/app.go` would result in unresolved references because the required code resides in the `autocli` package.

Applied to files:

  • x/hold/keeper/invariants_test.go
  • x/hold/keeper/keeper.go
📚 Learning: 2024-06-10T19:25:28.209Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 1993
File: x/marker/client/cli/cli_test.go:1090-1090
Timestamp: 2024-06-10T19:25:28.209Z
Learning: SpicyLemon prefers allowing tests to fail naturally if the setup is incorrect rather than explicitly checking and handling such errors within the test functions. They also recommend using `s.Require().NotNil(...)` instead of `s.T().Fatal(...)` for assertions in test functions.

Applied to files:

  • x/hold/keeper/invariants_test.go
  • x/hold/keeper/genesis_test.go
📚 Learning: 2024-10-02T03:37:18.486Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2140
File: x/metadata/keeper/msg_server_test.go:1225-1228
Timestamp: 2024-10-02T03:37:18.486Z
Learning: When test cases are run using `s.T().Run` instead of `s.Run`, use `require` with `t` instead of `s.Require()`, to ensure that assertions apply to the correct test instance.

Applied to files:

  • x/hold/keeper/invariants_test.go
📚 Learning: 2024-10-02T17:24:43.269Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2140
File: x/metadata/keeper/mocks_test.go:579-589
Timestamp: 2024-10-02T17:24:43.269Z
Learning: When mocking methods like `MintCoins` and `BurnCoins` in tests, using slices to store error strings and consuming them sequentially is the preferred approach, as it offers precise control and simplicity without unnecessary complexity.

Applied to files:

  • x/hold/keeper/invariants_test.go
  • x/hold/keeper/genesis_test.go
  • x/hold/keeper/keeper.go
  • x/hold/keeper/keeper_test.go
📚 Learning: 2024-06-10T19:25:28.209Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 0
File: :0-0
Timestamp: 2024-06-10T19:25:28.209Z
Learning: The `GetLockedCoins` function in `x/hold/keeper/locked_coins.go` must conform to the `banktypes.GetLockedCoinsFn` type, which does not allow returning an error. In cases where `k.GetHoldCoins` encounters an error, using `panic` is considered appropriate due to the severity of such errors.

Applied to files:

  • x/hold/keeper/invariants_test.go
  • x/hold/keeper/keeper.go
  • x/hold/keeper/grpc_query.go
  • x/hold/keeper/keeper_test.go
📚 Learning: 2024-10-08T18:12:51.936Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2140
File: x/metadata/types/signer_utils.go:138-143
Timestamp: 2024-10-08T18:12:51.936Z
Learning: In this codebase, methods like `GetAcc()` and `GetSignerAcc()` are intended to return `nil` if the address is invalid, and address validation is performed elsewhere. These methods should return a single value without an error.

Applied to files:

  • x/hold/keeper/invariants_test.go
  • x/hold/keeper/keeper.go
  • x/hold/keeper/grpc_query.go
  • x/hold/keeper/keeper_test.go
📚 Learning: 2024-06-10T19:25:28.209Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 0
File: :0-0
Timestamp: 2024-06-10T19:25:28.209Z
Learning: The `WithVestingLockedBypass` function in `x/hold/keeper/invariants.go` is specifically used to handle scenarios involving vesting and locked coins deep within third-party library code. It follows a pattern used by other injectable functions to allow bypassing specific portions of those functions.

Applied to files:

  • x/hold/keeper/invariants_test.go
  • x/hold/keeper/keeper.go
📚 Learning: 2025-08-15T20:34:01.110Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2422
File: x/asset/types/codec.go:9-10
Timestamp: 2025-08-15T20:34:01.110Z
Learning: In Go Cosmos SDK projects, sdk.Msg interface embeds proto.Message interface, making it valid to copy elements from []sdk.Msg to []proto.Message even though the slice types are different. The pattern of declaring AllRequestMsgs as []sdk.Msg for type assertions while copying to []proto.Message for RegisterImplementations is a common and correct pattern.

Applied to files:

  • x/hold/codec.go
📚 Learning: 2025-08-15T20:34:01.110Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2422
File: x/asset/types/codec.go:9-10
Timestamp: 2025-08-15T20:34:01.110Z
Learning: The pattern of declaring AllRequestMsgs as []sdk.Msg and then copying to []proto.Message for RegisterImplementations is a standard, correct pattern used throughout the Provenance codebase. This works because sdk.Msg interface embeds proto.Message interface, making the copy operation valid. This pattern appears consistently across 15+ modules and compiles without errors.

Applied to files:

  • x/hold/codec.go
📚 Learning: 2024-10-08T18:12:51.936Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2140
File: x/metadata/keeper/mocks_test.go:425-427
Timestamp: 2024-10-08T18:12:51.936Z
Learning: In this codebase, when using `sdk.AccAddress` as map keys, casting to `string` is preferred over calling `.String()` for performance reasons.

Applied to files:

  • x/hold/codec.go
  • x/hold/keeper/keeper_test.go
📚 Learning: 2024-10-02T03:04:32.929Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2140
File: x/metadata/keeper/migrations_v4_test.go:253-479
Timestamp: 2024-10-02T03:04:32.929Z
Learning: In Go tests within this project, it's acceptable to have similar helper functions like `newUUID`, `newScopeID`, `newSpecID`, and `newScope` defined within each test. Tests may tailor these functions to specific needs rather than sharing them, to avoid breaking other tests when changes are made and to keep them non-public.

Applied to files:

  • x/hold/keeper/genesis_test.go
📚 Learning: 2024-10-08T18:12:51.936Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2140
File: x/metadata/types/signer_utils_test.go:1837-1889
Timestamp: 2024-10-08T18:12:51.936Z
Learning: In Go tests where test cases are defined within a test case slice and iterated with `t.Run`, instances defined in the test case struct are not shared between subtests, ensuring test independence.

Applied to files:

  • x/hold/keeper/genesis_test.go
📚 Learning: 2024-10-02T03:49:31.991Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2140
File: x/metadata/types/address_test.go:69-80
Timestamp: 2024-10-02T03:49:31.991Z
Learning: In the `subSet` function used in unit tests, it's acceptable for it to panic if an out-of-bounds index is provided, as this indicates a test case was set up incorrectly.

Applied to files:

  • x/hold/keeper/genesis_test.go
📚 Learning: 2025-08-15T22:52:24.461Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2422
File: x/ledger/keeper/keeper.go:74-173
Timestamp: 2025-08-15T22:52:24.461Z
Learning: In the ledger keeper NewKeeper function in x/ledger/keeper/keeper.go, SpicyLemon prefers to allow bankKeeper and registryKeeper dependencies to be nil rather than adding validation that would panic on nil values. The design intentionally supports nil keepers for flexibility.

Applied to files:

  • x/hold/keeper/keeper.go
  • app/app.go
  • x/hold/keeper/grpc_query.go
📚 Learning: 2024-10-08T18:12:51.936Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 1868
File: x/exchange/keeper/payments.go:24-34
Timestamp: 2024-10-08T18:12:51.936Z
Learning: The `parsePaymentStoreValue` function in `x/exchange/keeper/payments.go` returns `nil, nil` when the input byte slice is empty, as a design decision to simplify handling for the caller. This behavior is based on the rationale provided by a user, emphasizing ease of use over explicit error signaling for empty inputs.

Applied to files:

  • x/hold/keeper/keeper.go
  • x/hold/keeper/keeper_test.go
📚 Learning: 2024-10-08T18:12:51.935Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2140
File: x/metadata/keeper/bank.go:76-87
Timestamp: 2024-10-08T18:12:51.935Z
Learning: In the `balanceValueOwnerTransformer` function in `x/metadata/keeper/bank.go`, it is intended to always return an `AccMDLink`, even if `mdAddr` is nil due to an error from `MetadataAddressFromDenom`.

Applied to files:

  • x/hold/keeper/keeper.go
📚 Learning: 2024-10-08T18:12:51.936Z
Learnt from: SpicyLemon
Repo: provenance-io/provenance PR: 2140
File: x/metadata/keeper/migrations_v4.go:162-166
Timestamp: 2024-10-08T18:12:51.936Z
Learning: In Go, iterating over a `nil` slice with `range` is safe and behaves like iterating over an empty slice; no nil check is needed before iterating over a slice.

Applied to files:

  • x/hold/keeper/keeper_test.go
🧬 Code graph analysis (4)
x/hold/keeper/invariants_test.go (1)
x/hold/keeper/keeper_test.go (1)
  • TestSuite (33-50)
x/hold/keeper/keeper.go (2)
x/hold/codec.go (23)
  • AddressKeyCodec (56-56)
  • AddressKeyCodec (58-71)
  • AddressKeyCodec (73-84)
  • AddressKeyCodec (86-88)
  • AddressKeyCodec (90-92)
  • AddressKeyCodec (94-100)
  • AddressKeyCodec (102-104)
  • AddressKeyCodec (106-108)
  • AddressKeyCodec (110-112)
  • AddressKeyCodec (114-116)
  • AddressKeyCodec (118-120)
  • DenomKeyCodec (123-123)
  • DenomKeyCodec (125-131)
  • DenomKeyCodec (133-135)
  • DenomKeyCodec (137-139)
  • DenomKeyCodec (141-143)
  • DenomKeyCodec (145-149)
  • DenomKeyCodec (151-153)
  • DenomKeyCodec (155-157)
  • DenomKeyCodec (159-161)
  • DenomKeyCodec (163-165)
  • DenomKeyCodec (167-169)
  • IntValueCodec (22-22)
x/hold/keeper/keys.go (1)
  • KeyPrefixHoldCoin (17-17)
app/app.go (1)
x/hold/keys.go (1)
  • StoreKey (8-8)
x/hold/keeper/grpc_query.go (4)
internal/provutils/pair.go (1)
  • Pair (6-9)
x/hold/keeper/keeper.go (1)
  • Keeper (24-32)
x/hold/hold.pb.go (3)
  • AccountHold (30-35)
  • AccountHold (39-39)
  • AccountHold (40-42)
x/hold/codec.go (22)
  • AddressKeyCodec (56-56)
  • AddressKeyCodec (58-71)
  • AddressKeyCodec (73-84)
  • AddressKeyCodec (86-88)
  • AddressKeyCodec (90-92)
  • AddressKeyCodec (94-100)
  • AddressKeyCodec (102-104)
  • AddressKeyCodec (106-108)
  • AddressKeyCodec (110-112)
  • AddressKeyCodec (114-116)
  • AddressKeyCodec (118-120)
  • DenomKeyCodec (123-123)
  • DenomKeyCodec (125-131)
  • DenomKeyCodec (133-135)
  • DenomKeyCodec (137-139)
  • DenomKeyCodec (141-143)
  • DenomKeyCodec (145-149)
  • DenomKeyCodec (151-153)
  • DenomKeyCodec (155-157)
  • DenomKeyCodec (159-161)
  • DenomKeyCodec (163-165)
  • DenomKeyCodec (167-169)
⏰ 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). (18)
  • GitHub Check: test-race (02)
  • GitHub Check: tests (04)
  • GitHub Check: tests (03)
  • GitHub Check: test-race (03)
  • GitHub Check: tests (02)
  • GitHub Check: tests (01)
  • GitHub Check: test-race (04)
  • GitHub Check: test-race (01)
  • GitHub Check: test-race (00)
  • GitHub Check: tests (00)
  • GitHub Check: build-linux
  • GitHub Check: Build Linux
  • GitHub Check: Build OSX
  • GitHub Check: heighliner-docker
  • GitHub Check: ghcr-publish
  • GitHub Check: docker
  • GitHub Check: Analyze (go)
  • GitHub Check: golangci-lint
🔇 Additional comments (11)
x/hold/keeper/genesis_test.go (2)

6-6: LGTM: Import updated to align with new store abstraction.

The import change from storetypes to cosmossdk.io/core/store is consistent with the PR-wide migration to KVStoreService-based storage patterns.


151-154: LGTM: Test function signatures correctly updated.

The setup function parameter type updated from storetypes.KVStore to store.KVStore is consistent across all test cases and aligns with the new storage abstraction used in the keeper.

app/app.go (1)

615-617: LGTM: HoldKeeper correctly initialized with KVStoreService.

The change to use runtime.NewKVStoreService(keys[hold.StoreKey]) follows the same pattern used by other keepers in this file (e.g., ConsensusParamsKeeper, AccountKeeper, BankKeeper), ensuring consistency with the collections-based storage migration.

x/hold/keeper/invariants_test.go (2)

7-8: LGTM: Import updated for new store abstraction.

The import change from storetypes to cosmossdk.io/core/store is consistent with the migration pattern across the module.


49-49: LGTM: Test setup function signatures consistently updated.

All test case setup functions have been updated to use store.KVStore parameter type, maintaining consistency with the new storage abstraction throughout the test file.

x/hold/keeper/grpc_query.go (3)

22-27: LGTM: Clean struct design for pagination processing.

The entryWithKey struct appropriately captures both successful entries and error states during iteration, enabling unified processing in the pagination loop.


54-67: LGTM: Good defensive nil checks with sensible defaults.

Properly handles nil request and nil pagination by initializing sensible defaults with CountTotal: true.


292-311: LGTM: Correct lexicographic key comparison.

The compareKeys function properly compares address bytes first, then denomination strings, providing correct lexicographic ordering for the composite key.

x/hold/codec.go (2)

22-53: LGTM: IntValueCodec implementation is correct.

The codec properly handles binary and JSON encoding/decoding for sdkmath.Int, with a sensible default of ZeroInt() for empty byte slices.


56-120: LGTM: AddressKeyCodec implementation is robust.

Good validation for nil addresses and empty buffers. The 1-byte length prefix encoding correctly supports addresses up to 255 bytes. NonTerminal variants properly delegate to base methods.

x/hold/keeper/keeper_test.go (1)

207-225: LGTM! Iterator error properly checked.

The error from store.Iterator is now properly checked before using the iterator, addressing the concern from the previous review.

Comment thread x/hold/codec.go
Comment thread x/hold/keeper/grpc_query.go
@nagarajdivine nagarajdivine force-pushed the feat/hold-module-collections branch from 90390cb to 721c742 Compare December 18, 2025 15:51
@nagarajdivine nagarajdivine force-pushed the feat/hold-module-collections branch from 721c742 to 05d52b2 Compare February 12, 2026 18:04
@nagarajdivine nagarajdivine force-pushed the feat/hold-module-collections branch from 05d52b2 to 53c716b Compare February 19, 2026 17:16

@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: 2

🧹 Nitpick comments (3)
x/hold/codec.go (1)

58-70: AddressKeyCodec.Encode validates only for an empty buffer, not for insufficient capacity.

The guard at line 62 (len(buffer) == 0) does not protect against the case where the buffer exists but is shorter than 1 + len(addr). In that scenario copy(buffer[1:], addr) silently writes zero bytes while the function returns 1 + len(addr) as bytes written — a silent, incorrect result. The collections framework always pre-allocates via Size() so this won't fire in practice, but the guard should match the actual requirement.

🛡️ Suggested defensive fix
-	if len(buffer) == 0 {
-		return 0, fmt.Errorf("buffer is empty")
-	}
+	if len(buffer) < 1+len(addr) {
+		return 0, fmt.Errorf("buffer too small: need %d bytes, got %d", 1+len(addr), len(buffer))
+	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@x/hold/codec.go` around lines 58 - 70, AddressKeyCodec.Encode currently only
checks for an empty buffer but must ensure the buffer has capacity for the
length prefix plus the address; change the guard to verify len(buffer) >=
1+len(addr) and return an error (e.g., collections.ErrEncoding or a descriptive
fmt.Errorf) when the buffer is too small so that copy(buffer[1:], addr) cannot
silently write zero bytes while the function returns a larger count; update the
error path in AddressKeyCodec.Encode accordingly and keep behavior for nil addr
and addr length > 255 the same.
x/hold/keeper/grpc_query.go (1)

22-27: entryErrors map is dead code; hasErr field is redundant.

Two cleanup opportunities in the local data structures:

  1. entryErrors map[int]error (lines 125 and 133) is populated at lines 142, 147, and 150 but is never consulted during the pagination processing loop — the same error values are already stored in allEntries[i].err and accessed exclusively via entry.err. Remove the map entirely.

  2. hasErr bool in entryWithKey is always set alongside a non-nil err, making it redundant with an err != nil check.

♻️ Proposed cleanup
 type entryWithKey struct {
 	key    collections.Pair[sdk.AccAddress, string]
 	amount sdkmath.Int
-	hasErr bool
 	err    error
 }
-var entryErrors map[int]error
 ...
-entryErrors = make(map[int]error)
-entryIndex := 0

 for ; iterator.Valid(); iterator.Next() {
 	kv, err := iterator.KeyValue()
 	if err != nil {
 		iterKey, keyErr := iterator.Key()
 		if keyErr == nil {
-			entryErrors[entryIndex] = fmt.Errorf("failed to read amount of %s for account %s: %w",
-				iterKey.K2(), iterKey.K1(), err)
-			allEntries = append(allEntries, entryWithKey{
-				key:    iterKey,
-				hasErr: true,
-				err:    entryErrors[entryIndex],
-			})
+			allEntries = append(allEntries, entryWithKey{
+				key: iterKey,
+				err: fmt.Errorf("failed to read amount of %s for account %s: %w", iterKey.K2(), iterKey.K1(), err),
+			})
 		} else {
-			entryErrors[entryIndex] = fmt.Errorf("failed to read entry: %w", err)
-			allEntries = append(allEntries, entryWithKey{
-				hasErr: true,
-				err:    entryErrors[entryIndex],
-			})
+			allEntries = append(allEntries, entryWithKey{
+				err: fmt.Errorf("failed to read entry: %w", err),
+			})
 		}
-		entryIndex++
 		continue
 	}
 	allEntries = append(allEntries, entryWithKey{
 		key:    kv.Key,
 		amount: kv.Value,
-		hasErr: false,
 	})
-	entryIndex++
 }

Replace all entry.hasErr checks in the processing loop with entry.err != nil.

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

In `@x/hold/keeper/grpc_query.go` around lines 22 - 27, Remove the dead
entryErrors map and the redundant hasErr field from entryWithKey: delete the
entryErrors map and all code that populates it, remove the hasErr bool from the
entryWithKey struct, stop setting hasErr when assigning entry.err, and replace
every check of entry.hasErr in the pagination/processing loop with entry.err !=
nil (continue using allEntries[i].err or entry.err for error handling). Ensure
no remaining references to entryErrors or hasErr remain and tests/compilation
reflect these deletions.
x/hold/keeper/keeper_test.go (1)

152-161: store parameter name shadows the store package import in helper functions.

Both requireSetHoldCoinAmount (line 152) and setHoldCoinAmountRaw (line 159) declare a parameter named store that shadows the imported store package within their bodies. This compiles correctly because neither body needs to reference the package itself, but it is a naming hazard that may confuse linters and future readers. Consider renaming to kvStore or st.

Additionally, requireSetHoldCoinAmount accepts the store store.KVStore parameter but never uses it — it drives the write through s.keeper.SetHoldCoinAmount(s.ctx, ...). The parameter exists to maintain a uniform call-site signature with setHoldCoinAmountRaw; a brief comment clarifying this intent would help.

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

In `@x/hold/keeper/keeper_test.go` around lines 152 - 161, Rename the shadowing
parameter named "store" in both helper functions to a non-conflicting identifier
(e.g., kvStore or st) to avoid shadowing the imported store package; update the
parameter name in setHoldCoinAmountRaw (function setHoldCoinAmountRaw) and in
requireSetHoldCoinAmount (function requireSetHoldCoinAmount) accordingly. Also
add a one-line comment to requireSetHoldCoinAmount explaining that the kvStore
parameter is intentionally unused and kept to preserve a uniform call-site
signature with setHoldCoinAmountRaw, and leave the function body calling
s.keeper.SetHoldCoinAmount(s.ctx, ...) as-is.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.changelog/unreleased/features/2419-hold-module-collections.md:
- Line 1: The changelog entry references issue `#2419` but the PR closes `#2491`;
update the changelog file so both the filename and the issue link inside the
content use the correct issue number (change the "2419" in the filename and the
"[`#2419`](...)" text to "2491" and adjust the URL to /issues/2491) so the
filename, the markdown text "Migrated hold module to collections for state
storage", and the linked issue number are consistent with the PR.

In `@x/hold/keeper/grpc_query.go`:
- Around line 124-165: The code in paginateAllHolds collects all entries into
allEntries via k.Holds.Iterate which materializes the entire dataset; change the
forward (non-reverse) pagination path to start the iterator at startKey using a
ranged iterator (collections.Range or equivalent) so you only scan from startKey
forward and stop once you have filled the requested page/limit instead of
appending every entry to allEntries; update logic in paginateAllHolds to stream
matching entries into the page result as you iterate and stop when page size is
reached, and for the reverse case retain early stopping (do not build the full
allEntries either) so you at least break out once the page is satisfied,
ensuring iterator.Close() is still deferred and entry error handling (the
entryWithKey/error mapping) is applied on-the-fly rather than storing everything
in allEntries.

---

Duplicate comments:
In `@x/hold/codec.go`:
- Around line 159-165: DecodeNonTerminal currently calls DenomKeyCodec.Decode
which consumes the entire buffer, so it cannot be used safely in non-terminal
key positions; update DenomKeyCodec to either implement a non-terminal-safe
format (e.g., change EncodeNonTerminal to write a length prefix and make
DecodeNonTerminal read that length and return only the denom bytes) or
explicitly reject non-terminal usage by having
EncodeNonTerminal/DecodeNonTerminal return an error indicating non-terminal
positions are unsupported; ensure you modify both
DenomKeyCodec.EncodeNonTerminal and DenomKeyCodec.DecodeNonTerminal (and any
related Encode/Decode helpers) so their behavior is consistent and documented in
the method comments.

In `@x/hold/keeper/keeper.go`:
- Line 64: The call to bankKeeper.AppendLockedCoinsGetter(k.GetLockedCoins) will
panic if bankKeeper is nil; update the initialization in keeper.go to check for
nil before calling AppendLockedCoinsGetter — e.g., only call
bankKeeper.AppendLockedCoinsGetter(k.GetLockedCoins) when bankKeeper != nil (or
when the provided BankKeeper implements an interface check), so tests can pass
nil keepers without dereferencing them; locate the call using the symbols
bankKeeper.AppendLockedCoinsGetter and k.GetLockedCoins and add the nil guard
around that invocation.

---

Nitpick comments:
In `@x/hold/codec.go`:
- Around line 58-70: AddressKeyCodec.Encode currently only checks for an empty
buffer but must ensure the buffer has capacity for the length prefix plus the
address; change the guard to verify len(buffer) >= 1+len(addr) and return an
error (e.g., collections.ErrEncoding or a descriptive fmt.Errorf) when the
buffer is too small so that copy(buffer[1:], addr) cannot silently write zero
bytes while the function returns a larger count; update the error path in
AddressKeyCodec.Encode accordingly and keep behavior for nil addr and addr
length > 255 the same.

In `@x/hold/keeper/grpc_query.go`:
- Around line 22-27: Remove the dead entryErrors map and the redundant hasErr
field from entryWithKey: delete the entryErrors map and all code that populates
it, remove the hasErr bool from the entryWithKey struct, stop setting hasErr
when assigning entry.err, and replace every check of entry.hasErr in the
pagination/processing loop with entry.err != nil (continue using
allEntries[i].err or entry.err for error handling). Ensure no remaining
references to entryErrors or hasErr remain and tests/compilation reflect these
deletions.

In `@x/hold/keeper/keeper_test.go`:
- Around line 152-161: Rename the shadowing parameter named "store" in both
helper functions to a non-conflicting identifier (e.g., kvStore or st) to avoid
shadowing the imported store package; update the parameter name in
setHoldCoinAmountRaw (function setHoldCoinAmountRaw) and in
requireSetHoldCoinAmount (function requireSetHoldCoinAmount) accordingly. Also
add a one-line comment to requireSetHoldCoinAmount explaining that the kvStore
parameter is intentionally unused and kept to preserve a uniform call-site
signature with setHoldCoinAmountRaw, and leave the function body calling
s.keeper.SetHoldCoinAmount(s.ctx, ...) as-is.

@@ -0,0 +1 @@
* Migrated hold module to collections for state storage [#2419](https://github.com/provenance-io/provenance/issues/2419).

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.

⚠️ Potential issue | 🟡 Minor

Issue number in changelog does not match the PR's linked issue.

The changelog filename and content both reference issue #2419, but the PR description states it closes issue #2491. Please verify and correct the issue number (the filename would also need renaming to match).

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

In @.changelog/unreleased/features/2419-hold-module-collections.md at line 1,
The changelog entry references issue `#2419` but the PR closes `#2491`; update the
changelog file so both the filename and the issue link inside the content use
the correct issue number (change the "2419" in the filename and the
"[`#2419`](...)" text to "2491" and adjust the URL to /issues/2491) so the
filename, the markdown text "Migrated hold module to collections for state
storage", and the linked issue number are consistent with the PR.

Comment on lines +124 to +165
var allEntries []entryWithKey
var entryErrors map[int]error

if len(key) != 0 {
iterator := getIterator(prefixStore, key, reverse)
defer iterator.Close() //nolint:errcheck // close error safe to ignore in this context.
iterator, err := k.Holds.Iterate(ctx, nil)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to create iterator: %v", err)
}
defer iterator.Close() //nolint:errcheck // close error safe to ignore in this context.

for ; iterator.Valid(); iterator.Next() {
if err := iterator.Error(); err != nil {
return nil, err
entryErrors = make(map[int]error)
entryIndex := 0

// Collect all entries
for ; iterator.Valid(); iterator.Next() {
kv, err := iterator.KeyValue()
if err != nil {
iterKey, keyErr := iterator.Key()
if keyErr == nil {
entryErrors[entryIndex] = fmt.Errorf("failed to read amount of %s for account %s: %w",
iterKey.K2(), iterKey.K1(), err)
allEntries = append(allEntries, entryWithKey{
key: iterKey,
hasErr: true,
err: entryErrors[entryIndex],
})
} else {
entryErrors[entryIndex] = fmt.Errorf("failed to read entry: %w", err)
allEntries = append(allEntries, entryWithKey{
hasErr: true,
err: entryErrors[entryIndex],
})
}
entryIndex++
continue
}

ikey := iterator.Key()
addr, denom := ParseHoldCoinKeyUnprefixed(ikey)
if !addr.Equals(lastAddr) {
if uint64(len(resp.Holds)) >= limit {
resp.Pagination.NextKey = ikey
break
allEntries = append(allEntries, entryWithKey{
key: kv.Key,
amount: kv.Value,
hasErr: false,
})
entryIndex++

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.

⚠️ Potential issue | 🟠 Major

paginateAllHolds performs a full collection scan into memory before applying pagination.

All hold entries across all accounts are materialized into allEntries (line 124) before any pagination logic runs. This is O(n) in both time and heap allocation, where n is the total number of hold coin entries in the module. Concurrent GetAllHolds queries compound the pressure. On a chain with a large number of hold entries, this:

  1. Allocates unbounded heap per request.
  2. Can exceed query timeout limits at scale.
  3. Is a potential amplification vector for resource exhaustion.

For forward, non-reverse, key-based pagination the iterator could be started directly at startKey using a ranged collections.Range starting at startKey, avoiding the full scan. The reverse case is harder to optimize this way, but it would at least halve memory usage for the common forward case.

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

In `@x/hold/keeper/grpc_query.go` around lines 124 - 165, The code in
paginateAllHolds collects all entries into allEntries via k.Holds.Iterate which
materializes the entire dataset; change the forward (non-reverse) pagination
path to start the iterator at startKey using a ranged iterator
(collections.Range or equivalent) so you only scan from startKey forward and
stop once you have filled the requested page/limit instead of appending every
entry to allEntries; update logic in paginateAllHolds to stream matching entries
into the page result as you iterate and stop when page size is reached, and for
the reverse case retain early stopping (do not build the full allEntries either)
so you at least break out once the page is satisfied, ensuring iterator.Close()
is still deferred and entry error handling (the entryWithKey/error mapping) is
applied on-the-fly rather than storing everything in allEntries.

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.

Switch to collections in the hold module.

1 participant