upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/fuzz_script.cpp
diff options
context:
space:
mode:
authorbitflash-maintainer <noreply@bitflash.invalid>2026-08-23 07:33:57 +0000
committerbitflash-maintainer <noreply@bitflash.invalid>2026-08-23 07:33:57 +0000
commit33f2e8a164d0b8a508dc97372a3592d126b96a1a (patch)
tree0702f1ff86cf487046037bca948c00d06055ea50 /src/fuzz_script.cpp
parentc095e07755c60a0c1393da04511f19ef517f7977 (diff)
payout: serialise ledger file writes and order them by snapshot version
Splitting SavePendingPayoutsLocked() into a serialise step that needs gPayoutLedgerMutex and a write step that does not left the write step with no serialisation at all. The event loop wrote pending_payouts.json from QueuePayouts and the orphan-void path with the ledger mutex HELD; the payout thread wrote it from RecordPayoutTxid and ForfeitPayoutRecipient with the mutex RELEASED. Both fopen()ed the same "<path>.tmp" with independent handles and both rename()d it. Two ways that ends, both live money: - the renamed file is a mixture of two images. LoadPendingPayouts throws inside its parse loop, logs one warning, and the node comes up with a nearly empty ledger -- every obligation not yet exploded into an in-flight record is simply gone, with nothing on disk saying anyone was owed it. - a lost update: the payout thread's older snapshot lands after the event loop's newer one and silently erases a whole round that had just been queued. Neither parent had this. Upstream writes under the mutex everywhere; the pre-merge tree had one writer thread and no ledger mutex. The merge invented the window while fixing a real problem -- the event loop should not block in FlushMaturePayouts behind a per-recipient fsync on the payout thread -- so putting the write back under the ledger mutex is not the fix. Instead: a per-file write mutex that is never held together with a data mutex, plus a version on every snapshot. - serialising happens under the data mutex and stamps a monotonically increasing sequence number (gPayoutLedgerSeq). - publishing happens with the data mutex released, under the file mutex, and drops any blob whose sequence number is not strictly newer than the one already on disk. Because each blob is a whole-state dump of a single in-memory structure and the sequence number is assigned under the mutex guarding that structure, a higher sequence number is by construction a later state -- so discarding an older blob loses nothing, and the file can only move forwards. Writers may queue and complete in any order and the published file is still the newest state anyone has serialised. A failed write deliberately does not advance the published sequence, so the next write still tries. The same treatment is applied to paying_payouts.json and pool_rounds.json. Neither had the interleaving race -- gPayoutExecMutex and gPoolRoundsMutex already serialise their writers -- but both did their I/O under a mutex the event loop takes (FlushMaturePayouts, RecordPoolRound), so both were a place for the event loop to wait on the payout thread's disk. They are now written outside those mutexes on the same versioned terms, which is also what makes adding fsync to these paths safe. pool_status.json keeps a plain atomic write with no version guard: RefreshStats is its only caller and only ever runs on the event loop, so it has a single writer by construction. It shares the same WriteFileAtomic helper so the durability rules live in one place. No on-disk format change.
Diffstat (limited to 'src/fuzz_script.cpp')
0 files changed, 0 insertions, 0 deletions