Skip to content

Conversation

@Kosench
Copy link
Contributor

@Kosench Kosench commented Oct 21, 2025

Challenge 7 Solution

Submitted by: @Kosench
Challenge: Challenge 7

Description

This PR contains my solution for Challenge 7.

Changes

  • Added solution file to challenge-7/submissions/Kosench/solution-template.go

Testing

  • Solution passes all test cases
  • Code follows Go best practices

Thank you for reviewing my submission! 🚀

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 21, 2025

Walkthrough

This PR introduces a new Go file implementing a BankAccount system with public types for accounts and specific error conditions. It provides constructor, deposit, withdrawal, and transfer methods with input validation, balance constraints, and mutex-based concurrency control to prevent race conditions and deadlocks.

Changes

Cohort / File(s) Summary
BankAccount System Implementation
challenge-7/submissions/Kosench/solution-template.go
Adds BankAccount struct with NewBankAccount constructor, Deposit/Withdraw/Transfer methods, specific error types (AccountError, InsufficientFundsError, NegativeAmountError, ExceedsLimitError), input validations, mutex-protected balance updates, and deadlock-avoiding lock ordering during transfers.

Sequence Diagram(s)

sequenceDiagram participant Client participant Account_A participant Account_B Client->>Account_A: Transfer(amount, Account_B) Note over Account_A: Validate amount<br/>(positive, within limit) rect rgb(200, 220, 255) Note over Account_A,Account_B: Deadlock-avoiding locking<br/>(ordered by ID) Account_A->>Account_A: Lock (lower ID) Account_A->>Account_B: Lock (higher ID) end rect rgb(220, 240, 220) Account_A->>Account_A: Check sufficient funds alt Insufficient Balance Account_A->>Account_A: Unlock both Account_A-->>Client: InsufficientFundsError else Sufficient Balance Account_A->>Account_A: Deduct from source Account_A->>Account_B: Add to target Note over Account_A,Account_B: Atomic update end end Account_A->>Account_B: Unlock (higher ID) Account_A->>Account_A: Unlock (lower ID) Account_A-->>Client: nil or error 
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

The review requires understanding multiple interconnected error types, validation logic across three related methods, and careful verification of the concurrency control pattern—particularly the deadlock-avoiding lock ordering during transfers and atomic balance updates.

Possibly related PRs

  • Add solution for Challenge 7 by RezaSi #555: Implements nearly identical BankAccount system with the same types, constructor, Deposit/Withdraw/Transfer methods, mutex-based concurrency, MaxTransactionAmount constant, and error handling patterns.

Pre-merge checks

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "Add solution for Challenge 7 by Kosench" is directly related to the main change in the changeset, which adds a new BankAccount solution implementation to the challenge-7 submissions directory. The title clearly identifies the primary action (adding a solution), specifies which challenge is being addressed, and credits the contributor, making it concise, specific, and easily understandable when scanning commit history.
Description Check ✅ Passed The pull request description is directly related to the changeset, explicitly stating that this PR contains the submitter's solution for Challenge 7 and specifically identifying the file path where the solution was added: challenge-7/submissions/Kosench/solution-template.go. The description provides relevant context about the submission, including confirmation that the solution passes test cases and follows Go best practices, all of which align with the actual changes present in the pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (7)
challenge-7/submissions/Kosench/solution-template.go (7)

9-15: Avoid exposing fields and copying a type with sync.Mutex.

Exported fields allow unsynchronized mutation and the struct is copyable (contains a Mutex). Prefer unexported fields + accessors or at least add a prominent “do not copy” doc comment.


37-42: Clarify InsufficientFundsError message (requested vs shortfall).

“needed: $%.2f” is ambiguous across operations. Consider formatting based on Operation and include shortfall for clarity.

Apply:

 func (e *InsufficientFundsError) Error() string { - return fmt.Sprintf( - "account %s: insufficient funds (balance: $%.2f, needed: $%.2f, min: $%.2f)", - e.AccountID, e.Balance, e.Amount, e.MinBalance, - ) + if e.Operation == "create" { + short := e.MinBalance - e.Balance + if short < 0 { short = 0 } + return fmt.Sprintf( + "account %s: opening balance $%.2f below minimum $%.2f (short by $%.2f)", + e.AccountID, e.Balance, e.MinBalance, short, + ) + } + short := e.MinBalance - (e.Balance - e.Amount) + if short < 0 { short = 0 } + return fmt.Sprintf( + "account %s: insufficient funds (balance: $%.2f, requested: $%.2f, min: $%.2f, short by $%.2f)", + e.AccountID, e.Balance, e.Amount, e.MinBalance, short, + ) }

4-7: Reject non‑finite deposit amounts.

