Skip to content

Solution/mritunjay kumar - #119

Open
mritunjaysingh22 wants to merge 3 commits into
Robustrade:mainfrom
mritunjaysingh22:solution/mritunjay-kumar
Open

Solution/mritunjay kumar#119
mritunjaysingh22 wants to merge 3 commits into
Robustrade:mainfrom
mritunjaysingh22:solution/mritunjay-kumar

Conversation

@mritunjaysingh22

Copy link
Copy Markdown

Summary

Implemented a wallet transfer service with a strong focus on correctness, transactional integrity, idempotency, and concurrency safety using PostgreSQL and Go.

The solution provides:

  • Atomic wallet-to-wallet transfers using PostgreSQL transactions.
  • Idempotent transfer processing using a unique idempotency key.
  • Protection against race conditions and double spending using row-level locking.
  • Ledger entries for debit and credit operations.
  • Comprehensive integration tests covering successful transfers, validation, concurrency, idempotency, insufficient balance, and failure scenarios.

AI Disclosure

1. AI tools used

I used ChatGPT (GPT-5.5) to assist with the implementation and review of the assignment.

2. How I used the tool

The AI was used as an engineering assistant throughout development. Specifically, I used it to:

  • Review the database schema and SQL queries.
  • Validate the idempotency approach.
  • Review concurrency handling and deadlock prevention.
  • refine integration test scenarios.
  • Review code coverage and race detection.
  • Review edge cases and suggest improvements.

All design decisions, implementation, debugging, and final verification were completed and validated manually.

3. Prompts used

The AI was used with prompts covering topics such as:

  • Preventing deadlocks while locking multiple wallets
  • Idempotency implementation using unique keys
  • Go transaction management using pgx
  • Concurrent transfer testing
  • Review of overall implementation

Schema Design

The following tables were introduced:

wallets

Stores the current balance for each wallet.

Columns include:

  • id
  • balance
  • timestamps

transfers

Stores every transfer request.

Important constraints:

  • Primary key on id
  • Unique constraint on idempotency_key
  • Foreign keys referencing source and destination wallets

Status values:

  • PENDING
  • PROCESSED

ledger_entries

Stores immutable accounting records.

Each successful transfer creates:

  • one DEBIT entry
  • one CREDIT entry

Foreign key relationships ensure ledger entries always belong to a valid transfer and wallet.

Idempotency Strategy

Every transfer request requires an Idempotency-Key.

Processing flow:

  1. Check whether a transfer already exists with the supplied idempotency key.
  2. If found, return the existing transfer instead of processing again.
  3. Otherwise, continue with normal processing.
  4. The database enforces uniqueness using a unique constraint on idempotency_key.
  5. Concurrent duplicate requests are safely handled by the database constraint together with transaction logic, ensuring only one transfer is created.

This guarantees clients can safely retry requests without creating duplicate transfers.

Concurrency Strategy

The implementation prevents race conditions and double spending using PostgreSQL transactions and row-level locks.

Key techniques include:

  • Each transfer executes inside a single database transaction.
  • Wallet rows are locked using SELECT ... FOR UPDATE.
  • Wallets are always locked in a deterministic order (sorted by wallet ID) to prevent deadlocks.
  • Balance validation occurs after acquiring locks.
  • Balance updates, ledger creation, and transfer status updates occur within the same transaction.
  • The transaction commits only after every operation succeeds; otherwise it rolls back.

Additionally, retry logic is implemented for PostgreSQL serialization failures (40001) and deadlock errors (40P01).

How to Run

go mod download

goose up

go run ./cmd/server

Or:

make run

(if using the provided Makefile)

How to Test

Run all tests:

go test ./tests

Run with race detection:

go test -race ./tests

Generate service coverage:

go test ./tests \
    -coverpkg=./internal/service \
    -coverprofile=coverage.out

go tool cover -func=coverage.out

Tradeoffs / Assumptions

  • PostgreSQL is used as the source of truth for transactional consistency.
  • Balances are stored directly in the wallets table while ledger entries provide an immutable audit trail.
  • Retry logic is implemented for serialization failures and deadlocks, although deterministic automated testing of these PostgreSQL-specific scenarios would require mocking or fault injection.
  • The solution prioritizes correctness and consistency over maximum throughput.
  • Integration tests use a real PostgreSQL database instead of mocks to validate transactional behavior.

Checklist

  • Tests pass
  • Lint passes
  • Format check passes
  • README or notes updated
  • PR description explains schema, idempotency, and concurrency

Copilot AI review requested due to automatic review settings July 22, 2026 13:42

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Implements a Go/PostgreSQL wallet-to-wallet transfer service with transactional balance updates, idempotency, and integration tests, exposing the operation via an HTTP endpoint.

