Skip to content

Indexer: add GetHistory(scripts) RPC#906

Open
louisinger wants to merge 2 commits intoarkade-os:masterfrom
louisinger:get-history
Open

Indexer: add GetHistory(scripts) RPC#906
louisinger wants to merge 2 commits intoarkade-os:masterfrom
louisinger:get-history

Conversation

@louisinger
Copy link
Collaborator

@louisinger louisinger commented Feb 8, 2026

This PR adds an RPC returning the list of txids where the given script are involved : either spend a coin locked by one of the scripts or create new coin locked by one of the scripts.

combined with GetVirtualTxs, it will allow clients to restore transaction history.

@Kukks @altafan please review

Summary by CodeRabbit

Release Notes

  • New Features
    • Added a new history endpoint that retrieves transaction IDs associated with provided scripts, with built-in pagination support for efficient browsing of results.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 8, 2026

Walkthrough

This PR adds a new GetHistory endpoint to retrieve transaction IDs associated with provided public keys. Changes span API specifications (OpenAPI and protobuf), application service layer, domain repository interfaces, database implementations across Badger/Postgres/SQLite with corresponding SQL queries, gRPC handler integration, and associated tests.

Changes

Cohort / File(s) Summary
API Specifications
api-spec/openapi/swagger/ark/v1/indexer.openapi.json, api-spec/protobuf/ark/v1/indexer.proto
Added new GetHistory RPC endpoint with GetHistoryRequest/GetHistoryResponse schemas; removed IndexerTxHistoryRecord schema. Endpoint accepts scripts array and pagination parameters, returning txids and page metadata.
Application Layer
internal/core/application/indexer.go, internal/core/application/types.go
Added GetHistory method to IndexerService interface and implementation with pubkey validation, transaction ID fetching, and pagination. Added GetHistoryResp struct with Txids and Page fields.
Domain Layer
internal/core/domain/vtxo_repo.go
Extended VtxoRepository interface with new GetTxidsWithPubKeys method; refactored GetAllVtxosWithPubKeys parameter formatting.
Database Layer - Badger
internal/infrastructure/db/badger/vtxo_repo.go
Implemented GetTxidsWithPubKeys by calling GetAllVtxosWithPubKeys and deduplicating Txid, ArkTxid, and SettledBy values.
Database Layer - Postgres
internal/infrastructure/db/postgres/sqlc/query.sql, internal/infrastructure/db/postgres/sqlc/queries/query.sql.go, internal/infrastructure/db/postgres/vtxo_repo.go
Added SelectDistinctTxidsWithPubkeys SQL query returning distinct txids from three sources (txid, ark_txid, settled_by); implemented GetTxidsWithPubKeys repository method.
Database Layer - SQLite
internal/infrastructure/db/sqlite/sqlc/query.sql, internal/infrastructure/db/sqlite/sqlc/queries/models.go, internal/infrastructure/db/sqlite/sqlc/queries/query.sql.go, internal/infrastructure/db/sqlite/vtxo_repo.go
Added SelectDistinctTxidsWithPubkeys SQL query; updated Vtxo and VtxoVw struct UpdatedAt fields to sql.NullInt64; modified After parameter handling in SelectPendingSpentVtxosWithPubkeysParams and SelectVtxosWithPubkeysParams to support nullable int64.
gRPC Handler
internal/interface/grpc/handlers/indexer.go
Added GetHistory handler method that validates scripts, derives pubkeys, parses pagination, delegates to service, and returns paginated txids.
Tests
internal/infrastructure/db/service_test.go
Added test_get_txids_with_pubkeys subtest validating GetTxidsWithPubKeys behavior across single and multiple pubkeys, including deduplication of Txid, ArkTxid, and SettledBy values.

Sequence Diagram

sequenceDiagram
    participant Client as gRPC Client
    participant Handler as Handler<br/>(indexer.go)
    participant Service as Service<br/>(indexer.go)
    participant Repo as Repository<br/>(vtxo_repo.go)
    participant DB as Database

    Client->>Handler: GetHistory(scripts, page)
    Handler->>Handler: Parse scripts to pubkeys
    Handler->>Handler: Parse pagination params
    Handler->>Service: GetHistory(pubkeys, page)
    Service->>Service: Validate pubkeys not empty
    Service->>Repo: GetTxidsWithPubKeys(pubkeys)
    Repo->>DB: SelectDistinctTxidsWithPubkeys(pubkeys)
    DB-->>Repo: []string (txids)
    Repo-->>Service: []string (txids)
    Service->>Service: Apply pagination
    Service-->>Handler: GetHistoryResp{Txids, Page}
    Handler-->>Client: GetHistoryResponse{Txids, Page}
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Update apis #647: Modifies indexer/vtxo repository and database query layer with multi-pubkey/script lookups affecting the same interfaces and SQL query signatures.
  • Tidy up wallet & Introduce Signer #702: Alters indexer service surface (removing pubkey field, changing NewIndexerService) that directly intersects with GetHistory modifications.
  • Final refactor #657: Updates indexer service proto and OpenAPI definitions affecting the same API surface.

