mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-08 23:18:15 -05:00
Optimize BuildBlobSidecars Merkle proof computation by pre-computing subtrees (#15473)
* Optimize BuildBlobSidecars Merkle proof computation by pre-computing subtrees Co-Authored-By: Claude <noreply@anthropic.com> * Add change log * Fix change log --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -28,8 +28,14 @@ func BuildBlobSidecars(blk interfaces.SignedBeaconBlock, blobs [][]byte, kzgProo
|
||||
return nil, err
|
||||
}
|
||||
body := blk.Block().Body()
|
||||
// Pre-compute subtrees once before the loop to avoid redundant calculations
|
||||
proofComponents, err := blocks.PrecomputeMerkleProofComponents(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range blobSidecars {
|
||||
proof, err := blocks.MerkleProofKZGCommitment(body, i)
|
||||
proof, err := blocks.MerkleProofKZGCommitmentFromComponents(proofComponents, i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
fieldparams "github.com/OffchainLabs/prysm/v6/config/fieldparams"
|
||||
"github.com/OffchainLabs/prysm/v6/consensus-types/blocks"
|
||||
"github.com/OffchainLabs/prysm/v6/consensus-types/interfaces"
|
||||
"github.com/OffchainLabs/prysm/v6/encoding/bytesutil"
|
||||
ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v6/runtime/version"
|
||||
"github.com/OffchainLabs/prysm/v6/testing/util"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
)
|
||||
|
||||
// BuildBlobSidecarsOriginal is the original implementation for comparison
|
||||
func BuildBlobSidecarsOriginal(blk interfaces.SignedBeaconBlock, blobs [][]byte, kzgProofs [][]byte) ([]*ethpb.BlobSidecar, error) {
|
||||
if blk.Version() < version.Deneb {
|
||||
return nil, nil // No blobs before deneb.
|
||||
}
|
||||
commits, err := blk.Block().Body().BlobKzgCommitments()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cLen := len(commits)
|
||||
if cLen != len(blobs) || cLen != len(kzgProofs) {
|
||||
return nil, errors.New("blob KZG commitments don't match number of blobs or KZG proofs")
|
||||
}
|
||||
blobSidecars := make([]*ethpb.BlobSidecar, cLen)
|
||||
header, err := blk.Header()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body := blk.Block().Body()
|
||||
for i := range blobSidecars {
|
||||
proof, err := blocks.MerkleProofKZGCommitment(body, i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blobSidecars[i] = ðpb.BlobSidecar{
|
||||
Index: uint64(i),
|
||||
Blob: blobs[i],
|
||||
KzgCommitment: commits[i],
|
||||
KzgProof: kzgProofs[i],
|
||||
SignedBlockHeader: header,
|
||||
CommitmentInclusionProof: proof,
|
||||
}
|
||||
}
|
||||
return blobSidecars, nil
|
||||
}
|
||||
|
||||
func setupBenchmarkData(b *testing.B, numBlobs int) (interfaces.SignedBeaconBlock, [][]byte, [][]byte) {
|
||||
b.Helper()
|
||||
|
||||
// Create KZG commitments
|
||||
kzgCommitments := make([][]byte, numBlobs)
|
||||
for i := 0; i < numBlobs; i++ {
|
||||
kzgCommitments[i] = bytesutil.PadTo([]byte{byte(i)}, 48)
|
||||
}
|
||||
|
||||
// Create block with KZG commitments
|
||||
blk, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlockDeneb())
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if err := blk.SetBlobKzgCommitments(kzgCommitments); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
// Create blobs
|
||||
blobs := make([][]byte, numBlobs)
|
||||
for i := 0; i < numBlobs; i++ {
|
||||
blobs[i] = make([]byte, fieldparams.BlobLength)
|
||||
// Add some variation to the blob data
|
||||
blobs[i][0] = byte(i)
|
||||
}
|
||||
|
||||
// Create KZG proofs
|
||||
proof, err := hexutil.Decode("0xb4021b0de10f743893d4f71e1bf830c019e832958efd6795baf2f83b8699a9eccc5dc99015d8d4d8ec370d0cc333c06a")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
kzgProofs := make([][]byte, numBlobs)
|
||||
for i := 0; i < numBlobs; i++ {
|
||||
kzgProofs[i] = proof
|
||||
}
|
||||
|
||||
return blk, blobs, kzgProofs
|
||||
}
|
||||
|
||||
func BenchmarkBuildBlobSidecars_Original_1Blob(b *testing.B) {
|
||||
blk, blobs, kzgProofs := setupBenchmarkData(b, 1)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := BuildBlobSidecarsOriginal(blk, blobs, kzgProofs)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBuildBlobSidecars_Optimized_1Blob(b *testing.B) {
|
||||
blk, blobs, kzgProofs := setupBenchmarkData(b, 1)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := BuildBlobSidecars(blk, blobs, kzgProofs)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBuildBlobSidecars_Original_2Blobs(b *testing.B) {
|
||||
blk, blobs, kzgProofs := setupBenchmarkData(b, 2)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := BuildBlobSidecarsOriginal(blk, blobs, kzgProofs)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBuildBlobSidecars_Optimized_3Blobs(b *testing.B) {
|
||||
blk, blobs, kzgProofs := setupBenchmarkData(b, 3)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := BuildBlobSidecars(blk, blobs, kzgProofs)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBuildBlobSidecars_Original_3Blobs(b *testing.B) {
|
||||
blk, blobs, kzgProofs := setupBenchmarkData(b, 3)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := BuildBlobSidecarsOriginal(blk, blobs, kzgProofs)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBuildBlobSidecars_Optimized_4Blobs(b *testing.B) {
|
||||
blk, blobs, kzgProofs := setupBenchmarkData(b, 4)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := BuildBlobSidecars(blk, blobs, kzgProofs)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBuildBlobSidecars_Original_9Blobs(b *testing.B) {
|
||||
blk, blobs, kzgProofs := setupBenchmarkData(b, 9)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := BuildBlobSidecarsOriginal(blk, blobs, kzgProofs)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBuildBlobSidecars_Optimized_9Blobs(b *testing.B) {
|
||||
blk, blobs, kzgProofs := setupBenchmarkData(b, 9)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := BuildBlobSidecars(blk, blobs, kzgProofs)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark the individual components to understand where the improvements come from
|
||||
func BenchmarkMerkleProofKZGCommitment_Original(b *testing.B) {
|
||||
blk, _, _ := setupBenchmarkData(b, 4)
|
||||
body := blk.Block().Body()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for j := 0; j < 4; j++ {
|
||||
_, err := blocks.MerkleProofKZGCommitment(body, j)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMerkleProofKZGCommitment_Optimized(b *testing.B) {
|
||||
blk, _, _ := setupBenchmarkData(b, 4)
|
||||
body := blk.Block().Body()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Pre-compute components once
|
||||
components, err := blocks.PrecomputeMerkleProofComponents(body)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
// Generate proofs for each index
|
||||
for j := 0; j < 4; j++ {
|
||||
_, err := blocks.MerkleProofKZGCommitmentFromComponents(components, j)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
changelog/tt_sushi.md
Normal file
3
changelog/tt_sushi.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Changed
|
||||
|
||||
- Optimize proposer inclusion proof calcuation by pre caching subtries
|
||||
@@ -25,6 +25,12 @@ var (
|
||||
errInvalidInclusionProof = errors.New("invalid KZG commitment inclusion proof")
|
||||
)
|
||||
|
||||
// MerkleProofComponents contains pre-computed components for efficient proof generation
|
||||
type MerkleProofComponents struct {
|
||||
kzgSubtree *trie.SparseMerkleTrie
|
||||
topLevelProof [][]byte
|
||||
}
|
||||
|
||||
// VerifyKZGInclusionProof verifies the Merkle proof in a Blob sidecar against
|
||||
// the beacon block body root.
|
||||
func VerifyKZGInclusionProof(blob ROBlob) error {
|
||||
@@ -80,6 +86,67 @@ func MerkleProofKZGCommitment(body interfaces.ReadOnlyBeaconBlockBody, index int
|
||||
return proof, nil
|
||||
}
|
||||
|
||||
// PrecomputeMerkleProofComponents pre-computes the expensive parts of Merkle proof generation
|
||||
// that are shared across all blob indices for a given block body.
|
||||
func PrecomputeMerkleProofComponents(body interfaces.ReadOnlyBeaconBlockBody) (*MerkleProofComponents, error) {
|
||||
bodyVersion := body.Version()
|
||||
if bodyVersion < version.Deneb {
|
||||
return nil, errUnsupportedBeaconBlockBody
|
||||
}
|
||||
|
||||
// Pre-compute KZG subtree
|
||||
commitments, err := body.BlobKzgCommitments()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// No work needed if there are no commitments
|
||||
if len(commitments) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
leaves := LeavesFromCommitments(commitments)
|
||||
kzgSubtree, err := trie.GenerateTrieFromItems(leaves, field_params.LogMaxBlobCommitments)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Pre-compute top-level components
|
||||
membersRoots, err := topLevelRoots(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
topLevelTrie, err := trie.GenerateTrieFromItems(membersRoots, logBodyLength)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
topLevelProof, err := topLevelTrie.MerkleProof(kzgPosition)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Remove the last element that is not needed in topProof
|
||||
topLevelProof = topLevelProof[:len(topLevelProof)-1]
|
||||
|
||||
return &MerkleProofComponents{
|
||||
kzgSubtree: kzgSubtree,
|
||||
topLevelProof: topLevelProof,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MerkleProofKZGCommitmentFromComponents constructs a Merkle proof for a specific index
|
||||
// using pre-computed components, avoiding redundant calculations.
|
||||
func MerkleProofKZGCommitmentFromComponents(components *MerkleProofComponents, index int) ([][]byte, error) {
|
||||
// Generate index-specific proof from pre-computed KZG subtree
|
||||
subtreeProof, err := components.kzgSubtree.MerkleProof(index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Combine with pre-computed top-level proof
|
||||
proof := append(subtreeProof, components.topLevelProof...)
|
||||
return proof, nil
|
||||
}
|
||||
|
||||
// MerkleProofKZGCommitments constructs a Merkle proof of inclusion of the KZG
|
||||
// commitments into the Beacon Block with the given `body`
|
||||
func MerkleProofKZGCommitments(body interfaces.ReadOnlyBeaconBlockBody) ([][]byte, error) {
|
||||
|
||||
Reference in New Issue
Block a user