Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@bitcoin-coder-bob
Copy link
Collaborator

@bitcoin-coder-bob bitcoin-coder-bob commented Jan 14, 2026

Issue: #883

rpc for GetIntent allowing caller to pass a txid and receive back corresponding intent.

Currently the intent table does not have a column for txid. This makes the lookup for intent by txid not easy. We can derive the txid of an intent via the proof column. This PR includes a db migration to add this new txid field, and backfill existing rows by grabbing the proof and deriving the txid and storing this in the new txid field.

The migration is crafted into files for sqlite and postgres called intent_txid_migration.go which will:

  1. check for existence of new txid column in intent table
  2. backfill txid based off each intent row's proof
  3. create an index on the txid, making efficient lookup for our new rpc to get intent by txid

I have this custom migration setup as:

intentTxidMigrationBegin := uint(20260114000000)
.
.
.
		if version < intentTxidMigrationBegin {
			if err := m.Migrate(intentTxidMigrationBegin); err != nil &&
.
.
.

(where 20260114000000 is the date of the new schema addtion migration sql file)

The idea here is that when we detect the db has not had the newest schema addition applied (which simply adds the txid column to the intent table), we apply this simple schema addition. Then we immediately backfill in this new txid column via the code in intent_txid_migration.go.

Testing was performed as follows:

  1. checkout master branch, bring up arkd (with sqlite configured as arkd's db), run integration tests to generate some db data.
  2. switch to this feature branch, run arkd. This will now trigger the migration code.
  3. Check that the sqlite db has the new txid column, and that the existing intent rows have this txid populated.

NOTE: This same custom migration is not being performed for badger.

Going forward we will write intent rows with the txid.

Summary by CodeRabbit

  • New Features

    • GET /v1/intent to retrieve an intent by txid (returns proof and message).
    • Service API and handlers to look up intents by txid.
  • Bug Fixes / Errors

    • New INTENT_NOT_FOUND error returned when no intent matches a queried txid.
  • Chores (Data & Migrations)

    • Added intent.txid column, backfill tooling, and index to populate and speed txid lookups.
  • Tests

    • Integration tests validating txid derivation and backfill.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 14, 2026

Walkthrough

Adds txid to Intent and propagates it across domain, constructors, repositories, SQLC, migrations and backfill; exposes GET /v1/intent (HTTP + gRPC) to fetch Intent by txid and adds INTENT_NOT_FOUND error.

Changes

Cohort / File(s) Summary
API Specs
api-spec/openapi/swagger/ark/v1/service.openapi.json, api-spec/protobuf/ark/v1/service.proto
New GET /v1/intent endpoint and protobuf RPC; adds GetIntentRequest/GetIntentResponse schemas and HTTP mapping.
Service API & App Logic
internal/core/application/service.go, internal/core/application/types.go
RegisterIntent now derives txid and calls NewIntent(txid, ...); added Service.GetIntentByTxid(ctx, txid).
Domain Model & Tests
internal/core/domain/intent.go, internal/core/domain/intent_test.go, internal/core/domain/round_repo.go, internal/core/domain/round_test.go
Added Intent.Txid, changed NewIntent signature to include txid, added RoundRepository.GetIntentByTxid, updated tests.
gRPC Handler & Permissions
internal/interface/grpc/handlers/arkservice.go, internal/interface/grpc/permissions/permissions.go
Added GetIntent gRPC handler calling GetIntentByTxid; whitelisted GetIntent with read permission.
Badger Repo
internal/infrastructure/db/badger/ark_repo.go
Added IntentIndex type, upsertIntentIndex, and GetIntentByTxid using the intent index.
Postgres Repo & SQLC
internal/infrastructure/db/postgres/round_repo.go, internal/infrastructure/db/postgres/sqlc/query.sql, internal/infrastructure/db/postgres/sqlc/queries/*, internal/infrastructure/db/postgres/sqlc/queries/models.go
Persist txid in UpsertIntent; added SelectIntentByTxid query, SQLC models include Txid, repository GetIntentByTxid implemented.
SQLite Repo & SQLC
internal/infrastructure/db/sqlite/round_repo.go, internal/infrastructure/db/sqlite/sqlc/query.sql, internal/infrastructure/db/sqlite/sqlc/queries/*, internal/infrastructure/db/sqlite/sqlc/queries/models.go
Persist txid in UpsertIntent; added SelectIntentByTxid query and GetIntentByTxid; SQLC models include Txid.
Migrations & Backfill (Postgres)
internal/infrastructure/db/postgres/migration/20260114000000_add_intent_txid.up.sql, ...down.sql, internal/infrastructure/db/postgres/intent_txid_migration.go, internal/infrastructure/db/postgres/intent_txid_migration_test.go
Adds intent.txid column, backfills txid from PSBT/raw proofs, creates index, and adds integration test.
Migrations & Backfill (SQLite)
internal/infrastructure/db/sqlite/migration/20260114000000_add_intent_txid.*, internal/infrastructure/db/sqlite/intent_txid_migration.go, internal/infrastructure/db/sqlite/intent_txid_migration_test.go
Adds intent.txid column, backfills txid from PSBT/raw proofs, creates index, and adds test.
Migration Orchestration & Tests
internal/infrastructure/db/service.go, internal/infrastructure/db/service_test.go
Runs backfill during migration orchestration; tests updated to assert GetIntentByTxid behavior.
Errors
pkg/errors/errors.go
Added exported error INTENT_NOT_FOUND.
Dependencies / ModFiles
go.mod, pkg/arkd-wallet/go.mod
Added/adjusted direct module dependencies (ltcd, google/uuid).

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant HTTP as HTTP/REST
  participant GRPC as ArkServiceHandler
  participant App as Service
  participant RepoMgr as RepoManager
  participant DB as Storage

  Client->>HTTP: GET /v1/intent?txid=...
  HTTP->>GRPC: Invoke GetIntent
  GRPC->>App: GetIntentByTxid(ctx, txid)
  App->>RepoMgr: Rounds().GetIntentByTxid(ctx, txid)
  RepoMgr->>DB: query SelectIntentByTxid(txid) / index lookup
  DB-->>RepoMgr: intent row or nil
  RepoMgr-->>App: domain.Intent or nil
  App-->>GRPC: GetIntentResponse or INTENT_NOT_FOUND
  GRPC-->>HTTP: 200 or 404
  HTTP-->>Client: response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • sekulicd
  • Kukks
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.44% 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 title 'get intent by txid' directly and clearly describes the main feature added: a new RPC endpoint to retrieve intents by transaction ID, which is the primary objective of this PR.

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


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.

@bitcoin-coder-bob
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 14, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

Caution

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

⚠️ Outside diff range comments (2)
internal/core/domain/round.go (1)

110-116: Silent error discard may hide malformed proofs.

The error from deriveTxidFromProof is discarded with _. If the proof is malformed and txid derivation fails, intent.Txid remains empty, which could cause lookup failures later. Consider logging the error or propagating it if txid derivation is required for valid intents.

🐛 Suggested fix to handle derivation errors
 for _, intent := range intents {
-	txid, _ := deriveTxidFromProof(intent.Proof)
-	intent.Txid = txid
+	txid, err := deriveTxidFromProof(intent.Proof)
+	if err != nil {
+		return nil, fmt.Errorf("failed to derive txid from proof for intent %s: %w", intent.Id, err)
+	}
+	intent.Txid = txid
 	if err := intent.validate(false); err != nil {
 		return nil, err
 	}
 }
internal/core/domain/intent.go (1)

20-32: Error from deriveTxidFromProof is silently discarded, losing diagnostic context.

Line 21 ignores the error returned by deriveTxidFromProof. If the proof is malformed, txid will be empty, and validation will fail with the generic "missing txid" error instead of the actual parsing error (e.g., "error deriving txid from proof: ...").

This loses valuable diagnostic information about why the intent creation failed.

Proposed fix
 func NewIntent(proof, message string, inputs []Vtxo) (*Intent, error) {
-	txid, _ := deriveTxidFromProof(proof)
+	txid, err := deriveTxidFromProof(proof)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create intent: %w", err)
+	}
 	intent := &Intent{
 		Id:      uuid.New().String(),
 		Inputs:  inputs,
 		Proof:   proof,
 		Message: message,
 		Txid:    txid,
 	}
 	if err := intent.validate(true); err != nil {
 		return nil, err
 	}
 	return intent, nil
 }
🤖 Fix all issues with AI agents
In `@internal/infrastructure/db/postgres/intent_txid_migration.go`:
- Around line 270-279: The current SQL in the copyData constant in
intent_txid_migration.go uses an INNER JOIN between vtxo and intent which drops
vtxo rows with NULL or missing intent_id; change the join to a LEFT JOIN (FROM
vtxo AS v LEFT JOIN intent AS i ON i.id = v.intent_id) so you still select v.*
and i.id AS intent_id (which will be NULL when no intent exists) and preserve
all vtxo rows into vtxo_new.
- Around line 190-247: fixReceiverTableFK currently uses SQLite-specific PRAGMA
and table-recreation DDL which will fail on PostgreSQL; replace the SQLite steps
with PostgreSQL-compatible ALTER TABLE statements and remove PRAGMA usage.
Specifically, in fixReceiverTableFK: drop the `PRAGMA foreign_keys = OFF/ON`
ExecContext calls, stop creating/dropping receiver_new and copying data, and
instead execute PostgreSQL DDL such as ALTER TABLE receiver ADD CONSTRAINT ...
FOREIGN KEY (intent_id) REFERENCES intent(id) and, if you need the composite
primary key, add or replace the primary key with ALTER TABLE receiver ADD
CONSTRAINT <name> PRIMARY KEY (intent_id, pubkey, onchain_address) (dropping any
existing PK first). Keep the transaction logic (tx := db.BeginTx,
tx.Commit/Rollback) and use tx.ExecContext for the ALTER TABLE statements,
returning formatted errors from the same function (fixReceiverTableFK) as
currently done.
- Around line 249-318: fixVtxoTableFK is using SQLite-specific PRAGMA and
table-recreation logic; remove the `PRAGMA foreign_keys = OFF;` and `PRAGMA
foreign_keys = ON;` Execs and replace the SQL with PostgreSQL-compatible
statements: create `vtxo_new` with proper Postgres types and the FOREIGN KEY
(intent_id) REFERENCES intent(id) clause, copy rows using an INSERT ... SELECT
(use LEFT JOIN if intent_id can be NULL or to preserve unmatched rows), then
DROP TABLE vtxo and ALTER TABLE vtxo_new RENAME TO vtxo inside the same
transaction; keep existing transaction handling (tx.BeginTx/Commit/Rollback) but
do not rely on PRAGMA toggles. Ensure the `createNew` and `copyData` SQL use
Postgres syntax (no IF NOT EXISTS on columns/PRAGMA) so the migration runs on
PostgreSQL.
- Around line 78-82: The INSERT SQL in the insertIntent constant (named
"InsertIntent" :exec) uses SQLite-style `?` placeholders which will fail on
Postgres; update the VALUES placeholders to positional Postgres form `$1, $2,
$3, $4, $5` (keeping the same column order id, round_id, proof, message, txid)
so the query executes correctly with the Postgres driver.
- Around line 156-182: The code incorrectly uses SQLite PRAGMA statements in the
migration (calls to tx.ExecContext executing `PRAGMA foreign_keys = OFF/ON`
around the intent swap). Replace those PRAGMA calls with a PostgreSQL-safe
approach: before dropping/renaming tables, either set session_replication_role =
'replica' for the session (requires superuser) via tx.ExecContext, or
programmatically drop and later recreate any foreign key constraints that
reference the intent table (find constraints referencing "intent", drop them,
perform the DROP TABLE/ALTER TABLE on intent and intent_new, then re-add the
constraints). Update the calls around the intent backfill/rename (the functions
using tx.ExecContext and the intent/intent_new checks and DROP/ALTER steps) to
use one of these Postgres methods instead of PRAGMA.

In `@internal/infrastructure/db/service.go`:
- Around line 296-315: The Postgres migration path is missing the same stepwise
backfill used for SQLite, so adding a NOT NULL UNIQUE txid will fail on existing
rows; update the initialization logic in service.go to call the Postgres
backfill before enforcing the final migration version: detect current migration
version via m.Version(), run m.Migrate(intentTxidMigrationBegin) if needed, then
invoke postgresdb.BackfillIntentTxid(ctx, db) (same as
sqlitedb.BackfillIntentTxid) to populate txid for existing intent rows, and only
after successful backfill proceed to the final migration step (or ensure the
Postgres migration SQL initially creates a nullable txid column then enforces
NOT NULL after backfill).

In `@internal/infrastructure/db/sqlite/intent_txid_migration.go`:
- Around line 31-33: The backfillIntent call uses context.Background() which
prevents propagation of the caller's context (cancellation/deadlines/tracing);
replace context.Background() with the provided ctx variable at the call site
where backfillIntent(context.Background(), dbh) is invoked so
backfillIntent(ctx, dbh) uses the caller's context for proper cancellation and
tracing.
- Around line 197-208: The INSERT SELECT using an INNER JOIN on intent will drop
receiver rows whose intent_id has no matching intent; change the SELECT to use a
LEFT JOIN (or LEFT OUTER JOIN) and preserve the original receiver.intent_id
(e.g., SELECT r.pubkey, r.onchain_address, r.amount, r.intent_id AS intent_id)
or use COALESCE(i.id, r.intent_id) so unmatched receivers are copied into
receiver_new instead of being lost; update the SQL string referenced by copyData
accordingly.
- Around line 267-279: The migration SQL in copyData uses an INNER JOIN between
vtxo and intent, which drops vtxo rows with NULL or missing intent_id; change
the SELECT to use a LEFT JOIN (FROM vtxo AS v LEFT JOIN intent AS i ON i.id =
v.intent_id) and select i.id AS intent_id so orphaned vtxos preserve NULL
intent_id into vtxo_new, and add a post-copy validation (similar to swapIntent
count check) comparing row counts between vtxo and vtxo_new to detect unexpected
losses.

In `@internal/infrastructure/db/sqlite/sqlc/queries/query.sql.go`:
- Around line 350-365: The SQLite variant SelectIntentByTxid is using
sql.NullString for txid while Postgres uses string, causing an inconsistent API;
update the Queries.SelectIntentByTxid signature to accept a plain string (same
as Postgres), inside the function convert that string to sql.NullString (e.g.,
set Valid=false when empty) before calling q.db.QueryRowContext with
selectIntentByTxid, and keep SelectIntentByTxidRow and returned types unchanged
so callers see a unified method signature across backends; alternatively, if you
prefer nullable behavior, wrap this conversion in the repository layer so the
public API remains string-based and backend-specific null handling is internal.
🧹 Nitpick comments (9)
api-spec/protobuf/ark/v1/service.proto (1)

285-289: Inconsistent indentation in oneof block.

The string txid field has extra leading spaces compared to other similar definitions in this file. Additionally, using oneof with a single field is unconventional but provides extensibility if you plan to add more filter options (e.g., by intent ID) in the future.

Suggested fix for formatting
 message GetIntentRequest {
   oneof filter {
-      string txid = 1;
-    }
+    string txid = 1;
+  }
 }
internal/core/application/service.go (1)

4099-4111: Consider distinguishing "not found" from internal errors.

The method wraps all repository errors as INTERNAL_ERROR, but a "not found" case is semantically different from an internal failure. API consumers may need to distinguish between "intent doesn't exist" vs "something went wrong internally."

♻️ Suggested improvement
 func (s *service) GetIntentByTxid(
 	ctx context.Context,
 	txid string,
 ) (domain.Intent, errors.Error) {
 	intent, err := s.repoManager.Rounds().GetIntentByTxid(ctx, txid)
 	if err != nil {
+		if strings.Contains(err.Error(), "not found") {
+			return domain.Intent{}, errors.TX_NOT_FOUND.New(
+				"intent with txid %s not found", txid,
+			)
+		}
 		return domain.Intent{}, errors.INTERNAL_ERROR.New(
 			"failed to get intent by txid %s: %w", txid, err,
 		)
 	}
 
 	return intent, nil
 }
internal/infrastructure/db/badger/ark_repo.go (1)

256-269: Use structured logging instead of fmt.Printf.

The error is logged using fmt.Printf which bypasses the application's logging infrastructure. This makes it harder to correlate errors and filter logs in production.

♻️ Suggested fix
+import log "github.com/sirupsen/logrus"
+
 // derive txids from intent proofs and add to each intents
 for k, v := range round.Intents {
 	if v.Txid != "" {
 		continue
 	}
 	txid, err := application.DeriveTxidFromProof(v.Proof)
 	if err != nil {
-		fmt.Printf("error deriving txid from proof: %s\n", err.Error())
+		log.WithError(err).Warnf("failed to derive txid from proof for intent %s", k)
 		continue
 	}
 	updatedIntent := v
 	updatedIntent.Txid = txid
 	round.Intents[k] = updatedIntent
 }
internal/infrastructure/db/sqlite/round_repo.go (2)

167-178: Redundant txid derivation when intent.Txid may already be populated.

The Intent struct now has a Txid field that's populated during NewIntent(). Here, application.DeriveTxidFromProof is called again to derive the txid, even though intent.Txid may already contain this value.

Additionally, there's code duplication between domain.deriveTxidFromProof and application.DeriveTxidFromProof.

Proposed fix: Use the existing intent.Txid field
 		for _, intent := range round.Intents {
-			txid, err := application.DeriveTxidFromProof(intent.Proof)
-			if err != nil {
-				return fmt.Errorf("failed to derive txid from proof: %w", err)
+			txid := intent.Txid
+			if txid == "" {
+				// Fallback for legacy intents without pre-populated Txid
+				var err error
+				txid, err = application.DeriveTxidFromProof(intent.Proof)
+				if err != nil {
+					return fmt.Errorf("failed to derive txid from proof: %w", err)
+				}
 			}
 			if err := querierWithTx.UpsertIntent(
 				ctx,
 				queries.UpsertIntentParams{
 					ID:      sql.NullString{String: intent.Id, Valid: true},
 					RoundID: sql.NullString{String: round.Id, Valid: true},
 					Proof:   sql.NullString{String: intent.Proof, Valid: true},
 					Message: sql.NullString{String: intent.Message, Valid: true},
 					Txid:    sql.NullString{String: txid, Valid: true},
 				},

462-475: Returned Intent is only partially populated.

The method returns a domain.Intent with only Proof and Message fields populated. The Id, Txid, Inputs, and Receivers fields are left as zero values. While this may suffice for the current API use case, returning partially populated domain objects can lead to subtle bugs if callers assume full initialization.

Consider either:

  1. Populating all available fields from the query result, or
  2. Returning a dedicated DTO instead of the domain object
api-spec/openapi/swagger/ark/v1/service.openapi.json (1)

371-409: Missing operation description and txid parameter should be required.

The new /v1/intent endpoint lacks a description field, unlike other operations in the spec. Additionally, the txid query parameter is not marked as required. If no txid is provided, the gRPC handler will return InvalidArgument with "unknown intent filter provided", which may be confusing to API consumers.

Suggested improvements
     "/v1/intent": {
       "get": {
         "tags": [
           "ArkService"
         ],
+        "description": "GetIntent retrieves an intent by its transaction ID.",
         "operationId": "ArkService_GetIntent",
         "parameters": [
           {
             "name": "txid",
             "in": "query",
+            "required": true,
             "schema": {
               "type": "string"
             }
           }
         ],
internal/infrastructure/db/postgres/round_repo.go (1)

431-444: Incomplete domain.Intent population in GetIntentByTxid.

The returned domain.Intent only populates Proof and Message fields, omitting Id, Inputs, Receivers, and Txid. If callers need the full intent data, this will cause issues.

Verify whether this minimal return is intentional for the use case (e.g., just returning the proof for verification) or if additional fields should be populated.

♻️ Suggested fix to include all available fields
 func (r *roundRepository) GetIntentByTxid(
 	ctx context.Context,
 	txid string,
 ) (domain.Intent, error) {
 	intent, err := r.querier.SelectIntentByTxid(ctx, txid)
 	if err != nil {
 		return domain.Intent{}, fmt.Errorf("failed to get intents by txid: %w", err)
 	}

 	return domain.Intent{
+		Txid:    txid,
 		Proof:   intent.Proof.String,
 		Message: intent.Message.String,
 	}, nil
 }
internal/infrastructure/db/sqlite/intent_txid_migration.go (1)

134-140: Duplicate deriveTxidFromProof implementation.

This function is duplicated in both SQLite and PostgreSQL migration files. Consider extracting it to a shared utility (e.g., application.DeriveTxidFromProof which already exists per the context).

♻️ Suggested refactor to use existing utility
+import "github.com/arkade-os/arkd/internal/core/application"
+
-func deriveTxidFromProof(proof string) (string, error) {
-	tx, err := psbt.NewFromRawBytes(strings.NewReader(proof), true)
-	if err != nil {
-		return "", fmt.Errorf("psbt parse: %w", err)
-	}
-	return tx.UnsignedTx.TxID(), nil
-}
+// Use application.DeriveTxidFromProof instead

Then replace calls to deriveTxidFromProof(proof) with application.DeriveTxidFromProof(proof).

internal/infrastructure/db/postgres/intent_txid_migration.go (1)

137-143: Duplicate deriveTxidFromProof - same as SQLite migration.

This is the third copy of deriveTxidFromProof. The utility already exists in internal/core/application/utils.go as DeriveTxidFromProof. Consider using that instead.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5eb9d84 and ae83eb9.

⛔ Files ignored due to path filters (4)
  • api-spec/protobuf/gen/ark/v1/indexer.pb.rgw.go is excluded by !**/gen/**
  • api-spec/protobuf/gen/ark/v1/service.pb.go is excluded by !**/*.pb.go, !**/gen/**
  • api-spec/protobuf/gen/ark/v1/service.pb.rgw.go is excluded by !**/gen/**
  • api-spec/protobuf/gen/ark/v1/service_grpc.pb.go is excluded by !**/*.pb.go, !**/gen/**
📒 Files selected for processing (31)
  • api-spec/openapi/swagger/ark/v1/service.openapi.json
  • api-spec/protobuf/ark/v1/service.proto
  • internal/core/application/service.go
  • internal/core/application/types.go
  • internal/core/application/utils.go
  • internal/core/domain/intent.go
  • internal/core/domain/intent_test.go
  • internal/core/domain/round.go
  • internal/core/domain/round_repo.go
  • internal/core/domain/round_test.go
  • internal/infrastructure/db/badger/ark_repo.go
  • internal/infrastructure/db/badger/intent_fees_repo.go
  • internal/infrastructure/db/postgres/intent_txid_migration.go
  • internal/infrastructure/db/postgres/migration/20260114000000_add_intent_txid.down.sql
  • internal/infrastructure/db/postgres/migration/20260114000000_add_intent_txid.up.sql
  • internal/infrastructure/db/postgres/round_repo.go
  • internal/infrastructure/db/postgres/sqlc/queries/models.go
  • internal/infrastructure/db/postgres/sqlc/queries/query.sql.go
  • internal/infrastructure/db/postgres/sqlc/query.sql
  • internal/infrastructure/db/service.go
  • internal/infrastructure/db/service_test.go
  • internal/infrastructure/db/sqlite/intent_txid_migration.go
  • internal/infrastructure/db/sqlite/migration/20260114000000_add_intent_txid.down.sql
  • internal/infrastructure/db/sqlite/migration/20260114000000_add_intent_txid.up.sql
  • internal/infrastructure/db/sqlite/round_repo.go
  • internal/infrastructure/db/sqlite/sqlc/queries/models.go
  • internal/infrastructure/db/sqlite/sqlc/queries/query.sql.go
  • internal/infrastructure/db/sqlite/sqlc/query.sql
  • internal/interface/grpc/handlers/arkservice.go
  • internal/interface/grpc/permissions/permissions.go
  • pkg/errors/errors.go
💤 Files with no reviewable changes (1)
  • internal/infrastructure/db/badger/intent_fees_repo.go
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-29T14:33:52.871Z
Learnt from: louisinger
Repo: arkade-os/arkd PR: 722
File: pkg/ark-lib/intent/proof.go:52-58
Timestamp: 2025-09-29T14:33:52.871Z
Learning: In btcsuite/btcd's psbt package, the NewFromRawBytes function's boolean parameter (b64) automatically handles base64 decoding when set to true, so passing a base64 string via strings.NewReader with b64=true is the correct usage pattern.

Applied to files:

  • internal/core/domain/intent.go
📚 Learning: 2025-09-29T14:33:52.871Z
Learnt from: louisinger
Repo: arkade-os/arkd PR: 722
File: pkg/ark-lib/intent/proof.go:52-58
Timestamp: 2025-09-29T14:33:52.871Z
Learning: In btcsuite/btcd's psbt package, the NewFromRawBytes function's boolean parameter (b64) automatically handles base64 decoding when set to true, so passing a base64 string via strings.NewReader with b64=true is the correct usage pattern. A common bug is manually base64-decoding the string and then passing b64=true, which causes a double-decode error.

Applied to files:

  • internal/core/domain/intent.go
🧬 Code graph analysis (14)
internal/core/domain/round_repo.go (3)
internal/core/domain/intent.go (1)
  • Intent (11-18)
internal/infrastructure/db/postgres/sqlc/queries/models.go (1)
  • Intent (33-39)
internal/infrastructure/db/sqlite/sqlc/queries/models.go (1)
  • Intent (31-37)
internal/core/domain/round.go (1)
pkg/ark-lib/intent/proof.go (1)
  • Proof (39-41)
internal/interface/grpc/permissions/permissions.go (1)
api-spec/protobuf/gen/ark/v1/service_grpc.pb.go (1)
  • ArkService_ServiceDesc (650-716)
internal/infrastructure/db/service_test.go (2)
pkg/ark-lib/intent/proof.go (1)
  • Proof (39-41)
internal/core/application/utils.go (1)
  • DeriveTxidFromProof (468-474)
internal/core/application/types.go (4)
internal/core/domain/intent.go (1)
  • Intent (11-18)
internal/infrastructure/db/postgres/sqlc/queries/models.go (1)
  • Intent (33-39)
internal/infrastructure/db/sqlite/sqlc/queries/models.go (1)
  • Intent (31-37)
pkg/errors/errors.go (1)
  • Error (39-46)
internal/core/domain/intent.go (3)
internal/infrastructure/db/postgres/sqlc/queries/models.go (2)
  • Vtxo (197-213)
  • Intent (33-39)
internal/infrastructure/db/sqlite/sqlc/queries/models.go (2)
  • Vtxo (184-200)
  • Intent (31-37)
pkg/ark-lib/intent/proof.go (1)
  • Proof (39-41)
internal/infrastructure/db/badger/ark_repo.go (3)
internal/core/domain/intent.go (2)
  • Intents (106-106)
  • Intent (11-18)
internal/core/application/utils.go (1)
  • DeriveTxidFromProof (468-474)
pkg/ark-lib/intent/proof.go (1)
  • Proof (39-41)
internal/infrastructure/db/sqlite/intent_txid_migration.go (1)
internal/infrastructure/db/postgres/intent_txid_migration.go (1)
  • BackfillIntentTxid (15-53)
internal/infrastructure/db/postgres/intent_txid_migration.go (1)
internal/infrastructure/db/sqlite/intent_txid_migration.go (1)
  • BackfillIntentTxid (13-50)
internal/infrastructure/db/service.go (2)
internal/infrastructure/db/postgres/intent_txid_migration.go (1)
  • BackfillIntentTxid (15-53)
internal/infrastructure/db/sqlite/intent_txid_migration.go (1)
  • BackfillIntentTxid (13-50)
internal/infrastructure/db/postgres/round_repo.go (5)
internal/core/application/utils.go (1)
  • DeriveTxidFromProof (468-474)
internal/infrastructure/db/postgres/sqlc/queries/query.sql.go (1)
  • UpsertIntentParams (1737-1743)
internal/core/domain/intent.go (1)
  • Intent (11-18)
internal/infrastructure/db/postgres/sqlc/queries/models.go (1)
  • Intent (33-39)
internal/infrastructure/db/sqlite/sqlc/queries/models.go (1)
  • Intent (31-37)
internal/infrastructure/db/sqlite/sqlc/queries/query.sql.go (2)
pkg/ark-lib/intent/proof.go (1)
  • Proof (39-41)
internal/infrastructure/db/postgres/sqlc/queries/query.sql.go (1)
  • SelectIntentByTxidRow (357-360)
internal/infrastructure/db/sqlite/round_repo.go (4)
internal/core/application/utils.go (1)
  • DeriveTxidFromProof (468-474)
internal/infrastructure/db/sqlite/sqlc/queries/query.sql.go (1)
  • UpsertIntentParams (1801-1807)
internal/core/domain/intent.go (1)
  • Intent (11-18)
internal/infrastructure/db/sqlite/sqlc/queries/models.go (1)
  • Intent (31-37)
internal/infrastructure/db/postgres/sqlc/queries/query.sql.go (4)
pkg/ark-lib/intent/proof.go (1)
  • Proof (39-41)
internal/infrastructure/db/postgres/sqlc/queries/db.go (1)
  • Queries (23-25)
internal/infrastructure/db/sqlite/sqlc/queries/db.go (1)
  • Queries (23-25)
internal/infrastructure/db/sqlite/sqlc/queries/query.sql.go (1)
  • SelectIntentByTxidRow (355-358)
⏰ 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). (2)
  • GitHub Check: unit tests
  • GitHub Check: integration tests
🔇 Additional comments (30)
internal/infrastructure/db/postgres/migration/20260114000000_add_intent_txid.down.sql (1)

1-2: LGTM!

The down migration correctly reverses the up migration by dropping the index first (with safe IF EXISTS), then removing the column.

internal/core/domain/round_repo.go (1)

27-27: LGTM!

The new GetIntentByTxid method appropriately extends the repository interface to support the new lookup capability. The signature aligns with the domain model where Intent now includes a Txid field.

pkg/errors/errors.go (1)

336-337: LGTM!

The new INTENT_NOT_FOUND error code follows established patterns, uses the appropriate grpccodes.NotFound for the gRPC mapping, and maintains sequential code numbering.

internal/infrastructure/db/postgres/sqlc/query.sql (2)

32-38: LGTM!

The UpsertIntent query correctly includes txid in both the INSERT values and the ON CONFLICT update clause, ensuring the transaction ID is persisted and updated appropriately.


418-420: The lightweight design returning only proof and message is intentional and sufficient.

Verification confirms that GetIntentByTxid is used exclusively by the gRPC handler to return an Intent response containing only these two fields. The other Intent struct fields (Id, Inputs, Receivers) are not needed for this lookup operation. No callers attempt to access these fields from the result, confirming the design is appropriate for its use case.

internal/infrastructure/db/postgres/migration/20260114000000_add_intent_txid.up.sql (1)

1-6: The explicit index on line 6 is redundant since the UNIQUE constraint already creates a unique index in PostgreSQL.

However, the primary concern in the original comment about adding a NOT NULL column without a DEFAULT is addressed by the accompanying application-level migration handler (internal/infrastructure/db/postgres/intent_txid_migration.go), which:

  1. Creates a new table with the txid column
  2. Backfills existing intents by deriving txid values from their proof data
  3. Swaps the old and new tables

This migration strategy safely handles existing data, making the SQL file safe to execute as part of the full migration flow.

internal/infrastructure/db/sqlite/sqlc/query.sql (2)

32-38: LGTM!

The UpsertIntent statement correctly includes txid in both the INSERT and ON CONFLICT clauses, ensuring the transaction ID is persisted and updated on conflict.


420-424: LGTM!

The new SelectIntentByTxid query correctly retrieves the proof and message for a given transaction ID, which aligns with the GetIntent API requirements.

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

60-64: LGTM!

The new GetIntentByTxid method follows the established interface patterns, using the custom errors.Error return type and returning the domain entity.

api-spec/protobuf/ark/v1/service.proto (1)

152-157: LGTM!

The new GetIntent RPC is well-defined with an appropriate HTTP GET mapping. The endpoint aligns with the service's RESTful API patterns.

internal/infrastructure/db/postgres/sqlc/queries/models.go (1)

33-39: Type difference is properly abstracted in the repository layer.

The Txid field is indeed string (non-nullable) in Postgres and sql.NullString (nullable) in SQLite, but this difference is correctly handled. Both GetIntentByTxid implementations expose the same interface (txid string), and the SQLite implementation properly wraps the value in sql.NullString{String: txid, Valid: true} when calling the generated querier. No action needed.

internal/infrastructure/db/sqlite/migration/20260114000000_add_intent_txid.up.sql (1)

1-6: Fix comment typos; SQL migration is incomplete by design due to application-level backfill.

The comment contains typos ("make int unique and and not null" should be "make it unique and not null"), which should be corrected. However, the missing constraints are intentional: this SQL migration file is step one of a two-phase strategy. The application code in BackfillIntentTxid() creates a new intent table with txid TEXT UNIQUE NOT NULL constraints, backfills all existing intents with derived txids from their proofs, and swaps the old and new tables. This pattern avoids SQLite's limitation on adding NOT NULL constraints to existing columns without defaults.

The current SQL migration is correct as-is; only fix the comment typos for clarity.

Likely an incorrect or invalid review comment.

internal/core/domain/intent_test.go (1)

14-14: LGTM!

The proof constant is correctly updated to a valid base64-encoded PSBT string, which is required for the new deriveTxidFromProof functionality that extracts transaction IDs from PSBT proofs during intent creation.

internal/interface/grpc/permissions/permissions.go (1)

143-146: LGTM!

The GetIntent endpoint is correctly added to the whitelist with EntityArk and "read" action, consistent with other read-only Ark service endpoints like GetInfo, GetPendingTx, and EstimateIntentFee.

internal/infrastructure/db/sqlite/sqlc/queries/models.go (1)

31-37: LGTM!

The Txid field addition to the Intent struct is correctly generated by sqlc, using sql.NullString to handle the nullable column appropriately for backward compatibility with existing intents.

internal/core/domain/round_test.go (1)

12-76: LGTM!

The test fixtures are correctly updated with valid base64-encoded PSBT strings for the Proof fields. Using distinct proof strings for each intent (Id "0" and Id "1") ensures proper test coverage for the new deriveTxidFromProof functionality.

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

467-474: LGTM!

The function is a clean utility that correctly parses a PSBT proof and extracts the transaction ID. Error handling appropriately wraps the underlying parse error with context.

internal/infrastructure/db/service_test.go (2)

41-42: LGTM!

The test data uses valid PSBT strings that can be parsed to derive transaction IDs, enabling proper testing of the GetIntentByTxid functionality.


508-521: LGTM!

Good test coverage for the new GetIntentByTxid functionality. The tests properly verify that:

  1. Transaction IDs can be derived from proofs
  2. Intents can be retrieved by their derived txid
  3. The retrieved intent contains the expected Proof and Message values
internal/infrastructure/db/service.go (1)

306-315: LGTM!

The stepwise migration logic for SQLite is well-structured: it checks if the current version is below the intent txid migration threshold, runs migrations up to that point, performs the backfill, and then continues with any remaining migrations. Error handling is comprehensive.

internal/core/domain/intent.go (2)

75-77: LGTM!

The validation for a non-empty Txid is appropriate. Since Txid is derived from the proof, validating it alongside the other core fields ensures data integrity.


133-139: LGTM!

The deriveTxidFromProof function correctly uses psbt.NewFromRawBytes with b64=true for base64-encoded PSBT parsing. Based on learnings, this is the correct usage pattern for btcsuite's psbt package.

internal/interface/grpc/handlers/arkservice.go (1)

593-614: LGTM!

The GetIntent handler is well-implemented:

  • Uses a type switch for extensibility when adding more filter types
  • Returns appropriate InvalidArgument status for unknown filters
  • Correctly maps the domain Intent to the protobuf response
api-spec/openapi/swagger/ark/v1/service.openapi.json (1)

853-870: LGTM!

The GetIntentRequest and GetIntentResponse schemas are properly defined and consistent with the existing schema patterns in the specification.

internal/infrastructure/db/postgres/round_repo.go (1)

169-180: LGTM - Txid derivation from proof during intent upsert.

The logic correctly derives the txid from the intent's proof using application.DeriveTxidFromProof and passes it to the upsert operation. Error handling is appropriate.

internal/infrastructure/db/sqlite/intent_txid_migration.go (1)

75-82: SQL placeholders use ? which is correct for SQLite.

The prepared statement syntax with ? placeholders is appropriate for SQLite. This differs from the PostgreSQL version which should use $1, $2... placeholders.

internal/infrastructure/db/postgres/sqlc/queries/query.sql.go (2)

352-367: LGTM - SQLC-generated query for SelectIntentByTxid.

The generated code correctly implements the query to fetch proof and message by txid. The parameter type string (non-nullable) is consistent with the Postgres model where Txid is a plain string.


1729-1751: LGTM - UpsertIntent updated to include txid.

The generated code correctly adds the Txid field to UpsertIntentParams and passes it to the SQL executor. The SQL includes txid in both INSERT and ON CONFLICT DO UPDATE SET clauses.

internal/infrastructure/db/sqlite/sqlc/queries/query.sql.go (1)

1793-1815: LGTM - UpsertIntent updated for SQLite with txid.

The generated code correctly adds the Txid field. Note that Txid is sql.NullString here (matching SQLite's nullable model), which differs from Postgres's non-nullable string.

internal/infrastructure/db/postgres/intent_txid_migration.go (1)

33-35: Inconsistent context usage.

Same issue as SQLite migration - uses context.Background() instead of the passed ctx.

🐛 Proposed fix
-	if err := backfillIntent(context.Background(), dbh); err != nil {
+	if err := backfillIntent(ctx, dbh); err != nil {
 		return fmt.Errorf("failed to backfill txids: %s", err)
 	}
⛔ Skipped due to learnings
Learnt from: louisinger
Repo: arkade-os/arkd PR: 686
File: internal/core/application/fraud.go:47-61
Timestamp: 2025-08-28T08:21:01.170Z
Learning: In reactToFraud function in internal/core/application/fraud.go, the goroutine that waits for confirmation and schedules checkpoint sweep should use context.Background() instead of the request context, as this is intentional design to decouple the checkpoint sweep scheduling from the request lifetime.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@bitcoin-coder-bob
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 15, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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 `@internal/infrastructure/db/postgres/intent_txid_migration.go`:
- Around line 30-33: The call to backfillIntent uses context.Background() which
discards the passed ctx and any cancellation/deadline; change the call to pass
the existing ctx (i.e., backfillIntent(ctx, dbh)) and ensure the surrounding
function signature provides that ctx into this call so cancellation/timeout is
respected by backfillIntent and any callers.
♻️ Duplicate comments (5)
internal/infrastructure/db/postgres/intent_txid_migration.go (4)

72-135: LGTM! Previous placeholder issue has been addressed.

The function now correctly uses PostgreSQL positional placeholders ($1, $2, $3, $4, $5) and properly handles transaction lifecycle, prepared statements, and row iteration errors.


145-232: LGTM! Previous PRAGMA issues have been addressed.

The function now uses PostgreSQL-compatible ALTER TABLE DROP CONSTRAINT statements instead of SQLite PRAGMA, properly handles dependent views, and validates row counts before the swap.


234-284: LGTM! Previous PRAGMA issues have been addressed.

The function uses PostgreSQL-compatible DDL without PRAGMA statements and correctly uses LEFT JOIN to preserve receiver records.


307-316: LEFT JOIN preserves vtxos with missing intents - verify this is intentional.

The query uses LEFT JOIN intent AS i ON i.id = v.intent_id and selects i.id AS intent_id. This means vtxos whose intent_id references a non-existent intent will have their intent_id set to NULL in the new table. This addresses the data loss concern from previous reviews, but verify this orphan cleanup behavior is desired.

internal/infrastructure/db/sqlite/sqlc/queries/query.sql.go (1)

351-366: Parameter type inconsistency between SQLite and PostgreSQL backends.

The SQLite SelectIntentByTxid accepts sql.NullString while the PostgreSQL version accepts string. This creates an API inconsistency that the repository layer must abstract. Ensure the calling code (e.g., round_repo.go) handles this difference appropriately.

🧹 Nitpick comments (3)
internal/infrastructure/db/service_test.go (1)

508-521: Consider adding a negative test case for non-existent txid.

The test correctly validates successful retrieval for both intents. Consider adding a test case for a non-existent txid to verify proper error handling.

💡 Optional: Add negative test case
// Test non-existent txid
_, err = svc.Rounds().GetIntentByTxid(ctx, "nonexistent_txid_0000000000000000")
require.Error(t, err)
internal/infrastructure/db/badger/ark_repo.go (1)

262-275: Consider using structured logging instead of fmt.Printf.

The error is logged using fmt.Printf, which won't integrate with the application's logging infrastructure. Consider using a logger if available, or returning/accumulating errors for visibility.

♻️ Suggested improvement

If a logger is available in the repository context, use it instead:

-		fmt.Printf("error deriving txid from proof: %s\n", err.Error())
+		// Consider using structured logging or returning errors
+		// log.Warnf("error deriving txid from proof: %v", err)

Alternatively, if silent failure is acceptable during migration, consider at minimum using log.Printf from the standard library for consistent output formatting.

internal/infrastructure/db/postgres/intent_txid_migration.go (1)

349-357: Consider using parameterized query to avoid potential SQL injection.

The function uses fmt.Sprintf to interpolate tableName and columnName directly into the SQL string. While currently called with hardcoded values, this pattern is risky if reused. PostgreSQL's information_schema doesn't support parameterized identifiers directly, but consider adding input validation or documenting this limitation.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ae83eb9 and 0568933.

📒 Files selected for processing (13)
  • internal/core/application/utils.go
  • internal/infrastructure/db/badger/ark_repo.go
  • internal/infrastructure/db/postgres/intent_txid_migration.go
  • internal/infrastructure/db/postgres/migration/20260114000000_add_intent_txid.up.sql
  • internal/infrastructure/db/postgres/sqlc/queries/models.go
  • internal/infrastructure/db/postgres/sqlc/queries/query.sql.go
  • internal/infrastructure/db/postgres/sqlc/query.sql
  • internal/infrastructure/db/service.go
  • internal/infrastructure/db/service_test.go
  • internal/infrastructure/db/sqlite/intent_txid_migration.go
  • internal/infrastructure/db/sqlite/sqlc/queries/models.go
  • internal/infrastructure/db/sqlite/sqlc/queries/query.sql.go
  • internal/infrastructure/db/sqlite/sqlc/query.sql
🚧 Files skipped from review as they are similar to previous changes (5)
  • internal/infrastructure/db/postgres/sqlc/queries/models.go
  • internal/core/application/utils.go
  • internal/infrastructure/db/sqlite/intent_txid_migration.go
  • internal/infrastructure/db/service.go
  • internal/infrastructure/db/postgres/migration/20260114000000_add_intent_txid.up.sql
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-28T08:21:01.170Z
Learnt from: louisinger
Repo: arkade-os/arkd PR: 686
File: internal/core/application/fraud.go:47-61
Timestamp: 2025-08-28T08:21:01.170Z
Learning: In reactToFraud function in internal/core/application/fraud.go, the goroutine that waits for confirmation and schedules checkpoint sweep should use context.Background() instead of the request context, as this is intentional design to decouple the checkpoint sweep scheduling from the request lifetime.

Applied to files:

  • internal/infrastructure/db/postgres/intent_txid_migration.go
🧬 Code graph analysis (4)
internal/infrastructure/db/sqlite/sqlc/queries/query.sql.go (3)
pkg/ark-lib/intent/proof.go (1)
  • Proof (39-41)
internal/infrastructure/db/sqlite/sqlc/queries/db.go (1)
  • Queries (23-25)
internal/infrastructure/db/postgres/sqlc/queries/query.sql.go (1)
  • SelectIntentByTxidRow (358-361)
internal/infrastructure/db/postgres/intent_txid_migration.go (1)
internal/infrastructure/db/sqlite/intent_txid_migration.go (1)
  • BackfillIntentTxid (13-50)
internal/infrastructure/db/service_test.go (2)
pkg/ark-lib/intent/proof.go (1)
  • Proof (39-41)
internal/core/application/utils.go (1)
  • DeriveTxidFromProof (468-474)
internal/infrastructure/db/badger/ark_repo.go (5)
internal/core/domain/intent.go (2)
  • Intents (106-106)
  • Intent (11-18)
internal/core/application/utils.go (1)
  • DeriveTxidFromProof (468-474)
pkg/ark-lib/intent/proof.go (1)
  • Proof (39-41)
internal/infrastructure/db/postgres/sqlc/queries/models.go (1)
  • Intent (33-39)
internal/infrastructure/db/sqlite/sqlc/queries/models.go (1)
  • Intent (31-37)
⏰ 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). (2)
  • GitHub Check: integration tests
  • GitHub Check: unit tests
🔇 Additional comments (16)
internal/infrastructure/db/service_test.go (4)

15-15: LGTM!

The import of the application package is necessary to access DeriveTxidFromProof for deriving transaction IDs from proofs in the test.


41-42: LGTM!

The proof constants are valid base64-encoded PSBTs that will produce distinct transaction IDs when decoded, enabling proper testing of the GetIntentByTxid functionality.


436-437: LGTM!

Using the defined proof1 constant ensures the intent has a valid PSBT proof from which a txid can be derived for the subsequent GetIntentByTxid test.


456-457: LGTM!

Using proof2 for the second intent ensures distinct txids for each intent, enabling comprehensive testing of the lookup functionality.

internal/infrastructure/db/sqlite/sqlc/query.sql (2)

33-38: LGTM!

The UpsertIntent statement correctly includes txid in both the INSERT and the ON CONFLICT UPDATE clauses, ensuring the transaction ID is properly persisted and updated.


427-429: Index on intent.txid already exists. The index idx_intent_txid was created in migration 20260114000000_add_intent_txid.up.sql to optimize lookups on this column. No action needed.

Likely an incorrect or invalid review comment.

internal/infrastructure/db/badger/ark_repo.go (3)

28-32: LGTM!

The IntentIndex struct provides an efficient secondary index mapping txid to round and intent IDs, enabling O(1) lookups instead of scanning all rounds.


561-580: LGTM - Addresses the performance concern from previous review.

This implementation uses the IntentIndex for O(1) lookup by txid, then fetches the round and iterates only through its intents. This is much more efficient than the previous approach of scanning all rounds.


582-613: No collision risk with BadgerHold's type-aware storage.

BadgerHold (timshannon/badgerhold) stores type information alongside values, allowing the same key to safely store different types. Since Tx and IntentIndex are distinct types, they can coexist with the same txid key without collision. Retrieval operations are type-specific (e.g., r.store.Get(txid, &idx) specifies the target type), ensuring correct type handling.

Likely an incorrect or invalid review comment.

internal/infrastructure/db/postgres/sqlc/queries/query.sql.go (2)

353-368: LGTM - Generated code correctly implements SelectIntentByTxid.

The query and function are correctly generated to fetch intent proof and message by txid. The return type uses sql.NullString appropriately for nullable columns.


1756-1782: No changes needed — txid handling is consistent across backends.

The typing difference (string vs sql.NullString) is intentional and correct. SQLite's generated code wraps all parameters in sql.NullString per its driver requirements, while PostgreSQL uses appropriate Go types. Both implementations handle txid derivation identically: they call DeriveTxidFromProof(), immediately check for errors, and only pass a non-empty txid to UpsertIntent by returning early on failure. Empty txid values are not a concern here.

internal/infrastructure/db/postgres/sqlc/query.sql (2)

33-38: LGTM!

The UpsertIntent statement correctly includes txid in both INSERT and ON CONFLICT UPDATE clauses, consistent with the SQLite implementation.


424-426: An index idx_intent_txid already exists on the intent.txid column, created specifically for this purpose in migration 20260114000000_add_intent_txid.up.sql. The query already benefits from efficient lookups.

Likely an incorrect or invalid review comment.

internal/infrastructure/db/postgres/intent_txid_migration.go (1)

53-70: LGTM!

The ensureIntentNew function correctly creates the new table with PostgreSQL-compatible syntax and appropriate column definitions.

internal/infrastructure/db/sqlite/sqlc/queries/models.go (1)

31-37: LGTM!

The Txid field addition to the Intent struct is consistent with the schema migration adding the txid column. This is a generated file matching the updated schema.

internal/infrastructure/db/sqlite/sqlc/queries/query.sql.go (1)

1823-1849: LGTM!

The UpsertIntent query and params struct correctly include the new txid field, with proper INSERT and ON CONFLICT UPDATE handling.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@bitcoin-coder-bob
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 15, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

🤖 Fix all issues with AI agents
In `@internal/infrastructure/db/badger/ark_repo.go`:
- Around line 316-323: The call to r.upsertIntentIndex(ctx, it.Txid, rnd.Id,
it.Id) in the loop over rnd.Intents returns an error that is currently ignored;
update the loop to explicitly handle the returned error by capturing it and
either discarding with _ = r.upsertIntentIndex(...) or, preferably, logging the
error for observability (e.g., err := r.upsertIntentIndex(...); if err != nil {
r.logger.Warnf("upsertIntentIndex failed txid=%s rnd=%s intent=%s: %v", it.Txid,
rnd.Id, it.Id, err) } ), keeping the existing behavior of not failing the whole
round.

In `@internal/infrastructure/db/postgres/intent_txid_migration.go`:
- Around line 53-70: The create statement in ensureIntentNew currently defines
txid as "txid TEXT" but must match SQLite by being "txid TEXT UNIQUE NOT NULL";
update the SQL in ensureIntentNew (the createIntentNew string) to declare txid
with UNIQUE NOT NULL, and if necessary adjust any upstream inserts or migration
logic to handle existing NULL/duplicate txid rows before applying the constraint
to avoid migration failures.
- Around line 234-252: fixReceiverTableFK defines receiver_new with intent_id
TEXT NOT NULL but copyData uses a LEFT JOIN that can bring orphan receiver rows
with NULL intent_id, causing the INSERT to fail; either make intent_id nullable
in the receiver_new CREATE statement to match the SQLite behavior (remove NOT
NULL on intent_id in the createNew constant) or change the copyData SELECT to
only include matched intents (use an INNER JOIN or add WHERE i.id IS NOT NULL)
so no NULL intent_id rows are inserted; update the constants createNew and/or
copyData in fixReceiverTableFK accordingly.

In `@internal/infrastructure/db/sqlite/intent_txid_migration.go`:
- Around line 171-185: The ALTER TABLE ... DROP CONSTRAINT statements using
tx.ExecContext are PostgreSQL-specific and not supported by SQLite; remove the
four EXECUTE calls that run `ALTER TABLE ... DROP CONSTRAINT IF EXISTS ...` (the
ones affecting receiver and vtxo) because you already disable foreign keys with
`PRAGMA foreign_keys = OFF` and the migration uses the table-recreation helpers;
if you need to remove or reapply constraints, rely on the existing
fixReceiverTableFK and fixVtxoTableFK functions (or the recreate-table pattern
they implement) instead of these unsupported DROP CONSTRAINT statements.
🧹 Nitpick comments (4)
internal/infrastructure/db/badger/ark_repo.go (2)

268-270: Consider using a proper logger instead of fmt.Printf.

Using fmt.Printf for error logging writes to stdout and may interfere with structured logging. Consider using the logger pattern consistent with the rest of the codebase, or at minimum log.Printf which writes to stderr.

♻️ Suggested improvement
-		fmt.Printf("error deriving txid from proof: %s\n", err.Error())
+		// If a logger is available in the repository, use it:
+		// r.logger.Warnf("error deriving txid from proof: %s", err.Error())
+		// Otherwise, at minimum use log package:
+		log.Printf("error deriving txid from proof: %s", err.Error())

560-579: Consider using pointer receiver for consistency.

The method uses a value receiver (r arkRepository) while other methods in this file use pointer receivers (r *arkRepository). For consistency and to avoid unnecessary copying, consider using a pointer receiver.

♻️ Suggested change
-func (r arkRepository) GetIntentByTxid(ctx context.Context, txid string) (domain.Intent, error) {
+func (r *arkRepository) GetIntentByTxid(ctx context.Context, txid string) (domain.Intent, error) {
internal/infrastructure/db/postgres/intent_txid_migration.go (2)

137-143: Consider extracting shared deriveTxidFromProof to a common utility.

This function is duplicated identically in both SQLite and PostgreSQL migrations. The AI summary mentions DeriveTxidFromProof exists in internal/core/application/utils.go. Consider reusing that implementation to avoid duplication.


198-225: View column order differs between PostgreSQL and SQLite migrations.

The intent_with_receivers_vw view has different column ordering:

  • PostgreSQL (line 210): SELECT receiver.*, intent.*
  • SQLite (line 216): SELECT intent.*, receiver.*

While this may not cause functional issues if columns are accessed by name, it could cause problems if code relies on positional column access. Consider aligning the column order for consistency.

♻️ Proposed fix: Align column order with SQLite
 	if _, err = tx.ExecContext(ctx, `
         CREATE VIEW intent_with_receivers_vw AS
-        SELECT receiver.*, intent.*
+        SELECT intent.*, receiver.*
         FROM intent
         LEFT OUTER JOIN receiver
         ON intent.id = receiver.intent_id;
     `); err != nil {
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0568933 and 8c5e844.

📒 Files selected for processing (3)
  • internal/infrastructure/db/badger/ark_repo.go
  • internal/infrastructure/db/postgres/intent_txid_migration.go
  • internal/infrastructure/db/sqlite/intent_txid_migration.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-28T08:21:01.170Z
Learnt from: louisinger
Repo: arkade-os/arkd PR: 686
File: internal/core/application/fraud.go:47-61
Timestamp: 2025-08-28T08:21:01.170Z
Learning: In reactToFraud function in internal/core/application/fraud.go, the goroutine that waits for confirmation and schedules checkpoint sweep should use context.Background() instead of the request context, as this is intentional design to decouple the checkpoint sweep scheduling from the request lifetime.

Applied to files:

  • internal/infrastructure/db/postgres/intent_txid_migration.go
  • internal/infrastructure/db/sqlite/intent_txid_migration.go
🧬 Code graph analysis (3)
internal/infrastructure/db/badger/ark_repo.go (4)
internal/core/domain/intent.go (2)
  • Intents (106-106)
  • Intent (11-18)
pkg/ark-lib/intent/proof.go (1)
  • Proof (39-41)
internal/infrastructure/db/postgres/sqlc/queries/models.go (1)
  • Intent (33-39)
internal/infrastructure/db/sqlite/sqlc/queries/models.go (1)
  • Intent (31-37)
internal/infrastructure/db/postgres/intent_txid_migration.go (1)
internal/infrastructure/db/sqlite/intent_txid_migration.go (1)
  • BackfillIntentTxid (13-50)
internal/infrastructure/db/sqlite/intent_txid_migration.go (1)
internal/infrastructure/db/postgres/intent_txid_migration.go (1)
  • BackfillIntentTxid (13-51)
🪛 GitHub Actions: ci_unit
internal/infrastructure/db/badger/ark_repo.go

[error] 322-322: Error return value of r.upsertIntentIndex is not checked (errcheck).

🪛 GitHub Check: unit tests
internal/infrastructure/db/badger/ark_repo.go

[failure] 322-322:
Error return value of r.upsertIntentIndex is not checked (errcheck)

⏰ 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). (2)
  • GitHub Check: Build and Scan
  • GitHub Check: integration tests
🔇 Additional comments (17)
internal/infrastructure/db/badger/ark_repo.go (4)

10-10: LGTM!

Import correctly added to support the DeriveTxidFromProof function usage.


28-32: LGTM!

The IntentIndex struct provides a clean secondary index structure to enable efficient intent lookups by txid, addressing the previous O(n) full table scan concern.


581-591: LGTM!

The helper follows the established pattern for transactional/non-transactional upserts in this repository.


593-612: LGTM!

Correct implementation with proper error handling for not-found cases.

internal/infrastructure/db/sqlite/intent_txid_migration.go (8)

1-11: LGTM!

Package declaration and imports are appropriate for the SQLite migration functionality.


13-50: LGTM!

The orchestration function correctly checks for existing migration, creates the new table, backfills data, swaps tables, and fixes foreign keys. Context is properly propagated throughout.


52-69: LGTM!

The intent_new table schema is well-defined with appropriate constraints including the UNIQUE NOT NULL constraint on txid.


71-134: LGTM!

The backfill logic correctly iterates through existing intents, derives txids from proofs, and inserts into the new table within a transaction. Good use of prepared statements and proper error handling with rows.Err() check.


136-142: LGTM!

Clean implementation of PSBT parsing to derive the transaction ID.


244-301: LGTM!

The receiver table FK fix correctly uses SQLite's table recreation pattern with PRAGMA foreign_keys and a LEFT JOIN to preserve all receiver rows.


303-372: LGTM!

The vtxo table FK fix correctly implements the table recreation pattern with LEFT JOIN to preserve all vtxo rows, including those with NULL intent_id.


374-380: LGTM!

The existsQuery function correctly uses SQLite's pragma_table_info to check for column existence.

internal/infrastructure/db/postgres/intent_txid_migration.go (5)

1-11: LGTM!

Package declaration and imports are appropriate for the PostgreSQL migration functionality.


13-51: LGTM!

The orchestration function correctly propagates context throughout all migration steps.


72-135: LGTM!

The backfill logic correctly uses PostgreSQL positional placeholders ($1, $2, $3, $4, $5) and properly handles transactions with error checking.


286-347: LGTM!

The vtxo table FK fix correctly uses PostgreSQL DDL without PRAGMA statements and preserves all vtxo rows using LEFT JOIN.


349-357: LGTM!

The existsQuery function correctly uses PostgreSQL's information_schema.columns to check for column existence.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@bitcoin-coder-bob
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 15, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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 `@internal/infrastructure/db/badger/ark_repo.go`:
- Line 561: GetIntentByTxid currently uses a value receiver (`r arkRepository`)
while other methods use pointer receivers; change its receiver to a pointer (`r
*arkRepository`) so the method signature matches the rest of the type, update
any internal references if necessary, and run tests/compile to ensure no
interface or call-site mismatches arise.

In `@internal/infrastructure/db/postgres/intent_txid_migration.go`:
- Around line 307-316: The INSERT...SELECT in the copyData SQL uses "i.id AS
intent_id" which will null-out orphaned vtxo.intent_id values; update the SQL in
the copyData constant so the selected intent_id comes from the original row
("v.intent_id") instead of the joined "i.id" (keep the LEFT JOIN to preserve
intent metadata if needed but ensure the SELECT uses v.intent_id for the
intent_id column when inserting into vtxo_new).
♻️ Duplicate comments (3)
internal/infrastructure/db/badger/ark_repo.go (1)

316-324: Acknowledged: nolint:errcheck addresses CI, but consider logging.

The nolint:errcheck directive resolves the pipeline failure. For better observability, consider logging the error (as suggested in the previous review) rather than silently discarding it.

internal/infrastructure/db/sqlite/intent_txid_migration.go (2)

171-177: SQLite does not support ALTER TABLE ... DROP CONSTRAINT syntax.

These statements use PostgreSQL-specific DDL that will fail on SQLite. SQLite's ALTER TABLE only supports RENAME TABLE, RENAME COLUMN, ADD COLUMN, and DROP COLUMN. Since foreign keys are already disabled via PRAGMA foreign_keys = OFF (line 156), these constraint-drop operations are redundant and should be removed.

🐛 Proposed fix: Remove unsupported statements
 	if oldCT != newCT {
 		return fmt.Errorf("backfill mismatch: intent=%d intent_new=%d", oldCT, newCT)
 	}
 
-	// drop dependent foreign keys
-	if _, err = tx.ExecContext(ctx, `ALTER TABLE IF EXISTS receiver DROP CONSTRAINT IF EXISTS receiver_intent_id_fkey;`); err != nil {
-		return fmt.Errorf("drop receiver FK: %w", err)
-	}
-	if _, err = tx.ExecContext(ctx, `ALTER TABLE IF EXISTS vtxo DROP CONSTRAINT IF EXISTS vtxo_intent_id_fkey;`); err != nil {
-		return fmt.Errorf("drop vtxo FK: %w", err)
-	}
-
 	// drop views that depend on intent before dropping the intent table

316-325: Data corruption risk: i.id AS intent_id may silently null-out orphaned vtxo references.

The copyData query uses i.id AS intent_id from the LEFT JOIN, which will be NULL if no matching intent exists. This differs from fixReceiverTableFK which correctly uses r.intent_id to preserve original values. If any vtxo has an intent_id that doesn't exist in the intent table, it will be silently changed to NULL.

🐛 Proposed fix: Use v.intent_id to preserve original values
 	const copyData = `
     INSERT INTO vtxo_new (
       txid, vout, pubkey, amount, expires_at, created_at, commitment_txid, spent_by, spent, unrolled, swept, preconfirmed, settled_by, ark_txid, intent_id
     )
     SELECT
       v.txid, v.vout, v.pubkey, v.amount, v.expires_at, v.created_at, v.commitment_txid, v.spent_by, v.spent, v.unrolled, v.swept, v.preconfirmed, v.settled_by, v.ark_txid,
-      i.id AS intent_id
-    FROM vtxo AS v
-		 LEFT JOIN intent AS i
-      ON i.id = v.intent_id;`
+      v.intent_id
+    FROM vtxo AS v;`
🧹 Nitpick comments (5)
internal/infrastructure/db/badger/ark_repo.go (2)

267-270: Replace fmt.Printf with structured logging.

Using fmt.Printf for error logging bypasses the application's logging infrastructure, making it harder to correlate errors in production. Consider using a structured logger consistent with the rest of the codebase.

♻️ Suggested approach

If a logger is available on the repository (or can be injected), replace:

-		fmt.Printf("error deriving txid from proof: %s\n", err.Error())
+		// Use structured logger, e.g.:
+		// log.Warnf("failed to derive txid from proof for intent %s: %v", v.Id, err)

Alternatively, consider returning a multi-error or logging via a passed-in logger interface.


10-10: This dependency pattern is consistent across the codebase.

Multiple infrastructure packages (postgres, sqlite, badger, and tests) already import application.DeriveTxidFromProof from internal/core/application/utils.go. This appears to be an intentional architectural pattern rather than an isolated violation.

If cleaner architectural separation is desired, consider moving DeriveTxidFromProof to the domain package as a utility function; however, the current design is established and consistent across all DB implementations.

internal/infrastructure/db/sqlite/intent_txid_migration.go (2)

13-50: Orchestration logic is sound, but clean up the TODO-style comments.

The function correctly checks for idempotency, propagates context, and sequences the migration steps properly. However, lines 40 and 44 contain uncertainty comments (// do we even need since we keep the intent id unchanged?). If the FK fixes are needed, remove the comments; if they're not needed, remove the function calls.


52-69: Consider adding a UNIQUE constraint on txid column.

If txid will be used for lookups (as suggested by the PR objective "get intent by txid"), adding a UNIQUE constraint would improve query performance and prevent duplicate entries.

♻️ Proposed schema change
 	createIntentNew := `
 		CREATE TABLE IF NOT EXISTS intent_new (
     id TEXT PRIMARY KEY,
     round_id TEXT NOT NULL,
     proof TEXT NOT NULL,
     message TEXT NOT NULL,
-		txid TEXT,
+		txid TEXT UNIQUE,
     FOREIGN KEY (round_id) REFERENCES round(id)
 );
 	`
internal/infrastructure/db/postgres/intent_txid_migration.go (1)

53-70: Schema is consistent with SQLite version.

Same suggestion applies: consider adding UNIQUE constraint on txid for query performance.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c5e844 and fa2121e.

📒 Files selected for processing (3)
  • internal/infrastructure/db/badger/ark_repo.go
  • internal/infrastructure/db/postgres/intent_txid_migration.go
  • internal/infrastructure/db/sqlite/intent_txid_migration.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-28T08:21:01.170Z
Learnt from: louisinger
Repo: arkade-os/arkd PR: 686
File: internal/core/application/fraud.go:47-61
Timestamp: 2025-08-28T08:21:01.170Z
Learning: In reactToFraud function in internal/core/application/fraud.go, the goroutine that waits for confirmation and schedules checkpoint sweep should use context.Background() instead of the request context, as this is intentional design to decouple the checkpoint sweep scheduling from the request lifetime.

Applied to files:

  • internal/infrastructure/db/badger/ark_repo.go
  • internal/infrastructure/db/sqlite/intent_txid_migration.go
  • internal/infrastructure/db/postgres/intent_txid_migration.go
🧬 Code graph analysis (2)
internal/infrastructure/db/sqlite/intent_txid_migration.go (1)
internal/infrastructure/db/postgres/intent_txid_migration.go (1)
  • BackfillIntentTxid (13-51)
internal/infrastructure/db/postgres/intent_txid_migration.go (1)
internal/infrastructure/db/sqlite/intent_txid_migration.go (1)
  • BackfillIntentTxid (13-50)
⏰ 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). (2)
  • GitHub Check: unit tests
  • GitHub Check: integration tests
🔇 Additional comments (15)
internal/infrastructure/db/badger/ark_repo.go (2)

28-32: LGTM!

The IntentIndex struct is well-designed for the secondary index pattern, enabling efficient lookup by txid without full table scans.


582-613: LGTM!

The upsertIntentIndex and getIntentIndexByTxid helper methods follow the established patterns in this file for transaction-aware persistence operations. Error handling for ErrNotFound is appropriate.

internal/infrastructure/db/sqlite/intent_txid_migration.go (6)

1-11: Imports and package setup look correct.

The imports appropriately include github.com/btcsuite/btcd/btcutil/psbt for PSBT parsing and logrus for debug logging. Using strings.NewReader for PSBT parsing is appropriate.


71-134: Well-structured backfill with proper transaction handling.

Good use of prepared statements for batch inserts, proper transaction rollback on error, and iteration error checking with rows.Err().


136-142: LGTM!

Clean implementation for deriving txid from PSBT proof.


179-234: View handling and table swap logic is correct.

The view drop/recreate pattern, row count validation, and FK pragma handling are appropriate for SQLite migrations.


236-293: Receiver table migration correctly preserves original intent_id values.

Using r.intent_id in the SELECT (rather than i.id) correctly preserves the original foreign key values during migration.


366-372: LGTM!

Correctly uses SQLite's pragma_table_info to check for column existence.

internal/infrastructure/db/postgres/intent_txid_migration.go (7)

1-11: Imports are appropriate for PostgreSQL migration.


13-51: Orchestration is correct; same cleanup suggestion as SQLite version.

Context is properly propagated. Consider removing the uncertainty comments on lines 40 and 44.


72-135: PostgreSQL placeholder syntax is correct; backfill logic is sound.

The $1, $2... positional placeholders are appropriate for PostgreSQL.


137-143: LGTM!


145-232: PostgreSQL-appropriate table swap with proper constraint and view handling.

The ALTER TABLE ... DROP CONSTRAINT IF EXISTS syntax is valid PostgreSQL DDL, and the view handling with CASCADE is appropriate.


234-284: Receiver table recreation is PostgreSQL-appropriate.

The use of BIGINT for amount (vs SQLite's INTEGER) and removal of PRAGMA statements are correct for PostgreSQL. The r.intent_id selection preserves original values.


349-357: LGTM!

Correctly uses PostgreSQL's information_schema.columns to check for column existence.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@bitcoin-coder-bob
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 15, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@bitcoin-coder-bob
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 15, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Comment on lines 602 to 604
if err != nil {
return nil, err
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

can be moved outside the switch

GetIntentByTxid(
ctx context.Context,
txid string,
) (domain.Intent, errors.Error)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
) (domain.Intent, errors.Error)
) (*domain.Intent, errors.Error)

@bitcoin-coder-bob bitcoin-coder-bob marked this pull request as ready for review January 19, 2026 16:43
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: 6

🤖 Fix all issues with AI agents
In `@internal/core/domain/intent_test.go`:
- Line 31: The test is missing a declaration for txid used when calling
domain.NewIntent and elsewhere; add txid to the top var block in the test (same
block that contains other test fixtures) as a suitable test value (e.g., var
txid = "test-txid" or similar) so NewIntent(txid, proof, message, inputs) and
other uses compile; ensure the symbol name is exactly txid to match existing
references in the file.

In `@internal/infrastructure/db/postgres/intent_txid_migration_test.go`:
- Around line 3-45: Gate the TestIntentTxidMigration so it only runs when a
Postgres DSN is provided via an env var (e.g. POSTGRES_TEST_DSN) and skip with
t.Skip if unset; read the DSN from that env var instead of hardcoding it. In
TestIntentTxidMigration ensure deterministic cleanup by either creating table
names with a unique suffix (or using a dedicated temporary schema) and adding
defer cleanup that drops those tables (or the schema) and closes the DB, or run
the test work inside a transaction and roll it back at the end; update calls to
setupRoundTable, setupOldIntentTable, insertTestRoundRows, insertTestIntentRows,
modifyIntentTable and BackfillIntentTxid to accept the dynamic table/schema
names or transaction handle so cleanup works reliably. Ensure defer db.Close()
remains and that any created tables are dropped in a deferred cleanup to make
the test safe for local `go test ./...`.

In `@internal/infrastructure/db/postgres/intent_txid_migration.go`:
- Around line 103-118: The doc comment for function columnExists is inaccurate
(mentions "PRAGMA table_info" / SQLite) while the implementation queries
PostgreSQL's information_schema.columns; update the comment to describe that
columnExists checks for a column using information_schema.columns (PostgreSQL)
and mention the parameters/context (ctx, db, tableName, columnName) to match the
implementation in columnExists.
- Around line 31-83: The backfillIntent function must use a named error return
(err error) and avoid shadowing so the deferred rollback can see failures:
change the signature to func backfillIntent(ctx context.Context, db *sql.DB)
(err error), replace any short-declared errors (e.g., the rows.Scan, rows.Err,
rows.Close, tx.QueryContext and tx.ExecContext checks) so they assign to the
outer err (use = not :=), and when DeriveTxidFromProof returns derr assign it to
err before returning; additionally track commit success (e.g., set committed :=
false, set committed = true after tx.Commit() succeeds) or simply defer a
rollback that runs unless the function returns nil by setting defer func(){ if
err != nil { _ = tx.Rollback() } }() so all error paths (including tx.Commit
failure) trigger rollback correctly.

In `@internal/infrastructure/db/service.go`:
- Around line 234-256: The migration currently advances to
intentTxidMigrationBegin before running BackfillIntentTxid so a failing backfill
leaves the DB version at the threshold and prevents retries; modify the flow in
the init sequence (the block using m.Version(), intentTxidMigrationBegin,
m.Migrate, and BackfillIntentTxid) to ensure backfill is retried: either run
BackfillIntentTxid before committing the version bump or, if you must call
m.Migrate first, add logic to detect version == intentTxidMigrationBegin and
re-run BackfillIntentTxid when NULL/empty intent txid rows remain (i.e., query
for missing txids) and only consider the migration fully complete when
BackfillIntentTxid succeeds; ensure errors from BackfillIntentTxid propagate so
startup can retry and do not leave the schema version marked as completed if
backfill failed.

In `@internal/infrastructure/db/sqlite/round_repo.go`:
- Around line 457-472: GetIntentByTxid currently constructs domain.Intent with
only Proof and Message, leaving Id and Txid empty; update the return to populate
domain.Intent.Id and domain.Intent.Txid from the DB row (the local variable
intent returned by r.querier.SelectIntentByTxid) and handle the SQL null
wrappers (e.g., sql.NullString/NullInt64) appropriately so Id and Txid are set
when present; change the construction inside GetIntentByTxid to map intent.Id
and intent.Txid into the domain.Intent fields alongside Proof and Message.
♻️ Duplicate comments (3)
internal/infrastructure/db/badger/ark_repo.go (2)

542-542: Use pointer receiver for consistency.

GetIntentByTxid uses a value receiver while all other methods in this file use pointer receivers. This inconsistency should be fixed.

♻️ Proposed fix
-func (r arkRepository) GetIntentByTxid(ctx context.Context, txid string) (*domain.Intent, error) {
+func (r *arkRepository) GetIntentByTxid(ctx context.Context, txid string) (*domain.Intent, error) {

543-546: Verify not-found error handling matches interface contract.

A past review comment requested returning (nil, nil) when the intent is not found, allowing the interface layer to map this to INTENT_NOT_FOUND. Currently, getIntentIndexByTxid returns an error on not-found (line 588), which propagates here. Confirm this aligns with the final agreed-upon contract.

#!/bin/bash
# Check how other GetIntentByTxid implementations handle not-found
rg -nA 10 "func.*GetIntentByTxid" --type=go internal/infrastructure/db/
internal/infrastructure/db/postgres/round_repo.go (1)

426-442: Populate Id/Txid in GetIntentByTxid result.

Same issue as SQLite: the returned intent lacks Id/Txid, which can lead to incomplete API responses.

🛠️ Suggested fix
-	return &domain.Intent{
-		Proof:   intent.Proof.String,
-		Message: intent.Message.String,
-	}, nil
+	return &domain.Intent{
+		Id:      intent.ID.String,
+		Txid:    intent.Txid.String,
+		Proof:   intent.Proof.String,
+		Message: intent.Message.String,
+	}, nil
🧹 Nitpick comments (2)
internal/infrastructure/db/badger/ark_repo.go (1)

300-305: Consider logging the suppressed error for observability.

While nolint:errcheck silences the linter, failures in intent index upserts will be silently dropped. Logging would aid debugging without failing the round upsert.

♻️ Suggested improvement
 	// upsert intent index for each intent with a txid
 	for _, it := range rnd.Intents {
 		// do not fail the whole round upsert if intent index upsert fails
-		// nolint:errcheck
-		r.upsertIntentIndex(ctx, it.Txid, rnd.Id, it.Id)
+		if err := r.upsertIntentIndex(ctx, it.Txid, rnd.Id, it.Id); err != nil {
+			// Log but don't fail the round upsert
+			fmt.Printf("failed to upsert intent index for txid %s: %v\n", it.Txid, err)
+		}
 	}
internal/infrastructure/db/postgres/intent_txid_migration.go (1)

86-92: Avoid duplicating txid derivation helpers across layers.
A similar DeriveTxidFromProof exists in internal/core/application/utils.go and SQLite migration; consider consolidating into a shared helper to prevent drift in error semantics.

return nil
}

func DeriveTxidFromProof(proof string) (string, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

no need to be exported

Suggested change
func DeriveTxidFromProof(proof string) (string, error) {
func deriveTxidFromProof(proof string) (string, error) {

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

handled in: 27c13e0

@@ -0,0 +1,141 @@
package pgdb
Copy link
Collaborator

Choose a reason for hiding this comment

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

The convention is to declare this a test package (and this is the only reason why the backfill function is exported)

Suggested change
package pgdb
package pgdb_test

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

handled in: 27c13e0

Comment on lines 438 to 441
return &domain.Intent{
Proof: intent.Proof.String,
Message: intent.Message.String,
}, nil
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
return &domain.Intent{
Proof: intent.Proof.String,
Message: intent.Message.String,
}, nil
return &domain.Intent{
Txid: intent.Txid.String,
Proof: intent.Proof.String,
Message: intent.Message.String,
}, nil

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

coderabbitai suggested this as well: #884 (comment)

I will modify the query so we can populate txid here

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

handled in: 27c13e0

Comment on lines 235 to 256
intentTxidMigrationBegin := uint(20260114000000)
version, dirty, verr := m.Version()
if verr != nil && !errors.Is(verr, migrate.ErrNilVersion) {
return nil, fmt.Errorf("failed to read migration version: %w", verr)
}
if dirty {
return nil, fmt.Errorf(
"database is in a dirty migration state; manual intervention required",
)
}

if version < intentTxidMigrationBegin {
if err := m.Migrate(intentTxidMigrationBegin); err != nil &&
!errors.Is(err, migrate.ErrNoChange) {
return nil, fmt.Errorf("failed to run migrations: %s", err)
}

err = pgdb.BackfillIntentTxid(context.Background(), db)
if err != nil {
return nil, fmt.Errorf("failed to backfill intent txid field: %w", err)
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

seems like this code is duplicated and can be moved to a util function handleIntentIdMigration (or smtn lke that)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

handled in: 27c13e0

@@ -0,0 +1,135 @@
package sqlitedb
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
package sqlitedb
package sqlitedb_test

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

handled in: 27c13e0

Comment on lines 469 to 472
return &domain.Intent{
Proof: intent.Proof.String,
Message: intent.Message.String,
}, nil
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
return &domain.Intent{
Proof: intent.Proof.String,
Message: intent.Message.String,
}, nil
return &domain.Intent{
Txid: intent.Txid.String,
Proof: intent.Proof.String,
Message: intent.Message.String,
}, nil

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

also added in the id field: 27c13e0

…ndling, package naming, returned intents to include id and txid
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: 1

🤖 Fix all issues with AI agents
In `@internal/infrastructure/db/postgres/intent_txid_migration_test.go`:
- Around line 83-86: Move the error check for psbt.NewFromRawBytes before
dereferencing the returned packet: call psbt.NewFromRawBytes(r.Proof...),
immediately assert require.NoError(t, err), and only then access
pkt.UnsignedTx.TxID() to compute txidFromProof and compare with require.Equal;
this fixes the potential panic when NewFromRawBytes returns an error (symbols:
psbt.NewFromRawBytes, pkt, pkt.UnsignedTx.TxID, require.NoError, require.Equal).
🧹 Nitpick comments (4)
internal/infrastructure/db/badger/ark_repo.go (2)

542-570: Fix receiver inconsistency: use pointer receiver.

GetIntentByTxid uses a value receiver (r arkRepository) while all other methods in this file use pointer receivers (r *arkRepository). This inconsistency should be fixed for consistency and to avoid potential issues if the method needs to access mutable state in the future.

♻️ Proposed fix
-func (r arkRepository) GetIntentByTxid(ctx context.Context, txid string) (*domain.Intent, error) {
+func (r *arkRepository) GetIntentByTxid(ctx context.Context, txid string) (*domain.Intent, error) {

27-31: Consider unexporting IntentIndex.

IntentIndex is only used internally within this package for badgerhold storage. Unless it needs to be accessed externally, it could be unexported to intentIndex to reduce the public API surface.

internal/infrastructure/db/postgres/intent_txid_migration.go (1)

90-96: Note: deriveTxidFromProof is duplicated in SQLite migration.

The deriveTxidFromProof function exists in both Postgres and SQLite migration files with identical implementation. This duplication is acceptable for migration code isolation, but if the logic needs to change, remember to update both locations.

internal/infrastructure/db/service.go (1)

623-658: LGTM! Consolidated migration handler addresses duplication concern.

The HandleIntentTxidMigration function properly:

  • Consolidates the stepwise migration logic (addressing the past review comment about duplication)
  • Handles dirty migration state detection
  • Calls the appropriate backfill function based on db type

One minor improvement: consider accepting a context.Context parameter instead of using context.Background() on lines 645 and 649, to allow callers to pass cancellation context during shutdown.

♻️ Optional: Accept context parameter
-func HandleIntentTxidMigration(m *migrate.Migrate, db *sql.DB, dbType string) error {
+func HandleIntentTxidMigration(ctx context.Context, m *migrate.Migrate, db *sql.DB, dbType string) error {
     intentTxidMigrationBegin := uint(20260114000000)
     // ...
     
         switch dbType {
         case "postgres":
-            if err := pgdb.BackfillIntentTxid(context.Background(), db); err != nil {
+            if err := pgdb.BackfillIntentTxid(ctx, db); err != nil {
                 return fmt.Errorf("failed to backfill intent txid field: %w", err)
             }
         case "sqlite":
-            if err := sqlitedb.BackfillIntentTxid(context.Background(), db); err != nil {
+            if err := sqlitedb.BackfillIntentTxid(ctx, db); err != nil {
                 return fmt.Errorf("failed to backfill intent txid field: %w", err)
             }

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

🤖 Fix all issues with AI agents
In `@internal/infrastructure/db/sqlite/intent_txid_migration.go`:
- Around line 40-89: backfillIntent's deferred rollback relies on an outer err
but multiple returns create shadowed/unnamed errors so the transaction won't
rollback; change backfillIntent to use a named return error (e.g., func
backfillIntent(ctx context.Context, db *sql.DB) (err error)) and remove any
shadowed short‑declares so all failures assign to that named err: capture errors
from deriveTxidFromProof (replace derr with assignment to err), check rows.Err()
by assigning to err, and assign tx.Commit() to err before returning; keep the
defer that rolls back when err != nil and ensure stmt.Close/rows.Close are
deferred without shadowing.
🧹 Nitpick comments (1)
internal/infrastructure/db/sqlite/migration/20260106000000_add_vtxo_updated_at.up.sql (1)

1-6: Confirm updated_at semantics and guard against NULLs.
updated_at is nullable with no default, and the backfill sets all existing rows to “now.” If any insert/update path omits updated_at or if historical ordering/TTL logic relies on true last-modified times, this can misbehave. Please verify that all write paths set updated_at and that the “now” backfill is intended; otherwise consider adding a DEFAULT/trigger or deriving from existing timestamps.

@altafan altafan merged commit dec35a9 into master Jan 20, 2026
6 checks passed
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.

3 participants