Suggested reviewers

  • Kukks
  • altafan
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
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.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Indexer: add GetHistory(scripts) RPC' accurately and concisely describes the main change—introducing a new GetHistory RPC endpoint that accepts scripts as parameters.

✏️ 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

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@api-spec/openapi/swagger/ark/v1/indexer.openapi.json`:
- Around line 345-381: The "scripts" query parameter in the GetHistory operation
(operationId IndexerService_GetHistory, path "/v1/indexer/history") must be
marked required and bounded: add "required": true for that parameter and add a
"maxItems" (e.g. 50) to its schema while keeping "minItems": 1 and
"type":"array" with "items":{"type":"string"} so the contract enforces a
non-empty, capped list consistent with server limits.

In `@api-spec/protobuf/ark/v1/indexer.proto`:
- Around line 347-356: The deleted protobuf message IndexerTxHistoryRecord is a
breaking change; restore compatibility by either (A) reintroducing a placeholder
message named IndexerTxHistoryRecord that declares reserved field numbers/names
previously used (use the original numeric tags as reserved) so existing
wire/JSON clients remain compatible, or (B) if deletion was intentional, update
the protobuf config to explicitly allow this breaking change by adding an
exception/annotation for IndexerTxHistoryRecord in your buf/breaking config;
locate references around GetHistoryRequest/GetHistoryResponse to ensure callers
still compile and update any imports or comments accordingly.
🧹 Nitpick comments (6)
internal/infrastructure/db/postgres/sqlc/query.sql (1)

258-270: Consider scanning vtxo_vw once instead of three times.

The query hits vtxo_vw three times with the same pubkey filter. This can be consolidated into a single pass, which is more efficient especially as the vtxo set grows:

♻️ Suggested optimization
 -- name: SelectDistinctTxidsWithPubkeys :many
-SELECT DISTINCT t.txid FROM (
-  SELECT vtxo_vw.txid AS txid FROM vtxo_vw
-  WHERE vtxo_vw.pubkey = ANY($1::varchar[])
-  UNION
-  SELECT vtxo_vw.ark_txid AS txid FROM vtxo_vw
-  WHERE vtxo_vw.pubkey = ANY($1::varchar[])
-    AND COALESCE(vtxo_vw.ark_txid, '') != ''
-  UNION
-  SELECT vtxo_vw.settled_by AS txid FROM vtxo_vw
-  WHERE vtxo_vw.pubkey = ANY($1::varchar[])
-    AND COALESCE(vtxo_vw.settled_by, '') != ''
-) t;
+SELECT DISTINCT t.txid FROM vtxo_vw v,
+LATERAL (VALUES (v.txid), (v.ark_txid), (v.settled_by)) AS t(txid)
+WHERE v.pubkey = ANY($1::varchar[])
+  AND COALESCE(t.txid, '') != '';

Also note: the outer DISTINCT is redundant since UNION (without ALL) already eliminates duplicates.

internal/core/application/indexer.go (1)

406-421: In-memory pagination loads all txids regardless of requested page.

GetTxidsWithPubKeys fetches every matching txid, then paginate slices in Go. For power users with very large histories this could become expensive. This is consistent with the rest of the file, so not blocking — but worth noting as a future scalability concern if history grows significantly. Pushing LIMIT/OFFSET into the SQL query would be more efficient.

api-spec/openapi/swagger/ark/v1/indexer.openapi.json (1)

915-929: Mirror the query constraints in GetHistoryRequest.

The request schema doesn’t currently enforce scripts presence or bounds. Keeping it aligned with the query parameter improves generated client behavior and validation.

Suggested update
       "GetHistoryRequest": {
         "title": "GetHistoryRequest",
         "type": "object",
+        "required": ["scripts"],
         "properties": {
           "page": {
             "$ref": "#/components/schemas/IndexerPageRequest"
           },
           "scripts": {
             "type": "array",
+            "minItems": 1,
+            "maxItems": 100,
             "items": {
               "type": "string"
             }
           }
         }
       },
internal/infrastructure/db/badger/vtxo_repo.go (1)

257-281: Sort txids for deterministic history results.

Map iteration order is random; sorting keeps paging stable across runs/backends.

Suggested update
 	out := make([]string, 0, len(seen))
 	for txid := range seen {
 		out = append(out, txid)
 	}
+	sort.Strings(out)
 	return out, nil
internal/infrastructure/db/postgres/vtxo_repo.go (1)

385-396: Sort txids for deterministic history results.

Keeps paging stable across repeated calls.

Suggested update
 	txids, err := v.querier.SelectDistinctTxidsWithPubkeys(ctx, pubkeys)
 	if err != nil {
 		if errors.Is(err, sql.ErrNoRows) {
 			return nil, nil
 		}
 		return nil, err
 	}
+	sort.Strings(txids)
 	return txids, nil
internal/infrastructure/db/sqlite/vtxo_repo.go (1)

391-402: Sort txids for deterministic history results.

Keeps paging stable across runs/backends.

Suggested update
 	txids, err := v.querier.SelectDistinctTxidsWithPubkeys(ctx, pubkeys)
 	if err != nil {
 		if errors.Is(err, sql.ErrNoRows) {
 			return nil, nil
 		}
 		return nil, err
 	}
+	sort.Strings(txids)
 	return txids, nil

Comment on lines +345 to +381
"/v1/indexer/history": {
"get": {
"tags": [
"IndexerService"
],
"description": "GetHistory returns the history of txids involving the provided scripts.",
"operationId": "IndexerService_GetHistory",
"parameters": [
{
"name": "scripts",
"in": "query",
"style": "simple",
"schema": {
"minItems": 1,
"type": "array",
"items": {
"type": "string"
}
}
},
{
"name": "page.size",
"in": "query",
"schema": {
"type": "integer",
"format": "int32"
}
},
{
"name": "page.index",
"in": "query",
"schema": {
"type": "integer",
"format": "int32"
}
}
],
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Bound and require the scripts query array.

minItems is set but required and maxItems aren’t; this leaves the contract open to empty/unbounded lists (and matches the CKV_OPENAPI_21 hint). Add a max cap aligned with server limits and mark the parameter required.

Suggested update
           {
             "name": "scripts",
             "in": "query",
+            "required": true,
             "style": "simple",
             "schema": {
               "minItems": 1,
+              "maxItems": 100,
               "type": "array",
               "items": {
                 "type": "string"
               }
             }
           }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"/v1/indexer/history": {
"get": {
"tags": [
"IndexerService"
],
"description": "GetHistory returns the history of txids involving the provided scripts.",
"operationId": "IndexerService_GetHistory",
"parameters": [
{
"name": "scripts",
"in": "query",
"style": "simple",
"schema": {
"minItems": 1,
"type": "array",
"items": {
"type": "string"
}
}
},
{
"name": "page.size",
"in": "query",
"schema": {
"type": "integer",
"format": "int32"
}
},
{
"name": "page.index",
"in": "query",
"schema": {
"type": "integer",
"format": "int32"
}
}
],
"/v1/indexer/history": {
"get": {
"tags": [
"IndexerService"
],
"description": "GetHistory returns the history of txids involving the provided scripts.",
"operationId": "IndexerService_GetHistory",
"parameters": [
{
"name": "scripts",
"in": "query",
"required": true,
"style": "simple",
"schema": {
"minItems": 1,
"maxItems": 100,
"type": "array",
"items": {
"type": "string"
}
}
},
{
"name": "page.size",
"in": "query",
"schema": {
"type": "integer",
"format": "int32"
}
},
{
"name": "page.index",
"in": "query",
"schema": {
"type": "integer",
"format": "int32"
}
}
],
🧰 Tools
🪛 Checkov (3.2.334)

[medium] 357-363: Ensure that arrays have a maximum number of items

(CKV_OPENAPI_21)

🤖 Prompt for AI Agents
In `@api-spec/openapi/swagger/ark/v1/indexer.openapi.json` around lines 345 - 381,
The "scripts" query parameter in the GetHistory operation (operationId
IndexerService_GetHistory, path "/v1/indexer/history") must be marked required
and bounded: add "required": true for that parameter and add a "maxItems" (e.g.
50) to its schema while keeping "minItems": 1 and "type":"array" with
"items":{"type":"string"} so the contract enforces a non-empty, capped list
consistent with server limits.

Comment on lines +347 to 356
}

message GetHistoryRequest {
repeated string scripts = 1;
IndexerPageRequest page = 2;
}
message GetHistoryResponse {
repeated string txids = 1;
IndexerPageResponse page = 2;
} No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Breaking protobuf change: deletion of IndexerTxHistoryRecord fails buf breaking check.

The pipeline reports that the previously present message IndexerTxHistoryRecord was deleted. This is a backward-incompatible change per buf's wire/JSON compatibility rules. If existing clients depend on this message, they will break.

Options to resolve:

  1. Mark IndexerTxHistoryRecord as reserved (keeping field numbers reserved) instead of deleting it outright, so the breaking check passes.
  2. If intentional, add an exception/annotation for buf breaking or update the buf.yaml config to allow this specific deletion.
🤖 Prompt for AI Agents
In `@api-spec/protobuf/ark/v1/indexer.proto` around lines 347 - 356, The deleted
protobuf message IndexerTxHistoryRecord is a breaking change; restore
compatibility by either (A) reintroducing a placeholder message named
IndexerTxHistoryRecord that declares reserved field numbers/names previously
used (use the original numeric tags as reserved) so existing wire/JSON clients
remain compatible, or (B) if deletion was intentional, update the protobuf
config to explicitly allow this breaking change by adding an
exception/annotation for IndexerTxHistoryRecord in your buf/breaking config;
locate references around GetHistoryRequest/GetHistoryResponse to ensure callers
still compile and update any imports or comments accordingly.

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