From c6682d5bc6f83f1d5cc38ea0431a6c2d2fcb170b Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 5 Feb 2026 11:03:03 -0500 Subject: [PATCH 01/14] Add HashBytea type for transaction hash BYTEA storage Implements Scan, Value, and String methods following the AddressBytea pattern. Stores 32-byte raw SHA-256 hash in database, represents as 64-character hex string in Go. --- internal/indexer/types/types.go | 43 +++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/internal/indexer/types/types.go b/internal/indexer/types/types.go index 06c024a4..8acbbc95 100644 --- a/internal/indexer/types/types.go +++ b/internal/indexer/types/types.go @@ -34,6 +34,7 @@ package types import ( "database/sql" "database/sql/driver" + "encoding/hex" "encoding/json" "fmt" "time" @@ -123,6 +124,48 @@ func (n NullAddressBytea) String() string { return string(n.AddressBytea) } +// HashBytea represents a transaction hash stored as BYTEA in the database. +// Storage format: 32 bytes (raw SHA-256 hash) +// Go representation: hex string (64 characters) +type HashBytea string + +// Scan implements sql.Scanner - converts BYTEA (32 bytes) to hex string +func (h *HashBytea) Scan(value any) error { + if value == nil { + *h = "" + return nil + } + bytes, ok := value.([]byte) + if !ok { + return fmt.Errorf("expected []byte, got %T", value) + } + if len(bytes) != 32 { + return fmt.Errorf("expected 32 bytes, got %d", len(bytes)) + } + *h = HashBytea(hex.EncodeToString(bytes)) + return nil +} + +// Value implements driver.Valuer - converts hex string to 32-byte []byte +func (h HashBytea) Value() (driver.Value, error) { + if h == "" { + return nil, nil + } + bytes, err := hex.DecodeString(string(h)) + if err != nil { + return nil, fmt.Errorf("decoding hex hash %s: %w", h, err) + } + if len(bytes) != 32 { + return nil, fmt.Errorf("invalid hash length: expected 32 bytes, got %d", len(bytes)) + } + return bytes, nil +} + +// String returns the hash as a hex string. +func (h HashBytea) String() string { + return string(h) +} + type ContractType string const ( From 6f73fdf3e1af471e6976291194fa0e6028a78962 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 5 Feb 2026 11:03:17 -0500 Subject: [PATCH 02/14] Update Transaction.Hash field to use HashBytea type Changes the Hash field from string to HashBytea for automatic BYTEA serialization/deserialization in the database. --- internal/indexer/types/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/indexer/types/types.go b/internal/indexer/types/types.go index 8acbbc95..9c939abe 100644 --- a/internal/indexer/types/types.go +++ b/internal/indexer/types/types.go @@ -258,7 +258,7 @@ type AccountWithOperationID struct { } type Transaction struct { - Hash string `json:"hash,omitempty" db:"hash"` + Hash HashBytea `json:"hash,omitempty" db:"hash"` ToID int64 `json:"toId,omitempty" db:"to_id"` EnvelopeXDR *string `json:"envelopeXdr,omitempty" db:"envelope_xdr"` FeeCharged int64 `json:"feeCharged,omitempty" db:"fee_charged"` From e743465c2728646de5190e5198062f412982c04c Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 5 Feb 2026 11:03:29 -0500 Subject: [PATCH 03/14] Wrap transaction hash in HashBytea in ConvertTransaction Converts the ingest transaction hash to HashBytea type for proper database serialization. --- internal/indexer/processors/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/indexer/processors/utils.go b/internal/indexer/processors/utils.go index 4176d098..04c88b4f 100644 --- a/internal/indexer/processors/utils.go +++ b/internal/indexer/processors/utils.go @@ -309,7 +309,7 @@ func ConvertTransaction(transaction *ingest.LedgerTransaction, skipTxMeta bool, return &types.Transaction{ ToID: transactionID, - Hash: transaction.Hash.HexString(), + Hash: types.HashBytea(transaction.Hash.HexString()), LedgerCreatedAt: transaction.Ledger.ClosedAt(), EnvelopeXDR: envelopeXDR, FeeCharged: feeCharged, From 5d165326826ecc408171ac7553805a2efac0e2c9 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 5 Feb 2026 11:03:43 -0500 Subject: [PATCH 04/14] Use .String() for map key in IndexerBuffer Converts HashBytea to string for use as map key since HashBytea is not directly comparable as a map key type. --- internal/indexer/indexer_buffer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/indexer/indexer_buffer.go b/internal/indexer/indexer_buffer.go index 3f5714c3..9cae55f0 100644 --- a/internal/indexer/indexer_buffer.go +++ b/internal/indexer/indexer_buffer.go @@ -114,7 +114,7 @@ func (b *IndexerBuffer) PushTransaction(participant string, transaction types.Tr // // Caller must hold write lock. func (b *IndexerBuffer) pushTransactionUnsafe(participant string, transaction *types.Transaction) { - txHash := transaction.Hash + txHash := transaction.Hash.String() if _, exists := b.txByHash[txHash]; !exists { b.txByHash[txHash] = transaction } From 1ad897d2a47a82da7d25b5ec82e39dd3d918561e Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 5 Feb 2026 11:04:36 -0500 Subject: [PATCH 05/14] Update transactions.go for BYTEA hash storage - GetByHash: Convert input hash string to HashBytea for query - BatchInsert: Convert hashes to [][]byte, use bytea[] in SQL, return hex-encoded hashes - BatchCopy: Convert hash to bytes using Hash.Value() --- internal/data/transactions.go | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/internal/data/transactions.go b/internal/data/transactions.go index f25e13eb..ecf16fd1 100644 --- a/internal/data/transactions.go +++ b/internal/data/transactions.go @@ -27,7 +27,8 @@ func (m *TransactionModel) GetByHash(ctx context.Context, hash string, columns s query := fmt.Sprintf(`SELECT %s FROM transactions WHERE hash = $1`, columns) var transaction types.Transaction start := time.Now() - err := m.DB.GetContext(ctx, &transaction, query, hash) + hashBytea := types.HashBytea(hash) + err := m.DB.GetContext(ctx, &transaction, query, hashBytea) duration := time.Since(start).Seconds() m.MetricsService.ObserveDBQueryDuration("GetByHash", "transactions", duration) if err != nil { @@ -190,7 +191,7 @@ func (m *TransactionModel) BatchInsert( } // 1. Flatten the transactions into parallel slices - hashes := make([]string, len(txs)) + hashes := make([][]byte, len(txs)) toIDs := make([]int64, len(txs)) envelopeXDRs := make([]*string, len(txs)) feesCharged := make([]int64, len(txs)) @@ -201,7 +202,11 @@ func (m *TransactionModel) BatchInsert( isFeeBumps := make([]bool, len(txs)) for i, t := range txs { - hashes[i] = t.Hash + hashBytes, err := t.Hash.Value() + if err != nil { + return nil, fmt.Errorf("converting hash %s to bytes: %w", t.Hash, err) + } + hashes[i] = hashBytes.([]byte) toIDs[i] = t.ToID envelopeXDRs[i] = t.EnvelopeXDR feesCharged[i] = t.FeeCharged @@ -237,7 +242,7 @@ func (m *TransactionModel) BatchInsert( t.hash, t.to_id, t.envelope_xdr, t.fee_charged, t.result_code, t.meta_xdr, t.ledger_number, t.ledger_created_at, t.is_fee_bump FROM ( SELECT - UNNEST($1::text[]) AS hash, + UNNEST($1::bytea[]) AS hash, UNNEST($2::bigint[]) AS to_id, UNNEST($3::text[]) AS envelope_xdr, UNNEST($4::bigint[]) AS fee_charged, @@ -248,7 +253,7 @@ func (m *TransactionModel) BatchInsert( UNNEST($9::boolean[]) AS is_fee_bump ) t ON CONFLICT (to_id) DO NOTHING - RETURNING hash + RETURNING encode(hash, 'hex') ), -- Insert transactions_accounts links @@ -266,7 +271,7 @@ func (m *TransactionModel) BatchInsert( ) -- Return the hashes of successfully inserted transactions - SELECT hash FROM inserted_transactions; + SELECT encode FROM inserted_transactions; ` start := time.Now() @@ -331,8 +336,12 @@ func (m *TransactionModel) BatchCopy( []string{"hash", "to_id", "envelope_xdr", "fee_charged", "result_code", "meta_xdr", "ledger_number", "ledger_created_at", "is_fee_bump"}, pgx.CopyFromSlice(len(txs), func(i int) ([]any, error) { tx := txs[i] + hashBytes, err := tx.Hash.Value() + if err != nil { + return nil, fmt.Errorf("converting hash %s to bytes: %w", tx.Hash, err) + } return []any{ - pgtype.Text{String: tx.Hash, Valid: true}, + hashBytes, pgtype.Int8{Int64: tx.ToID, Valid: true}, pgtypeTextFromPtr(tx.EnvelopeXDR), pgtype.Int8{Int64: tx.FeeCharged, Valid: true}, From caacd8561df89104ba192259bcf69776a190c52f Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 5 Feb 2026 11:04:47 -0500 Subject: [PATCH 06/14] Change transactions.hash column from TEXT to BYTEA Stores 32-byte raw SHA-256 hash instead of 64-char hex string, reducing storage by ~50%. --- internal/db/migrations/2025-06-10.2-transactions.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/db/migrations/2025-06-10.2-transactions.sql b/internal/db/migrations/2025-06-10.2-transactions.sql index 4ec3abf1..44ee11ae 100644 --- a/internal/db/migrations/2025-06-10.2-transactions.sql +++ b/internal/db/migrations/2025-06-10.2-transactions.sql @@ -3,7 +3,7 @@ -- Table: transactions CREATE TABLE transactions ( to_id BIGINT PRIMARY KEY, - hash TEXT NOT NULL UNIQUE, + hash BYTEA NOT NULL UNIQUE, envelope_xdr TEXT, fee_charged BIGINT NOT NULL, result_code TEXT NOT NULL, From a04135893d0eea266a733775483ed0bd897426ef Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 5 Feb 2026 11:05:20 -0500 Subject: [PATCH 07/14] Add HashBytea unit tests Tests for Scan, Value, Roundtrip, and String methods following the same pattern as AddressBytea tests. --- internal/indexer/types/types_test.go | 138 +++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) diff --git a/internal/indexer/types/types_test.go b/internal/indexer/types/types_test.go index 3c507656..c8422f89 100644 --- a/internal/indexer/types/types_test.go +++ b/internal/indexer/types/types_test.go @@ -2,6 +2,7 @@ package types import ( "database/sql/driver" + "encoding/hex" "testing" "github.com/stellar/go-stellar-sdk/keypair" @@ -241,3 +242,140 @@ func TestNullableJSONB_Value(t *testing.T) { }) } } + +func TestHashBytea_Scan(t *testing.T) { + // Valid 32-byte hash + validHex := "e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48760" + validBytes, err := hex.DecodeString(validHex) + require.NoError(t, err) + + testCases := []struct { + name string + input any + want HashBytea + wantErrContains string + }{ + { + name: "🟢nil value", + input: nil, + want: "", + }, + { + name: "🟢valid 32-byte hash", + input: validBytes, + want: HashBytea(validHex), + }, + { + name: "🔴wrong type", + input: 12345, + wantErrContains: "expected []byte", + }, + { + name: "🔴wrong length", + input: []byte{1, 2, 3}, + wantErrContains: "expected 32 bytes", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var h HashBytea + err := h.Scan(tc.input) + if tc.wantErrContains != "" { + assert.ErrorContains(t, err, tc.wantErrContains) + } else { + assert.NoError(t, err) + assert.Equal(t, tc.want, h) + } + }) + } +} + +func TestHashBytea_Value(t *testing.T) { + // Valid 32-byte hash + validHex := "e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48760" + expectedBytes, err := hex.DecodeString(validHex) + require.NoError(t, err) + + testCases := []struct { + name string + input HashBytea + want driver.Value + wantErrContains string + }{ + { + name: "🟢empty string", + input: "", + want: nil, + }, + { + name: "🟢valid hex hash", + input: HashBytea(validHex), + want: expectedBytes, + }, + { + name: "🔴invalid hex", + input: "not-a-valid-hex", + wantErrContains: "decoding hex hash", + }, + { + name: "🔴wrong length (short)", + input: "abcd", + wantErrContains: "invalid hash length", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got, err := tc.input.Value() + if tc.wantErrContains != "" { + assert.ErrorContains(t, err, tc.wantErrContains) + } else { + assert.NoError(t, err) + assert.Equal(t, tc.want, got) + } + }) + } +} + +func TestHashBytea_Roundtrip(t *testing.T) { + // Test that Value -> Scan produces the original hash + original := HashBytea("e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48760") + + // Convert to bytes + bytes, err := original.Value() + require.NoError(t, err) + + // Convert back to hash + var restored HashBytea + err = restored.Scan(bytes) + require.NoError(t, err) + + assert.Equal(t, original, restored) +} + +func TestHashBytea_String(t *testing.T) { + testCases := []struct { + name string + input HashBytea + want string + }{ + { + name: "🟢empty string", + input: "", + want: "", + }, + { + name: "🟢valid hex hash", + input: HashBytea("e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48760"), + want: "e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48760", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got := tc.input.String() + assert.Equal(t, tc.want, got) + }) + } +} From 4c1e5dcb6bb8df4d71f9eb894901f6bfa9a0d800 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 5 Feb 2026 11:09:25 -0500 Subject: [PATCH 08/14] Update test files with valid 64-char hex hashes All test files now use valid hex strings that can be converted to 32-byte BYTEA values instead of simple strings like "tx1", "tx2". --- internal/data/operations_test.go | 8 +- internal/data/statechanges_test.go | 8 +- internal/data/transactions_test.go | 118 ++++++++++-------- internal/indexer/indexer_buffer_test.go | 84 ++++++------- .../serve/graphql/resolvers/test_utils.go | 2 +- .../resolvers/transaction_resolvers_test.go | 12 +- 6 files changed, 122 insertions(+), 110 deletions(-) diff --git a/internal/data/operations_test.go b/internal/data/operations_test.go index fb8f64a3..070069be 100644 --- a/internal/data/operations_test.go +++ b/internal/data/operations_test.go @@ -65,7 +65,7 @@ func Test_OperationModel_BatchInsert(t *testing.T) { meta1, meta2 := "meta1", "meta2" envelope1, envelope2 := "envelope1", "envelope2" tx1 := types.Transaction{ - Hash: "tx1", + Hash: "d176b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48774", ToID: 4096, EnvelopeXDR: &envelope1, FeeCharged: 100, @@ -76,7 +76,7 @@ func Test_OperationModel_BatchInsert(t *testing.T) { IsFeeBump: false, } tx2 := types.Transaction{ - Hash: "tx2", + Hash: "e176b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48775", ToID: 8192, EnvelopeXDR: &envelope2, FeeCharged: 200, @@ -252,7 +252,7 @@ func Test_OperationModel_BatchCopy(t *testing.T) { meta1, meta2 := "meta1", "meta2" envelope1, envelope2 := "envelope1", "envelope2" tx1 := types.Transaction{ - Hash: "tx1", + Hash: "d176b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48774", ToID: 4096, EnvelopeXDR: &envelope1, FeeCharged: 100, @@ -263,7 +263,7 @@ func Test_OperationModel_BatchCopy(t *testing.T) { IsFeeBump: false, } tx2 := types.Transaction{ - Hash: "tx2", + Hash: "e176b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48775", ToID: 8192, EnvelopeXDR: &envelope2, FeeCharged: 200, diff --git a/internal/data/statechanges_test.go b/internal/data/statechanges_test.go index 59c10ab7..8a0e32df 100644 --- a/internal/data/statechanges_test.go +++ b/internal/data/statechanges_test.go @@ -86,7 +86,7 @@ func TestStateChangeModel_BatchInsert(t *testing.T) { meta1, meta2 := "meta1", "meta2" envelope1, envelope2 := "envelope1", "envelope2" tx1 := types.Transaction{ - Hash: "tx1", + Hash: "f176b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48776", ToID: 1, EnvelopeXDR: &envelope1, FeeCharged: 100, @@ -97,7 +97,7 @@ func TestStateChangeModel_BatchInsert(t *testing.T) { IsFeeBump: false, } tx2 := types.Transaction{ - Hash: "tx2", + Hash: "0276b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48777", ToID: 2, EnvelopeXDR: &envelope2, FeeCharged: 200, @@ -242,7 +242,7 @@ func TestStateChangeModel_BatchCopy(t *testing.T) { meta1, meta2 := "meta1", "meta2" envelope1, envelope2 := "envelope1", "envelope2" tx1 := types.Transaction{ - Hash: "tx1", + Hash: "f176b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48776", ToID: 1, EnvelopeXDR: &envelope1, FeeCharged: 100, @@ -253,7 +253,7 @@ func TestStateChangeModel_BatchCopy(t *testing.T) { IsFeeBump: false, } tx2 := types.Transaction{ - Hash: "tx2", + Hash: "0276b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48777", ToID: 2, EnvelopeXDR: &envelope2, FeeCharged: 200, diff --git a/internal/data/transactions_test.go b/internal/data/transactions_test.go index 34bd6a40..7cc15a69 100644 --- a/internal/data/transactions_test.go +++ b/internal/data/transactions_test.go @@ -31,13 +31,13 @@ func generateTestTransactions(n int, startLedger int32) ([]*types.Transaction, m ledgerSeq := startLedger + int32(i) txIndex := int32(1) // First transaction in each ledger toID := toid.New(ledgerSeq, txIndex, 0).ToInt64() - hash := fmt.Sprintf("e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48760-%d", toID) + hash := fmt.Sprintf("e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0%08x", i) envelope := "AAAAAgAAAAB/NpQ+s+cP+ztX7ryuKgXrxowZPHd4qAxhseOye/JeUgAehIAC2NL/AAflugAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAwAAAAFQQUxMAAAAAKHc4IKbcW8HPPgy3zOhuqv851y72nfLGa0HVXxIRNzHAAAAAAAAAAAAQ3FwMxshxQAfwV8AAAAAYTGQ3QAAAAAAAAAMAAAAAAAAAAFQQUxMAAAAAKHc4IKbcW8HPPgy3zOhuqv851y72nfLGa0HVXxIRNzHAAAAAAAGXwFksiHwAEXz8QAAAABhoaQjAAAAAAAAAAF78l5SAAAAQD7LgvZA8Pdvfh5L2b9B9RC7DlacGBJuOchuZDHQdVD1P0bn6nGQJXxDDI4oN76J49JxB7bIgDVim39MU43MOgE=" meta := "AAAAAwAAAAAAAAAEAAAAAwM6nhwAAAAAAAAAAJjy0MY1CPlZ/co80nzufVmo4gd7NqWMb+RiGiPhiviJAAAAC4SozKUDMWgAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAQM6nhwAAAAAAAAAAJjy0MY1CPlZ/co80nzufVmo4gd7NqWMb+RiGiPhiviJAAAAC4SozKUDMWgAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAwM6LTkAAAAAAAAAAKl6DQcpepRdTbO/Vw4hYBENfE/95GevM7SNA0ftK0gtAAAAA8Kuf0AC+zZCAAAATAAAAAMAAAABAAAAAMRxxkNwYslQaok0LlOKGtpATS9Bzx06JV9DIffG4OF1AAAAAAAAAAlsb2JzdHIuY28AAAABAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAMAAAAAAyZ54QAAAABmrTXCAAAAAAAAAAEDOp4cAAAAAAAAAACpeg0HKXqUXU2zv1cOIWARDXxP/eRnrzO0jQNH7StILQAAAAPCrn9AAvs2QgAAAE0AAAADAAAAAQAAAADEccZDcGLJUGqJNC5TihraQE0vQc8dOiVfQyH3xuDhdQAAAAAAAAAJbG9ic3RyLmNvAAAAAQAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAADAAAAAAM6nhwAAAAAZyGdHwAAAAAAAAABAAAABAAAAAMDOp4cAAAAAAAAAACpeg0HKXqUXU2zv1cOIWARDXxP/eRnrzO0jQNH7StILQAAAAPCrn9AAvs2QgAAAE0AAAADAAAAAQAAAADEccZDcGLJUGqJNC5TihraQE0vQc8dOiVfQyH3xuDhdQAAAAAAAAAJbG9ic3RyLmNvAAAAAQAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAADAAAAAAM6nhwAAAAAZyGdHwAAAAAAAAABAzqeHAAAAAAAAAAAqXoNByl6lF1Ns79XDiFgEQ18T/3kZ68ztI0DR+0rSC0AAAACmKiNQAL7NkIAAABNAAAAAwAAAAEAAAAAxHHGQ3BiyVBqiTQuU4oa2kBNL0HPHTolX0Mh98bg4XUAAAAAAAAACWxvYnN0ci5jbwAAAAEAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAwAAAAADOp4cAAAAAGchnR8AAAAAAAAAAwM6nZoAAAAAAAAAALKxMozkOH3rgpz3/u3+93wsR4p6z4K82HmJ5NTuaZbYAAACZaqAwoIBqycyAABVlQAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAMAAAAAAzqSNgAAAABnIVdaAAAAAAAAAAEDOp4cAAAAAAAAAACysTKM5Dh964Kc9/7t/vd8LEeKes+CvNh5ieTU7mmW2AAAAmbUhrSCAasnMgAAVZUAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAADAAAAAAM6kjYAAAAAZyFXWgAAAAAAAAAAAAAAAA==" address := keypair.MustRandom().Address() txs[i] = &types.Transaction{ - Hash: hash, + Hash: types.HashBytea(hash), ToID: toID, EnvelopeXDR: &envelope, FeeCharged: int64(100 * (i + 1)), @@ -73,7 +73,7 @@ func Test_TransactionModel_BatchInsert(t *testing.T) { meta1, meta2 := "meta1", "meta2" envelope1, envelope2 := "envelope1", "envelope2" tx1 := types.Transaction{ - Hash: "tx1", + Hash: "e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48760", ToID: 1, EnvelopeXDR: &envelope1, FeeCharged: 100, @@ -84,7 +84,7 @@ func Test_TransactionModel_BatchInsert(t *testing.T) { IsFeeBump: false, } tx2 := types.Transaction{ - Hash: "tx2", + Hash: "a76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48761", ToID: 2, EnvelopeXDR: &envelope2, FeeCharged: 200, @@ -111,7 +111,7 @@ func Test_TransactionModel_BatchInsert(t *testing.T) { stellarAddressesByToID: map[int64]set.Set[string]{tx1.ToID: set.NewSet(kp1.Address()), tx2.ToID: set.NewSet(kp2.Address())}, wantAccountLinks: map[int64][]string{tx1.ToID: {kp1.Address()}, tx2.ToID: {kp2.Address()}}, wantErrContains: "", - wantHashes: []string{tx1.Hash, tx2.Hash}, + wantHashes: []string{tx1.Hash.String(), tx2.Hash.String()}, }, { name: "🟢successful_insert_with_dbTx", @@ -120,7 +120,7 @@ func Test_TransactionModel_BatchInsert(t *testing.T) { stellarAddressesByToID: map[int64]set.Set[string]{tx1.ToID: set.NewSet(kp1.Address())}, wantAccountLinks: map[int64][]string{tx1.ToID: {kp1.Address()}}, wantErrContains: "", - wantHashes: []string{tx1.Hash}, + wantHashes: []string{tx1.Hash.String()}, }, { name: "🟢empty_input", @@ -138,7 +138,7 @@ func Test_TransactionModel_BatchInsert(t *testing.T) { stellarAddressesByToID: map[int64]set.Set[string]{tx1.ToID: set.NewSet(kp1.Address())}, wantAccountLinks: map[int64][]string{tx1.ToID: {kp1.Address()}}, wantErrContains: "", - wantHashes: []string{tx1.Hash}, + wantHashes: []string{tx1.Hash.String()}, }, } @@ -237,8 +237,8 @@ func Test_TransactionModel_BatchCopy(t *testing.T) { meta1, meta2 := "meta1", "meta2" envelope1, envelope2 := "envelope1", "envelope2" - tx1 := types.Transaction{ - Hash: "tx1", + txCopy1 := types.Transaction{ + Hash: "b76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48762", ToID: 1, EnvelopeXDR: &envelope1, FeeCharged: 100, @@ -248,8 +248,8 @@ func Test_TransactionModel_BatchCopy(t *testing.T) { LedgerCreatedAt: now, IsFeeBump: false, } - tx2 := types.Transaction{ - Hash: "tx2", + txCopy2 := types.Transaction{ + Hash: "c76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48763", ToID: 2, EnvelopeXDR: &envelope2, FeeCharged: 200, @@ -260,8 +260,8 @@ func Test_TransactionModel_BatchCopy(t *testing.T) { IsFeeBump: true, } // Transaction with nullable fields (nil envelope and meta) - tx3 := types.Transaction{ - Hash: "tx3", + txCopy3 := types.Transaction{ + Hash: "d76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48764", ToID: 3, EnvelopeXDR: nil, FeeCharged: 300, @@ -281,8 +281,8 @@ func Test_TransactionModel_BatchCopy(t *testing.T) { }{ { name: "🟢successful_insert_multiple", - txs: []*types.Transaction{&tx1, &tx2}, - stellarAddressesByToID: map[int64]set.Set[string]{tx1.ToID: set.NewSet(kp1.Address()), tx2.ToID: set.NewSet(kp2.Address())}, + txs: []*types.Transaction{&txCopy1, &txCopy2}, + stellarAddressesByToID: map[int64]set.Set[string]{txCopy1.ToID: set.NewSet(kp1.Address()), txCopy2.ToID: set.NewSet(kp2.Address())}, wantCount: 2, }, { @@ -293,13 +293,13 @@ func Test_TransactionModel_BatchCopy(t *testing.T) { }, { name: "🟢nullable_fields", - txs: []*types.Transaction{&tx3}, - stellarAddressesByToID: map[int64]set.Set[string]{tx3.ToID: set.NewSet(kp1.Address())}, + txs: []*types.Transaction{&txCopy3}, + stellarAddressesByToID: map[int64]set.Set[string]{txCopy3.ToID: set.NewSet(kp1.Address())}, wantCount: 1, }, { name: "🟢no_participants", - txs: []*types.Transaction{&tx1}, + txs: []*types.Transaction{&txCopy1}, stellarAddressesByToID: map[int64]set.Set[string]{}, wantCount: 1, }, @@ -404,8 +404,8 @@ func Test_TransactionModel_BatchCopy_DuplicateFails(t *testing.T) { meta := "meta1" envelope := "envelope1" - tx1 := types.Transaction{ - Hash: "tx_duplicate_test", + txDup := types.Transaction{ + Hash: "f76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48766", ToID: 100, EnvelopeXDR: &envelope, FeeCharged: 100, @@ -420,14 +420,14 @@ func Test_TransactionModel_BatchCopy_DuplicateFails(t *testing.T) { sqlxDB, err := dbConnectionPool.SqlxDB(ctx) require.NoError(t, err) txModel := &TransactionModel{DB: dbConnectionPool, MetricsService: metrics.NewMetricsService(sqlxDB)} - _, err = txModel.BatchInsert(ctx, nil, []*types.Transaction{&tx1}, map[int64]set.Set[string]{ - tx1.ToID: set.NewSet(kp1.Address()), + _, err = txModel.BatchInsert(ctx, nil, []*types.Transaction{&txDup}, map[int64]set.Set[string]{ + txDup.ToID: set.NewSet(kp1.Address()), }) require.NoError(t, err) // Verify the transaction was inserted var count int - err = dbConnectionPool.GetContext(ctx, &count, "SELECT COUNT(*) FROM transactions WHERE hash = $1", tx1.Hash) + err = dbConnectionPool.GetContext(ctx, &count, "SELECT COUNT(*) FROM transactions WHERE hash = $1", txDup.Hash) require.NoError(t, err) require.Equal(t, 1, count) @@ -449,8 +449,8 @@ func Test_TransactionModel_BatchCopy_DuplicateFails(t *testing.T) { pgxTx, err := conn.Begin(ctx) require.NoError(t, err) - _, err = m.BatchCopy(ctx, pgxTx, []*types.Transaction{&tx1}, map[int64]set.Set[string]{ - tx1.ToID: set.NewSet(kp1.Address()), + _, err = m.BatchCopy(ctx, pgxTx, []*types.Transaction{&txDup}, map[int64]set.Set[string]{ + txDup.ToID: set.NewSet(kp1.Address()), }) // BatchCopy should fail with a unique constraint violation @@ -482,7 +482,7 @@ func TestTransactionModel_GetByHash(t *testing.T) { now := time.Now() // Create test transaction - txHash := "test_tx_hash" + txHash := types.HashBytea("0076b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48767") _, err = dbConnectionPool.ExecContext(ctx, ` INSERT INTO transactions (hash, to_id, envelope_xdr, fee_charged, result_code, meta_xdr, ledger_number, ledger_created_at, is_fee_bump) VALUES ($1, 1, 'envelope', 100, 'TransactionResultCodeTxSuccess', 'meta', 1, $2, false) @@ -490,7 +490,7 @@ func TestTransactionModel_GetByHash(t *testing.T) { require.NoError(t, err) // Test GetByHash - transaction, err := m.GetByHash(ctx, txHash, "") + transaction, err := m.GetByHash(ctx, txHash.String(), "") require.NoError(t, err) assert.Equal(t, txHash, transaction.Hash) assert.Equal(t, int64(1), transaction.ToID) @@ -519,13 +519,16 @@ func TestTransactionModel_GetAll(t *testing.T) { now := time.Now() // Create test transactions + testHash1 := types.HashBytea("1076b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48768") + testHash2 := types.HashBytea("2076b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48769") + testHash3 := types.HashBytea("3076b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4876a") _, err = dbConnectionPool.ExecContext(ctx, ` INSERT INTO transactions (hash, to_id, envelope_xdr, fee_charged, result_code, meta_xdr, ledger_number, ledger_created_at, is_fee_bump) VALUES - ('tx1', 1, 'envelope1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $1, false), - ('tx2', 2, 'envelope2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $1, true), - ('tx3', 3, 'envelope3', 300, 'TransactionResultCodeTxSuccess', 'meta3', 3, $1, false) - `, now) + ($1, 1, 'envelope1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $4, false), + ($2, 2, 'envelope2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $4, true), + ($3, 3, 'envelope3', 300, 'TransactionResultCodeTxSuccess', 'meta3', 3, $4, false) + `, testHash1, testHash2, testHash3, now) require.NoError(t, err) // Test GetAll without specifying cursor and limit (gets all transactions) @@ -573,13 +576,16 @@ func TestTransactionModel_BatchGetByAccountAddress(t *testing.T) { require.NoError(t, err) // Create test transactions + accTestHash1 := types.HashBytea("4076b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4876b") + accTestHash2 := types.HashBytea("5076b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4876c") + accTestHash3 := types.HashBytea("6076b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4876d") _, err = dbConnectionPool.ExecContext(ctx, ` INSERT INTO transactions (hash, to_id, envelope_xdr, fee_charged, result_code, meta_xdr, ledger_number, ledger_created_at, is_fee_bump) VALUES - ('tx1', 1, 'envelope1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $1, false), - ('tx2', 2, 'envelope2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $1, true), - ('tx3', 3, 'envelope3', 300, 'TransactionResultCodeTxSuccess', 'meta3', 3, $1, false) - `, now) + ($1, 1, 'envelope1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $4, false), + ($2, 2, 'envelope2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $4, true), + ($3, 3, 'envelope3', 300, 'TransactionResultCodeTxSuccess', 'meta3', 3, $4, false) + `, accTestHash1, accTestHash2, accTestHash3, now) require.NoError(t, err) // Create test transactions_accounts links (account_id is BYTEA) @@ -624,18 +630,21 @@ func TestTransactionModel_BatchGetByOperationIDs(t *testing.T) { // Create test transactions with specific ToIDs // Operations IDs must be in TOID range for each transaction: (to_id, to_id + 4096) + opTestHash1 := types.HashBytea("7076b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4876e") + opTestHash2 := types.HashBytea("8076b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4876f") + opTestHash3 := types.HashBytea("9076b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48770") _, err = dbConnectionPool.ExecContext(ctx, ` INSERT INTO transactions (hash, to_id, envelope_xdr, fee_charged, result_code, meta_xdr, ledger_number, ledger_created_at, is_fee_bump) VALUES - ('tx1', 4096, 'envelope1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $1, false), - ('tx2', 8192, 'envelope2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $1, true), - ('tx3', 12288, 'envelope3', 300, 'TransactionResultCodeTxSuccess', 'meta3', 3, $1, false) - `, now) + ($1, 4096, 'envelope1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $4, false), + ($2, 8192, 'envelope2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $4, true), + ($3, 12288, 'envelope3', 300, 'TransactionResultCodeTxSuccess', 'meta3', 3, $4, false) + `, opTestHash1, opTestHash2, opTestHash3, now) require.NoError(t, err) // Create test operations (IDs must be in TOID range for each transaction) - // tx1 (to_id=4096): ops 4097, 4098 - // tx2 (to_id=8192): op 8193 + // opTestHash1 (to_id=4096): ops 4097, 4098 + // opTestHash2 (to_id=8192): op 8193 _, err = dbConnectionPool.ExecContext(ctx, ` INSERT INTO operations (id, operation_type, operation_xdr, result_code, successful, ledger_number, ledger_created_at) VALUES @@ -651,13 +660,13 @@ func TestTransactionModel_BatchGetByOperationIDs(t *testing.T) { assert.Len(t, transactions, 3) // Verify transactions are for correct operation IDs - operationIDsFound := make(map[int64]string) + operationIDsFound := make(map[int64]types.HashBytea) for _, tx := range transactions { operationIDsFound[tx.OperationID] = tx.Hash } - assert.Equal(t, "tx1", operationIDsFound[4097]) - assert.Equal(t, "tx2", operationIDsFound[8193]) - assert.Equal(t, "tx1", operationIDsFound[4098]) + assert.Equal(t, opTestHash1, operationIDsFound[4097]) + assert.Equal(t, opTestHash2, operationIDsFound[8193]) + assert.Equal(t, opTestHash1, operationIDsFound[4098]) } func TestTransactionModel_BatchGetByStateChangeIDs(t *testing.T) { @@ -687,13 +696,16 @@ func TestTransactionModel_BatchGetByStateChangeIDs(t *testing.T) { require.NoError(t, err) // Create test transactions + scTestHash1 := types.HashBytea("a176b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48771") + scTestHash2 := types.HashBytea("b176b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48772") + scTestHash3 := types.HashBytea("c176b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48773") _, err = dbConnectionPool.ExecContext(ctx, ` INSERT INTO transactions (hash, to_id, envelope_xdr, fee_charged, result_code, meta_xdr, ledger_number, ledger_created_at, is_fee_bump) VALUES - ('tx1', 1, 'envelope1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $1, false), - ('tx2', 2, 'envelope2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $1, true), - ('tx3', 3, 'envelope3', 300, 'TransactionResultCodeTxSuccess', 'meta3', 3, $1, false) - `, now) + ($1, 1, 'envelope1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $4, false), + ($2, 2, 'envelope2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $4, true), + ($3, 3, 'envelope3', 300, 'TransactionResultCodeTxSuccess', 'meta3', 3, $4, false) + `, scTestHash1, scTestHash2, scTestHash3, now) require.NoError(t, err) // Create test state changes @@ -713,13 +725,13 @@ func TestTransactionModel_BatchGetByStateChangeIDs(t *testing.T) { // Verify transactions are for correct state change IDs (format: to_id-operation_id-state_change_order) // State change (to_id, operation_id, order) should return transaction with matching to_id - stateChangeIDsFound := make(map[string]string) + stateChangeIDsFound := make(map[string]types.HashBytea) for _, tx := range transactions { stateChangeIDsFound[tx.StateChangeID] = tx.Hash } - assert.Equal(t, "tx1", stateChangeIDsFound["1-1-1"]) // to_id=1 -> tx1 (to_id=1) - assert.Equal(t, "tx2", stateChangeIDsFound["2-2-1"]) // to_id=2 -> tx2 (to_id=2) - assert.Equal(t, "tx3", stateChangeIDsFound["3-3-1"]) // to_id=3 -> tx3 (to_id=3) + assert.Equal(t, scTestHash1, stateChangeIDsFound["1-1-1"]) // to_id=1 -> scTestHash1 (to_id=1) + assert.Equal(t, scTestHash2, stateChangeIDsFound["2-2-1"]) // to_id=2 -> scTestHash2 (to_id=2) + assert.Equal(t, scTestHash3, stateChangeIDsFound["3-3-1"]) // to_id=3 -> scTestHash3 (to_id=3) } func BenchmarkTransactionModel_BatchInsert(b *testing.B) { diff --git a/internal/indexer/indexer_buffer_test.go b/internal/indexer/indexer_buffer_test.go index 93818cff..c868b533 100644 --- a/internal/indexer/indexer_buffer_test.go +++ b/internal/indexer/indexer_buffer_test.go @@ -27,8 +27,8 @@ func TestIndexerBuffer_PushTransaction(t *testing.T) { t.Run("🟢 sequential pushes", func(t *testing.T) { indexerBuffer := NewIndexerBuffer() - tx1 := types.Transaction{Hash: "tx_hash_1", ToID: 1} - tx2 := types.Transaction{Hash: "tx_hash_2", ToID: 2} + tx1 := types.Transaction{Hash: "e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48760", ToID: 1} + tx2 := types.Transaction{Hash: "a76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48761", ToID: 2} indexerBuffer.PushTransaction("alice", tx1) indexerBuffer.PushTransaction("alice", tx2) @@ -50,8 +50,8 @@ func TestIndexerBuffer_PushTransaction(t *testing.T) { t.Run("🟢 concurrent pushes", func(t *testing.T) { indexerBuffer := NewIndexerBuffer() - tx1 := types.Transaction{Hash: "tx_hash_1", ToID: 1} - tx2 := types.Transaction{Hash: "tx_hash_2", ToID: 2} + tx1 := types.Transaction{Hash: "e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48760", ToID: 1} + tx2 := types.Transaction{Hash: "a76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48761", ToID: 2} wg := sync.WaitGroup{} wg.Add(4) @@ -87,8 +87,8 @@ func TestIndexerBuffer_PushOperation(t *testing.T) { t.Run("🟢 sequential pushes", func(t *testing.T) { indexerBuffer := NewIndexerBuffer() - tx1 := types.Transaction{Hash: "tx_hash_1", ToID: 1} - tx2 := types.Transaction{Hash: "tx_hash_2", ToID: 2} + tx1 := types.Transaction{Hash: "e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48760", ToID: 1} + tx2 := types.Transaction{Hash: "a76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48761", ToID: 2} op1 := types.Operation{ID: 1} op2 := types.Operation{ID: 2} @@ -111,8 +111,8 @@ func TestIndexerBuffer_PushOperation(t *testing.T) { t.Run("🟢 concurrent pushes", func(t *testing.T) { indexerBuffer := NewIndexerBuffer() - tx1 := types.Transaction{Hash: "tx_hash_1", ToID: 1} - tx2 := types.Transaction{Hash: "tx_hash_2", ToID: 2} + tx1 := types.Transaction{Hash: "e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48760", ToID: 1} + tx2 := types.Transaction{Hash: "a76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48761", ToID: 2} op1 := types.Operation{ID: 1} op2 := types.Operation{ID: 2} @@ -147,7 +147,7 @@ func TestIndexerBuffer_PushStateChange(t *testing.T) { t.Run("🟢 sequential pushes", func(t *testing.T) { indexerBuffer := NewIndexerBuffer() - tx := types.Transaction{Hash: "test_tx_hash", ToID: 1} + tx := types.Transaction{Hash: "c76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48763", ToID: 1} op := types.Operation{ID: 1} sc1 := types.StateChange{ToID: 1, StateChangeOrder: 1} @@ -165,7 +165,7 @@ func TestIndexerBuffer_PushStateChange(t *testing.T) { t.Run("🟢 concurrent pushes", func(t *testing.T) { indexerBuffer := NewIndexerBuffer() - tx := types.Transaction{Hash: "test_tx_hash", ToID: 1} + tx := types.Transaction{Hash: "c76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48763", ToID: 1} op := types.Operation{ID: 1} sc1 := types.StateChange{ToID: 1, StateChangeOrder: 1} @@ -196,8 +196,8 @@ func TestIndexerBuffer_PushStateChange(t *testing.T) { t.Run("🟢 with operations and transactions", func(t *testing.T) { indexerBuffer := NewIndexerBuffer() - tx1 := types.Transaction{Hash: "tx_hash_1", ToID: 1} - tx2 := types.Transaction{Hash: "tx_hash_2", ToID: 2} + tx1 := types.Transaction{Hash: "e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48760", ToID: 1} + tx2 := types.Transaction{Hash: "a76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48761", ToID: 2} op1 := types.Operation{ID: 3} op2 := types.Operation{ID: 4} op3 := types.Operation{ID: 5} @@ -239,8 +239,8 @@ func TestIndexerBuffer_GetNumberOfTransactions(t *testing.T) { assert.Equal(t, 0, indexerBuffer.GetNumberOfTransactions()) - tx1 := types.Transaction{Hash: "tx_hash_1", ToID: 1} - tx2 := types.Transaction{Hash: "tx_hash_2", ToID: 2} + tx1 := types.Transaction{Hash: "e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48760", ToID: 1} + tx2 := types.Transaction{Hash: "a76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48761", ToID: 2} indexerBuffer.PushTransaction("alice", tx1) assert.Equal(t, 1, indexerBuffer.GetNumberOfTransactions()) @@ -258,8 +258,8 @@ func TestIndexerBuffer_GetAllTransactions(t *testing.T) { t.Run("🟢 returns all unique transactions", func(t *testing.T) { indexerBuffer := NewIndexerBuffer() - tx1 := types.Transaction{Hash: "tx_hash_1", ToID: 1, LedgerNumber: 100} - tx2 := types.Transaction{Hash: "tx_hash_2", ToID: 2, LedgerNumber: 101} + tx1 := types.Transaction{Hash: "e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48760", ToID: 1, LedgerNumber: 100} + tx2 := types.Transaction{Hash: "a76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48761", ToID: 2, LedgerNumber: 101} indexerBuffer.PushTransaction("alice", tx1) indexerBuffer.PushTransaction("bob", tx2) @@ -275,8 +275,8 @@ func TestIndexerBuffer_GetAllTransactionsParticipants(t *testing.T) { t.Run("🟢 returns correct participants mapping", func(t *testing.T) { indexerBuffer := NewIndexerBuffer() - tx1 := types.Transaction{Hash: "tx_hash_1", ToID: 1} - tx2 := types.Transaction{Hash: "tx_hash_2", ToID: 2} + tx1 := types.Transaction{Hash: "e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48760", ToID: 1} + tx2 := types.Transaction{Hash: "a76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48761", ToID: 2} indexerBuffer.PushTransaction("alice", tx1) indexerBuffer.PushTransaction("bob", tx1) @@ -292,7 +292,7 @@ func TestIndexerBuffer_GetAllOperations(t *testing.T) { t.Run("🟢 returns all unique operations", func(t *testing.T) { indexerBuffer := NewIndexerBuffer() - tx1 := types.Transaction{Hash: "tx_hash_1", ToID: 1} + tx1 := types.Transaction{Hash: "e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48760", ToID: 1} op1 := types.Operation{ID: 1} op2 := types.Operation{ID: 2} @@ -310,7 +310,7 @@ func TestIndexerBuffer_GetAllOperationsParticipants(t *testing.T) { t.Run("🟢 returns correct participants mapping", func(t *testing.T) { indexerBuffer := NewIndexerBuffer() - tx1 := types.Transaction{Hash: "tx_hash_1", ToID: 1} + tx1 := types.Transaction{Hash: "e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48760", ToID: 1} op1 := types.Operation{ID: 1} op2 := types.Operation{ID: 2} @@ -328,7 +328,7 @@ func TestIndexerBuffer_GetAllStateChanges(t *testing.T) { t.Run("🟢 returns all state changes in order", func(t *testing.T) { indexerBuffer := NewIndexerBuffer() - tx := types.Transaction{Hash: "test_tx_hash", ToID: 1} + tx := types.Transaction{Hash: "c76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48763", ToID: 1} op := types.Operation{ID: 1} sc1 := types.StateChange{ToID: 1, StateChangeOrder: 1, AccountID: "alice"} @@ -354,8 +354,8 @@ func TestIndexerBuffer_GetAllParticipants(t *testing.T) { t.Run("🟢 collects participants from transactions", func(t *testing.T) { indexerBuffer := NewIndexerBuffer() - tx1 := types.Transaction{Hash: "tx_hash_1", ToID: 1} - tx2 := types.Transaction{Hash: "tx_hash_2", ToID: 2} + tx1 := types.Transaction{Hash: "e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48760", ToID: 1} + tx2 := types.Transaction{Hash: "a76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48761", ToID: 2} indexerBuffer.PushTransaction("alice", tx1) indexerBuffer.PushTransaction("bob", tx2) @@ -368,7 +368,7 @@ func TestIndexerBuffer_GetAllParticipants(t *testing.T) { t.Run("🟢 collects participants from operations", func(t *testing.T) { indexerBuffer := NewIndexerBuffer() - tx := types.Transaction{Hash: "tx_hash_1", ToID: 1} + tx := types.Transaction{Hash: "e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48760", ToID: 1} op1 := types.Operation{ID: 1} op2 := types.Operation{ID: 2} @@ -383,7 +383,7 @@ func TestIndexerBuffer_GetAllParticipants(t *testing.T) { t.Run("🟢 collects participants from state changes", func(t *testing.T) { indexerBuffer := NewIndexerBuffer() - tx := types.Transaction{Hash: "tx_hash_1", ToID: 1} + tx := types.Transaction{Hash: "e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48760", ToID: 1} op := types.Operation{ID: 1} sc1 := types.StateChange{ToID: 1, StateChangeOrder: 1, AccountID: "alice", OperationID: 1} @@ -401,7 +401,7 @@ func TestIndexerBuffer_GetAllParticipants(t *testing.T) { t.Run("🟢 collects unique participants from all sources", func(t *testing.T) { indexerBuffer := NewIndexerBuffer() - tx := types.Transaction{Hash: "tx_hash_1", ToID: 1} + tx := types.Transaction{Hash: "e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48760", ToID: 1} op := types.Operation{ID: 1} sc := types.StateChange{ToID: 1, StateChangeOrder: 1, AccountID: "dave", OperationID: 1} @@ -418,7 +418,7 @@ func TestIndexerBuffer_GetAllParticipants(t *testing.T) { t.Run("🟢 ignores empty participants", func(t *testing.T) { indexerBuffer := NewIndexerBuffer() - tx := types.Transaction{Hash: "tx_hash_1", ToID: 1} + tx := types.Transaction{Hash: "e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48760", ToID: 1} indexerBuffer.PushTransaction("", tx) // empty participant indexerBuffer.PushTransaction("alice", tx) @@ -441,8 +441,8 @@ func TestIndexerBuffer_Merge(t *testing.T) { buffer1 := NewIndexerBuffer() buffer2 := NewIndexerBuffer() - tx1 := types.Transaction{Hash: "tx_hash_1", ToID: 1} - tx2 := types.Transaction{Hash: "tx_hash_2", ToID: 2} + tx1 := types.Transaction{Hash: "e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48760", ToID: 1} + tx2 := types.Transaction{Hash: "a76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48761", ToID: 2} buffer1.PushTransaction("alice", tx1) buffer2.PushTransaction("bob", tx2) @@ -464,7 +464,7 @@ func TestIndexerBuffer_Merge(t *testing.T) { buffer1 := NewIndexerBuffer() buffer2 := NewIndexerBuffer() - tx1 := types.Transaction{Hash: "tx_hash_1", ToID: 1} + tx1 := types.Transaction{Hash: "e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48760", ToID: 1} op1 := types.Operation{ID: 1} op2 := types.Operation{ID: 2} @@ -488,7 +488,7 @@ func TestIndexerBuffer_Merge(t *testing.T) { buffer1 := NewIndexerBuffer() buffer2 := NewIndexerBuffer() - tx := types.Transaction{Hash: "test_tx_hash", ToID: 1} + tx := types.Transaction{Hash: "c76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48763", ToID: 1} op := types.Operation{ID: 1} sc1 := types.StateChange{ToID: 1, StateChangeOrder: 1, AccountID: "alice"} @@ -510,8 +510,8 @@ func TestIndexerBuffer_Merge(t *testing.T) { buffer1 := NewIndexerBuffer() buffer2 := NewIndexerBuffer() - tx1 := types.Transaction{Hash: "tx_hash_1", ToID: 1} - tx2 := types.Transaction{Hash: "tx_hash_2", ToID: 2} + tx1 := types.Transaction{Hash: "e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48760", ToID: 1} + tx2 := types.Transaction{Hash: "a76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48761", ToID: 2} op1 := types.Operation{ID: 1} // Buffer1 has tx1 with alice @@ -543,7 +543,7 @@ func TestIndexerBuffer_Merge(t *testing.T) { buffer1 := NewIndexerBuffer() buffer2 := NewIndexerBuffer() - tx1 := types.Transaction{Hash: "tx_hash_1", ToID: 1} + tx1 := types.Transaction{Hash: "e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48760", ToID: 1} op1 := types.Operation{ID: 1} sc1 := types.StateChange{ToID: 1, StateChangeOrder: 1, AccountID: "alice"} @@ -562,7 +562,7 @@ func TestIndexerBuffer_Merge(t *testing.T) { buffer1 := NewIndexerBuffer() buffer2 := NewIndexerBuffer() - tx1 := types.Transaction{Hash: "tx_hash_1", ToID: 1} + tx1 := types.Transaction{Hash: "e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48760", ToID: 1} buffer1.PushTransaction("alice", tx1) buffer1.Merge(buffer2) @@ -575,9 +575,9 @@ func TestIndexerBuffer_Merge(t *testing.T) { buffer2 := NewIndexerBuffer() buffer3 := NewIndexerBuffer() - tx1 := types.Transaction{Hash: "tx_hash_1", ToID: 1} - tx2 := types.Transaction{Hash: "tx_hash_2", ToID: 2} - tx3 := types.Transaction{Hash: "tx_hash_3", ToID: 3} + tx1 := types.Transaction{Hash: "e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48760", ToID: 1} + tx2 := types.Transaction{Hash: "a76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48761", ToID: 2} + tx3 := types.Transaction{Hash: "b76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48762", ToID: 3} buffer1.PushTransaction("alice", tx1) buffer2.PushTransaction("bob", tx2) @@ -606,8 +606,8 @@ func TestIndexerBuffer_Merge(t *testing.T) { buffer1 := NewIndexerBuffer() buffer2 := NewIndexerBuffer() - tx1 := types.Transaction{Hash: "tx_hash_1", ToID: 1} - tx2 := types.Transaction{Hash: "tx_hash_2", ToID: 2} + tx1 := types.Transaction{Hash: "e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48760", ToID: 1} + tx2 := types.Transaction{Hash: "a76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48761", ToID: 2} op1 := types.Operation{ID: 1} op2 := types.Operation{ID: 2} sc1 := types.StateChange{ToID: 1, StateChangeOrder: 1, AccountID: "alice", OperationID: 1} @@ -653,8 +653,8 @@ func TestIndexerBuffer_Merge(t *testing.T) { buffer1 := NewIndexerBuffer() buffer2 := NewIndexerBuffer() - tx1 := types.Transaction{Hash: "tx_hash_1", ToID: 1} - tx2 := types.Transaction{Hash: "tx_hash_2", ToID: 2} + tx1 := types.Transaction{Hash: "e76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48760", ToID: 1} + tx2 := types.Transaction{Hash: "a76b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48761", ToID: 2} buffer1.PushTransaction("alice", tx1) buffer1.PushTransaction("bob", tx1) diff --git a/internal/serve/graphql/resolvers/test_utils.go b/internal/serve/graphql/resolvers/test_utils.go index e18cedaf..1897e27c 100644 --- a/internal/serve/graphql/resolvers/test_utils.go +++ b/internal/serve/graphql/resolvers/test_utils.go @@ -62,7 +62,7 @@ func setupDB(ctx context.Context, t *testing.T, dbConnectionPool db.ConnectionPo opIdx := 1 for i := range 4 { txn := &types.Transaction{ - Hash: fmt.Sprintf("tx%d", i+1), + Hash: types.HashBytea(fmt.Sprintf("3476b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4877%x", i)), ToID: toid.New(testLedger, int32(i+1), 0).ToInt64(), EnvelopeXDR: ptr(fmt.Sprintf("envelope%d", i+1)), FeeCharged: int64(100 * (i + 1)), diff --git a/internal/serve/graphql/resolvers/transaction_resolvers_test.go b/internal/serve/graphql/resolvers/transaction_resolvers_test.go index 17df19e4..af123290 100644 --- a/internal/serve/graphql/resolvers/transaction_resolvers_test.go +++ b/internal/serve/graphql/resolvers/transaction_resolvers_test.go @@ -32,7 +32,7 @@ func TestTransactionResolver_Operations(t *testing.T) { }, }} // ToID=toid.New(1000, 1, 0) matches the test data setup in test_utils.go (testLedger=1000, i=0) - parentTx := &types.Transaction{Hash: "tx1", ToID: toid.New(1000, 1, 0).ToInt64()} + parentTx := &types.Transaction{Hash: "1376b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48778", ToID: toid.New(1000, 1, 0).ToInt64()} t.Run("success", func(t *testing.T) { loaders := dataloaders.NewDataloaders(resolver.models) @@ -56,7 +56,7 @@ func TestTransactionResolver_Operations(t *testing.T) { }) t.Run("transaction with no operations", func(t *testing.T) { - nonExistentTx := &types.Transaction{Hash: "non-existent-tx", ToID: 999999} + nonExistentTx := &types.Transaction{Hash: "2376b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48779", ToID: 999999} loaders := dataloaders.NewDataloaders(resolver.models) ctx := context.WithValue(getTestCtx("operations", []string{"id"}), middleware.LoadersKey, loaders) @@ -167,7 +167,7 @@ func TestTransactionResolver_Accounts(t *testing.T) { }, }, }} - parentTx := &types.Transaction{ToID: toid.New(1000, 1, 0).ToInt64(), Hash: "tx1"} + parentTx := &types.Transaction{ToID: toid.New(1000, 1, 0).ToInt64(), Hash: "1376b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48778"} t.Run("success", func(t *testing.T) { loaders := dataloaders.NewDataloaders(resolver.models) @@ -190,7 +190,7 @@ func TestTransactionResolver_Accounts(t *testing.T) { }) t.Run("transaction with no associated accounts", func(t *testing.T) { - nonExistentTx := &types.Transaction{Hash: "non-existent-tx"} + nonExistentTx := &types.Transaction{Hash: "2376b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48779"} loaders := dataloaders.NewDataloaders(resolver.models) ctx := context.WithValue(getTestCtx("accounts", []string{"address"}), middleware.LoadersKey, loaders) @@ -219,8 +219,8 @@ func TestTransactionResolver_StateChanges(t *testing.T) { }, }, }} - parentTx := &types.Transaction{Hash: "tx1", ToID: toid.New(1000, 1, 0).ToInt64()} - nonExistentTx := &types.Transaction{Hash: "non-existent-tx", ToID: 0} + parentTx := &types.Transaction{Hash: "1376b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48778", ToID: toid.New(1000, 1, 0).ToInt64()} + nonExistentTx := &types.Transaction{Hash: "2376b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48779", ToID: 0} t.Run("success without pagination", func(t *testing.T) { loaders := dataloaders.NewDataloaders(resolver.models) From 09348d96354f67caa26b47af58904e6af8948892 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 5 Feb 2026 11:22:19 -0500 Subject: [PATCH 09/14] fix tests --- internal/data/operations_test.go | 8 ++-- internal/data/statechanges_test.go | 8 ++-- internal/data/transactions.go | 14 +++++-- internal/data/transactions_test.go | 37 +++++++++++-------- .../resolvers/transaction_resolvers_test.go | 12 +++--- 5 files changed, 45 insertions(+), 34 deletions(-) diff --git a/internal/data/operations_test.go b/internal/data/operations_test.go index 070069be..447b96c9 100644 --- a/internal/data/operations_test.go +++ b/internal/data/operations_test.go @@ -65,7 +65,7 @@ func Test_OperationModel_BatchInsert(t *testing.T) { meta1, meta2 := "meta1", "meta2" envelope1, envelope2 := "envelope1", "envelope2" tx1 := types.Transaction{ - Hash: "d176b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48774", + Hash: "d176b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4877", ToID: 4096, EnvelopeXDR: &envelope1, FeeCharged: 100, @@ -76,7 +76,7 @@ func Test_OperationModel_BatchInsert(t *testing.T) { IsFeeBump: false, } tx2 := types.Transaction{ - Hash: "e176b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48775", + Hash: "e176b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4877", ToID: 8192, EnvelopeXDR: &envelope2, FeeCharged: 200, @@ -252,7 +252,7 @@ func Test_OperationModel_BatchCopy(t *testing.T) { meta1, meta2 := "meta1", "meta2" envelope1, envelope2 := "envelope1", "envelope2" tx1 := types.Transaction{ - Hash: "d176b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48774", + Hash: "d176b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4877", ToID: 4096, EnvelopeXDR: &envelope1, FeeCharged: 100, @@ -263,7 +263,7 @@ func Test_OperationModel_BatchCopy(t *testing.T) { IsFeeBump: false, } tx2 := types.Transaction{ - Hash: "e176b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48775", + Hash: "e176b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4877", ToID: 8192, EnvelopeXDR: &envelope2, FeeCharged: 200, diff --git a/internal/data/statechanges_test.go b/internal/data/statechanges_test.go index 8a0e32df..31610029 100644 --- a/internal/data/statechanges_test.go +++ b/internal/data/statechanges_test.go @@ -86,7 +86,7 @@ func TestStateChangeModel_BatchInsert(t *testing.T) { meta1, meta2 := "meta1", "meta2" envelope1, envelope2 := "envelope1", "envelope2" tx1 := types.Transaction{ - Hash: "f176b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48776", + Hash: "f176b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4877", ToID: 1, EnvelopeXDR: &envelope1, FeeCharged: 100, @@ -97,7 +97,7 @@ func TestStateChangeModel_BatchInsert(t *testing.T) { IsFeeBump: false, } tx2 := types.Transaction{ - Hash: "0276b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48777", + Hash: "0276b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4877", ToID: 2, EnvelopeXDR: &envelope2, FeeCharged: 200, @@ -242,7 +242,7 @@ func TestStateChangeModel_BatchCopy(t *testing.T) { meta1, meta2 := "meta1", "meta2" envelope1, envelope2 := "envelope1", "envelope2" tx1 := types.Transaction{ - Hash: "f176b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48776", + Hash: "f176b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4877", ToID: 1, EnvelopeXDR: &envelope1, FeeCharged: 100, @@ -253,7 +253,7 @@ func TestStateChangeModel_BatchCopy(t *testing.T) { IsFeeBump: false, } tx2 := types.Transaction{ - Hash: "0276b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48777", + Hash: "0276b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4877", ToID: 2, EnvelopeXDR: &envelope2, FeeCharged: 200, diff --git a/internal/data/transactions.go b/internal/data/transactions.go index ecf16fd1..7e51a892 100644 --- a/internal/data/transactions.go +++ b/internal/data/transactions.go @@ -253,7 +253,7 @@ func (m *TransactionModel) BatchInsert( UNNEST($9::boolean[]) AS is_fee_bump ) t ON CONFLICT (to_id) DO NOTHING - RETURNING encode(hash, 'hex') + RETURNING hash ), -- Insert transactions_accounts links @@ -271,11 +271,11 @@ func (m *TransactionModel) BatchInsert( ) -- Return the hashes of successfully inserted transactions - SELECT encode FROM inserted_transactions; + SELECT hash FROM inserted_transactions; ` start := time.Now() - var insertedHashes []string + var insertedHashes []types.HashBytea err := sqlExecuter.SelectContext(ctx, &insertedHashes, insertQuery, pq.Array(hashes), pq.Array(toIDs), @@ -306,7 +306,13 @@ func (m *TransactionModel) BatchInsert( return nil, fmt.Errorf("batch inserting transactions and transactions_accounts: %w", err) } - return insertedHashes, nil + // Convert HashBytea to string for the return value + result := make([]string, len(insertedHashes)) + for i, h := range insertedHashes { + result[i] = h.String() + } + + return result, nil } // BatchCopy inserts transactions using pgx's binary COPY protocol. diff --git a/internal/data/transactions_test.go b/internal/data/transactions_test.go index 7cc15a69..bf15daea 100644 --- a/internal/data/transactions_test.go +++ b/internal/data/transactions_test.go @@ -185,10 +185,15 @@ func Test_TransactionModel_BatchInsert(t *testing.T) { // Verify the results require.NoError(t, err) - var dbInsertedHashes []string + var dbInsertedHashes []types.HashBytea err = sqlExecuter.SelectContext(ctx, &dbInsertedHashes, "SELECT hash FROM transactions") require.NoError(t, err) - assert.ElementsMatch(t, tc.wantHashes, dbInsertedHashes) + // Convert HashBytea to string for comparison + dbHashStrings := make([]string, len(dbInsertedHashes)) + for i, h := range dbInsertedHashes { + dbHashStrings[i] = h.String() + } + assert.ElementsMatch(t, tc.wantHashes, dbHashStrings) assert.ElementsMatch(t, tc.wantHashes, gotInsertedHashes) // Verify the account links @@ -356,7 +361,7 @@ func Test_TransactionModel_BatchCopy(t *testing.T) { assert.Equal(t, tc.wantCount, gotCount) // Verify from DB - var dbInsertedHashes []string + var dbInsertedHashes []types.HashBytea err = dbConnectionPool.SelectContext(ctx, &dbInsertedHashes, "SELECT hash FROM transactions ORDER BY hash") require.NoError(t, err) assert.Len(t, dbInsertedHashes, tc.wantCount) @@ -482,7 +487,7 @@ func TestTransactionModel_GetByHash(t *testing.T) { now := time.Now() // Create test transaction - txHash := types.HashBytea("0076b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48767") + txHash := types.HashBytea("0076b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4876") _, err = dbConnectionPool.ExecContext(ctx, ` INSERT INTO transactions (hash, to_id, envelope_xdr, fee_charged, result_code, meta_xdr, ledger_number, ledger_created_at, is_fee_bump) VALUES ($1, 1, 'envelope', 100, 'TransactionResultCodeTxSuccess', 'meta', 1, $2, false) @@ -519,9 +524,9 @@ func TestTransactionModel_GetAll(t *testing.T) { now := time.Now() // Create test transactions - testHash1 := types.HashBytea("1076b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48768") - testHash2 := types.HashBytea("2076b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48769") - testHash3 := types.HashBytea("3076b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4876a") + testHash1 := types.HashBytea("1076b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4876") + testHash2 := types.HashBytea("2076b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4876") + testHash3 := types.HashBytea("3076b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4876") _, err = dbConnectionPool.ExecContext(ctx, ` INSERT INTO transactions (hash, to_id, envelope_xdr, fee_charged, result_code, meta_xdr, ledger_number, ledger_created_at, is_fee_bump) VALUES @@ -576,9 +581,9 @@ func TestTransactionModel_BatchGetByAccountAddress(t *testing.T) { require.NoError(t, err) // Create test transactions - accTestHash1 := types.HashBytea("4076b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4876b") - accTestHash2 := types.HashBytea("5076b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4876c") - accTestHash3 := types.HashBytea("6076b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4876d") + accTestHash1 := types.HashBytea("4076b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4876") + accTestHash2 := types.HashBytea("5076b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4876") + accTestHash3 := types.HashBytea("6076b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4876") _, err = dbConnectionPool.ExecContext(ctx, ` INSERT INTO transactions (hash, to_id, envelope_xdr, fee_charged, result_code, meta_xdr, ledger_number, ledger_created_at, is_fee_bump) VALUES @@ -630,9 +635,9 @@ func TestTransactionModel_BatchGetByOperationIDs(t *testing.T) { // Create test transactions with specific ToIDs // Operations IDs must be in TOID range for each transaction: (to_id, to_id + 4096) - opTestHash1 := types.HashBytea("7076b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4876e") - opTestHash2 := types.HashBytea("8076b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4876f") - opTestHash3 := types.HashBytea("9076b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48770") + opTestHash1 := types.HashBytea("7076b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4876") + opTestHash2 := types.HashBytea("8076b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4876") + opTestHash3 := types.HashBytea("9076b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4877") _, err = dbConnectionPool.ExecContext(ctx, ` INSERT INTO transactions (hash, to_id, envelope_xdr, fee_charged, result_code, meta_xdr, ledger_number, ledger_created_at, is_fee_bump) VALUES @@ -696,9 +701,9 @@ func TestTransactionModel_BatchGetByStateChangeIDs(t *testing.T) { require.NoError(t, err) // Create test transactions - scTestHash1 := types.HashBytea("a176b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48771") - scTestHash2 := types.HashBytea("b176b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48772") - scTestHash3 := types.HashBytea("c176b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48773") + scTestHash1 := types.HashBytea("a176b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4877") + scTestHash2 := types.HashBytea("b176b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4877") + scTestHash3 := types.HashBytea("c176b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4877") _, err = dbConnectionPool.ExecContext(ctx, ` INSERT INTO transactions (hash, to_id, envelope_xdr, fee_charged, result_code, meta_xdr, ledger_number, ledger_created_at, is_fee_bump) VALUES diff --git a/internal/serve/graphql/resolvers/transaction_resolvers_test.go b/internal/serve/graphql/resolvers/transaction_resolvers_test.go index af123290..a27b2851 100644 --- a/internal/serve/graphql/resolvers/transaction_resolvers_test.go +++ b/internal/serve/graphql/resolvers/transaction_resolvers_test.go @@ -32,7 +32,7 @@ func TestTransactionResolver_Operations(t *testing.T) { }, }} // ToID=toid.New(1000, 1, 0) matches the test data setup in test_utils.go (testLedger=1000, i=0) - parentTx := &types.Transaction{Hash: "1376b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48778", ToID: toid.New(1000, 1, 0).ToInt64()} + parentTx := &types.Transaction{Hash: "1376b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4877", ToID: toid.New(1000, 1, 0).ToInt64()} t.Run("success", func(t *testing.T) { loaders := dataloaders.NewDataloaders(resolver.models) @@ -56,7 +56,7 @@ func TestTransactionResolver_Operations(t *testing.T) { }) t.Run("transaction with no operations", func(t *testing.T) { - nonExistentTx := &types.Transaction{Hash: "2376b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48779", ToID: 999999} + nonExistentTx := &types.Transaction{Hash: "2376b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4877", ToID: 999999} loaders := dataloaders.NewDataloaders(resolver.models) ctx := context.WithValue(getTestCtx("operations", []string{"id"}), middleware.LoadersKey, loaders) @@ -167,7 +167,7 @@ func TestTransactionResolver_Accounts(t *testing.T) { }, }, }} - parentTx := &types.Transaction{ToID: toid.New(1000, 1, 0).ToInt64(), Hash: "1376b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48778"} + parentTx := &types.Transaction{ToID: toid.New(1000, 1, 0).ToInt64(), Hash: "1376b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4877"} t.Run("success", func(t *testing.T) { loaders := dataloaders.NewDataloaders(resolver.models) @@ -190,7 +190,7 @@ func TestTransactionResolver_Accounts(t *testing.T) { }) t.Run("transaction with no associated accounts", func(t *testing.T) { - nonExistentTx := &types.Transaction{Hash: "2376b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48779"} + nonExistentTx := &types.Transaction{Hash: "2376b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4877"} loaders := dataloaders.NewDataloaders(resolver.models) ctx := context.WithValue(getTestCtx("accounts", []string{"address"}), middleware.LoadersKey, loaders) @@ -219,8 +219,8 @@ func TestTransactionResolver_StateChanges(t *testing.T) { }, }, }} - parentTx := &types.Transaction{Hash: "1376b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48778", ToID: toid.New(1000, 1, 0).ToInt64()} - nonExistentTx := &types.Transaction{Hash: "2376b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48779", ToID: 0} + parentTx := &types.Transaction{Hash: "1376b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4877", ToID: toid.New(1000, 1, 0).ToInt64()} + nonExistentTx := &types.Transaction{Hash: "2376b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4877", ToID: 0} t.Run("success without pagination", func(t *testing.T) { loaders := dataloaders.NewDataloaders(resolver.models) From d238b0b18f5baa3fbdc473d5e36424f791be313e Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 5 Feb 2026 11:43:39 -0500 Subject: [PATCH 10/14] add resolver for hash --- internal/serve/graphql/generated/generated.go | 47 ++++++++++++++++--- .../resolvers/transaction.resolvers.go | 5 ++ .../serve/graphql/schema/transaction.graphqls | 2 +- internal/services/ingest_test.go | 2 +- 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/internal/serve/graphql/generated/generated.go b/internal/serve/graphql/generated/generated.go index 88c82a13..47cb64d3 100644 --- a/internal/serve/graphql/generated/generated.go +++ b/internal/serve/graphql/generated/generated.go @@ -453,6 +453,8 @@ type StandardBalanceChangeResolver interface { Amount(ctx context.Context, obj *types.StandardBalanceStateChangeModel) (string, error) } type TransactionResolver interface { + Hash(ctx context.Context, obj *types.Transaction) (string, error) + Operations(ctx context.Context, obj *types.Transaction, first *int32, after *string, last *int32, before *string) (*OperationConnection, error) Accounts(ctx context.Context, obj *types.Transaction) ([]*types.Account, error) StateChanges(ctx context.Context, obj *types.Transaction, first *int32, after *string, last *int32, before *string) (*StateChangeConnection, error) @@ -2551,7 +2553,7 @@ type BalanceAuthorizationChange implements BaseStateChange{ {Name: "../schema/transaction.graphqls", Input: `# GraphQL Transaction type - represents a blockchain transaction # gqlgen generates Go structs from this schema definition type Transaction{ - hash: String! + hash: String! @goField(forceResolver: true) envelopeXdr: String feeCharged: Int64! resultCode: String! @@ -11278,7 +11280,7 @@ func (ec *executionContext) _Transaction_hash(ctx context.Context, field graphql }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { ctx = rctx // use context from middleware stack in children - return obj.Hash, nil + return ec.resolvers.Transaction().Hash(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -11299,8 +11301,8 @@ func (ec *executionContext) fieldContext_Transaction_hash(_ context.Context, fie fc = &graphql.FieldContext{ Object: "Transaction", Field: field, - IsMethod: false, - IsResolver: false, + IsMethod: true, + IsResolver: true, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { return nil, errors.New("field of type String does not have child fields") }, @@ -19054,10 +19056,41 @@ func (ec *executionContext) _Transaction(ctx context.Context, sel ast.SelectionS case "__typename": out.Values[i] = graphql.MarshalString("Transaction") case "hash": - out.Values[i] = ec._Transaction_hash(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&out.Invalids, 1) + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Transaction_hash(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) case "envelopeXdr": out.Values[i] = ec._Transaction_envelopeXdr(ctx, field, obj) case "feeCharged": diff --git a/internal/serve/graphql/resolvers/transaction.resolvers.go b/internal/serve/graphql/resolvers/transaction.resolvers.go index 7d83bf8f..eeb7dec2 100644 --- a/internal/serve/graphql/resolvers/transaction.resolvers.go +++ b/internal/serve/graphql/resolvers/transaction.resolvers.go @@ -15,6 +15,11 @@ import ( "github.com/stellar/wallet-backend/internal/serve/middleware" ) +// Hash is the resolver for the hash field. +func (r *transactionResolver) Hash(ctx context.Context, obj *types.Transaction) (string, error) { + return obj.Hash.String(), nil +} + // Operations is the resolver for the operations field. // This is a field resolver for the "operations" field on a Transaction object // It's called when a GraphQL query requests the operations within a transaction diff --git a/internal/serve/graphql/schema/transaction.graphqls b/internal/serve/graphql/schema/transaction.graphqls index 6a2bd458..a5c74d6a 100644 --- a/internal/serve/graphql/schema/transaction.graphqls +++ b/internal/serve/graphql/schema/transaction.graphqls @@ -1,7 +1,7 @@ # GraphQL Transaction type - represents a blockchain transaction # gqlgen generates Go structs from this schema definition type Transaction{ - hash: String! + hash: String! @goField(forceResolver: true) envelopeXdr: String feeCharged: Int64! resultCode: String! diff --git a/internal/services/ingest_test.go b/internal/services/ingest_test.go index 65fc4ebd..abdf6789 100644 --- a/internal/services/ingest_test.go +++ b/internal/services/ingest_test.go @@ -508,7 +508,7 @@ func createTestTransaction(hash string, toID int64) types.Transaction { envelope := "test_envelope_xdr" meta := "test_meta_xdr" return types.Transaction{ - Hash: hash, + Hash: types.HashBytea(hash), ToID: toID, EnvelopeXDR: &envelope, FeeCharged: 100, From d6526ef1a2d7fb779105dc019399a6d499b8d7e0 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 5 Feb 2026 11:58:05 -0500 Subject: [PATCH 11/14] Update statechanges.go --- internal/data/statechanges.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/data/statechanges.go b/internal/data/statechanges.go index c9e95798..ae0aee54 100644 --- a/internal/data/statechanges.go +++ b/internal/data/statechanges.go @@ -38,7 +38,7 @@ func (m *StateChangeModel) BatchGetByAccountAddress(ctx context.Context, account // Add transaction hash filter if provided (uses subquery to find to_id by hash) if txHash != nil { queryBuilder.WriteString(fmt.Sprintf(" AND to_id = (SELECT to_id FROM transactions WHERE hash = $%d)", argIndex)) - args = append(args, *txHash) + args = append(args, types.HashBytea(*txHash)) argIndex++ } From 6ff993f66f257094707e77123a7974804d287f29 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 5 Feb 2026 13:04:27 -0500 Subject: [PATCH 12/14] fix all tests --- internal/data/accounts_test.go | 18 ++-- internal/data/operations_test.go | 70 +++++++++------ internal/data/statechanges_test.go | 86 +++++++++++-------- .../resolvers/account_resolvers_test.go | 32 +++---- .../resolvers/operation_resolvers_test.go | 2 +- .../resolvers/queries_resolvers_test.go | 28 +++--- .../resolvers/statechange_resolvers_test.go | 2 +- .../serve/graphql/resolvers/test_utils.go | 8 ++ internal/services/ingest_test.go | 75 +++++++++------- 9 files changed, 193 insertions(+), 128 deletions(-) diff --git a/internal/data/accounts_test.go b/internal/data/accounts_test.go index 0dac677e..f34b35f3 100644 --- a/internal/data/accounts_test.go +++ b/internal/data/accounts_test.go @@ -266,8 +266,10 @@ func TestAccountModelBatchGetByToIDs(t *testing.T) { types.AddressBytea(address1), types.AddressBytea(address2)) require.NoError(t, err) - // Insert test transactions first - _, err = m.DB.ExecContext(ctx, "INSERT INTO transactions (hash, to_id, envelope_xdr, fee_charged, result_code, meta_xdr, ledger_number, ledger_created_at) VALUES ('tx1', $1, 'env1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, NOW()), ('tx2', $2, 'env2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, NOW())", toID1, toID2) + // Insert test transactions first (hash is BYTEA, using valid 64-char hex strings) + testHash1 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000001") + testHash2 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000002") + _, err = m.DB.ExecContext(ctx, "INSERT INTO transactions (hash, to_id, envelope_xdr, fee_charged, result_code, meta_xdr, ledger_number, ledger_created_at) VALUES ($1, $2, 'env1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, NOW()), ($3, $4, 'env2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, NOW())", testHash1, toID1, testHash2, toID2) require.NoError(t, err) // Insert test transactions_accounts links @@ -318,8 +320,10 @@ func TestAccountModelBatchGetByOperationIDs(t *testing.T) { types.AddressBytea(address1), types.AddressBytea(address2)) require.NoError(t, err) - // Insert test transactions first - _, err = m.DB.ExecContext(ctx, "INSERT INTO transactions (hash, to_id, envelope_xdr, fee_charged, result_code, meta_xdr, ledger_number, ledger_created_at) VALUES ('tx1', 4096, 'env1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, NOW()), ('tx2', 8192, 'env2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, NOW())") + // Insert test transactions first (hash is BYTEA, using valid 64-char hex strings) + testHash1 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000001") + testHash2 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000002") + _, err = m.DB.ExecContext(ctx, "INSERT INTO transactions (hash, to_id, envelope_xdr, fee_charged, result_code, meta_xdr, ledger_number, ledger_created_at) VALUES ($1, 4096, 'env1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, NOW()), ($2, 8192, 'env2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, NOW())", testHash1, testHash2) require.NoError(t, err) // Insert test operations (IDs don't need to be in TOID range here since we're just testing operations_accounts links) @@ -411,8 +415,10 @@ func TestAccountModelBatchGetByStateChangeIDs(t *testing.T) { types.AddressBytea(address1), types.AddressBytea(address2)) require.NoError(t, err) - // Insert test transactions first - _, err = m.DB.ExecContext(ctx, "INSERT INTO transactions (hash, to_id, envelope_xdr, fee_charged, result_code, meta_xdr, ledger_number, ledger_created_at) VALUES ('tx1', 4096, 'env1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, NOW()), ('tx2', 8192, 'env2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, NOW())") + // Insert test transactions first (hash is BYTEA, using valid 64-char hex strings) + testHash1 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000001") + testHash2 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000002") + _, err = m.DB.ExecContext(ctx, "INSERT INTO transactions (hash, to_id, envelope_xdr, fee_charged, result_code, meta_xdr, ledger_number, ledger_created_at) VALUES ($1, 4096, 'env1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, NOW()), ($2, 8192, 'env2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, NOW())", testHash1, testHash2) require.NoError(t, err) // Insert test operations (IDs must be in TOID range for each transaction) diff --git a/internal/data/operations_test.go b/internal/data/operations_test.go index 447b96c9..36420617 100644 --- a/internal/data/operations_test.go +++ b/internal/data/operations_test.go @@ -502,14 +502,17 @@ func TestOperationModel_GetAll(t *testing.T) { ctx := context.Background() now := time.Now() - // Create test transactions first + // Create test transactions first (hash is BYTEA, using valid 64-char hex strings) + testHash1 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000001") + testHash2 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000002") + testHash3 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000003") _, err = dbConnectionPool.ExecContext(ctx, ` INSERT INTO transactions (hash, to_id, envelope_xdr, fee_charged, result_code, meta_xdr, ledger_number, ledger_created_at, is_fee_bump) VALUES - ('tx1', 1, 'env1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $1, false), - ('tx2', 2, 'env2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $1, true), - ('tx3', 3, 'env3', 300, 'TransactionResultCodeTxSuccess', 'meta3', 3, $1, false) - `, now) + ($2, 1, 'env1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $1, false), + ($3, 2, 'env2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $1, true), + ($4, 3, 'env3', 300, 'TransactionResultCodeTxSuccess', 'meta3', 3, $1, false) + `, now, testHash1, testHash2, testHash3) require.NoError(t, err) // Create test operations (IDs must be in TOID range for each transaction: (to_id, to_id + 4096)) @@ -552,13 +555,16 @@ func TestOperationModel_BatchGetByToIDs(t *testing.T) { // Create test transactions first with specific ToIDs // ToID encoding: operations for a tx with to_id are in range (to_id, to_id + 4096) // Using to_id values: 4096, 8192, 12288 (multiples of 4096 for clarity) + testHash1 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000001") + testHash2 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000002") + testHash3 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000003") _, err = dbConnectionPool.ExecContext(ctx, ` INSERT INTO transactions (hash, to_id, envelope_xdr, fee_charged, result_code, meta_xdr, ledger_number, ledger_created_at, is_fee_bump) VALUES - ('tx1', 4096, 'env1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $1, false), - ('tx2', 8192, 'env2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $1, true), - ('tx3', 12288, 'env3', 300, 'TransactionResultCodeTxSuccess', 'meta3', 3, $1, false) - `, now) + ($2, 4096, 'env1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $1, false), + ($3, 8192, 'env2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $1, true), + ($4, 12288, 'env3', 300, 'TransactionResultCodeTxSuccess', 'meta3', 3, $1, false) + `, now, testHash1, testHash2, testHash3) require.NoError(t, err) // Create test operations - IDs must be in TOID range for each transaction @@ -742,13 +748,15 @@ func TestOperationModel_BatchGetByToID(t *testing.T) { ctx := context.Background() now := time.Now() - // Create test transactions first with specific ToIDs + // Create test transactions first with specific ToIDs (hash is BYTEA) + testHash1 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000001") + testHash2 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000002") _, err = dbConnectionPool.ExecContext(ctx, ` INSERT INTO transactions (hash, to_id, envelope_xdr, fee_charged, result_code, meta_xdr, ledger_number, ledger_created_at, is_fee_bump) VALUES - ('tx1', 4096, 'env1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $1, false), - ('tx2', 8192, 'env2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $1, true) - `, now) + ($2, 4096, 'env1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $1, false), + ($3, 8192, 'env2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $1, true) + `, now, testHash1, testHash2) require.NoError(t, err) // Create test operations - IDs must be in TOID range for each transaction @@ -797,14 +805,17 @@ func TestOperationModel_BatchGetByAccountAddresses(t *testing.T) { _, err = dbConnectionPool.ExecContext(ctx, "INSERT INTO accounts (stellar_address) VALUES ($1), ($2)", address1, address2) require.NoError(t, err) - // Create test transactions first + // Create test transactions first (hash is BYTEA) + testHash1 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000001") + testHash2 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000002") + testHash3 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000003") _, err = dbConnectionPool.ExecContext(ctx, ` INSERT INTO transactions (hash, to_id, envelope_xdr, fee_charged, result_code, meta_xdr, ledger_number, ledger_created_at, is_fee_bump) VALUES - ('tx1', 4096, 'env1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $1, false), - ('tx2', 8192, 'env2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $1, true), - ('tx3', 12288, 'env3', 300, 'TransactionResultCodeTxSuccess', 'meta3', 3, $1, false) - `, now) + ($2, 4096, 'env1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $1, false), + ($3, 8192, 'env2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $1, true), + ($4, 12288, 'env3', 300, 'TransactionResultCodeTxSuccess', 'meta3', 3, $1, false) + `, now, testHash1, testHash2, testHash3) require.NoError(t, err) // Create test operations (IDs must be in TOID range for each transaction) @@ -845,13 +856,15 @@ func TestOperationModel_GetByID(t *testing.T) { ctx := context.Background() now := time.Now() - // Create test transactions first + // Create test transactions first (hash is BYTEA) + testHash1 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000001") + testHash2 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000002") _, err = dbConnectionPool.ExecContext(ctx, ` INSERT INTO transactions (hash, to_id, envelope_xdr, fee_charged, result_code, meta_xdr, ledger_number, ledger_created_at, is_fee_bump) VALUES - ('tx1', 4096, 'env1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $1, false), - ('tx2', 8192, 'env2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $1, true) - `, now) + ($2, 4096, 'env1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $1, false), + ($3, 8192, 'env2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $1, true) + `, now, testHash1, testHash2) require.NoError(t, err) // Create test operations (IDs must be in TOID range for each transaction) @@ -907,14 +920,17 @@ func TestOperationModel_BatchGetByStateChangeIDs(t *testing.T) { _, err = dbConnectionPool.ExecContext(ctx, "INSERT INTO accounts (stellar_address) VALUES ($1)", address) require.NoError(t, err) - // Create test transactions first + // Create test transactions first (hash is BYTEA) + testHash1 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000001") + testHash2 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000002") + testHash3 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000003") _, err = dbConnectionPool.ExecContext(ctx, ` INSERT INTO transactions (hash, to_id, envelope_xdr, fee_charged, result_code, meta_xdr, ledger_number, ledger_created_at, is_fee_bump) VALUES - ('tx1', 4096, 'env1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $1, false), - ('tx2', 8192, 'env2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $1, true), - ('tx3', 12288, 'env3', 300, 'TransactionResultCodeTxSuccess', 'meta3', 3, $1, false) - `, now) + ($2, 4096, 'env1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $1, false), + ($3, 8192, 'env2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $1, true), + ($4, 12288, 'env3', 300, 'TransactionResultCodeTxSuccess', 'meta3', 3, $1, false) + `, now, testHash1, testHash2, testHash3) require.NoError(t, err) // Create test operations (IDs must be in TOID range for each transaction) diff --git a/internal/data/statechanges_test.go b/internal/data/statechanges_test.go index 31610029..8267b5fa 100644 --- a/internal/data/statechanges_test.go +++ b/internal/data/statechanges_test.go @@ -487,14 +487,17 @@ func TestStateChangeModel_BatchGetByAccountAddress(t *testing.T) { types.AddressBytea(address1), types.AddressBytea(address2)) require.NoError(t, err) - // Create test transactions first + // Create test transactions first (hash is BYTEA) + testHash1 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000001") + testHash2 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000002") + testHash3 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000003") _, err = dbConnectionPool.ExecContext(ctx, ` INSERT INTO transactions (hash, to_id, envelope_xdr, fee_charged, result_code, meta_xdr, ledger_number, ledger_created_at, is_fee_bump) VALUES - ('tx1', 1, 'env1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $1, false), - ('tx2', 2, 'env2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $1, true), - ('tx3', 3, 'env3', 300, 'TransactionResultCodeTxSuccess', 'meta3', 3, $1, false) - `, now) + ($2, 1, 'env1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $1, false), + ($3, 2, 'env2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $1, true), + ($4, 3, 'env3', 300, 'TransactionResultCodeTxSuccess', 'meta3', 3, $1, false) + `, now, testHash1, testHash2, testHash3) require.NoError(t, err) // Create test state changes @@ -549,14 +552,18 @@ func TestStateChangeModel_BatchGetByAccountAddress_WithFilters(t *testing.T) { _, err = dbConnectionPool.ExecContext(ctx, "INSERT INTO accounts (stellar_address) VALUES ($1)", types.AddressBytea(address)) require.NoError(t, err) - // Create test transactions + // Create test transactions (hash is BYTEA) + testHash1 := "0000000000000000000000000000000000000000000000000000000000000001" + testHash2 := "0000000000000000000000000000000000000000000000000000000000000002" + testHash3 := "0000000000000000000000000000000000000000000000000000000000000003" + testHashNonExistent := "0000000000000000000000000000000000000000000000000000000000000004" _, err = dbConnectionPool.ExecContext(ctx, ` INSERT INTO transactions (hash, to_id, envelope_xdr, fee_charged, result_code, meta_xdr, ledger_number, ledger_created_at) VALUES - ('tx1', 1, 'env1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $1), - ('tx2', 2, 'env2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $1), - ('tx3', 3, 'env3', 300, 'TransactionResultCodeTxSuccess', 'meta3', 3, $1) - `, now) + ($2, 1, 'env1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $1), + ($3, 2, 'env2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $1), + ($4, 3, 'env3', 300, 'TransactionResultCodeTxSuccess', 'meta3', 3, $1) + `, now, types.HashBytea(testHash1), types.HashBytea(testHash2), types.HashBytea(testHash3)) require.NoError(t, err) // Create test state changes with different operation IDs, categories, and reasons @@ -583,7 +590,7 @@ func TestStateChangeModel_BatchGetByAccountAddress_WithFilters(t *testing.T) { MetricsService: mockMetricsService, } - txHash := "tx1" + txHash := testHash1 stateChanges, err := m.BatchGetByAccountAddress(ctx, address, &txHash, nil, nil, nil, "", nil, nil, ASC) require.NoError(t, err) // tx1 has to_id=1, so we get state changes where to_id=1 (2 state changes now) @@ -627,7 +634,7 @@ func TestStateChangeModel_BatchGetByAccountAddress_WithFilters(t *testing.T) { MetricsService: mockMetricsService, } - txHash := "tx1" + txHash := testHash1 operationID := int64(123) stateChanges, err := m.BatchGetByAccountAddress(ctx, address, &txHash, &operationID, nil, nil, "", nil, nil, ASC) require.NoError(t, err) @@ -716,7 +723,7 @@ func TestStateChangeModel_BatchGetByAccountAddress_WithFilters(t *testing.T) { MetricsService: mockMetricsService, } - txHash := "tx1" + txHash := testHash1 operationID := int64(123) category := "BALANCE" reason := "CREDIT" @@ -743,7 +750,7 @@ func TestStateChangeModel_BatchGetByAccountAddress_WithFilters(t *testing.T) { MetricsService: mockMetricsService, } - txHash := "nonexistent" + txHash := testHashNonExistent stateChanges, err := m.BatchGetByAccountAddress(ctx, address, &txHash, nil, nil, nil, "", nil, nil, ASC) require.NoError(t, err) assert.Empty(t, stateChanges) @@ -760,7 +767,7 @@ func TestStateChangeModel_BatchGetByAccountAddress_WithFilters(t *testing.T) { MetricsService: mockMetricsService, } - txHash := "tx1" + txHash := testHash1 limit := int32(1) stateChanges, err := m.BatchGetByAccountAddress(ctx, address, &txHash, nil, nil, nil, "", &limit, nil, ASC) require.NoError(t, err) @@ -794,14 +801,17 @@ func TestStateChangeModel_GetAll(t *testing.T) { _, err = dbConnectionPool.ExecContext(ctx, "INSERT INTO accounts (stellar_address) VALUES ($1)", types.AddressBytea(address)) require.NoError(t, err) - // Create test transactions first + // Create test transactions first (hash is BYTEA) + testHash1 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000001") + testHash2 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000002") + testHash3 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000003") _, err = dbConnectionPool.ExecContext(ctx, ` INSERT INTO transactions (hash, to_id, envelope_xdr, fee_charged, result_code, meta_xdr, ledger_number, ledger_created_at, is_fee_bump) VALUES - ('tx1', 1, 'env1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $1, false), - ('tx2', 2, 'env2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $1, true), - ('tx3', 3, 'env3', 300, 'TransactionResultCodeTxSuccess', 'meta3', 3, $1, false) - `, now) + ($2, 1, 'env1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $1, false), + ($3, 2, 'env2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $1, true), + ($4, 3, 'env3', 300, 'TransactionResultCodeTxSuccess', 'meta3', 3, $1, false) + `, now, testHash1, testHash2, testHash3) require.NoError(t, err) // Create test state changes @@ -841,14 +851,17 @@ func TestStateChangeModel_BatchGetByToIDs(t *testing.T) { _, err = dbConnectionPool.ExecContext(ctx, "INSERT INTO accounts (stellar_address) VALUES ($1)", types.AddressBytea(address)) require.NoError(t, err) - // Create test transactions first + // Create test transactions first (hash is BYTEA) + testHash1 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000001") + testHash2 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000002") + testHash3 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000003") _, err = dbConnectionPool.ExecContext(ctx, ` INSERT INTO transactions (hash, to_id, envelope_xdr, fee_charged, result_code, meta_xdr, ledger_number, ledger_created_at, is_fee_bump) VALUES - ('tx1', 1, 'env1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $1, false), - ('tx2', 2, 'env2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $1, true), - ('tx3', 3, 'env3', 300, 'TransactionResultCodeTxSuccess', 'meta3', 3, $1, false) - `, now) + ($2, 1, 'env1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $1, false), + ($3, 2, 'env2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $1, true), + ($4, 3, 'env3', 300, 'TransactionResultCodeTxSuccess', 'meta3', 3, $1, false) + `, now, testHash1, testHash2, testHash3) require.NoError(t, err) // Create test state changes - multiple state changes per to_id to test ranking @@ -997,14 +1010,17 @@ func TestStateChangeModel_BatchGetByOperationIDs(t *testing.T) { _, err = dbConnectionPool.ExecContext(ctx, "INSERT INTO accounts (stellar_address) VALUES ($1)", types.AddressBytea(address)) require.NoError(t, err) - // Create test transactions first + // Create test transactions first (hash is BYTEA) + testHash1 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000001") + testHash2 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000002") + testHash3 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000003") _, err = dbConnectionPool.ExecContext(ctx, ` INSERT INTO transactions (hash, to_id, envelope_xdr, fee_charged, result_code, meta_xdr, ledger_number, ledger_created_at, is_fee_bump) VALUES - ('tx1', 1, 'env1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $1, false), - ('tx2', 2, 'env2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $1, true), - ('tx3', 3, 'env3', 300, 'TransactionResultCodeTxSuccess', 'meta3', 3, $1, false) - `, now) + ($2, 1, 'env1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $1, false), + ($3, 2, 'env2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $1, true), + ($4, 3, 'env3', 300, 'TransactionResultCodeTxSuccess', 'meta3', 3, $1, false) + `, now, testHash1, testHash2, testHash3) require.NoError(t, err) // Create test state changes @@ -1057,13 +1073,15 @@ func TestStateChangeModel_BatchGetByToID(t *testing.T) { _, err = dbConnectionPool.ExecContext(ctx, "INSERT INTO accounts (stellar_address) VALUES ($1)", types.AddressBytea(address)) require.NoError(t, err) - // Create test transactions first + // Create test transactions first (hash is BYTEA) + testHash1 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000001") + testHash2 := types.HashBytea("0000000000000000000000000000000000000000000000000000000000000002") _, err = dbConnectionPool.ExecContext(ctx, ` INSERT INTO transactions (hash, to_id, envelope_xdr, fee_charged, result_code, meta_xdr, ledger_number, ledger_created_at, is_fee_bump) VALUES - ('tx1', 1, 'env1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $1, false), - ('tx2', 2, 'env2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $1, true) - `, now) + ($2, 1, 'env1', 100, 'TransactionResultCodeTxSuccess', 'meta1', 1, $1, false), + ($3, 2, 'env2', 200, 'TransactionResultCodeTxSuccess', 'meta2', 2, $1, true) + `, now, testHash1, testHash2) require.NoError(t, err) // Create test state changes for to_id=1 (multiple state_change_orders) diff --git a/internal/serve/graphql/resolvers/account_resolvers_test.go b/internal/serve/graphql/resolvers/account_resolvers_test.go index 9107b53a..6db5c8aa 100644 --- a/internal/serve/graphql/resolvers/account_resolvers_test.go +++ b/internal/serve/graphql/resolvers/account_resolvers_test.go @@ -39,10 +39,10 @@ func TestAccountResolver_Transactions(t *testing.T) { require.NoError(t, err) require.Len(t, transactions.Edges, 4) - assert.Equal(t, "tx1", transactions.Edges[0].Node.Hash) - assert.Equal(t, "tx2", transactions.Edges[1].Node.Hash) - assert.Equal(t, "tx3", transactions.Edges[2].Node.Hash) - assert.Equal(t, "tx4", transactions.Edges[3].Node.Hash) + assert.Equal(t, testTxHash1, transactions.Edges[0].Node.Hash.String()) + assert.Equal(t, testTxHash2, transactions.Edges[1].Node.Hash.String()) + assert.Equal(t, testTxHash3, transactions.Edges[2].Node.Hash.String()) + assert.Equal(t, testTxHash4, transactions.Edges[3].Node.Hash.String()) mockMetricsService.AssertExpectations(t) }) @@ -52,8 +52,8 @@ func TestAccountResolver_Transactions(t *testing.T) { txs, err := resolver.Transactions(ctx, parentAccount, &first, nil, nil, nil) require.NoError(t, err) assert.Len(t, txs.Edges, 2) - assert.Equal(t, "tx1", txs.Edges[0].Node.Hash) - assert.Equal(t, "tx2", txs.Edges[1].Node.Hash) + assert.Equal(t, testTxHash1, txs.Edges[0].Node.Hash.String()) + assert.Equal(t, testTxHash2, txs.Edges[1].Node.Hash.String()) assert.True(t, txs.PageInfo.HasNextPage) assert.False(t, txs.PageInfo.HasPreviousPage) @@ -63,8 +63,8 @@ func TestAccountResolver_Transactions(t *testing.T) { txs, err = resolver.Transactions(ctx, parentAccount, &first, nextCursor, nil, nil) require.NoError(t, err) assert.Len(t, txs.Edges, 2) - assert.Equal(t, "tx3", txs.Edges[0].Node.Hash) - assert.Equal(t, "tx4", txs.Edges[1].Node.Hash) + assert.Equal(t, testTxHash3, txs.Edges[0].Node.Hash.String()) + assert.Equal(t, testTxHash4, txs.Edges[1].Node.Hash.String()) assert.False(t, txs.PageInfo.HasNextPage) assert.True(t, txs.PageInfo.HasPreviousPage) mockMetricsService.AssertExpectations(t) @@ -76,8 +76,8 @@ func TestAccountResolver_Transactions(t *testing.T) { txs, err := resolver.Transactions(ctx, parentAccount, nil, nil, &last, nil) require.NoError(t, err) assert.Len(t, txs.Edges, 2) - assert.Equal(t, "tx3", txs.Edges[0].Node.Hash) - assert.Equal(t, "tx4", txs.Edges[1].Node.Hash) + assert.Equal(t, testTxHash3, txs.Edges[0].Node.Hash.String()) + assert.Equal(t, testTxHash4, txs.Edges[1].Node.Hash.String()) assert.False(t, txs.PageInfo.HasNextPage) assert.True(t, txs.PageInfo.HasPreviousPage) @@ -88,7 +88,7 @@ func TestAccountResolver_Transactions(t *testing.T) { txs, err = resolver.Transactions(ctx, parentAccount, nil, nil, &last, nextCursor) require.NoError(t, err) assert.Len(t, txs.Edges, 1) - assert.Equal(t, "tx2", txs.Edges[0].Node.Hash) + assert.Equal(t, testTxHash2, txs.Edges[0].Node.Hash.String()) assert.True(t, txs.PageInfo.HasNextPage) assert.True(t, txs.PageInfo.HasPreviousPage) @@ -98,7 +98,7 @@ func TestAccountResolver_Transactions(t *testing.T) { txs, err = resolver.Transactions(ctx, parentAccount, nil, nil, &last, nextCursor) require.NoError(t, err) assert.Len(t, txs.Edges, 1) - assert.Equal(t, "tx1", txs.Edges[0].Node.Hash) + assert.Equal(t, testTxHash1, txs.Edges[0].Node.Hash.String()) assert.True(t, txs.PageInfo.HasNextPage) assert.False(t, txs.PageInfo.HasPreviousPage) mockMetricsService.AssertExpectations(t) @@ -518,7 +518,7 @@ func TestAccountResolver_StateChanges_WithFilters(t *testing.T) { t.Run("filter by transaction hash only", func(t *testing.T) { ctx := getTestCtx("state_changes", []string{""}) - txHash := "tx1" + txHash := testTxHash1 filter := &graphql1.AccountStateChangeFilterInput{ TransactionHash: &txHash, } @@ -591,7 +591,7 @@ func TestAccountResolver_StateChanges_WithFilters(t *testing.T) { t.Run("filter by both transaction hash and operation ID", func(t *testing.T) { ctx := getTestCtx("state_changes", []string{""}) - txHash := "tx2" + txHash := testTxHash2 opID := toid.New(1000, 2, 1).ToInt64() txToID := opID &^ 0xFFF // Derive transaction to_id from operation_id filter := &graphql1.AccountStateChangeFilterInput{ @@ -632,7 +632,7 @@ func TestAccountResolver_StateChanges_WithFilters(t *testing.T) { t.Run("filter with pagination", func(t *testing.T) { ctx := getTestCtx("state_changes", []string{""}) - txHash := "tx1" + txHash := testTxHash1 filter := &graphql1.AccountStateChangeFilterInput{ TransactionHash: &txHash, } @@ -766,7 +766,7 @@ func TestAccountResolver_StateChanges_WithCategoryReasonFilters(t *testing.T) { t.Run("filter with all filters - txHash, operationID, category, reason", func(t *testing.T) { ctx := getTestCtx("state_changes", []string{""}) - txHash := "tx1" + txHash := testTxHash1 opID := toid.New(1000, 1, 1).ToInt64() txToID := opID &^ 0xFFF // Derive transaction to_id from operation_id category := "BALANCE" diff --git a/internal/serve/graphql/resolvers/operation_resolvers_test.go b/internal/serve/graphql/resolvers/operation_resolvers_test.go index 241f02b7..fcb384c4 100644 --- a/internal/serve/graphql/resolvers/operation_resolvers_test.go +++ b/internal/serve/graphql/resolvers/operation_resolvers_test.go @@ -42,7 +42,7 @@ func TestOperationResolver_Transaction(t *testing.T) { require.NoError(t, err) require.NotNil(t, transaction) - assert.Equal(t, "tx1", transaction.Hash) + assert.Equal(t, testTxHash1, transaction.Hash.String()) }) t.Run("nil operation panics", func(t *testing.T) { diff --git a/internal/serve/graphql/resolvers/queries_resolvers_test.go b/internal/serve/graphql/resolvers/queries_resolvers_test.go index 2696b88b..bb139b9d 100644 --- a/internal/serve/graphql/resolvers/queries_resolvers_test.go +++ b/internal/serve/graphql/resolvers/queries_resolvers_test.go @@ -32,10 +32,10 @@ func TestQueryResolver_TransactionByHash(t *testing.T) { t.Run("success", func(t *testing.T) { ctx := getTestCtx("transactions", []string{"hash", "toId", "envelopeXdr", "feeCharged", "resultCode", "metaXdr", "ledgerNumber", "ledgerCreatedAt", "isFeeBump"}) - tx, err := resolver.TransactionByHash(ctx, "tx1") + tx, err := resolver.TransactionByHash(ctx, testTxHash1) require.NoError(t, err) - assert.Equal(t, "tx1", tx.Hash) + assert.Equal(t, testTxHash1, tx.Hash.String()) assert.Equal(t, toid.New(1000, 1, 0).ToInt64(), tx.ToID) require.NotNil(t, tx.EnvelopeXDR) assert.Equal(t, "envelope1", *tx.EnvelopeXDR) @@ -86,10 +86,10 @@ func TestQueryResolver_Transactions(t *testing.T) { require.NoError(t, err) require.Len(t, transactions.Edges, 4) - assert.Equal(t, "tx1", transactions.Edges[0].Node.Hash) - assert.Equal(t, "tx2", transactions.Edges[1].Node.Hash) - assert.Equal(t, "tx3", transactions.Edges[2].Node.Hash) - assert.Equal(t, "tx4", transactions.Edges[3].Node.Hash) + assert.Equal(t, testTxHash1, transactions.Edges[0].Node.Hash.String()) + assert.Equal(t, testTxHash2, transactions.Edges[1].Node.Hash.String()) + assert.Equal(t, testTxHash3, transactions.Edges[2].Node.Hash.String()) + assert.Equal(t, testTxHash4, transactions.Edges[3].Node.Hash.String()) }) t.Run("get transactions with first/after limit and cursor", func(t *testing.T) { @@ -98,8 +98,8 @@ func TestQueryResolver_Transactions(t *testing.T) { txs, err := resolver.Transactions(ctx, &first, nil, nil, nil) require.NoError(t, err) assert.Len(t, txs.Edges, 2) - assert.Equal(t, "tx1", txs.Edges[0].Node.Hash) - assert.Equal(t, "tx2", txs.Edges[1].Node.Hash) + assert.Equal(t, testTxHash1, txs.Edges[0].Node.Hash.String()) + assert.Equal(t, testTxHash2, txs.Edges[1].Node.Hash.String()) assert.True(t, txs.PageInfo.HasNextPage) assert.False(t, txs.PageInfo.HasPreviousPage) @@ -110,7 +110,7 @@ func TestQueryResolver_Transactions(t *testing.T) { txs, err = resolver.Transactions(ctx, &first, nextCursor, nil, nil) require.NoError(t, err) assert.Len(t, txs.Edges, 1) - assert.Equal(t, "tx3", txs.Edges[0].Node.Hash) + assert.Equal(t, testTxHash3, txs.Edges[0].Node.Hash.String()) assert.True(t, txs.PageInfo.HasNextPage) assert.True(t, txs.PageInfo.HasPreviousPage) @@ -121,7 +121,7 @@ func TestQueryResolver_Transactions(t *testing.T) { txs, err = resolver.Transactions(ctx, &first, nextCursor, nil, nil) require.NoError(t, err) assert.Len(t, txs.Edges, 1) - assert.Equal(t, "tx4", txs.Edges[0].Node.Hash) + assert.Equal(t, testTxHash4, txs.Edges[0].Node.Hash.String()) assert.False(t, txs.PageInfo.HasNextPage) assert.True(t, txs.PageInfo.HasPreviousPage) }) @@ -132,8 +132,8 @@ func TestQueryResolver_Transactions(t *testing.T) { txs, err := resolver.Transactions(ctx, nil, nil, &last, nil) require.NoError(t, err) assert.Len(t, txs.Edges, 2) - assert.Equal(t, "tx3", txs.Edges[0].Node.Hash) - assert.Equal(t, "tx4", txs.Edges[1].Node.Hash) + assert.Equal(t, testTxHash3, txs.Edges[0].Node.Hash.String()) + assert.Equal(t, testTxHash4, txs.Edges[1].Node.Hash.String()) assert.False(t, txs.PageInfo.HasNextPage) assert.True(t, txs.PageInfo.HasPreviousPage) @@ -144,7 +144,7 @@ func TestQueryResolver_Transactions(t *testing.T) { txs, err = resolver.Transactions(ctx, nil, nil, &last, nextCursor) require.NoError(t, err) assert.Len(t, txs.Edges, 1) - assert.Equal(t, "tx2", txs.Edges[0].Node.Hash) + assert.Equal(t, testTxHash2, txs.Edges[0].Node.Hash.String()) assert.True(t, txs.PageInfo.HasNextPage) assert.True(t, txs.PageInfo.HasPreviousPage) @@ -154,7 +154,7 @@ func TestQueryResolver_Transactions(t *testing.T) { txs, err = resolver.Transactions(ctx, nil, nil, &last, nextCursor) require.NoError(t, err) assert.Len(t, txs.Edges, 1) - assert.Equal(t, "tx1", txs.Edges[0].Node.Hash) + assert.Equal(t, testTxHash1, txs.Edges[0].Node.Hash.String()) assert.True(t, txs.PageInfo.HasNextPage) assert.False(t, txs.PageInfo.HasPreviousPage) }) diff --git a/internal/serve/graphql/resolvers/statechange_resolvers_test.go b/internal/serve/graphql/resolvers/statechange_resolvers_test.go index 39a04468..8076e5ac 100644 --- a/internal/serve/graphql/resolvers/statechange_resolvers_test.go +++ b/internal/serve/graphql/resolvers/statechange_resolvers_test.go @@ -376,7 +376,7 @@ func TestStateChangeResolver_Transaction(t *testing.T) { tx, err := resolver.Transaction(ctx, &parentSC) require.NoError(t, err) - assert.Equal(t, "tx1", tx.Hash) + assert.Equal(t, testTxHash1, tx.Hash.String()) }) t.Run("nil state change panics", func(t *testing.T) { diff --git a/internal/serve/graphql/resolvers/test_utils.go b/internal/serve/graphql/resolvers/test_utils.go index 1897e27c..e1d69c2c 100644 --- a/internal/serve/graphql/resolvers/test_utils.go +++ b/internal/serve/graphql/resolvers/test_utils.go @@ -54,6 +54,14 @@ var sharedTestAccountAddress = keypair.MustRandom().Address() // sharedNonExistentAccountAddress is a valid Stellar address that doesn't exist in the test DB. var sharedNonExistentAccountAddress = keypair.MustRandom().Address() +// Test transaction hashes used by setupDB (64-char hex strings for BYTEA storage) +var ( + testTxHash1 = "3476b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48770" + testTxHash2 = "3476b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48771" + testTxHash3 = "3476b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48772" + testTxHash4 = "3476b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48773" +) + func setupDB(ctx context.Context, t *testing.T, dbConnectionPool db.ConnectionPool) { testLedger := int32(1000) parentAccount := &types.Account{StellarAddress: types.AddressBytea(sharedTestAccountAddress)} diff --git a/internal/services/ingest_test.go b/internal/services/ingest_test.go index abdf6789..84884ec1 100644 --- a/internal/services/ingest_test.go +++ b/internal/services/ingest_test.go @@ -43,6 +43,23 @@ var ( const ( defaultGetLedgersLimit = 50 + // Test hash constants for ingest tests (64-char hex strings for BYTEA storage) + flushTxHash1 = "f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f101" + flushTxHash2 = "f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f202" + flushTxHash3 = "f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f303" + flushTxHash4 = "f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f404" + flushTxHash5 = "f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f505" + flushTxHash6 = "f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f606" + catchupTxHash1 = "c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c101" + catchupTxHash2 = "c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c202" + catchupTxHash3 = "c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c303" + catchupTxHash4 = "c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c404" + catchupTxHash5 = "c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c505" + catchupTxHash6 = "c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c606" + prevTxHash = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + txHash1 = "1111111111111111111111111111111111111111111111111111111111111111" + txHash2 = "2222222222222222222222222222222222222222222222222222222222222222" + // Test fixtures for ledger metadata ledgerMetadataWith0Tx = "AAAAAQAAAACB7Zh2o0NTFwl1nvs7xr3SJ7w8PpwnSRb8QyG9k6acEwAAABaeASPlzu/ZFxwyyWsxtGoj3KCrybm2yN7WOweR0BWdLYjyoO5BI41g1PFT+iHW68giP49Koo+q3VmH8I4GdtW2AAAAAGhTTB8AAAAAAAAAAQAAAAC1XRCyu30oTtXAOkel4bWQyQ9Xg1VHHMRQe76CBNI8iwAAAEDSH4sE7cL7UJyOqUo9ZZeNqPT7pt7su8iijHjWYg4MbeFUh/gkGf6N40bZjP/dlIuGXmuEhWoEX0VTV58xOB4C3z9hmASpL9tAVxktxD3XSOp3itxSvEmM6AUkwBS4ERm+pITz+1V1m+3/v6eaEKglCnon3a5xkn02sLltJ9CSzwAAEYIN4Lazp2QAAAAAAAMtYtQzAAAAAAAAAAAAAAAMAAAAZABMS0AAAADIXukLfWC53MCmzxKd/+LBbaYxQkgxATFDLI3hWj7EqWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGeASPlzu/ZFxwyyWsxtGoj3KCrybm2yN7WOweR0BWdLQAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9yHMAAAAAAAAAAA==" ledgerMetadataWith1Tx = "AAAAAQAAAAD8G2qemHnBKFkbq90RTagxAypNnA7DXDc63Giipq9mNwAAABYLEZ5DrTv6njXTOAFEdOO0yeLtJjCRyH4ryJkgpRh7VPJvwbisrc9A0yzFxxCdkICgB3Gv7qHOi8ZdsK2CNks2AAAAAGhTTAsAAAAAAAAAAQAAAACoJM0YvJ11Bk0pmltbrKQ7w6ovMmk4FT2ML5u1y23wMwAAAEAunZtorOSbnRpgnykoDe4kzAvLwNXefncy1R/1ynBWyDv0DfdnqJ6Hcy/0AJf6DkBZlRayg775h3HjV0GKF/oPua7l8wkLlJBtSk1kRDt55qSf6btSrgcupB/8bnpJfUUgZJ76saUrj29HukYHS1bq7SyuoCAY+5F9iBYTmW1G9QAAEX4N4Lazp2QAAAAAAAMtS3veAAAAAAAAAAAAAAAMAAAAZABMS0AAAADIXukLfWC53MCmzxKd/+LBbaYxQkgxATFDLI3hWj7EqWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAELEZ5DrTv6njXTOAFEdOO0yeLtJjCRyH4ryJkgpRh7VAAAAAIAAAAAAAAAAQAAAAAAAAABAAAAAAAAAGQAAAABAAAAAgAAAADg4mtiLKjJVgrmOpO9+Ff3XAmnycHyNUKu/v9KhHevAAAAAGQAAA7FAAAAGgAAAAAAAAAAAAAAAQAAAAAAAAABAAAAALvqzdVyRxgBMcLzbw1wNWcJYHPNPok1GdVSgmy4sjR2AAAAAVVTREMAAAAA4OJrYiyoyVYK5jqTvfhX91wJp8nB8jVCrv7/SoR3rwAAAAACVAvkAAAAAAAAAAABhHevAAAAAEDq2yIDzXUoLboBHQkbr8U2oKqLzf0gfpwXbmRPLB6Ek3G8uCEYyry1vt5Sb+LCEd81fefFQcQN0nydr1FmiXcDAAAAAAAAAAAAAAABXFSiWcxpDRa8frBs1wbEaMUw4hMe7ctFtdw3Ci73IEwAAAAAAAAAZAAAAAAAAAABAAAAAAAAAAEAAAAAAAAAAAAAAAIAAAADAAARfQAAAAAAAAAA4OJrYiyoyVYK5jqTvfhX91wJp8nB8jVCrv7/SoR3rwAAAAAukO3GPAAADsUAAAAZAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAwAAAAAAABF9AAAAAGhTTAYAAAAAAAAAAQAAEX4AAAAAAAAAAODia2IsqMlWCuY6k734V/dcCafJwfI1Qq7+/0qEd68AAAAALpDtxdgAAA7FAAAAGQAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAMAAAAAAAARfQAAAABoU0wGAAAAAAAAAAMAAAAAAAAAAgAAAAMAABF+AAAAAAAAAADg4mtiLKjJVgrmOpO9+Ff3XAmnycHyNUKu/v9KhHevAAAAAC6Q7cXYAAAOxQAAABkAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAADAAAAAAAAEX0AAAAAaFNMBgAAAAAAAAABAAARfgAAAAAAAAAA4OJrYiyoyVYK5jqTvfhX91wJp8nB8jVCrv7/SoR3rwAAAAAukO3F2AAADsUAAAAaAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAwAAAAAAABF+AAAAAGhTTAsAAAAAAAAAAQAAAAIAAAADAAARcwAAAAEAAAAAu+rN1XJHGAExwvNvDXA1Zwlgc80+iTUZ1VKCbLiyNHYAAAABVVNEQwAAAADg4mtiLKjJVgrmOpO9+Ff3XAmnycHyNUKu/v9KhHevAAAAAAlQL5AAf/////////8AAAABAAAAAAAAAAAAAAABAAARfgAAAAEAAAAAu+rN1XJHGAExwvNvDXA1Zwlgc80+iTUZ1VKCbLiyNHYAAAABVVNEQwAAAADg4mtiLKjJVgrmOpO9+Ff3XAmnycHyNUKu/v9KhHevAAAAAAukO3QAf/////////8AAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8RxEAAAAAAAAAAA==" @@ -1152,8 +1169,8 @@ func Test_ingestService_flushBatchBufferWithRetry(t *testing.T) { name: "flush_with_data_inserts_to_database", setupBuffer: func() *indexer.IndexerBuffer { buf := indexer.NewIndexerBuffer() - tx1 := createTestTransaction("flush_tx_1", 1) - tx2 := createTestTransaction("flush_tx_2", 2) + tx1 := createTestTransaction(flushTxHash1, 1) + tx2 := createTestTransaction(flushTxHash2, 2) op1 := createTestOperation(200) op2 := createTestOperation(201) sc1 := createTestStateChange(1, testAddr1, 200) @@ -1173,13 +1190,13 @@ func Test_ingestService_flushBatchBufferWithRetry(t *testing.T) { wantTxCount: 2, wantOpCount: 2, wantStateChangeCount: 2, - txHashes: []string{"flush_tx_1", "flush_tx_2"}, + txHashes: []string{flushTxHash1, flushTxHash2}, }, { name: "flush_with_cursor_update_to_lower_value", setupBuffer: func() *indexer.IndexerBuffer { buf := indexer.NewIndexerBuffer() - tx1 := createTestTransaction("flush_tx_3", 3) + tx1 := createTestTransaction(flushTxHash3, 3) buf.PushTransaction(testAddr1, tx1) return buf }, @@ -1189,13 +1206,13 @@ func Test_ingestService_flushBatchBufferWithRetry(t *testing.T) { wantTxCount: 1, wantOpCount: 0, wantStateChangeCount: 0, - txHashes: []string{"flush_tx_3"}, + txHashes: []string{flushTxHash3}, }, { name: "flush_with_cursor_update_to_higher_value_keeps_existing", setupBuffer: func() *indexer.IndexerBuffer { buf := indexer.NewIndexerBuffer() - tx1 := createTestTransaction("flush_tx_4", 4) + tx1 := createTestTransaction(flushTxHash4, 4) buf.PushTransaction(testAddr1, tx1) return buf }, @@ -1205,14 +1222,14 @@ func Test_ingestService_flushBatchBufferWithRetry(t *testing.T) { wantTxCount: 1, wantOpCount: 0, wantStateChangeCount: 0, - txHashes: []string{"flush_tx_4"}, + txHashes: []string{flushTxHash4}, }, { name: "flush_with_filtering_only_inserts_registered", setupBuffer: func() *indexer.IndexerBuffer { buf := indexer.NewIndexerBuffer() - tx1 := createTestTransaction("flush_tx_5", 5) // Registered participant - tx2 := createTestTransaction("flush_tx_6", 6) // No registered participant + tx1 := createTestTransaction(flushTxHash5, 5) // Registered participant + tx2 := createTestTransaction(flushTxHash6, 6) // No registered participant buf.PushTransaction(testAddr1, tx1) buf.PushTransaction(testAddrUnreg, tx2) @@ -1226,17 +1243,17 @@ func Test_ingestService_flushBatchBufferWithRetry(t *testing.T) { wantTxCount: 1, // Only tx1 wantOpCount: 0, wantStateChangeCount: 0, - txHashes: []string{"flush_tx_5"}, + txHashes: []string{flushTxHash5}, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - // Clean up test data from previous runs - for _, hash := range []string{"flush_tx_1", "flush_tx_2", "flush_tx_3", "flush_tx_4", "flush_tx_5", "flush_tx_6"} { - _, err = dbConnectionPool.ExecContext(ctx, `DELETE FROM state_changes WHERE to_id IN (SELECT to_id FROM transactions WHERE hash = $1)`, hash) + // Clean up test data from previous runs (using HashBytea for BYTEA column) + for _, hash := range []string{flushTxHash1, flushTxHash2, flushTxHash3, flushTxHash4, flushTxHash5, flushTxHash6} { + _, err = dbConnectionPool.ExecContext(ctx, `DELETE FROM state_changes WHERE to_id IN (SELECT to_id FROM transactions WHERE hash = $1)`, types.HashBytea(hash)) require.NoError(t, err) - _, err = dbConnectionPool.ExecContext(ctx, `DELETE FROM transactions WHERE hash = $1`, hash) + _, err = dbConnectionPool.ExecContext(ctx, `DELETE FROM transactions WHERE hash = $1`, types.HashBytea(hash)) require.NoError(t, err) } // Also clean up any orphan operations @@ -1364,8 +1381,8 @@ func Test_ingestService_filterParticipantData(t *testing.T) { enableParticipantFiltering: false, setupBuffer: func() *indexer.IndexerBuffer { buf := indexer.NewIndexerBuffer() - tx1 := createTestTransaction("tx_hash_1", 1) - tx2 := createTestTransaction("tx_hash_2", 2) + tx1 := createTestTransaction(txHash1, 1) + tx2 := createTestTransaction(txHash2, 2) op1 := createTestOperation(100) op2 := createTestOperation(101) sc1 := createTestStateChange(1, testAddr1, 100) @@ -1389,7 +1406,7 @@ func Test_ingestService_filterParticipantData(t *testing.T) { registeredAccounts: []string{testAddr1}, setupBuffer: func() *indexer.IndexerBuffer { buf := indexer.NewIndexerBuffer() - tx1 := createTestTransaction("tx_hash_1", 1) + tx1 := createTestTransaction(txHash1, 1) op1 := createTestOperation(100) sc1 := createTestStateChange(1, testAddr1, 100) @@ -1417,8 +1434,8 @@ func Test_ingestService_filterParticipantData(t *testing.T) { registeredAccounts: []string{testAddr1}, setupBuffer: func() *indexer.IndexerBuffer { buf := indexer.NewIndexerBuffer() - tx1 := createTestTransaction("tx_hash_1", 1) // Has registered - tx2 := createTestTransaction("tx_hash_2", 2) // No registered + tx1 := createTestTransaction(txHash1, 1) // Has registered + tx2 := createTestTransaction(txHash2, 2) // No registered op1 := createTestOperation(100) op2 := createTestOperation(101) @@ -1438,7 +1455,7 @@ func Test_ingestService_filterParticipantData(t *testing.T) { registeredAccounts: []string{}, setupBuffer: func() *indexer.IndexerBuffer { buf := indexer.NewIndexerBuffer() - tx1 := createTestTransaction("tx_hash_1", 1) + tx1 := createTestTransaction(txHash1, 1) buf.PushTransaction(testAddrUnreg, tx1) return buf }, @@ -1452,7 +1469,7 @@ func Test_ingestService_filterParticipantData(t *testing.T) { registeredAccounts: []string{testAddr1, testAddr2}, setupBuffer: func() *indexer.IndexerBuffer { buf := indexer.NewIndexerBuffer() - tx1 := createTestTransaction("tx_hash_1", 1) + tx1 := createTestTransaction(txHash1, 1) op1 := createTestOperation(100) // 3 state changes: 2 for registered accounts, 1 for unregistered @@ -2650,7 +2667,7 @@ func Test_ingestService_flushBatchBuffer_batchChanges(t *testing.T) { name: "collects_trustline_changes_when_batchChanges_provided", setupBuffer: func() *indexer.IndexerBuffer { buf := indexer.NewIndexerBuffer() - tx1 := createTestTransaction("catchup_tx_1", 1) + tx1 := createTestTransaction(catchupTxHash1, 1) buf.PushTransaction(testAddr1, tx1) buf.PushTrustlineChange(types.TrustlineChange{ AccountID: testAddr1, @@ -2669,7 +2686,7 @@ func Test_ingestService_flushBatchBuffer_batchChanges(t *testing.T) { name: "collects_contract_changes_when_batchChanges_provided", setupBuffer: func() *indexer.IndexerBuffer { buf := indexer.NewIndexerBuffer() - tx1 := createTestTransaction("catchup_tx_2", 2) + tx1 := createTestTransaction(catchupTxHash2, 2) buf.PushTransaction(testAddr2, tx1) buf.PushContractChange(types.ContractChange{ AccountID: testAddr2, @@ -2696,7 +2713,7 @@ func Test_ingestService_flushBatchBuffer_batchChanges(t *testing.T) { name: "nil_batchChanges_does_not_collect", setupBuffer: func() *indexer.IndexerBuffer { buf := indexer.NewIndexerBuffer() - tx1 := createTestTransaction("catchup_tx_5", 5) + tx1 := createTestTransaction(catchupTxHash5, 5) buf.PushTransaction(testAddr1, tx1) buf.PushTrustlineChange(types.TrustlineChange{ AccountID: testAddr1, @@ -2715,7 +2732,7 @@ func Test_ingestService_flushBatchBuffer_batchChanges(t *testing.T) { name: "accumulates_across_multiple_flushes", setupBuffer: func() *indexer.IndexerBuffer { buf := indexer.NewIndexerBuffer() - tx1 := createTestTransaction("catchup_tx_6", 6) + tx1 := createTestTransaction(catchupTxHash6, 6) buf.PushTransaction(testAddr1, tx1) buf.PushTrustlineChange(types.TrustlineChange{ AccountID: testAddr1, @@ -2741,11 +2758,11 @@ func Test_ingestService_flushBatchBuffer_batchChanges(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - // Clean up test data from previous runs - for _, hash := range []string{"catchup_tx_1", "catchup_tx_2", "catchup_tx_3", "catchup_tx_4", "catchup_tx_5", "catchup_tx_6", "prev_tx"} { - _, err = dbConnectionPool.ExecContext(ctx, `DELETE FROM state_changes WHERE to_id IN (SELECT to_id FROM transactions WHERE hash = $1)`, hash) + // Clean up test data from previous runs (using HashBytea for BYTEA column) + for _, hash := range []string{catchupTxHash1, catchupTxHash2, catchupTxHash3, catchupTxHash4, catchupTxHash5, catchupTxHash6, prevTxHash} { + _, err = dbConnectionPool.ExecContext(ctx, `DELETE FROM state_changes WHERE to_id IN (SELECT to_id FROM transactions WHERE hash = $1)`, types.HashBytea(hash)) require.NoError(t, err) - _, err = dbConnectionPool.ExecContext(ctx, `DELETE FROM transactions WHERE hash = $1`, hash) + _, err = dbConnectionPool.ExecContext(ctx, `DELETE FROM transactions WHERE hash = $1`, types.HashBytea(hash)) require.NoError(t, err) } // Also clean up any orphan operations From 5058261c6e3bff46d9ae6628ef760d6e887676ff Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 5 Feb 2026 13:09:19 -0500 Subject: [PATCH 13/14] fix more tests --- .../serve/graphql/resolvers/account_resolvers_test.go | 3 ++- internal/serve/graphql/resolvers/test_utils.go | 11 ++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/internal/serve/graphql/resolvers/account_resolvers_test.go b/internal/serve/graphql/resolvers/account_resolvers_test.go index 6db5c8aa..470287f2 100644 --- a/internal/serve/graphql/resolvers/account_resolvers_test.go +++ b/internal/serve/graphql/resolvers/account_resolvers_test.go @@ -618,7 +618,8 @@ func TestAccountResolver_StateChanges_WithFilters(t *testing.T) { t.Run("filter with no matching results", func(t *testing.T) { ctx := getTestCtx("state_changes", []string{""}) - txHash := "non-existent-tx" + // Use a valid 64-char hex hash that doesn't exist in the test DB + txHash := "0000000000000000000000000000000000000000000000000000000000000000" filter := &graphql1.AccountStateChangeFilterInput{ TransactionHash: &txHash, } diff --git a/internal/serve/graphql/resolvers/test_utils.go b/internal/serve/graphql/resolvers/test_utils.go index e1d69c2c..1e71dfb0 100644 --- a/internal/serve/graphql/resolvers/test_utils.go +++ b/internal/serve/graphql/resolvers/test_utils.go @@ -55,11 +55,12 @@ var sharedTestAccountAddress = keypair.MustRandom().Address() var sharedNonExistentAccountAddress = keypair.MustRandom().Address() // Test transaction hashes used by setupDB (64-char hex strings for BYTEA storage) +// These must match the pattern in setupDB: fmt.Sprintf("...487%x", i) where i = 0,1,2,3 var ( - testTxHash1 = "3476b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48770" - testTxHash2 = "3476b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48771" - testTxHash3 = "3476b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48772" - testTxHash4 = "3476b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa48773" + testTxHash1 = "3476b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4870" + testTxHash2 = "3476b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4871" + testTxHash3 = "3476b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4872" + testTxHash4 = "3476b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4873" ) func setupDB(ctx context.Context, t *testing.T, dbConnectionPool db.ConnectionPool) { @@ -70,7 +71,7 @@ func setupDB(ctx context.Context, t *testing.T, dbConnectionPool db.ConnectionPo opIdx := 1 for i := range 4 { txn := &types.Transaction{ - Hash: types.HashBytea(fmt.Sprintf("3476b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa4877%x", i)), + Hash: types.HashBytea(fmt.Sprintf("3476b7b0133690fbfb2de8fa9ca2273cb4f2e29447e0cf0e14a5f82d0daa487%x", i)), ToID: toid.New(testLedger, int32(i+1), 0).ToInt64(), EnvelopeXDR: ptr(fmt.Sprintf("envelope%d", i+1)), FeeCharged: int64(100 * (i + 1)), From 6477fa96554bde8a0d35af400841f75acbc28dcd Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 5 Feb 2026 14:14:51 -0500 Subject: [PATCH 14/14] Update ingest_test.go --- internal/services/ingest_test.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/internal/services/ingest_test.go b/internal/services/ingest_test.go index 84884ec1..aac607e1 100644 --- a/internal/services/ingest_test.go +++ b/internal/services/ingest_test.go @@ -1327,10 +1327,16 @@ func Test_ingestService_flushBatchBufferWithRetry(t *testing.T) { // Verify transaction count in database if len(tc.txHashes) > 0 { + hashBytes := make([][]byte, len(tc.txHashes)) + for i, h := range tc.txHashes { + val, err := types.HashBytea(h).Value() + require.NoError(t, err) + hashBytes[i] = val.([]byte) + } var txCount int err = dbConnectionPool.GetContext(ctx, &txCount, `SELECT COUNT(*) FROM transactions WHERE hash = ANY($1)`, - pq.Array(tc.txHashes)) + pq.Array(hashBytes)) require.NoError(t, err) assert.Equal(t, tc.wantTxCount, txCount, "transaction count mismatch") } @@ -1346,10 +1352,16 @@ func Test_ingestService_flushBatchBufferWithRetry(t *testing.T) { // Verify state change count in database if tc.wantStateChangeCount > 0 { + scHashBytes := make([][]byte, len(tc.txHashes)) + for i, h := range tc.txHashes { + val, err := types.HashBytea(h).Value() + require.NoError(t, err) + scHashBytes[i] = val.([]byte) + } var scCount int err = dbConnectionPool.GetContext(ctx, &scCount, `SELECT COUNT(*) FROM state_changes WHERE to_id IN (SELECT to_id FROM transactions WHERE hash = ANY($1))`, - pq.Array(tc.txHashes)) + pq.Array(scHashBytes)) require.NoError(t, err) assert.Equal(t, tc.wantStateChangeCount, scCount, "state change count mismatch") }