Skip to content

Fix uint64 underflow in sent amount calculation for commitment txs#172

Open
aruokhai wants to merge 6 commits into
masterfrom
fix_underflow_error
Open

Fix uint64 underflow in sent amount calculation for commitment txs#172
aruokhai wants to merge 6 commits into
masterfrom
fix_underflow_error

Conversation

@aruokhai
Copy link
Copy Markdown
Contributor

@aruokhai aruokhai commented May 11, 2026

Summary

Fixes #170

  • handleCommitmentTx computes the net sent amount by subtracting vtxosToAdd (our change outputs) from myVtxos (our spent inputs). Because both are uint64, if vtxosToAdd > myVtxos the result silently wraps to a huge number, recording a bogus TxSent entry.
  • Added pendingBoardingTxs amounts to totalIn so boarding inputs are correctly counted alongside spent vtxos.
  • Replaced the raw subtraction with a guarded totalIn > totalOut check before subtracting.

Boarding scenario (root cause highlighted)

The invariant myVtxos >= vtxosToAdd assumed this branch always represents a pure send, but it breaks whenever boarding and a vtxo spend land in the same commitment tx:

  • myVtxos — existing vtxos being spent (e.g. small)
  • pendingBoardingTxs — on-chain boarding UTXOs being settled (potentially large)
  • vtxosToAdd — all new vtxos issued to us, including the one created from the boarding input

So vtxosToAdd easily exceeds myVtxos alone, causing the underflow. The fix counts boarding amounts as part of totalIn so the net calculation reflects the full picture.

Test plan

  • Send vtxos in a round where no boarding occurs — TxSent amount is unchanged
  • Board and spend vtxos in the same commitment tx — no underflow, TxSent reflects correct net amount
  • Receive a payment in the same round as a vtxo spend — totalIn <= totalOut, no spurious TxSent recorded

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed transaction amount calculations for committed transactions with pending transactions. The system now correctly computes sent amounts by accounting for all input and output transactions, ensuring accurate financial reporting.

Review Change Stack

When computing the net sent amount, the previous code subtracted vtxosToAdd
directly from myVtxos without a bounds check, causing a uint64 underflow if
outputs exceeded inputs — e.g. when boarding and spending vtxos occur in the
same commitment tx. Include pendingBoardingTxs in totalIn and guard the
subtraction.

Closes #170

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 11, 2026

Warning

Rate limit exceeded

@aruokhai has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 18 minutes and 27 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8ddfc417-e6d0-4b3a-aab3-ed32a8ff5c52

📥 Commits

Reviewing files that changed from the base of the PR and between 4091a1a and 4bb2694.

📒 Files selected for processing (2)
  • client.go
  • client_internal_test.go

Walkthrough

In handleCommitmentTx, the TxSent transaction amount calculation was corrected to prevent uint64 underflow. The logic now sums myVtxos and pendingBoardingTxs for total input, sums vtxosToAdd for total output, and records a TxSent transaction only when input exceeds output with the computed difference as the amount.

Changes

TxSent Underflow Fix

Layer / File(s) Summary
TxSent Amount Calculation
client.go
handleCommitmentTx now computes totalIn from myVtxos and pendingBoardingTxs, computes totalOut from vtxosToAdd, and appends TxSent with amount totalIn - totalOut only when totalIn > totalOut, preventing uint64 underflow corruption.

🎯 2 (Simple) | ⏱️ ~8 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately reflects the main change: fixing a uint64 underflow bug in the commitment transaction sent amount calculation.
Linked Issues check ✅ Passed The code changes address all key requirements from issue #170: including pending boarding transactions in totalIn, replacing raw subtraction with guarded check (totalIn > totalOut), and preventing spurious TxSent records.
Out of Scope Changes check ✅ Passed All changes in client.go are directly related to fixing the uint64 underflow issue in handleCommitmentTx as specified in issue #170; no extraneous modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix_underflow_error

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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
Copy Markdown

@arkanaai arkanaai Bot left a comment

Choose a reason for hiding this comment

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

🔴 Protocol-Critical Review — Requesting Changes

This PR touches VTXO amount accounting and transaction record creation. Requires human sign-off before merge.


✅ The fix itself is correct

The underflow diagnosis is spot-on. The old code in handleCommitmentTx did:

amount -= v.Amount  // uint64 subtraction with no guard

When boarding inputs contribute to vtxosToAdd but aren't counted in myVtxos, the subtraction wraps to ~2^64. The fix correctly:

  1. Adds pendingBoardingTxs amounts to totalIn
  2. Accumulates vtxosToAdd into a separate totalOut
  3. Guards with if totalIn > totalOut before subtracting

