Skip to content

perf(rpc): introduce progressive trace caching - #4080

Open
danielntmd wants to merge 2 commits into
danielntmd/progressive-trace-primitivesfrom
danielntmd/progressive-trace-integration
Open

danielntmd wants to merge 2 commits into
danielntmd/progressive-trace-primitivesfrom
danielntmd/progressive-trace-integration

Conversation

@danielntmd

@danielntmd danielntmd commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

User description

Summary

TraceTransaction currently executes every transaction in a finalized block on a cache miss, even when the requested transaction appears near the beginning.

Workflow

This PR caches successful tracing progress from the beginning of each block (a prefix). Later requests reconstruct the state at the cached point and execute only the remaining transactions through the requested target (the missing suffix). Only one request extends a block at a time, while different blocks can be traced concurrently.

lookup(block, target)
├─ target cached → return, even if an extension is active
├─ extension active
│ ├─ completed → retry lookup
│ └─ caller cancelled → return cancellation error
└─ target missing → acquire lease, reconstruct state, execute missing suffix
├─ success → combine prefix + suffix, publish, wake waiters
└─ failure or panic → preserve record, release lease, wake waiters

Benchmarks

Cold starknet_traceTransaction at 100 VUs

Broad corpus sample size: 1,000
Default size: 200

Block corpus Target Throughput Δ Baseline avg PR avg Latency Δ CPU/request Δ
Broad Beginning +626.67% 7,433.0 ms 1,000.2 ms -86.54% -86.72%
Broad Middle +86.93% 7,512.1 ms 4,013.8 ms -46.57% -46.82%
Broad End -1.87% 7,486.8 ms 7,628.2 ms +1.89% +1.80%
1 transaction Beginning +8.38% 724.5 ms 696.5 ms -3.87% -2.45%
1 transaction Middle -1.03% 710.7 ms 721.1 ms +1.47% +1.04%
1 transaction End -4.00% 681.6 ms 730.0 ms +7.10% +5.61%
10 transactions Beginning +608.77% 7,095.1 ms 805.0 ms -88.65% -86.72%
10 transactions Middle +88.07% 7,100.6 ms 3,693.2 ms -47.99% -47.61%
10 transactions End -0.28% 7,185.9 ms 7,213.5 ms +0.38% +0.63%
>20 transactions Beginning +1,557.25% 14,864.3 ms 778.8 ms -94.76% -94.57%
>20 transactions Middle +86.93% 14,855.5 ms 8,088.9 ms -45.55% -45.37%
>20 transactions End +0.30% 14,971.4 ms 14,973.1 ms +0.01% -0.74%

Interleaved traceTransaction -> traceBlockTransactions

Sample size: 200

Block corpus VUs Throughput Δ Baseline avg PR avg Latency Δ CPU/pair Δ
Broad 1 -4.18% 449.8 ms 469.4 ms +4.36% +5.42%
Broad 50 -2.91% 3,380.7 ms 3,531.7 ms +4.46% +2.11%
1 transaction 1 -0.82% 46.5 ms 47.0 ms +0.93% +1.39%
1 transaction 50 -7.33% 392.5 ms 412.7 ms +5.15% +4.29%
10 transactions 1 -2.93% 591.6 ms 609.4 ms +3.01% +3.81%
10 transactions 50 -0.26% 4,264.5 ms 4,418.0 ms +3.60% +1.09%
>20 transactions 1 -0.65% 1,086.6 ms 1,093.7 ms +0.65% +1.14%
>20 transactions 50 +1.67% 8,646.3 ms 8,971.4 ms +3.76% +0.67%

PR Type

Enhancement


Description

  • Integrate progressive trace caching in RPC handlers.

  • Resume execution state from cached trace prefixes.

  • Support partial block execution for target transactions.

  • Add comprehensive tests for progressive trace functionality.


File Walkthrough

Relevant files
Tests
shared_trace_test.go
Add shared tests for progressive trace caching                     

rpc/shared_trace_test.go

  • Update TestSharedTraceCacheInitialReads test assertions for initial
    reads.
  • Add TestProgressiveTraceEachVersionExtends test for cross-version
    progressive tracing.
  • Register the transaction10 trace method for testing.
+106/-9 
progressive_trace_test.go
Implement dedicated tests for v10 progressive tracing       

rpc/v10/progressive_trace_test.go

  • Introduce dedicated test suite for v10 progressive tracing.
  • Cover edge cases including trace failures, waiter cancellations, and
    state reconstruction.
  • Test initial reads caching behavior and preservation of trace
    prefixes.
