Skip to content

fix(vm): fail closed when pulled funds are left in the queue - #217

Merged
ascandone merged 3 commits into
mainfrom
fix/vm-malformed-pull-send
Oct 29, 2026
Merged

ascandone merged 3 commits into
mainfrom
fix/vm-malformed-pull-send

Conversation

@ascandone

@ascandone ascandone commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Problem

A pull debits its source at once; the posting only exists once a send drains the queue. Two shapes pass vm.Verify and go wrong with no error:

  • Funds still queued when set_current_asset runs are posted under the new asset.
  • Funds still queued when the run ends are a debit that never becomes a posting.

Compiled numscript never emits either: every send statement either pulls exactly what it sends (check_enough_funds) or sends everything it pulled. They matter for bytecode that did not come out of this process's compiler, which is what the ledger replays.

Fix

Exec returns an InternalError for both, via a new funds.RunState.HasQueuedFunds():

  • errSetAssetWithQueuedFunds at set_current_asset
  • errEndWithQueuedFunds at the end of the run

HasQueuedFunds() is a length check. For that to hold, Pull no longer queues a zero amount, matching PullUncapped. Before, a zero-cap or empty-source pull queued a zero entry, and a send that stopped once its cap was met left it in the queue (e.g. send [USD 0]). A zero entry never produced a posting, so outputs are unchanged.

This is a run-time check because it is not statically decidable on the current bytecode: whether a capped send empties the queue depends on the balances. bytecode-verifier.md documents it under "What it does not check". #226 is the static alternative, at the cost of a compiler change and a bytecode version bump.

The two TODO crashes comments on the ext-word reads in vm.go are removed: Verify rejects a truncated two-word instruction, and Exec is total only for verified programs.

Not changed

Tests

  • vm_test.go: each check, plus a zero-amount pull that must not trip it.
  • ir_test.go: both shapes as IR, so they sit in the "sequences the compiler never emits" corpus.
  • go test ./... (including the frozen bytecode corpus) and the difftest module pass.

Performance

The run-time checks themselves: BenchmarkCompiledVM*, 8 runs each against main, benchstat: no regression, identical B/op and allocs/op.

Not queuing zero amounts, 6 interleaved runs against the previous commit:

Benchmark ns/op allocs/op
{@a @b @c}, @a and @b empty (ad hoc, not committed) −3.6% 14 → 12
CompiledVMAllotment +0.8% (noise) 42 → 42
CompiledVMCapped +0.7% (noise) 23 → 23

@NumaryBot NumaryBot added risk: medium NumaryBot classified this pull request as medium risk. bot-reviewed NumaryBot completed its review workflow for the current head. review-approved The NumaryBot review gate is satisfied for the current head. labels Oct 12, 2026
NumaryBot
NumaryBot previously approved these changes Oct 12, 2026

@NumaryBot NumaryBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The required automated review completed with no remaining findings.

@ascandone
ascandone marked this pull request as draft October 14, 2026 06:56
@NumaryBot NumaryBot added draft-reviewed NumaryBot reviewed this draft, but published no readiness signal. and removed review-approved The NumaryBot review gate is satisfied for the current head. labels Oct 14, 2026
Comment thread internal/vm/vm.go
Comment thread internal/vm/vm.go
Exec returns an InternalError when set_current_asset runs, or the run ends,
while the source queue still holds a positive amount. Whether a capped send
drains the queue depends on the balances, so this is not checked by Verify.
@ascandone
ascandone force-pushed the fix/vm-malformed-pull-send branch from 665f356 to c5639f4 Compare October 28, 2026 10:45
@ascandone
ascandone requested a review from Azorlogh October 28, 2026 10:57
@ascandone
ascandone marked this pull request as ready for review October 28, 2026 10:57
@NumaryBot NumaryBot removed the draft-reviewed NumaryBot reviewed this draft, but published no readiness signal. label Oct 28, 2026
@ascandone ascandone changed the title fix(vm): fail closed on malformed pull/send bytecode fix(vm): fail closed when pulled funds are left in the queue Oct 28, 2026
@NumaryBot NumaryBot added the review-approved The NumaryBot review gate is satisfied for the current head. label Oct 29, 2026
NumaryBot
NumaryBot previously approved these changes Oct 29, 2026

@NumaryBot NumaryBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The required automated review completed with no remaining findings.

@NumaryBot NumaryBot added review-approved The NumaryBot review gate is satisfied for the current head. and removed review-approved The NumaryBot review gate is satisfied for the current head. labels Oct 29, 2026

@NumaryBot NumaryBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The required automated review completed with no remaining findings.

@ascandone
ascandone merged commit c2b66f9 into main Oct 29, 2026
11 checks passed
@ascandone
ascandone deleted the fix/vm-malformed-pull-send branch October 29, 2026 09:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot-reviewed NumaryBot completed its review workflow for the current head. review-approved The NumaryBot review gate is satisfied for the current head. risk: medium NumaryBot classified this pull request as medium risk.

Development

Successfully merging this pull request may close these issues.

3 participants