Clean, minimal, easy to reason about. Good.


🔴 P0 — Same underflow exists in handleArkTx (client.go:1540-1544)

// client.go:1530-1544 (master)
inAmount := uint64(0)
for _, vtxo := range myVtxos {
    inAmount += vtxo.Amount
}
outAmount := uint64(0)
for _, vtxo := range vtxosToAdd {
    outAmount += vtxo.Amount
}
txsToAdd = append(txsToAdd, clientTypes.Transaction{
    ...
    Amount: inAmount - outAmount,  // ← SAME uint64 underflow, NO guard at all
    Type:   clientTypes.TxSent,

This is worse than the commitment tx case — there isn't even an if amount > 0 pseudo-guard. If a user receives a payment in the same ark tx where they spend vtxos, and outAmount > inAmount, this silently records a bogus TxSent with a wrapped amount.

Please fix handleArkTx in this same PR. Same pattern: guard with if inAmount > outAmount before subtracting.


🟡 P1 — No test coverage

This is a money-accounting bug. It needs at least a table-driven unit test covering:

  • Pure send (totalIn > totalOut) → correct TxSent amount
  • Boarding + vtxo spend in same round (the original bug scenario)
  • totalIn == totalOut → no TxSent recorded
  • totalIn < totalOut → no TxSent recorded, no underflow

handleCommitmentTx and handleArkTx are internal methods that take concrete types — they should be testable via client_internal_test.go.


🟢 Minor observations (non-blocking)

  • totalIn < totalOut case silently drops: When boarding + vtxo inputs < vtxo outputs, no transaction record is created at all. This is pre-existing behavior and not a regression, but worth a // TODO comment noting that a TxReceived might be appropriate here.
  • No cross-repo API breakage: The change is internal to handleCommitmentTx, no public API or type changes. No downstream SDK impact.

Summary

Finding Severity Status
Underflow fix in handleCommitmentTx ✅ Correct
Same underflow in handleArkTx:1544 P0 🔴 Must fix
Missing test coverage P1 🟡 Should add
Silent drop when totalIn < totalOut P2 🟢 Pre-existing, note it

⚠️ Protocol-critical change — requires human approval before merge even after code fixes are addressed.

aruokhai and others added 2 commits May 11, 2026 16:28
- handleArkTx had the same uint64 underflow as handleCommitmentTx: no
  guard before inAmount - outAmount, so a receive-in-same-tx would wrap
  to a huge bogus TxSent amount.
- Extracted committedSentAmount and arkSentAmount as pure functions so
  the guard logic is unit-testable without store mocking.
- Added table-driven tests covering pure send, boarding+vtxo in same
  round, totalIn==totalOut, and totalIn<totalOut for both helpers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously the spent-vtxos branch of handleCommitmentTx/handleArkTx only
emitted a TxSent when our inputs exceeded our outputs; when a tx both spent
one of our vtxos and credited us with more than we put in (e.g. a payment to
us in the same round, a collaborative tx), it recorded nothing — leaving the
balance change invisible in history.

committedNetAmount/arkNetAmount now return a direction: TxSent when inputs
win, TxReceived when outputs win, skip only on exact equality.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@arkanaai arkanaai Bot left a comment

Choose a reason for hiding this comment

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

✅ Follow-up Review — All Previous Findings Addressed

Previous review requested changes for 3 findings. All resolved in commits b71e494 and 528b632.

P0 ✅ handleArkTx underflow — Fixed

arkNetAmount (client.go:2122) now guards the subtraction identically to committedNetAmount. The raw inAmount - outAmount is gone.

P1 ✅ Test coverage — Added

Table-driven tests in client_internal_test.go:10-117 cover:

  • Pure send (in > out) → correct TxSent amount
  • Boarding + vtxo in same round → boarding counted as input, no underflow
  • Net receive (out > in) → TxReceived, no underflow
  • Equal in/out → ok=false, nothing recorded

P2 ✅ Silent drop on net-receive — Now records TxReceived

netAmount (client.go:2134) returns TxReceived when totalOut > totalIn instead of dropping. This closes the accounting gap I flagged.

Code quality

  • netAmount helper is clean: switch on three cases, no arithmetic without a guard, no possible underflow.
  • committedNetAmount correctly includes boardingTxs in totalIn.
  • arkNetAmount correctly excludes boarding (boarding doesn't apply in the ark tx path).
  • All helpers are unexported — zero cross-repo API impact.
  • Pre-existing boarding-only path (client.go:1388) is untouched and out of scope.

⚠️ Protocol-critical (VTXO amount accounting) — requires human approval before merge.

Copy link
Copy Markdown

@arkanaai arkanaai Bot left a comment

Choose a reason for hiding this comment

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

✅ Follow-up Review — All Previous Findings Addressed

Previous review requested changes for 3 findings. All resolved in commits b71e494 and 528b632.

P0 ✅ handleArkTx underflow — Fixed

arkNetAmount (client.go:2122) now guards the subtraction identically to committedNetAmount. The raw inAmount - outAmount is gone.

P1 ✅ Test coverage — Added

Table-driven tests in client_internal_test.go:10-117 cover:

  • Pure send (in > out) → correct TxSent amount
  • Boarding + vtxo in same round → boarding counted as input, no underflow
  • Net receive (out > in) → TxReceived, no underflow
  • Equal in/out → ok=false, nothing recorded

P2 ✅ Silent drop on net-receive — Now records TxReceived

netAmount (client.go:2134) returns TxReceived when totalOut > totalIn instead of dropping. This closes the accounting gap I flagged.

Code quality

  • netAmount helper is clean: switch on three cases, no arithmetic without a guard, no possible underflow.
  • committedNetAmount correctly includes boardingTxs in totalIn.
  • arkNetAmount correctly excludes boarding (boarding doesn't apply in the ark tx path).
  • All helpers are unexported — zero cross-repo API impact.
  • Pre-existing boarding-only path (client.go:1388) is untouched and out of scope.

⚠️ Protocol-critical (VTXO amount accounting) — requires human approval before merge.

Copy link
Copy Markdown

@arkanaai arkanaai Bot left a comment

Choose a reason for hiding this comment

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

Follow-up Review — All Previous Findings Addressed

Previous review requested changes for 3 findings. All resolved in commits b71e494 and 528b632.

P0 - handleArkTx underflow — Fixed

arkNetAmount (client.go:2122) now guards the subtraction identically to committedNetAmount. The raw inAmount - outAmount is gone.

P1 - Test coverage — Added

Table-driven tests in client_internal_test.go:10-117 cover:

  • Pure send (in > out) -> correct TxSent amount
  • Boarding + vtxo in same round -> boarding counted as input, no underflow
  • Net receive (out > in) -> TxReceived, no underflow
  • Equal in/out -> ok=false, nothing recorded

P2 - Silent drop on net-receive — Now records TxReceived

netAmount (client.go:2134) returns TxReceived when totalOut > totalIn instead of dropping. This closes the accounting gap I flagged.

Code quality

  • netAmount helper is clean: switch on three cases, no arithmetic without a guard, no possible underflow.
  • committedNetAmount correctly includes boardingTxs in totalIn.
  • arkNetAmount correctly excludes boarding (boarding does not apply in the ark tx path).
  • All helpers are unexported — zero cross-repo API impact.
  • Pre-existing boarding-only path (client.go:1388) is untouched and out of scope.

Protocol-critical (VTXO amount accounting) — requires human approval before merge.

Copy link
Copy Markdown

@arkanaai arkanaai Bot left a comment

Choose a reason for hiding this comment

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

✅ Incremental Review — Commit ebbf64e

Pure rename refactor: committedNetAmountcommitmentTxNetAmount, arkNetAmountarkTxNetAmount. Minor doc comment cleanup. No logic changes.

All call sites, definitions, and test invocations updated consistently. Approval stands.

⚠️ Reminder: this PR is protocol-critical (VTXO amount accounting). Requires human sign-off before merge.

…itmentTxNetAmount and TestArkNetAmount to TestArkTxNetAmount
Copy link
Copy Markdown

@arkanaai arkanaai Bot left a comment

Choose a reason for hiding this comment

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

✅ Incremental Review — Commit 96d3fe9

Test function renames only: TestCommittedNetAmountTestCommitmentTxNetAmount, TestArkNetAmountTestArkTxNetAmount. Aligns test names with the function renames from ebbf64e. No logic changes.

Approval stands.

⚠️ Protocol-critical (VTXO amount accounting) — requires human sign-off before merge.

Hoist commitmentTxNetAmount out of the if-init and wrap arkTxNetAmount's
signature so both stay under the 100-char limit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@arkanaai arkanaai Bot left a comment

Choose a reason for hiding this comment

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

✅ Incremental Review — Commit 4bb2694

Formatting-only change: golines max-len 100 line wrapping on commitmentTxNetAmount call site and arkTxNetAmount function signature. No logic changes.

Approval stands.

⚠️ Protocol-critical (VTXO amount accounting) — requires human sign-off before merge.

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.

uint64 underflow in commitment tx handler produces corrupt TxSent records with astronomical amounts

1 participant