Changes:

  • Adds transactional transfer workflow (row locks, balance updates, ledger entries, retry on serialization/deadlock).
  • Introduces PostgreSQL schema + sqlc queries/models, plus Makefile targets for local workflows.
  • Adds integration tests covering success, validation, idempotency, concurrency, ledger correctness, and failure scenarios.

Reviewed changes

Copilot reviewed 21 out of 29 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
tests/transfer_service_test.go Integration tests for basic transfer flows and validations
tests/ledger_test.go Integration tests validating debit/credit ledger entry behavior
tests/idempotency_test.go Integration tests for idempotent retries and distinct keys
tests/helper_test.go Test harness: DB pool setup and helper functions
tests/failure_test.go Integration tests for failure/validation scenarios
tests/concurrency_test.go Integration tests for concurrent transfer behaviors
sqlc.yaml sqlc configuration for generating Go db layer from SQL
sql/queries/wallet.sql Wallet queries (including FOR UPDATE read + balance update)
sql/queries/transfer.sql Transfer queries (idempotency lookup + create + status update)
sql/queries/ledger.sql Ledger entry insert and query-by-transfer
sql/migrations/20260721105133_init_schema.sql Initial database schema (wallets, transfers, ledger_entries)
Makefile Local developer targets (build/run/migrate/test/coverage/etc.)
internal/service/transfer_service.go Core transfer workflow (tx, locks, idempotency, ledger, retries)
internal/routes/routes.go HTTP routing (health + POST /api/v1/transfers)
internal/logger/logger.go Placeholder logger package
internal/handler/transfer_handler.go HTTP handler for transfer creation and error mapping
internal/db/wallet.sql.go sqlc-generated wallet query bindings
internal/db/transfer.sql.go sqlc-generated transfer query bindings
internal/db/querier.go sqlc-generated Querier interface
internal/db/postgres.go Postgres pool creation + connectivity verification
internal/db/models.go sqlc-generated models/enums
internal/db/ledger.sql.go sqlc-generated ledger query bindings
internal/db/db.go sqlc DBTX abstraction + WithTx support
internal/config/config.go App config loading (env/.env)
cmd/server/main.go Application wiring: config, db, service, handler, router, server
.gitignore Ignore patterns for env/IDE/build artifacts
go.mod Module definition and dependencies
go.sum Dependency checksums
coverage.out Coverage profile artifact committed into the repo
Files not reviewed (6)
  • internal/db/db.go: Generated file
  • internal/db/ledger.sql.go: Generated file
  • internal/db/models.go: Generated file
  • internal/db/querier.go: Generated file
  • internal/db/transfer.sql.go: Generated file
  • internal/db/wallet.sql.go: Generated file

Comment thread Makefile

DB_URL=postgres://postgres:postgres@localhost:5432/wallet_transfer?sslmode=disable

GOOSE_DIR=sql/schema
Comment thread tests/concurrency_test.go
Comment on lines +243 to +251
req := service.TransferRequest{
IdempotencyKey: uuid.NewString(),
FromWalletID: from,
ToWalletID: to,
Amount: amount,
}

_, _ = transferService.CreateTransfer(ctx, req)

Comment thread tests/helper_test.go
Comment on lines +71 to +73
_, _ = testPool.Exec(ctx, "DELETE FROM ledger_entries")
_, _ = testPool.Exec(ctx, "DELETE FROM transfers")
_, _ = testPool.Exec(ctx, "DELETE FROM wallets")
Comment on lines +148 to +159
// defer func() {
// if p := recover(); p != nil {
// _ = tx.Rollback(ctx)
// panic(p)
// }

// // Safe to call even after Commit()
// if err != nil {
// _ = tx.Rollback(ctx)
// }
// }()

Comment on lines +231 to +234
existing, err := s.queries.GetTransferByIdempotencyKey(
ctx,
req.IdempotencyKey,
)
Comment on lines +43 to +66
case errors.Is(err, service.ErrInvalidAmount):
writeError(
w,
http.StatusBadRequest,
err.Error(),
)
return

case errors.Is(err, service.ErrSameWallet):
writeError(
w,
http.StatusBadRequest,
err.Error(),
)
return

case errors.Is(err, service.ErrInsufficientBalance):
writeError(
w,
http.StatusConflict,
err.Error(),
)
return

Comment on lines +21 to +30
CREATE TABLE transfers (
id UUID PRIMARY KEY,
idempotency_key VARCHAR(255) NOT NULL UNIQUE,
from_wallet_id UUID NOT NULL REFERENCES wallets(id),
to_wallet_id UUID NOT NULL REFERENCES wallets(id),
amount BIGINT NOT NULL CHECK (amount > 0),
status transfer_status NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Comment on lines +44 to +46
CREATE INDEX idx_transfer_idempotency
ON transfers(idempotency_key);

Comment thread coverage.out
Comment on lines +1 to +2
mode: set
github.com/mritunjaysingh22/wallet-transfer-assignment/internal/service/transfer_service.go:51.20,57.2 1 1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants