Commit fa7b5dc
Optimize JSON-RPC request parsing and processing (#10453)
* Add encoded-tx root calc and trie decode perf
Allow computing the transactions trie root directly from RLP-encoded transactions and update callers to use it. ExecutionPayload.TryGetBlock now passes the encoded Transactions to TxTrie.CalculateRoot. Implement TxTrie.CalculateRoot(ReadOnlySpan<byte[]>) and InitializeFromEncodedTransactions to populate the trie from encoded payloads and compute RootHash; use a TrackingCappedArrayPool and UpdateRootHash. Add a unit test to assert encoded and decoded transaction paths produce the same root. Also refine trie node RLP encoding parallelization: UseParallel now checks the node's non-null child count (only parallelize when >= 4 children and multiple CPU cores) to avoid parallel overhead on small branches.
* Fast block re-encode
* Feedback
* Cache TokenValidationParameters as readonly field
Move TokenValidationParameters construction from per-request AuthenticateCore
to the constructor, eliminating repeated allocation on cache-miss auth path.
* Implement manual HS256 JWT fast validator with library fallback
Add zero-allocation manual JWT validation for known HS256 header formats.
Uses HMACSHA256.HashData (static) and CryptographicOperations.FixedTimeEquals
for signature verification. Handles iat and exp claims. Falls back to
Microsoft.IdentityModel for unrecognized header formats.
* Refactor ByteArrayConverter write path
Replace WriteRawValue with WriteStringValue(ReadOnlySpan<byte>) on the main
value-write path, eliminating manual quote handling. Raise stackalloc
threshold to 256 bytes. Use "0x0"u8 literal for zero-value fast path.
Delegate-based overload retained for property name writes.
* Add fixed-size fast path for 32-byte hash converters
Hash256Converter and ValueHash256Converter now use a dedicated 66-byte
stackalloc path (0x + 64 hex chars) that skips CountLeadingNibbleZeros,
ArrayPool, and the generic ByteArrayConverter write path.
* Direct numeric-to-hex for Long, ULong, UInt256 converters
Use BitOperations.LeadingZeroCount and nibble-extraction loop to write hex
directly without byte array intermediary. UInt256 uses LZCNT on limbs.
All paths use WriteStringValue instead of WriteRawValue. Zero values use
"0x0"u8 literal. ZeroPaddedHex uses fixed 66-byte stackalloc path.
* Replace ForcedNumberConversion AsyncLocal with ThreadStatic cache
GetFinalConversion() now reads from a ThreadStatic cache instead of
AsyncLocal on every call. The ThreadAwareAsyncLocal wrapper updates both
the AsyncLocal and ThreadStatic when Value is set, maintaining backward
compatibility with tracing/debug converters.
* Pool CancellationTokenSource with TryReset
BuildTimeoutCancellationToken now rents from a ConcurrentBag pool.
TryReset() returns CTS to pool on completion. Debugger-attached path
skips pooling. Eliminates per-request CTS allocation on hot path.
* Increase StreamPipeWriter minimumBufferSize from 4KB to 16KB
Reduces Grow calls during serialization of typical Engine API responses
(~5-20KB). Tradeoff: slightly higher per-connection memory baseline.
* Bypass BufferResponses for Engine API + call StartAsync before body
Authenticated single responses (Engine API) skip RecyclableStream
double-copy. Call StartAsync() before serialization to flush headers
early and avoid PinnedBlockMemoryPool.Rent allocations.
* Replace JsonRpcContext.Current AsyncLocal with ThreadStatic
Replace AsyncLocal<JsonRpcContext?> with [ThreadStatic] field and a
ThreadStaticAccessor wrapper that preserves the .Value API shape. This
eliminates the ExecutionContext capture cost on every JSON-RPC request.
* Use synchronous JsonSerializer.Serialize for PipeWriter path
Replace JsonSerializer.SerializeAsync(PipeWriter, ...) with synchronous
Utf8JsonWriter + JsonSerializer.Serialize. This eliminates the async
state machine allocation since PipeWriter implements IBufferWriter<byte>
and data is flushed by the caller's CompleteAsync().
* Eliminate CTS for Engine API path
Skip per-request CancellationTokenSource allocation for authenticated
Engine API requests. They use CancellationToken.None since they are from
trusted consensus clients and must complete for consensus. Connection
drops are handled by the PipeReader. Non-engine paths still use the
pooled CTS from T08.
* Engine API terminal middleware before routing/CORS
Add an early-pipeline middleware that intercepts authenticated engine
port POST requests before routing, CORS, response compression, and
WebSocket middleware. Engine API requests are handled directly, writing
to the response body without buffering. Non-engine requests pass through
to the standard middleware pipeline.
* Skip CountingPipeReader when Content-Length is known
In the engine API fast lane, use ctx.Request.BodyReader directly and
Content-Length for metrics, eliminating CountingPipeReader overhead.
In the standard handler, skip CountingPipeReader when Content-Length
is available and fall back to it only for chunked requests.
* Seal hot classes and capture concrete types
Seal EthereumJsonSerializer, JsonRpcProcessor, and JsonRpcService (no
subclasses exist). In Startup.cs, cast DI-resolved interfaces to their
concrete types and use those in middleware closures, enabling JIT
devirtualization of hot-path method calls.
* Micro-optimizations in request processing
- Replace LINQ Select in batch deserialization with explicit loop
- Use TryGetDecimal instead of GetRawText for numeric JSON-RPC id parsing
- Replace ElementAtOrDefault with bounds-checked indexed access in LogRequest
- Intern known engine method names via ValueEquals to avoid string allocation
- Remove unused System.Linq using from JsonRpcProcessor
* Startup warmup for serializer metadata
Add EthereumJsonSerializer.WarmupSerializer() that pre-serializes
instances to populate System.Text.Json metadata caches. Call it at
startup with JsonRpcSuccessResponse and JsonRpcErrorResponse to
eliminate cold-start serialization overhead on first engine request.
* Move log interpolation to NoInlining local/static functions
Keep string interpolation out of hot paths by moving log bodies to
[MethodImpl(MethodImplOptions.NoInlining)] functions so the JIT
does not inline the interpolation into callers.
* STJ source generation for engine, eth, debug, and trace API types
Add source-generated JsonSerializerContext instances for all major RPC
type families to eliminate reflection-based metadata lookup on hot paths.
- EngineApiJsonContext: 19 engine API types (payloads, forkchoice, blobs)
- FacadeJsonContext: BlockForRpc, TransactionForRpc, FilterLog, SyncingResult
- EthRpcJsonContext: receipts, fee history, account proofs, plus debug/trace
types (GethLikeTxTrace, ParityTxTraceFromStore, ChainLevelForRpc, etc.)
- JsonRpcResponseJsonContext: success/error response envelope types
Infrastructure changes:
- AddTypeInfoResolver() on EthereumJsonSerializer with version-tracked
propagation to all existing serializer instances
- Cache JsonTypeInfo on ExpectedParameter for typed deserialization
- WriteJsonRpcResponse() in Startup.cs for typed response serialization
- Add [JsonIgnore] to Span properties on Hash256, Bloom, Signature to
prevent SYSLIB1225 source generator errors
* Optimize JwtAuthentication
* Optimize ByteArrayConverter
* Test fix
* Spelling
* Optimize LongConverter
* Spell
* Optimize Hash256Converter
* Optimize UInt256Converter
* Fix tests
* Spelling
* Feedback
* Optimize
* Optimize
* Spell
* Also Avx512
* Formatting
* Spell
* Drop the weird pattern matching
* Use concurrentqueue instead
* Stream blobs directly
Before (3 copies of hex data):
1. ByteArrayConverter → hex-encode into rented ArrayPool<byte> buffer (262KB)
2. Utf8JsonWriter.WriteRawValue → memcpy into Utf8JsonWriter's internal buffer (262KB)
3. Utf8JsonWriter flush → memcpy into PipeWriter/Kestrel send buffer (262KB)
After (1 copy):
1. OutputBytesToByteHex → hex-encode directly into writer.GetSpan() (Kestrel's send buffer)
So copies 2 and 3 are eliminated. Copy 1 (the binary→hex transform) remains
* formatting
* Spell
* Tidy up
* tidy up
* Feeedback
* Feedback
* Reduce contention in trie root hashing
Replace lock+List in TrackingCappedArrayPool with ConcurrentQueue for
parallel paths and bare List for sequential paths. Skip parallel root
hashing for small tries (<=64 items) to avoid scheduling overhead.
Fix race condition where ReceiptTrie and TxTrie called UpdateRootHash
without propagating canBeParallel, causing concurrent List.Add on a
non-thread-safe collection.
* Feedback
* Run newPayload inline (#10479)
* Skip response compression for Engine API requests
* Optimize engine_getBlobsV2 with fused batch lookup and zero-copy proofs
Replace N+1 lock acquisitions with a single fused TryGetBlobsAndProofsV1
that atomically counts and extracts blobs under one lock. Use
ReadOnlyMemory<byte[]> to window into wrapper.Proofs arrays instead of
copying via Slice+spread, eliminating ~0.4MB of proof allocations per
request. Replace ArrayPoolList with parallel arrays, removing the pool
rent/return overhead.
* Split blob lookups into two phases to reduce lock hold time (#10173)
Add ITxStorage.TryGetMany for batched RocksDB MultiGet and override
TryGetBlobsAndProofsV1 in PersistentBlobTxDistinctSortedPool with a
two-phase approach: fast in-memory + cache lookups under lock, then a
single batched DB read outside the lock. This avoids holding the pool's
McsLock during potentially slow I/O for up to 128 blobs per request.
Co-Authored-By: Lukasz Rozmej <lukasz@nethermind.io>
* Remove dead GetBlobCounts method (#10159)
No longer called after getBlobsV2 switched to batched
TryGetBlobsAndProofsV1.
Co-Authored-By: Marcin Sobczak <77129288+marcindsobczak@users.noreply.github.com>
* Feedback
* Spelling
* Add some const to JwtAuthentication to understand the code better
* Consolidate duplicated JSON-RPC request processing pipelines in Startup
Extract shared ProcessJsonRpcRequestCoreAsync and PushErrorResponseAsync
instance methods from the engine API fast lane and standard handler,
eliminating ~100 lines of duplicated processing logic. Unify status code
constants, auth error handling, and add streamable response support to
the standard path.
* Make BlobsV2DirectResponse enumerator explicit; pool byte[64] keys in BlobTxStorage
- Convert GetEnumerator to explicit interface implementation since it is
only used by tests via IEnumerable<T> cast
- Add ConcurrentQueue-based pool for exact-size byte[64] DB lookup keys
in TryGetMany to avoid per-call allocations
---------
Co-authored-by: Lukasz Rozmej <lukasz@nethermind.io>
Co-authored-by: Marcin Sobczak <77129288+marcindsobczak@users.noreply.github.com>
Co-authored-by: lukasz.rozmej <lukasz.rozmej@gmail.com>1 parent fd73930 commit fa7b5dc
69 files changed
Lines changed: 3266 additions & 688 deletions
File tree
- src/Nethermind
- Nethermind.Api
- Nethermind.Blockchain.Test
- Proofs
- Nethermind.Consensus/Processing
- Nethermind.Core.Test/Json
- Nethermind.Core
- Authentication
- Crypto
- JsonConverters
- Nethermind.Facade/Eth
- Nethermind.JsonRpc
- Data
- Nethermind.Merge.Plugin.Test
- Nethermind.Merge.Plugin
- Data
- Handlers
- Nethermind.Runner.Test/Ethereum
- Steps
- Nethermind.Runner
- Ethereum
- Api
- Modules
- JsonRpc
- Nethermind.Serialization.Json
- Nethermind.State/Proofs
- Nethermind.Trie
- Nethermind.TxPool.Test
- Nethermind.TxPool
- Collections
- Nethermind.Xdc.Test/Helpers
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
50 | 50 | | |
51 | 51 | | |
52 | 52 | | |
| 53 | + | |
53 | 54 | | |
54 | 55 | | |
55 | 56 | | |
| |||
160 | 161 | | |
161 | 162 | | |
162 | 163 | | |
| 164 | + | |
163 | 165 | | |
164 | 166 | | |
165 | 167 | | |
| |||
199 | 201 | | |
200 | 202 | | |
201 | 203 | | |
| 204 | + | |
202 | 205 | | |
203 | 206 | | |
204 | 207 | | |
| |||
327 | 330 | | |
328 | 331 | | |
329 | 332 | | |
| 333 | + | |
330 | 334 | | |
331 | 335 | | |
332 | 336 | | |
| |||
404 | 408 | | |
405 | 409 | | |
406 | 410 | | |
| 411 | + | |
407 | 412 | | |
408 | 413 | | |
409 | 414 | | |
| |||
416 | 421 | | |
417 | 422 | | |
418 | 423 | | |
| 424 | + | |
419 | 425 | | |
420 | 426 | | |
421 | 427 | | |
| |||
461 | 467 | | |
462 | 468 | | |
463 | 469 | | |
| 470 | + | |
464 | 471 | | |
465 | 472 | | |
466 | 473 | | |
| |||
577 | 584 | | |
578 | 585 | | |
579 | 586 | | |
| 587 | + | |
580 | 588 | | |
581 | 589 | | |
582 | 590 | | |
| |||
706 | 714 | | |
707 | 715 | | |
708 | 716 | | |
| 717 | + | |
709 | 718 | | |
710 | 719 | | |
711 | 720 | | |
| |||
810 | 819 | | |
811 | 820 | | |
812 | 821 | | |
| 822 | + | |
| 823 | + | |
813 | 824 | | |
| 825 | + | |
814 | 826 | | |
815 | 827 | | |
816 | 828 | | |
| |||
834 | 846 | | |
835 | 847 | | |
836 | 848 | | |
| 849 | + | |
837 | 850 | | |
838 | 851 | | |
839 | 852 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
35 | | - | |
| 35 | + | |
36 | 36 | | |
37 | 37 | | |
38 | 38 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
45 | 45 | | |
46 | 46 | | |
47 | 47 | | |
48 | | - | |
| 48 | + | |
49 | 49 | | |
50 | 50 | | |
51 | 51 | | |
| |||
78 | 78 | | |
79 | 79 | | |
80 | 80 | | |
81 | | - | |
| 81 | + | |
82 | 82 | | |
83 | 83 | | |
84 | 84 | | |
| |||
Lines changed: 24 additions & 6 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
284 | 284 | | |
285 | 285 | | |
286 | 286 | | |
287 | | - | |
288 | | - | |
| 287 | + | |
289 | 288 | | |
290 | | - | |
291 | | - | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
292 | 311 | | |
293 | 312 | | |
294 | 313 | | |
| |||
329 | 348 | | |
330 | 349 | | |
331 | 350 | | |
332 | | - | |
333 | | - | |
| 351 | + | |
334 | 352 | | |
335 | 353 | | |
336 | 354 | | |
| |||
Lines changed: 22 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
58 | 58 | | |
59 | 59 | | |
60 | 60 | | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
61 | 83 | | |
62 | 84 | | |
63 | 85 | | |
| |||
Lines changed: 21 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| 10 | + | |
10 | 11 | | |
11 | 12 | | |
12 | 13 | | |
| |||
99 | 100 | | |
100 | 101 | | |
101 | 102 | | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
102 | 123 | | |
103 | 124 | | |
104 | 125 | | |
| |||
Lines changed: 3 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
57 | 57 | | |
58 | 58 | | |
59 | 59 | | |
60 | | - | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
61 | 63 | | |
62 | 64 | | |
63 | 65 | | |
| |||
Lines changed: 65 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
296 | 296 | | |
297 | 297 | | |
298 | 298 | | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
299 | 364 | | |
300 | 365 | | |
301 | 366 | | |
| |||
Lines changed: 63 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
| 4 | + | |
4 | 5 | | |
5 | 6 | | |
6 | 7 | | |
| |||
22 | 23 | | |
23 | 24 | | |
24 | 25 | | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
25 | 88 | | |
26 | 89 | | |
0 commit comments