+383/-0 
trace_test.go
Update v10 trace tests with valid state diffs                       

rpc/v10/trace_test.go

  • Initialize StateDiff field in mock VM execution results to prevent nil
    pointer panics.
+2/-2     
Enhancement
trace.go
Integrate progressive trace caching in v10 endpoints         

rpc/v10/trace.go

  • Use tracecache.PlanRange to trace partial block suffixes up to a
    target transaction.
  • Update traceBlockWithVM to reconstruct execution state via
    plan.ResumeState.
  • Apply errorIndexOffset to accurately map execution errors.
  • Recombine partial trace results using plan.Combine.
+35/-5   
trace.go
Integrate progressive trace caching in v8 endpoints           

rpc/v8/trace.go

  • Add TransactionTarget support for endpoints to locate requested
    transactions efficiently.
  • Implement tracecache.PlanRange to execute missing transaction
    suffixes.
  • Reconstruct block state iteratively using plan.ResumeState.
  • Offset execution errors relative to the executed transaction range.
+59/-10 
trace.go
Integrate progressive trace caching in v9 endpoints           

rpc/v9/trace.go

  • Refactor endpoints to support target-based progressive block tracing.
  • Employ tracecache utilities to plan ranges, execute prefixes, and
    combine results.
  • Shift VM execution errors correctly based on transaction offset.
+39/-6   

@codecov

codecov Bot commented Sep 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.15789% with 16 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (danielntmd/progressive-trace-primitives@62964ea). Learn more about missing BASE report.

Files with missing lines Patch % Lines
rpc/v8/trace.go 82.92% 7 Missing ⚠️
rpc/v9/trace.go 75.00% 7 Missing ⚠️
rpc/v10/trace.go 92.30% 2 Missing ⚠️
Additional details and impacted files
@@                            Coverage Diff                             @@
##             danielntmd/progressive-trace-primitives    #4080   +/-   ##
==========================================================================
  Coverage                                           ?   79.49%           
==========================================================================
  Files                                              ?      470           
  Lines                                              ?    36345           
  Branches                                           ?        0           
==========================================================================
  Hits                                               ?    28894           
  Misses                                             ?     7442           
  Partials                                           ?        9           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@danielntmd danielntmd changed the title perf(rpc): resume shared trace prefixes in RPC workflows perf(rpc): introduce progressive trace caching Sep 15, 2026
@danielntmd
danielntmd force-pushed the danielntmd/progressive-trace-integration branch from 441385b to b5bbbac Compare September 15, 2026 22:47
@danielntmd
danielntmd marked this pull request as ready for review September 15, 2026 22:47
@github-actions

github-actions Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Review in progress

  • Gather context (PR body, diff, CLAUDE.md)
  • Review rpc/v8/trace.go, rpc/v9/trace.go, rpc/v10/trace.go progressive trace logic
  • Review concurrency safety of lease/wake mechanism
  • Review test coverage (shared_trace_test.go, progressive_trace_test.go)
  • Post inline comments for any issues found
  • Post final summary

View job run

@github-actions

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 5 🔵🔵🔵🔵🔵
⚠️ Risk level: Medium
📂 Priority files

  • rpc/v10/trace.go
  • rpc/v8/trace.go
  • rpc/v9/trace.go
🏅 Score: 62
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Nil plan dereference

traceBlockTransactionWithVM unconditionally calls plan.ResumeState guarded by if plan != nil, but later transactions = transactions[plan.Start:plan.End] and tracecache.OffsetExecutionError(err, plan.Start) and traceTransactionsWithState(..., plan.Start) are called without checking plan != nil first for the pending-block path where plan remains nil (isPending branch skips plan assignment). This will panic with a nil pointer dereference when tracing a pending block, since plan is only set if !isPending.