Guard against NaN/Inf to avoid poisoning state.

 import ( - "sync" "fmt" + "math" + "sync" ) @@ func (a *BankAccount) Deposit(amount float64) error { a.mu.Lock() defer a.mu.Unlock() + if math.IsNaN(amount) || math.IsInf(amount, 0) { + return &AccountError{AccountID: a.ID, Operation: "deposit"} + }

Also applies to: 124-151


153-192: Reject non‑finite withdrawal amounts.

Same guard as Deposit.

 func (a *BankAccount) Withdraw(amount float64) error { a.mu.Lock() defer a.mu.Unlock() + if math.IsNaN(amount) || math.IsInf(amount, 0) { + return &AccountError{AccountID: a.ID, Operation: "withdraw"} + }

244-244: Optional: concurrency‑safe balance accessor.

Expose a read method to avoid racy external reads of Balance.

+// BalanceSnapshot returns the current balance under lock. +func (a *BankAccount) BalanceSnapshot() float64 { + a.mu.Lock() + defer a.mu.Unlock() + return a.Balance +}

12-13: Money amounts as float64.

For financial values, prefer fixed‑precision (e.g., int64 cents or decimal) to avoid rounding drift.


21-28: Generic AccountError for invalid id/owner.

Consider specific error types/messages (e.g., EmptyIDError, EmptyOwnerError) to aid callers.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 464b940 and c460079.

📒 Files selected for processing (1)
  • challenge-7/submissions/Kosench/solution-template.go (1 hunks)
Comment on lines +104 to +114
if initialBalance < minBalance {
return nil, &InsufficientFundsError{
AccountError: AccountError{
AccountID: id,
Operation: "create",
},
Balance: initialBalance,
Amount: minBalance,
MinBalance: minBalance,
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Ctor sets Amount to minBalance; use shortfall instead.

Amount should reflect the additional funds needed when creating the account.

- Balance: initialBalance, - Amount: minBalance, - MinBalance: minBalance, + Balance: initialBalance, + Amount: minBalance - initialBalance, // shortfall + MinBalance: minBalance,
🤖 Prompt for AI Agents
In challenge-7/submissions/Kosench/solution-template.go around lines 104 to 114, the InsufficientFundsError constructed on account creation sets Amount to minBalance instead of the shortfall; change Amount to the difference (minBalance - initialBalance) so it represents the additional funds required (ensure it remains a non-negative value), leaving MinBalance as minBalance and Balance as initialBalance. 
Comment on lines +194 to +205
func (a *BankAccount) Transfer(amount float64, target *BankAccount) error {
if a.ID < target.ID {
a.mu.Lock()
defer a.mu.Unlock()
target.mu.Lock()
defer target.mu.Unlock()
} else {
target.mu.Lock()
defer target.mu.Unlock()
a.mu.Lock()
defer a.mu.Unlock()
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix nil‑target panic and self‑transfer deadlock; enforce stable lock order and validate early.

  • Panic: target.ID is read before nil check.
  • Deadlock: a==target attempts to lock the same mutex twice.
  • Equal IDs across opposite transfers can lock in opposite orders.
 func (a *BankAccount) Transfer(amount float64, target *BankAccount) error { - if a.ID < target.ID { - a.mu.Lock() - defer a.mu.Unlock() - target.mu.Lock() - defer target.mu.Unlock() - } else { - target.mu.Lock() - defer target.mu.Unlock() - a.mu.Lock() - defer a.mu.Unlock() - } + // Pre-lock validations + if target == nil { + return &AccountError{AccountID: a.ID, Operation: "transfer(nil)"} + } + if a == target { + return &AccountError{AccountID: a.ID, Operation: "transfer(self)"} + } + if amount < 0 { + return &NegativeAmountError{ + AccountError: AccountError{AccountID: a.ID, Operation: "transfer"}, + Amount: amount, + } + } + if amount > MaxTransactionAmount { + return &ExceedsLimitError{ + AccountError: AccountError{AccountID: a.ID, Operation: "transfer"}, + Amount: amount, + Limit: MaxTransactionAmount, + } + } + // Stable lock order: by ID, tie-break by address to avoid equal-ID deadlocks. + first, second := a, target + if a.ID > target.ID || (a.ID == target.ID && fmt.Sprintf("%p", a) > fmt.Sprintf("%p", target)) { + first, second = target, a + } + first.mu.Lock() + defer first.mu.Unlock() + second.mu.Lock() + defer second.mu.Unlock() - if amount < 0 { - return &NegativeAmountError{ ... } - } - if amount > MaxTransactionAmount { - return &ExceedsLimitError{ ... } - } if a.Balance-amount < a.MinBalance { return &InsufficientFundsError{ AccountError: AccountError{ AccountID: a.ID, Operation: "transfer", }, Balance: a.Balance, Amount: amount, MinBalance: a.MinBalance, } } a.Balance -= amount target.Balance += amount return nil }

Also applies to: 206-244

🤖 Prompt for AI Agents
In challenge-7/submissions/Kosench/solution-template.go around lines 194-205 (and also applies to 206-244): the Transfer method reads target.ID before checking for nil and may deadlock when a == target or when IDs are equal and two transfers use opposite ordering. First validate target != nil and return an error immediately; then if a == target return a no-op or error to avoid trying to lock the same mutex twice. To enforce a stable lock order, order locks by a stable key that cannot cause ambiguity (for example pointer address comparison or combine ID with pointer) and handle equal-key case by falling back to pointer comparison; acquire the first lock, then the second, and defer their unlocks. Ensure no code path attempts to lock the same mutex twice. 
Comment on lines +194 to +244
func (a *BankAccount) Transfer(amount float64, target *BankAccount) error {
if a.ID < target.ID {
a.mu.Lock()
defer a.mu.Unlock()
target.mu.Lock()
defer target.mu.Unlock()
} else {
target.mu.Lock()
defer target.mu.Unlock()
a.mu.Lock()
defer a.mu.Unlock()
}

if amount < 0 {
return &NegativeAmountError{
AccountError: AccountError{
AccountID: a.ID,
Operation: "transfer",
},
Amount: amount,
}
}

if amount > MaxTransactionAmount {
return &ExceedsLimitError{
AccountError: AccountError{
AccountID: a.ID,
Operation: "transfer",
},
Amount: amount,
Limit: MaxTransactionAmount,
}
}

if a.Balance-amount < a.MinBalance {
return &InsufficientFundsError{
AccountError: AccountError{
AccountID: a.ID,
Operation: "transfer",
},
Balance: a.Balance,
Amount: amount,
MinBalance: a.MinBalance,
}
}

a.Balance -= amount
target.Balance += amount

return nil
} No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verification: scan repo for self/nil transfers.

Run to spot potential problematic callsites.


🏁 Script executed:

#!/bin/bash # Find Transfer call sites with suspicious args rg -nP --type=go -C2 '\.\s*Transfer\s*\('

Length of output: 327


I need to examine the test cases more closely to determine if self-transfer or nil-transfer scenarios are tested or could be problematic.

 

🏁 Script executed:

#!/bin/bash # Find Transfer test cases and look for self-transfer or nil scenarios rg -nP --type=go -C5 'Transfer.*error|shouldError' challenge-7/solution-template_test.go | head -60

Length of output: 1658


🏁 Script executed:

#!/bin/bash # Check if implementation validates target parameter or guards against self-transfer ast-grep --pattern $'func (a *BankAccount) Transfer(amount float64, target *BankAccount) error {  $$$ }' 

Length of output: 10601


🏁 Script executed:

#!/bin/bash # Look for any test table definitions that might include self-transfer or nil cases rg -nP --type=go -B20 'source.Transfer' challenge-7/solution-template_test.go | grep -E '(tc|case|struct|Transfer)' | head -30

Length of output: 126


Let me check the Transfer test cases to see if these edge cases are tested:

 

🏁 Script executed:

#!/bin/bash # Find Transfer test cases with full context rg -nP --type=go -A100 'func.*TestTransfer' challenge-7/solution-template_test.go | grep -A80 'testCases.*:=' | head -100

Length of output: 2648


🏁 Script executed:

#!/bin/bash # Search for any test cases mentioning self, same, identical, or nil in transfer context rg -nP --type=go -i '(self|same|identical|nil).*[Tt]ransfer|[Tt]ransfer.*(self|same|identical|nil)' challenge-7/solution-template_test.go

Length of output: 0


🏁 Script executed:

#!/bin/bash # Check the complete Transfer test function sed -n '/func.*TestTransfer/,/^func /p' challenge-7/solution-template_test.go | head -150

Length of output: 2411


Add guard against self-transfer and nil target in Transfer method.

The implementation lacks validation for two critical edge cases:

  1. Self-transfer deadlock: When a.ID == target.ID, the else branch (lines 200-204) attempts to lock the same mutex twice, causing a deadlock since Go mutexes are non-reentrant.

  2. Nil target panic: Line 195 accesses target.ID without checking if target is nil, causing a panic.

Add checks at the start of Transfer (after line 194):

  • Return error if target == nil
  • Return error if a.ID == target.ID

Current test usage (distinct accounts) is safe, but the implementation should handle these edge cases defensively.

🤖 Prompt for AI Agents
In challenge-7/submissions/Kosench/solution-template.go around lines 194 to 244, add defensive checks at the start of Transfer: first verify target != nil and return an error if it is (so you don't dereference target.ID), then check if a.ID == target.ID and return an error to forbid self-transfers (this prevents attempting to lock the same mutex twice and deadlocking); keep the existing ID-based lock ordering only after these validations. Use an appropriate error return (existing error type in the package or fmt.Errorf) consistent with other methods. 
@RezaSi RezaSi merged commit 023fc02 into RezaSi:main Oct 23, 2025
5 checks passed
@Kosench Kosench deleted the challenge-7-Kosench-1761044953 branch November 1, 2025 12:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants