From 28e32380e6ed221beed256d69d840c591d5ccac2 Mon Sep 17 00:00:00 2001 From: 7layermagik <7layermagik@users.noreply.github.com> Date: Sun, 13 Sep 2026 23:12:41 -0500 Subject: [PATCH 01/16] Move transaction-status checkpoint encoding off the replay loop --- pkg/replay/async_checkpoint_capture_test.go | 172 ++++++++++++++++++ pkg/replay/async_promotion_test.go | 18 +- pkg/replay/block.go | 6 +- pkg/replay/promotion.go | 92 +++++++--- pkg/replay/transaction_status_cache.go | 54 +++++- .../transaction_status_capture_bench_test.go | 66 +++++++ pkg/replay/transaction_status_capture_test.go | 162 +++++++++++++++++ 7 files changed, 524 insertions(+), 46 deletions(-) create mode 100644 pkg/replay/async_checkpoint_capture_test.go create mode 100644 pkg/replay/transaction_status_capture_bench_test.go create mode 100644 pkg/replay/transaction_status_capture_test.go diff --git a/pkg/replay/async_checkpoint_capture_test.go b/pkg/replay/async_checkpoint_capture_test.go new file mode 100644 index 000000000..a556c5a37 --- /dev/null +++ b/pkg/replay/async_checkpoint_capture_test.go @@ -0,0 +1,172 @@ +package replay + +import ( + "encoding/json" + "errors" + "sync" + "testing" + "time" + + "github.com/Overclock-Validator/mithril/pkg/accounts" + "github.com/Overclock-Validator/mithril/pkg/state" + "github.com/stretchr/testify/require" +) + +type testCheckpointEncoder func() ([]byte, error) + +func (f testCheckpointEncoder) MarshalBinary() ([]byte, error) { return f() } + +func testCheckpointBytes(payload []byte) TransactionStatusSnapshot { + owned := append([]byte(nil), payload...) + return testCheckpointEncoder(func() ([]byte, error) { return append([]byte(nil), owned...), nil }) +} + +func TestAsyncCheckpointEncodingDoesNotRunDuringJobBuild(t *testing.T) { + fc := &fakeCommitter{durable: accounts.NewMemAccounts()} + tail := asyncTestTail(fc, 5, 6) + started, release := make(chan struct{}), make(chan struct{}) + blockEncoding := false + rootDir := t.TempDir() + require.NoError(t, tail.SetTransactionStatusCheckpointHooks(TransactionStatusCheckpointHooks{ + Capture: func(through uint64) (TransactionStatusSnapshot, error) { + require.Equal(t, uint64(6), through) + return testCheckpointEncoder(func() ([]byte, error) { + if !blockEncoding { + return nil, errors.New("encoder ran during job construction") + } + close(started) + <-release + return []byte("encoded-on-worker"), nil + }), nil + }, + Install: func(through uint64, payload []byte) (*state.TransactionStatusCheckpointRef, error) { + return PrepareTransactionStatusCheckpoint(rootDir, through, payload) + }, + })) + job, err := tail.buildFoldJob(6, false) + require.NoError(t, err) + select { + case <-started: + t.Fatal("job construction ran the encoder") + default: + } + promoter := newAsyncPromoter(fc) + // Always unblock the worker before draining it, including a failed assertion. + var releaseOnce sync.Once + unblock := func() { releaseOnce.Do(func() { close(release) }) } + defer promoter.stop() + defer unblock() + blockEncoding = true + promoter.enqueue(job) + select { + case <-started: + case <-time.After(2 * time.Second): + t.Fatal("checkpoint worker did not start encoding") + } + // Replay can publish a later bank while the worker's encoder is blocked. + tail.Add(7, []*accounts.Account{testAccount(3, 7)}, testHashBytes(7)) + tail.SetContext(7, &state.ResumeContext{Slot: 7}) + require.Equal(t, 3, tail.overlay.HeldSlots()) + require.Nil(t, promoter.poll()) + + unblock() + result := promoter.drain() + require.NotNil(t, result) + require.NoError(t, result.err) + require.Nil(t, result.job.transactionStatusSnapshot) + tail.applyFoldJob(result.job) + require.Equal(t, 1, tail.overlay.HeldSlots()) +} + +func TestCheckpointCaptureFailureOrdering(t *testing.T) { + cases := []struct { + name string + capture func(uint64) (TransactionStatusSnapshot, error) + want string + buildFails bool + }{ + {"nil capture", func(uint64) (TransactionStatusSnapshot, error) { return nil, nil }, "capture is nil", true}, + {"capture error", func(uint64) (TransactionStatusSnapshot, error) { return nil, errors.New("capture failed") }, "capture failed", true}, + {"encode error", func(uint64) (TransactionStatusSnapshot, error) { + return testCheckpointEncoder(func() ([]byte, error) { return nil, errors.New("encode failed") }), nil + }, "encode failed", false}, + {"empty encoding", func(uint64) (TransactionStatusSnapshot, error) { return testCheckpointBytes(nil), nil }, "snapshot is empty", false}, + } + for _, tc := range cases { + for _, forced := range []bool{false, true} { + name := tc.name + "/async" + if forced { + name = tc.name + "/forced" + } + t.Run(name, func(t *testing.T) { + fc := &fakeCommitter{durable: accounts.NewMemAccounts()} + tail := asyncTestTail(fc, 5, 6) + installed := false + require.NoError(t, tail.SetTransactionStatusCheckpointHooks(TransactionStatusCheckpointHooks{ + Capture: tc.capture, + Install: func(uint64, []byte) (*state.TransactionStatusCheckpointRef, error) { + installed = true + return nil, errors.New("unexpected install") + }, + })) + if forced { + through, _, err := tail.flush(6) + require.ErrorContains(t, err, tc.want) + require.Zero(t, through) + } else { + job, err := tail.buildFoldJob(6, false) + if tc.buildFails { + require.ErrorContains(t, err, tc.want) + require.Nil(t, job) + } else { + require.NoError(t, err) + require.ErrorContains(t, runFoldJob(fc, job), tc.want) + require.Nil(t, job.transactionStatusSnapshot, "failed result retained its captured deltas") + } + } + require.False(t, installed) + require.Empty(t, fc.throughs) + require.Equal(t, 2, tail.overlay.HeldSlots()) + }) + } + } +} + +func TestFoldCheckpointKeepsCapturedRootAfterLiveCacheAdvances(t *testing.T) { + c := importedStatusCacheForTest(t) + for slot := uint64(301); slot <= 350; slot++ { + require.NoError(t, c.CommitBlock(captureTestBlock(slot, 1))) + } + want, err := legacyStatusSnapshotForTest(c, 320) + require.NoError(t, err) + fc := &fakeCommitter{durable: accounts.NewMemAccounts()} + tail := asyncTestTail(fc, 319, 320, 321) + rootDir := t.TempDir() + require.NoError(t, tail.SetTransactionStatusCheckpointHooks(TransactionStatusCheckpointHooks{ + Capture: c.CaptureSnapshotThrough, + Install: func(through uint64, payload []byte) (*state.TransactionStatusCheckpointRef, error) { + return PrepareTransactionStatusCheckpoint(rootDir, through, payload) + }, + })) + job, err := tail.buildFoldJob(321, false) + require.NoError(t, err) + for slot := uint64(351); slot <= 660; slot++ { + require.NoError(t, c.CommitBlock(captureTestBlock(slot, 1))) + c.Root(slot - 20) + } + require.NoError(t, runFoldJob(fc, job)) + require.Nil(t, job.transactionStatusSnapshot, "completed result retained captured deltas") + require.Positive(t, job.checkpointCaptureTime) + require.Positive(t, job.checkpointEncodeTime) + require.Equal(t, len(want), job.checkpointBytes) + var manifest state.ResumeContext + require.NoError(t, json.Unmarshal(fc.ctxs[320], &manifest)) + require.Equal(t, uint64(320), manifest.TransactionStatusCheckpoint.Root) + got, err := ReadTransactionStatusCheckpoint(rootDir, manifest.TransactionStatusCheckpoint) + require.NoError(t, err) + require.Equal(t, want, got) + restored, err := NewTransactionStatusCacheFromSnapshot(got) + require.NoError(t, err) + require.Equal(t, uint64(320), restored.RootedThrough()) + require.True(t, restored.CoverageComplete()) +} diff --git a/pkg/replay/async_promotion_test.go b/pkg/replay/async_promotion_test.go index 49d47a49e..eff0de206 100644 --- a/pkg/replay/async_promotion_test.go +++ b/pkg/replay/async_promotion_test.go @@ -22,7 +22,7 @@ type slowCommitter struct { delay time.Duration } -func TestFoldJobSnapshotsStatusOnLoopAndReferenceRidesManifest(t *testing.T) { +func TestFoldJobCapturesStatusOnLoopAndReferenceRidesManifest(t *testing.T) { rootDir := t.TempDir() fc := &fakeCommitter{durable: accounts.NewMemAccounts()} tail := asyncTestTail(fc, 5, 6, 7) @@ -32,10 +32,10 @@ func TestFoldJobSnapshotsStatusOnLoopAndReferenceRidesManifest(t *testing.T) { installCalled := false afterCommitCalled := false hooks := TransactionStatusCheckpointHooks{ - Snapshot: func(through uint64) ([]byte, error) { + Capture: func(through uint64) (TransactionStatusSnapshot, error) { require.Equal(t, uint64(6), through) snapshotCalled = true - return scratch, nil + return testCheckpointBytes(scratch), nil }, Install: func(through uint64, payload []byte) (*state.TransactionStatusCheckpointRef, error) { require.True(t, snapshotCalled, "worker install ran before loop snapshot") @@ -83,7 +83,7 @@ func TestFoldJobCheckpointFailuresCannotReachCommitBatch(t *testing.T) { fc := &fakeCommitter{durable: accounts.NewMemAccounts()} tail := asyncTestTail(fc, 5, 6) require.NoError(t, tail.SetTransactionStatusCheckpointHooks(TransactionStatusCheckpointHooks{ - Snapshot: func(uint64) ([]byte, error) { return nil, errors.New("snapshot boom") }, + Capture: func(uint64) (TransactionStatusSnapshot, error) { return nil, errors.New("snapshot boom") }, Install: func(uint64, []byte) (*state.TransactionStatusCheckpointRef, error) { t.Fatal("install must not run") return nil, nil @@ -101,7 +101,7 @@ func TestFoldJobCheckpointFailuresCannotReachCommitBatch(t *testing.T) { fc := &fakeCommitter{durable: accounts.NewMemAccounts()} tail := asyncTestTail(fc, 5, 6) require.NoError(t, tail.SetTransactionStatusCheckpointHooks(TransactionStatusCheckpointHooks{ - Snapshot: func(uint64) ([]byte, error) { return []byte("captured"), nil }, + Capture: func(uint64) (TransactionStatusSnapshot, error) { return testCheckpointBytes([]byte("captured")), nil }, Install: func(uint64, []byte) (*state.TransactionStatusCheckpointRef, error) { return nil, errors.New("fsync boom") }, @@ -120,7 +120,7 @@ func TestFoldJobCheckpointFailuresCannotReachCommitBatch(t *testing.T) { tail := asyncTestTail(fc, 5, 6) afterCommitCalled := false require.NoError(t, tail.SetTransactionStatusCheckpointHooks(TransactionStatusCheckpointHooks{ - Snapshot: func(uint64) ([]byte, error) { return []byte("captured"), nil }, + Capture: func(uint64) (TransactionStatusSnapshot, error) { return testCheckpointBytes([]byte("captured")), nil }, Install: func(through uint64, payload []byte) (*state.TransactionStatusCheckpointRef, error) { return PrepareTransactionStatusCheckpoint(rootDir, through, payload) }, @@ -144,9 +144,9 @@ func TestForcedFoldCarriesStatusCheckpointReference(t *testing.T) { tail := asyncTestTail(fc, 5) afterCommitCalled := false require.NoError(t, tail.SetTransactionStatusCheckpointHooks(TransactionStatusCheckpointHooks{ - Snapshot: func(through uint64) ([]byte, error) { + Capture: func(through uint64) (TransactionStatusSnapshot, error) { require.Equal(t, uint64(5), through) - return []byte("forced-partial-status"), nil + return testCheckpointBytes([]byte("forced-partial-status")), nil }, Install: func(through uint64, payload []byte) (*state.TransactionStatusCheckpointRef, error) { return PrepareTransactionStatusCheckpoint(rootDir, through, payload) @@ -175,7 +175,7 @@ func TestCheckpointAfterCommitRequiresDurabilityHooks(t *testing.T) { err := tail.SetTransactionStatusCheckpointHooks(TransactionStatusCheckpointHooks{ AfterCommit: func(*state.TransactionStatusCheckpointRef) error { return nil }, }) - require.ErrorContains(t, err, "requires Snapshot and Install") + require.ErrorContains(t, err, "requires Capture and Install") } func (c *slowCommitter) CommitBatch(deltas []accounts.SlotDelta, throughSlot uint64, bankhashes map[uint64][32]byte, resumeCtx []byte) (accountsdb.BatchCommitResult, error) { diff --git a/pkg/replay/block.go b/pkg/replay/block.go index c45c6cb9a..d28f3b8ca 100644 --- a/pkg/replay/block.go +++ b/pkg/replay/block.go @@ -1987,9 +1987,9 @@ func ReplayBlocks( checkpointAfterCommit = consensusOpts.TransactionStatusCheckpointAfterCommit } if hookErr := unrootedTailState.SetTransactionStatusCheckpointHooks(TransactionStatusCheckpointHooks{ - // Snapshot runs here on the replay loop during fold-job construction; - // only its immutable bytes cross to the async worker. - Snapshot: transactionStatuses.SnapshotThrough, + // Pin the exact immutable view on replay. Sorting and encoding run + // on the existing fold worker, after releasing the live cache lock. + Capture: transactionStatuses.CaptureSnapshotThrough, Install: func(through uint64, payload []byte) (*state.TransactionStatusCheckpointRef, error) { return PrepareTransactionStatusCheckpoint(acctsDbPath, through, payload) }, diff --git a/pkg/replay/promotion.go b/pkg/replay/promotion.go index c750009ea..752e0cddc 100644 --- a/pkg/replay/promotion.go +++ b/pkg/replay/promotion.go @@ -27,14 +27,13 @@ type batchCommitter interface { CommitBatch(deltas []accounts.SlotDelta, throughSlot uint64, bankhashes map[uint64][32]byte, resumeCtx []byte) (accountsdb.BatchCommitResult, error) } -// TransactionStatusCheckpointHooks deliberately split status-cache capture -// from sidecar I/O. Snapshot runs on the replay loop while its mutable cache is -// coherent; Install runs on the fold worker using only those immutable bytes. -// This makes it impossible for the async worker to traverse concurrently -// changing replay lineage. The later AccountsDB manifest remains the selector. +// TransactionStatusCheckpointHooks split immutable status capture from encoding +// and sidecar I/O. Capture runs on replay; the fold worker serializes the captured +// view and then calls Install. Neither worker operation revisits live lineage. +// The later AccountsDB manifest remains the durable checkpoint selector. type TransactionStatusCheckpointHooks struct { - Snapshot func(through uint64) ([]byte, error) - Install func(through uint64, payload []byte) (*state.TransactionStatusCheckpointRef, error) + Capture func(through uint64) (TransactionStatusSnapshot, error) + Install func(through uint64, payload []byte) (*state.TransactionStatusCheckpointRef, error) // AfterCommit is an advisory retention hook. It runs only after CommitBatch // has durably selected the manifest carrying selected. Its error is logged // and ignored: once CommitBatch succeeds, the fold must remain successful. @@ -378,7 +377,10 @@ type foldJob struct { ctx *state.ResumeContext ctxJSON []byte stakeIdxDir string - transactionStatusCheckpointPayload []byte + transactionStatusSnapshot TransactionStatusSnapshot + checkpointCaptureTime time.Duration + checkpointEncodeTime time.Duration + checkpointBytes int installTransactionStatusCheckpoint func(through uint64, payload []byte) (*state.TransactionStatusCheckpointRef, error) afterTransactionStatusCheckpointCommit func(selected *state.TransactionStatusCheckpointRef) error } @@ -415,18 +417,18 @@ func (t *unrootedTail) buildFoldJob(through uint64, force bool, hookOverrides .. return nil, fmt.Errorf("fold chunk through slot %d: no resume context recorded for chunk-top slot", through) } ctx = cloneResumeContextForFold(ctx) - var checkpointPayload []byte - if hooks.Snapshot != nil { - checkpointPayload, err = hooks.Snapshot(through) + var snapshot TransactionStatusSnapshot + var captureTime time.Duration + if hooks.Capture != nil { + start := time.Now() + snapshot, err = hooks.Capture(through) + captureTime = time.Since(start) if err != nil { - return nil, fmt.Errorf("fold chunk through slot %d: snapshot transaction status checkpoint: %w", through, err) + return nil, fmt.Errorf("fold chunk through slot %d: capture transaction status checkpoint: %w", through, err) } - if len(checkpointPayload) == 0 { - return nil, fmt.Errorf("fold chunk through slot %d: transaction status checkpoint snapshot is empty", through) + if snapshot == nil { + return nil, fmt.Errorf("fold chunk through slot %d: transaction status checkpoint capture is nil", through) } - // The worker owns this immutable copy. Even a future Snapshot - // implementation that reuses a scratch buffer cannot race it. - checkpointPayload = append([]byte(nil), checkpointPayload...) } bankhashes := make(map[uint64][32]byte, len(chunk)) for _, sd := range chunk { @@ -440,7 +442,8 @@ func (t *unrootedTail) buildFoldJob(through uint64, force bool, hookOverrides .. bankhashes: bankhashes, ctx: ctx, stakeIdxDir: t.stakeIdxDir, - transactionStatusCheckpointPayload: checkpointPayload, + transactionStatusSnapshot: snapshot, + checkpointCaptureTime: captureTime, installTransactionStatusCheckpoint: hooks.Install, afterTransactionStatusCheckpointCommit: hooks.AfterCommit, }, nil @@ -450,12 +453,26 @@ func (t *unrootedTail) buildFoldJob(through uint64, force bool, hookOverrides .. // state). Stake-index entries flush (fsync'd) BEFORE the batch commit — see // promoteRootedBatched for why that order is a correctness requirement. func runFoldJob(committer batchCommitter, job *foldJob) error { - if job == nil || job.ctx == nil { + if job == nil { + return errors.New("fold job has no resume context") + } + // Failed folds are rebuilt from the retained tail. Neither a failed result + // nor a completed-but-unapplied job should keep checkpoint deltas alive. + defer func() { job.transactionStatusSnapshot = nil }() + if job.ctx == nil { return errors.New("fold job has no resume context") } var selectedCheckpoint *state.TransactionStatusCheckpointRef if job.installTransactionStatusCheckpoint != nil { - ref, err := job.installTransactionStatusCheckpoint(job.through, job.transactionStatusCheckpointPayload) + start := time.Now() + payload, err := encodeTransactionStatusCheckpoint(job.transactionStatusSnapshot) + job.checkpointEncodeTime = time.Since(start) + job.transactionStatusSnapshot = nil + if err != nil { + return fmt.Errorf("fold chunk through slot %d: encode transaction status checkpoint: %w", job.through, err) + } + job.checkpointBytes = len(payload) + ref, err := job.installTransactionStatusCheckpoint(job.through, payload) if err != nil { return fmt.Errorf("fold chunk through slot %d: prepare transaction status checkpoint: %w", job.through, err) } @@ -539,7 +556,9 @@ func (p *asyncPromoter) run() { start := time.Now() err := runFoldJob(p.committer, job) if err == nil { - mlog.Log.FileOnlyf("async fold: committed %d slots through %d in %s", len(job.chunk), job.through, time.Since(start).Round(time.Millisecond)) + mlog.Log.FileOnlyf("async fold: committed %d slots through %d in %s checkpoint_capture=%s checkpoint_encode=%s checkpoint_bytes=%d", + len(job.chunk), job.through, time.Since(start).Round(time.Millisecond), + job.checkpointCaptureTime, job.checkpointEncodeTime, job.checkpointBytes) } p.results <- foldResult{job: job, err: err} } @@ -713,14 +732,15 @@ func promoteRootedBatched( } ctx = cloneResumeContextForFold(ctx) var selectedCheckpoint *state.TransactionStatusCheckpointRef - if hooks.Snapshot != nil { - payload, serr := hooks.Snapshot(chunkThrough) + if hooks.Capture != nil { + snapshot, serr := hooks.Capture(chunkThrough) if serr != nil { - err = fmt.Errorf("promote chunk through slot %d: snapshot transaction status checkpoint: %w", chunkThrough, serr) + err = fmt.Errorf("promote chunk through slot %d: capture transaction status checkpoint: %w", chunkThrough, serr) break } - if len(payload) == 0 { - err = fmt.Errorf("promote chunk through slot %d: transaction status checkpoint snapshot is empty", chunkThrough) + payload, serr := encodeTransactionStatusCheckpoint(snapshot) + if serr != nil { + err = fmt.Errorf("promote chunk through slot %d: encode transaction status checkpoint: %w", chunkThrough, serr) break } ref, perr := hooks.Install(chunkThrough, payload) @@ -792,15 +812,29 @@ func resolveTransactionStatusCheckpointHooks(configured TransactionStatusCheckpo } func validateTransactionStatusCheckpointHooks(hooks TransactionStatusCheckpointHooks) error { - if (hooks.Snapshot == nil) != (hooks.Install == nil) { - return errors.New("transaction status checkpoint Snapshot and Install hooks must either both be set or both be nil") + if (hooks.Capture == nil) != (hooks.Install == nil) { + return errors.New("transaction status checkpoint Capture and Install hooks must either both be set or both be nil") } if hooks.AfterCommit != nil && hooks.Install == nil { - return errors.New("transaction status checkpoint AfterCommit hook requires Snapshot and Install hooks") + return errors.New("transaction status checkpoint AfterCommit hook requires Capture and Install hooks") } return nil } +func encodeTransactionStatusCheckpoint(snapshot TransactionStatusSnapshot) ([]byte, error) { + if snapshot == nil { + return nil, errors.New("transaction status checkpoint capture is nil") + } + payload, err := snapshot.MarshalBinary() + if err != nil { + return nil, err + } + if len(payload) == 0 { + return nil, errors.New("transaction status checkpoint snapshot is empty") + } + return payload, nil +} + func cloneResumeContextForFold(ctx *state.ResumeContext) *state.ResumeContext { if ctx == nil { return nil diff --git a/pkg/replay/transaction_status_cache.go b/pkg/replay/transaction_status_cache.go index 8320df056..03437ba02 100644 --- a/pkg/replay/transaction_status_cache.go +++ b/pkg/replay/transaction_status_cache.go @@ -681,10 +681,32 @@ func (c *TransactionStatusCache) Root(through uint64) bool { return !wasComplete && c.coverageComplete } -// SnapshotThrough serializes only the rooted lineage needed at through. It is -// called while constructing a fold job, so the blob rides in that exact durable -// manifest without being copied into every speculative ResumeContext. -func (c *TransactionStatusCache) SnapshotThrough(through uint64) ([]byte, error) { +// TransactionStatusSnapshot pins an immutable checkpoint view. MarshalBinary +// must use only captured data, without locking or revisiting the live cache, +// and return an owned payload. It can run on the checkpoint worker while replay +// commits, roots, or unwinds its current lineage. +type TransactionStatusSnapshot interface { + MarshalBinary() ([]byte, error) +} + +type transactionStatusSnapshot struct { + nodes []*transactionStatusNode + rootedSinceSeed uint16 + complete bool + coverageFromGenesis bool +} + +func (s *transactionStatusSnapshot) MarshalBinary() ([]byte, error) { + if s == nil { + return nil, nil + } + return marshalTransactionStatusNodes(s.nodes, s.rootedSinceSeed, s.complete, s.coverageFromGenesis) +} + +// CaptureSnapshotThrough selects the exact checkpoint lineage and coverage on +// replay, but leaves transaction-key sorting and serialization to the worker. +// Published deltas are immutable; only small node headers are copied here. +func (c *TransactionStatusCache) CaptureSnapshotThrough(through uint64) (TransactionStatusSnapshot, error) { if c == nil { return nil, nil } @@ -700,7 +722,29 @@ func (c *TransactionStatusCache) SnapshotThrough(through uint64) ([]byte, error) if rootedSinceSeed > maxTransactionStatusRoots { rootedSinceSeed = maxTransactionStatusRoots } - return marshalTransactionStatusNodes(nodes, uint16(rootedSinceSeed), complete, c.coverageFromGenesis) + owned := make([]transactionStatusNode, len(nodes)) + pinned := make([]*transactionStatusNode, len(nodes)) + for i, node := range nodes { + owned[i] = *node + // The encoder consumes only these node deltas. Do not keep the old + // parent chain, which could retain roots excluded from this snapshot. + owned[i].parent = nil + pinned[i] = &owned[i] + } + return &transactionStatusSnapshot{ + nodes: pinned, rootedSinceSeed: uint16(rootedSinceSeed), complete: complete, + coverageFromGenesis: c.coverageFromGenesis, + }, nil +} + +// SnapshotThrough is the synchronous convenience API. Serialization still +// happens after releasing the cache lock; normal folds use CaptureSnapshotThrough. +func (c *TransactionStatusCache) SnapshotThrough(through uint64) ([]byte, error) { + snapshot, err := c.CaptureSnapshotThrough(through) + if err != nil || snapshot == nil { + return nil, err + } + return snapshot.MarshalBinary() } func (c *TransactionStatusCache) processedSlotLocked(blockhash solana.Hash, key transactionStatusKey) uint64 { diff --git a/pkg/replay/transaction_status_capture_bench_test.go b/pkg/replay/transaction_status_capture_bench_test.go new file mode 100644 index 000000000..64bf7a7eb --- /dev/null +++ b/pkg/replay/transaction_status_capture_bench_test.go @@ -0,0 +1,66 @@ +package replay + +import ( + "crypto/sha256" + "encoding/binary" + "testing" + + "github.com/gagliardetto/solana-go" +) + +var checkpointBenchmarkPayload []byte +var checkpointBenchmarkCapture TransactionStatusSnapshot + +func BenchmarkTransactionStatusCheckpointCapture(b *testing.B) { + // A private, not-yet-published fixture with the same complete 300-root + // metadata as an imported cache. 1.5 million keys encode to roughly 30 MB. + c := newTransactionStatusCache(true) + c.coverageFromGenesis = false + c.rootedSinceSeed = maxTransactionStatusRoots + c.rootedThrough = maxTransactionStatusRoots + for slot := uint64(1); slot <= maxTransactionStatusRoots; slot++ { + keys := make(map[transactionStatusKey]struct{}, 5000) + var seed [16]byte + binary.LittleEndian.PutUint64(seed[:8], slot) + for i := uint64(0); i < 5000; i++ { + binary.LittleEndian.PutUint64(seed[8:], i) + hash := sha256.Sum256(seed[:]) + var key transactionStatusKey + copy(key[:], hash[:]) + keys[key] = struct{}{} + } + c.tip = &transactionStatusNode{slot: slot, parent: c.tip, + delta: transactionStatusDelta{solana.Hash{1}: {keyIndex: 7, keys: keys}}} + } + view, err := c.CaptureSnapshotThrough(maxTransactionStatusRoots) + if err != nil { + b.Fatal(err) + } + b.Run("SynchronousBaseline", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + checkpointBenchmarkPayload, err = legacyStatusSnapshotForTest(c, maxTransactionStatusRoots) + if err != nil { + b.Fatal(err) + } + } + }) + b.Run("CaptureOnReplay", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + checkpointBenchmarkCapture, err = c.CaptureSnapshotThrough(maxTransactionStatusRoots) + if err != nil { + b.Fatal(err) + } + } + }) + b.Run("EncodeOnWorker", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + checkpointBenchmarkPayload, err = view.MarshalBinary() + if err != nil { + b.Fatal(err) + } + } + }) +} diff --git a/pkg/replay/transaction_status_capture_test.go b/pkg/replay/transaction_status_capture_test.go new file mode 100644 index 000000000..755fa9dfd --- /dev/null +++ b/pkg/replay/transaction_status_capture_test.go @@ -0,0 +1,162 @@ +package replay + +import ( + "encoding/binary" + "sync" + "testing" + "time" + + b "github.com/Overclock-Validator/mithril/pkg/block" + "github.com/Overclock-Validator/mithril/pkg/txstatus" + "github.com/gagliardetto/solana-go" + "github.com/stretchr/testify/require" +) + +// Keep the pre-split selection/metadata calculation as a differential oracle. +// The wire encoder itself did not change. +func legacyStatusSnapshotForTest(c *TransactionStatusCache, through uint64) ([]byte, error) { + c.mu.RLock() + defer c.mu.RUnlock() + nodes := c.nodesThroughLocked(through) + if len(nodes) > maxTransactionStatusRoots { + nodes = nodes[len(nodes)-maxTransactionStatusRoots:] + } + rooted := uint32(c.rootedSinceSeed) + uint32(c.countNodesBetweenLocked(c.rootedThrough, through)) + complete := c.coverageComplete || rooted >= maxTransactionStatusRoots + if rooted > maxTransactionStatusRoots { + rooted = maxTransactionStatusRoots + } + return marshalTransactionStatusNodes(nodes, uint16(rooted), complete, c.coverageFromGenesis) +} + +func importedStatusCacheForTest(t *testing.T) *TransactionStatusCache { + t.Helper() + roots := make([]txstatus.SnapshotSlotDelta, maxTransactionStatusRoots) + for i := range roots { + roots[i] = txstatus.SnapshotSlotDelta{Slot: uint64(i + 1), IsRoot: true} + } + c, err := NewTransactionStatusCacheFromAgaveSnapshot(roots, maxTransactionStatusRoots) + require.NoError(t, err) + return c +} + +func captureTestBlock(slot uint64, branch byte) *b.Block { + tx := statusCacheTestTransaction(1, 2, branch) + data := make([]byte, 9) + binary.LittleEndian.PutUint64(data, slot) + data[8] = branch + tx.Message.Instructions[0].Data = data + return statusCacheTestBlock(slot, tx) +} + +func TestTransactionStatusCaptureSurvivesConcurrentPruneAndUnwind(t *testing.T) { + c := importedStatusCacheForTest(t) + for slot := uint64(301); slot <= 350; slot++ { + require.NoError(t, c.CommitBlock(captureTestBlock(slot, 1))) + } + want, err := legacyStatusSnapshotForTest(c, 320) + require.NoError(t, err) + captured, err := c.CaptureSnapshotThrough(320) + require.NoError(t, err) + view := captured.(*transactionStatusSnapshot) + require.Len(t, view.nodes, maxTransactionStatusRoots) + require.Equal(t, uint64(21), view.nodes[0].slot) + require.Equal(t, uint64(320), view.nodes[len(view.nodes)-1].slot) + for _, node := range view.nodes { + require.Nil(t, node.parent, "capture retained excluded ancestry") + } + + var wg sync.WaitGroup + wg.Add(1) + errs := make(chan error, 1) + go func() { + defer wg.Done() + for slot := uint64(351); slot <= 750; slot++ { + if err := c.CommitBlock(captureTestBlock(slot, 1)); err != nil { + errs <- err + return + } + c.Root(slot - 20) + } + if err := c.Unwind(741); err != nil { + errs <- err + return + } + for slot := uint64(741); slot <= 755; slot++ { + if err := c.CommitBlock(captureTestBlock(slot, 2)); err != nil { + errs <- err + return + } + } + }() + for i := 0; i < 50; i++ { + got, err := captured.MarshalBinary() + if err != nil || string(want) != string(got) { + t.Errorf("captured bytes changed during replay: %v", err) + break + } + } + wg.Wait() + close(errs) + for err := range errs { + require.NoError(t, err) + } + got, err := captured.MarshalBinary() + require.NoError(t, err) + require.Equal(t, want, got) + restored, err := NewTransactionStatusCacheFromSnapshot(got) + require.NoError(t, err) + require.Equal(t, uint64(320), restored.RootedThrough()) + require.True(t, restored.CoverageComplete()) + retry := statusCacheTestBlock(321, captureTestBlock(301, 1).Transactions[0]) + require.Error(t, restored.ValidateBlock(retry), "captured ancestor was forgotten") + // A bank after the capture's through-slot must not leak into recovery. + future := statusCacheTestBlock(321, captureTestBlock(350, 1).Transactions[0]) + require.NoError(t, restored.ValidateBlock(future)) +} + +func TestTransactionStatusCapturePreservesCoverageAndOwnedBytes(t *testing.T) { + for _, complete := range []bool{false, true} { + c := newTransactionStatusCache(complete) + // Exercise metadata selection without changing its pre-existing rules. + for slot := uint64(1); slot <= 310; slot++ { + c.tip = &transactionStatusNode{slot: slot, parent: c.tip, + delta: transactionStatusDelta{solana.Hash{1}: {keyIndex: 7, keys: map[transactionStatusKey]struct{}{{byte(slot), byte(slot >> 8)}: {}}}}} + } + for _, through := range []uint64{0, 1, 299, 300, 310, 400} { + want, err := legacyStatusSnapshotForTest(c, through) + require.NoError(t, err) + view, err := c.CaptureSnapshotThrough(through) + require.NoError(t, err) + got, err := view.MarshalBinary() + require.NoError(t, err) + require.Equal(t, want, got) + got[0] ^= 0xff + again, err := view.MarshalBinary() + require.NoError(t, err) + require.Equal(t, want, again, "caller mutated the captured data through encoded bytes") + } + } + var absent *TransactionStatusCache + view, err := absent.CaptureSnapshotThrough(1) + require.NoError(t, err) + require.Nil(t, view) +} + +func TestTransactionStatusCaptureEncodingDoesNotLockLiveCache(t *testing.T) { + c := importedStatusCacheForTest(t) + require.NoError(t, c.CommitBlock(captureTestBlock(301, 1))) + view, err := c.CaptureSnapshotThrough(301) + require.NoError(t, err) + c.mu.Lock() + done := make(chan error, 1) + go func() { _, err := view.MarshalBinary(); done <- err }() + select { + case err := <-done: + c.mu.Unlock() + require.NoError(t, err) + case <-time.After(2 * time.Second): + c.mu.Unlock() + t.Fatal("checkpoint encoding waited for the live cache lock") + } +} From f1a15c25631abaa7f7012b637ae781681ead524d Mon Sep 17 00:00:00 2001 From: 7layermagik <7layermagik@users.noreply.github.com> Date: Mon, 14 Sep 2026 14:51:49 -0500 Subject: [PATCH 02/16] Expire transaction-status groups in batches on durable promotion --- docs/transaction-status-expiry.md | 47 +++++++ pkg/replay/transaction_status_cache.go | 63 ++++++++- pkg/replay/transaction_status_expiry_test.go | 129 +++++++++++++++++++ 3 files changed, 236 insertions(+), 3 deletions(-) create mode 100644 docs/transaction-status-expiry.md create mode 100644 pkg/replay/transaction_status_expiry_test.go diff --git a/docs/transaction-status-expiry.md b/docs/transaction-status-expiry.md new file mode 100644 index 000000000..4d3f665a4 --- /dev/null +++ b/docs/transaction-status-expiry.md @@ -0,0 +1,47 @@ +# Batched transaction-status expiry + +Applying an asynchronous checkpoint still calls `TransactionStatusCache.Root` +on replay. Live Zen 5 instruction probes measured 43–101 ms inside that function. +The previous expiry path visited every key in every retired bank, even when an +entire recent-blockhash group could be discarded. + +Expiry now examines the expired and retained bank deltas by blockhash. It drops +fully expired groups directly. For a group spanning the cutoff, it either +subtracts the expired keys or rebuilds the visible reference counts from the +retained deltas, whichever requires fewer key visits. Retained unrooted banks +are included. Physical map reclamation is still Go GC work; this is not a claim +that memory reclamation costs disappear. + +The 300-root retention rule, immediate logical expiry, duplicate-key reference +counts, selected-parent validation, checkpoint format and immutable producer +views are unchanged. All index changes remain under the existing cache lock. +This does not move unsafe mutable state to another goroutine or delay expiry. +A long-lived blockhash with many transactions on both sides of the cutoff can +still require substantial per-key work. This patch reduces that work to the +smaller side; it does not give a constant-time worst-case bound. + +## Validation + +The replay race suite, replay vet and validator production build pass. New tests +compare exact visible indexes against the original per-key removal for 100 +random lineages with shared hashes, collisions and empty groups, then unwind +surviving banks. A Root integration test checks pinned producer views, +checkpoint bytes, restored duplicate detection and rooted-unwind rejection. + +M4 Pro, Go benchmark, single caller, two iterations per case. Each iteration +expires 128 banks of 33,760 unique keys (4,321,280 entries) and retains another +33,760 entries. Setup is outside the timer. The baseline invokes the original +per-key removal; the new path invokes batched expiry. These are **expiry-path** +measurements, not end-to-end Root/replay or a prediction of live FAST scores. + +| Recent-blockhash grouping | Old expiry | Batched expiry | +|---|---:|---:| +| Groups shared by four expired banks | 185–189 ms | 0.037–0.080 ms | +| One fully expired group | 604–614 ms | 0.025–0.026 ms | +| One group shared by expired and retained banks | 590 ms | 1.63–2.36 ms | + +Run `go test ./pkg/replay -run '^$' -bench '^BenchmarkTransactionStatusBatchExpiry$' -benchtime=1x -count=2`. + +Prepared on `7layer/status-expiry-performance` above the isolated Votor fix. +This source is not the exact live FEC-integrated source. No deployment or public +PR change is implied by these local results. diff --git a/pkg/replay/transaction_status_cache.go b/pkg/replay/transaction_status_cache.go index 03437ba02..bf043fb17 100644 --- a/pkg/replay/transaction_status_cache.go +++ b/pkg/replay/transaction_status_cache.go @@ -883,10 +883,8 @@ func (c *TransactionStatusCache) pruneLocked(through uint64) { if drop <= 0 { return } - for _, node := range nodes[:drop] { - c.removeDeltaVisibleLocked(node.delta) - } retained := nodes[drop:] + c.expireVisibleLocked(nodes[:drop], retained) var parent *transactionStatusNode for _, old := range retained { parent = &transactionStatusNode{ @@ -897,6 +895,65 @@ func (c *TransactionStatusCache) pruneLocked(through uint64) { c.tip = parent } +// expireVisibleLocked expires a whole rooted batch. Most old blockhash groups +// have no surviving bank and can be removed without visiting their transaction +// keys. For a group crossing the boundary, update whichever side is smaller. +// Immutable node deltas (including those pinned by producer views/checkpoints) +// are never mutated. Unrooted retained banks count as survivors too. +func (c *TransactionStatusCache) expireVisibleLocked(expired, retained []*transactionStatusNode) { + type groupExpiry struct { + expiredKeys int + retainedKeys int + survivors []*transactionStatusGroup + } + groups := make(map[solana.Hash]*groupExpiry) + for _, node := range expired { + for hash, delta := range node.delta { + g := groups[hash] + if g == nil { + g = &groupExpiry{} + groups[hash] = g + } + g.expiredKeys += len(delta.keys) + } + } + for _, node := range retained { + for hash, delta := range node.delta { + if g := groups[hash]; g != nil { + g.retainedKeys += len(delta.keys) + g.survivors = append(g.survivors, delta) + } + } + } + for hash, g := range groups { + if len(g.survivors) == 0 { + delete(c.visible, hash) + } else if g.retainedKeys < g.expiredKeys { + rebuilt := &visibleTransactionStatusGroup{keyIndex: g.survivors[0].keyIndex, keys: make(map[transactionStatusKey]uint16)} + for _, delta := range g.survivors { + for key := range delta.keys { + rebuilt.keys[key]++ + } + } + c.visible[hash] = rebuilt + if len(rebuilt.keys) == 0 { + delete(c.visible, hash) + } + } + } + for _, node := range expired { + for hash, delta := range node.delta { + g := groups[hash] + if len(g.survivors) == 0 || g.retainedKeys < g.expiredKeys { + continue + } + // The existing removal path preserves reference counts for keys + // occurring in more than one retained/expired bank. + c.removeDeltaVisibleLocked(transactionStatusDelta{hash: delta}) + } + } +} + func sliceTransactionStatusKey(messageHash [32]byte, keyIndex uint8) transactionStatusKey { // Match Agave's saturating_sub(CACHED_KEY_SIZE + 1), including its // deliberate exclusion of the final possible starting offset. diff --git a/pkg/replay/transaction_status_expiry_test.go b/pkg/replay/transaction_status_expiry_test.go new file mode 100644 index 000000000..84a11cd28 --- /dev/null +++ b/pkg/replay/transaction_status_expiry_test.go @@ -0,0 +1,129 @@ +package replay + +import ( + "encoding/binary" + "fmt" + "github.com/gagliardetto/solana-go" + "github.com/stretchr/testify/require" + "math/rand" + "testing" +) + +func TestTransactionStatusBatchExpiryMatchesPerKeyRemoval(t *testing.T) { + for seed := int64(0); seed < 100; seed++ { + rng := rand.New(rand.NewSource(seed)) + fast, ref := NewTransactionStatusCache(), NewTransactionStatusCache() + var nodes []*transactionStatusNode + for slot := 0; slot < 40; slot++ { + d := make(transactionStatusDelta) + for j := 0; j < 6; j++ { + h := solana.Hash{byte(rng.Intn(12))} + g := &transactionStatusGroup{keyIndex: h[0], keys: make(map[transactionStatusKey]struct{})} + for k := 0; k < rng.Intn(30); k++ { + g.keys[transactionStatusKey{byte(rng.Intn(40))}] = struct{}{} + } + d[h] = g + } + nodes = append(nodes, &transactionStatusNode{slot: uint64(slot), delta: d}) + require.NoError(t, fast.addDeltaVisibleLocked(d)) + require.NoError(t, ref.addDeltaVisibleLocked(d)) + } + cut := 1 + rng.Intn(len(nodes)-1) + fast.expireVisibleLocked(nodes[:cut], nodes[cut:]) + for _, n := range nodes[:cut] { + ref.removeDeltaVisibleLocked(n.delta) + } + require.Equal(t, ref.visible, fast.visible, "seed %d", seed) + for i := len(nodes) - 1; i >= cut; i-- { + fast.removeDeltaVisibleLocked(nodes[i].delta) + ref.removeDeltaVisibleLocked(nodes[i].delta) + } + require.Equal(t, ref.visible, fast.visible, "unwind seed %d", seed) + } +} + +func TestTransactionStatusBatchExpiryPinnedViewsAndSnapshot(t *testing.T) { + c := NewTransactionStatusCache() + old := statusCacheTestTransaction(1, 1, 1) + keep := statusCacheTestTransaction(2, 2, 2) + require.NoError(t, c.CommitBlock(statusCacheTestBlock(1, old))) + for slot := uint64(2); slot <= maxTransactionStatusRoots+1; slot++ { + blk := statusCacheTestBlock(slot) + if slot == maxTransactionStatusRoots+1 { + blk.Transactions = append(blk.Transactions, keep) + } + require.NoError(t, c.CommitBlock(blk)) + } + pinned := c.View() + snapshot, err := c.CaptureSnapshotThrough(maxTransactionStatusRoots + 1) + require.NoError(t, err) + before, err := snapshot.MarshalBinary() + require.NoError(t, err) + c.coverageComplete = false // Exercise completion once 300 banks become rooted. + c.Root(maxTransactionStatusRoots + 1) + after, err := snapshot.MarshalBinary() + require.NoError(t, err) + require.Equal(t, before, after) + found, err := pinned.ContainsTransaction(old) + require.NoError(t, err) + require.True(t, found) + found, err = c.View().ContainsTransaction(old) + require.NoError(t, err) + require.False(t, found) + require.NoError(t, c.ValidateBlock(statusCacheTestBlock(maxTransactionStatusRoots+2, old))) + require.Error(t, c.ValidateBlock(statusCacheTestBlock(maxTransactionStatusRoots+2, keep))) + blob, err := c.SnapshotThrough(maxTransactionStatusRoots + 1) + require.NoError(t, err) + restored, err := NewTransactionStatusCacheFromSnapshot(blob) + require.NoError(t, err) + require.NoError(t, restored.ValidateBlock(statusCacheTestBlock(maxTransactionStatusRoots+2, old))) + require.Error(t, restored.ValidateBlock(statusCacheTestBlock(maxTransactionStatusRoots+2, keep))) + require.Error(t, c.Unwind(maxTransactionStatusRoots+1)) +} + +func BenchmarkTransactionStatusBatchExpiry(b *testing.B) { + for _, shape := range []string{"four-bank-groups", "one-expired-group", "crossing-group"} { + for _, legacy := range []bool{true, false} { + b.Run(fmt.Sprintf("%s/legacy=%t", shape, legacy), func(b *testing.B) { + for i := 0; i < b.N; i++ { + b.StopTimer() + c := NewTransactionStatusCache() + var expired, retained []*transactionStatusNode + for slot := 0; slot < 129; slot++ { + var h solana.Hash + if shape == "four-bank-groups" || (shape == "one-expired-group" && slot == 128) { + binary.LittleEndian.PutUint64(h[:], uint64(slot/4+1)) + } + g := &transactionStatusGroup{keys: make(map[transactionStatusKey]struct{})} + for k := 0; k < 33760; k++ { + var key transactionStatusKey + binary.LittleEndian.PutUint64(key[:], uint64(slot*33760+k)) + g.keys[key] = struct{}{} + } + n := &transactionStatusNode{delta: transactionStatusDelta{h: g}} + if err := c.addDeltaVisibleLocked(n.delta); err != nil { + b.Fatal(err) + } + if slot < 128 { + expired = append(expired, n) + } else { + retained = append(retained, n) + } + } + b.StartTimer() + if legacy { + for _, n := range expired { + c.removeDeltaVisibleLocked(n.delta) + } + } else { + c.expireVisibleLocked(expired, retained) + } + b.StopTimer() + if len(c.visible) != 1 { + b.Fatal("retained group missing") + } + } + }) + } + } +} From 852aae99c91197d2093ec1bc6778b1bd86ed5a09 Mon Sep 17 00:00:00 2001 From: 7layermagik <7layermagik@users.noreply.github.com> Date: Mon, 14 Sep 2026 15:05:52 -0500 Subject: [PATCH 03/16] Record native Zen 5 expiry benchmark results --- docs/transaction-status-expiry.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/transaction-status-expiry.md b/docs/transaction-status-expiry.md index 4d3f665a4..179202360 100644 --- a/docs/transaction-status-expiry.md +++ b/docs/transaction-status-expiry.md @@ -45,3 +45,28 @@ Run `go test ./pkg/replay -run '^$' -bench '^BenchmarkTransactionStatusBatchExpi Prepared on `7layer/status-expiry-performance` above the isolated Votor fix. This source is not the exact live FEC-integrated source. No deployment or public PR change is implied by these local results. + +## Zen 5 validation — 20:05 UTC + +Native AMD Ryzen 7 9700X tests used an isolated copy of the preserved live +FEC-integrated source at `/srv/mithril-status-expiry-test-20260914/source`. +The original status-cache file was byte-identical to the change's parent. +Only the reviewed cache source and new tests were overlaid, with SHA256 checks. +The full replay race suite passed (2.189 s), and vet passed. No binary was deployed. + +Benchmarks ran with GOMAXPROCS=2, nice=15, one caller and three iterations per +case, while the validator and loader remained active. Setup and later GC are +excluded from the expiry timer. Each case expires 4,321,280 entries (128 banks +of 33,760) and retains 33,760 entries. These synthetic batches exceed the earlier +live stall samples and are not an end-to-end replay or FAST-score comparison. + +| Shape | Original expiry | New expiry | +|---|---:|---:| +| Four-bank blockhash groups | 306–311 ms | 0.049–0.057 ms | +| One fully expired blockhash group | 700–718 ms | 0.024–0.031 ms | +| Group crossing the retention boundary | 717–735 ms | 1.85–2.05 ms | + +At 20:05:13 UTC the enrolled validator PID 291548 was at RPC/local slot 3,708,093, +last vote 3,708,092. Loader unpaused; validator, loader, FAST and Titan services +all active. Source/implementation and deployment status remain unchanged. +See zen5-benchmark.log, zen5-race.log, zen5-vet.log and zen5-health.json. From 6e5278985d415063cdd902899333ab014e9e74e9 Mon Sep 17 00:00:00 2001 From: 7layermagik <7layermagik@users.noreply.github.com> Date: Mon, 14 Sep 2026 20:45:28 -0500 Subject: [PATCH 04/16] Document isolated PR validation and retain relevant benchmark evidence --- .gitattributes | 7 ++ .../pr-split-2026-09-15/status/README.md | 7 ++ .../status/status-tests.log | 1 + .../pr-split-2026-09-15/status/status-vet.log | 0 .../baseline-failure-comparison.json | 26 ++++ .../2026-09-14/checkpoint-live.json | 118 ++++++++++++++++++ .../checkpoint-native-benchmark.log | 15 +++ .../2026-09-14/zen5-benchmark.log | 24 ++++ .../status-cache/2026-09-14/zen5-race.log | 1 + .../status-cache/2026-09-14/zen5-vet.log | 0 docs/status-checkpoint-capture.md | 7 ++ 11 files changed, 206 insertions(+) create mode 100644 .gitattributes create mode 100644 docs/results/pr-split-2026-09-15/status/README.md create mode 100644 docs/results/pr-split-2026-09-15/status/status-tests.log create mode 100644 docs/results/pr-split-2026-09-15/status/status-vet.log create mode 100644 docs/results/status-cache/2026-09-14/baseline-failure-comparison.json create mode 100644 docs/results/status-cache/2026-09-14/checkpoint-live.json create mode 100644 docs/results/status-cache/2026-09-14/checkpoint-native-benchmark.log create mode 100644 docs/results/status-cache/2026-09-14/zen5-benchmark.log create mode 100644 docs/results/status-cache/2026-09-14/zen5-race.log create mode 100644 docs/results/status-cache/2026-09-14/zen5-vet.log create mode 100644 docs/status-checkpoint-capture.md diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..d64360d9a --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +# Keep raw benchmark evidence available without overwhelming the review. +# Retain raw Go CPU padding and test-framework space/tab indentation. +docs/results/**/*.json linguist-generated=true +docs/results/**/*.jsonl linguist-generated=true +docs/results/**/*.txt linguist-generated=true whitespace=-blank-at-eol +docs/results/**/*.log linguist-generated=true -whitespace +docs/results/**/*.tar.gz linguist-generated=true diff --git a/docs/results/pr-split-2026-09-15/status/README.md b/docs/results/pr-split-2026-09-15/status/README.md new file mode 100644 index 000000000..134adf562 --- /dev/null +++ b/docs/results/pr-split-2026-09-15/status/README.md @@ -0,0 +1,7 @@ +# Review branch validation, September 15 + +This PR is split from #279 plus the later working-tree improvements. The tested code commit before this documentation commit was `a111ba892fb88ea95768930f56dd2f9a5091e6ce`. Tests ran locally on Apple M4 Pro, Go1.26.4, GOMAXPROCS3 and package parallelism2. Logs beside this file are the fresh split-branch checks, not native measurements. Existing Zen5 benchmark documents retain their original baselines and scope. No validator restart or deployment occurred during this reorganization. + +Four independent branches start at current alpenglow-dev33dde405. Voting is based on the certificate-processing PR; leader packing is based on the streaming-preparation PR. Runtime changes and status-cache changes are independent. Shared CLI/configuration additions need an ordinary three-file merge reconciliation when combining leader packing and voting. A separate audit checkout reconciled these additions and matched the preserved full implementation exactly across Go sources, module files, TOML configuration and CI. + +The branch-specific race suites and vet passed. The combined audit has a separately documented pre-existing intermittent peer reconnect timeout; this is not reported as an entirely green combined race run. diff --git a/docs/results/pr-split-2026-09-15/status/status-tests.log b/docs/results/pr-split-2026-09-15/status/status-tests.log new file mode 100644 index 000000000..b6b40ef23 --- /dev/null +++ b/docs/results/pr-split-2026-09-15/status/status-tests.log @@ -0,0 +1 @@ +ok github.com/Overclock-Validator/mithril/pkg/replay 4.657s diff --git a/docs/results/pr-split-2026-09-15/status/status-vet.log b/docs/results/pr-split-2026-09-15/status/status-vet.log new file mode 100644 index 000000000..e69de29bb diff --git a/docs/results/status-cache/2026-09-14/baseline-failure-comparison.json b/docs/results/status-cache/2026-09-14/baseline-failure-comparison.json new file mode 100644 index 000000000..5353cc6eb --- /dev/null +++ b/docs/results/status-cache/2026-09-14/baseline-failure-comparison.json @@ -0,0 +1,26 @@ +{ + "failed_tests": [ + "TestExecute_Tx_BpfLoader_Write_Success", + "TestExecute_Tx_BpfLoader_Write_Offset_Too_Large_Failure", + "TestExecute_Tx_BpfLoader_Write_Buffer_Authority_Didnt_Sign_Failure", + "TestExecute_Tx_BpfLoader_Write_Incorrect_Authority_Failure", + "TestExecute_Tx_BpfLoader_SetAuthority_Not_Enough_Instr_Accts_Failure", + "TestExecute_Tx_BpfLoader_SetAuthorityChecked_Not_Enough_Instr_Accts_Failure", + "TestExecute_Tx_BpfLoader_SetAuthorityChecked_Buffer_Success", + "TestExecute_Tx_BpfLoader_SetAuthorityChecked_ProgramData_Success", + "TestExecute_Tx_BpfLoader_SetAuthorityChecked_Buffer_Immutable_Failure", + "TestExecute_Tx_BpfLoader_SetAuthorityChecked_Buffer_Wrong_Upgrade_Authority_Failure", + "TestExecute_Tx_BpfLoader_SetAuthorityChecked_Buffer_Authority_Didnt_Sign_Failure", + "TestExecute_Tx_BpfLoader_SetAuthorityChecked_Buffer_New_Authority_Didnt_Sign_Failure", + "TestExecute_Tx_BpfLoader_SetAuthorityChecked_Buffer_Uninitialized_Account_Failure", + "TestExecute_Tx_BpfLoader_SetAuthorityChecked_ProgramData_Immutable_Failure", + "TestExecute_Tx_BpfLoader_SetAuthorityChecked_ProgramData_Authority_Didnt_Sign_Failure", + "TestExecute_Tx_BpfLoader_SetAuthorityChecked_ProgramData_New_Authority_Didnt_Sign_Failure", + "TestExecute_Tx_BpfLoader_SetAuthorityChecked_ProgramData_Wrong_Authority_Failure", + "TestExecute_Tx_BpfLoader_Close_Buffer_Not_Enough_Accounts", + "TestExecute_Tx_BpfLoader_Close_ProgramData_Success" + ], + "same_failed_test_names": true, + "same_panic_function": "UpgradeableLoaderClose", + "same_failed_test_names_on_native_base": true +} diff --git a/docs/results/status-cache/2026-09-14/checkpoint-live.json b/docs/results/status-cache/2026-09-14/checkpoint-live.json new file mode 100644 index 000000000..0072e9189 --- /dev/null +++ b/docs/results/status-cache/2026-09-14/checkpoint-live.json @@ -0,0 +1,118 @@ +{ + "utc": "2026-09-14T03:34:45Z", + "count": 10, + "log_bytes": 1607896, + "rows": [ + { + "slots": 128, + "through": 3427284, + "worker_ms": 331.0, + "capture_us": 34.174, + "encode_ms": 236.108854, + "bytes": 31488298, + "line": "(+ 28s) async fold: committed 128 slots through 3427284 in 331ms checkpoint_capture=34.174\u00b5s checkpoint_encode=236.108854ms checkpoint_bytes=31488298" + }, + { + "slots": 128, + "through": 3427428, + "worker_ms": 264.0, + "capture_us": 30.366, + "encode_ms": 182.148884, + "bytes": 25219543, + "line": "(+ 59s) async fold: committed 128 slots through 3427428 in 264ms checkpoint_capture=30.366\u00b5s checkpoint_encode=182.148884ms checkpoint_bytes=25219543" + }, + { + "slots": 128, + "through": 3427569, + "worker_ms": 318.0, + "capture_us": 30.417, + "encode_ms": 225.601155, + "bytes": 29886172, + "line": "(+ 1m29s) async fold: committed 128 slots through 3427569 in 318ms checkpoint_capture=30.417\u00b5s checkpoint_encode=225.601155ms checkpoint_bytes=29886172" + }, + { + "slots": 128, + "through": 3427701, + "worker_ms": 258.0, + "capture_us": 29.495, + "encode_ms": 179.07318000000004, + "bytes": 24530250, + "line": "(+ 1m58s) async fold: committed 128 slots through 3427701 in 258ms checkpoint_capture=29.495\u00b5s checkpoint_encode=179.07318ms checkpoint_bytes=24530250" + }, + { + "slots": 128, + "through": 3427841, + "worker_ms": 305.0, + "capture_us": 30.888, + "encode_ms": 216.143473, + "bytes": 29469092, + "line": "(+ 2m28s) async fold: committed 128 slots through 3427841 in 305ms checkpoint_capture=30.888\u00b5s checkpoint_encode=216.143473ms checkpoint_bytes=29469092" + }, + { + "slots": 128, + "through": 3427989, + "worker_ms": 286.0, + "capture_us": 28.694, + "encode_ms": 201.186937, + "bytes": 27782921, + "line": "(+ 2m59s) async fold: committed 128 slots through 3427989 in 286ms checkpoint_capture=28.694\u00b5s checkpoint_encode=201.186937ms checkpoint_bytes=27782921" + }, + { + "slots": 128, + "through": 3428133, + "worker_ms": 376.0, + "capture_us": 28.874, + "encode_ms": 278.613922, + "bytes": 35692638, + "line": "(+ 3m29s) async fold: committed 128 slots through 3428133 in 376ms checkpoint_capture=28.874\u00b5s checkpoint_encode=278.613922ms checkpoint_bytes=35692638" + }, + { + "slots": 128, + "through": 3428269, + "worker_ms": 431.0, + "capture_us": 32.671, + "encode_ms": 329.865906, + "bytes": 37615609, + "line": "(+ 3m58s) async fold: committed 128 slots through 3428269 in 431ms checkpoint_capture=32.671\u00b5s checkpoint_encode=329.865906ms checkpoint_bytes=37615609" + }, + { + "slots": 128, + "through": 3428409, + "worker_ms": 403.0, + "capture_us": 31.108, + "encode_ms": 301.569555, + "bytes": 38403988, + "line": "(+ 4m28s) async fold: committed 128 slots through 3428409 in 403ms checkpoint_capture=31.108\u00b5s checkpoint_encode=301.569555ms checkpoint_bytes=38403988" + }, + { + "slots": 128, + "through": 3428549, + "worker_ms": 477.0, + "capture_us": 34.464, + "encode_ms": 366.581817, + "bytes": 43443732, + "line": "(+ 4m58s) async fold: committed 128 slots through 3428549 in 477ms checkpoint_capture=34.464\u00b5s checkpoint_encode=366.581817ms checkpoint_bytes=43443732" + } + ], + "capture_us": { + "min": 28.694, + "median": 30.652500000000003, + "max": 34.464 + }, + "encode_ms": { + "min": 179.07318000000004, + "median": 230.8550045, + "max": 366.581817 + }, + "bytes": { + "min": 24530250, + "median": 30687235.0, + "max": 43443732 + }, + "worker_ms": { + "min": 258.0, + "median": 324.5, + "max": 477.0 + }, + "error_lines": [] +} diff --git a/docs/results/status-cache/2026-09-14/checkpoint-native-benchmark.log b/docs/results/status-cache/2026-09-14/checkpoint-native-benchmark.log new file mode 100644 index 000000000..33f4c2cd6 --- /dev/null +++ b/docs/results/status-cache/2026-09-14/checkpoint-native-benchmark.log @@ -0,0 +1,15 @@ +goos: linux +goarch: amd64 +pkg: github.com/Overclock-Validator/mithril/pkg/replay +cpu: AMD Ryzen 7 9700X 8-Core Processor +BenchmarkTransactionStatusCheckpointCapture/SynchronousBaseline 1 206554597 ns/op 99120032 B/op 2731 allocs/op +BenchmarkTransactionStatusCheckpointCapture/SynchronousBaseline 1 207352232 ns/op 99120072 B/op 2732 allocs/op +BenchmarkTransactionStatusCheckpointCapture/SynchronousBaseline 1 202442401 ns/op 99120048 B/op 2731 allocs/op +BenchmarkTransactionStatusCheckpointCapture/CaptureOnReplay 47412 5936 ns/op 35168 B/op 11 allocs/op +BenchmarkTransactionStatusCheckpointCapture/CaptureOnReplay 39506 6057 ns/op 35168 B/op 11 allocs/op +BenchmarkTransactionStatusCheckpointCapture/CaptureOnReplay 37850 5922 ns/op 35168 B/op 11 allocs/op +BenchmarkTransactionStatusCheckpointCapture/EncodeOnWorker 2 202522908 ns/op 99108084 B/op 2723 allocs/op +BenchmarkTransactionStatusCheckpointCapture/EncodeOnWorker 2 201841085 ns/op 99108076 B/op 2723 allocs/op +BenchmarkTransactionStatusCheckpointCapture/EncodeOnWorker 1 200834149 ns/op 99108104 B/op 2724 allocs/op +PASS +ok github.com/Overclock-Validator/mithril/pkg/replay 3.133s diff --git a/docs/results/status-cache/2026-09-14/zen5-benchmark.log b/docs/results/status-cache/2026-09-14/zen5-benchmark.log new file mode 100644 index 000000000..0229c19af --- /dev/null +++ b/docs/results/status-cache/2026-09-14/zen5-benchmark.log @@ -0,0 +1,24 @@ +goos: linux +goarch: amd64 +pkg: github.com/Overclock-Validator/mithril/pkg/replay +cpu: AMD Ryzen 7 9700X 8-Core Processor +BenchmarkTransactionStatusBatchExpiry/four-bank-groups/legacy=true-2 1 308302685 ns/op +BenchmarkTransactionStatusBatchExpiry/four-bank-groups/legacy=true-2 1 311104834 ns/op +BenchmarkTransactionStatusBatchExpiry/four-bank-groups/legacy=true-2 1 306059722 ns/op +BenchmarkTransactionStatusBatchExpiry/four-bank-groups/legacy=false-2 1 56685 ns/op +BenchmarkTransactionStatusBatchExpiry/four-bank-groups/legacy=false-2 1 49743 ns/op +BenchmarkTransactionStatusBatchExpiry/four-bank-groups/legacy=false-2 1 48581 ns/op +BenchmarkTransactionStatusBatchExpiry/one-expired-group/legacy=true-2 1 700015562 ns/op +BenchmarkTransactionStatusBatchExpiry/one-expired-group/legacy=true-2 1 718445604 ns/op +BenchmarkTransactionStatusBatchExpiry/one-expired-group/legacy=true-2 1 716785553 ns/op +BenchmarkTransactionStatusBatchExpiry/one-expired-group/legacy=false-2 1 30758 ns/op +BenchmarkTransactionStatusBatchExpiry/one-expired-group/legacy=false-2 1 26119 ns/op +BenchmarkTransactionStatusBatchExpiry/one-expired-group/legacy=false-2 1 24436 ns/op +BenchmarkTransactionStatusBatchExpiry/crossing-group/legacy=true-2 1 734956959 ns/op +BenchmarkTransactionStatusBatchExpiry/crossing-group/legacy=true-2 1 722327226 ns/op +BenchmarkTransactionStatusBatchExpiry/crossing-group/legacy=true-2 1 717188987 ns/op +BenchmarkTransactionStatusBatchExpiry/crossing-group/legacy=false-2 1 2045462 ns/op +BenchmarkTransactionStatusBatchExpiry/crossing-group/legacy=false-2 1 1845859 ns/op +BenchmarkTransactionStatusBatchExpiry/crossing-group/legacy=false-2 1 1983167 ns/op +PASS +ok github.com/Overclock-Validator/mithril/pkg/replay 22.725s diff --git a/docs/results/status-cache/2026-09-14/zen5-race.log b/docs/results/status-cache/2026-09-14/zen5-race.log new file mode 100644 index 000000000..100c86a11 --- /dev/null +++ b/docs/results/status-cache/2026-09-14/zen5-race.log @@ -0,0 +1 @@ +ok github.com/Overclock-Validator/mithril/pkg/replay 2.189s diff --git a/docs/results/status-cache/2026-09-14/zen5-vet.log b/docs/results/status-cache/2026-09-14/zen5-vet.log new file mode 100644 index 000000000..e69de29bb diff --git a/docs/status-checkpoint-capture.md b/docs/status-checkpoint-capture.md new file mode 100644 index 000000000..e47e67e2b --- /dev/null +++ b/docs/status-checkpoint-capture.md @@ -0,0 +1,7 @@ +# Transaction-status checkpoint capture + +Replay used to encode and sort the full status checkpoint while preparing a promotion. Capture now pins immutable lineage and coverage metadata; the existing promotion worker encodes the same checkpoint later. Capture occurs before pruning and publication ordering stays unchanged. Pinned snapshots retain their nodes across pruning/unwind. + +The native Zen5 benchmark captured roughly30MB of status data: original202–207ms versus5.92–6.06microseconds on replay. Encoding moved to the worker and was not eliminated. Ten live captures took29–34microseconds; worker encoding179–367ms. These are historical stage measurements, not a promise of total validator speedup; see docs/results/status-cache/2026-09-14. + +This PR also includes batched expiry; see transaction-status-expiry.md. The proposed transaction-status publication optimization has not been implemented or included. Fresh standalone replay race and vet checks are under docs/results/pr-split-2026-09-15/status. From 6c31ff511693b542818669d9dcc1ae6d8cd79f0a Mon Sep 17 00:00:00 2001 From: 7layermagik <7layermagik@users.noreply.github.com> Date: Mon, 14 Sep 2026 22:01:40 -0500 Subject: [PATCH 05/16] Prepare transaction-status deltas during block execution --- .../2026-09-15/native-benchmark-summary.json | 82 ++++++++++ .../native-overlap-final-summary.json | 104 ++++++++++++ .../2026-09-15/native-overlap-summary.json | 104 ++++++++++++ .../2026-09-15/native-window.json | 4 + .../2026-09-15/post-test-health.json | 1 + .../2026-09-15/tested-source.json | 16 ++ docs/transaction-status-publication.md | 52 ++++++ pkg/metrics/metrics.go | 24 +-- pkg/replay/block.go | 14 +- pkg/replay/transaction_status_cache.go | 38 +++-- ...ansaction_status_overlap_benchmark_test.go | 110 +++++++++++++ .../transaction_status_plan_binding_test.go | 3 +- .../transaction_status_prepared_test.go | 9 +- pkg/replay/transaction_status_publication.go | 83 ++++++++++ ...ction_status_publication_benchmark_test.go | 153 ++++++++++++++++++ .../transaction_status_publication_test.go | 133 +++++++++++++++ 16 files changed, 900 insertions(+), 30 deletions(-) create mode 100644 docs/results/status-publication/2026-09-15/native-benchmark-summary.json create mode 100644 docs/results/status-publication/2026-09-15/native-overlap-final-summary.json create mode 100644 docs/results/status-publication/2026-09-15/native-overlap-summary.json create mode 100644 docs/results/status-publication/2026-09-15/native-window.json create mode 100644 docs/results/status-publication/2026-09-15/post-test-health.json create mode 100644 docs/results/status-publication/2026-09-15/tested-source.json create mode 100644 docs/transaction-status-publication.md create mode 100644 pkg/replay/transaction_status_overlap_benchmark_test.go create mode 100644 pkg/replay/transaction_status_publication.go create mode 100644 pkg/replay/transaction_status_publication_benchmark_test.go create mode 100644 pkg/replay/transaction_status_publication_test.go diff --git a/docs/results/status-publication/2026-09-15/native-benchmark-summary.json b/docs/results/status-publication/2026-09-15/native-benchmark-summary.json new file mode 100644 index 000000000..3936921d1 --- /dev/null +++ b/docs/results/status-publication/2026-09-15/native-benchmark-summary.json @@ -0,0 +1,82 @@ +{ + "BenchmarkTransactionStatusPublication/groups_1/existing_false/legacy-2": { + "ns/op": 4990269.0, + "B/op": 6302003.0, + "allocs/op": 555.0 + }, + "BenchmarkTransactionStatusPublication/groups_1/existing_false/sized-2": { + "ns/op": 3331600.0, + "B/op": 3151475.0, + "allocs/op": 265.0 + }, + "BenchmarkTransactionStatusPublication/groups_1/existing_false/prepared_total-2": { + "ns/op": 3393252.0, + "B/op": 3151718.0, + "allocs/op": 269.0 + }, + "BenchmarkTransactionStatusPublication/groups_1/existing_false/prepared_commit-2": { + "ns/op": 1587430.0, + "B/op": 1575587.0, + "allocs/op": 132.0 + }, + "BenchmarkTransactionStatusPublication/groups_1/existing_true/legacy-2": { + "ns/op": 4991180.0, + "B/op": 3466193.0, + "allocs/op": 304.0 + }, + "BenchmarkTransactionStatusPublication/groups_1/existing_true/sized-2": { + "ns/op": 4657032.0, + "B/op": 1891049.0, + "allocs/op": 159.0 + }, + "BenchmarkTransactionStatusPublication/groups_1/existing_true/prepared_total-2": { + "ns/op": 4434153.0, + "B/op": 1891281.0, + "allocs/op": 163.0 + }, + "BenchmarkTransactionStatusPublication/groups_1/existing_true/prepared_commit-2": { + "ns/op": 2756954.0, + "B/op": 315161.0, + "allocs/op": 26.0 + }, + "BenchmarkTransactionStatusPublication/groups_4/existing_false/legacy-2": { + "ns/op": 4624988.0, + "B/op": 6301427.0, + "allocs/op": 659.0 + }, + "BenchmarkTransactionStatusPublication/groups_4/existing_false/sized-2": { + "ns/op": 3743889.0, + "B/op": 3151859.0, + "allocs/op": 283.0 + }, + "BenchmarkTransactionStatusPublication/groups_4/existing_false/prepared_total-2": { + "ns/op": 5915974.0, + "B/op": 3152091.0, + "allocs/op": 287.0 + }, + "BenchmarkTransactionStatusPublication/groups_4/existing_false/prepared_commit-2": { + "ns/op": 2205044.0, + "B/op": 1575779.0, + "allocs/op": 141.0 + }, + "BenchmarkTransactionStatusPublication/groups_4/existing_true/legacy-2": { + "ns/op": 5224367.0, + "B/op": 3465532.0, + "allocs/op": 357.0 + }, + "BenchmarkTransactionStatusPublication/groups_4/existing_true/sized-2": { + "ns/op": 4644107.0, + "B/op": 1891228.0, + "allocs/op": 169.0 + }, + "BenchmarkTransactionStatusPublication/groups_4/existing_true/prepared_total-2": { + "ns/op": 4949162.0, + "B/op": 1891460.0, + "allocs/op": 173.0 + }, + "BenchmarkTransactionStatusPublication/groups_4/existing_true/prepared_commit-2": { + "ns/op": 2858236.0, + "B/op": 315148.0, + "allocs/op": 27.0 + } +} \ No newline at end of file diff --git a/docs/results/status-publication/2026-09-15/native-overlap-final-summary.json b/docs/results/status-publication/2026-09-15/native-overlap-final-summary.json new file mode 100644 index 000000000..7f2b767e3 --- /dev/null +++ b/docs/results/status-publication/2026-09-15/native-overlap-final-summary.json @@ -0,0 +1,104 @@ +{ + "BenchmarkTransactionStatusExecutionOverlap/legacy": { + "ns/op": 23370663.0, + "commit-with-wait-ns/op": 6062517.0, + "execution-ns/op": 17289566.0, + "B/op": 23276118.0, + "allocs/op": 242222.0 + }, + "BenchmarkTransactionStatusExecutionOverlap/legacy-2": { + "ns/op": 20677847.0, + "commit-with-wait-ns/op": 5394296.0, + "execution-ns/op": 15069600.0, + "B/op": 23276627.0, + "allocs/op": 242225.0 + }, + "BenchmarkTransactionStatusExecutionOverlap/sized": { + "ns/op": 21296975.0, + "commit-with-wait-ns/op": 4299800.0, + "execution-ns/op": 17436468.0, + "B/op": 20125587.0, + "allocs/op": 241932.0 + }, + "BenchmarkTransactionStatusExecutionOverlap/sized-2": { + "ns/op": 18573776.0, + "commit-with-wait-ns/op": 4299496.0, + "execution-ns/op": 14369209.0, + "B/op": 20126012.0, + "allocs/op": 241934.0 + }, + "BenchmarkTransactionStatusExecutionOverlap/overlap": { + "ns/op": 22188363.0, + "commit-with-wait-ns/op": 4616295.0, + "execution-ns/op": 17694970.0, + "B/op": 20125587.0, + "allocs/op": 241932.0 + }, + "BenchmarkTransactionStatusExecutionOverlap/overlap-2": { + "ns/op": 17090947.0, + "commit-with-wait-ns/op": 2119131.0, + "execution-ns/op": 14966507.0, + "B/op": 20126212.0, + "allocs/op": 241938.0 + }, + "BenchmarkTransactionStatusSmallPublication/txs_0/legacy": { + "ns/op": 95.12, + "B/op": 112.0, + "allocs/op": 2.0 + }, + "BenchmarkTransactionStatusSmallPublication/txs_0/legacy-2": { + "ns/op": 74.71, + "B/op": 112.0, + "allocs/op": 2.0 + }, + "BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total": { + "ns/op": 119.4, + "B/op": 112.0, + "allocs/op": 2.0 + }, + "BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total-2": { + "ns/op": 100.0, + "B/op": 112.0, + "allocs/op": 2.0 + }, + "BenchmarkTransactionStatusSmallPublication/txs_1/legacy": { + "ns/op": 593.0, + "B/op": 960.0, + "allocs/op": 9.0 + }, + "BenchmarkTransactionStatusSmallPublication/txs_1/legacy-2": { + "ns/op": 466.8, + "B/op": 960.0, + "allocs/op": 9.0 + }, + "BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total": { + "ns/op": 708.3, + "B/op": 960.0, + "allocs/op": 9.0 + }, + "BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total-2": { + "ns/op": 609.1, + "B/op": 960.0, + "allocs/op": 9.0 + }, + "BenchmarkTransactionStatusSmallPublication/txs_32/legacy": { + "ns/op": 6085.0, + "B/op": 6320.0, + "allocs/op": 23.0 + }, + "BenchmarkTransactionStatusSmallPublication/txs_32/legacy-2": { + "ns/op": 5145.0, + "B/op": 6320.0, + "allocs/op": 23.0 + }, + "BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total": { + "ns/op": 4456.0, + "B/op": 3616.0, + "allocs/op": 13.0 + }, + "BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total-2": { + "ns/op": 3770.0, + "B/op": 3616.0, + "allocs/op": 13.0 + } +} \ No newline at end of file diff --git a/docs/results/status-publication/2026-09-15/native-overlap-summary.json b/docs/results/status-publication/2026-09-15/native-overlap-summary.json new file mode 100644 index 000000000..efa0017aa --- /dev/null +++ b/docs/results/status-publication/2026-09-15/native-overlap-summary.json @@ -0,0 +1,104 @@ +{ + "BenchmarkTransactionStatusExecutionOverlap/legacy": { + "ns/op": 22833095.0, + "commit-with-wait-ns/op": 5848843.0, + "execution-ns/op": 16779392.0, + "B/op": 23276116.0, + "allocs/op": 242222.0 + }, + "BenchmarkTransactionStatusExecutionOverlap/legacy-2": { + "ns/op": 19163664.0, + "commit-with-wait-ns/op": 5114189.0, + "execution-ns/op": 14034884.0, + "B/op": 23276650.0, + "allocs/op": 242225.0 + }, + "BenchmarkTransactionStatusExecutionOverlap/sized": { + "ns/op": 21164557.0, + "commit-with-wait-ns/op": 4745710.0, + "execution-ns/op": 17057597.0, + "B/op": 20125556.0, + "allocs/op": 241932.0 + }, + "BenchmarkTransactionStatusExecutionOverlap/sized-2": { + "ns/op": 18233386.0, + "commit-with-wait-ns/op": 4143998.0, + "execution-ns/op": 14088971.0, + "B/op": 20126009.0, + "allocs/op": 241934.0 + }, + "BenchmarkTransactionStatusExecutionOverlap/overlap": { + "ns/op": 21351433.0, + "commit-with-wait-ns/op": 3481211.0, + "execution-ns/op": 18286875.0, + "B/op": 20125816.0, + "allocs/op": 241936.0 + }, + "BenchmarkTransactionStatusExecutionOverlap/overlap-2": { + "ns/op": 16814463.0, + "commit-with-wait-ns/op": 2297246.0, + "execution-ns/op": 14538161.0, + "B/op": 20126252.0, + "allocs/op": 241938.0 + }, + "BenchmarkTransactionStatusSmallPublication/txs_0/legacy": { + "ns/op": 94.81, + "B/op": 112.0, + "allocs/op": 2.0 + }, + "BenchmarkTransactionStatusSmallPublication/txs_0/legacy-2": { + "ns/op": 76.19, + "B/op": 112.0, + "allocs/op": 2.0 + }, + "BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total": { + "ns/op": 181.7, + "B/op": 264.0, + "allocs/op": 5.0 + }, + "BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total-2": { + "ns/op": 142.9, + "B/op": 264.0, + "allocs/op": 5.0 + }, + "BenchmarkTransactionStatusSmallPublication/txs_1/legacy": { + "ns/op": 810.9, + "B/op": 960.0, + "allocs/op": 9.0 + }, + "BenchmarkTransactionStatusSmallPublication/txs_1/legacy-2": { + "ns/op": 633.3, + "B/op": 960.0, + "allocs/op": 9.0 + }, + "BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total": { + "ns/op": 1645.0, + "B/op": 1192.0, + "allocs/op": 13.0 + }, + "BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total-2": { + "ns/op": 1511.0, + "B/op": 1192.0, + "allocs/op": 13.0 + }, + "BenchmarkTransactionStatusSmallPublication/txs_32/legacy": { + "ns/op": 6067.0, + "B/op": 6320.0, + "allocs/op": 23.0 + }, + "BenchmarkTransactionStatusSmallPublication/txs_32/legacy-2": { + "ns/op": 5064.0, + "B/op": 6320.0, + "allocs/op": 23.0 + }, + "BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total": { + "ns/op": 5837.0, + "B/op": 3848.0, + "allocs/op": 17.0 + }, + "BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total-2": { + "ns/op": 5961.0, + "B/op": 3848.0, + "allocs/op": 17.0 + } +} \ No newline at end of file diff --git a/docs/results/status-publication/2026-09-15/native-window.json b/docs/results/status-publication/2026-09-15/native-window.json new file mode 100644 index 000000000..f283937ee --- /dev/null +++ b/docs/results/status-publication/2026-09-15/native-window.json @@ -0,0 +1,4 @@ +{ + "start": "2026-09-15T02:57:29.581963+00:00", + "end": "2026-09-15T02:58:07.245339+00:00" +} \ No newline at end of file diff --git a/docs/results/status-publication/2026-09-15/post-test-health.json b/docs/results/status-publication/2026-09-15/post-test-health.json new file mode 100644 index 000000000..df730357e --- /dev/null +++ b/docs/results/status-publication/2026-09-15/post-test-health.json @@ -0,0 +1 @@ +{"utc": "2026-09-15T02:58:13.874813+00:00", "health": {"utc": "2026-09-15T02:58:13.949166+00:00", "pid": 568418, "rpc_slot": 3824460, "local_slot": 3824460, "last_vote": 3824459, "vote_lag": 1}, "pid": "MainPID=568418", "services": ["active", "active", "active", "active"]} diff --git a/docs/results/status-publication/2026-09-15/tested-source.json b/docs/results/status-publication/2026-09-15/tested-source.json new file mode 100644 index 000000000..c4889f6a2 --- /dev/null +++ b/docs/results/status-publication/2026-09-15/tested-source.json @@ -0,0 +1,16 @@ +{ + "base": "33dde4050d9250557583395810799aaac2f54017", + "branch_before_change": "31e0d8c0", + "files": { + "pkg/replay/transaction_status_overlap_benchmark_test.go": "6310ce381e6e88151b14a7f0e5f56e7370a8e1d94af466911dd69707379d8cdb", + "pkg/replay/transaction_status_publication.go": "a94c1227ce0b0e329dfff390c2303bb36525493e0cde3fce7d3fe177eaa1681b", + "pkg/replay/transaction_status_publication_benchmark_test.go": "0ff3a1f0c2a8d0ceafb5d7ba300c6cb5358082fd019f75a4b6fe0c35fde1575e", + "pkg/replay/transaction_status_publication_test.go": "eae775d2d10e6ca5b13b1e6f201cff15d8d0174b40ff20a69aa4279004799ca1", + "pkg/metrics/metrics.go": "17005056d872f9b8acf75fee15dc2c175bc4addad1b2dafee61445124dfbe307", + "pkg/replay/block.go": "19e7e931ec5e8aaab2e910808cb2f6e19f5721ff2ad953541f42828a88076a0d", + "pkg/replay/transaction_status_cache.go": "731febda7fdfe09b84d4281c53c3d0f4381e8f7d67d9d47f084c8a10c1523935", + "pkg/replay/transaction_status_plan_binding_test.go": "6c1fe9c92af2011402025ca96601847a1313bf2ffb564082eb06aa89088e250b", + "pkg/replay/transaction_status_prepared_test.go": "1944b443624d1075f6ea3e89630cdca42a6245a95a3116bbd47bd670311ad876" + }, + "baseline_check": "Frozen commitBlockWithPlan and addDeltaVisibleLocked exactly match alpenglow-dev at the recorded base after renaming benchmark helper methods." +} diff --git a/docs/transaction-status-publication.md b/docs/transaction-status-publication.md new file mode 100644 index 000000000..d182bc6fb --- /dev/null +++ b/docs/transaction-status-publication.md @@ -0,0 +1,52 @@ +# Preparing transaction-status publication during execution + +Replay previously built the immutable per-bank transaction-status delta and grew the visible duplicate index only after execution and bank-state publication. In a prior live sample of 25 large blocks, TransactionStatusCommit took 7.704 ms median and 10.206 ms maximum. Those live timings motivate this change; they are not the controlled benchmark baseline below. + +Count identities by recent blockhash and allocate each delta map at its final capacity. Pre-size newly created visible maps too. For banks with more than 32 transactions and GOMAXPROCS greater than one, prepare the immutable delta during account loading and execution. Smaller banks and single-thread configurations keep the work inline. There is at most one preparation task per ProcessBlock call, and every return joins it, including rejected banks. No status becomes visible during preparation. + +The worker reads immutable prepared message identities and briefly snapshots only blockhash slice offsets under the cache read lock. It builds its private maps outside the lock. Commit still checks exact block/identity binding, complete coverage, parent lineage and all ancestor duplicates under the publication lock. A changed slice offset or mismatched preparation triggers a rebuild from the actual block's identities. Publication still happens only after successful bank-state commit. Failed instructions within an accepted bank remain processed; rejected banks publish nothing. Pinned views, snapshots, reference counts and unwind keep their existing semantics. + +TransactionStatusPreparation measures worker wall time, which overlaps execution; it is not additive with replay wall time. TransactionStatusPreparationWait measures the residual join and is nested inside TransactionStatusCommit. The latter still includes waiting, final checks, visible-index updates and node publication. Preparation time excludes initial goroutine scheduling delay; any residual scheduling delay remains in the join/commit timer. + +## Native benchmark + +AMD Ryzen 7 9700X (Zen 5), Go 1.26.4, GOMAXPROCS=2. Tests ran in a separate process on the validator host with Nice=15 and a 200% CPU quota; the validator and loader continued running. This is a shared-host microbenchmark, with observable timing variation. Five samples per case, ten iterations per sample; values below are medians of sample means, not per-block percentiles. + +Each block has 33,760 unique prepared message identities spread across one or four recent blockhashes. Existing-group cases seed 33,760 different ancestor transactions. Fixture creation, hashing, seeding and unwind are untimed. Existing maps retain capacity after unwind: the first timed commit's growth is amortized across the ten iterations. This does not model an index growing indefinitely across live blocks. + +The frozen baseline functions exactly match alpenglow-dev commit `33dde4050d9250557583395810799aaac2f54017`. Both versions use the same prepared identities, parent/duplicate checks and fixtures. + +| Recent blockhash groups | Parent has keys in these groups | Baseline commit | Sized maps, inline | Preparation + commit, no overlap | Commit after preparation | +|---|---|---:|---:|---:|---:| +| 1 | No | 4.990 ms | 3.332 ms | 3.393 ms | 1.587 ms | +| 1 | Yes | 4.991 ms | 4.657 ms | 4.434 ms | 2.757 ms | +| 4 | No | 4.625 ms | 3.744 ms | 5.916 ms | 2.205 ms | +| 4 | Yes | 5.224 ms | 4.644 ms | 4.949 ms | 2.858 ms | + +The last column deliberately excludes delta preparation: it measures the work remaining if execution hides preparation completely. It is not total replay or CPU work. Total publication allocations with new groups fell from approximately 6.30 MB to 3.15 MB per block. Existing-group allocation figures include the amortized first growth described above. + +The four-new-group total-work sample was slower. Preserve that result rather than claiming improvement in every sample. A subsequent baseline/candidate/candidate/baseline comparison of that same case, with 50 iterations per sample, measured baseline **4.400 and 4.565 ms**, candidate **2.985 and 3.131 ms**. This supports a reduction in work but does not isolate the cause of the earlier timing variation. + +## Execution contention and small blocks + +A separate controlled benchmark performs 4,096 load-and-execute calls using the existing transfer fixture while preparing 33,760 independent status keys. It does not commit transfer accounts, and its status fixture differs from the repeated transfer fixture. It tests scheduling/allocation contention, not whole-block replay or a valid block workload. + +With two Go execution threads, the final implementation measured **20.678 ms baseline**, **18.574 ms with sizing alone**, and **17.091 ms with overlap**. Execution itself measured 15.070, 14.369 and 14.967 ms respectively. Thus preparation competed with execution relative to sizing alone, but the shorter final stage outweighed that cost in this controlled workload. These are separate medians and need not add exactly. + +The initial unrestricted version showed no additional total-time benefit from overlap with GOMAXPROCS=1. Tiny-block measurements also showed roughly a microsecond of avoidable scheduling overhead. The final implementation therefore does no background preparation with one Go execution thread or at most 32 transactions. Empty and one-transaction cases retain the baseline allocation counts. The 32-transaction case benefits from sizing without launching a worker. Threshold and single-thread behavior have regression coverage. + +## Validation and limits + +Full replay and block race suites passed on both Zen 5 and M4 Pro. Metrics has no tests. Native vet for replay/metrics and the validator build passed. Tests cover fork replacement introducing a duplicate after preparation, concurrent sibling publication, stale identity binding, changed snapshot slice offsets, rejected/incomplete banks, mismatched preparation, pinned views, snapshot restore, unwind, empty banks and scheduling boundaries. + +Raw logs, source hashes, summaries and the alternating recheck are in [results/status-publication/2026-09-15](results/status-publication/2026-09-15). The baseline comparison covers only status publication. No live replay or FAST improvement is claimed. The staging binary was not deployed; the existing validator remained active and voting throughout the tests. + +Reproduce from this branch: + +```sh +GOMAXPROCS=2 go test -race -p 2 ./pkg/replay ./pkg/block ./pkg/metrics -count=1 +GOMAXPROCS=2 go vet -p 2 ./pkg/replay ./pkg/metrics +GOMAXPROCS=2 go build -p 2 ./cmd/mithril +GOMAXPROCS=2 go test ./pkg/replay -run '^$' -bench '^BenchmarkTransactionStatusPublication$' -benchtime=10x -count=5 +go test ./pkg/replay -run '^$' -bench '^BenchmarkTransactionStatus(ExecutionOverlap|SmallPublication)$' -benchtime=100ms -count=5 -cpu=1,2 +``` diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index 10548fdd4..ae2b8a4ac 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -171,16 +171,20 @@ type BlockReplay struct { // BlockUpdateAccounts is synchronous critical-path work: rooted-tail // buffering (including its callback) or legacy store enqueue. It excludes // legacy asynchronous disk completion. - BlockUpdateAccounts Timing - TransactionStatusCommit Timing - SignatureVerificationJoin Timing - AccountsDeltaHash Timing - LtHashDedupe Timing - LtHashWorkerCompute Timing - LtHashPartialReduce Timing - BankHashFinalize Timing - BankHash Timing - AlpenglowFooterVerification Timing + BlockUpdateAccounts Timing + TransactionStatusCommit Timing + // Preparation overlaps execution and is not additive with replay wall time. + // PreparationWait is the residual join nested within TransactionStatusCommit. + TransactionStatusPreparation Timing + TransactionStatusPreparationWait Timing + SignatureVerificationJoin Timing + AccountsDeltaHash Timing + LtHashDedupe Timing + LtHashWorkerCompute Timing + LtHashPartialReduce Timing + BankHashFinalize Timing + BankHash Timing + AlpenglowFooterVerification Timing // PostProcessBlock is caller-side state publication and replay // bookkeeping after ProcessBlock returns. TransactionStatusView, // ChainTipUpdate, and ResumeContext are nested sub-phases; logging, summary diff --git a/pkg/replay/block.go b/pkg/replay/block.go index d28f3b8ca..22cecfc18 100644 --- a/pkg/replay/block.go +++ b/pkg/replay/block.go @@ -4115,6 +4115,15 @@ func ProcessBlock( if statusValidationErr != nil { return nil, fmt.Errorf("validate transaction statuses for slot %d: %w", block.Slot, statusValidationErr) } + statusPreparation := transactionStatuses.startStatusPreparation(executionPlan) + defer func() { + // Join before returning so a rejected bank cannot leave work behind or + // charge its preparation time to the next block's metrics record. + statusPreparation.wait() + if statusPreparation != nil { + metrics.GlobalBlockReplay.TransactionStatusPreparation.AddTiming(statusPreparation.duration) + } + }() ctx, task := trace.NewTask(context.Background(), "ProcessBlock") defer task.End() trace.Log(ctx, "slot", fmt.Sprintf("%d", block.Slot)) @@ -4374,7 +4383,10 @@ func ProcessBlock( return slotCtx, err } statusCommitStart := time.Now() - statusErr := transactionStatuses.commitBlockWithPlan(block, executionPlan) + statusWaitStart := time.Now() + preparedStatuses := statusPreparation.wait() + metrics.GlobalBlockReplay.TransactionStatusPreparationWait.AddTimingSince(statusWaitStart) + statusErr := transactionStatuses.commitBlockWithPreparedDelta(block, executionPlan, preparedStatuses) metrics.GlobalBlockReplay.TransactionStatusCommit.AddTimingSince(statusCommitStart) if statusErr != nil { return nil, fmt.Errorf("commit transaction statuses for slot %d after bank state commit: %w", block.Slot, statusErr) diff --git a/pkg/replay/transaction_status_cache.go b/pkg/replay/transaction_status_cache.go index bf043fb17..2703c4199 100644 --- a/pkg/replay/transaction_status_cache.go +++ b/pkg/replay/transaction_status_cache.go @@ -586,6 +586,10 @@ func (c *TransactionStatusCache) CommitBlock(block *b.Block) error { // commitBlockWithPlan atomically rechecks the mutable lineage/status state and // publishes the already-prepared immutable transaction identities. func (c *TransactionStatusCache) commitBlockWithPlan(block *b.Block, plan blockTransactionExecutionPlan) error { + return c.commitBlockWithPreparedDelta(block, plan, nil) +} + +func (c *TransactionStatusCache) commitBlockWithPreparedDelta(block *b.Block, plan blockTransactionExecutionPlan, prepared *preparedTransactionStatusDelta) error { if block == nil || plan.messageIdentities == nil || !plan.messageIdentities.MatchesBlock(block) { return errors.New("prepared transaction message identities do not match block") } @@ -604,23 +608,27 @@ func (c *TransactionStatusCache) commitBlockWithPlan(block *b.Block, plan blockT return err } - delta := make(transactionStatusDelta) - for index := 0; index < plan.messageIdentities.Len(); index++ { - identity := plan.messageIdentities.Identity(index) - blockhash := identity.RecentBlockhash - group := delta[blockhash] - if group == nil { - keyIndex := uint8(0) - if visible := c.visible[blockhash]; visible != nil { - keyIndex = visible.keyIndex + delta := transactionStatusDelta(nil) + if prepared != nil && prepared.identities == plan.messageIdentities { + delta = prepared.delta + // A restore or branch transition can change a blockhash's slice offset. + // Rebuild from full identities if any current group uses another offset. + for blockhash, group := range delta { + if visible := c.visible[blockhash]; visible != nil && visible.keyIndex != group.keyIndex { + delta = nil + break } - group = &transactionStatusGroup{ - keyIndex: keyIndex, - keys: make(map[transactionStatusKey]struct{}), + } + } + if delta == nil { + counts := countTransactionStatusGroups(plan.messageIdentities) + indexes := make(map[solana.Hash]uint8, len(counts)) + for blockhash := range counts { + if visible := c.visible[blockhash]; visible != nil { + indexes[blockhash] = visible.keyIndex } - delta[blockhash] = group } - group.keys[sliceTransactionStatusKey(identity.MessageHash, group.keyIndex)] = struct{}{} + delta = buildTransactionStatusDelta(plan.messageIdentities, counts, indexes) } if err := c.addDeltaVisibleLocked(delta); err != nil { @@ -804,7 +812,7 @@ func (c *TransactionStatusCache) addDeltaVisibleLocked(delta transactionStatusDe if group == nil { group = &visibleTransactionStatusGroup{ keyIndex: deltaGroup.keyIndex, - keys: make(map[transactionStatusKey]uint16), + keys: make(map[transactionStatusKey]uint16, len(deltaGroup.keys)), } c.visible[blockhash] = group } diff --git a/pkg/replay/transaction_status_overlap_benchmark_test.go b/pkg/replay/transaction_status_overlap_benchmark_test.go new file mode 100644 index 000000000..0da5f739f --- /dev/null +++ b/pkg/replay/transaction_status_overlap_benchmark_test.go @@ -0,0 +1,110 @@ +package replay + +import ( + "fmt" + "testing" + "time" + + "github.com/Overclock-Validator/mithril/pkg/tpu/txfixture" + "github.com/gagliardetto/solana-go" +) + +// This controlled workload runs 4,096 executions of the transfer fixture while +// preparing 33,760 independent status keys. It measures scheduling/GC contention, +// not full replay: no accounts are committed, and the status fixture differs +// from the repeated transfer fixture. Run with -cpu=1,2 to compare contention +// without and with a spare execution thread. Check live replay separately. +func BenchmarkTransactionStatusExecutionOverlap(tb *testing.B) { + for _, mode := range []string{"legacy", "sized", "overlap"} { + tb.Run(mode, func(tb *testing.B) { + slotCtx, cleanup := newCommitTestSlotCtx() + defer cleanup() + tx, err := solana.TransactionFromBytes(txfixture.MustSignedTransferWire(0)) + if err != nil { + tb.Fatal(err) + } + cache := NewTransactionStatusCache() + if err := cache.CommitBlock(statusCacheTestBlock(10)); err != nil { + tb.Fatal(err) + } + blk := statusCacheTestBlock(11, benchmarkUniqueTransactions(33760)...) + plan, err := planBlockTransactionExecution(blk) + if err != nil { + tb.Fatal(err) + } + var execution, commit time.Duration + tb.ReportAllocs() + tb.ResetTimer() + for range tb.N { + var p *transactionStatusPreparation + if mode == "overlap" { + p = cache.startStatusPreparation(plan) + } + start := time.Now() + for range 4096 { + output := LoadAndExecuteTransaction(LoadAndExecuteTransactionInput{SlotCtx: slotCtx, Transaction: tx, LeanResult: true}) + if output.ProcessingResult.TransactionError != nil { + tb.Fatal(output.ProcessingResult.TransactionError) + } + } + execution += time.Since(start) + start = time.Now() + switch mode { + case "legacy": + err = cache.legacyCommitStatusForBenchmark(blk, plan) + case "sized": + err = cache.commitBlockWithPlan(blk, plan) + case "overlap": + err = cache.commitBlockWithPreparedDelta(blk, plan, p.wait()) + } + commit += time.Since(start) + if err != nil { + tb.Fatal(err) + } + tb.StopTimer() + if err := cache.Unwind(11); err != nil { + tb.Fatal(err) + } + tb.StartTimer() + } + tb.StopTimer() + tb.ReportMetric(float64(execution.Nanoseconds())/float64(tb.N), "execution-ns/op") + tb.ReportMetric(float64(commit.Nanoseconds())/float64(tb.N), "commit-with-wait-ns/op") + }) + } +} + +func BenchmarkTransactionStatusSmallPublication(tb *testing.B) { + for _, count := range []int{0, 1, 32} { + for _, mode := range []string{"legacy", "prepared_total"} { + tb.Run(fmt.Sprintf("txs_%d/%s", count, mode), func(tb *testing.B) { + cache := NewTransactionStatusCache() + if err := cache.CommitBlock(statusCacheTestBlock(10)); err != nil { + tb.Fatal(err) + } + blk := statusCacheTestBlock(11, benchmarkUniqueTransactions(count)...) + plan, err := planBlockTransactionExecution(blk) + if err != nil { + tb.Fatal(err) + } + tb.ReportAllocs() + tb.ResetTimer() + for range tb.N { + if mode == "legacy" { + err = cache.legacyCommitStatusForBenchmark(blk, plan) + } else { + err = cache.commitBlockWithPreparedDelta(blk, plan, cache.startStatusPreparation(plan).wait()) + } + if err != nil { + tb.Fatal(err) + } + // Include unwind equally in this small-work benchmark, avoiding + // timer start/stop overhead around microsecond operations. + if err := cache.Unwind(11); err != nil { + tb.Fatal(err) + } + } + }) + } + } +} diff --git a/pkg/replay/transaction_status_plan_binding_test.go b/pkg/replay/transaction_status_plan_binding_test.go index 30b456d3a..faf0b2540 100644 --- a/pkg/replay/transaction_status_plan_binding_test.go +++ b/pkg/replay/transaction_status_plan_binding_test.go @@ -15,9 +15,10 @@ func TestPreparedCommitRejectsTransactionReplacement(t *testing.T) { if err != nil { t.Fatal(err) } + prepared := cache.prepareTransactionStatusDelta(plan.messageIdentities) candidate.Transactions[0] = statusCacheTestTransaction(4, 5, 6) - err = cache.commitBlockWithPlan(candidate, plan) + err = cache.commitBlockWithPreparedDelta(candidate, plan, prepared) if err == nil || err.Error() != "prepared transaction message identities do not match block" { t.Fatalf("commit error = %v, want prepared-plan binding failure", err) } diff --git a/pkg/replay/transaction_status_prepared_test.go b/pkg/replay/transaction_status_prepared_test.go index 44a42ca83..fd71b6857 100644 --- a/pkg/replay/transaction_status_prepared_test.go +++ b/pkg/replay/transaction_status_prepared_test.go @@ -26,6 +26,7 @@ func TestPreparedCommitRechecksAncestorAfterForkSwitch(t *testing.T) { plan, err := planBlockTransactionExecution(candidate) requireNoError(err) requireNoError(cache.validateBlockWithPlan(candidate, plan)) + prepared := cache.prepareTransactionStatusDelta(plan.messageIdentities) requireNoError(cache.Unwind(11)) replacement := statusCacheTestBlock( @@ -34,7 +35,7 @@ func TestPreparedCommitRechecksAncestorAfterForkSwitch(t *testing.T) { ) requireNoError(cache.CommitBlock(replacement)) - err = cache.commitBlockWithPlan(candidate, plan) + err = cache.commitBlockWithPreparedDelta(candidate, plan, prepared) var ancestorErr *AncestorAlreadyProcessedTransactionMessagesError if !errors.As(err, &ancestorErr) { t.Fatalf("prepared commit error = %v, want ancestor AlreadyProcessed", err) @@ -83,15 +84,17 @@ func TestConcurrentPreparedSiblingCommitsPublishExactlyOne(t *testing.T) { t.Fatalf("prevalidate right sibling: %v", err) } + leftPrepared := cache.prepareTransactionStatusDelta(leftPlan.messageIdentities) + rightPrepared := cache.prepareTransactionStatusDelta(rightPlan.messageIdentities) start := make(chan struct{}) results := make(chan error, 2) go func() { <-start - results <- cache.commitBlockWithPlan(left, leftPlan) + results <- cache.commitBlockWithPreparedDelta(left, leftPlan, leftPrepared) }() go func() { <-start - results <- cache.commitBlockWithPlan(right, rightPlan) + results <- cache.commitBlockWithPreparedDelta(right, rightPlan, rightPrepared) }() close(start) diff --git a/pkg/replay/transaction_status_publication.go b/pkg/replay/transaction_status_publication.go new file mode 100644 index 000000000..e821071ad --- /dev/null +++ b/pkg/replay/transaction_status_publication.go @@ -0,0 +1,83 @@ +package replay + +import ( + "runtime" + "time" + + b "github.com/Overclock-Validator/mithril/pkg/block" + "github.com/gagliardetto/solana-go" +) + +// Preparation owns private, immutable maps. It never publishes a status or +// authorizes a bank: commit still checks coverage, lineage, and duplicates. +type preparedTransactionStatusDelta struct { + identities *b.PreparedTransactionMessageIdentities + delta transactionStatusDelta +} + +type transactionStatusPreparation struct { + done chan struct{} + prepared *preparedTransactionStatusDelta + duration time.Duration +} + +// Replay joins this task on every exit, including rejected banks. It only reads +// the immutable identities, so account loading and ALT resolution can proceed. +func (c *TransactionStatusCache) startStatusPreparation(plan blockTransactionExecutionPlan) *transactionStatusPreparation { + // Small-block measurements show dispatch/join costs as much as the work. + // With one Go execution thread preparation cannot overlap execution at all. + if plan.messageIdentities.Len() <= 32 || runtime.GOMAXPROCS(0) == 1 { + return nil + } + p := &transactionStatusPreparation{done: make(chan struct{})} + go func() { + defer close(p.done) + start := time.Now() + p.prepared = c.prepareTransactionStatusDelta(plan.messageIdentities) + p.duration = time.Since(start) + }() + return p +} + +func (p *transactionStatusPreparation) wait() *preparedTransactionStatusDelta { + if p == nil { + return nil + } + <-p.done + return p.prepared +} + +func countTransactionStatusGroups(identities *b.PreparedTransactionMessageIdentities) map[solana.Hash]int { + counts := make(map[solana.Hash]int) + for i := 0; i < identities.Len(); i++ { + counts[identities.Identity(i).RecentBlockhash]++ + } + return counts +} + +func buildTransactionStatusDelta(identities *b.PreparedTransactionMessageIdentities, counts map[solana.Hash]int, indexes map[solana.Hash]uint8) transactionStatusDelta { + delta := make(transactionStatusDelta, len(counts)) + for blockhash, count := range counts { + delta[blockhash] = &transactionStatusGroup{keyIndex: indexes[blockhash], keys: make(map[transactionStatusKey]struct{}, count)} + } + for i := 0; i < identities.Len(); i++ { + identity := identities.Identity(i) + group := delta[identity.RecentBlockhash] + group.keys[sliceTransactionStatusKey(identity.MessageHash, group.keyIndex)] = struct{}{} + } + return delta +} + +func (c *TransactionStatusCache) prepareTransactionStatusDelta(identities *b.PreparedTransactionMessageIdentities) *preparedTransactionStatusDelta { + counts := countTransactionStatusGroups(identities) + indexes := make(map[solana.Hash]uint8, len(counts)) + // Copy offsets only, never share mutable visible maps with the worker. + c.mu.RLock() + for blockhash := range counts { + if visible := c.visible[blockhash]; visible != nil { + indexes[blockhash] = visible.keyIndex + } + } + c.mu.RUnlock() + return &preparedTransactionStatusDelta{identities: identities, delta: buildTransactionStatusDelta(identities, counts, indexes)} +} diff --git a/pkg/replay/transaction_status_publication_benchmark_test.go b/pkg/replay/transaction_status_publication_benchmark_test.go new file mode 100644 index 000000000..1bb19d66c --- /dev/null +++ b/pkg/replay/transaction_status_publication_benchmark_test.go @@ -0,0 +1,153 @@ +package replay + +import ( + "encoding/binary" + "errors" + "fmt" + "testing" + + b "github.com/Overclock-Validator/mithril/pkg/block" + "github.com/gagliardetto/solana-go" +) + +func (c *TransactionStatusCache) legacyCommitStatusForBenchmark(block *b.Block, plan blockTransactionExecutionPlan) error { + if block == nil || plan.messageIdentities == nil || !plan.messageIdentities.MatchesBlock(block) { + return errors.New("prepared transaction message identities do not match block") + } + c.mu.Lock() + defer c.mu.Unlock() + if !c.coverageComplete { + return &IncompleteTransactionStatusCoverageError{CachedRoot: c.rootedThrough} + } + // Parent lineage and ancestor status are mutable, so both remain under the + // publication lock even when hashing and same-bank deduplication happened + // earlier. This keeps commit safe across a concurrent branch transition. + if err := c.validateParentLocked(block); err != nil { + return err + } + if err := c.validateAncestorTransactionsLocked(block.Slot, plan.messageIdentities); err != nil { + return err + } + + delta := make(transactionStatusDelta) + for index := 0; index < plan.messageIdentities.Len(); index++ { + identity := plan.messageIdentities.Identity(index) + blockhash := identity.RecentBlockhash + group := delta[blockhash] + if group == nil { + keyIndex := uint8(0) + if visible := c.visible[blockhash]; visible != nil { + keyIndex = visible.keyIndex + } + group = &transactionStatusGroup{ + keyIndex: keyIndex, + keys: make(map[transactionStatusKey]struct{}), + } + delta[blockhash] = group + } + group.keys[sliceTransactionStatusKey(identity.MessageHash, group.keyIndex)] = struct{}{} + } + + if err := c.legacyAddStatusForBenchmark(delta); err != nil { + return err + } + c.tip = &transactionStatusNode{ + slot: block.Slot, + blockID: solana.Hash(block.AlpenglowBlockID), + hasBlockID: block.HasAlpenglowBlockID, + parent: c.tip, + delta: delta, + } + return nil +} + +// Frozen production commit algorithm before publication optimization. This is +// an independent baseline, including its original visible-index allocation. +func (c *TransactionStatusCache) legacyAddStatusForBenchmark(delta transactionStatusDelta) error { + for blockhash, deltaGroup := range delta { + if group := c.visible[blockhash]; group != nil && group.keyIndex != deltaGroup.keyIndex { + return fmt.Errorf("transaction status blockhash %s uses inconsistent key indexes %d and %d", + blockhash, group.keyIndex, deltaGroup.keyIndex) + } + } + for blockhash, deltaGroup := range delta { + group := c.visible[blockhash] + if group == nil { + group = &visibleTransactionStatusGroup{ + keyIndex: deltaGroup.keyIndex, + keys: make(map[transactionStatusKey]uint16), + } + c.visible[blockhash] = group + } + for key := range deltaGroup.keys { + group.keys[key]++ + } + } + return nil +} + +// BenchmarkTransactionStatusPublication times only status publication, with +// prepared message identities. No execution, disk I/O, signing or networking. +// prepared_commit excludes delta preparation; prepared_total includes it and +// goroutine dispatch/join, with no execution overlap. Neither measures replay. +// Each iteration restores the same ancestor contents; existing maps retain +// steady-state capacity. Fixture creation, seeding and unwind are not timed. +func BenchmarkTransactionStatusPublication(tb *testing.B) { + const count = 33760 + for _, groups := range []int{1, 4} { + for _, existing := range []bool{false, true} { + tb.Run(fmt.Sprintf("groups_%d/existing_%t", groups, existing), func(tb *testing.B) { + txs := benchmarkUniqueTransactions(count * 2) + for i, tx := range txs { + binary.LittleEndian.PutUint32(tx.Message.RecentBlockhash[:], uint32(i%groups+1)) + } + parent := statusCacheTestBlock(10, txs[:count]...) + if !existing { + parent.Transactions = nil + } + blk := statusCacheTestBlock(11, txs[count:]...) + plan, err := planBlockTransactionExecution(blk) + if err != nil { + tb.Fatal(err) + } + for _, name := range []string{"legacy", "sized", "prepared_total", "prepared_commit"} { + tb.Run(name, func(tb *testing.B) { + cache := NewTransactionStatusCache() + if err := cache.CommitBlock(parent); err != nil { + tb.Fatal(err) + } + tb.ReportAllocs() + tb.ResetTimer() + for range tb.N { + var err error + if name == "prepared_commit" { + tb.StopTimer() + prepared := cache.prepareTransactionStatusDelta(plan.messageIdentities) + tb.StartTimer() + err = cache.commitBlockWithPreparedDelta(blk, plan, prepared) + } else if name == "prepared_total" { + prepared := cache.startStatusPreparation(plan).wait() + err = cache.commitBlockWithPreparedDelta(blk, plan, prepared) + } else if name == "legacy" { + err = cache.legacyCommitStatusForBenchmark(blk, plan) + } else { + err = cache.commitBlockWithPlan(blk, plan) + } + if err != nil { + tb.Fatal(err) + } + tb.StopTimer() + if got := cache.tip.slot; got != 11 { + tb.Fatalf("tip=%d", got) + } + if err := cache.Unwind(11); err != nil { + tb.Fatal(err) + } + tb.StartTimer() + } + }) + } + }) + } + } +} diff --git a/pkg/replay/transaction_status_publication_test.go b/pkg/replay/transaction_status_publication_test.go new file mode 100644 index 000000000..58a24b06f --- /dev/null +++ b/pkg/replay/transaction_status_publication_test.go @@ -0,0 +1,133 @@ +package replay + +import ( + "fmt" + "runtime" + "testing" + + "github.com/Overclock-Validator/mithril/pkg/txstatus" + "github.com/stretchr/testify/require" +) + +func TestPreparedStatusDeltaRebindsSnapshotOffsets(t *testing.T) { + for _, from := range []uint64{0, 7, txstatus.MaxCachedKeyIndex} { + for _, to := range []uint64{0, 7, txstatus.MaxCachedKeyIndex} { + t.Run(fmt.Sprintf("%d_to_%d", from, to), func(t *testing.T) { + ancestor := statusCacheTestTransaction(1, 2, 3) + seed := func(offset uint64) *TransactionStatusCache { + cache, err := NewTransactionStatusCacheFromAgaveSnapshot([]txstatus.SnapshotSlotDelta{ + {Slot: 0, IsRoot: true, Statuses: []txstatus.SnapshotStatus{snapshotStatusCacheStatusForTx(t, ancestor, offset)}}, + }, 0) + require.NoError(t, err) + return cache + } + candidate := statusCacheTestBlock(1, statusCacheTestTransaction(1, 4, 5)) + plan, err := planBlockTransactionExecution(candidate) + require.NoError(t, err) + prepared := seed(from).prepareTransactionStatusDelta(plan.messageIdentities) + cache := seed(to) + pinned := cache.View() + require.NoError(t, cache.commitBlockWithPreparedDelta(candidate, plan, prepared)) + require.Equal(t, uint8(to), cache.tip.delta[candidate.Transactions[0].Message.RecentBlockhash].keyIndex) + found, err := cache.View().ContainsTransaction(candidate.Transactions[0]) + require.NoError(t, err) + require.True(t, found) + found, err = pinned.ContainsTransaction(candidate.Transactions[0]) + require.NoError(t, err) + require.False(t, found) + blob, err := cache.SnapshotThrough(1) + require.NoError(t, err) + restored, err := NewTransactionStatusCacheFromSnapshot(blob) + require.NoError(t, err) + found, err = restored.View().ContainsTransaction(candidate.Transactions[0]) + require.NoError(t, err) + require.True(t, found) + require.NoError(t, cache.Unwind(1)) + found, err = cache.View().ContainsTransaction(candidate.Transactions[0]) + require.NoError(t, err) + require.False(t, found) + found, err = cache.View().ContainsTransaction(ancestor) + require.NoError(t, err) + require.True(t, found) + }) + } + } +} + +func TestPreparedStatusDeltaDoesNotPublishUntilCommit(t *testing.T) { + prior := runtime.GOMAXPROCS(2) + defer runtime.GOMAXPROCS(prior) + cache := NewTransactionStatusCache() + require.NoError(t, cache.CommitBlock(statusCacheTestBlock(10))) + candidate := statusCacheTestBlock(11, benchmarkUniqueTransactions(33760)...) + plan, err := planBlockTransactionExecution(candidate) + require.NoError(t, err) + p := cache.startStatusPreparation(plan) + // A rejected bank joins and discards the prepared maps. Waiting is also + // idempotent for the normal commit followed by ProcessBlock's deferred join. + require.Same(t, p.wait(), p.wait()) + found, err := cache.View().ContainsTransaction(candidate.Transactions[0]) + require.NoError(t, err) + require.False(t, found) + require.Equal(t, uint64(10), cache.tip.slot) + cache.mu.Lock() + cache.coverageComplete = false + cache.mu.Unlock() + var incomplete *IncompleteTransactionStatusCoverageError + require.ErrorAs(t, cache.commitBlockWithPreparedDelta(candidate, plan, p.wait()), &incomplete) + require.Equal(t, uint64(10), cache.tip.slot) +} + +func TestPreparedStatusDeltaRejectsWrongPlanWithoutPublishingIt(t *testing.T) { + cache := NewTransactionStatusCache() + require.NoError(t, cache.CommitBlock(statusCacheTestBlock(10))) + left := statusCacheTestBlock(11, statusCacheTestTransaction(1, 2, 3)) + right := statusCacheTestBlock(11, statusCacheTestTransaction(1, 4, 5)) + leftPlan, err := planBlockTransactionExecution(left) + require.NoError(t, err) + rightPlan, err := planBlockTransactionExecution(right) + require.NoError(t, err) + prepared := cache.prepareTransactionStatusDelta(leftPlan.messageIdentities) + // A mismatched prepared delta falls back to the actual block's identities. + require.NoError(t, cache.commitBlockWithPreparedDelta(right, rightPlan, prepared)) + found, err := cache.View().ContainsTransaction(left.Transactions[0]) + require.NoError(t, err) + require.False(t, found) + found, err = cache.View().ContainsTransaction(right.Transactions[0]) + require.NoError(t, err) + require.True(t, found) +} + +func TestPreparedStatusDeltaEmptyBlock(t *testing.T) { + cache := NewTransactionStatusCache() + block := statusCacheTestBlock(10) + plan, err := planBlockTransactionExecution(block) + require.NoError(t, err) + p := cache.startStatusPreparation(plan) + require.Nil(t, p, "empty bank must not queue background work") + require.NoError(t, cache.commitBlockWithPreparedDelta(block, plan, p.wait())) +} + +func TestPreparedStatusDeltaScheduling(t *testing.T) { + for _, threads := range []int{1, 2} { + for _, count := range []int{1, 32, 33} { + t.Run(fmt.Sprintf("threads_%d/txs_%d", threads, count), func(t *testing.T) { + previous := runtime.GOMAXPROCS(threads) + defer runtime.GOMAXPROCS(previous) + cache := NewTransactionStatusCache() + block := statusCacheTestBlock(10, benchmarkUniqueTransactions(count)...) + plan, err := planBlockTransactionExecution(block) + require.NoError(t, err) + p := cache.startStatusPreparation(plan) + defer p.wait() + require.Equal(t, threads > 1 && count > 32, p != nil) + require.NoError(t, cache.commitBlockWithPreparedDelta(block, plan, p.wait())) + for _, tx := range block.Transactions { + found, err := cache.View().ContainsTransaction(tx) + require.NoError(t, err) + require.True(t, found) + } + }) + } + } +} From c241d6b512fd99da44fbf6ec2192eb4f86570b2d Mon Sep 17 00:00:00 2001 From: 7layermagik <7layermagik@users.noreply.github.com> Date: Mon, 14 Sep 2026 22:02:01 -0500 Subject: [PATCH 06/16] Include raw status-publication benchmark and validation logs --- .../2026-09-15/local-race-final.log | 3 + .../2026-09-15/native-benchmark.log | 85 +++++++++++++++ .../2026-09-15/native-build.log | 0 .../2026-09-15/native-final-run.log | 12 +++ .../2026-09-15/native-overlap-final.log | 95 ++++++++++++++++ .../2026-09-15/native-overlap.log | 102 ++++++++++++++++++ .../2026-09-15/native-race.log | 3 + .../2026-09-15/native-vet.log | 0 .../2026-09-15/recheck-abba.log | 34 ++++++ 9 files changed, 334 insertions(+) create mode 100644 docs/results/status-publication/2026-09-15/local-race-final.log create mode 100644 docs/results/status-publication/2026-09-15/native-benchmark.log create mode 100644 docs/results/status-publication/2026-09-15/native-build.log create mode 100644 docs/results/status-publication/2026-09-15/native-final-run.log create mode 100644 docs/results/status-publication/2026-09-15/native-overlap-final.log create mode 100644 docs/results/status-publication/2026-09-15/native-overlap.log create mode 100644 docs/results/status-publication/2026-09-15/native-race.log create mode 100644 docs/results/status-publication/2026-09-15/native-vet.log create mode 100644 docs/results/status-publication/2026-09-15/recheck-abba.log diff --git a/docs/results/status-publication/2026-09-15/local-race-final.log b/docs/results/status-publication/2026-09-15/local-race-final.log new file mode 100644 index 000000000..17b7d4d2a --- /dev/null +++ b/docs/results/status-publication/2026-09-15/local-race-final.log @@ -0,0 +1,3 @@ +ok github.com/Overclock-Validator/mithril/pkg/replay 4.618s +ok github.com/Overclock-Validator/mithril/pkg/block 1.774s +? github.com/Overclock-Validator/mithril/pkg/metrics [no test files] diff --git a/docs/results/status-publication/2026-09-15/native-benchmark.log b/docs/results/status-publication/2026-09-15/native-benchmark.log new file mode 100644 index 000000000..ea4b94603 --- /dev/null +++ b/docs/results/status-publication/2026-09-15/native-benchmark.log @@ -0,0 +1,85 @@ +goos: linux +goarch: amd64 +pkg: github.com/Overclock-Validator/mithril/pkg/replay +cpu: AMD Ryzen 7 9700X 8-Core Processor +BenchmarkTransactionStatusPublication/groups_1/existing_false/legacy-2 10 4971792 ns/op 6302003 B/op 555 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_false/legacy-2 10 5003752 ns/op 6302003 B/op 555 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_false/legacy-2 10 4990269 ns/op 6302003 B/op 555 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_false/legacy-2 10 5067117 ns/op 6302003 B/op 555 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_false/legacy-2 10 4855132 ns/op 6302003 B/op 555 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_false/sized-2 10 3869972 ns/op 3151475 B/op 265 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_false/sized-2 10 3593875 ns/op 3151477 B/op 265 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_false/sized-2 10 3116164 ns/op 3151475 B/op 265 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_false/sized-2 10 3292134 ns/op 3151475 B/op 265 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_false/sized-2 10 3331600 ns/op 3151475 B/op 265 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_false/prepared_total-2 10 3459366 ns/op 3151780 B/op 269 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_false/prepared_total-2 10 3555785 ns/op 3151707 B/op 269 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_false/prepared_total-2 10 3338994 ns/op 3151718 B/op 269 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_false/prepared_total-2 10 3393252 ns/op 3151755 B/op 269 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_false/prepared_total-2 10 3035989 ns/op 3151707 B/op 269 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_false/prepared_commit-2 10 1433079 ns/op 1575587 B/op 132 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_false/prepared_commit-2 10 1465220 ns/op 1575587 B/op 132 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_false/prepared_commit-2 10 1587430 ns/op 1575587 B/op 132 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_false/prepared_commit-2 10 1668259 ns/op 1575587 B/op 132 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_false/prepared_commit-2 10 1625507 ns/op 1575587 B/op 132 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_true/legacy-2 10 5038118 ns/op 3466193 B/op 304 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_true/legacy-2 10 4926609 ns/op 3466193 B/op 304 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_true/legacy-2 10 4965933 ns/op 3466196 B/op 304 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_true/legacy-2 10 4991180 ns/op 3466193 B/op 304 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_true/legacy-2 10 5174867 ns/op 3466193 B/op 304 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_true/sized-2 10 4592110 ns/op 1891049 B/op 159 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_true/sized-2 10 4689758 ns/op 1891049 B/op 159 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_true/sized-2 10 4579822 ns/op 1891049 B/op 159 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_true/sized-2 10 4893416 ns/op 1891052 B/op 159 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_true/sized-2 10 4657032 ns/op 1891049 B/op 159 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_true/prepared_total-2 10 4617374 ns/op 1891281 B/op 163 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_true/prepared_total-2 10 4434153 ns/op 1891281 B/op 163 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_true/prepared_total-2 10 4425112 ns/op 1891281 B/op 163 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_true/prepared_total-2 10 4362413 ns/op 1891281 B/op 163 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_true/prepared_total-2 10 4649669 ns/op 1891281 B/op 163 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_true/prepared_commit-2 10 2756954 ns/op 315161 B/op 26 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_true/prepared_commit-2 10 2987364 ns/op 315161 B/op 26 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_true/prepared_commit-2 10 2747530 ns/op 315161 B/op 26 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_true/prepared_commit-2 10 3033686 ns/op 315161 B/op 26 allocs/op +BenchmarkTransactionStatusPublication/groups_1/existing_true/prepared_commit-2 10 2735607 ns/op 315161 B/op 26 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_false/legacy-2 10 4829304 ns/op 6301427 B/op 659 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_false/legacy-2 10 4554119 ns/op 6301427 B/op 659 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_false/legacy-2 10 4920607 ns/op 6301427 B/op 659 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_false/legacy-2 10 4624988 ns/op 6301427 B/op 659 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_false/legacy-2 10 4587116 ns/op 6301427 B/op 659 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_false/sized-2 10 3398182 ns/op 3151859 B/op 283 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_false/sized-2 10 3626107 ns/op 3151859 B/op 283 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_false/sized-2 10 3743889 ns/op 3151859 B/op 283 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_false/sized-2 10 4029608 ns/op 3151861 B/op 283 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_false/sized-2 10 4958660 ns/op 3151859 B/op 283 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_false/prepared_total-2 10 5919178 ns/op 3152091 B/op 287 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_false/prepared_total-2 10 5632307 ns/op 3152091 B/op 287 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_false/prepared_total-2 10 5473052 ns/op 3152091 B/op 287 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_false/prepared_total-2 10 5915974 ns/op 3152091 B/op 287 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_false/prepared_total-2 10 6688551 ns/op 3152091 B/op 287 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_false/prepared_commit-2 10 2205247 ns/op 1575779 B/op 141 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_false/prepared_commit-2 10 2480043 ns/op 1575779 B/op 141 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_false/prepared_commit-2 10 2205044 ns/op 1575779 B/op 141 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_false/prepared_commit-2 10 1927871 ns/op 1575779 B/op 141 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_false/prepared_commit-2 10 2036015 ns/op 1575779 B/op 141 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_true/legacy-2 10 7297434 ns/op 3465532 B/op 357 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_true/legacy-2 10 5048127 ns/op 3465532 B/op 357 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_true/legacy-2 10 5224367 ns/op 3465532 B/op 357 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_true/legacy-2 10 5277320 ns/op 3465532 B/op 357 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_true/legacy-2 10 4971670 ns/op 3465535 B/op 357 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_true/sized-2 10 5041342 ns/op 1891228 B/op 169 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_true/sized-2 10 4491682 ns/op 1891228 B/op 169 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_true/sized-2 10 4966235 ns/op 1891228 B/op 169 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_true/sized-2 10 4644107 ns/op 1891228 B/op 169 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_true/sized-2 10 4479055 ns/op 1891228 B/op 169 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_true/prepared_total-2 10 4914007 ns/op 1891511 B/op 173 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_true/prepared_total-2 10 5031889 ns/op 1891508 B/op 173 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_true/prepared_total-2 10 5046504 ns/op 1891460 B/op 173 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_true/prepared_total-2 10 4496234 ns/op 1891460 B/op 173 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_true/prepared_total-2 10 4949162 ns/op 1891460 B/op 173 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_true/prepared_commit-2 10 2603068 ns/op 315148 B/op 27 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_true/prepared_commit-2 10 2859800 ns/op 315148 B/op 27 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_true/prepared_commit-2 10 2858236 ns/op 315148 B/op 27 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_true/prepared_commit-2 10 3113986 ns/op 315148 B/op 27 allocs/op +BenchmarkTransactionStatusPublication/groups_4/existing_true/prepared_commit-2 10 2788538 ns/op 315148 B/op 27 allocs/op +PASS diff --git a/docs/results/status-publication/2026-09-15/native-build.log b/docs/results/status-publication/2026-09-15/native-build.log new file mode 100644 index 000000000..e69de29bb diff --git a/docs/results/status-publication/2026-09-15/native-final-run.log b/docs/results/status-publication/2026-09-15/native-final-run.log new file mode 100644 index 000000000..c6d28bfba --- /dev/null +++ b/docs/results/status-publication/2026-09-15/native-final-run.log @@ -0,0 +1,12 @@ +Running as unit: mithril-status-publication-final-20260915.service +race 0 +vet 0 +build 0 +benchmark-build 0 +benchmark 0 +overlap-final 0 + Finished with result: success +Main processes terminated with: code=exited, status=0/SUCCESS + Service runtime: 37.879s + CPU time consumed: 45.681s + Memory peak: 634.8M (swap: 0B) diff --git a/docs/results/status-publication/2026-09-15/native-overlap-final.log b/docs/results/status-publication/2026-09-15/native-overlap-final.log new file mode 100644 index 000000000..a1e297aaf --- /dev/null +++ b/docs/results/status-publication/2026-09-15/native-overlap-final.log @@ -0,0 +1,95 @@ +goos: linux +goarch: amd64 +pkg: github.com/Overclock-Validator/mithril/pkg/replay +cpu: AMD Ryzen 7 9700X 8-Core Processor +BenchmarkTransactionStatusExecutionOverlap/legacy 5 24692874 ns/op 6513086 commit-with-wait-ns/op 18179262 execution-ns/op 23276118 B/op 242222 allocs/op +BenchmarkTransactionStatusExecutionOverlap/legacy 5 22834712 ns/op 5772168 commit-with-wait-ns/op 17062174 execution-ns/op 23276118 B/op 242222 allocs/op +BenchmarkTransactionStatusExecutionOverlap/legacy 5 23370663 ns/op 6486422 commit-with-wait-ns/op 16883798 execution-ns/op 23276136 B/op 242222 allocs/op +BenchmarkTransactionStatusExecutionOverlap/legacy 5 23246803 ns/op 5956655 commit-with-wait-ns/op 17289566 execution-ns/op 23276145 B/op 242222 allocs/op +BenchmarkTransactionStatusExecutionOverlap/legacy 5 24078611 ns/op 6062517 commit-with-wait-ns/op 18015548 execution-ns/op 23276059 B/op 242221 allocs/op +BenchmarkTransactionStatusExecutionOverlap/legacy-2 5 20677847 ns/op 5607605 commit-with-wait-ns/op 15069600 execution-ns/op 23276630 B/op 242225 allocs/op +BenchmarkTransactionStatusExecutionOverlap/legacy-2 5 20222041 ns/op 5394296 commit-with-wait-ns/op 14827324 execution-ns/op 23276627 B/op 242225 allocs/op +BenchmarkTransactionStatusExecutionOverlap/legacy-2 6 19971750 ns/op 5078614 commit-with-wait-ns/op 14892735 execution-ns/op 23276532 B/op 242224 allocs/op +BenchmarkTransactionStatusExecutionOverlap/legacy-2 5 22424538 ns/op 5368235 commit-with-wait-ns/op 17055698 execution-ns/op 23276712 B/op 242226 allocs/op +BenchmarkTransactionStatusExecutionOverlap/legacy-2 5 20975910 ns/op 5790017 commit-with-wait-ns/op 15184788 execution-ns/op 23276624 B/op 242225 allocs/op +BenchmarkTransactionStatusExecutionOverlap/sized 5 21296975 ns/op 3860111 commit-with-wait-ns/op 17436468 execution-ns/op 20125587 B/op 241932 allocs/op +BenchmarkTransactionStatusExecutionOverlap/sized 5 21108482 ns/op 4260022 commit-with-wait-ns/op 16848123 execution-ns/op 20125592 B/op 241932 allocs/op +BenchmarkTransactionStatusExecutionOverlap/sized 6 20802767 ns/op 4299800 commit-with-wait-ns/op 16502635 execution-ns/op 20125558 B/op 241932 allocs/op +BenchmarkTransactionStatusExecutionOverlap/sized 6 22758328 ns/op 5033044 commit-with-wait-ns/op 17724954 execution-ns/op 20125533 B/op 241931 allocs/op +BenchmarkTransactionStatusExecutionOverlap/sized 5 23246396 ns/op 5467937 commit-with-wait-ns/op 17778082 execution-ns/op 20125614 B/op 241932 allocs/op +BenchmarkTransactionStatusExecutionOverlap/sized-2 6 19020036 ns/op 4526523 commit-with-wait-ns/op 14493163 execution-ns/op 20126028 B/op 241935 allocs/op +BenchmarkTransactionStatusExecutionOverlap/sized-2 6 18422586 ns/op 4167392 commit-with-wait-ns/op 14254792 execution-ns/op 20126009 B/op 241934 allocs/op +BenchmarkTransactionStatusExecutionOverlap/sized-2 6 18573776 ns/op 4299496 commit-with-wait-ns/op 14273834 execution-ns/op 20126012 B/op 241934 allocs/op +BenchmarkTransactionStatusExecutionOverlap/sized-2 6 18332367 ns/op 3962772 commit-with-wait-ns/op 14369209 execution-ns/op 20126006 B/op 241934 allocs/op +BenchmarkTransactionStatusExecutionOverlap/sized-2 6 19268730 ns/op 4381208 commit-with-wait-ns/op 14886998 execution-ns/op 20126038 B/op 241935 allocs/op +BenchmarkTransactionStatusExecutionOverlap/overlap 6 21422337 ns/op 4616295 commit-with-wait-ns/op 16804831 execution-ns/op 20125514 B/op 241931 allocs/op +BenchmarkTransactionStatusExecutionOverlap/overlap 5 21450447 ns/op 3882218 commit-with-wait-ns/op 17567431 execution-ns/op 20125593 B/op 241932 allocs/op +BenchmarkTransactionStatusExecutionOverlap/overlap 5 23556986 ns/op 5081649 commit-with-wait-ns/op 18474472 execution-ns/op 20125593 B/op 241932 allocs/op +BenchmarkTransactionStatusExecutionOverlap/overlap 5 22188363 ns/op 4492746 commit-with-wait-ns/op 17694970 execution-ns/op 20125587 B/op 241932 allocs/op +BenchmarkTransactionStatusExecutionOverlap/overlap 6 22888561 ns/op 4725085 commit-with-wait-ns/op 18162634 execution-ns/op 20125536 B/op 241931 allocs/op +BenchmarkTransactionStatusExecutionOverlap/overlap-2 6 17090947 ns/op 2119131 commit-with-wait-ns/op 14966507 execution-ns/op 20126253 B/op 241938 allocs/op +BenchmarkTransactionStatusExecutionOverlap/overlap-2 6 17313847 ns/op 2127423 commit-with-wait-ns/op 15181536 execution-ns/op 20126212 B/op 241938 allocs/op +BenchmarkTransactionStatusExecutionOverlap/overlap-2 7 17116780 ns/op 1984282 commit-with-wait-ns/op 15127492 execution-ns/op 20126437 B/op 241939 allocs/op +BenchmarkTransactionStatusExecutionOverlap/overlap-2 7 16485753 ns/op 1917207 commit-with-wait-ns/op 14563704 execution-ns/op 20126176 B/op 241938 allocs/op +BenchmarkTransactionStatusExecutionOverlap/overlap-2 6 16709920 ns/op 2210525 commit-with-wait-ns/op 14495070 execution-ns/op 20126125 B/op 241937 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/legacy 1299566 94.83 ns/op 112 B/op 2 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/legacy 1284991 93.05 ns/op 112 B/op 2 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/legacy 1247547 96.51 ns/op 112 B/op 2 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/legacy 1211845 96.60 ns/op 112 B/op 2 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/legacy 1238605 95.12 ns/op 112 B/op 2 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/legacy-2 1596568 76.21 ns/op 112 B/op 2 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/legacy-2 1614400 74.16 ns/op 112 B/op 2 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/legacy-2 1581337 76.14 ns/op 112 B/op 2 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/legacy-2 1574422 74.71 ns/op 112 B/op 2 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/legacy-2 1589880 74.16 ns/op 112 B/op 2 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total 1000000 123.0 ns/op 112 B/op 2 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total 1000000 119.6 ns/op 112 B/op 2 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total 1000000 118.8 ns/op 112 B/op 2 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total 1000000 119.4 ns/op 112 B/op 2 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total 1000000 117.4 ns/op 112 B/op 2 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total-2 1000000 100.4 ns/op 112 B/op 2 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total-2 1231928 97.12 ns/op 112 B/op 2 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total-2 1000000 101.3 ns/op 112 B/op 2 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total-2 1200760 99.52 ns/op 112 B/op 2 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total-2 1206331 100.0 ns/op 112 B/op 2 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/legacy 189308 586.1 ns/op 960 B/op 9 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/legacy 200305 607.5 ns/op 960 B/op 9 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/legacy 188314 597.3 ns/op 960 B/op 9 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/legacy 196060 586.6 ns/op 960 B/op 9 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/legacy 197784 593.0 ns/op 960 B/op 9 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/legacy-2 227380 470.5 ns/op 960 B/op 9 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/legacy-2 240021 461.5 ns/op 960 B/op 9 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/legacy-2 231192 472.9 ns/op 960 B/op 9 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/legacy-2 254556 466.8 ns/op 960 B/op 9 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/legacy-2 239902 443.4 ns/op 960 B/op 9 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total 167852 716.6 ns/op 960 B/op 9 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total 167379 708.2 ns/op 960 B/op 9 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total 167563 708.3 ns/op 960 B/op 9 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total 169233 699.7 ns/op 960 B/op 9 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total 164168 735.9 ns/op 960 B/op 9 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total-2 190630 609.1 ns/op 960 B/op 9 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total-2 190263 615.6 ns/op 960 B/op 9 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total-2 176863 604.2 ns/op 960 B/op 9 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total-2 196165 632.1 ns/op 960 B/op 9 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total-2 185583 588.6 ns/op 960 B/op 9 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/legacy 17916 6009 ns/op 6320 B/op 23 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/legacy 19801 6085 ns/op 6320 B/op 23 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/legacy 19857 6155 ns/op 6320 B/op 23 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/legacy 19308 6126 ns/op 6320 B/op 23 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/legacy 20053 6002 ns/op 6320 B/op 23 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/legacy-2 23298 5038 ns/op 6320 B/op 23 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/legacy-2 23206 5158 ns/op 6320 B/op 23 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/legacy-2 22210 5145 ns/op 6320 B/op 23 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/legacy-2 23406 5305 ns/op 6320 B/op 23 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/legacy-2 23084 5114 ns/op 6320 B/op 23 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total 26672 4343 ns/op 3616 B/op 13 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total 26752 4456 ns/op 3616 B/op 13 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total 27342 4471 ns/op 3616 B/op 13 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total 26960 4312 ns/op 3616 B/op 13 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total 27775 4592 ns/op 3616 B/op 13 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total-2 31856 3644 ns/op 3616 B/op 13 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total-2 32778 3717 ns/op 3616 B/op 13 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total-2 32239 3784 ns/op 3616 B/op 13 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total-2 30372 3818 ns/op 3616 B/op 13 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total-2 32263 3770 ns/op 3616 B/op 13 allocs/op +PASS diff --git a/docs/results/status-publication/2026-09-15/native-overlap.log b/docs/results/status-publication/2026-09-15/native-overlap.log new file mode 100644 index 000000000..e347eed1c --- /dev/null +++ b/docs/results/status-publication/2026-09-15/native-overlap.log @@ -0,0 +1,102 @@ +Running as unit: mithril-status-overlap-bench-20260915.service +goos: linux +goarch: amd64 +pkg: github.com/Overclock-Validator/mithril/pkg/replay +cpu: AMD Ryzen 7 9700X 8-Core Processor +BenchmarkTransactionStatusExecutionOverlap/legacy 6 22659962 ns/op 5699140 commit-with-wait-ns/op 16960453 execution-ns/op 23276112 B/op 242222 allocs/op +BenchmarkTransactionStatusExecutionOverlap/legacy 5 22853486 ns/op 5584087 commit-with-wait-ns/op 17269100 execution-ns/op 23276118 B/op 242222 allocs/op +BenchmarkTransactionStatusExecutionOverlap/legacy 5 23170780 ns/op 6502884 commit-with-wait-ns/op 16667603 execution-ns/op 23276136 B/op 242222 allocs/op +BenchmarkTransactionStatusExecutionOverlap/legacy 5 22833095 ns/op 6053272 commit-with-wait-ns/op 16779392 execution-ns/op 23276116 B/op 242222 allocs/op +BenchmarkTransactionStatusExecutionOverlap/legacy 5 22058768 ns/op 5848843 commit-with-wait-ns/op 16209621 execution-ns/op 23276112 B/op 242222 allocs/op +BenchmarkTransactionStatusExecutionOverlap/legacy-2 6 19456453 ns/op 5114189 commit-with-wait-ns/op 14341895 execution-ns/op 23276694 B/op 242226 allocs/op +BenchmarkTransactionStatusExecutionOverlap/legacy-2 5 20721459 ns/op 5463814 commit-with-wait-ns/op 15257144 execution-ns/op 23276633 B/op 242225 allocs/op +BenchmarkTransactionStatusExecutionOverlap/legacy-2 6 18883924 ns/op 5031514 commit-with-wait-ns/op 13851997 execution-ns/op 23276650 B/op 242225 allocs/op +BenchmarkTransactionStatusExecutionOverlap/legacy-2 6 19163664 ns/op 5128481 commit-with-wait-ns/op 14034884 execution-ns/op 23276584 B/op 242225 allocs/op +BenchmarkTransactionStatusExecutionOverlap/legacy-2 6 18648556 ns/op 4971799 commit-with-wait-ns/op 13676368 execution-ns/op 23276654 B/op 242226 allocs/op +BenchmarkTransactionStatusExecutionOverlap/sized 6 20730994 ns/op 3673079 commit-with-wait-ns/op 17057597 execution-ns/op 20125561 B/op 241932 allocs/op +BenchmarkTransactionStatusExecutionOverlap/sized 5 21164557 ns/op 4762666 commit-with-wait-ns/op 16401568 execution-ns/op 20125556 B/op 241932 allocs/op +BenchmarkTransactionStatusExecutionOverlap/sized 5 20803959 ns/op 3673651 commit-with-wait-ns/op 17130021 execution-ns/op 20125587 B/op 241932 allocs/op +BenchmarkTransactionStatusExecutionOverlap/sized 5 21585622 ns/op 4745710 commit-with-wait-ns/op 16839541 execution-ns/op 20125537 B/op 241931 allocs/op +BenchmarkTransactionStatusExecutionOverlap/sized 5 22061011 ns/op 4786467 commit-with-wait-ns/op 17274047 execution-ns/op 20125526 B/op 241931 allocs/op +BenchmarkTransactionStatusExecutionOverlap/sized-2 6 18481807 ns/op 4332538 commit-with-wait-ns/op 14148935 execution-ns/op 20126009 B/op 241934 allocs/op +BenchmarkTransactionStatusExecutionOverlap/sized-2 6 18233386 ns/op 4143998 commit-with-wait-ns/op 14088971 execution-ns/op 20125942 B/op 241934 allocs/op +BenchmarkTransactionStatusExecutionOverlap/sized-2 6 17926782 ns/op 3900186 commit-with-wait-ns/op 14026293 execution-ns/op 20126008 B/op 241935 allocs/op +BenchmarkTransactionStatusExecutionOverlap/sized-2 6 17641308 ns/op 3846237 commit-with-wait-ns/op 13794796 execution-ns/op 20126009 B/op 241934 allocs/op +BenchmarkTransactionStatusExecutionOverlap/sized-2 6 18874807 ns/op 4403265 commit-with-wait-ns/op 14471150 execution-ns/op 20126009 B/op 241934 allocs/op +BenchmarkTransactionStatusExecutionOverlap/overlap 6 21101004 ns/op 2063760 commit-with-wait-ns/op 19033430 execution-ns/op 20125788 B/op 241936 allocs/op +BenchmarkTransactionStatusExecutionOverlap/overlap 5 21351433 ns/op 3481211 commit-with-wait-ns/op 17866676 execution-ns/op 20125816 B/op 241936 allocs/op +BenchmarkTransactionStatusExecutionOverlap/overlap 5 21378252 ns/op 3800343 commit-with-wait-ns/op 17574134 execution-ns/op 20125816 B/op 241936 allocs/op +BenchmarkTransactionStatusExecutionOverlap/overlap 5 20630833 ns/op 1688017 commit-with-wait-ns/op 18939909 execution-ns/op 20125819 B/op 241936 allocs/op +BenchmarkTransactionStatusExecutionOverlap/overlap 5 22262745 ns/op 3971910 commit-with-wait-ns/op 18286875 execution-ns/op 20125816 B/op 241936 allocs/op +BenchmarkTransactionStatusExecutionOverlap/overlap-2 6 16698061 ns/op 2155589 commit-with-wait-ns/op 14538161 execution-ns/op 20126252 B/op 241938 allocs/op +BenchmarkTransactionStatusExecutionOverlap/overlap-2 7 16586794 ns/op 2151713 commit-with-wait-ns/op 14431196 execution-ns/op 20126259 B/op 241938 allocs/op +BenchmarkTransactionStatusExecutionOverlap/overlap-2 6 16814463 ns/op 2297246 commit-with-wait-ns/op 14512878 execution-ns/op 20126390 B/op 241939 allocs/op +BenchmarkTransactionStatusExecutionOverlap/overlap-2 6 17545014 ns/op 2416401 commit-with-wait-ns/op 15124436 execution-ns/op 20126122 B/op 241937 allocs/op +BenchmarkTransactionStatusExecutionOverlap/overlap-2 6 17149268 ns/op 2323257 commit-with-wait-ns/op 14821130 execution-ns/op 20126128 B/op 241937 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/legacy 1273947 96.94 ns/op 112 B/op 2 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/legacy 1294056 91.35 ns/op 112 B/op 2 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/legacy 1271734 92.04 ns/op 112 B/op 2 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/legacy 1300807 94.81 ns/op 112 B/op 2 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/legacy 1272133 96.92 ns/op 112 B/op 2 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/legacy-2 1589078 76.19 ns/op 112 B/op 2 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/legacy-2 1577804 77.30 ns/op 112 B/op 2 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/legacy-2 1563583 77.34 ns/op 112 B/op 2 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/legacy-2 1626802 74.57 ns/op 112 B/op 2 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/legacy-2 1591627 75.31 ns/op 112 B/op 2 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total 721636 185.7 ns/op 264 B/op 5 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total 704294 179.3 ns/op 264 B/op 5 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total 725799 182.1 ns/op 264 B/op 5 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total 725127 181.7 ns/op 264 B/op 5 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total 700783 176.9 ns/op 264 B/op 5 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total-2 726256 138.7 ns/op 264 B/op 5 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total-2 728829 139.5 ns/op 264 B/op 5 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total-2 861189 144.8 ns/op 264 B/op 5 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total-2 765130 143.7 ns/op 264 B/op 5 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total-2 751879 142.9 ns/op 264 B/op 5 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/legacy 194295 653.8 ns/op 960 B/op 9 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/legacy 176815 648.9 ns/op 960 B/op 9 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/legacy 174601 882.8 ns/op 960 B/op 9 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/legacy 149839 829.7 ns/op 960 B/op 9 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/legacy 162600 810.9 ns/op 960 B/op 9 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/legacy-2 242948 633.3 ns/op 960 B/op 9 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/legacy-2 140822 720.2 ns/op 960 B/op 9 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/legacy-2 156864 754.5 ns/op 960 B/op 9 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/legacy-2 235440 462.3 ns/op 960 B/op 9 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/legacy-2 215985 471.1 ns/op 960 B/op 9 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total 66573 1705 ns/op 1192 B/op 13 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total 71836 1640 ns/op 1192 B/op 13 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total 70480 1655 ns/op 1192 B/op 13 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total 71564 1641 ns/op 1192 B/op 13 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total 73791 1645 ns/op 1192 B/op 13 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total-2 77616 1489 ns/op 1192 B/op 13 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total-2 78930 1520 ns/op 1192 B/op 13 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total-2 75838 1511 ns/op 1192 B/op 13 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total-2 74458 1491 ns/op 1192 B/op 13 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total-2 79232 1551 ns/op 1192 B/op 13 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/legacy 19609 6019 ns/op 6320 B/op 23 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/legacy 18758 6100 ns/op 6320 B/op 23 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/legacy 19924 6118 ns/op 6320 B/op 23 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/legacy 19128 6067 ns/op 6320 B/op 23 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/legacy 19795 6000 ns/op 6320 B/op 23 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/legacy-2 23222 5027 ns/op 6320 B/op 23 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/legacy-2 23133 5064 ns/op 6320 B/op 23 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/legacy-2 23482 5243 ns/op 6320 B/op 23 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/legacy-2 22717 5013 ns/op 6320 B/op 23 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/legacy-2 24062 5630 ns/op 6320 B/op 23 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total 20682 6564 ns/op 3848 B/op 17 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total 18136 5879 ns/op 3848 B/op 17 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total 21844 5837 ns/op 3848 B/op 17 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total 19086 5635 ns/op 3848 B/op 17 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total 19254 5724 ns/op 3848 B/op 17 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total-2 20040 5546 ns/op 3848 B/op 17 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total-2 18378 5860 ns/op 3848 B/op 17 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total-2 19862 5961 ns/op 3848 B/op 17 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total-2 19912 6906 ns/op 3848 B/op 17 allocs/op +BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total-2 21711 7037 ns/op 3848 B/op 17 allocs/op +PASS +ok github.com/Overclock-Validator/mithril/pkg/replay 17.258s + Finished with result: success +Main processes terminated with: code=exited, status=0/SUCCESS + Service runtime: 19.605s + CPU time consumed: 23.467s + Memory peak: 387.6M (swap: 0B) diff --git a/docs/results/status-publication/2026-09-15/native-race.log b/docs/results/status-publication/2026-09-15/native-race.log new file mode 100644 index 000000000..00504d333 --- /dev/null +++ b/docs/results/status-publication/2026-09-15/native-race.log @@ -0,0 +1,3 @@ +ok github.com/Overclock-Validator/mithril/pkg/replay 2.267s +ok github.com/Overclock-Validator/mithril/pkg/block 1.133s +? github.com/Overclock-Validator/mithril/pkg/metrics [no test files] diff --git a/docs/results/status-publication/2026-09-15/native-vet.log b/docs/results/status-publication/2026-09-15/native-vet.log new file mode 100644 index 000000000..e69de29bb diff --git a/docs/results/status-publication/2026-09-15/recheck-abba.log b/docs/results/status-publication/2026-09-15/recheck-abba.log new file mode 100644 index 000000000..9720620a0 --- /dev/null +++ b/docs/results/status-publication/2026-09-15/recheck-abba.log @@ -0,0 +1,34 @@ +Running as unit: mithril-status-publication-recheck-20260915.service; invocation ID: 8079e456482443308a68c660fb8f6b29 +goos: linux +goarch: amd64 +pkg: github.com/Overclock-Validator/mithril/pkg/replay +cpu: AMD Ryzen 7 9700X 8-Core Processor +BenchmarkTransactionStatusPublication/groups_4/existing_false/legacy-2 50 4400299 ns/op 6301401 B/op 659 allocs/op +PASS + +goos: linux +goarch: amd64 +pkg: github.com/Overclock-Validator/mithril/pkg/replay +cpu: AMD Ryzen 7 9700X 8-Core Processor +BenchmarkTransactionStatusPublication/groups_4/existing_false/prepared_total-2 50 2984879 ns/op 3152087 B/op 287 allocs/op +PASS + +goos: linux +goarch: amd64 +pkg: github.com/Overclock-Validator/mithril/pkg/replay +cpu: AMD Ryzen 7 9700X 8-Core Processor +BenchmarkTransactionStatusPublication/groups_4/existing_false/prepared_total-2 50 3131031 ns/op 3152091 B/op 287 allocs/op +PASS + +goos: linux +goarch: amd64 +pkg: github.com/Overclock-Validator/mithril/pkg/replay +cpu: AMD Ryzen 7 9700X 8-Core Processor +BenchmarkTransactionStatusPublication/groups_4/existing_false/legacy-2 50 4564884 ns/op 6301399 B/op 659 allocs/op +PASS + + Finished with result: success +Main processes terminated with: code=exited, status=0/SUCCESS + Service runtime: 1.278s + CPU time consumed: 1.456s + Memory peak: 73.9M (swap: 0B) From fe54faa2af45c00954b154bcf8fd65607dd1b0cd Mon Sep 17 00:00:00 2001 From: 7layermagik <7layermagik@users.noreply.github.com> Date: Mon, 14 Sep 2026 22:42:51 -0500 Subject: [PATCH 07/16] Reuse immutable node encodings across status checkpoints Share lazy encoding state through capture and pruning without retaining parent links or copying synchronization primitives. Keep MTS2 bytes and caller-owned output unchanged; allocate exactly sized node and checkpoint buffers. Check the original wire encoder, concurrent pruning and encoding, output ownership and recovery. Benchmark moving windows including the default 128-root cadence; document retained-memory costs and the all-new-window limit. --- docs/status-checkpoint-capture.md | 53 ++++++- pkg/replay/transaction_status_cache.go | 138 +++++++++++------ .../transaction_status_capture_bench_test.go | 49 +++++- pkg/replay/transaction_status_capture_test.go | 141 +++++++++++++++++- 4 files changed, 331 insertions(+), 50 deletions(-) diff --git a/docs/status-checkpoint-capture.md b/docs/status-checkpoint-capture.md index e47e67e2b..7f096d6b5 100644 --- a/docs/status-checkpoint-capture.md +++ b/docs/status-checkpoint-capture.md @@ -1,7 +1,52 @@ -# Transaction-status checkpoint capture +# Transaction-status checkpoint capture and encoding -Replay used to encode and sort the full status checkpoint while preparing a promotion. Capture now pins immutable lineage and coverage metadata; the existing promotion worker encodes the same checkpoint later. Capture occurs before pruning and publication ordering stays unchanged. Pinned snapshots retain their nodes across pruning/unwind. +Replay captures immutable lineage and coverage metadata before submitting a +checkpoint to the promotion worker. Sorting, encoding and writing happen on +that worker. Capture does not retain parent links outside the selected window. +Publication and durable-root ordering are unchanged. -The native Zen5 benchmark captured roughly30MB of status data: original202–207ms versus5.92–6.06microseconds on replay. Encoding moved to the worker and was not eliminated. Ten live captures took29–34microseconds; worker encoding179–367ms. These are historical stage measurements, not a promise of total validator speedup; see docs/results/status-cache/2026-09-14. +Each node memoizes its canonical encoded body on first serialization. Capture +and pruning share the same cache object when copying a node header; they never +copy a used synchronization primitive. Encoding depends on the immutable slot, +block-ID presence/value and status delta, not its parent link. Concurrent +encoders synchronize through `sync.Once` without taking the live cache lock. +Each snapshot still constructs its own coverage header and returns an owned +output buffer. The MTS2 format and restore validation are unchanged. -This PR also includes batched expiry; see transaction-status-expiry.md. The proposed transaction-status publication optimization has not been implemented or included. Fresh standalone replay race and vet checks are under docs/results/pr-split-2026-09-15/status. +The cache retains roughly one extra encoded window (30 MB for 1.5 million +keys), plus any nodes pinned by older views. There is no global encoding map: +caches become collectible with their last node/view. A completely new window +still pays for all sorting. Output copying and checkpoint I/O remain necessary. + +## Encoding benchmark + +`BenchmarkTransactionStatusCheckpointEncoding` uses a 300-root window with +5,000 keys per root (1.5 million keys, roughly 30 MB encoded). Each iteration +replaces the specified number of roots. Fixture creation and initial warming +are excluded; new node headers, sorting and output allocations are included. +The baseline is the original uncached wire encoder retained in tests. + +Apple M4 Pro, Go 1.26.4, one caller, GOMAXPROCS=12; medians of three runs: + +| New roots per checkpoint | Original encoding | Cached encoding | +| --- | ---: | ---: | +| 1 | 158.05 ms | 1.35 ms | +| 8 | 157.14 ms | 5.09 ms | +| 32 | 155.62 ms | 17.51 ms | +| 128 (default fold cadence) | 153.74 ms | 67.11 ms | +| 300 (entirely new) | 155.90 ms | 156.29 ms | + +At the default cadence, allocated bytes per encoding fell from 99.12 MB to +57.33 MB; this excludes retained heap. These are encoding measurements, not +end-to-end fold/replay timings or live FAST improvements. Data distribution +matters: newly rooted large blocks can account for most keys in the window. + +Run `go test ./pkg/replay -run '^$' -bench '^BenchmarkTransactionStatusCheckpointEncoding$' -benchmem -benchtime=1s -count=3`. + +Tests compare exact bytes with the original encoder across coverage flags, +block IDs and sorted groups; check concurrent encoding during pruning/unwind; +verify cache sharing before and after warming; and restore checkpoints after +callers mutate their own output buffers. The replay race suite and vet pass. + +Related behavior: [status expiry](transaction-status-expiry.md) and +[status publication](transaction-status-publication.md). diff --git a/pkg/replay/transaction_status_cache.go b/pkg/replay/transaction_status_cache.go index 2703c4199..f299f8a02 100644 --- a/pkg/replay/transaction_status_cache.go +++ b/pkg/replay/transaction_status_cache.go @@ -10,6 +10,7 @@ import ( "path/filepath" "sort" "sync" + "sync/atomic" b "github.com/Overclock-Validator/mithril/pkg/block" "github.com/Overclock-Validator/mithril/pkg/state" @@ -48,6 +49,38 @@ type transactionStatusNode struct { hasBlockID bool parent *transactionStatusNode delta transactionStatusDelta + + // Shared by parentless checkpoint copies and relinked retained nodes. + // Only the memoized encoding changes after publication; lineage and delta + // remain immutable. Never copy the atomic field after its first use. + encoding atomic.Pointer[transactionStatusNodeEncoding] +} + +type transactionStatusNodeEncoding struct { + once sync.Once + data []byte +} + +func (n *transactionStatusNode) encodingCache() *transactionStatusNodeEncoding { + if cache := n.encoding.Load(); cache != nil { + return cache + } + cache := new(transactionStatusNodeEncoding) + if n.encoding.CompareAndSwap(nil, cache) { + return cache + } + return n.encoding.Load() +} + +// copyInto initializes a fresh node, sharing its encoding without retaining +// excluded ancestry or copying a used synchronization primitive. The encoding excludes +// parent links and depends only on the immutable slot, block ID and delta. +func (n *transactionStatusNode) copyInto(copy *transactionStatusNode, parent *transactionStatusNode) { + *copy = transactionStatusNode{ + slot: n.slot, blockID: n.blockID, hasBlockID: n.hasBlockID, + parent: parent, delta: n.delta, + } + copy.encoding.Store(n.encodingCache()) } type visibleTransactionStatusGroup struct { @@ -733,10 +766,8 @@ func (c *TransactionStatusCache) CaptureSnapshotThrough(through uint64) (Transac owned := make([]transactionStatusNode, len(nodes)) pinned := make([]*transactionStatusNode, len(nodes)) for i, node := range nodes { - owned[i] = *node - // The encoder consumes only these node deltas. Do not keep the old - // parent chain, which could retain roots excluded from this snapshot. - owned[i].parent = nil + // Do not keep the old parent chain or copy its atomic field. + node.copyInto(&owned[i], nil) pinned[i] = &owned[i] } return &transactionStatusSnapshot{ @@ -895,10 +926,9 @@ func (c *TransactionStatusCache) pruneLocked(through uint64) { c.expireVisibleLocked(nodes[:drop], retained) var parent *transactionStatusNode for _, old := range retained { - parent = &transactionStatusNode{ - slot: old.slot, blockID: old.blockID, hasBlockID: old.hasBlockID, - parent: parent, delta: old.delta, - } + next := new(transactionStatusNode) + old.copyInto(next, parent) + parent = next } c.tip = parent } @@ -976,7 +1006,16 @@ func sliceTransactionStatusKey(messageHash [32]byte, keyIndex uint8) transaction } func marshalTransactionStatusNodes(nodes []*transactionStatusNode, rootedSinceSeed uint16, complete bool, coverageFromGenesis bool) ([]byte, error) { - var buf bytes.Buffer + encoded := make([][]byte, len(nodes)) + size := 9 // magic, flags, rooted count and node count + for i, node := range nodes { + cache := node.encodingCache() + cache.once.Do(func() { cache.data = marshalTransactionStatusNode(node) }) + encoded[i] = cache.data + size += len(cache.data) + } + // Every caller owns its result. Never return or append into a cached slice. + buf := bytes.NewBuffer(make([]byte, 0, size)) buf.Write(transactionStatusSnapshotMagic[:]) flags := byte(0) if complete { @@ -986,44 +1025,57 @@ func marshalTransactionStatusNodes(nodes []*transactionStatusNode, rootedSinceSe flags |= 2 } buf.WriteByte(flags) - _ = binary.Write(&buf, binary.LittleEndian, rootedSinceSeed) - _ = binary.Write(&buf, binary.LittleEndian, uint16(len(nodes))) - for _, node := range nodes { - _ = binary.Write(&buf, binary.LittleEndian, node.slot) - nodeFlags := byte(0) - if node.hasBlockID { - nodeFlags = 1 - } - buf.WriteByte(nodeFlags) - if node.hasBlockID { - buf.Write(node.blockID[:]) - } - blockhashes := make([]solana.Hash, 0, len(node.delta)) - for blockhash := range node.delta { - blockhashes = append(blockhashes, blockhash) + _ = binary.Write(buf, binary.LittleEndian, rootedSinceSeed) + _ = binary.Write(buf, binary.LittleEndian, uint16(len(nodes))) + for _, data := range encoded { + buf.Write(data) + } + return buf.Bytes(), nil +} + +func marshalTransactionStatusNode(node *transactionStatusNode) []byte { + size := 8 + 1 + 4 // slot, flags and group count + if node.hasBlockID { + size += len(node.blockID) + } + for _, group := range node.delta { + size += 32 + 1 + 4 + transactionStatusKeySize*len(group.keys) + } + buf := bytes.NewBuffer(make([]byte, 0, size)) + _ = binary.Write(buf, binary.LittleEndian, node.slot) + nodeFlags := byte(0) + if node.hasBlockID { + nodeFlags = 1 + } + buf.WriteByte(nodeFlags) + if node.hasBlockID { + buf.Write(node.blockID[:]) + } + blockhashes := make([]solana.Hash, 0, len(node.delta)) + for blockhash := range node.delta { + blockhashes = append(blockhashes, blockhash) + } + sort.Slice(blockhashes, func(i, j int) bool { + return bytes.Compare(blockhashes[i][:], blockhashes[j][:]) < 0 + }) + _ = binary.Write(buf, binary.LittleEndian, uint32(len(blockhashes))) + for _, blockhash := range blockhashes { + group := node.delta[blockhash] + buf.Write(blockhash[:]) + buf.WriteByte(group.keyIndex) + keys := make([]transactionStatusKey, 0, len(group.keys)) + for key := range group.keys { + keys = append(keys, key) } - sort.Slice(blockhashes, func(i, j int) bool { - return bytes.Compare(blockhashes[i][:], blockhashes[j][:]) < 0 + sort.Slice(keys, func(i, j int) bool { + return bytes.Compare(keys[i][:], keys[j][:]) < 0 }) - _ = binary.Write(&buf, binary.LittleEndian, uint32(len(blockhashes))) - for _, blockhash := range blockhashes { - group := node.delta[blockhash] - buf.Write(blockhash[:]) - buf.WriteByte(group.keyIndex) - keys := make([]transactionStatusKey, 0, len(group.keys)) - for key := range group.keys { - keys = append(keys, key) - } - sort.Slice(keys, func(i, j int) bool { - return bytes.Compare(keys[i][:], keys[j][:]) < 0 - }) - _ = binary.Write(&buf, binary.LittleEndian, uint32(len(keys))) - for _, key := range keys { - buf.Write(key[:]) - } + _ = binary.Write(buf, binary.LittleEndian, uint32(len(keys))) + for _, key := range keys { + buf.Write(key[:]) } } - return buf.Bytes(), nil + return buf.Bytes() } func (c *TransactionStatusCache) restore(data []byte) error { diff --git a/pkg/replay/transaction_status_capture_bench_test.go b/pkg/replay/transaction_status_capture_bench_test.go index 64bf7a7eb..234743222 100644 --- a/pkg/replay/transaction_status_capture_bench_test.go +++ b/pkg/replay/transaction_status_capture_bench_test.go @@ -3,6 +3,7 @@ package replay import ( "crypto/sha256" "encoding/binary" + "fmt" "testing" "github.com/gagliardetto/solana-go" @@ -11,7 +12,7 @@ import ( var checkpointBenchmarkPayload []byte var checkpointBenchmarkCapture TransactionStatusSnapshot -func BenchmarkTransactionStatusCheckpointCapture(b *testing.B) { +func checkpointEncodingFixture() *TransactionStatusCache { // A private, not-yet-published fixture with the same complete 300-root // metadata as an imported cache. 1.5 million keys encode to roughly 30 MB. c := newTransactionStatusCache(true) @@ -32,6 +33,11 @@ func BenchmarkTransactionStatusCheckpointCapture(b *testing.B) { c.tip = &transactionStatusNode{slot: slot, parent: c.tip, delta: transactionStatusDelta{solana.Hash{1}: {keyIndex: 7, keys: keys}}} } + return c +} + +func BenchmarkTransactionStatusCheckpointCapture(b *testing.B) { + c := checkpointEncodingFixture() view, err := c.CaptureSnapshotThrough(maxTransactionStatusRoots) if err != nil { b.Fatal(err) @@ -64,3 +70,44 @@ func BenchmarkTransactionStatusCheckpointCapture(b *testing.B) { } }) } + +// Moving 300-root windows at several checkpoint cadences. Fixtures and initial +// cache warming are excluded; allocating new node headers and encoding/output +// allocation are included. This measures encoding only, not fsync or account +// checkpoint work. Cold encodes represent first startup/all-new windows. +func BenchmarkTransactionStatusCheckpointEncoding(b *testing.B) { + c := checkpointEncodingFixture() + view, err := c.CaptureSnapshotThrough(300) + if err != nil { + b.Fatal(err) + } + seed := view.(*transactionStatusSnapshot).nodes + for _, advance := range []int{0, 1, 8, 32, defaultFoldBatchSlots, 300} { + for _, cached := range []bool{false, true} { + b.Run(fmt.Sprintf("new=%d/cached=%t", advance, cached), func(b *testing.B) { + nodes := append([]*transactionStatusNode(nil), seed...) + if cached { + _, _ = marshalTransactionStatusNodes(nodes, 300, true, false) + } + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + copy(nodes, nodes[advance:]) + for i := 300 - advance; i < 300; i++ { + nodes[i] = &transactionStatusNode{slot: uint64(301 + n*advance + i), delta: seed[i].delta} + } + var err error + if cached { + checkpointBenchmarkPayload, err = marshalTransactionStatusNodes(nodes, 300, true, false) + } else { + checkpointBenchmarkPayload, err = marshalTransactionStatusNodesUncached(nodes, 300, true, false) + } + if err != nil { + b.Fatal(err) + } + } + b.SetBytes(int64(len(checkpointBenchmarkPayload))) + }) + } + } +} diff --git a/pkg/replay/transaction_status_capture_test.go b/pkg/replay/transaction_status_capture_test.go index 755fa9dfd..8e1344c6f 100644 --- a/pkg/replay/transaction_status_capture_test.go +++ b/pkg/replay/transaction_status_capture_test.go @@ -1,7 +1,10 @@ package replay import ( + "bytes" "encoding/binary" + "fmt" + "sort" "sync" "testing" "time" @@ -9,11 +12,12 @@ import ( b "github.com/Overclock-Validator/mithril/pkg/block" "github.com/Overclock-Validator/mithril/pkg/txstatus" "github.com/gagliardetto/solana-go" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // Keep the pre-split selection/metadata calculation as a differential oracle. -// The wire encoder itself did not change. +// Use the original uncached wire encoder to check byte-for-byte compatibility. func legacyStatusSnapshotForTest(c *TransactionStatusCache, through uint64) ([]byte, error) { c.mu.RLock() defer c.mu.RUnlock() @@ -26,7 +30,7 @@ func legacyStatusSnapshotForTest(c *TransactionStatusCache, through uint64) ([]b if rooted > maxTransactionStatusRoots { rooted = maxTransactionStatusRoots } - return marshalTransactionStatusNodes(nodes, uint16(rooted), complete, c.coverageFromGenesis) + return marshalTransactionStatusNodesUncached(nodes, uint16(rooted), complete, c.coverageFromGenesis) } func importedStatusCacheForTest(t *testing.T) *TransactionStatusCache { @@ -160,3 +164,136 @@ func TestTransactionStatusCaptureEncodingDoesNotLockLiveCache(t *testing.T) { t.Fatal("checkpoint encoding waited for the live cache lock") } } + +func marshalTransactionStatusNodesUncached(nodes []*transactionStatusNode, rootedSinceSeed uint16, complete bool, coverageFromGenesis bool) ([]byte, error) { + var buf bytes.Buffer + buf.Write(transactionStatusSnapshotMagic[:]) + flags := byte(0) + if complete { + flags = 1 + } + if coverageFromGenesis { + flags |= 2 + } + buf.WriteByte(flags) + _ = binary.Write(&buf, binary.LittleEndian, rootedSinceSeed) + _ = binary.Write(&buf, binary.LittleEndian, uint16(len(nodes))) + for _, node := range nodes { + _ = binary.Write(&buf, binary.LittleEndian, node.slot) + nodeFlags := byte(0) + if node.hasBlockID { + nodeFlags = 1 + } + buf.WriteByte(nodeFlags) + if node.hasBlockID { + buf.Write(node.blockID[:]) + } + blockhashes := make([]solana.Hash, 0, len(node.delta)) + for blockhash := range node.delta { + blockhashes = append(blockhashes, blockhash) + } + sort.Slice(blockhashes, func(i, j int) bool { + return bytes.Compare(blockhashes[i][:], blockhashes[j][:]) < 0 + }) + _ = binary.Write(&buf, binary.LittleEndian, uint32(len(blockhashes))) + for _, blockhash := range blockhashes { + group := node.delta[blockhash] + buf.Write(blockhash[:]) + buf.WriteByte(group.keyIndex) + keys := make([]transactionStatusKey, 0, len(group.keys)) + for key := range group.keys { + keys = append(keys, key) + } + sort.Slice(keys, func(i, j int) bool { + return bytes.Compare(keys[i][:], keys[j][:]) < 0 + }) + _ = binary.Write(&buf, binary.LittleEndian, uint32(len(keys))) + for _, key := range keys { + buf.Write(key[:]) + } + } + } + return buf.Bytes(), nil +} + +func TestTransactionStatusEncodingSharedAcrossCaptureAndPrune(t *testing.T) { + for _, warmBeforePrune := range []bool{false, true} { + t.Run(fmt.Sprintf("warm=%t", warmBeforePrune), func(t *testing.T) { + c := importedStatusCacheForTest(t) + for slot := uint64(301); slot <= 305; slot++ { + require.NoError(t, c.CommitBlock(captureTestBlock(slot, 1))) + } + first, err := c.CaptureSnapshotThrough(304) + require.NoError(t, err) + pinned := first.(*transactionStatusSnapshot) + want, err := legacyStatusSnapshotForTest(c, 304) + require.NoError(t, err) + if warmBeforePrune { + got, err := first.MarshalBinary() + require.NoError(t, err) + require.Equal(t, want, got) + } + // Force relinking of retained nodes after the snapshot has copied + // their headers, including the still-unencoded case. + c.Root(305) + second, err := c.CaptureSnapshotThrough(305) + require.NoError(t, err) + current := second.(*transactionStatusSnapshot) + caches := make(map[uint64]*transactionStatusNodeEncoding) + for _, node := range pinned.nodes { + caches[node.slot] = node.encodingCache() + } + for _, node := range current.nodes { + if prior := caches[node.slot]; prior != nil { + require.Same(t, prior, node.encodingCache()) + } + } + var wg sync.WaitGroup + for i := 0; i < 8; i++ { + wg.Add(1) + go func() { + defer wg.Done() + got, err := first.MarshalBinary() + assert.NoError(t, err) + assert.Equal(t, want, got) + // Mutate the node body as well as the header; neither may + // alias the memoized node data or another caller's result. + clear(got) + }() + } + wg.Wait() + currentWant, err := legacyStatusSnapshotForTest(c, 305) + require.NoError(t, err) + got, err := second.MarshalBinary() + require.NoError(t, err) + require.Equal(t, currentWant, got) + restored, err := NewTransactionStatusCacheFromSnapshot(got) + require.NoError(t, err) + roundTrip, err := restored.SnapshotThrough(305) + require.NoError(t, err) + require.Equal(t, got, roundTrip) + }) + } +} + +func TestTransactionStatusEncodingMatchesOriginalWireFormat(t *testing.T) { + // Deliberately unsorted groups/keys, nonzero offsets, empty deltas and + // mixed block-ID presence exercise every independently cached field. + nodes := []*transactionStatusNode{ + {slot: 3, hasBlockID: true, blockID: solana.Hash{9}, delta: transactionStatusDelta{ + solana.Hash{7}: {keyIndex: 11, keys: map[transactionStatusKey]struct{}{{8}: {}, {1}: {}, {4}: {}}}, + solana.Hash{1}: {keyIndex: 2, keys: map[transactionStatusKey]struct{}{{9}: {}, {2}: {}}}, + }}, + {slot: 5}, + {slot: 8, delta: transactionStatusDelta{solana.Hash{3}: {keyIndex: 0, keys: map[transactionStatusKey]struct{}{}}}}, + } + for _, complete := range []bool{false, true} { + for _, genesis := range []bool{false, true} { + want, err := marshalTransactionStatusNodesUncached(nodes, 3, complete, genesis) + require.NoError(t, err) + got, err := marshalTransactionStatusNodes(nodes, 3, complete, genesis) + require.NoError(t, err) + require.Equal(t, want, got) + } + } +} From f9f2f64fd0247d595e8f460e2afe61a19ca3504c Mon Sep 17 00:00:00 2001 From: 7layermagik <7layermagik@users.noreply.github.com> Date: Mon, 14 Sep 2026 22:51:15 -0500 Subject: [PATCH 08/16] Document native checkpoint encoding measurements Record moving-window results at the real 128-root cadence, retained-memory tradeoffs and unchanged cold-window costs. Keep raw benchmark artifacts outside the source tree; do not infer live voting gains from staging measurements. --- docs/status-checkpoint-capture.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/status-checkpoint-capture.md b/docs/status-checkpoint-capture.md index 7f096d6b5..ff11bc1fc 100644 --- a/docs/status-checkpoint-capture.md +++ b/docs/status-checkpoint-capture.md @@ -50,3 +50,24 @@ callers mutate their own output buffers. The replay race suite and vet pass. Related behavior: [status expiry](transaction-status-expiry.md) and [status publication](transaction-status-publication.md). + +## Native Zen 5 validation + +AMD Ryzen 7 9700X, Go 1.26.4, GOMAXPROCS=2, Nice 15 and a two-core CPU quota, +while the validator continued its normal workload. Same moving-window fixture; +three samples per case, medians below. This compares the original uncached +encoder with memoization, not the whole status-publication change against dev. + +| New roots per checkpoint | Original encoding | Cached encoding | +| --- | ---: | ---: | +| 1 | 195.34 ms | 3.73 ms | +| 8 | 195.15 ms | 8.56 ms | +| 32 | 194.77 ms | 23.54 ms | +| 128 (default fold cadence) | 194.52 ms | 85.02 ms | +| 300 (entirely new) | 201.88 ms | 198.55 ms | + +The default-cadence result is approximately 2.3x, with the same 99.12 → 57.33 MB +allocation reduction. Cold/all-new windows remain roughly unchanged. Native +combined race suites, vet and the validator build passed. These are staging +measurements: the encoding cache has not been deployed, so a live reduction in +durable-root lag or missed FAST votes has not yet been established. From b8a3aedf11122ed59dc5b5d87d50c87e6f85b802 Mon Sep 17 00:00:00 2001 From: 7layermagik <7layermagik@users.noreply.github.com> Date: Tue, 15 Sep 2026 00:32:25 -0500 Subject: [PATCH 09/16] replay: preflight fold batch eligibility before copying account writes --- docs/status-checkpoint-capture.md | 26 ++++++++++++++++++++ pkg/accounts/overlay_test.go | 39 ++++++++++++++++++++++++++++++ pkg/accounts/working_set.go | 32 +++++++++++++++++++----- pkg/replay/async_promotion_test.go | 23 ++++++++++++++++++ pkg/replay/promotion.go | 13 ++++------ 5 files changed, 119 insertions(+), 14 deletions(-) diff --git a/docs/status-checkpoint-capture.md b/docs/status-checkpoint-capture.md index ff11bc1fc..62a4dc0e8 100644 --- a/docs/status-checkpoint-capture.md +++ b/docs/status-checkpoint-capture.md @@ -71,3 +71,29 @@ allocation reduction. Cold/all-new windows remain roughly unchanged. Native combined race suites, vet and the validator build passed. These are staging measurements: the encoding cache has not been deployed, so a live reduction in durable-root lag or missed FAST votes has not yet been established. + +## Fold admission before collecting account writes + +Replay checks for a checkpoint batch on every iteration, including skipped +slots. `WorkingSet.PromotionChunk` first counts eligible held slots under its +read lock. If fewer than the configured batch size are available, ordinary +admission returns nil without allocating account-pointer lists. When ready, +it collects only the oldest batch, not the entire eligible suffix. Forced +partial folds still collect the available prefix. + +This preflight is not a finality shortcut or a new recovery policy. Replay's +existing finality/verification gates supply the upper bound. Selection and +collection hold the same lock; account pointers retain their existing ownership +contract. Preparation does not prune the suffix or advance the durable root. +The worker's write/commit order, required resume context, checkpoint reference +validation, completion bookkeeping, and forced shutdown/epoch-boundary paths +are unchanged. + +`BenchmarkBuildFoldJobWaitingForBatch` holds 127 slots with 512 account writes +each while waiting for the default 128-slot batch. On Ryzen 9700X, +GOMAXPROCS=8, three 300 ms runs, median admission-check time fell from 426 µs +to 31.8 ns; 627,008 bytes and 134 allocations per rejected preparation became +zero. This measures an ineligible batch check, not encoding, disk I/O, or a +ready checkpoint. Boundary tests cover gaps, the finality upper bound, a full +batch, forced partial batches, and selection after promotion; existing replay +checkpoint/recovery tests cover the unchanged durable path. diff --git a/pkg/accounts/overlay_test.go b/pkg/accounts/overlay_test.go index f40a26dfc..682e47217 100644 --- a/pkg/accounts/overlay_test.go +++ b/pkg/accounts/overlay_test.go @@ -411,3 +411,42 @@ func TestOverlayDeltaAccountsIncludesOverride(t *testing.T) { assert.Equal(t, pk(1), delta[0].Key) assert.Equal(t, uint64(99), delta[0].Lamports) } + +func TestWorkingSetPromotionChunkBoundaries(t *testing.T) { + w := NewWorkingSet() + for _, slot := range []uint64{5, 7, 9, 11} { + w.Add(slot, []*Account{uoAcct(1, slot), uoAcct(2, slot+100)}) + } + for _, tc := range []struct { + through uint64 + limit int + partial bool + slots []uint64 + }{ + {4, 2, true, nil}, {5, 2, false, nil}, {7, 2, false, []uint64{5, 7}}, + {11, 2, false, []uint64{5, 7}}, {9, 4, true, []uint64{5, 7, 9}}, + {9, 4, false, nil}, {11, 0, true, nil}, {11, -1, false, nil}, + } { + got := w.PromotionChunk(tc.through, tc.limit, tc.partial) + var slots []uint64 + for _, sd := range got { + slots = append(slots, sd.Slot) + require.Len(t, sd.Delta, 2) + for _, acct := range sd.Delta { + require.True(t, acct.Lamports == sd.Slot || acct.Lamports == sd.Slot+100) + } + } + require.Equal(t, tc.slots, slots) + } + // Preparing a job leaves the live suffix intact. Once the caller commits + // and promotes a prefix, the next chunk must start at the surviving slot. + require.Equal(t, 4, w.HeldSlots()) + w.PromotePrefix(7) + chunk := w.PromotionChunk(11, 2, false) + require.Equal(t, []uint64{9, 11}, []uint64{chunk[0].Slot, chunk[1].Slot}) + require.Zero(t, testing.AllocsPerRun(100, func() { + if w.PromotionChunk(9, 2, false) != nil { + panic("partial chunk escaped") + } + })) +} diff --git a/pkg/accounts/working_set.go b/pkg/accounts/working_set.go index d88725f24..083a4405d 100644 --- a/pkg/accounts/working_set.go +++ b/pkg/accounts/working_set.go @@ -134,17 +134,37 @@ func (w *WorkingSet) PromotionPrefix(through uint64) []SlotDelta { w.mu.RLock() defer w.mu.RUnlock() - var batch []SlotDelta - for _, slot := range w.order { // ascending - if slot > through { - break - } + return w.promotionChunkLocked(through, len(w.order), true) +} + +// PromotionChunk returns at most maxSlots oldest held slots through the caller's +// verified promotion bound. Unless allowPartial is set, an incomplete chunk +// returns nil before allocating or collecting account writes. Selection and +// collection share one read lock, so pruning cannot change the selected prefix. +// This only prepares borrowed account pointers; it does not commit, prune, or +// advance durability. Callers still own finality checks and durable commit order. +func (w *WorkingSet) PromotionChunk(through uint64, maxSlots int, allowPartial bool) []SlotDelta { + w.mu.RLock() + defer w.mu.RUnlock() + return w.promotionChunkLocked(through, maxSlots, allowPartial) +} + +func (w *WorkingSet) promotionChunkLocked(through uint64, maxSlots int, allowPartial bool) []SlotDelta { + count := 0 + for count < len(w.order) && count < maxSlots && w.order[count] <= through { + count++ + } + if count == 0 || (!allowPartial && count < maxSlots) { + return nil + } + batch := make([]SlotDelta, count) + for i, slot := range w.order[:count] { layer := w.bySlot[slot] delta := make([]*Account, 0, len(layer.writes)) for _, a := range layer.writes { delta = append(delta, a) } - batch = append(batch, SlotDelta{Slot: slot, Delta: delta}) + batch[i] = SlotDelta{Slot: slot, Delta: delta} } return batch } diff --git a/pkg/replay/async_promotion_test.go b/pkg/replay/async_promotion_test.go index eff0de206..2780e425d 100644 --- a/pkg/replay/async_promotion_test.go +++ b/pkg/replay/async_promotion_test.go @@ -444,3 +444,26 @@ func TestShutdownFlushCannotFoldPastGateTarget(t *testing.T) { target = safePromoteTarget(9, true, 7, 6) assert.Equal(t, uint64(5), target, "persisted-divergence floor holds promotion below the disputed slot") } + +// Model repeated replay/skip iterations while a nearly full checkpoint batch +// waits for one more held bank. Account writes must not be copied on this path. +func BenchmarkBuildFoldJobWaitingForBatch(b *testing.B) { + tail := newUnrootedTail(&fakeDurable{}, &fakeCommitter{durable: accounts.NewMemAccounts()}, 512, 128, "") + writes := make([]*accounts.Account, 512) + for i := range writes { + var key [32]byte + key[0], key[1] = byte(i), byte(i>>8) + writes[i] = &accounts.Account{Key: key, Lamports: 1} + } + for slot := uint64(1); slot <= 127; slot++ { + tail.Add(slot, writes, nil) + } + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + job, err := tail.buildFoldJob(127, false) + if err != nil || job != nil { + b.Fatalf("unexpected fold admission: job=%v err=%v", job, err) + } + } +} diff --git a/pkg/replay/promotion.go b/pkg/replay/promotion.go index 752e0cddc..5472ec4ed 100644 --- a/pkg/replay/promotion.go +++ b/pkg/replay/promotion.go @@ -400,16 +400,13 @@ func (t *unrootedTail) buildFoldJob(through uint64, force bool, hookOverrides .. if err != nil { return nil, err } - prefix := t.overlay.PromotionPrefix(through) - if len(prefix) == 0 { + // Check chunk eligibility before materializing account-write lists. Replay + // calls this on every iteration, including skipped slots; a partial batch + // remains in RAM without rescanning all of its accounts each time. + chunk := t.overlay.PromotionChunk(through, t.batchSlots, force) + if len(chunk) == 0 { return nil, nil } - chunk := prefix - if len(chunk) > t.batchSlots { - chunk = chunk[:t.batchSlots] - } else if len(chunk) < t.batchSlots && !force { - return nil, nil // trailing partial chunk stays in RAM - } through = chunk[len(chunk)-1].Slot ctx := t.contexts[through] From 3f97e9c0d8700364f1406dd16af2a124d488a746 Mon Sep 17 00:00:00 2001 From: 7layermagik <7layermagik@users.noreply.github.com> Date: Tue, 15 Sep 2026 07:21:18 -0500 Subject: [PATCH 10/16] replay: retire completed rewards bookkeeping after durable promotion --- docs/rewards-unwind-retirement.md | 58 +++++++++++++ pkg/replay/block.go | 7 ++ pkg/replay/rewards_retirement.go | 49 +++++++++++ pkg/replay/rewards_retirement_test.go | 119 ++++++++++++++++++++++++++ 4 files changed, 233 insertions(+) create mode 100644 docs/rewards-unwind-retirement.md create mode 100644 pkg/replay/rewards_retirement.go create mode 100644 pkg/replay/rewards_retirement_test.go diff --git a/docs/rewards-unwind-retirement.md b/docs/rewards-unwind-retirement.md new file mode 100644 index 000000000..edf207be8 --- /dev/null +++ b/docs/rewards-unwind-retirement.md @@ -0,0 +1,58 @@ +# Retiring durable rewards bookkeeping + +A completed partitioned-rewards distribution used to leave its in-memory +descriptor alive for the rest of the replay attempt. The fork-switch guard +rejects any such descriptor because account-overlay unwind cannot restore the +consumed spool or its distribution counters. This is necessary while completion +is speculative, but unnecessarily forces checkpoint replay after completion +has become durable. + +Replay now observes the inactive EpochRewards sysvar in a successfully executed +bank's immutable snapshot, with zero partitions remaining. It remembers that +bank's slot and the exact distribution descriptor. Only applying a successful +durable fold through that slot retires the descriptor. Later bank observations +do not move the completion slot forward. A new descriptor/epoch invalidates the +old evidence; missing sysvars or unknown completion retain the old fallback. + +## Safety and recovery contract + +- Completion in memory, certificate finality, and submitting a fold do not + authorize retirement. Failed folds leave the durable watermark unchanged. +- Active distribution and completed-but-not-durable distribution retain the + existing rewards guard. No spool reconstruction or rewards rollback is added. +- After retirement, in-memory switches still require the existing epoch, + vote/stake-cache, parent-context, sysvar and transaction-status checks. + Switches at/below the durable watermark still require durable recovery. +- Completion evidence is replay-thread-owned and process-local. It does not + change checkpoint formats, signing reservations, persisted vote history, + clean-shutdown rules or restart authorization. Restart retains the existing + persisted EpochRewards validation. No extra file or disk sync is introduced. + +## Incident motivating the change + +On Zen 5, distribution completed at slot 3,942,001. At a later parent-linked +switch, the durable checkpoint was already 3,944,067; child 3,944,076 selected +parent 3,944,073, abandoning the suffix from 3,944,074. The remaining descriptor +forced the rewards-window fallback even though completion was below the root. +Checkpoint recovery re-fetched previously received blocks, with logged waits +of 2.739 seconds and 0.967 seconds. A buffered 665-transaction block waited +3,613.510 ms for replay admission and then executed in 7.520 ms. + +These are incident observations, not a before/after benchmark or a measurement +of checkpoint encoding/fsync time. Thirteen observed FAST aggregates omitted +our vote during the recovery interval; that does not prove absence from every +FAST aggregate or a single cause for all thirteen omissions. No live latency +improvement is established until a comparable switch exercises the new path. + +## Validation + +`rewards_retirement_test.go` covers active/missing bank state, unknown completion, +the exact durable boundary, later-bank observations, generation changes, failed +and successful folds, and an exact-parent unwind after retirement (including +account values, resume state and immutable rewards sysvars). Existing unwind +tests still require fallback for zero-remaining bookkeeping without retirement, +cross-epoch switches, dirty vote/stake caches and invalid parent snapshots. + +Full replay/rewards race suites passed locally and in the combined native +build; native node recovery/checkpoint race tests, vet and validator build also +passed. These are software tests, not mainnet power-loss qualification. diff --git a/pkg/replay/block.go b/pkg/replay/block.go index 22cecfc18..1b1f23d74 100644 --- a/pkg/replay/block.go +++ b/pkg/replay/block.go @@ -1747,6 +1747,7 @@ func ReplayBlocks( var unwoundParentBankSysvars *sealevel.BankSysvars var partitionedEpochRewardsEnabled bool var partitionedRewardsInfo *rewards.PartitionedRewardDistributionInfo + var rewardsCompletion partitionedRewardsCompletion var featuresActivatedInFirstSlot []*accounts.Account var parentFeaturesActivatedInFirstSlot []*accounts.Account @@ -2061,6 +2062,10 @@ func ReplayBlocks( mithrilState.LastRootedSlot = promotedThrough mithrilState.LastRootedBankhash = rootedCtx.Bankhash mithrilState.LastRootedContext = rootedCtx + if rewardsCompletion.retire(&partitionedRewardsInfo, promotedThrough) { + rewardsHoldBelowSlot = 0 + mlog.Log.Infof("epoch rewards bookkeeping retired through durable slot %d; later fork switches may unwind in memory", promotedThrough) + } if transactionStatuses.Root(promotedThrough) { mlog.Log.Infof("transaction status cache reconstructed complete %d-root coverage through durable slot %d", maxTransactionStatusRoots, promotedThrough) @@ -2873,6 +2878,7 @@ func ReplayBlocks( boundaryParentCtx = epochBoundaryParentCtx(acctsDb, block, currentEpoch, replayCtx.CurrentFeatures) } partitionedRewardsInfo = handleEpochTransition(acctsDb, partitionedEpochRewardsEnabled, boundaryParentCtx, replayCtx, epochSchedule, replayCtx.CurrentFeatures, block, currentEpoch, rpcc, dbgOpts) + rewardsCompletion = partitionedRewardsCompletion{} currentEpoch = block.Epoch justCrossedEpochBoundary = true // While partitioned rewards are distributing, hold durable promotion @@ -2985,6 +2991,7 @@ func ReplayBlocks( } // The successful child now owns its derived snapshot. Any later bank uses // lastSlotCtx; the one-shot retained unwind bridge is no longer needed. + rewardsCompletion.observeBank(partitionedRewardsInfo, lastSlotCtx.BankSysvars()) unwoundParentBankSysvars = nil postProcessBlockStart := processBlockEnd statusViewStart := time.Now() diff --git a/pkg/replay/rewards_retirement.go b/pkg/replay/rewards_retirement.go new file mode 100644 index 000000000..a03a00cbf --- /dev/null +++ b/pkg/replay/rewards_retirement.go @@ -0,0 +1,49 @@ +package replay + +import ( + "github.com/Overclock-Validator/mithril/pkg/rewards" + "github.com/Overclock-Validator/mithril/pkg/sealevel" +) + +// partitionedRewardsCompletion is replay-thread-owned, process-local evidence +// that a successfully executed bank contains all effects of this distribution. +// It is not a checkpoint or signing authority. Until that bank is durable, +// tryInLoopUnwind must still reject even a zero-remaining distribution: its +// spool has been consumed and cannot be rolled back with the account overlay. +type partitionedRewardsCompletion struct { + info *rewards.PartitionedRewardDistributionInfo + slot uint64 +} + +// observeBank must run only after successful block execution/publication, using +// that bank's immutable sysvars (never the speculative global sysvar cache). +// If the first completed bank lacks evidence, recording a later descendant is +// conservative: retirement then waits for that later bank to become durable. +func (c *partitionedRewardsCompletion) observeBank(info *rewards.PartitionedRewardDistributionInfo, bank *sealevel.BankSysvars) { + if c.info != info { + *c = partitionedRewardsCompletion{info: info} + } + if info == nil || c.slot != 0 || info.NumRewardPartitionsRemaining != 0 || bank == nil || bank.Slot() == 0 { + return + } + epochRewards, ok := bank.EpochRewards() + if ok && !epochRewards.Active { + c.slot = bank.Slot() + } +} + +// retire is called only when replay applies a successfully committed fold and +// advances LastRootedSlot. Finality, an enqueued/in-flight fold, and a failed +// commit do not acknowledge durability. At this boundary every rewards effect +// is in AccountsDB; in-memory switches above it cannot undo distribution. +// Switches at/below it still take durable recovery, whose persisted +// EpochRewards validation remains unchanged. Restart loses this optional +// evidence and reconstructs state through the existing recovery path. +func (c *partitionedRewardsCompletion) retire(info **rewards.PartitionedRewardDistributionInfo, durableSlot uint64) bool { + if *info == nil || *info != c.info || c.slot == 0 || durableSlot < c.slot || (*info).NumRewardPartitionsRemaining != 0 { + return false + } + *info = nil + *c = partitionedRewardsCompletion{} + return true +} diff --git a/pkg/replay/rewards_retirement_test.go b/pkg/replay/rewards_retirement_test.go new file mode 100644 index 000000000..460584eee --- /dev/null +++ b/pkg/replay/rewards_retirement_test.go @@ -0,0 +1,119 @@ +package replay + +import ( + "bytes" + "encoding/base64" + "testing" + + "github.com/Overclock-Validator/mithril/pkg/accounts" + "github.com/Overclock-Validator/mithril/pkg/rewards" + "github.com/Overclock-Validator/mithril/pkg/sealevel" + "github.com/Overclock-Validator/mithril/pkg/state" + bin "github.com/gagliardetto/binary" + "github.com/mr-tron/base58" + "github.com/stretchr/testify/require" +) + +func TestRewardsRetirementRequiresCompletedBankAndDurability(t *testing.T) { + active := &sealevel.SysvarEpochRewards{Active: true} + var raw bytes.Buffer + require.NoError(t, active.MarshalWithEncoder(bin.NewBinEncoder(&raw))) + activeBank, err := sealevel.NewBankSysvars(5, &accounts.Account{Key: sealevel.SysvarEpochRewardsAddr, Data: raw.Bytes()}) + require.NoError(t, err) + missingBank, err := sealevel.NewBankSysvars(5) + require.NoError(t, err) + for _, tc := range []struct { + name string + remaining uint64 + bank *sealevel.BankSysvars + }{ + {"active distribution", 1, testUnwindBankSysvars(t, 5, 50)}, + {"active bank", 0, activeBank}, + {"missing bank", 0, nil}, + {"missing rewards", 0, missingBank}, + {"unknown slot", 0, testUnwindBankSysvars(t, 0, 50)}, + } { + t.Run(tc.name, func(t *testing.T) { + info := &rewards.PartitionedRewardDistributionInfo{NumRewardPartitionsRemaining: tc.remaining} + var completed partitionedRewardsCompletion + completed.observeBank(info, tc.bank) + require.False(t, completed.retire(&info, 100)) + require.NotNil(t, info) + }) + } + + info := &rewards.PartitionedRewardDistributionInfo{} + var completed partitionedRewardsCompletion + require.False(t, completed.retire(&info, 100), "zero remaining without observed completion is insufficient") + completed.observeBank(info, testUnwindBankSysvars(t, 5, 50)) + completed.observeBank(info, testUnwindBankSysvars(t, 7, 50)) + require.False(t, completed.retire(&info, 4), "uncommitted completion must retain the guard") + require.True(t, completed.retire(&info, 5), "later observations must not postpone recorded completion") + require.Nil(t, info) + require.False(t, completed.retire(&info, 100), "retirement is one-shot") +} + +func TestRewardsRetirementDoesNotCrossGenerations(t *testing.T) { + old := &rewards.PartitionedRewardDistributionInfo{SpoolSlot: 1} + next := &rewards.PartitionedRewardDistributionInfo{SpoolSlot: 10} + var completed partitionedRewardsCompletion + completed.observeBank(old, testUnwindBankSysvars(t, 5, 50)) + require.False(t, completed.retire(&next, 100), "old completion cannot retire new bookkeeping") + completed.observeBank(next, testUnwindBankSysvars(t, 11, 60)) + require.False(t, completed.retire(&next, 10)) + require.True(t, completed.retire(&next, 11)) +} + +func TestRewardsRetirementWaitsForSuccessfulFold(t *testing.T) { + fc := &fakeCommitter{durable: accounts.NewMemAccounts(), failOn: 5} + tail := asyncTestTail(fc, 5, 6) + info := &rewards.PartitionedRewardDistributionInfo{} + var completed partitionedRewardsCompletion + completed.observeBank(info, testUnwindBankSysvars(t, 5, 50)) + job, err := tail.buildFoldJob(6, true) + require.NoError(t, err) + require.NotNil(t, job) + root := uint64(4) + require.False(t, completed.retire(&info, root), "capturing a job does not make its bank durable") + require.Error(t, runFoldJob(fc, job)) + require.False(t, completed.retire(&info, root), "a failed fold leaves the old durable root") + fc.failOn = 0 + require.NoError(t, runFoldJob(fc, job)) + ctx := tail.applyFoldJob(job) + require.NotNil(t, ctx) + root = job.through + require.True(t, completed.retire(&info, root)) +} + +func TestRewardsRetirementAllowsExactParentUnwind(t *testing.T) { + resetVoteStakeDirty() + t.Cleanup(resetVoteStakeDirty) + info := &rewards.PartitionedRewardDistributionInfo{} + var completed partitionedRewardsCompletion + completed.observeBank(info, testUnwindBankSysvars(t, 5, 50)) + tail := newUnrootedTail(&fakeDurable{}, &fakeCommitter{durable: accounts.NewMemAccounts()}, 512, 1, "") + parent := &state.ResumeContext{Slot: 7, Bankhash: base58.Encode(make([]byte, 32)), AcctsLtHash: base64.StdEncoding.EncodeToString(make([]byte, 2048)), Capitalization: 700} + bank := testUnwindBankSysvars(t, 7, 50) + tail.Add(7, []*accounts.Account{testAccount(1, 71)}, testHashBytes(7)) + tail.SetContext(7, parent, bank) + tail.Add(8, []*accounts.Account{testAccount(1, 81)}, testHashBytes(8)) + tail.SetContext(8, &state.ResumeContext{Slot: 8}, testUnwindBankSysvars(t, 8, 999)) + sw := &CertifiedSwitch{Slot: 8} + ms := &state.MithrilState{LastRootedSlot: 4} + sched := &sealevel.SysvarEpochSchedule{SlotsPerEpoch: 432000} + rs, _, reason := tryInLoopUnwind(sw, tail, ms, sched, 0, info) + require.Nil(t, rs) + require.Equal(t, unwindFallbackRewardsWindow, reason) + ms.LastRootedSlot = 5 + markVoteStakeDirty(5) // completed reward writes are also below the durable root + require.True(t, completed.retire(&info, ms.LastRootedSlot)) + rs, restored, reason := tryInLoopUnwind(sw, tail, ms, sched, 0, info) + require.Empty(t, reason) + require.Same(t, bank, restored, "use the surviving bank, never abandoned reward sysvars") + want, err := ResumeStateFromRootedContext(parent, nil) + require.NoError(t, err) + require.Equal(t, want, rs, "resume state must match rebuilding the exact retained parent") + acct, err := tail.GetAccount(8, testAccount(1, 0).Key) + require.NoError(t, err) + require.Equal(t, uint64(71), acct.Lamports, "abandoned account writes must be removed") +} From cb34ddcf6ac5179edc3a89e0f76ee2ba47519a54 Mon Sep 17 00:00:00 2001 From: 7layermagik <7layermagik@users.noreply.github.com> Date: Tue, 15 Sep 2026 07:38:35 -0500 Subject: [PATCH 11/16] replay: reuse ancestor validation for unchanged status cache --- docs/transaction-status-publication.md | 23 ++- pkg/replay/block.go | 4 +- pkg/replay/transaction_status_cache.go | 45 ++++-- .../transaction_status_prepared_test.go | 15 +- ...ction_status_publication_benchmark_test.go | 19 ++- pkg/replay/transaction_status_validation.go | 37 +++++ .../transaction_status_validation_test.go | 151 ++++++++++++++++++ 7 files changed, 271 insertions(+), 23 deletions(-) create mode 100644 pkg/replay/transaction_status_validation.go create mode 100644 pkg/replay/transaction_status_validation_test.go diff --git a/docs/transaction-status-publication.md b/docs/transaction-status-publication.md index d182bc6fb..d20ddc2d4 100644 --- a/docs/transaction-status-publication.md +++ b/docs/transaction-status-publication.md @@ -4,7 +4,7 @@ Replay previously built the immutable per-bank transaction-status delta and grew Count identities by recent blockhash and allocate each delta map at its final capacity. Pre-size newly created visible maps too. For banks with more than 32 transactions and GOMAXPROCS greater than one, prepare the immutable delta during account loading and execution. Smaller banks and single-thread configurations keep the work inline. There is at most one preparation task per ProcessBlock call, and every return joins it, including rejected banks. No status becomes visible during preparation. -The worker reads immutable prepared message identities and briefly snapshots only blockhash slice offsets under the cache read lock. It builds its private maps outside the lock. Commit still checks exact block/identity binding, complete coverage, parent lineage and all ancestor duplicates under the publication lock. A changed slice offset or mismatched preparation triggers a rebuild from the actual block's identities. Publication still happens only after successful bank-state commit. Failed instructions within an accepted bank remain processed; rejected banks publish nothing. Pinned views, snapshots, reference counts and unwind keep their existing semantics. +The worker reads immutable prepared message identities and briefly snapshots only blockhash slice offsets under the cache read lock. It builds its private maps outside the lock. Commit checks exact block/identity binding, complete coverage and parent lineage under the publication lock. It rechecks ancestor duplicates unless the successful pre-execution validation belongs to the same cache instance, immutable identity set and unchanged cache version (see below). A changed slice offset or mismatched preparation triggers a rebuild from the actual block's identities. Publication still happens only after successful bank-state commit. Failed instructions within an accepted bank remain processed; rejected banks publish nothing. Pinned views, snapshots, reference counts and unwind keep their existing semantics. TransactionStatusPreparation measures worker wall time, which overlaps execution; it is not additive with replay wall time. TransactionStatusPreparationWait measures the residual join and is nested inside TransactionStatusCommit. The latter still includes waiting, final checks, visible-index updates and node publication. Preparation time excludes initial goroutine scheduling delay; any residual scheduling delay remains in the join/commit timer. @@ -50,3 +50,24 @@ GOMAXPROCS=2 go build -p 2 ./cmd/mithril GOMAXPROCS=2 go test ./pkg/replay -run '^$' -bench '^BenchmarkTransactionStatusPublication$' -benchtime=10x -count=5 go test ./pkg/replay -run '^$' -bench '^BenchmarkTransactionStatus(ExecutionOverlap|SmallPublication)$' -benchtime=100ms -count=5 -cpu=1,2 ``` + +## Reusing pre-execution ancestor validation + +`ProcessBlock` now carries a private validation receipt from its successful ancestor scan to status publication. Under the commit lock, an unchanged receipt avoids scanning all transaction messages again. Publication still checks block binding, complete coverage and parent lineage every time; a missing, foreign or stale receipt performs the full ancestor scan. Direct `CommitBlock` callers retain the full scan. + +The receipt is bound to the cache instance and exact immutable prepared-identity pointer. Visible-index insertion/removal, tip binding, root/prune and restore invalidate the version, including empty commits. Committing and then unwinding back to an identical parent cannot revive a receipt. Version saturation disables reuse permanently rather than wrapping. Snapshot/Agave recovery creates a new cache instance. Receipts are never persisted, and no checkpoint format, durability, voting-resume or crash-recovery guarantee changes. + +The publication benchmark adds `validated_commit` and `invalidated_commit` alongside `prepared_commit`. All three exclude delta preparation and the pre-execution scan. The first reuses that scan; the second calls `Root` between validation and publication, forcing revalidation. Each iteration unwinds and obtains a fresh receipt outside the timer. These are incremental publication comparisons, not the full PR against alpenglow-dev or per-block tail latency. Tests exercise fork replacement introducing duplicates, concurrent sibling commits, cross-cache and cross-identity misuse, snapshot replacement, pruning/root invalidation, binding changes, transaction replacement and version saturation. + +Zen 5 incremental measurement (Ryzen 9700X, Go 1.26.4, GOMAXPROCS=2, five samples × 20 iterations, Nice=19 / 200% CPU quota on the running validator host): + +| Recent blockhash groups | Existing ancestor groups | Full recheck | Reused validation | Invalidated validation | +|---|---|---|---|---| +| 1 | yes | 2.510 ms | 1.364 ms | 2.536 ms | +| 4 | yes | 2.412 ms | 1.283 ms | 2.440 ms | +| 1 | no | 1.461 ms | 1.472 ms | 1.769 ms | +| 4 | no | 1.323 ms | 1.395 ms | 1.303 ms | + +Values are medians of sample means. Existing-group cases remove approximately 1.1 ms of repeated lookup work; new-group cases show no clear gain and shared-host variation. Full native replay/block race suites, targeted node recovery race tests, vet and the combined build passed. Local replay race tests and vet also passed. + +A separate 180-second pre-change live trace observed 728 publications. In the 145 publications taking at least 1 ms, the repeated scan measured 1.805 ms median / 3.180 ms maximum; insertion 2.198 / 6.774 ms. Lock acquisition was at most 0.0058 ms across all publications, and the preparation join at most 0.0010 ms. This latency-selected cohort is not a fixed transaction-size sample or a before/after p99 comparison. Probe overhead is included. These measurements identify removable work; they do not establish a sustained FAST improvement. Raw traces, native test windows and exact combined source stay on the validator host at `/srv/mithril-status-validation-20260915`. diff --git a/pkg/replay/block.go b/pkg/replay/block.go index 1b1f23d74..e4ad3b366 100644 --- a/pkg/replay/block.go +++ b/pkg/replay/block.go @@ -4117,7 +4117,7 @@ func ProcessBlock( return nil, fmt.Errorf("validate transaction messages for slot %d: %w", block.Slot, err) } statusValidationStart := time.Now() - statusValidationErr := transactionStatuses.validateBlockWithPlan(block, executionPlan) + statusValidation, statusValidationErr := transactionStatuses.validateBlockForPublication(block, executionPlan) metrics.GlobalBlockReplay.TransactionStatusValidation.AddTimingSince(statusValidationStart) if statusValidationErr != nil { return nil, fmt.Errorf("validate transaction statuses for slot %d: %w", block.Slot, statusValidationErr) @@ -4393,7 +4393,7 @@ func ProcessBlock( statusWaitStart := time.Now() preparedStatuses := statusPreparation.wait() metrics.GlobalBlockReplay.TransactionStatusPreparationWait.AddTimingSince(statusWaitStart) - statusErr := transactionStatuses.commitBlockWithPreparedDelta(block, executionPlan, preparedStatuses) + statusErr := transactionStatuses.commitBlockWithValidation(block, executionPlan, preparedStatuses, statusValidation) metrics.GlobalBlockReplay.TransactionStatusCommit.AddTimingSince(statusCommitStart) if statusErr != nil { return nil, fmt.Errorf("commit transaction statuses for slot %d after bank state commit: %w", block.Slot, statusErr) diff --git a/pkg/replay/transaction_status_cache.go b/pkg/replay/transaction_status_cache.go index f299f8a02..bb4a157fb 100644 --- a/pkg/replay/transaction_status_cache.go +++ b/pkg/replay/transaction_status_cache.go @@ -105,6 +105,9 @@ type TransactionStatusCache struct { // from a known-empty genesis cache. Without this bit, completeness requires // the full 300 retained roots; a serialized boolean alone is not evidence. coverageFromGenesis bool + + // Protected by mu; see transactionStatusValidation. Never serialized. + validationVersion uint64 } // TransactionStatusView is an immutable view of one bank lineage. It lazily @@ -445,6 +448,7 @@ func (c *TransactionStatusCache) BindTipBlockID(slot uint64, blockID solana.Hash if c.tip.hasBlockID && c.tip.blockID != blockID { return fmt.Errorf("transaction status tip at slot %d has block id %s, cannot bind %s", slot, c.tip.blockID, blockID) } + c.invalidateValidationLocked() c.tip = &transactionStatusNode{ slot: slot, blockID: blockID, hasBlockID: true, parent: c.tip.parent, delta: c.tip.delta, @@ -548,25 +552,33 @@ func (c *TransactionStatusCache) ValidateBlock(block *b.Block) error { // validateBlockWithPlan preserves the status-cache checks while letting // replay reuse the exact immutable identities used for execution planning. func (c *TransactionStatusCache) validateBlockWithPlan(block *b.Block, plan blockTransactionExecutionPlan) error { + _, err := c.validateBlockForPublication(block, plan) + return err +} + +func (c *TransactionStatusCache) validateBlockForPublication(block *b.Block, plan blockTransactionExecutionPlan) (transactionStatusValidation, error) { if block == nil { - return errors.New("nil block") + return transactionStatusValidation{}, errors.New("nil block") } if plan.messageIdentities == nil || !plan.messageIdentities.MatchesBlock(block) { - return errors.New("prepared transaction message identities do not match block") + return transactionStatusValidation{}, errors.New("prepared transaction message identities do not match block") } if c == nil { - return &IncompleteTransactionStatusCoverageError{} + return transactionStatusValidation{}, &IncompleteTransactionStatusCoverageError{} } c.mu.RLock() defer c.mu.RUnlock() if !c.coverageComplete { - return &IncompleteTransactionStatusCoverageError{CachedRoot: c.rootedThrough} + return transactionStatusValidation{}, &IncompleteTransactionStatusCoverageError{CachedRoot: c.rootedThrough} } if err := c.validateParentLocked(block); err != nil { - return err + return transactionStatusValidation{}, err } - return c.validateAncestorTransactionsLocked(block.Slot, plan.messageIdentities) + if err := c.validateAncestorTransactionsLocked(block.Slot, plan.messageIdentities); err != nil { + return transactionStatusValidation{}, err + } + return transactionStatusValidation{cache: c, identities: plan.messageIdentities, version: c.validationVersion}, nil } func (c *TransactionStatusCache) validateAncestorTransactionsLocked(slot uint64, identities *b.PreparedTransactionMessageIdentities) error { @@ -623,6 +635,10 @@ func (c *TransactionStatusCache) commitBlockWithPlan(block *b.Block, plan blockT } func (c *TransactionStatusCache) commitBlockWithPreparedDelta(block *b.Block, plan blockTransactionExecutionPlan, prepared *preparedTransactionStatusDelta) error { + return c.commitBlockWithValidation(block, plan, prepared, transactionStatusValidation{}) +} + +func (c *TransactionStatusCache) commitBlockWithValidation(block *b.Block, plan blockTransactionExecutionPlan, prepared *preparedTransactionStatusDelta, validation transactionStatusValidation) error { if block == nil || plan.messageIdentities == nil || !plan.messageIdentities.MatchesBlock(block) { return errors.New("prepared transaction message identities do not match block") } @@ -631,14 +647,17 @@ func (c *TransactionStatusCache) commitBlockWithPreparedDelta(block *b.Block, pl if !c.coverageComplete { return &IncompleteTransactionStatusCoverageError{CachedRoot: c.rootedThrough} } - // Parent lineage and ancestor status are mutable, so both remain under the - // publication lock even when hashing and same-bank deduplication happened - // earlier. This keeps commit safe across a concurrent branch transition. + // Always check coverage, block binding and parent lineage. Reuse the earlier + // ancestor scan only under this lock and only for the same unchanged cache + // and immutable identities. A branch transition (including away and back) + // or root/prune invalidates it, requiring a fresh scan before publication. if err := c.validateParentLocked(block); err != nil { return err } - if err := c.validateAncestorTransactionsLocked(block.Slot, plan.messageIdentities); err != nil { - return err + if !validation.reusableForLocked(c, plan.messageIdentities) { + if err := c.validateAncestorTransactionsLocked(block.Slot, plan.messageIdentities); err != nil { + return err + } } delta := transactionStatusDelta(nil) @@ -704,6 +723,7 @@ func (c *TransactionStatusCache) Root(through uint64) bool { } c.mu.Lock() defer c.mu.Unlock() + c.invalidateValidationLocked() wasComplete := c.coverageComplete newlyRooted := c.countNodesBetweenLocked(c.rootedThrough, through) if through > c.rootedThrough { @@ -832,6 +852,7 @@ func (c *TransactionStatusCache) validateParentLocked(block *b.Block) error { } func (c *TransactionStatusCache) addDeltaVisibleLocked(delta transactionStatusDelta) error { + c.invalidateValidationLocked() for blockhash, deltaGroup := range delta { if group := c.visible[blockhash]; group != nil && group.keyIndex != deltaGroup.keyIndex { return fmt.Errorf("transaction status blockhash %s uses inconsistent key indexes %d and %d", @@ -855,6 +876,7 @@ func (c *TransactionStatusCache) addDeltaVisibleLocked(delta transactionStatusDe } func (c *TransactionStatusCache) removeDeltaVisibleLocked(delta transactionStatusDelta) { + c.invalidateValidationLocked() for blockhash, deltaGroup := range delta { group := c.visible[blockhash] if group == nil { @@ -1079,6 +1101,7 @@ func marshalTransactionStatusNode(node *transactionStatusNode) []byte { } func (c *TransactionStatusCache) restore(data []byte) error { + c.invalidateValidationLocked() reader := bytes.NewReader(data) var magic [4]byte if _, err := io.ReadFull(reader, magic[:]); err != nil { diff --git a/pkg/replay/transaction_status_prepared_test.go b/pkg/replay/transaction_status_prepared_test.go index fd71b6857..0172fffa6 100644 --- a/pkg/replay/transaction_status_prepared_test.go +++ b/pkg/replay/transaction_status_prepared_test.go @@ -25,7 +25,8 @@ func TestPreparedCommitRechecksAncestorAfterForkSwitch(t *testing.T) { candidate := statusCacheTestBlock(12, retried, unique) plan, err := planBlockTransactionExecution(candidate) requireNoError(err) - requireNoError(cache.validateBlockWithPlan(candidate, plan)) + validation, err := cache.validateBlockForPublication(candidate, plan) + requireNoError(err) prepared := cache.prepareTransactionStatusDelta(plan.messageIdentities) requireNoError(cache.Unwind(11)) @@ -35,7 +36,7 @@ func TestPreparedCommitRechecksAncestorAfterForkSwitch(t *testing.T) { ) requireNoError(cache.CommitBlock(replacement)) - err = cache.commitBlockWithPreparedDelta(candidate, plan, prepared) + err = cache.commitBlockWithValidation(candidate, plan, prepared, validation) var ancestorErr *AncestorAlreadyProcessedTransactionMessagesError if !errors.As(err, &ancestorErr) { t.Fatalf("prepared commit error = %v, want ancestor AlreadyProcessed", err) @@ -77,10 +78,12 @@ func TestConcurrentPreparedSiblingCommitsPublishExactlyOne(t *testing.T) { if err != nil { t.Fatal(err) } - if err := cache.validateBlockWithPlan(left, leftPlan); err != nil { + leftValidation, err := cache.validateBlockForPublication(left, leftPlan) + if err != nil { t.Fatalf("prevalidate left sibling: %v", err) } - if err := cache.validateBlockWithPlan(right, rightPlan); err != nil { + rightValidation, err := cache.validateBlockForPublication(right, rightPlan) + if err != nil { t.Fatalf("prevalidate right sibling: %v", err) } @@ -90,11 +93,11 @@ func TestConcurrentPreparedSiblingCommitsPublishExactlyOne(t *testing.T) { results := make(chan error, 2) go func() { <-start - results <- cache.commitBlockWithPreparedDelta(left, leftPlan, leftPrepared) + results <- cache.commitBlockWithValidation(left, leftPlan, leftPrepared, leftValidation) }() go func() { <-start - results <- cache.commitBlockWithPreparedDelta(right, rightPlan, rightPrepared) + results <- cache.commitBlockWithValidation(right, rightPlan, rightPrepared, rightValidation) }() close(start) diff --git a/pkg/replay/transaction_status_publication_benchmark_test.go b/pkg/replay/transaction_status_publication_benchmark_test.go index 1bb19d66c..2fc7d0f45 100644 --- a/pkg/replay/transaction_status_publication_benchmark_test.go +++ b/pkg/replay/transaction_status_publication_benchmark_test.go @@ -90,6 +90,8 @@ func (c *TransactionStatusCache) legacyAddStatusForBenchmark(delta transactionSt // prepared message identities. No execution, disk I/O, signing or networking. // prepared_commit excludes delta preparation; prepared_total includes it and // goroutine dispatch/join, with no execution overlap. Neither measures replay. +// validated_commit also excludes the successful pre-execution ancestor scan. +// invalidated_commit roots between validation and commit, forcing a full recheck. // Each iteration restores the same ancestor contents; existing maps retain // steady-state capacity. Fixture creation, seeding and unwind are not timed. func BenchmarkTransactionStatusPublication(tb *testing.B) { @@ -110,7 +112,7 @@ func BenchmarkTransactionStatusPublication(tb *testing.B) { if err != nil { tb.Fatal(err) } - for _, name := range []string{"legacy", "sized", "prepared_total", "prepared_commit"} { + for _, name := range []string{"legacy", "sized", "prepared_total", "prepared_commit", "validated_commit", "invalidated_commit"} { tb.Run(name, func(tb *testing.B) { cache := NewTransactionStatusCache() if err := cache.CommitBlock(parent); err != nil { @@ -120,11 +122,22 @@ func BenchmarkTransactionStatusPublication(tb *testing.B) { tb.ResetTimer() for range tb.N { var err error - if name == "prepared_commit" { + if name == "prepared_commit" || name == "validated_commit" || name == "invalidated_commit" { tb.StopTimer() prepared := cache.prepareTransactionStatusDelta(plan.messageIdentities) + validation, validationErr := cache.validateBlockForPublication(blk, plan) + if validationErr != nil { + tb.Fatal(validationErr) + } + if name == "invalidated_commit" { + cache.Root(10) + } tb.StartTimer() - err = cache.commitBlockWithPreparedDelta(blk, plan, prepared) + if name == "prepared_commit" { + err = cache.commitBlockWithPreparedDelta(blk, plan, prepared) + } else { + err = cache.commitBlockWithValidation(blk, plan, prepared, validation) + } } else if name == "prepared_total" { prepared := cache.startStatusPreparation(plan).wait() err = cache.commitBlockWithPreparedDelta(blk, plan, prepared) diff --git a/pkg/replay/transaction_status_validation.go b/pkg/replay/transaction_status_validation.go new file mode 100644 index 000000000..4413b8b8f --- /dev/null +++ b/pkg/replay/transaction_status_validation.go @@ -0,0 +1,37 @@ +package replay + +import ( + "math" + + b "github.com/Overclock-Validator/mithril/pkg/block" +) + +// transactionStatusValidation records a successful ancestor scan under cache.mu. +// It authorizes skipping only that scan, never the coverage, parent or exact +// block-identity checks. The receipt is private, bound to one cache instance and +// one immutable identity set, and checked under the publication lock. +// +// Mutating the visible index, binding the tip, rooting/pruning or restoring +// invalidates earlier receipts. In particular, committing and then unwinding to +// the same tip cannot resurrect one. Snapshot/Agave constructors create a new +// cache instance; this receipt is neither persisted nor usable after recovery. +// This optimization changes no crash-recovery or durable-checkpoint guarantee. +type transactionStatusValidation struct { + cache *TransactionStatusCache + identities *b.PreparedTransactionMessageIdentities + version uint64 +} + +func (v transactionStatusValidation) reusableForLocked(c *TransactionStatusCache, identities *b.PreparedTransactionMessageIdentities) bool { + return v.cache == c && v.identities == identities && + v.version == c.validationVersion && c.validationVersion != math.MaxUint64 +} + +// invalidateValidationLocked requires exclusive access (mu, or an unpublished +// constructor). Saturation permanently disables reuse instead of wrapping into +// an old generation. Even empty commits invalidate, because they change lineage. +func (c *TransactionStatusCache) invalidateValidationLocked() { + if c.validationVersion != math.MaxUint64 { + c.validationVersion++ + } +} diff --git a/pkg/replay/transaction_status_validation_test.go b/pkg/replay/transaction_status_validation_test.go new file mode 100644 index 000000000..9a1346125 --- /dev/null +++ b/pkg/replay/transaction_status_validation_test.go @@ -0,0 +1,151 @@ +package replay + +import ( + "errors" + "math" + "testing" + + "github.com/gagliardetto/solana-go" +) + +func TestStatusValidationInvalidation(t *testing.T) { + for _, action := range []string{"commit", "unwind", "round_trip", "root", "bind", "restore"} { + t.Run(action, func(t *testing.T) { + cache := NewTransactionStatusCache() + if err := cache.CommitBlock(statusCacheTestBlock(10)); err != nil { + t.Fatal(err) + } + blk := statusCacheTestBlock(11, statusCacheTestTransaction(1, 2, 3)) + plan, err := planBlockTransactionExecution(blk) + if err != nil { + t.Fatal(err) + } + receipt, err := cache.validateBlockForPublication(blk, plan) + if err != nil { + t.Fatal(err) + } + if !receipt.reusableForLocked(cache, plan.messageIdentities) { + t.Fatal("unchanged receipt not reusable") + } + switch action { + case "commit", "round_trip": + err = cache.CommitBlock(statusCacheTestBlock(11)) + if err == nil && action == "round_trip" { + err = cache.Unwind(11) + } + case "unwind": + err = cache.Unwind(10) + case "root": + cache.Root(10) + case "bind": + err = cache.BindTipBlockID(10, solana.Hash{1}) + case "restore": + var data []byte + data, err = cache.SnapshotThrough(10) + if err == nil { + cache, err = NewTransactionStatusCacheFromSnapshot(data) + } + } + if err != nil { + t.Fatal(err) + } + if receipt.reusableForLocked(cache, plan.messageIdentities) { + t.Fatal("receipt survived " + action) + } + }) + } +} + +func TestStatusValidationCannotCrossCacheOrIdentity(t *testing.T) { + good := NewTransactionStatusCache() + bad := NewTransactionStatusCache() + tx := statusCacheTestTransaction(1, 2, 3) + if err := good.CommitBlock(statusCacheTestBlock(10)); err != nil { + t.Fatal(err) + } + if err := bad.CommitBlock(statusCacheTestBlock(10, tx)); err != nil { + t.Fatal(err) + } + blk := statusCacheTestBlock(11, tx) + plan, err := planBlockTransactionExecution(blk) + if err != nil { + t.Fatal(err) + } + receipt, err := good.validateBlockForPublication(blk, plan) + if err != nil { + t.Fatal(err) + } + // Both caches have the same version and parent slot, but different contents. + if good.validationVersion != bad.validationVersion { + t.Fatal("fixture must have equal versions") + } + prepared := bad.prepareTransactionStatusDelta(plan.messageIdentities) + var already *AncestorAlreadyProcessedTransactionMessagesError + if err := bad.commitBlockWithValidation(blk, plan, prepared, receipt); !errors.As(err, &already) { + t.Fatalf("foreign cache: %v", err) + } + + unique := statusCacheTestBlock(11, statusCacheTestTransaction(4, 5, 6)) + uniquePlan, err := planBlockTransactionExecution(unique) + if err != nil { + t.Fatal(err) + } + receipt, err = bad.validateBlockForPublication(unique, uniquePlan) + if err != nil { + t.Fatal(err) + } + if err := bad.commitBlockWithValidation(blk, plan, prepared, receipt); !errors.As(err, &already) { + t.Fatalf("foreign identities: %v", err) + } + failed, err := bad.validateBlockForPublication(blk, plan) + if err == nil || failed.cache != nil { + t.Fatalf("failed validation returned a receipt: %+v, %v", failed, err) + } +} + +func TestStatusValidationSaturation(t *testing.T) { + cache := NewTransactionStatusCache() + cache.validationVersion = math.MaxUint64 - 1 + blk := statusCacheTestBlock(1) + plan, err := planBlockTransactionExecution(blk) + if err != nil { + t.Fatal(err) + } + receipt, err := cache.validateBlockForPublication(blk, plan) + if err != nil { + t.Fatal(err) + } + cache.Root(0) + cache.Root(0) + if cache.validationVersion != math.MaxUint64 || receipt.reusableForLocked(cache, plan.messageIdentities) { + t.Fatal("generation wrapped or old receipt reusable") + } + receipt, err = cache.validateBlockForPublication(blk, plan) + if err != nil { + t.Fatal(err) + } + if receipt.reusableForLocked(cache, plan.messageIdentities) { + t.Fatal("saturated cache allowed reuse") + } + if err := cache.commitBlockWithValidation(blk, plan, nil, receipt); err != nil { + t.Fatal(err) + } +} + +func TestStatusValidationStillChecksBlockBinding(t *testing.T) { + cache := NewTransactionStatusCache() + blk := statusCacheTestBlock(1, statusCacheTestTransaction(1, 2, 3)) + plan, err := planBlockTransactionExecution(blk) + if err != nil { + t.Fatal(err) + } + receipt, err := cache.validateBlockForPublication(blk, plan) + if err != nil { + t.Fatal(err) + } + prepared := cache.prepareTransactionStatusDelta(plan.messageIdentities) + blk.Transactions[0] = statusCacheTestTransaction(4, 5, 6) + if err := cache.commitBlockWithValidation(blk, plan, prepared, receipt); err == nil { + t.Fatal("replaced transaction accepted") + } +} From c1f864fbfde9efa0a2e395d9cf35069efd17c756 Mon Sep 17 00:00:00 2001 From: 7layermagik <7layermagik@users.noreply.github.com> Date: Tue, 15 Sep 2026 12:44:04 -0500 Subject: [PATCH 12/16] replay: partition large transaction status index updates --- docs/transaction-status-publication.md | 70 ++++++++- pkg/replay/transaction_status_cache.go | 38 +++-- pkg/replay/transaction_status_index.go | 147 ++++++++++++++++++ ...transaction_status_index_benchmark_test.go | 62 ++++++++ pkg/replay/transaction_status_index_test.go | 104 +++++++++++++ pkg/replay/transaction_status_publication.go | 35 ++++- ...ction_status_publication_benchmark_test.go | 8 +- 7 files changed, 439 insertions(+), 25 deletions(-) create mode 100644 pkg/replay/transaction_status_index.go create mode 100644 pkg/replay/transaction_status_index_benchmark_test.go create mode 100644 pkg/replay/transaction_status_index_test.go diff --git a/docs/transaction-status-publication.md b/docs/transaction-status-publication.md index d20ddc2d4..7078d81e5 100644 --- a/docs/transaction-status-publication.md +++ b/docs/transaction-status-publication.md @@ -14,7 +14,7 @@ AMD Ryzen 7 9700X (Zen 5), Go 1.26.4, GOMAXPROCS=2. Tests ran in a separate proc Each block has 33,760 unique prepared message identities spread across one or four recent blockhashes. Existing-group cases seed 33,760 different ancestor transactions. Fixture creation, hashing, seeding and unwind are untimed. Existing maps retain capacity after unwind: the first timed commit's growth is amortized across the ten iterations. This does not model an index growing indefinitely across live blocks. -The frozen baseline functions exactly match alpenglow-dev commit `33dde4050d9250557583395810799aaac2f54017`. Both versions use the same prepared identities, parent/duplicate checks and fixtures. +These historical measurements used the benchmark at `23e18d81`, whose baseline functions matched alpenglow-dev commit `33dde4050d9250557583395810799aaac2f54017`. Both versions used the same prepared identities, parent/duplicate checks and fixtures. The current `legacy` helper uses the current visible-index representation; reproduce this historical comparison at that commit, not by treating today’s helper as a frozen index baseline. | Recent blockhash groups | Parent has keys in these groups | Baseline commit | Sized maps, inline | Preparation + commit, no overlap | Commit after preparation | |---|---|---:|---:|---:|---:| @@ -71,3 +71,71 @@ Zen 5 incremental measurement (Ryzen 9700X, Go 1.26.4, GOMAXPROCS=2, five sample Values are medians of sample means. Existing-group cases remove approximately 1.1 ms of repeated lookup work; new-group cases show no clear gain and shared-host variation. Full native replay/block race suites, targeted node recovery race tests, vet and the combined build passed. Local replay race tests and vet also passed. A separate 180-second pre-change live trace observed 728 publications. In the 145 publications taking at least 1 ms, the repeated scan measured 1.805 ms median / 3.180 ms maximum; insertion 2.198 / 6.774 ms. Lock acquisition was at most 0.0058 ms across all publications, and the preparation join at most 0.0010 ms. This latency-selected cohort is not a fixed transaction-size sample or a before/after p99 comparison. Probe overhead is included. These measurements identify removable work; they do not establish a sustained FAST improvement. Raw traces, native test windows and exact combined source stay on the validator host at `/srv/mithril-status-validation-20260915`. + +## Partitioned visible status index + +Large blockhash groups use 64 smaller reference-count maps, selected by the low +six bits of the stored message key's first byte. During delta preparation, unique +keys are grouped by partition in private scratch space; publication then updates +one partition at a time. Groups starting below 1,024 keys retain one map for their +lifetime, avoiding a full-index copy when they grow. All visible-index reads and +writes retain the existing cache lock. No extra publication worker is introduced. + +The immutable per-bank deltas and MTS2 checkpoint bytes are unchanged. Restore +reconstructs this derived index from those deltas; unwind removes the same bank +references. Preparation does not authorize a block, persist a vote, or extend a +checkpoint's durable coverage. Commit still checks identity binding, complete +coverage, parent lineage and the ancestor-validation receipt. Changed key-slice +offsets discard both the prepared delta and its partition batches. These are the +same duplicate-prevention and crash-recovery guarantees as before this change. + +Incremental Zen 5 comparison against the previously deployed combined validator +(binary SHA256 `2c3628ad62a313a9dcf13878cafcb6fbd8f5fa12cd8637038de762758489c651`), +not the full PR against alpenglow-dev: Go 1.26.4, GOMAXPROCS=2, Nice=19, +200% CPU quota on the active validator host, three samples of 30 iterations. +Each block contains 33,760 unique identities. Values are medians of sample means. + +| Blockhash groups | Existing groups | Validated commit before → after | Preparation + commit, without overlap | +|---|---|---|---| +| 1 | No | 1.492 → 0.852 ms | 3.422 → 3.479 ms | +| 1 | Yes | 1.425 → 1.126 ms | 4.676 → 4.977 ms | +| 4 | No | 1.151 → 0.869 ms | 3.072 → 4.306 ms | +| 4 | Yes | 1.281 → 0.984 ms | 4.346 → 5.812 ms | + +Publication improves in these samples, but total preparation work increases, +especially with multiple blockhashes. Scratch costs roughly 20 bytes per unique +key plus partition metadata and is not retained in published bank nodes. The +benefit depends on execution hiding preparation without excessive contention. + +`BenchmarkStatusMapCriticalTail` measures individual validated commits with +preparation and unwind excluded. Run the identical benchmark file on both source +revisions: three samples of 150 iterations, one blockhash, nearest-rank p99. The +median of each run's p99 fell from 3.142 to 1.488 ms for new groups, but rose from +2.549 to 2.869 ms for warmed existing groups. This is not a consistent component +p99 win, and neither benchmark predicts live FAST inclusion. + +Native targeted replay/block-production race tests, vet and the validator build +passed. Coverage includes a randomized reference-count oracle with concentrated +keys, compact-group growth, expiry, snapshot restore, fork unwind, stale identity +binding and concurrent publication. Source copies, native results, exclusions for +test load and deployment metadata are retained on Zen 5 under +`/srv/mithril-status-index-20260915`. + + +Initial live trial: 958 baseline versus 182 candidate received blocks with at +least 30,000 transactions, excluding startup/native-test windows. Publication +median/p99 measured **3.607/6.901 → 2.875/6.343 ms**. All 182 candidates had +controls matched by leader, position, sender overlap and transaction/CU within +10%; the median per-block difference was **−0.737 ms publication**, **+1.100 ms +preparation**, **+0.498 ms execution**, and **−0.298 ms full assembly-to-local +serialization**. Preparation-wait p99 remained 0.001 ms. Controls are reused and +windows are unequal, so this is observational evidence, not isolated causation. + +Overall large-block p99 was **119.050 → 123.871 ms**; an overall tail improvement +is not established. Five candidate admission outliers (four empty blocks) spent +31.823 ms median / 39.907 ms maximum between spool-completion entry and beginning +delivery, before status publication. Their deeper cause is not yet established +on this binary. Two initial five-minute captures contained 1,911 inclusions in +1,933 unique observed FAST proofs (98.86%); startup is included in this operational +score, and it is not a before/after FAST comparison. Keep the candidate under +monitoring; the status-stage gain alone does not establish the final p99 goal. diff --git a/pkg/replay/transaction_status_cache.go b/pkg/replay/transaction_status_cache.go index bb4a157fb..07a66e00a 100644 --- a/pkg/replay/transaction_status_cache.go +++ b/pkg/replay/transaction_status_cache.go @@ -85,7 +85,7 @@ func (n *transactionStatusNode) copyInto(copy *transactionStatusNode, parent *tr type visibleTransactionStatusGroup struct { keyIndex uint8 - keys map[transactionStatusKey]uint16 + keys transactionStatusIndex } // TransactionStatusCache is replay's authoritative, fork-aware @@ -590,7 +590,7 @@ func (c *TransactionStatusCache) validateAncestorTransactionsLocked(slot uint64, continue } key := sliceTransactionStatusKey(identity.MessageHash, group.keyIndex) - if group.keys[key] == 0 { + if group.keys.count(key) == 0 { continue } if already == nil { @@ -661,13 +661,16 @@ func (c *TransactionStatusCache) commitBlockWithValidation(block *b.Block, plan } delta := transactionStatusDelta(nil) + var indexBatches map[solana.Hash]*transactionStatusIndexBatch if prepared != nil && prepared.identities == plan.messageIdentities { delta = prepared.delta + indexBatches = prepared.indexBatches // A restore or branch transition can change a blockhash's slice offset. // Rebuild from full identities if any current group uses another offset. for blockhash, group := range delta { if visible := c.visible[blockhash]; visible != nil && visible.keyIndex != group.keyIndex { delta = nil + indexBatches = nil break } } @@ -683,7 +686,7 @@ func (c *TransactionStatusCache) commitBlockWithValidation(block *b.Block, plan delta = buildTransactionStatusDelta(plan.messageIdentities, counts, indexes) } - if err := c.addDeltaVisibleLocked(delta); err != nil { + if err := c.addDeltaVisibleBatchesLocked(delta, indexBatches); err != nil { return err } c.tip = &transactionStatusNode{ @@ -852,6 +855,10 @@ func (c *TransactionStatusCache) validateParentLocked(block *b.Block) error { } func (c *TransactionStatusCache) addDeltaVisibleLocked(delta transactionStatusDelta) error { + return c.addDeltaVisibleBatchesLocked(delta, nil) +} + +func (c *TransactionStatusCache) addDeltaVisibleBatchesLocked(delta transactionStatusDelta, batches map[solana.Hash]*transactionStatusIndexBatch) error { c.invalidateValidationLocked() for blockhash, deltaGroup := range delta { if group := c.visible[blockhash]; group != nil && group.keyIndex != deltaGroup.keyIndex { @@ -864,12 +871,16 @@ func (c *TransactionStatusCache) addDeltaVisibleLocked(delta transactionStatusDe if group == nil { group = &visibleTransactionStatusGroup{ keyIndex: deltaGroup.keyIndex, - keys: make(map[transactionStatusKey]uint16, len(deltaGroup.keys)), } + group.keys.init(len(deltaGroup.keys)) c.visible[blockhash] = group } - for key := range deltaGroup.keys { - group.keys[key]++ + if batch := batches[blockhash]; batch != nil { + group.keys.addBatch(batch) + } else { + for key := range deltaGroup.keys { + group.keys.add(key) + } } } return nil @@ -883,13 +894,9 @@ func (c *TransactionStatusCache) removeDeltaVisibleLocked(delta transactionStatu continue } for key := range deltaGroup.keys { - if group.keys[key] <= 1 { - delete(group.keys, key) - } else { - group.keys[key]-- - } + group.keys.remove(key) } - if len(group.keys) == 0 { + if group.keys.empty() { delete(c.visible, blockhash) } } @@ -989,14 +996,15 @@ func (c *TransactionStatusCache) expireVisibleLocked(expired, retained []*transa if len(g.survivors) == 0 { delete(c.visible, hash) } else if g.retainedKeys < g.expiredKeys { - rebuilt := &visibleTransactionStatusGroup{keyIndex: g.survivors[0].keyIndex, keys: make(map[transactionStatusKey]uint16)} + rebuilt := &visibleTransactionStatusGroup{keyIndex: g.survivors[0].keyIndex} + rebuilt.keys.init(g.retainedKeys) for _, delta := range g.survivors { for key := range delta.keys { - rebuilt.keys[key]++ + rebuilt.keys.add(key) } } c.visible[hash] = rebuilt - if len(rebuilt.keys) == 0 { + if rebuilt.keys.empty() { delete(c.visible, hash) } } diff --git a/pkg/replay/transaction_status_index.go b/pkg/replay/transaction_status_index.go new file mode 100644 index 000000000..a7c044b0e --- /dev/null +++ b/pkg/replay/transaction_status_index.go @@ -0,0 +1,147 @@ +package replay + +// Partition only the mutable lookup index, never the immutable bank deltas or +// checkpoint format. Message-hash bytes select a partition; even adversarially +// concentrated keys retain exactly the same membership/reference-count rules. +// Grouping prepared updates by partition keeps a smaller working set hot while +// publishing. All access is still protected by TransactionStatusCache.mu; this +// introduces neither background publication nor additional mutation workers. +// Recovery guarantee: this is a derived in-memory index. Snapshots still store +// the same immutable per-bank keys; restore rebuilds the counts from those keys. +// No checkpoint coverage, duplicate-check, vote persistence, or unwind rule is +// relaxed, and no prepared batch becomes authoritative before commit succeeds. +const transactionStatusIndexPartitions = 64 + +type transactionStatusIndex []map[transactionStatusKey]uint16 + +// Keep small groups in one map. The chosen layout remains fixed for the group +// lifetime: growing an existing group never forces a full-index copy on replay. +func (index *transactionStatusIndex) init(expected int) { + if len(*index) != 0 { + return + } + partitions := 1 + if expected >= 1024 { + partitions = transactionStatusIndexPartitions + } + *index = make(transactionStatusIndex, partitions) +} + +func statusIndexPartition(key transactionStatusKey) int { + return int(key[0]) & (transactionStatusIndexPartitions - 1) +} + +func (index *transactionStatusIndex) count(key transactionStatusKey) uint16 { + if len(*index) == 0 { + return 0 + } + return (*index)[int(key[0])&(len(*index)-1)][key] +} + +func (index *transactionStatusIndex) add(key transactionStatusKey) { + index.init(1) + partition := int(key[0]) & (len(*index) - 1) + if (*index)[partition] == nil { + (*index)[partition] = make(map[transactionStatusKey]uint16) + } + (*index)[partition][key]++ +} + +func (index *transactionStatusIndex) remove(key transactionStatusKey) { + if len(*index) == 0 { + return + } + number := int(key[0]) & (len(*index) - 1) + partition := (*index)[number] + if partition[key] <= 1 { + delete(partition, key) + } else { + partition[key]-- + } + if len(partition) == 0 { + (*index)[number] = nil + } +} + +func (index *transactionStatusIndex) empty() bool { + for _, partition := range *index { + if len(partition) != 0 { + return false + } + } + return true +} + +// A batch is private preparation scratch, not retained in a bank node or +// serialized. Build it from the deduplicated immutable delta so collisions in +// the stored 20-byte key still contribute only once per bank, as before. +type transactionStatusIndexBatch struct { + keys []transactionStatusKey + ends [transactionStatusIndexPartitions]int +} + +func prepareStatusIndexBatch(group *transactionStatusGroup) *transactionStatusIndexBatch { + batch := &transactionStatusIndexBatch{keys: make([]transactionStatusKey, 0, len(group.keys))} + for key := range group.keys { + batch.append(key) + } + batch.partition() + return batch +} + +func (batch *transactionStatusIndexBatch) append(key transactionStatusKey) { + batch.keys = append(batch.keys, key) + batch.ends[statusIndexPartition(key)]++ +} + +// Counting partition in place: each swap fills one destination position. This +// avoids a second key array and repeated iteration over the immutable key map. +func (batch *transactionStatusIndexBatch) partition() { + var positions [transactionStatusIndexPartitions]int + for i := 1; i < len(batch.ends); i++ { + batch.ends[i] += batch.ends[i-1] + positions[i] = batch.ends[i-1] + } + for bucket, end := range batch.ends { + for positions[bucket] < end { + at := positions[bucket] + key := batch.keys[at] + destination := statusIndexPartition(key) + if destination == bucket { + positions[bucket]++ + continue + } + to := positions[destination] + batch.keys[at], batch.keys[to] = batch.keys[to], key + positions[destination]++ + } + } +} + +func (index *transactionStatusIndex) addBatch(batch *transactionStatusIndexBatch) { + index.init(len(batch.keys)) + if len(*index) == 1 { + if (*index)[0] == nil && len(batch.keys) > 0 { + (*index)[0] = make(map[transactionStatusKey]uint16, len(batch.keys)) + } + for _, key := range batch.keys { + (*index)[0][key]++ + } + return + } + start := 0 + for i, end := range batch.ends { + if start == end { + continue + } + partition := (*index)[i] + if partition == nil { + partition = make(map[transactionStatusKey]uint16, end-start) + (*index)[i] = partition + } + for _, key := range batch.keys[start:end] { + partition[key]++ + } + start = end + } +} diff --git a/pkg/replay/transaction_status_index_benchmark_test.go b/pkg/replay/transaction_status_index_benchmark_test.go new file mode 100644 index 000000000..f8f11358b --- /dev/null +++ b/pkg/replay/transaction_status_index_benchmark_test.go @@ -0,0 +1,62 @@ +package replay + +import ( + "fmt" + "sort" + "testing" + "time" +) + +// Measure the publication tail separately from preparation and ancestor checks. +// Use the identical benchmark file on both source revisions. Includes binding +// checks and index publication; excludes fixture creation, preparation and unwind. +// A warmed existing blockhash index is kept across iterations. This is an +// isolated component benchmark, not a prediction of live voting percentiles. +func BenchmarkStatusMapCriticalTail(b *testing.B) { + for _, existing := range []bool{false, true} { + b.Run(fmt.Sprintf("existing_%t", existing), func(b *testing.B) { + txs := benchmarkUniqueTransactions(67520) + parent := statusCacheTestBlock(10, txs[:33760]...) + if !existing { + parent.Transactions = nil + } + block := statusCacheTestBlock(11, txs[33760:]...) + plan, err := planBlockTransactionExecution(block) + if err != nil { + b.Fatal(err) + } + cache := NewTransactionStatusCache() + if err := cache.CommitBlock(parent); err != nil { + b.Fatal(err) + } + durations := make([]int64, 0, b.N) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + b.StopTimer() + prepared := cache.prepareTransactionStatusDelta(plan.messageIdentities) + receipt, err := cache.validateBlockForPublication(block, plan) + if err != nil { + b.Fatal(err) + } + b.StartTimer() + start := time.Now() + err = cache.commitBlockWithValidation(block, plan, prepared, receipt) + duration := time.Since(start).Nanoseconds() + b.StopTimer() + if err != nil { + b.Fatal(err) + } + durations = append(durations, duration) + if err := cache.Unwind(11); err != nil { + b.Fatal(err) + } + b.StartTimer() + } + b.StopTimer() + sort.Slice(durations, func(i, j int) bool { return durations[i] < durations[j] }) + b.ReportMetric(float64(durations[(len(durations)-1)/2]), "p50-ns") + b.ReportMetric(float64(durations[(99*len(durations)+99)/100-1]), "p99-ns") + }) + } +} diff --git a/pkg/replay/transaction_status_index_test.go b/pkg/replay/transaction_status_index_test.go new file mode 100644 index 000000000..7fba9c88d --- /dev/null +++ b/pkg/replay/transaction_status_index_test.go @@ -0,0 +1,104 @@ +package replay + +import ( + "math/rand" + "testing" +) + +func TestTransactionStatusPartitionedIndexReferenceCounts(t *testing.T) { + for _, concentrated := range []bool{false, true} { + rng := rand.New(rand.NewSource(71)) + var index transactionStatusIndex + index.init(33760) + reference := make(map[transactionStatusKey]uint16) + keys := make([]transactionStatusKey, 1024) + for i := range keys { + rng.Read(keys[i][:]) + if concentrated { + keys[i][0] = 255 + } + } + var banks []*transactionStatusGroup + for step := 0; step < 600; step++ { + if len(banks) > 0 && (step%3 == 0 || len(banks) == 300) { + group := banks[0] + banks = banks[1:] + for k := range group.keys { + index.remove(k) + reference[k]-- + if reference[k] == 0 { + delete(reference, k) + } + } + } else { + group := &transactionStatusGroup{keys: make(map[transactionStatusKey]struct{})} + for i := 0; i < 100; i++ { + group.keys[keys[rng.Intn(len(keys))]] = struct{}{} + } + index.addBatch(prepareStatusIndexBatch(group)) + banks = append(banks, group) + for k := range group.keys { + reference[k]++ + } + } + for _, k := range keys { + if got := index.count(k); got != reference[k] { + t.Fatalf("concentrated=%t step=%d count=%d want=%d", concentrated, step, got, reference[k]) + } + } + } + for _, group := range banks { + for k := range group.keys { + index.remove(k) + } + } + if !index.empty() { + t.Fatal("index retained keys after all bank references removed") + } + } +} + +func TestTransactionStatusIndexBatchPartitionEdges(t *testing.T) { + for _, first := range []byte{0, 63, 64, 127, 255} { + group := &transactionStatusGroup{keys: map[transactionStatusKey]struct{}{{first, 1}: {}, {first, 2}: {}}} + batch := prepareStatusIndexBatch(group) + var index transactionStatusIndex + index.addBatch(batch) + index.addBatch(batch) + for k := range group.keys { + if index.count(k) != 2 { + t.Fatal("lost overlapping bank reference") + } + index.remove(k) + if index.count(k) != 1 { + t.Fatal("removed key still required by another bank") + } + index.remove(k) + } + if !index.empty() { + t.Fatal("partition did not empty") + } + index.addBatch(prepareStatusIndexBatch(&transactionStatusGroup{})) + if !index.empty() { + t.Fatal("empty batch introduced keys") + } + } +} + +func TestTransactionStatusIndexSmallGroupDoesNotRepartition(t *testing.T) { + var index transactionStatusIndex + index.add(transactionStatusKey{0, 1}) + group := &transactionStatusGroup{keys: make(map[transactionStatusKey]struct{})} + for i := 0; i < 4096; i++ { + group.keys[transactionStatusKey{byte(i), byte(i >> 8)}] = struct{}{} + } + index.addBatch(prepareStatusIndexBatch(group)) + if len(index) != 1 { + t.Fatal("existing small group copied into a new layout during publication") + } + for k := range group.keys { + if index.count(k) == 0 { + t.Fatal("batch lost a key when using compact layout") + } + } +} diff --git a/pkg/replay/transaction_status_publication.go b/pkg/replay/transaction_status_publication.go index e821071ad..5203fa17c 100644 --- a/pkg/replay/transaction_status_publication.go +++ b/pkg/replay/transaction_status_publication.go @@ -11,8 +11,9 @@ import ( // Preparation owns private, immutable maps. It never publishes a status or // authorizes a bank: commit still checks coverage, lineage, and duplicates. type preparedTransactionStatusDelta struct { - identities *b.PreparedTransactionMessageIdentities - delta transactionStatusDelta + identities *b.PreparedTransactionMessageIdentities + delta transactionStatusDelta + indexBatches map[solana.Hash]*transactionStatusIndexBatch } type transactionStatusPreparation struct { @@ -56,14 +57,33 @@ func countTransactionStatusGroups(identities *b.PreparedTransactionMessageIdenti } func buildTransactionStatusDelta(identities *b.PreparedTransactionMessageIdentities, counts map[solana.Hash]int, indexes map[solana.Hash]uint8) transactionStatusDelta { + return buildTransactionStatusDeltaWithBatches(identities, counts, indexes, nil) +} + +func buildTransactionStatusDeltaWithBatches(identities *b.PreparedTransactionMessageIdentities, counts map[solana.Hash]int, indexes map[solana.Hash]uint8, batches map[solana.Hash]*transactionStatusIndexBatch) transactionStatusDelta { delta := make(transactionStatusDelta, len(counts)) for blockhash, count := range counts { delta[blockhash] = &transactionStatusGroup{keyIndex: indexes[blockhash], keys: make(map[transactionStatusKey]struct{}, count)} + if batches != nil && count >= 1024 { + batches[blockhash] = &transactionStatusIndexBatch{keys: make([]transactionStatusKey, 0, count)} + } } + var previous solana.Hash + var group *transactionStatusGroup + var batch *transactionStatusIndexBatch for i := 0; i < identities.Len(); i++ { identity := identities.Identity(i) - group := delta[identity.RecentBlockhash] - group.keys[sliceTransactionStatusKey(identity.MessageHash, group.keyIndex)] = struct{}{} + if group == nil || identity.RecentBlockhash != previous { + previous = identity.RecentBlockhash + group = delta[previous] + batch = batches[previous] + } + key := sliceTransactionStatusKey(identity.MessageHash, group.keyIndex) + before := len(group.keys) + group.keys[key] = struct{}{} + if batch != nil && len(group.keys) != before { + batch.append(key) + } } return delta } @@ -79,5 +99,10 @@ func (c *TransactionStatusCache) prepareTransactionStatusDelta(identities *b.Pre } } c.mu.RUnlock() - return &preparedTransactionStatusDelta{identities: identities, delta: buildTransactionStatusDelta(identities, counts, indexes)} + batches := make(map[solana.Hash]*transactionStatusIndexBatch, len(counts)) + delta := buildTransactionStatusDeltaWithBatches(identities, counts, indexes, batches) + for _, batch := range batches { + batch.partition() + } + return &preparedTransactionStatusDelta{identities: identities, delta: delta, indexBatches: batches} } diff --git a/pkg/replay/transaction_status_publication_benchmark_test.go b/pkg/replay/transaction_status_publication_benchmark_test.go index 2fc7d0f45..b6b73685c 100644 --- a/pkg/replay/transaction_status_publication_benchmark_test.go +++ b/pkg/replay/transaction_status_publication_benchmark_test.go @@ -61,8 +61,9 @@ func (c *TransactionStatusCache) legacyCommitStatusForBenchmark(block *b.Block, return nil } -// Frozen production commit algorithm before publication optimization. This is -// an independent baseline, including its original visible-index allocation. +// Historical unprepared delta construction, using the current visible index. +// For before/after index comparisons run the same benchmark at both commits; +// this helper is not a frozen baseline for the mutable index implementation. func (c *TransactionStatusCache) legacyAddStatusForBenchmark(delta transactionStatusDelta) error { for blockhash, deltaGroup := range delta { if group := c.visible[blockhash]; group != nil && group.keyIndex != deltaGroup.keyIndex { @@ -75,12 +76,11 @@ func (c *TransactionStatusCache) legacyAddStatusForBenchmark(delta transactionSt if group == nil { group = &visibleTransactionStatusGroup{ keyIndex: deltaGroup.keyIndex, - keys: make(map[transactionStatusKey]uint16), } c.visible[blockhash] = group } for key := range deltaGroup.keys { - group.keys[key]++ + group.keys.add(key) } } return nil From b8df19d61ae02670f364a655242bbc4b261a6960 Mon Sep 17 00:00:00 2001 From: 7layermagik <7layermagik@users.noreply.github.com> Date: Tue, 15 Sep 2026 19:32:17 -0500 Subject: [PATCH 13/16] review: defer status-map partitioning and archive investigation artifacts --- .gitattributes | 7 - .../pr-split-2026-09-15/status/README.md | 7 - .../status/status-tests.log | 1 - .../pr-split-2026-09-15/status/status-vet.log | 0 .../baseline-failure-comparison.json | 26 ---- .../2026-09-14/checkpoint-live.json | 118 -------------- .../checkpoint-native-benchmark.log | 15 -- .../2026-09-14/zen5-benchmark.log | 24 --- .../status-cache/2026-09-14/zen5-race.log | 1 - .../status-cache/2026-09-14/zen5-vet.log | 0 .../2026-09-15/local-race-final.log | 3 - .../2026-09-15/native-benchmark-summary.json | 82 ---------- .../2026-09-15/native-benchmark.log | 85 ---------- .../2026-09-15/native-build.log | 0 .../2026-09-15/native-final-run.log | 12 -- .../native-overlap-final-summary.json | 104 ------------- .../2026-09-15/native-overlap-final.log | 95 ----------- .../2026-09-15/native-overlap-summary.json | 104 ------------- .../2026-09-15/native-overlap.log | 102 ------------ .../2026-09-15/native-race.log | 3 - .../2026-09-15/native-vet.log | 0 .../2026-09-15/native-window.json | 4 - .../2026-09-15/post-test-health.json | 1 - .../2026-09-15/recheck-abba.log | 34 ---- .../2026-09-15/tested-source.json | 16 -- docs/status-checkpoint-capture.md | 5 +- docs/status-checkpoint-expiry-evidence.md | 19 +++ docs/transaction-status-expiry.md | 19 +-- docs/transaction-status-publication.md | 72 +-------- pkg/replay/transaction_status_cache.go | 38 ++--- pkg/replay/transaction_status_index.go | 147 ------------------ ...transaction_status_index_benchmark_test.go | 62 -------- pkg/replay/transaction_status_index_test.go | 104 ------------- pkg/replay/transaction_status_publication.go | 35 +---- ...ction_status_publication_benchmark_test.go | 8 +- 35 files changed, 51 insertions(+), 1302 deletions(-) delete mode 100644 .gitattributes delete mode 100644 docs/results/pr-split-2026-09-15/status/README.md delete mode 100644 docs/results/pr-split-2026-09-15/status/status-tests.log delete mode 100644 docs/results/pr-split-2026-09-15/status/status-vet.log delete mode 100644 docs/results/status-cache/2026-09-14/baseline-failure-comparison.json delete mode 100644 docs/results/status-cache/2026-09-14/checkpoint-live.json delete mode 100644 docs/results/status-cache/2026-09-14/checkpoint-native-benchmark.log delete mode 100644 docs/results/status-cache/2026-09-14/zen5-benchmark.log delete mode 100644 docs/results/status-cache/2026-09-14/zen5-race.log delete mode 100644 docs/results/status-cache/2026-09-14/zen5-vet.log delete mode 100644 docs/results/status-publication/2026-09-15/local-race-final.log delete mode 100644 docs/results/status-publication/2026-09-15/native-benchmark-summary.json delete mode 100644 docs/results/status-publication/2026-09-15/native-benchmark.log delete mode 100644 docs/results/status-publication/2026-09-15/native-build.log delete mode 100644 docs/results/status-publication/2026-09-15/native-final-run.log delete mode 100644 docs/results/status-publication/2026-09-15/native-overlap-final-summary.json delete mode 100644 docs/results/status-publication/2026-09-15/native-overlap-final.log delete mode 100644 docs/results/status-publication/2026-09-15/native-overlap-summary.json delete mode 100644 docs/results/status-publication/2026-09-15/native-overlap.log delete mode 100644 docs/results/status-publication/2026-09-15/native-race.log delete mode 100644 docs/results/status-publication/2026-09-15/native-vet.log delete mode 100644 docs/results/status-publication/2026-09-15/native-window.json delete mode 100644 docs/results/status-publication/2026-09-15/post-test-health.json delete mode 100644 docs/results/status-publication/2026-09-15/recheck-abba.log delete mode 100644 docs/results/status-publication/2026-09-15/tested-source.json create mode 100644 docs/status-checkpoint-expiry-evidence.md delete mode 100644 pkg/replay/transaction_status_index.go delete mode 100644 pkg/replay/transaction_status_index_benchmark_test.go delete mode 100644 pkg/replay/transaction_status_index_test.go diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index d64360d9a..000000000 --- a/.gitattributes +++ /dev/null @@ -1,7 +0,0 @@ -# Keep raw benchmark evidence available without overwhelming the review. -# Retain raw Go CPU padding and test-framework space/tab indentation. -docs/results/**/*.json linguist-generated=true -docs/results/**/*.jsonl linguist-generated=true -docs/results/**/*.txt linguist-generated=true whitespace=-blank-at-eol -docs/results/**/*.log linguist-generated=true -whitespace -docs/results/**/*.tar.gz linguist-generated=true diff --git a/docs/results/pr-split-2026-09-15/status/README.md b/docs/results/pr-split-2026-09-15/status/README.md deleted file mode 100644 index 134adf562..000000000 --- a/docs/results/pr-split-2026-09-15/status/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Review branch validation, September 15 - -This PR is split from #279 plus the later working-tree improvements. The tested code commit before this documentation commit was `a111ba892fb88ea95768930f56dd2f9a5091e6ce`. Tests ran locally on Apple M4 Pro, Go1.26.4, GOMAXPROCS3 and package parallelism2. Logs beside this file are the fresh split-branch checks, not native measurements. Existing Zen5 benchmark documents retain their original baselines and scope. No validator restart or deployment occurred during this reorganization. - -Four independent branches start at current alpenglow-dev33dde405. Voting is based on the certificate-processing PR; leader packing is based on the streaming-preparation PR. Runtime changes and status-cache changes are independent. Shared CLI/configuration additions need an ordinary three-file merge reconciliation when combining leader packing and voting. A separate audit checkout reconciled these additions and matched the preserved full implementation exactly across Go sources, module files, TOML configuration and CI. - -The branch-specific race suites and vet passed. The combined audit has a separately documented pre-existing intermittent peer reconnect timeout; this is not reported as an entirely green combined race run. diff --git a/docs/results/pr-split-2026-09-15/status/status-tests.log b/docs/results/pr-split-2026-09-15/status/status-tests.log deleted file mode 100644 index b6b40ef23..000000000 --- a/docs/results/pr-split-2026-09-15/status/status-tests.log +++ /dev/null @@ -1 +0,0 @@ -ok github.com/Overclock-Validator/mithril/pkg/replay 4.657s diff --git a/docs/results/pr-split-2026-09-15/status/status-vet.log b/docs/results/pr-split-2026-09-15/status/status-vet.log deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/results/status-cache/2026-09-14/baseline-failure-comparison.json b/docs/results/status-cache/2026-09-14/baseline-failure-comparison.json deleted file mode 100644 index 5353cc6eb..000000000 --- a/docs/results/status-cache/2026-09-14/baseline-failure-comparison.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "failed_tests": [ - "TestExecute_Tx_BpfLoader_Write_Success", - "TestExecute_Tx_BpfLoader_Write_Offset_Too_Large_Failure", - "TestExecute_Tx_BpfLoader_Write_Buffer_Authority_Didnt_Sign_Failure", - "TestExecute_Tx_BpfLoader_Write_Incorrect_Authority_Failure", - "TestExecute_Tx_BpfLoader_SetAuthority_Not_Enough_Instr_Accts_Failure", - "TestExecute_Tx_BpfLoader_SetAuthorityChecked_Not_Enough_Instr_Accts_Failure", - "TestExecute_Tx_BpfLoader_SetAuthorityChecked_Buffer_Success", - "TestExecute_Tx_BpfLoader_SetAuthorityChecked_ProgramData_Success", - "TestExecute_Tx_BpfLoader_SetAuthorityChecked_Buffer_Immutable_Failure", - "TestExecute_Tx_BpfLoader_SetAuthorityChecked_Buffer_Wrong_Upgrade_Authority_Failure", - "TestExecute_Tx_BpfLoader_SetAuthorityChecked_Buffer_Authority_Didnt_Sign_Failure", - "TestExecute_Tx_BpfLoader_SetAuthorityChecked_Buffer_New_Authority_Didnt_Sign_Failure", - "TestExecute_Tx_BpfLoader_SetAuthorityChecked_Buffer_Uninitialized_Account_Failure", - "TestExecute_Tx_BpfLoader_SetAuthorityChecked_ProgramData_Immutable_Failure", - "TestExecute_Tx_BpfLoader_SetAuthorityChecked_ProgramData_Authority_Didnt_Sign_Failure", - "TestExecute_Tx_BpfLoader_SetAuthorityChecked_ProgramData_New_Authority_Didnt_Sign_Failure", - "TestExecute_Tx_BpfLoader_SetAuthorityChecked_ProgramData_Wrong_Authority_Failure", - "TestExecute_Tx_BpfLoader_Close_Buffer_Not_Enough_Accounts", - "TestExecute_Tx_BpfLoader_Close_ProgramData_Success" - ], - "same_failed_test_names": true, - "same_panic_function": "UpgradeableLoaderClose", - "same_failed_test_names_on_native_base": true -} diff --git a/docs/results/status-cache/2026-09-14/checkpoint-live.json b/docs/results/status-cache/2026-09-14/checkpoint-live.json deleted file mode 100644 index 0072e9189..000000000 --- a/docs/results/status-cache/2026-09-14/checkpoint-live.json +++ /dev/null @@ -1,118 +0,0 @@ -{ - "utc": "2026-09-14T03:34:45Z", - "count": 10, - "log_bytes": 1607896, - "rows": [ - { - "slots": 128, - "through": 3427284, - "worker_ms": 331.0, - "capture_us": 34.174, - "encode_ms": 236.108854, - "bytes": 31488298, - "line": "(+ 28s) async fold: committed 128 slots through 3427284 in 331ms checkpoint_capture=34.174\u00b5s checkpoint_encode=236.108854ms checkpoint_bytes=31488298" - }, - { - "slots": 128, - "through": 3427428, - "worker_ms": 264.0, - "capture_us": 30.366, - "encode_ms": 182.148884, - "bytes": 25219543, - "line": "(+ 59s) async fold: committed 128 slots through 3427428 in 264ms checkpoint_capture=30.366\u00b5s checkpoint_encode=182.148884ms checkpoint_bytes=25219543" - }, - { - "slots": 128, - "through": 3427569, - "worker_ms": 318.0, - "capture_us": 30.417, - "encode_ms": 225.601155, - "bytes": 29886172, - "line": "(+ 1m29s) async fold: committed 128 slots through 3427569 in 318ms checkpoint_capture=30.417\u00b5s checkpoint_encode=225.601155ms checkpoint_bytes=29886172" - }, - { - "slots": 128, - "through": 3427701, - "worker_ms": 258.0, - "capture_us": 29.495, - "encode_ms": 179.07318000000004, - "bytes": 24530250, - "line": "(+ 1m58s) async fold: committed 128 slots through 3427701 in 258ms checkpoint_capture=29.495\u00b5s checkpoint_encode=179.07318ms checkpoint_bytes=24530250" - }, - { - "slots": 128, - "through": 3427841, - "worker_ms": 305.0, - "capture_us": 30.888, - "encode_ms": 216.143473, - "bytes": 29469092, - "line": "(+ 2m28s) async fold: committed 128 slots through 3427841 in 305ms checkpoint_capture=30.888\u00b5s checkpoint_encode=216.143473ms checkpoint_bytes=29469092" - }, - { - "slots": 128, - "through": 3427989, - "worker_ms": 286.0, - "capture_us": 28.694, - "encode_ms": 201.186937, - "bytes": 27782921, - "line": "(+ 2m59s) async fold: committed 128 slots through 3427989 in 286ms checkpoint_capture=28.694\u00b5s checkpoint_encode=201.186937ms checkpoint_bytes=27782921" - }, - { - "slots": 128, - "through": 3428133, - "worker_ms": 376.0, - "capture_us": 28.874, - "encode_ms": 278.613922, - "bytes": 35692638, - "line": "(+ 3m29s) async fold: committed 128 slots through 3428133 in 376ms checkpoint_capture=28.874\u00b5s checkpoint_encode=278.613922ms checkpoint_bytes=35692638" - }, - { - "slots": 128, - "through": 3428269, - "worker_ms": 431.0, - "capture_us": 32.671, - "encode_ms": 329.865906, - "bytes": 37615609, - "line": "(+ 3m58s) async fold: committed 128 slots through 3428269 in 431ms checkpoint_capture=32.671\u00b5s checkpoint_encode=329.865906ms checkpoint_bytes=37615609" - }, - { - "slots": 128, - "through": 3428409, - "worker_ms": 403.0, - "capture_us": 31.108, - "encode_ms": 301.569555, - "bytes": 38403988, - "line": "(+ 4m28s) async fold: committed 128 slots through 3428409 in 403ms checkpoint_capture=31.108\u00b5s checkpoint_encode=301.569555ms checkpoint_bytes=38403988" - }, - { - "slots": 128, - "through": 3428549, - "worker_ms": 477.0, - "capture_us": 34.464, - "encode_ms": 366.581817, - "bytes": 43443732, - "line": "(+ 4m58s) async fold: committed 128 slots through 3428549 in 477ms checkpoint_capture=34.464\u00b5s checkpoint_encode=366.581817ms checkpoint_bytes=43443732" - } - ], - "capture_us": { - "min": 28.694, - "median": 30.652500000000003, - "max": 34.464 - }, - "encode_ms": { - "min": 179.07318000000004, - "median": 230.8550045, - "max": 366.581817 - }, - "bytes": { - "min": 24530250, - "median": 30687235.0, - "max": 43443732 - }, - "worker_ms": { - "min": 258.0, - "median": 324.5, - "max": 477.0 - }, - "error_lines": [] -} diff --git a/docs/results/status-cache/2026-09-14/checkpoint-native-benchmark.log b/docs/results/status-cache/2026-09-14/checkpoint-native-benchmark.log deleted file mode 100644 index 33f4c2cd6..000000000 --- a/docs/results/status-cache/2026-09-14/checkpoint-native-benchmark.log +++ /dev/null @@ -1,15 +0,0 @@ -goos: linux -goarch: amd64 -pkg: github.com/Overclock-Validator/mithril/pkg/replay -cpu: AMD Ryzen 7 9700X 8-Core Processor -BenchmarkTransactionStatusCheckpointCapture/SynchronousBaseline 1 206554597 ns/op 99120032 B/op 2731 allocs/op -BenchmarkTransactionStatusCheckpointCapture/SynchronousBaseline 1 207352232 ns/op 99120072 B/op 2732 allocs/op -BenchmarkTransactionStatusCheckpointCapture/SynchronousBaseline 1 202442401 ns/op 99120048 B/op 2731 allocs/op -BenchmarkTransactionStatusCheckpointCapture/CaptureOnReplay 47412 5936 ns/op 35168 B/op 11 allocs/op -BenchmarkTransactionStatusCheckpointCapture/CaptureOnReplay 39506 6057 ns/op 35168 B/op 11 allocs/op -BenchmarkTransactionStatusCheckpointCapture/CaptureOnReplay 37850 5922 ns/op 35168 B/op 11 allocs/op -BenchmarkTransactionStatusCheckpointCapture/EncodeOnWorker 2 202522908 ns/op 99108084 B/op 2723 allocs/op -BenchmarkTransactionStatusCheckpointCapture/EncodeOnWorker 2 201841085 ns/op 99108076 B/op 2723 allocs/op -BenchmarkTransactionStatusCheckpointCapture/EncodeOnWorker 1 200834149 ns/op 99108104 B/op 2724 allocs/op -PASS -ok github.com/Overclock-Validator/mithril/pkg/replay 3.133s diff --git a/docs/results/status-cache/2026-09-14/zen5-benchmark.log b/docs/results/status-cache/2026-09-14/zen5-benchmark.log deleted file mode 100644 index 0229c19af..000000000 --- a/docs/results/status-cache/2026-09-14/zen5-benchmark.log +++ /dev/null @@ -1,24 +0,0 @@ -goos: linux -goarch: amd64 -pkg: github.com/Overclock-Validator/mithril/pkg/replay -cpu: AMD Ryzen 7 9700X 8-Core Processor -BenchmarkTransactionStatusBatchExpiry/four-bank-groups/legacy=true-2 1 308302685 ns/op -BenchmarkTransactionStatusBatchExpiry/four-bank-groups/legacy=true-2 1 311104834 ns/op -BenchmarkTransactionStatusBatchExpiry/four-bank-groups/legacy=true-2 1 306059722 ns/op -BenchmarkTransactionStatusBatchExpiry/four-bank-groups/legacy=false-2 1 56685 ns/op -BenchmarkTransactionStatusBatchExpiry/four-bank-groups/legacy=false-2 1 49743 ns/op -BenchmarkTransactionStatusBatchExpiry/four-bank-groups/legacy=false-2 1 48581 ns/op -BenchmarkTransactionStatusBatchExpiry/one-expired-group/legacy=true-2 1 700015562 ns/op -BenchmarkTransactionStatusBatchExpiry/one-expired-group/legacy=true-2 1 718445604 ns/op -BenchmarkTransactionStatusBatchExpiry/one-expired-group/legacy=true-2 1 716785553 ns/op -BenchmarkTransactionStatusBatchExpiry/one-expired-group/legacy=false-2 1 30758 ns/op -BenchmarkTransactionStatusBatchExpiry/one-expired-group/legacy=false-2 1 26119 ns/op -BenchmarkTransactionStatusBatchExpiry/one-expired-group/legacy=false-2 1 24436 ns/op -BenchmarkTransactionStatusBatchExpiry/crossing-group/legacy=true-2 1 734956959 ns/op -BenchmarkTransactionStatusBatchExpiry/crossing-group/legacy=true-2 1 722327226 ns/op -BenchmarkTransactionStatusBatchExpiry/crossing-group/legacy=true-2 1 717188987 ns/op -BenchmarkTransactionStatusBatchExpiry/crossing-group/legacy=false-2 1 2045462 ns/op -BenchmarkTransactionStatusBatchExpiry/crossing-group/legacy=false-2 1 1845859 ns/op -BenchmarkTransactionStatusBatchExpiry/crossing-group/legacy=false-2 1 1983167 ns/op -PASS -ok github.com/Overclock-Validator/mithril/pkg/replay 22.725s diff --git a/docs/results/status-cache/2026-09-14/zen5-race.log b/docs/results/status-cache/2026-09-14/zen5-race.log deleted file mode 100644 index 100c86a11..000000000 --- a/docs/results/status-cache/2026-09-14/zen5-race.log +++ /dev/null @@ -1 +0,0 @@ -ok github.com/Overclock-Validator/mithril/pkg/replay 2.189s diff --git a/docs/results/status-cache/2026-09-14/zen5-vet.log b/docs/results/status-cache/2026-09-14/zen5-vet.log deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/results/status-publication/2026-09-15/local-race-final.log b/docs/results/status-publication/2026-09-15/local-race-final.log deleted file mode 100644 index 17b7d4d2a..000000000 --- a/docs/results/status-publication/2026-09-15/local-race-final.log +++ /dev/null @@ -1,3 +0,0 @@ -ok github.com/Overclock-Validator/mithril/pkg/replay 4.618s -ok github.com/Overclock-Validator/mithril/pkg/block 1.774s -? github.com/Overclock-Validator/mithril/pkg/metrics [no test files] diff --git a/docs/results/status-publication/2026-09-15/native-benchmark-summary.json b/docs/results/status-publication/2026-09-15/native-benchmark-summary.json deleted file mode 100644 index 3936921d1..000000000 --- a/docs/results/status-publication/2026-09-15/native-benchmark-summary.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "BenchmarkTransactionStatusPublication/groups_1/existing_false/legacy-2": { - "ns/op": 4990269.0, - "B/op": 6302003.0, - "allocs/op": 555.0 - }, - "BenchmarkTransactionStatusPublication/groups_1/existing_false/sized-2": { - "ns/op": 3331600.0, - "B/op": 3151475.0, - "allocs/op": 265.0 - }, - "BenchmarkTransactionStatusPublication/groups_1/existing_false/prepared_total-2": { - "ns/op": 3393252.0, - "B/op": 3151718.0, - "allocs/op": 269.0 - }, - "BenchmarkTransactionStatusPublication/groups_1/existing_false/prepared_commit-2": { - "ns/op": 1587430.0, - "B/op": 1575587.0, - "allocs/op": 132.0 - }, - "BenchmarkTransactionStatusPublication/groups_1/existing_true/legacy-2": { - "ns/op": 4991180.0, - "B/op": 3466193.0, - "allocs/op": 304.0 - }, - "BenchmarkTransactionStatusPublication/groups_1/existing_true/sized-2": { - "ns/op": 4657032.0, - "B/op": 1891049.0, - "allocs/op": 159.0 - }, - "BenchmarkTransactionStatusPublication/groups_1/existing_true/prepared_total-2": { - "ns/op": 4434153.0, - "B/op": 1891281.0, - "allocs/op": 163.0 - }, - "BenchmarkTransactionStatusPublication/groups_1/existing_true/prepared_commit-2": { - "ns/op": 2756954.0, - "B/op": 315161.0, - "allocs/op": 26.0 - }, - "BenchmarkTransactionStatusPublication/groups_4/existing_false/legacy-2": { - "ns/op": 4624988.0, - "B/op": 6301427.0, - "allocs/op": 659.0 - }, - "BenchmarkTransactionStatusPublication/groups_4/existing_false/sized-2": { - "ns/op": 3743889.0, - "B/op": 3151859.0, - "allocs/op": 283.0 - }, - "BenchmarkTransactionStatusPublication/groups_4/existing_false/prepared_total-2": { - "ns/op": 5915974.0, - "B/op": 3152091.0, - "allocs/op": 287.0 - }, - "BenchmarkTransactionStatusPublication/groups_4/existing_false/prepared_commit-2": { - "ns/op": 2205044.0, - "B/op": 1575779.0, - "allocs/op": 141.0 - }, - "BenchmarkTransactionStatusPublication/groups_4/existing_true/legacy-2": { - "ns/op": 5224367.0, - "B/op": 3465532.0, - "allocs/op": 357.0 - }, - "BenchmarkTransactionStatusPublication/groups_4/existing_true/sized-2": { - "ns/op": 4644107.0, - "B/op": 1891228.0, - "allocs/op": 169.0 - }, - "BenchmarkTransactionStatusPublication/groups_4/existing_true/prepared_total-2": { - "ns/op": 4949162.0, - "B/op": 1891460.0, - "allocs/op": 173.0 - }, - "BenchmarkTransactionStatusPublication/groups_4/existing_true/prepared_commit-2": { - "ns/op": 2858236.0, - "B/op": 315148.0, - "allocs/op": 27.0 - } -} \ No newline at end of file diff --git a/docs/results/status-publication/2026-09-15/native-benchmark.log b/docs/results/status-publication/2026-09-15/native-benchmark.log deleted file mode 100644 index ea4b94603..000000000 --- a/docs/results/status-publication/2026-09-15/native-benchmark.log +++ /dev/null @@ -1,85 +0,0 @@ -goos: linux -goarch: amd64 -pkg: github.com/Overclock-Validator/mithril/pkg/replay -cpu: AMD Ryzen 7 9700X 8-Core Processor -BenchmarkTransactionStatusPublication/groups_1/existing_false/legacy-2 10 4971792 ns/op 6302003 B/op 555 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_false/legacy-2 10 5003752 ns/op 6302003 B/op 555 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_false/legacy-2 10 4990269 ns/op 6302003 B/op 555 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_false/legacy-2 10 5067117 ns/op 6302003 B/op 555 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_false/legacy-2 10 4855132 ns/op 6302003 B/op 555 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_false/sized-2 10 3869972 ns/op 3151475 B/op 265 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_false/sized-2 10 3593875 ns/op 3151477 B/op 265 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_false/sized-2 10 3116164 ns/op 3151475 B/op 265 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_false/sized-2 10 3292134 ns/op 3151475 B/op 265 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_false/sized-2 10 3331600 ns/op 3151475 B/op 265 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_false/prepared_total-2 10 3459366 ns/op 3151780 B/op 269 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_false/prepared_total-2 10 3555785 ns/op 3151707 B/op 269 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_false/prepared_total-2 10 3338994 ns/op 3151718 B/op 269 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_false/prepared_total-2 10 3393252 ns/op 3151755 B/op 269 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_false/prepared_total-2 10 3035989 ns/op 3151707 B/op 269 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_false/prepared_commit-2 10 1433079 ns/op 1575587 B/op 132 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_false/prepared_commit-2 10 1465220 ns/op 1575587 B/op 132 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_false/prepared_commit-2 10 1587430 ns/op 1575587 B/op 132 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_false/prepared_commit-2 10 1668259 ns/op 1575587 B/op 132 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_false/prepared_commit-2 10 1625507 ns/op 1575587 B/op 132 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_true/legacy-2 10 5038118 ns/op 3466193 B/op 304 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_true/legacy-2 10 4926609 ns/op 3466193 B/op 304 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_true/legacy-2 10 4965933 ns/op 3466196 B/op 304 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_true/legacy-2 10 4991180 ns/op 3466193 B/op 304 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_true/legacy-2 10 5174867 ns/op 3466193 B/op 304 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_true/sized-2 10 4592110 ns/op 1891049 B/op 159 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_true/sized-2 10 4689758 ns/op 1891049 B/op 159 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_true/sized-2 10 4579822 ns/op 1891049 B/op 159 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_true/sized-2 10 4893416 ns/op 1891052 B/op 159 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_true/sized-2 10 4657032 ns/op 1891049 B/op 159 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_true/prepared_total-2 10 4617374 ns/op 1891281 B/op 163 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_true/prepared_total-2 10 4434153 ns/op 1891281 B/op 163 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_true/prepared_total-2 10 4425112 ns/op 1891281 B/op 163 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_true/prepared_total-2 10 4362413 ns/op 1891281 B/op 163 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_true/prepared_total-2 10 4649669 ns/op 1891281 B/op 163 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_true/prepared_commit-2 10 2756954 ns/op 315161 B/op 26 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_true/prepared_commit-2 10 2987364 ns/op 315161 B/op 26 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_true/prepared_commit-2 10 2747530 ns/op 315161 B/op 26 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_true/prepared_commit-2 10 3033686 ns/op 315161 B/op 26 allocs/op -BenchmarkTransactionStatusPublication/groups_1/existing_true/prepared_commit-2 10 2735607 ns/op 315161 B/op 26 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_false/legacy-2 10 4829304 ns/op 6301427 B/op 659 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_false/legacy-2 10 4554119 ns/op 6301427 B/op 659 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_false/legacy-2 10 4920607 ns/op 6301427 B/op 659 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_false/legacy-2 10 4624988 ns/op 6301427 B/op 659 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_false/legacy-2 10 4587116 ns/op 6301427 B/op 659 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_false/sized-2 10 3398182 ns/op 3151859 B/op 283 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_false/sized-2 10 3626107 ns/op 3151859 B/op 283 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_false/sized-2 10 3743889 ns/op 3151859 B/op 283 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_false/sized-2 10 4029608 ns/op 3151861 B/op 283 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_false/sized-2 10 4958660 ns/op 3151859 B/op 283 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_false/prepared_total-2 10 5919178 ns/op 3152091 B/op 287 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_false/prepared_total-2 10 5632307 ns/op 3152091 B/op 287 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_false/prepared_total-2 10 5473052 ns/op 3152091 B/op 287 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_false/prepared_total-2 10 5915974 ns/op 3152091 B/op 287 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_false/prepared_total-2 10 6688551 ns/op 3152091 B/op 287 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_false/prepared_commit-2 10 2205247 ns/op 1575779 B/op 141 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_false/prepared_commit-2 10 2480043 ns/op 1575779 B/op 141 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_false/prepared_commit-2 10 2205044 ns/op 1575779 B/op 141 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_false/prepared_commit-2 10 1927871 ns/op 1575779 B/op 141 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_false/prepared_commit-2 10 2036015 ns/op 1575779 B/op 141 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_true/legacy-2 10 7297434 ns/op 3465532 B/op 357 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_true/legacy-2 10 5048127 ns/op 3465532 B/op 357 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_true/legacy-2 10 5224367 ns/op 3465532 B/op 357 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_true/legacy-2 10 5277320 ns/op 3465532 B/op 357 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_true/legacy-2 10 4971670 ns/op 3465535 B/op 357 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_true/sized-2 10 5041342 ns/op 1891228 B/op 169 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_true/sized-2 10 4491682 ns/op 1891228 B/op 169 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_true/sized-2 10 4966235 ns/op 1891228 B/op 169 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_true/sized-2 10 4644107 ns/op 1891228 B/op 169 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_true/sized-2 10 4479055 ns/op 1891228 B/op 169 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_true/prepared_total-2 10 4914007 ns/op 1891511 B/op 173 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_true/prepared_total-2 10 5031889 ns/op 1891508 B/op 173 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_true/prepared_total-2 10 5046504 ns/op 1891460 B/op 173 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_true/prepared_total-2 10 4496234 ns/op 1891460 B/op 173 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_true/prepared_total-2 10 4949162 ns/op 1891460 B/op 173 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_true/prepared_commit-2 10 2603068 ns/op 315148 B/op 27 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_true/prepared_commit-2 10 2859800 ns/op 315148 B/op 27 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_true/prepared_commit-2 10 2858236 ns/op 315148 B/op 27 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_true/prepared_commit-2 10 3113986 ns/op 315148 B/op 27 allocs/op -BenchmarkTransactionStatusPublication/groups_4/existing_true/prepared_commit-2 10 2788538 ns/op 315148 B/op 27 allocs/op -PASS diff --git a/docs/results/status-publication/2026-09-15/native-build.log b/docs/results/status-publication/2026-09-15/native-build.log deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/results/status-publication/2026-09-15/native-final-run.log b/docs/results/status-publication/2026-09-15/native-final-run.log deleted file mode 100644 index c6d28bfba..000000000 --- a/docs/results/status-publication/2026-09-15/native-final-run.log +++ /dev/null @@ -1,12 +0,0 @@ -Running as unit: mithril-status-publication-final-20260915.service -race 0 -vet 0 -build 0 -benchmark-build 0 -benchmark 0 -overlap-final 0 - Finished with result: success -Main processes terminated with: code=exited, status=0/SUCCESS - Service runtime: 37.879s - CPU time consumed: 45.681s - Memory peak: 634.8M (swap: 0B) diff --git a/docs/results/status-publication/2026-09-15/native-overlap-final-summary.json b/docs/results/status-publication/2026-09-15/native-overlap-final-summary.json deleted file mode 100644 index 7f2b767e3..000000000 --- a/docs/results/status-publication/2026-09-15/native-overlap-final-summary.json +++ /dev/null @@ -1,104 +0,0 @@ -{ - "BenchmarkTransactionStatusExecutionOverlap/legacy": { - "ns/op": 23370663.0, - "commit-with-wait-ns/op": 6062517.0, - "execution-ns/op": 17289566.0, - "B/op": 23276118.0, - "allocs/op": 242222.0 - }, - "BenchmarkTransactionStatusExecutionOverlap/legacy-2": { - "ns/op": 20677847.0, - "commit-with-wait-ns/op": 5394296.0, - "execution-ns/op": 15069600.0, - "B/op": 23276627.0, - "allocs/op": 242225.0 - }, - "BenchmarkTransactionStatusExecutionOverlap/sized": { - "ns/op": 21296975.0, - "commit-with-wait-ns/op": 4299800.0, - "execution-ns/op": 17436468.0, - "B/op": 20125587.0, - "allocs/op": 241932.0 - }, - "BenchmarkTransactionStatusExecutionOverlap/sized-2": { - "ns/op": 18573776.0, - "commit-with-wait-ns/op": 4299496.0, - "execution-ns/op": 14369209.0, - "B/op": 20126012.0, - "allocs/op": 241934.0 - }, - "BenchmarkTransactionStatusExecutionOverlap/overlap": { - "ns/op": 22188363.0, - "commit-with-wait-ns/op": 4616295.0, - "execution-ns/op": 17694970.0, - "B/op": 20125587.0, - "allocs/op": 241932.0 - }, - "BenchmarkTransactionStatusExecutionOverlap/overlap-2": { - "ns/op": 17090947.0, - "commit-with-wait-ns/op": 2119131.0, - "execution-ns/op": 14966507.0, - "B/op": 20126212.0, - "allocs/op": 241938.0 - }, - "BenchmarkTransactionStatusSmallPublication/txs_0/legacy": { - "ns/op": 95.12, - "B/op": 112.0, - "allocs/op": 2.0 - }, - "BenchmarkTransactionStatusSmallPublication/txs_0/legacy-2": { - "ns/op": 74.71, - "B/op": 112.0, - "allocs/op": 2.0 - }, - "BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total": { - "ns/op": 119.4, - "B/op": 112.0, - "allocs/op": 2.0 - }, - "BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total-2": { - "ns/op": 100.0, - "B/op": 112.0, - "allocs/op": 2.0 - }, - "BenchmarkTransactionStatusSmallPublication/txs_1/legacy": { - "ns/op": 593.0, - "B/op": 960.0, - "allocs/op": 9.0 - }, - "BenchmarkTransactionStatusSmallPublication/txs_1/legacy-2": { - "ns/op": 466.8, - "B/op": 960.0, - "allocs/op": 9.0 - }, - "BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total": { - "ns/op": 708.3, - "B/op": 960.0, - "allocs/op": 9.0 - }, - "BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total-2": { - "ns/op": 609.1, - "B/op": 960.0, - "allocs/op": 9.0 - }, - "BenchmarkTransactionStatusSmallPublication/txs_32/legacy": { - "ns/op": 6085.0, - "B/op": 6320.0, - "allocs/op": 23.0 - }, - "BenchmarkTransactionStatusSmallPublication/txs_32/legacy-2": { - "ns/op": 5145.0, - "B/op": 6320.0, - "allocs/op": 23.0 - }, - "BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total": { - "ns/op": 4456.0, - "B/op": 3616.0, - "allocs/op": 13.0 - }, - "BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total-2": { - "ns/op": 3770.0, - "B/op": 3616.0, - "allocs/op": 13.0 - } -} \ No newline at end of file diff --git a/docs/results/status-publication/2026-09-15/native-overlap-final.log b/docs/results/status-publication/2026-09-15/native-overlap-final.log deleted file mode 100644 index a1e297aaf..000000000 --- a/docs/results/status-publication/2026-09-15/native-overlap-final.log +++ /dev/null @@ -1,95 +0,0 @@ -goos: linux -goarch: amd64 -pkg: github.com/Overclock-Validator/mithril/pkg/replay -cpu: AMD Ryzen 7 9700X 8-Core Processor -BenchmarkTransactionStatusExecutionOverlap/legacy 5 24692874 ns/op 6513086 commit-with-wait-ns/op 18179262 execution-ns/op 23276118 B/op 242222 allocs/op -BenchmarkTransactionStatusExecutionOverlap/legacy 5 22834712 ns/op 5772168 commit-with-wait-ns/op 17062174 execution-ns/op 23276118 B/op 242222 allocs/op -BenchmarkTransactionStatusExecutionOverlap/legacy 5 23370663 ns/op 6486422 commit-with-wait-ns/op 16883798 execution-ns/op 23276136 B/op 242222 allocs/op -BenchmarkTransactionStatusExecutionOverlap/legacy 5 23246803 ns/op 5956655 commit-with-wait-ns/op 17289566 execution-ns/op 23276145 B/op 242222 allocs/op -BenchmarkTransactionStatusExecutionOverlap/legacy 5 24078611 ns/op 6062517 commit-with-wait-ns/op 18015548 execution-ns/op 23276059 B/op 242221 allocs/op -BenchmarkTransactionStatusExecutionOverlap/legacy-2 5 20677847 ns/op 5607605 commit-with-wait-ns/op 15069600 execution-ns/op 23276630 B/op 242225 allocs/op -BenchmarkTransactionStatusExecutionOverlap/legacy-2 5 20222041 ns/op 5394296 commit-with-wait-ns/op 14827324 execution-ns/op 23276627 B/op 242225 allocs/op -BenchmarkTransactionStatusExecutionOverlap/legacy-2 6 19971750 ns/op 5078614 commit-with-wait-ns/op 14892735 execution-ns/op 23276532 B/op 242224 allocs/op -BenchmarkTransactionStatusExecutionOverlap/legacy-2 5 22424538 ns/op 5368235 commit-with-wait-ns/op 17055698 execution-ns/op 23276712 B/op 242226 allocs/op -BenchmarkTransactionStatusExecutionOverlap/legacy-2 5 20975910 ns/op 5790017 commit-with-wait-ns/op 15184788 execution-ns/op 23276624 B/op 242225 allocs/op -BenchmarkTransactionStatusExecutionOverlap/sized 5 21296975 ns/op 3860111 commit-with-wait-ns/op 17436468 execution-ns/op 20125587 B/op 241932 allocs/op -BenchmarkTransactionStatusExecutionOverlap/sized 5 21108482 ns/op 4260022 commit-with-wait-ns/op 16848123 execution-ns/op 20125592 B/op 241932 allocs/op -BenchmarkTransactionStatusExecutionOverlap/sized 6 20802767 ns/op 4299800 commit-with-wait-ns/op 16502635 execution-ns/op 20125558 B/op 241932 allocs/op -BenchmarkTransactionStatusExecutionOverlap/sized 6 22758328 ns/op 5033044 commit-with-wait-ns/op 17724954 execution-ns/op 20125533 B/op 241931 allocs/op -BenchmarkTransactionStatusExecutionOverlap/sized 5 23246396 ns/op 5467937 commit-with-wait-ns/op 17778082 execution-ns/op 20125614 B/op 241932 allocs/op -BenchmarkTransactionStatusExecutionOverlap/sized-2 6 19020036 ns/op 4526523 commit-with-wait-ns/op 14493163 execution-ns/op 20126028 B/op 241935 allocs/op -BenchmarkTransactionStatusExecutionOverlap/sized-2 6 18422586 ns/op 4167392 commit-with-wait-ns/op 14254792 execution-ns/op 20126009 B/op 241934 allocs/op -BenchmarkTransactionStatusExecutionOverlap/sized-2 6 18573776 ns/op 4299496 commit-with-wait-ns/op 14273834 execution-ns/op 20126012 B/op 241934 allocs/op -BenchmarkTransactionStatusExecutionOverlap/sized-2 6 18332367 ns/op 3962772 commit-with-wait-ns/op 14369209 execution-ns/op 20126006 B/op 241934 allocs/op -BenchmarkTransactionStatusExecutionOverlap/sized-2 6 19268730 ns/op 4381208 commit-with-wait-ns/op 14886998 execution-ns/op 20126038 B/op 241935 allocs/op -BenchmarkTransactionStatusExecutionOverlap/overlap 6 21422337 ns/op 4616295 commit-with-wait-ns/op 16804831 execution-ns/op 20125514 B/op 241931 allocs/op -BenchmarkTransactionStatusExecutionOverlap/overlap 5 21450447 ns/op 3882218 commit-with-wait-ns/op 17567431 execution-ns/op 20125593 B/op 241932 allocs/op -BenchmarkTransactionStatusExecutionOverlap/overlap 5 23556986 ns/op 5081649 commit-with-wait-ns/op 18474472 execution-ns/op 20125593 B/op 241932 allocs/op -BenchmarkTransactionStatusExecutionOverlap/overlap 5 22188363 ns/op 4492746 commit-with-wait-ns/op 17694970 execution-ns/op 20125587 B/op 241932 allocs/op -BenchmarkTransactionStatusExecutionOverlap/overlap 6 22888561 ns/op 4725085 commit-with-wait-ns/op 18162634 execution-ns/op 20125536 B/op 241931 allocs/op -BenchmarkTransactionStatusExecutionOverlap/overlap-2 6 17090947 ns/op 2119131 commit-with-wait-ns/op 14966507 execution-ns/op 20126253 B/op 241938 allocs/op -BenchmarkTransactionStatusExecutionOverlap/overlap-2 6 17313847 ns/op 2127423 commit-with-wait-ns/op 15181536 execution-ns/op 20126212 B/op 241938 allocs/op -BenchmarkTransactionStatusExecutionOverlap/overlap-2 7 17116780 ns/op 1984282 commit-with-wait-ns/op 15127492 execution-ns/op 20126437 B/op 241939 allocs/op -BenchmarkTransactionStatusExecutionOverlap/overlap-2 7 16485753 ns/op 1917207 commit-with-wait-ns/op 14563704 execution-ns/op 20126176 B/op 241938 allocs/op -BenchmarkTransactionStatusExecutionOverlap/overlap-2 6 16709920 ns/op 2210525 commit-with-wait-ns/op 14495070 execution-ns/op 20126125 B/op 241937 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/legacy 1299566 94.83 ns/op 112 B/op 2 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/legacy 1284991 93.05 ns/op 112 B/op 2 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/legacy 1247547 96.51 ns/op 112 B/op 2 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/legacy 1211845 96.60 ns/op 112 B/op 2 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/legacy 1238605 95.12 ns/op 112 B/op 2 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/legacy-2 1596568 76.21 ns/op 112 B/op 2 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/legacy-2 1614400 74.16 ns/op 112 B/op 2 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/legacy-2 1581337 76.14 ns/op 112 B/op 2 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/legacy-2 1574422 74.71 ns/op 112 B/op 2 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/legacy-2 1589880 74.16 ns/op 112 B/op 2 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total 1000000 123.0 ns/op 112 B/op 2 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total 1000000 119.6 ns/op 112 B/op 2 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total 1000000 118.8 ns/op 112 B/op 2 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total 1000000 119.4 ns/op 112 B/op 2 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total 1000000 117.4 ns/op 112 B/op 2 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total-2 1000000 100.4 ns/op 112 B/op 2 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total-2 1231928 97.12 ns/op 112 B/op 2 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total-2 1000000 101.3 ns/op 112 B/op 2 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total-2 1200760 99.52 ns/op 112 B/op 2 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total-2 1206331 100.0 ns/op 112 B/op 2 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/legacy 189308 586.1 ns/op 960 B/op 9 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/legacy 200305 607.5 ns/op 960 B/op 9 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/legacy 188314 597.3 ns/op 960 B/op 9 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/legacy 196060 586.6 ns/op 960 B/op 9 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/legacy 197784 593.0 ns/op 960 B/op 9 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/legacy-2 227380 470.5 ns/op 960 B/op 9 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/legacy-2 240021 461.5 ns/op 960 B/op 9 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/legacy-2 231192 472.9 ns/op 960 B/op 9 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/legacy-2 254556 466.8 ns/op 960 B/op 9 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/legacy-2 239902 443.4 ns/op 960 B/op 9 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total 167852 716.6 ns/op 960 B/op 9 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total 167379 708.2 ns/op 960 B/op 9 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total 167563 708.3 ns/op 960 B/op 9 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total 169233 699.7 ns/op 960 B/op 9 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total 164168 735.9 ns/op 960 B/op 9 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total-2 190630 609.1 ns/op 960 B/op 9 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total-2 190263 615.6 ns/op 960 B/op 9 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total-2 176863 604.2 ns/op 960 B/op 9 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total-2 196165 632.1 ns/op 960 B/op 9 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total-2 185583 588.6 ns/op 960 B/op 9 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/legacy 17916 6009 ns/op 6320 B/op 23 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/legacy 19801 6085 ns/op 6320 B/op 23 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/legacy 19857 6155 ns/op 6320 B/op 23 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/legacy 19308 6126 ns/op 6320 B/op 23 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/legacy 20053 6002 ns/op 6320 B/op 23 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/legacy-2 23298 5038 ns/op 6320 B/op 23 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/legacy-2 23206 5158 ns/op 6320 B/op 23 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/legacy-2 22210 5145 ns/op 6320 B/op 23 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/legacy-2 23406 5305 ns/op 6320 B/op 23 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/legacy-2 23084 5114 ns/op 6320 B/op 23 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total 26672 4343 ns/op 3616 B/op 13 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total 26752 4456 ns/op 3616 B/op 13 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total 27342 4471 ns/op 3616 B/op 13 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total 26960 4312 ns/op 3616 B/op 13 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total 27775 4592 ns/op 3616 B/op 13 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total-2 31856 3644 ns/op 3616 B/op 13 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total-2 32778 3717 ns/op 3616 B/op 13 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total-2 32239 3784 ns/op 3616 B/op 13 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total-2 30372 3818 ns/op 3616 B/op 13 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total-2 32263 3770 ns/op 3616 B/op 13 allocs/op -PASS diff --git a/docs/results/status-publication/2026-09-15/native-overlap-summary.json b/docs/results/status-publication/2026-09-15/native-overlap-summary.json deleted file mode 100644 index efa0017aa..000000000 --- a/docs/results/status-publication/2026-09-15/native-overlap-summary.json +++ /dev/null @@ -1,104 +0,0 @@ -{ - "BenchmarkTransactionStatusExecutionOverlap/legacy": { - "ns/op": 22833095.0, - "commit-with-wait-ns/op": 5848843.0, - "execution-ns/op": 16779392.0, - "B/op": 23276116.0, - "allocs/op": 242222.0 - }, - "BenchmarkTransactionStatusExecutionOverlap/legacy-2": { - "ns/op": 19163664.0, - "commit-with-wait-ns/op": 5114189.0, - "execution-ns/op": 14034884.0, - "B/op": 23276650.0, - "allocs/op": 242225.0 - }, - "BenchmarkTransactionStatusExecutionOverlap/sized": { - "ns/op": 21164557.0, - "commit-with-wait-ns/op": 4745710.0, - "execution-ns/op": 17057597.0, - "B/op": 20125556.0, - "allocs/op": 241932.0 - }, - "BenchmarkTransactionStatusExecutionOverlap/sized-2": { - "ns/op": 18233386.0, - "commit-with-wait-ns/op": 4143998.0, - "execution-ns/op": 14088971.0, - "B/op": 20126009.0, - "allocs/op": 241934.0 - }, - "BenchmarkTransactionStatusExecutionOverlap/overlap": { - "ns/op": 21351433.0, - "commit-with-wait-ns/op": 3481211.0, - "execution-ns/op": 18286875.0, - "B/op": 20125816.0, - "allocs/op": 241936.0 - }, - "BenchmarkTransactionStatusExecutionOverlap/overlap-2": { - "ns/op": 16814463.0, - "commit-with-wait-ns/op": 2297246.0, - "execution-ns/op": 14538161.0, - "B/op": 20126252.0, - "allocs/op": 241938.0 - }, - "BenchmarkTransactionStatusSmallPublication/txs_0/legacy": { - "ns/op": 94.81, - "B/op": 112.0, - "allocs/op": 2.0 - }, - "BenchmarkTransactionStatusSmallPublication/txs_0/legacy-2": { - "ns/op": 76.19, - "B/op": 112.0, - "allocs/op": 2.0 - }, - "BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total": { - "ns/op": 181.7, - "B/op": 264.0, - "allocs/op": 5.0 - }, - "BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total-2": { - "ns/op": 142.9, - "B/op": 264.0, - "allocs/op": 5.0 - }, - "BenchmarkTransactionStatusSmallPublication/txs_1/legacy": { - "ns/op": 810.9, - "B/op": 960.0, - "allocs/op": 9.0 - }, - "BenchmarkTransactionStatusSmallPublication/txs_1/legacy-2": { - "ns/op": 633.3, - "B/op": 960.0, - "allocs/op": 9.0 - }, - "BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total": { - "ns/op": 1645.0, - "B/op": 1192.0, - "allocs/op": 13.0 - }, - "BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total-2": { - "ns/op": 1511.0, - "B/op": 1192.0, - "allocs/op": 13.0 - }, - "BenchmarkTransactionStatusSmallPublication/txs_32/legacy": { - "ns/op": 6067.0, - "B/op": 6320.0, - "allocs/op": 23.0 - }, - "BenchmarkTransactionStatusSmallPublication/txs_32/legacy-2": { - "ns/op": 5064.0, - "B/op": 6320.0, - "allocs/op": 23.0 - }, - "BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total": { - "ns/op": 5837.0, - "B/op": 3848.0, - "allocs/op": 17.0 - }, - "BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total-2": { - "ns/op": 5961.0, - "B/op": 3848.0, - "allocs/op": 17.0 - } -} \ No newline at end of file diff --git a/docs/results/status-publication/2026-09-15/native-overlap.log b/docs/results/status-publication/2026-09-15/native-overlap.log deleted file mode 100644 index e347eed1c..000000000 --- a/docs/results/status-publication/2026-09-15/native-overlap.log +++ /dev/null @@ -1,102 +0,0 @@ -Running as unit: mithril-status-overlap-bench-20260915.service -goos: linux -goarch: amd64 -pkg: github.com/Overclock-Validator/mithril/pkg/replay -cpu: AMD Ryzen 7 9700X 8-Core Processor -BenchmarkTransactionStatusExecutionOverlap/legacy 6 22659962 ns/op 5699140 commit-with-wait-ns/op 16960453 execution-ns/op 23276112 B/op 242222 allocs/op -BenchmarkTransactionStatusExecutionOverlap/legacy 5 22853486 ns/op 5584087 commit-with-wait-ns/op 17269100 execution-ns/op 23276118 B/op 242222 allocs/op -BenchmarkTransactionStatusExecutionOverlap/legacy 5 23170780 ns/op 6502884 commit-with-wait-ns/op 16667603 execution-ns/op 23276136 B/op 242222 allocs/op -BenchmarkTransactionStatusExecutionOverlap/legacy 5 22833095 ns/op 6053272 commit-with-wait-ns/op 16779392 execution-ns/op 23276116 B/op 242222 allocs/op -BenchmarkTransactionStatusExecutionOverlap/legacy 5 22058768 ns/op 5848843 commit-with-wait-ns/op 16209621 execution-ns/op 23276112 B/op 242222 allocs/op -BenchmarkTransactionStatusExecutionOverlap/legacy-2 6 19456453 ns/op 5114189 commit-with-wait-ns/op 14341895 execution-ns/op 23276694 B/op 242226 allocs/op -BenchmarkTransactionStatusExecutionOverlap/legacy-2 5 20721459 ns/op 5463814 commit-with-wait-ns/op 15257144 execution-ns/op 23276633 B/op 242225 allocs/op -BenchmarkTransactionStatusExecutionOverlap/legacy-2 6 18883924 ns/op 5031514 commit-with-wait-ns/op 13851997 execution-ns/op 23276650 B/op 242225 allocs/op -BenchmarkTransactionStatusExecutionOverlap/legacy-2 6 19163664 ns/op 5128481 commit-with-wait-ns/op 14034884 execution-ns/op 23276584 B/op 242225 allocs/op -BenchmarkTransactionStatusExecutionOverlap/legacy-2 6 18648556 ns/op 4971799 commit-with-wait-ns/op 13676368 execution-ns/op 23276654 B/op 242226 allocs/op -BenchmarkTransactionStatusExecutionOverlap/sized 6 20730994 ns/op 3673079 commit-with-wait-ns/op 17057597 execution-ns/op 20125561 B/op 241932 allocs/op -BenchmarkTransactionStatusExecutionOverlap/sized 5 21164557 ns/op 4762666 commit-with-wait-ns/op 16401568 execution-ns/op 20125556 B/op 241932 allocs/op -BenchmarkTransactionStatusExecutionOverlap/sized 5 20803959 ns/op 3673651 commit-with-wait-ns/op 17130021 execution-ns/op 20125587 B/op 241932 allocs/op -BenchmarkTransactionStatusExecutionOverlap/sized 5 21585622 ns/op 4745710 commit-with-wait-ns/op 16839541 execution-ns/op 20125537 B/op 241931 allocs/op -BenchmarkTransactionStatusExecutionOverlap/sized 5 22061011 ns/op 4786467 commit-with-wait-ns/op 17274047 execution-ns/op 20125526 B/op 241931 allocs/op -BenchmarkTransactionStatusExecutionOverlap/sized-2 6 18481807 ns/op 4332538 commit-with-wait-ns/op 14148935 execution-ns/op 20126009 B/op 241934 allocs/op -BenchmarkTransactionStatusExecutionOverlap/sized-2 6 18233386 ns/op 4143998 commit-with-wait-ns/op 14088971 execution-ns/op 20125942 B/op 241934 allocs/op -BenchmarkTransactionStatusExecutionOverlap/sized-2 6 17926782 ns/op 3900186 commit-with-wait-ns/op 14026293 execution-ns/op 20126008 B/op 241935 allocs/op -BenchmarkTransactionStatusExecutionOverlap/sized-2 6 17641308 ns/op 3846237 commit-with-wait-ns/op 13794796 execution-ns/op 20126009 B/op 241934 allocs/op -BenchmarkTransactionStatusExecutionOverlap/sized-2 6 18874807 ns/op 4403265 commit-with-wait-ns/op 14471150 execution-ns/op 20126009 B/op 241934 allocs/op -BenchmarkTransactionStatusExecutionOverlap/overlap 6 21101004 ns/op 2063760 commit-with-wait-ns/op 19033430 execution-ns/op 20125788 B/op 241936 allocs/op -BenchmarkTransactionStatusExecutionOverlap/overlap 5 21351433 ns/op 3481211 commit-with-wait-ns/op 17866676 execution-ns/op 20125816 B/op 241936 allocs/op -BenchmarkTransactionStatusExecutionOverlap/overlap 5 21378252 ns/op 3800343 commit-with-wait-ns/op 17574134 execution-ns/op 20125816 B/op 241936 allocs/op -BenchmarkTransactionStatusExecutionOverlap/overlap 5 20630833 ns/op 1688017 commit-with-wait-ns/op 18939909 execution-ns/op 20125819 B/op 241936 allocs/op -BenchmarkTransactionStatusExecutionOverlap/overlap 5 22262745 ns/op 3971910 commit-with-wait-ns/op 18286875 execution-ns/op 20125816 B/op 241936 allocs/op -BenchmarkTransactionStatusExecutionOverlap/overlap-2 6 16698061 ns/op 2155589 commit-with-wait-ns/op 14538161 execution-ns/op 20126252 B/op 241938 allocs/op -BenchmarkTransactionStatusExecutionOverlap/overlap-2 7 16586794 ns/op 2151713 commit-with-wait-ns/op 14431196 execution-ns/op 20126259 B/op 241938 allocs/op -BenchmarkTransactionStatusExecutionOverlap/overlap-2 6 16814463 ns/op 2297246 commit-with-wait-ns/op 14512878 execution-ns/op 20126390 B/op 241939 allocs/op -BenchmarkTransactionStatusExecutionOverlap/overlap-2 6 17545014 ns/op 2416401 commit-with-wait-ns/op 15124436 execution-ns/op 20126122 B/op 241937 allocs/op -BenchmarkTransactionStatusExecutionOverlap/overlap-2 6 17149268 ns/op 2323257 commit-with-wait-ns/op 14821130 execution-ns/op 20126128 B/op 241937 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/legacy 1273947 96.94 ns/op 112 B/op 2 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/legacy 1294056 91.35 ns/op 112 B/op 2 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/legacy 1271734 92.04 ns/op 112 B/op 2 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/legacy 1300807 94.81 ns/op 112 B/op 2 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/legacy 1272133 96.92 ns/op 112 B/op 2 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/legacy-2 1589078 76.19 ns/op 112 B/op 2 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/legacy-2 1577804 77.30 ns/op 112 B/op 2 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/legacy-2 1563583 77.34 ns/op 112 B/op 2 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/legacy-2 1626802 74.57 ns/op 112 B/op 2 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/legacy-2 1591627 75.31 ns/op 112 B/op 2 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total 721636 185.7 ns/op 264 B/op 5 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total 704294 179.3 ns/op 264 B/op 5 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total 725799 182.1 ns/op 264 B/op 5 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total 725127 181.7 ns/op 264 B/op 5 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total 700783 176.9 ns/op 264 B/op 5 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total-2 726256 138.7 ns/op 264 B/op 5 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total-2 728829 139.5 ns/op 264 B/op 5 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total-2 861189 144.8 ns/op 264 B/op 5 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total-2 765130 143.7 ns/op 264 B/op 5 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_0/prepared_total-2 751879 142.9 ns/op 264 B/op 5 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/legacy 194295 653.8 ns/op 960 B/op 9 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/legacy 176815 648.9 ns/op 960 B/op 9 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/legacy 174601 882.8 ns/op 960 B/op 9 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/legacy 149839 829.7 ns/op 960 B/op 9 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/legacy 162600 810.9 ns/op 960 B/op 9 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/legacy-2 242948 633.3 ns/op 960 B/op 9 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/legacy-2 140822 720.2 ns/op 960 B/op 9 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/legacy-2 156864 754.5 ns/op 960 B/op 9 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/legacy-2 235440 462.3 ns/op 960 B/op 9 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/legacy-2 215985 471.1 ns/op 960 B/op 9 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total 66573 1705 ns/op 1192 B/op 13 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total 71836 1640 ns/op 1192 B/op 13 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total 70480 1655 ns/op 1192 B/op 13 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total 71564 1641 ns/op 1192 B/op 13 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total 73791 1645 ns/op 1192 B/op 13 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total-2 77616 1489 ns/op 1192 B/op 13 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total-2 78930 1520 ns/op 1192 B/op 13 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total-2 75838 1511 ns/op 1192 B/op 13 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total-2 74458 1491 ns/op 1192 B/op 13 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_1/prepared_total-2 79232 1551 ns/op 1192 B/op 13 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/legacy 19609 6019 ns/op 6320 B/op 23 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/legacy 18758 6100 ns/op 6320 B/op 23 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/legacy 19924 6118 ns/op 6320 B/op 23 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/legacy 19128 6067 ns/op 6320 B/op 23 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/legacy 19795 6000 ns/op 6320 B/op 23 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/legacy-2 23222 5027 ns/op 6320 B/op 23 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/legacy-2 23133 5064 ns/op 6320 B/op 23 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/legacy-2 23482 5243 ns/op 6320 B/op 23 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/legacy-2 22717 5013 ns/op 6320 B/op 23 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/legacy-2 24062 5630 ns/op 6320 B/op 23 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total 20682 6564 ns/op 3848 B/op 17 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total 18136 5879 ns/op 3848 B/op 17 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total 21844 5837 ns/op 3848 B/op 17 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total 19086 5635 ns/op 3848 B/op 17 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total 19254 5724 ns/op 3848 B/op 17 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total-2 20040 5546 ns/op 3848 B/op 17 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total-2 18378 5860 ns/op 3848 B/op 17 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total-2 19862 5961 ns/op 3848 B/op 17 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total-2 19912 6906 ns/op 3848 B/op 17 allocs/op -BenchmarkTransactionStatusSmallPublication/txs_32/prepared_total-2 21711 7037 ns/op 3848 B/op 17 allocs/op -PASS -ok github.com/Overclock-Validator/mithril/pkg/replay 17.258s - Finished with result: success -Main processes terminated with: code=exited, status=0/SUCCESS - Service runtime: 19.605s - CPU time consumed: 23.467s - Memory peak: 387.6M (swap: 0B) diff --git a/docs/results/status-publication/2026-09-15/native-race.log b/docs/results/status-publication/2026-09-15/native-race.log deleted file mode 100644 index 00504d333..000000000 --- a/docs/results/status-publication/2026-09-15/native-race.log +++ /dev/null @@ -1,3 +0,0 @@ -ok github.com/Overclock-Validator/mithril/pkg/replay 2.267s -ok github.com/Overclock-Validator/mithril/pkg/block 1.133s -? github.com/Overclock-Validator/mithril/pkg/metrics [no test files] diff --git a/docs/results/status-publication/2026-09-15/native-vet.log b/docs/results/status-publication/2026-09-15/native-vet.log deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/results/status-publication/2026-09-15/native-window.json b/docs/results/status-publication/2026-09-15/native-window.json deleted file mode 100644 index f283937ee..000000000 --- a/docs/results/status-publication/2026-09-15/native-window.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "start": "2026-09-15T02:57:29.581963+00:00", - "end": "2026-09-15T02:58:07.245339+00:00" -} \ No newline at end of file diff --git a/docs/results/status-publication/2026-09-15/post-test-health.json b/docs/results/status-publication/2026-09-15/post-test-health.json deleted file mode 100644 index df730357e..000000000 --- a/docs/results/status-publication/2026-09-15/post-test-health.json +++ /dev/null @@ -1 +0,0 @@ -{"utc": "2026-09-15T02:58:13.874813+00:00", "health": {"utc": "2026-09-15T02:58:13.949166+00:00", "pid": 568418, "rpc_slot": 3824460, "local_slot": 3824460, "last_vote": 3824459, "vote_lag": 1}, "pid": "MainPID=568418", "services": ["active", "active", "active", "active"]} diff --git a/docs/results/status-publication/2026-09-15/recheck-abba.log b/docs/results/status-publication/2026-09-15/recheck-abba.log deleted file mode 100644 index 9720620a0..000000000 --- a/docs/results/status-publication/2026-09-15/recheck-abba.log +++ /dev/null @@ -1,34 +0,0 @@ -Running as unit: mithril-status-publication-recheck-20260915.service; invocation ID: 8079e456482443308a68c660fb8f6b29 -goos: linux -goarch: amd64 -pkg: github.com/Overclock-Validator/mithril/pkg/replay -cpu: AMD Ryzen 7 9700X 8-Core Processor -BenchmarkTransactionStatusPublication/groups_4/existing_false/legacy-2 50 4400299 ns/op 6301401 B/op 659 allocs/op -PASS - -goos: linux -goarch: amd64 -pkg: github.com/Overclock-Validator/mithril/pkg/replay -cpu: AMD Ryzen 7 9700X 8-Core Processor -BenchmarkTransactionStatusPublication/groups_4/existing_false/prepared_total-2 50 2984879 ns/op 3152087 B/op 287 allocs/op -PASS - -goos: linux -goarch: amd64 -pkg: github.com/Overclock-Validator/mithril/pkg/replay -cpu: AMD Ryzen 7 9700X 8-Core Processor -BenchmarkTransactionStatusPublication/groups_4/existing_false/prepared_total-2 50 3131031 ns/op 3152091 B/op 287 allocs/op -PASS - -goos: linux -goarch: amd64 -pkg: github.com/Overclock-Validator/mithril/pkg/replay -cpu: AMD Ryzen 7 9700X 8-Core Processor -BenchmarkTransactionStatusPublication/groups_4/existing_false/legacy-2 50 4564884 ns/op 6301399 B/op 659 allocs/op -PASS - - Finished with result: success -Main processes terminated with: code=exited, status=0/SUCCESS - Service runtime: 1.278s - CPU time consumed: 1.456s - Memory peak: 73.9M (swap: 0B) diff --git a/docs/results/status-publication/2026-09-15/tested-source.json b/docs/results/status-publication/2026-09-15/tested-source.json deleted file mode 100644 index c4889f6a2..000000000 --- a/docs/results/status-publication/2026-09-15/tested-source.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "base": "33dde4050d9250557583395810799aaac2f54017", - "branch_before_change": "31e0d8c0", - "files": { - "pkg/replay/transaction_status_overlap_benchmark_test.go": "6310ce381e6e88151b14a7f0e5f56e7370a8e1d94af466911dd69707379d8cdb", - "pkg/replay/transaction_status_publication.go": "a94c1227ce0b0e329dfff390c2303bb36525493e0cde3fce7d3fe177eaa1681b", - "pkg/replay/transaction_status_publication_benchmark_test.go": "0ff3a1f0c2a8d0ceafb5d7ba300c6cb5358082fd019f75a4b6fe0c35fde1575e", - "pkg/replay/transaction_status_publication_test.go": "eae775d2d10e6ca5b13b1e6f201cff15d8d0174b40ff20a69aa4279004799ca1", - "pkg/metrics/metrics.go": "17005056d872f9b8acf75fee15dc2c175bc4addad1b2dafee61445124dfbe307", - "pkg/replay/block.go": "19e7e931ec5e8aaab2e910808cb2f6e19f5721ff2ad953541f42828a88076a0d", - "pkg/replay/transaction_status_cache.go": "731febda7fdfe09b84d4281c53c3d0f4381e8f7d67d9d47f084c8a10c1523935", - "pkg/replay/transaction_status_plan_binding_test.go": "6c1fe9c92af2011402025ca96601847a1313bf2ffb564082eb06aa89088e250b", - "pkg/replay/transaction_status_prepared_test.go": "1944b443624d1075f6ea3e89630cdca42a6245a95a3116bbd47bd670311ad876" - }, - "baseline_check": "Frozen commitBlockWithPlan and addDeltaVisibleLocked exactly match alpenglow-dev at the recorded base after renaming benchmark helper methods." -} diff --git a/docs/status-checkpoint-capture.md b/docs/status-checkpoint-capture.md index 62a4dc0e8..8ba391cb5 100644 --- a/docs/status-checkpoint-capture.md +++ b/docs/status-checkpoint-capture.md @@ -68,9 +68,8 @@ encoder with memoization, not the whole status-publication change against dev. The default-cadence result is approximately 2.3x, with the same 99.12 → 57.33 MB allocation reduction. Cold/all-new windows remain roughly unchanged. Native -combined race suites, vet and the validator build passed. These are staging -measurements: the encoding cache has not been deployed, so a live reduction in -durable-root lag or missed FAST votes has not yet been established. +combined race suites, vet and the validator build passed. These are historical staging measurements. They do not establish an isolated +live reduction in durable-root lag or missed FAST votes. ## Fold admission before collecting account writes diff --git a/docs/status-checkpoint-expiry-evidence.md b/docs/status-checkpoint-expiry-evidence.md new file mode 100644 index 000000000..22661d482 --- /dev/null +++ b/docs/status-checkpoint-expiry-evidence.md @@ -0,0 +1,19 @@ +# Status Checkpoint Expiry: benchmark evidence + +The maintained subsystem documentation and reusable Go benchmarks describe the +implementation and reproduction method. Historical raw results and session +notes are retained at [the tested source snapshot](https://github.com/Overclock-Validator/mithril/blob/a511ad3b0bc77cf8b5ae4ee16359ac6b453fc7bf) +(tag `review-evidence-20260916-status-checkpoint-expiry`). They are omitted from this proposed merge. + +[Historical result files](https://github.com/Overclock-Validator/mithril/tree/a511ad3b0bc77cf8b5ae4ee16359ac6b453fc7bf/docs/results) + +Measurements retain their original baselines. Rebasing onto PR #278 does not +turn an intermediate-version benchmark into a comparison with the new base. +Component timings and short live observations do not establish sustained FAST +inclusion gains. The final review description records validation of the rebased +source separately from historical benchmark results. + +The tagged snapshot also preserves the later 64-partition visible-status-map +experiment. That experiment is deliberately excluded from this review: it added +preparation work and did not demonstrate an overall large-block p99 benefit. +Earlier publication preparation and immutable-node encoding reuse remain. diff --git a/docs/transaction-status-expiry.md b/docs/transaction-status-expiry.md index 179202360..538acb0f8 100644 --- a/docs/transaction-status-expiry.md +++ b/docs/transaction-status-expiry.md @@ -42,18 +42,9 @@ measurements, not end-to-end Root/replay or a prediction of live FAST scores. Run `go test ./pkg/replay -run '^$' -bench '^BenchmarkTransactionStatusBatchExpiry$' -benchtime=1x -count=2`. -Prepared on `7layer/status-expiry-performance` above the isolated Votor fix. -This source is not the exact live FEC-integrated source. No deployment or public -PR change is implied by these local results. - -## Zen 5 validation — 20:05 UTC - -Native AMD Ryzen 7 9700X tests used an isolated copy of the preserved live -FEC-integrated source at `/srv/mithril-status-expiry-test-20260914/source`. -The original status-cache file was byte-identical to the change's parent. -Only the reviewed cache source and new tests were overlaid, with SHA256 checks. -The full replay race suite passed (2.189 s), and vet passed. No binary was deployed. +## Native benchmark +Ryzen 7 9700X, Go 1.26.4, original per-key expiry versus batched expiry. Benchmarks ran with GOMAXPROCS=2, nice=15, one caller and three iterations per case, while the validator and loader remained active. Setup and later GC are excluded from the expiry timer. Each case expires 4,321,280 entries (128 banks @@ -66,7 +57,5 @@ live stall samples and are not an end-to-end replay or FAST-score comparison. | One fully expired blockhash group | 700–718 ms | 0.024–0.031 ms | | Group crossing the retention boundary | 717–735 ms | 1.85–2.05 ms | -At 20:05:13 UTC the enrolled validator PID 291548 was at RPC/local slot 3,708,093, -last vote 3,708,092. Loader unpaused; validator, loader, FAST and Titan services -all active. Source/implementation and deployment status remain unchanged. -See zen5-benchmark.log, zen5-race.log, zen5-vet.log and zen5-health.json. +[Historical evidence](status-checkpoint-expiry-evidence.md) preserves the original +source revisions, raw measurements and validation. diff --git a/docs/transaction-status-publication.md b/docs/transaction-status-publication.md index 7078d81e5..a099af906 100644 --- a/docs/transaction-status-publication.md +++ b/docs/transaction-status-publication.md @@ -14,7 +14,7 @@ AMD Ryzen 7 9700X (Zen 5), Go 1.26.4, GOMAXPROCS=2. Tests ran in a separate proc Each block has 33,760 unique prepared message identities spread across one or four recent blockhashes. Existing-group cases seed 33,760 different ancestor transactions. Fixture creation, hashing, seeding and unwind are untimed. Existing maps retain capacity after unwind: the first timed commit's growth is amortized across the ten iterations. This does not model an index growing indefinitely across live blocks. -These historical measurements used the benchmark at `23e18d81`, whose baseline functions matched alpenglow-dev commit `33dde4050d9250557583395810799aaac2f54017`. Both versions used the same prepared identities, parent/duplicate checks and fixtures. The current `legacy` helper uses the current visible-index representation; reproduce this historical comparison at that commit, not by treating today’s helper as a frozen index baseline. +The frozen baseline functions exactly match alpenglow-dev commit `33dde4050d9250557583395810799aaac2f54017`. Both versions use the same prepared identities, parent/duplicate checks and fixtures. | Recent blockhash groups | Parent has keys in these groups | Baseline commit | Sized maps, inline | Preparation + commit, no overlap | Commit after preparation | |---|---|---:|---:|---:|---:| @@ -39,7 +39,7 @@ The initial unrestricted version showed no additional total-time benefit from ov Full replay and block race suites passed on both Zen 5 and M4 Pro. Metrics has no tests. Native vet for replay/metrics and the validator build passed. Tests cover fork replacement introducing a duplicate after preparation, concurrent sibling publication, stale identity binding, changed snapshot slice offsets, rejected/incomplete banks, mismatched preparation, pinned views, snapshot restore, unwind, empty banks and scheduling boundaries. -Raw logs, source hashes, summaries and the alternating recheck are in [results/status-publication/2026-09-15](results/status-publication/2026-09-15). The baseline comparison covers only status publication. No live replay or FAST improvement is claimed. The staging binary was not deployed; the existing validator remained active and voting throughout the tests. +Raw logs, source hashes, summaries and the alternating recheck are in [results/status-publication/2026-09-15](https://github.com/Overclock-Validator/mithril/blob/a511ad3b0bc77cf8b5ae4ee16359ac6b453fc7bf/docs/results/status-publication/2026-09-15). The baseline comparison covers only status publication. No live replay or FAST improvement is claimed. The staging binary was not deployed; the existing validator remained active and voting throughout the tests. Reproduce from this branch: @@ -71,71 +71,3 @@ Zen 5 incremental measurement (Ryzen 9700X, Go 1.26.4, GOMAXPROCS=2, five sample Values are medians of sample means. Existing-group cases remove approximately 1.1 ms of repeated lookup work; new-group cases show no clear gain and shared-host variation. Full native replay/block race suites, targeted node recovery race tests, vet and the combined build passed. Local replay race tests and vet also passed. A separate 180-second pre-change live trace observed 728 publications. In the 145 publications taking at least 1 ms, the repeated scan measured 1.805 ms median / 3.180 ms maximum; insertion 2.198 / 6.774 ms. Lock acquisition was at most 0.0058 ms across all publications, and the preparation join at most 0.0010 ms. This latency-selected cohort is not a fixed transaction-size sample or a before/after p99 comparison. Probe overhead is included. These measurements identify removable work; they do not establish a sustained FAST improvement. Raw traces, native test windows and exact combined source stay on the validator host at `/srv/mithril-status-validation-20260915`. - -## Partitioned visible status index - -Large blockhash groups use 64 smaller reference-count maps, selected by the low -six bits of the stored message key's first byte. During delta preparation, unique -keys are grouped by partition in private scratch space; publication then updates -one partition at a time. Groups starting below 1,024 keys retain one map for their -lifetime, avoiding a full-index copy when they grow. All visible-index reads and -writes retain the existing cache lock. No extra publication worker is introduced. - -The immutable per-bank deltas and MTS2 checkpoint bytes are unchanged. Restore -reconstructs this derived index from those deltas; unwind removes the same bank -references. Preparation does not authorize a block, persist a vote, or extend a -checkpoint's durable coverage. Commit still checks identity binding, complete -coverage, parent lineage and the ancestor-validation receipt. Changed key-slice -offsets discard both the prepared delta and its partition batches. These are the -same duplicate-prevention and crash-recovery guarantees as before this change. - -Incremental Zen 5 comparison against the previously deployed combined validator -(binary SHA256 `2c3628ad62a313a9dcf13878cafcb6fbd8f5fa12cd8637038de762758489c651`), -not the full PR against alpenglow-dev: Go 1.26.4, GOMAXPROCS=2, Nice=19, -200% CPU quota on the active validator host, three samples of 30 iterations. -Each block contains 33,760 unique identities. Values are medians of sample means. - -| Blockhash groups | Existing groups | Validated commit before → after | Preparation + commit, without overlap | -|---|---|---|---| -| 1 | No | 1.492 → 0.852 ms | 3.422 → 3.479 ms | -| 1 | Yes | 1.425 → 1.126 ms | 4.676 → 4.977 ms | -| 4 | No | 1.151 → 0.869 ms | 3.072 → 4.306 ms | -| 4 | Yes | 1.281 → 0.984 ms | 4.346 → 5.812 ms | - -Publication improves in these samples, but total preparation work increases, -especially with multiple blockhashes. Scratch costs roughly 20 bytes per unique -key plus partition metadata and is not retained in published bank nodes. The -benefit depends on execution hiding preparation without excessive contention. - -`BenchmarkStatusMapCriticalTail` measures individual validated commits with -preparation and unwind excluded. Run the identical benchmark file on both source -revisions: three samples of 150 iterations, one blockhash, nearest-rank p99. The -median of each run's p99 fell from 3.142 to 1.488 ms for new groups, but rose from -2.549 to 2.869 ms for warmed existing groups. This is not a consistent component -p99 win, and neither benchmark predicts live FAST inclusion. - -Native targeted replay/block-production race tests, vet and the validator build -passed. Coverage includes a randomized reference-count oracle with concentrated -keys, compact-group growth, expiry, snapshot restore, fork unwind, stale identity -binding and concurrent publication. Source copies, native results, exclusions for -test load and deployment metadata are retained on Zen 5 under -`/srv/mithril-status-index-20260915`. - - -Initial live trial: 958 baseline versus 182 candidate received blocks with at -least 30,000 transactions, excluding startup/native-test windows. Publication -median/p99 measured **3.607/6.901 → 2.875/6.343 ms**. All 182 candidates had -controls matched by leader, position, sender overlap and transaction/CU within -10%; the median per-block difference was **−0.737 ms publication**, **+1.100 ms -preparation**, **+0.498 ms execution**, and **−0.298 ms full assembly-to-local -serialization**. Preparation-wait p99 remained 0.001 ms. Controls are reused and -windows are unequal, so this is observational evidence, not isolated causation. - -Overall large-block p99 was **119.050 → 123.871 ms**; an overall tail improvement -is not established. Five candidate admission outliers (four empty blocks) spent -31.823 ms median / 39.907 ms maximum between spool-completion entry and beginning -delivery, before status publication. Their deeper cause is not yet established -on this binary. Two initial five-minute captures contained 1,911 inclusions in -1,933 unique observed FAST proofs (98.86%); startup is included in this operational -score, and it is not a before/after FAST comparison. Keep the candidate under -monitoring; the status-stage gain alone does not establish the final p99 goal. diff --git a/pkg/replay/transaction_status_cache.go b/pkg/replay/transaction_status_cache.go index 07a66e00a..bb4a157fb 100644 --- a/pkg/replay/transaction_status_cache.go +++ b/pkg/replay/transaction_status_cache.go @@ -85,7 +85,7 @@ func (n *transactionStatusNode) copyInto(copy *transactionStatusNode, parent *tr type visibleTransactionStatusGroup struct { keyIndex uint8 - keys transactionStatusIndex + keys map[transactionStatusKey]uint16 } // TransactionStatusCache is replay's authoritative, fork-aware @@ -590,7 +590,7 @@ func (c *TransactionStatusCache) validateAncestorTransactionsLocked(slot uint64, continue } key := sliceTransactionStatusKey(identity.MessageHash, group.keyIndex) - if group.keys.count(key) == 0 { + if group.keys[key] == 0 { continue } if already == nil { @@ -661,16 +661,13 @@ func (c *TransactionStatusCache) commitBlockWithValidation(block *b.Block, plan } delta := transactionStatusDelta(nil) - var indexBatches map[solana.Hash]*transactionStatusIndexBatch if prepared != nil && prepared.identities == plan.messageIdentities { delta = prepared.delta - indexBatches = prepared.indexBatches // A restore or branch transition can change a blockhash's slice offset. // Rebuild from full identities if any current group uses another offset. for blockhash, group := range delta { if visible := c.visible[blockhash]; visible != nil && visible.keyIndex != group.keyIndex { delta = nil - indexBatches = nil break } } @@ -686,7 +683,7 @@ func (c *TransactionStatusCache) commitBlockWithValidation(block *b.Block, plan delta = buildTransactionStatusDelta(plan.messageIdentities, counts, indexes) } - if err := c.addDeltaVisibleBatchesLocked(delta, indexBatches); err != nil { + if err := c.addDeltaVisibleLocked(delta); err != nil { return err } c.tip = &transactionStatusNode{ @@ -855,10 +852,6 @@ func (c *TransactionStatusCache) validateParentLocked(block *b.Block) error { } func (c *TransactionStatusCache) addDeltaVisibleLocked(delta transactionStatusDelta) error { - return c.addDeltaVisibleBatchesLocked(delta, nil) -} - -func (c *TransactionStatusCache) addDeltaVisibleBatchesLocked(delta transactionStatusDelta, batches map[solana.Hash]*transactionStatusIndexBatch) error { c.invalidateValidationLocked() for blockhash, deltaGroup := range delta { if group := c.visible[blockhash]; group != nil && group.keyIndex != deltaGroup.keyIndex { @@ -871,16 +864,12 @@ func (c *TransactionStatusCache) addDeltaVisibleBatchesLocked(delta transactionS if group == nil { group = &visibleTransactionStatusGroup{ keyIndex: deltaGroup.keyIndex, + keys: make(map[transactionStatusKey]uint16, len(deltaGroup.keys)), } - group.keys.init(len(deltaGroup.keys)) c.visible[blockhash] = group } - if batch := batches[blockhash]; batch != nil { - group.keys.addBatch(batch) - } else { - for key := range deltaGroup.keys { - group.keys.add(key) - } + for key := range deltaGroup.keys { + group.keys[key]++ } } return nil @@ -894,9 +883,13 @@ func (c *TransactionStatusCache) removeDeltaVisibleLocked(delta transactionStatu continue } for key := range deltaGroup.keys { - group.keys.remove(key) + if group.keys[key] <= 1 { + delete(group.keys, key) + } else { + group.keys[key]-- + } } - if group.keys.empty() { + if len(group.keys) == 0 { delete(c.visible, blockhash) } } @@ -996,15 +989,14 @@ func (c *TransactionStatusCache) expireVisibleLocked(expired, retained []*transa if len(g.survivors) == 0 { delete(c.visible, hash) } else if g.retainedKeys < g.expiredKeys { - rebuilt := &visibleTransactionStatusGroup{keyIndex: g.survivors[0].keyIndex} - rebuilt.keys.init(g.retainedKeys) + rebuilt := &visibleTransactionStatusGroup{keyIndex: g.survivors[0].keyIndex, keys: make(map[transactionStatusKey]uint16)} for _, delta := range g.survivors { for key := range delta.keys { - rebuilt.keys.add(key) + rebuilt.keys[key]++ } } c.visible[hash] = rebuilt - if rebuilt.keys.empty() { + if len(rebuilt.keys) == 0 { delete(c.visible, hash) } } diff --git a/pkg/replay/transaction_status_index.go b/pkg/replay/transaction_status_index.go deleted file mode 100644 index a7c044b0e..000000000 --- a/pkg/replay/transaction_status_index.go +++ /dev/null @@ -1,147 +0,0 @@ -package replay - -// Partition only the mutable lookup index, never the immutable bank deltas or -// checkpoint format. Message-hash bytes select a partition; even adversarially -// concentrated keys retain exactly the same membership/reference-count rules. -// Grouping prepared updates by partition keeps a smaller working set hot while -// publishing. All access is still protected by TransactionStatusCache.mu; this -// introduces neither background publication nor additional mutation workers. -// Recovery guarantee: this is a derived in-memory index. Snapshots still store -// the same immutable per-bank keys; restore rebuilds the counts from those keys. -// No checkpoint coverage, duplicate-check, vote persistence, or unwind rule is -// relaxed, and no prepared batch becomes authoritative before commit succeeds. -const transactionStatusIndexPartitions = 64 - -type transactionStatusIndex []map[transactionStatusKey]uint16 - -// Keep small groups in one map. The chosen layout remains fixed for the group -// lifetime: growing an existing group never forces a full-index copy on replay. -func (index *transactionStatusIndex) init(expected int) { - if len(*index) != 0 { - return - } - partitions := 1 - if expected >= 1024 { - partitions = transactionStatusIndexPartitions - } - *index = make(transactionStatusIndex, partitions) -} - -func statusIndexPartition(key transactionStatusKey) int { - return int(key[0]) & (transactionStatusIndexPartitions - 1) -} - -func (index *transactionStatusIndex) count(key transactionStatusKey) uint16 { - if len(*index) == 0 { - return 0 - } - return (*index)[int(key[0])&(len(*index)-1)][key] -} - -func (index *transactionStatusIndex) add(key transactionStatusKey) { - index.init(1) - partition := int(key[0]) & (len(*index) - 1) - if (*index)[partition] == nil { - (*index)[partition] = make(map[transactionStatusKey]uint16) - } - (*index)[partition][key]++ -} - -func (index *transactionStatusIndex) remove(key transactionStatusKey) { - if len(*index) == 0 { - return - } - number := int(key[0]) & (len(*index) - 1) - partition := (*index)[number] - if partition[key] <= 1 { - delete(partition, key) - } else { - partition[key]-- - } - if len(partition) == 0 { - (*index)[number] = nil - } -} - -func (index *transactionStatusIndex) empty() bool { - for _, partition := range *index { - if len(partition) != 0 { - return false - } - } - return true -} - -// A batch is private preparation scratch, not retained in a bank node or -// serialized. Build it from the deduplicated immutable delta so collisions in -// the stored 20-byte key still contribute only once per bank, as before. -type transactionStatusIndexBatch struct { - keys []transactionStatusKey - ends [transactionStatusIndexPartitions]int -} - -func prepareStatusIndexBatch(group *transactionStatusGroup) *transactionStatusIndexBatch { - batch := &transactionStatusIndexBatch{keys: make([]transactionStatusKey, 0, len(group.keys))} - for key := range group.keys { - batch.append(key) - } - batch.partition() - return batch -} - -func (batch *transactionStatusIndexBatch) append(key transactionStatusKey) { - batch.keys = append(batch.keys, key) - batch.ends[statusIndexPartition(key)]++ -} - -// Counting partition in place: each swap fills one destination position. This -// avoids a second key array and repeated iteration over the immutable key map. -func (batch *transactionStatusIndexBatch) partition() { - var positions [transactionStatusIndexPartitions]int - for i := 1; i < len(batch.ends); i++ { - batch.ends[i] += batch.ends[i-1] - positions[i] = batch.ends[i-1] - } - for bucket, end := range batch.ends { - for positions[bucket] < end { - at := positions[bucket] - key := batch.keys[at] - destination := statusIndexPartition(key) - if destination == bucket { - positions[bucket]++ - continue - } - to := positions[destination] - batch.keys[at], batch.keys[to] = batch.keys[to], key - positions[destination]++ - } - } -} - -func (index *transactionStatusIndex) addBatch(batch *transactionStatusIndexBatch) { - index.init(len(batch.keys)) - if len(*index) == 1 { - if (*index)[0] == nil && len(batch.keys) > 0 { - (*index)[0] = make(map[transactionStatusKey]uint16, len(batch.keys)) - } - for _, key := range batch.keys { - (*index)[0][key]++ - } - return - } - start := 0 - for i, end := range batch.ends { - if start == end { - continue - } - partition := (*index)[i] - if partition == nil { - partition = make(map[transactionStatusKey]uint16, end-start) - (*index)[i] = partition - } - for _, key := range batch.keys[start:end] { - partition[key]++ - } - start = end - } -} diff --git a/pkg/replay/transaction_status_index_benchmark_test.go b/pkg/replay/transaction_status_index_benchmark_test.go deleted file mode 100644 index f8f11358b..000000000 --- a/pkg/replay/transaction_status_index_benchmark_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package replay - -import ( - "fmt" - "sort" - "testing" - "time" -) - -// Measure the publication tail separately from preparation and ancestor checks. -// Use the identical benchmark file on both source revisions. Includes binding -// checks and index publication; excludes fixture creation, preparation and unwind. -// A warmed existing blockhash index is kept across iterations. This is an -// isolated component benchmark, not a prediction of live voting percentiles. -func BenchmarkStatusMapCriticalTail(b *testing.B) { - for _, existing := range []bool{false, true} { - b.Run(fmt.Sprintf("existing_%t", existing), func(b *testing.B) { - txs := benchmarkUniqueTransactions(67520) - parent := statusCacheTestBlock(10, txs[:33760]...) - if !existing { - parent.Transactions = nil - } - block := statusCacheTestBlock(11, txs[33760:]...) - plan, err := planBlockTransactionExecution(block) - if err != nil { - b.Fatal(err) - } - cache := NewTransactionStatusCache() - if err := cache.CommitBlock(parent); err != nil { - b.Fatal(err) - } - durations := make([]int64, 0, b.N) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - b.StopTimer() - prepared := cache.prepareTransactionStatusDelta(plan.messageIdentities) - receipt, err := cache.validateBlockForPublication(block, plan) - if err != nil { - b.Fatal(err) - } - b.StartTimer() - start := time.Now() - err = cache.commitBlockWithValidation(block, plan, prepared, receipt) - duration := time.Since(start).Nanoseconds() - b.StopTimer() - if err != nil { - b.Fatal(err) - } - durations = append(durations, duration) - if err := cache.Unwind(11); err != nil { - b.Fatal(err) - } - b.StartTimer() - } - b.StopTimer() - sort.Slice(durations, func(i, j int) bool { return durations[i] < durations[j] }) - b.ReportMetric(float64(durations[(len(durations)-1)/2]), "p50-ns") - b.ReportMetric(float64(durations[(99*len(durations)+99)/100-1]), "p99-ns") - }) - } -} diff --git a/pkg/replay/transaction_status_index_test.go b/pkg/replay/transaction_status_index_test.go deleted file mode 100644 index 7fba9c88d..000000000 --- a/pkg/replay/transaction_status_index_test.go +++ /dev/null @@ -1,104 +0,0 @@ -package replay - -import ( - "math/rand" - "testing" -) - -func TestTransactionStatusPartitionedIndexReferenceCounts(t *testing.T) { - for _, concentrated := range []bool{false, true} { - rng := rand.New(rand.NewSource(71)) - var index transactionStatusIndex - index.init(33760) - reference := make(map[transactionStatusKey]uint16) - keys := make([]transactionStatusKey, 1024) - for i := range keys { - rng.Read(keys[i][:]) - if concentrated { - keys[i][0] = 255 - } - } - var banks []*transactionStatusGroup - for step := 0; step < 600; step++ { - if len(banks) > 0 && (step%3 == 0 || len(banks) == 300) { - group := banks[0] - banks = banks[1:] - for k := range group.keys { - index.remove(k) - reference[k]-- - if reference[k] == 0 { - delete(reference, k) - } - } - } else { - group := &transactionStatusGroup{keys: make(map[transactionStatusKey]struct{})} - for i := 0; i < 100; i++ { - group.keys[keys[rng.Intn(len(keys))]] = struct{}{} - } - index.addBatch(prepareStatusIndexBatch(group)) - banks = append(banks, group) - for k := range group.keys { - reference[k]++ - } - } - for _, k := range keys { - if got := index.count(k); got != reference[k] { - t.Fatalf("concentrated=%t step=%d count=%d want=%d", concentrated, step, got, reference[k]) - } - } - } - for _, group := range banks { - for k := range group.keys { - index.remove(k) - } - } - if !index.empty() { - t.Fatal("index retained keys after all bank references removed") - } - } -} - -func TestTransactionStatusIndexBatchPartitionEdges(t *testing.T) { - for _, first := range []byte{0, 63, 64, 127, 255} { - group := &transactionStatusGroup{keys: map[transactionStatusKey]struct{}{{first, 1}: {}, {first, 2}: {}}} - batch := prepareStatusIndexBatch(group) - var index transactionStatusIndex - index.addBatch(batch) - index.addBatch(batch) - for k := range group.keys { - if index.count(k) != 2 { - t.Fatal("lost overlapping bank reference") - } - index.remove(k) - if index.count(k) != 1 { - t.Fatal("removed key still required by another bank") - } - index.remove(k) - } - if !index.empty() { - t.Fatal("partition did not empty") - } - index.addBatch(prepareStatusIndexBatch(&transactionStatusGroup{})) - if !index.empty() { - t.Fatal("empty batch introduced keys") - } - } -} - -func TestTransactionStatusIndexSmallGroupDoesNotRepartition(t *testing.T) { - var index transactionStatusIndex - index.add(transactionStatusKey{0, 1}) - group := &transactionStatusGroup{keys: make(map[transactionStatusKey]struct{})} - for i := 0; i < 4096; i++ { - group.keys[transactionStatusKey{byte(i), byte(i >> 8)}] = struct{}{} - } - index.addBatch(prepareStatusIndexBatch(group)) - if len(index) != 1 { - t.Fatal("existing small group copied into a new layout during publication") - } - for k := range group.keys { - if index.count(k) == 0 { - t.Fatal("batch lost a key when using compact layout") - } - } -} diff --git a/pkg/replay/transaction_status_publication.go b/pkg/replay/transaction_status_publication.go index 5203fa17c..e821071ad 100644 --- a/pkg/replay/transaction_status_publication.go +++ b/pkg/replay/transaction_status_publication.go @@ -11,9 +11,8 @@ import ( // Preparation owns private, immutable maps. It never publishes a status or // authorizes a bank: commit still checks coverage, lineage, and duplicates. type preparedTransactionStatusDelta struct { - identities *b.PreparedTransactionMessageIdentities - delta transactionStatusDelta - indexBatches map[solana.Hash]*transactionStatusIndexBatch + identities *b.PreparedTransactionMessageIdentities + delta transactionStatusDelta } type transactionStatusPreparation struct { @@ -57,33 +56,14 @@ func countTransactionStatusGroups(identities *b.PreparedTransactionMessageIdenti } func buildTransactionStatusDelta(identities *b.PreparedTransactionMessageIdentities, counts map[solana.Hash]int, indexes map[solana.Hash]uint8) transactionStatusDelta { - return buildTransactionStatusDeltaWithBatches(identities, counts, indexes, nil) -} - -func buildTransactionStatusDeltaWithBatches(identities *b.PreparedTransactionMessageIdentities, counts map[solana.Hash]int, indexes map[solana.Hash]uint8, batches map[solana.Hash]*transactionStatusIndexBatch) transactionStatusDelta { delta := make(transactionStatusDelta, len(counts)) for blockhash, count := range counts { delta[blockhash] = &transactionStatusGroup{keyIndex: indexes[blockhash], keys: make(map[transactionStatusKey]struct{}, count)} - if batches != nil && count >= 1024 { - batches[blockhash] = &transactionStatusIndexBatch{keys: make([]transactionStatusKey, 0, count)} - } } - var previous solana.Hash - var group *transactionStatusGroup - var batch *transactionStatusIndexBatch for i := 0; i < identities.Len(); i++ { identity := identities.Identity(i) - if group == nil || identity.RecentBlockhash != previous { - previous = identity.RecentBlockhash - group = delta[previous] - batch = batches[previous] - } - key := sliceTransactionStatusKey(identity.MessageHash, group.keyIndex) - before := len(group.keys) - group.keys[key] = struct{}{} - if batch != nil && len(group.keys) != before { - batch.append(key) - } + group := delta[identity.RecentBlockhash] + group.keys[sliceTransactionStatusKey(identity.MessageHash, group.keyIndex)] = struct{}{} } return delta } @@ -99,10 +79,5 @@ func (c *TransactionStatusCache) prepareTransactionStatusDelta(identities *b.Pre } } c.mu.RUnlock() - batches := make(map[solana.Hash]*transactionStatusIndexBatch, len(counts)) - delta := buildTransactionStatusDeltaWithBatches(identities, counts, indexes, batches) - for _, batch := range batches { - batch.partition() - } - return &preparedTransactionStatusDelta{identities: identities, delta: delta, indexBatches: batches} + return &preparedTransactionStatusDelta{identities: identities, delta: buildTransactionStatusDelta(identities, counts, indexes)} } diff --git a/pkg/replay/transaction_status_publication_benchmark_test.go b/pkg/replay/transaction_status_publication_benchmark_test.go index b6b73685c..2fc7d0f45 100644 --- a/pkg/replay/transaction_status_publication_benchmark_test.go +++ b/pkg/replay/transaction_status_publication_benchmark_test.go @@ -61,9 +61,8 @@ func (c *TransactionStatusCache) legacyCommitStatusForBenchmark(block *b.Block, return nil } -// Historical unprepared delta construction, using the current visible index. -// For before/after index comparisons run the same benchmark at both commits; -// this helper is not a frozen baseline for the mutable index implementation. +// Frozen production commit algorithm before publication optimization. This is +// an independent baseline, including its original visible-index allocation. func (c *TransactionStatusCache) legacyAddStatusForBenchmark(delta transactionStatusDelta) error { for blockhash, deltaGroup := range delta { if group := c.visible[blockhash]; group != nil && group.keyIndex != deltaGroup.keyIndex { @@ -76,11 +75,12 @@ func (c *TransactionStatusCache) legacyAddStatusForBenchmark(delta transactionSt if group == nil { group = &visibleTransactionStatusGroup{ keyIndex: deltaGroup.keyIndex, + keys: make(map[transactionStatusKey]uint16), } c.visible[blockhash] = group } for key := range deltaGroup.keys { - group.keys.add(key) + group.keys[key]++ } } return nil From f164274b871498a916d2f5fa5d8f855f7ae42369 Mon Sep 17 00:00:00 2001 From: 7layermagik <7layermagik@users.noreply.github.com> Date: Tue, 15 Sep 2026 19:33:09 -0500 Subject: [PATCH 14/16] docs: keep review specifications and archive operational notes --- docs/transaction-status-publication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/transaction-status-publication.md b/docs/transaction-status-publication.md index a099af906..2ee1d38c9 100644 --- a/docs/transaction-status-publication.md +++ b/docs/transaction-status-publication.md @@ -70,4 +70,4 @@ Zen 5 incremental measurement (Ryzen 9700X, Go 1.26.4, GOMAXPROCS=2, five sample Values are medians of sample means. Existing-group cases remove approximately 1.1 ms of repeated lookup work; new-group cases show no clear gain and shared-host variation. Full native replay/block race suites, targeted node recovery race tests, vet and the combined build passed. Local replay race tests and vet also passed. -A separate 180-second pre-change live trace observed 728 publications. In the 145 publications taking at least 1 ms, the repeated scan measured 1.805 ms median / 3.180 ms maximum; insertion 2.198 / 6.774 ms. Lock acquisition was at most 0.0058 ms across all publications, and the preparation join at most 0.0010 ms. This latency-selected cohort is not a fixed transaction-size sample or a before/after p99 comparison. Probe overhead is included. These measurements identify removable work; they do not establish a sustained FAST improvement. Raw traces, native test windows and exact combined source stay on the validator host at `/srv/mithril-status-validation-20260915`. +Original run artifacts are retained in the [evidence archive](status-checkpoint-expiry-evidence.md). \ No newline at end of file From ced869c8d964c39d1159150780935da3eefbe73b Mon Sep 17 00:00:00 2001 From: 7layermagik <7layermagik@users.noreply.github.com> Date: Tue, 15 Sep 2026 19:39:57 -0500 Subject: [PATCH 15/16] docs: validate archive links and formatting --- docs/status-checkpoint-expiry-evidence.md | 2 +- docs/transaction-status-publication.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/status-checkpoint-expiry-evidence.md b/docs/status-checkpoint-expiry-evidence.md index 22661d482..978180120 100644 --- a/docs/status-checkpoint-expiry-evidence.md +++ b/docs/status-checkpoint-expiry-evidence.md @@ -2,7 +2,7 @@ The maintained subsystem documentation and reusable Go benchmarks describe the implementation and reproduction method. Historical raw results and session -notes are retained at [the tested source snapshot](https://github.com/Overclock-Validator/mithril/blob/a511ad3b0bc77cf8b5ae4ee16359ac6b453fc7bf) +notes are retained at [the tested source snapshot](https://github.com/Overclock-Validator/mithril/tree/a511ad3b0bc77cf8b5ae4ee16359ac6b453fc7bf) (tag `review-evidence-20260916-status-checkpoint-expiry`). They are omitted from this proposed merge. [Historical result files](https://github.com/Overclock-Validator/mithril/tree/a511ad3b0bc77cf8b5ae4ee16359ac6b453fc7bf/docs/results) diff --git a/docs/transaction-status-publication.md b/docs/transaction-status-publication.md index 2ee1d38c9..c74be8335 100644 --- a/docs/transaction-status-publication.md +++ b/docs/transaction-status-publication.md @@ -70,4 +70,4 @@ Zen 5 incremental measurement (Ryzen 9700X, Go 1.26.4, GOMAXPROCS=2, five sample Values are medians of sample means. Existing-group cases remove approximately 1.1 ms of repeated lookup work; new-group cases show no clear gain and shared-host variation. Full native replay/block race suites, targeted node recovery race tests, vet and the combined build passed. Local replay race tests and vet also passed. -Original run artifacts are retained in the [evidence archive](status-checkpoint-expiry-evidence.md). \ No newline at end of file +Original run artifacts are retained in the [evidence archive](status-checkpoint-expiry-evidence.md). From 939aa261933133a47b45f5867f7f2eabadc69e3c Mon Sep 17 00:00:00 2001 From: 7layermagik <7layermagik@users.noreply.github.com> Date: Tue, 15 Sep 2026 20:57:11 -0500 Subject: [PATCH 16/16] replay: rebind disappeared status groups and randomize encoding checks --- docs/transaction-status-publication.md | 2 +- pkg/replay/transaction_status_cache.go | 9 +++-- pkg/replay/transaction_status_capture_test.go | 35 +++++++++++++++++++ ...ction_status_publication_benchmark_test.go | 3 ++ .../transaction_status_publication_test.go | 21 +++++++++++ 5 files changed, 67 insertions(+), 3 deletions(-) diff --git a/docs/transaction-status-publication.md b/docs/transaction-status-publication.md index c74be8335..68ca309fa 100644 --- a/docs/transaction-status-publication.md +++ b/docs/transaction-status-publication.md @@ -4,7 +4,7 @@ Replay previously built the immutable per-bank transaction-status delta and grew Count identities by recent blockhash and allocate each delta map at its final capacity. Pre-size newly created visible maps too. For banks with more than 32 transactions and GOMAXPROCS greater than one, prepare the immutable delta during account loading and execution. Smaller banks and single-thread configurations keep the work inline. There is at most one preparation task per ProcessBlock call, and every return joins it, including rejected banks. No status becomes visible during preparation. -The worker reads immutable prepared message identities and briefly snapshots only blockhash slice offsets under the cache read lock. It builds its private maps outside the lock. Commit checks exact block/identity binding, complete coverage and parent lineage under the publication lock. It rechecks ancestor duplicates unless the successful pre-execution validation belongs to the same cache instance, immutable identity set and unchanged cache version (see below). A changed slice offset or mismatched preparation triggers a rebuild from the actual block's identities. Publication still happens only after successful bank-state commit. Failed instructions within an accepted bank remain processed; rejected banks publish nothing. Pinned views, snapshots, reference counts and unwind keep their existing semantics. +The worker reads immutable prepared message identities and briefly snapshots only blockhash slice offsets under the cache read lock. It builds its private maps outside the lock. Commit checks exact block/identity binding, complete coverage and parent lineage under the publication lock. It rechecks ancestor duplicates unless the successful pre-execution validation belongs to the same cache instance, immutable identity set and unchanged cache version (see below). A changed slice offset, disappearance of a previously nonzero-offset group, or mismatched preparation triggers a rebuild from the actual block's identities. Publication still happens only after successful bank-state commit. Failed instructions within an accepted bank remain processed; rejected banks publish nothing. Pinned views, snapshots, reference counts and unwind keep their existing semantics. TransactionStatusPreparation measures worker wall time, which overlaps execution; it is not additive with replay wall time. TransactionStatusPreparationWait measures the residual join and is nested inside TransactionStatusCommit. The latter still includes waiting, final checks, visible-index updates and node publication. Preparation time excludes initial goroutine scheduling delay; any residual scheduling delay remains in the join/commit timer. diff --git a/pkg/replay/transaction_status_cache.go b/pkg/replay/transaction_status_cache.go index bb4a157fb..d17110491 100644 --- a/pkg/replay/transaction_status_cache.go +++ b/pkg/replay/transaction_status_cache.go @@ -664,9 +664,14 @@ func (c *TransactionStatusCache) commitBlockWithValidation(block *b.Block, plan if prepared != nil && prepared.identities == plan.messageIdentities { delta = prepared.delta // A restore or branch transition can change a blockhash's slice offset. - // Rebuild from full identities if any current group uses another offset. + // Rebuild from full identities if a group changed offset or disappeared; + // a missing group uses the same zero offset as fresh preparation. for blockhash, group := range delta { - if visible := c.visible[blockhash]; visible != nil && visible.keyIndex != group.keyIndex { + index := uint8(0) + if visible := c.visible[blockhash]; visible != nil { + index = visible.keyIndex + } + if index != group.keyIndex { delta = nil break } diff --git a/pkg/replay/transaction_status_capture_test.go b/pkg/replay/transaction_status_capture_test.go index 8e1344c6f..34b8273af 100644 --- a/pkg/replay/transaction_status_capture_test.go +++ b/pkg/replay/transaction_status_capture_test.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/binary" "fmt" + "math/rand" "sort" "sync" "testing" @@ -297,3 +298,37 @@ func TestTransactionStatusEncodingMatchesOriginalWireFormat(t *testing.T) { } } } + +func TestTransactionStatusEncodingRandomizedByteIdentity(t *testing.T) { + rng := rand.New(rand.NewSource(20260916)) + for trial := 0; trial < 200; trial++ { + nodes := make([]*transactionStatusNode, rng.Intn(13)) + var slot uint64 + for i := range nodes { + slot += uint64(1 + rng.Intn(5)) + node := &transactionStatusNode{slot: slot, hasBlockID: rng.Intn(2) == 1, delta: make(transactionStatusDelta)} + _, _ = rng.Read(node.blockID[:]) + for g := rng.Intn(8); g > 0; g-- { + var hash solana.Hash + _, _ = rng.Read(hash[:]) + group := &transactionStatusGroup{keyIndex: uint8(rng.Intn(int(txstatus.MaxCachedKeyIndex) + 1)), keys: make(map[transactionStatusKey]struct{})} + for k := rng.Intn(13); k > 0; k-- { + var key transactionStatusKey + _, _ = rng.Read(key[:]) + group.keys[key] = struct{}{} + } + node.delta[hash] = group + } + nodes[i] = node + } + rooted := uint16(rng.Intn(301)) + complete, genesis := rng.Intn(2) == 1, rng.Intn(2) == 1 + want, err := marshalTransactionStatusNodesUncached(nodes, rooted, complete, genesis) + require.NoError(t, err) + for pass := 0; pass < 2; pass++ { + got, err := marshalTransactionStatusNodes(nodes, rooted, complete, genesis) + require.NoError(t, err) + require.Equal(t, want, got, "trial=%d pass=%d", trial, pass) + } + } +} diff --git a/pkg/replay/transaction_status_publication_benchmark_test.go b/pkg/replay/transaction_status_publication_benchmark_test.go index 2fc7d0f45..2151ef330 100644 --- a/pkg/replay/transaction_status_publication_benchmark_test.go +++ b/pkg/replay/transaction_status_publication_benchmark_test.go @@ -63,6 +63,9 @@ func (c *TransactionStatusCache) legacyCommitStatusForBenchmark(block *b.Block, // Frozen production commit algorithm before publication optimization. This is // an independent baseline, including its original visible-index allocation. +// Benchmark fixtures never reuse validation receipts on this path. This frozen +// helper deliberately omits validation-version bumps and must not be used by +// production callers or copied as a model for mutating the live cache. func (c *TransactionStatusCache) legacyAddStatusForBenchmark(delta transactionStatusDelta) error { for blockhash, deltaGroup := range delta { if group := c.visible[blockhash]; group != nil && group.keyIndex != deltaGroup.keyIndex { diff --git a/pkg/replay/transaction_status_publication_test.go b/pkg/replay/transaction_status_publication_test.go index 58a24b06f..3b9b92885 100644 --- a/pkg/replay/transaction_status_publication_test.go +++ b/pkg/replay/transaction_status_publication_test.go @@ -131,3 +131,24 @@ func TestPreparedStatusDeltaScheduling(t *testing.T) { } } } + +func TestPreparedStatusDeltaRebindsMissingSnapshotGroup(t *testing.T) { + ancestor := statusCacheTestTransaction(1, 2, 3) + seed, err := NewTransactionStatusCacheFromAgaveSnapshot([]txstatus.SnapshotSlotDelta{ + {Slot: 0, IsRoot: true, Statuses: []txstatus.SnapshotStatus{snapshotStatusCacheStatusForTx(t, ancestor, 7)}}, + }, 0) + require.NoError(t, err) + candidate := statusCacheTestBlock(1, statusCacheTestTransaction(1, 4, 5)) + plan, err := planBlockTransactionExecution(candidate) + require.NoError(t, err) + prepared := seed.prepareTransactionStatusDelta(plan.messageIdentities) + // Model restore/branch replacement removing the group after preparation. + cache := NewTransactionStatusCache() + require.NoError(t, cache.commitBlockWithPreparedDelta(candidate, plan, prepared)) + inline := NewTransactionStatusCache() + require.NoError(t, inline.commitBlockWithPlan(candidate, plan)) + require.Equal(t, inline.tip.delta, cache.tip.delta) + found, err := cache.View().ContainsTransaction(candidate.Transactions[0]) + require.NoError(t, err) + require.True(t, found) +}