func (h *Handler) traceBlockTransactionWithVM(block *core.Block, plan *tracecache.Range) (
	*tracecache.BlockTrace, http.Header, *jsonrpc.Error,
) {
	httpHeader := defaultExecutionHeader()
	transactions := block.Transactions
	if plan != nil {
		transactions = transactions[plan.Start:plan.End]
	}
	state, closer, err := h.bcReader.StateAtBlockHash(block.ParentHash)
	if err != nil {
		return nil, httpHeader, rpccore.ErrBlockNotFound
	}
	defer h.callAndLogErr(closer, "Failed to close state in traceBlockTransactions")

	var (
		headState       core.StateReader
		headStateCloser blockchain.StateCloser
	)

	headState, headStateCloser, err = h.bcReader.HeadState()
	if err != nil {
		return nil, httpHeader, jsonrpc.Err(jsonrpc.InternalError, err.Error())
	}
	defer h.callAndLogErr(headStateCloser, "Failed to close head state in traceBlockTransactions")

	if plan != nil {
		state, err = plan.ResumeState(state, headState, block.Number)
		if err != nil {
			return nil, httpHeader, jsonrpc.Err(jsonrpc.InternalError, err.Error())
		}
	}

	var classes []core.ClassDefinition
	paidFeesOnL1 := []*felt.Felt{}

	for _, transaction := range transactions {
		switch tx := transaction.(type) {
		case *core.DeclareTransaction:
			class, stateErr := headState.Class(tx.ClassHash)
			if stateErr != nil {
				return nil, httpHeader, jsonrpc.Err(jsonrpc.InternalError, stateErr.Error())
			}
			classes = append(classes, class.Class)
		case *core.L1HandlerTransaction:
			// TODO (granza): use real L1 message fee.
			paidFeesOnL1 = append(paidFeesOnL1, &felt.One)
		}
	}

	blockHashToBeRevealed, err := h.getRevealedBlockHash(block.Number)
	if err != nil {
		return nil, httpHeader, rpccore.ErrInternal.CloneWithData(err)
	}

	header := block.Header
	blockInfo := vm.BlockInfo{
		Header:                header,
		BlockHashToBeRevealed: blockHashToBeRevealed,
	}

	executionResult, err := h.vm.Trace(transactions, classes, paidFeesOnL1,
		&blockInfo, state, vm.TraceOptions{})

	if plan != nil {
		err = tracecache.OffsetExecutionError(err, plan.Start)
	}
Nil plan dereference on feeder path

In traceFinalisedBlock, plan is only computed inside the else branch (non-feeder path). When fetchFromFeederGW is true, plan stays nil, but the code after the if/else calls plan.Combine(traces) only inside the else block, so that part is fine, but traceBlockWithVM unconditionally dereferences plan.Start/plan.End — this function is only called from the else branch so it should be fine. However, verify that plan cannot be nil in traceBlockWithVM since it directly does transactions[plan.Start:plan.End] without a nil check, differing from v8/v10 pending-block handling; if any future caller passes nil plan this will panic. Confirm all call sites always supply a non-nil plan.

// traceBlockWithVM traces a block using the local VM.
func (h *Handler) traceBlockWithVM(
	header *core.Header,
	transactions []core.Transaction,
	plan *tracecache.Range,
) (*tracecache.BlockTrace, http.Header, *jsonrpc.Error) {
	transactions = transactions[plan.Start:plan.End]
Unconditional plan dereference

traceBlockWithVM in rpc/v10 does transactions[plan.Start:plan.End] without checking plan for nil, unlike v8's pending-block path which passes a nil plan. If traceBlockWithVM is ever invoked with a nil plan (e.g. future refactor or missed initialization), this will panic. Confirm plan is always non-nil for all call sites in this file.

func (h *Handler) traceBlockWithVM(
	header *core.Header,
	transactions []core.Transaction,
	returnInitialReads bool,
	plan *tracecache.Range,
) (*tracecache.BlockTrace, http.Header, *jsonrpc.Error) {
	transactions = transactions[plan.Start:plan.End]

@danielntmd
danielntmd force-pushed the danielntmd/progressive-trace-integration branch from b5bbbac to a557964 Compare September 18, 2026 06:58
@github-actions

github-actions Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Review in progress

  • Gather context (PR body, diff, prior automated review comments)
  • Review rpc/v8/trace.go, rpc/v9/trace.go, rpc/v10/trace.go progressive trace logic
  • Check nil-plan dereference concerns raised by prior automated review
  • Review concurrency safety of lease/wake mechanism (tracecache)
  • Review test coverage (shared_trace_test.go, progressive_trace_test.go)
  • Post inline comments for any issues found
  • Post final summary

View job run

@github-actions

Copy link
Copy Markdown
Contributor

Standalone PR Review

PR-Agent could not safely update the persistent review. This standalone result will not replace the canonical review.

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
⚠️ Risk level: Low
📂 Priority files

  • rpc/v10/trace.go
  • rpc/v9/trace.go
  • rpc/v8/trace.go
🏅 Score: 95
🧪 PR contains tests
🔒 No security concerns identified
⚡ No major issues detected

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant