perf(saevm): Implement block.BatchedChainVM - #5788
Draft
StephenButtolph wants to merge 17 commits into
Draft
Conversation
block.BatchedChainVM
StephenButtolph
commented
Aug 11, 2026
Comment on lines
+114
to
+133
| // BatchedParseBlock parses each block in its own goroutine, returning an error | ||
| // if any of the blocks fail to parse. | ||
| func (vm adaptor[BP]) BatchedParseBlock(ctx context.Context, blocksBytes [][]byte) ([]snowman.Block, error) { | ||
| var ( | ||
| eg errgroup.Group | ||
| parsed = make([]snowman.Block, len(blocksBytes)) | ||
| ) | ||
| eg.SetLimit(runtime.GOMAXPROCS(0)) | ||
| for i, buf := range blocksBytes { | ||
| eg.Go(func() error { | ||
| b, err := vm.ParseBlock(ctx, buf) | ||
| parsed[i] = b | ||
| return err | ||
| }) | ||
| } | ||
| if err := eg.Wait(); err != nil { | ||
| return nil, err | ||
| } | ||
| return parsed, nil | ||
| } |
Contributor
Author
There was a problem hiding this comment.
This is implemented here rather than as a pass-through because we want the C-chain's version to call it's overridden ParseBlock function. We could alternatively have re-implemented this in the C-Chain code... But that's silly.
Comment on lines
+29
to
+35
| GetAncestors( | ||
| ctx context.Context, | ||
| blkID ids.ID, | ||
| maxBlocksNum int, | ||
| maxBlocksSize int, | ||
| timeout time.Duration, | ||
| ) ([][]byte, error) |
Contributor
Author
There was a problem hiding this comment.
GetAncestors really needs named arguments. For consistently I named all the arguments on this interface... Although could go back to just the types for the other methods if people care.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why this should be merged
This reduces the time for
GetAncestorsto be served by the C-Chain on Fuji by~x%(TODO: Fill me in).For the cchainvm specifically, this reduces the time for
GetAncestorsto be served by the saevm by~97%. It also reduces the time forBatchedParseBlockto be served by the saevm by~80%.For
BatchedParseBlock, this implementation is only faster (on my machine) when at least 4 blocks are provided.For
GetAncestors, this implementation always significantly outperforms the current behavior.How this works
VMs can optionally implement
block.BatchedChainVMwhich is preferred by the consensus engine over repeatedly callingVM.GetBlockto serveGetAncestorsrequests.Because of VM wrapping, it isn't enough just for the base VM to implement this optional interface - each wrapping VM must implement it. This VM wrapping was originally added to avoid multiple gRPC calls, so the tracedvm, metervm, proposervm, and rpcchainvm implemented this interface. However, the avm, platformvm, coreth, and subnet-evm didn't implement it. When adding cchainvm and transitionvm, we didn't implement this interface either.
By implementing this interface in transitionvm and cchainvm, the propservm will serve its optimized path, improving the performance of both pre-proposervm and post-proposervm block serving. A TODO is added to use this optimized path for post-proposervm block serving without needing the underlying VM to implement
block.BatchedChainVM.For pre-proposervm_ blocks, this delegates the implementation to the cchainvm directly. In the cchainvm, for
BatchedParseBlock, we just throw the operation into multiple goroutines. ForGetAncestors:We avoid calling
VM.GetBlock. This function currently falls back tovm.settledBlockFromDBfor blocks on disk - which callsrawdb.ReadBlockandblocks.RestoreSettledBlock.blocks.RestoreSettledBlockis slow because it restores the execution artifacts. Which includes reading the execution results DB, reading the receipts from disk, and deriving the receipts (which can even perform signature verification) - in addition to various unnecessary memory allocations. This buys us an~66%performance improvement.rawdb.ReadBlockis slow because it 2xrlp.DecodeBytes+ does a bunch of useless copies to get a block, only for us to immediatelyrlp.EncodeToBytes. We instead usetypes.BlockBytesto stitch the header and body bytes together to get the block bytes with only a single allocation. This buys us an other~90%performance improvement.These combine to the
~97%performance improvement.A TODO is left to investigate a further
~66%performance improvement around using DB iterators, but I worry that we are overly focusing on this micro-benchmark rather than testing in a real system before we make this implementation (significantly) more complex. Additionally, this code-path is only hit withpre-proposervmblocks.How this was tested
Need to be documented in RELEASES.md?
No