lightninglabs / lightninglabs/aperture

meterd: reserve prompt tokens and bound the per-request estimate

Open
#252 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug metering
Dominant language
Go
Stars
268
Forks
78
Avg merge
22h 25m
Merged PRs (30d)
1

Description

Found by an adversarial review pass over #247 and deferred out of it, since #247 was already large. This is the one deferred item where a buyer can deliberately cost the seller money on a live path, so it is the first thing to pick up after that PR lands.

The reservation only covers completions

AuthorizeRequest reserves an estimate against the bundle before the request goes upstream, and the estimate is derived from max_tokens (or the configured EstimatedTokens fallback). That covers completion tokens only. Prompt tokens are debited after the fact but never reserved.

So a buyer sending max_tokens: 1 with a 100k-token prompt reserves one token and consumes a hundred thousand. Fire N of those concurrently against a nearly-empty bundle and the debit clamps at zero, which means everything past the balance is served for free. The loss is bounded by concurrency times prompt size, and it is entirely buyer-controlled.

The fix is to floor the estimate by the body we already have in hand. readBodyPrefix gives the serialized request, so something on the order of len(body)/4 is a defensible lower bound on prompt tokens, and the estimate becomes max(completionEstimate, promptFloor).

max_tokens is unbounded in the other direction

Nothing caps what a client can put in max_tokens, so a large value inflates Reserved and can make a bundle read as exhausted while it still has balance. That is self-inflicted rather than an attack on the seller, but it amplifies any reservation that later leaks, so the estimate should be capped at the bundle size.

While we are in the debit math

weightedDebitMsat computes 2 * (prompt*inRate + completion*outRate) as an intermediate. With absurd counts from a hostile or broken backend that overflows int64 and goes negative, and a negative debit is no debit at all. The response tail is otherwise well defended (escaped quotes cannot forge a usage object, and keyInObjectPosition blocks string-embedded keys), so this needs a broken upstream rather than a malicious client, but clamping the counts to something sane before the multiply is cheap belt-and-braces.

Acceptance

  • The estimate reserved at authorization accounts for prompt tokens.
  • The estimate is capped at the bundle size.
  • Debit arithmetic cannot go negative on any input.
  • Tests cover a small-max_tokens/large-prompt request against a nearly-empty bundle, and the overflow clamp.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Locate AuthorizeRequest, readBodyPrefix, and weightedDebitMsat, then inspect the existing authorization and debit tests around those entry points. Verify that prompt tokens are included, reservations are capped by bundle size, and debit arithmetic remains non-negative for extreme inputs; add coverage for the acceptance cases described.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
backend, payments, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
56/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.