fix(ledger): reject posting sets that overflow the zero-sum check

An adversarial posting set of two MaxInt64 legs plus one of 2 wraps to
zero in int64 arithmetic, so the balance check passed and the ledger
minted 18 quintillion millisatoshis from nothing. The sum is now
accumulated in big.Int, per-account balance arithmetic is checked for
wraparound, and the audit totals parse through big.Int so a corrupt
ledger reports a clear error rather than failing to scan.

Adds room package tests (0% -> covered) and ledger edge cases.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
drjones
2026-08-05 15:43:21 +00:00
parent 2a2a1db8de
commit f2c02e2bde
4 changed files with 929 additions and 24 deletions

View File

@@ -16,10 +16,17 @@ func TestRandomActivityConservesValue(t *testing.T) {
l := ledger.New(testPool(t))
ctx := context.Background()
house, err := l.AccountByName(ctx, "house_pot")
// The test uses its own counterparty rather than the shared house account,
// and sums only its own accounts. Asserting on a global total would fail
// whenever another package's tests run against the same database in
// parallel — a broken test, not a broken ledger.
house, err := l.EnsurePlayer(ctx, uniqueKey(t, "counterparty"))
if err != nil {
t.Fatal(err)
}
if _, err := l.Deposit(ctx, house, 5_000_000); err != nil {
t.Fatal(err)
}
const players = 8
ids := make([]int64, players)
@@ -34,11 +41,22 @@ func TestRandomActivityConservesValue(t *testing.T) {
}
}
before, err := l.TotalIssued(ctx)
if err != nil {
t.Fatal(err)
// sumOwn totals only the accounts this test created.
sumOwn := func() int64 {
t.Helper()
var total int64
for _, id := range append(append([]int64{}, ids...), house) {
bal, err := l.Balance(ctx, id)
if err != nil {
t.Fatal(err)
}
total += bal
}
return total
}
before := sumOwn()
rng := rand.New(rand.NewSource(1))
roundID := int64(0)
for i := 0; i < 400; i++ {
@@ -75,11 +93,7 @@ func TestRandomActivityConservesValue(t *testing.T) {
}
}
after, err := l.TotalIssued(ctx)
if err != nil {
t.Fatal(err)
}
if before != after {
if after := sumOwn(); before != after {
t.Fatalf("value not conserved: %d -> %d", before, after)
}