Skip to content

Commit 6c6c1c1

Browse files
eth/catalyst: implement getBlobsV2
1 parent 14d1269 commit 6c6c1c1

File tree

16 files changed

+478
-55
lines changed

16 files changed

+478
-55
lines changed

beacon/engine/types.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ type BlobAndProofV1 struct {
123123
Proof hexutil.Bytes `json:"proof"`
124124
}
125125

126+
type BlobAndProofV2 struct {
127+
Blob hexutil.Bytes `json:"blob"`
128+
CellProofs []hexutil.Bytes `json:"proofs"`
129+
}
130+
126131
// JSON type overrides for ExecutionPayloadEnvelope.
127132
type executionPayloadEnvelopeMarshaling struct {
128133
BlockValue *hexutil.Big
@@ -331,7 +336,9 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.
331336
for j := range sidecar.Blobs {
332337
bundle.Blobs = append(bundle.Blobs, hexutil.Bytes(sidecar.Blobs[j][:]))
333338
bundle.Commitments = append(bundle.Commitments, hexutil.Bytes(sidecar.Commitments[j][:]))
334-
bundle.Proofs = append(bundle.Proofs, hexutil.Bytes(sidecar.Proofs[j][:]))
339+
}
340+
for _, proof := range sidecar.Proofs {
341+
bundle.Proofs = append(bundle.Proofs, hexutil.Bytes(proof[:]))
335342
}
336343
}
337344

beacon/engine/types_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2025 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package engine
18+
19+
import (
20+
"testing"
21+
22+
"github.com/ethereum/go-ethereum/common"
23+
"github.com/ethereum/go-ethereum/core/types"
24+
"github.com/ethereum/go-ethereum/crypto/kzg4844"
25+
)
26+
27+
func TestBlobs(t *testing.T) {
28+
var (
29+
emptyBlob = new(kzg4844.Blob)
30+
emptyBlobCommit, _ = kzg4844.BlobToCommitment(emptyBlob)
31+
emptyBlobProof, _ = kzg4844.ComputeBlobProof(emptyBlob, emptyBlobCommit)
32+
emptyCellProof, _ = kzg4844.ComputeCells(emptyBlob)
33+
)
34+
header := types.Header{}
35+
block := types.NewBlock(&header, &types.Body{}, nil, nil)
36+
37+
sidecarWithoutCellProofs := &types.BlobTxSidecar{
38+
Blobs: []kzg4844.Blob{*emptyBlob},
39+
Commitments: []kzg4844.Commitment{emptyBlobCommit},
40+
Proofs: []kzg4844.Proof{emptyBlobProof},
41+
}
42+
env := BlockToExecutableData(block, common.Big0, []*types.BlobTxSidecar{sidecarWithoutCellProofs}, nil)
43+
if len(env.BlobsBundle.Proofs) != 1 {
44+
t.Fatalf("Expect 1 proof in blobs bundle, got %v", len(env.BlobsBundle.Proofs))
45+
}
46+
47+
sidecarWithCellProofs := &types.BlobTxSidecar{
48+
Blobs: []kzg4844.Blob{*emptyBlob},
49+
Commitments: []kzg4844.Commitment{emptyBlobCommit},
50+
Proofs: emptyCellProof,
51+
}
52+
env = BlockToExecutableData(block, common.Big0, []*types.BlobTxSidecar{sidecarWithCellProofs}, nil)
53+
if len(env.BlobsBundle.Proofs) != 128 {
54+
t.Fatalf("Expect 128 proofs in blobs bundle, got %v", len(env.BlobsBundle.Proofs))
55+
}
56+
}

core/txpool/blobpool/blobpool.go

Lines changed: 19 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ import (
3636
"github.com/ethereum/go-ethereum/core/state"
3737
"github.com/ethereum/go-ethereum/core/txpool"
3838
"github.com/ethereum/go-ethereum/core/types"
39-
"github.com/ethereum/go-ethereum/crypto/kzg4844"
4039
"github.com/ethereum/go-ethereum/event"
4140
"github.com/ethereum/go-ethereum/log"
4241
"github.com/ethereum/go-ethereum/metrics"
@@ -1302,27 +1301,13 @@ func (p *BlobPool) GetMetadata(hash common.Hash) *txpool.TxMetadata {
13021301
}
13031302
}
13041303

1305-
// GetBlobs returns a number of blobs are proofs for the given versioned hashes.
1304+
// GetBlobs returns a number of blobs and proofs for the given versioned hashes.
13061305
// This is a utility method for the engine API, enabling consensus clients to
13071306
// retrieve blobs from the pools directly instead of the network.
1308-
func (p *BlobPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Proof) {
1309-
// Create a map of the blob hash to indices for faster fills
1310-
var (
1311-
blobs = make([]*kzg4844.Blob, len(vhashes))
1312-
proofs = make([]*kzg4844.Proof, len(vhashes))
1313-
)
1314-
index := make(map[common.Hash]int)
1315-
for i, vhash := range vhashes {
1316-
index[vhash] = i
1317-
}
1318-
// Iterate over the blob hashes, pulling transactions that fill it. Take care
1319-
// to also fill anything else the transaction might include (probably will).
1320-
for i, vhash := range vhashes {
1321-
// If already filled by a previous fetch, skip
1322-
if blobs[i] != nil {
1323-
continue
1324-
}
1325-
// Unfilled, retrieve the datastore item (in a short lock)
1307+
func (p *BlobPool) GetBlobs(vhashes []common.Hash) []*types.BlobTxSidecar {
1308+
sidecars := make([]*types.BlobTxSidecar, len(vhashes))
1309+
for idx, vhash := range vhashes {
1310+
// Retrieve the datastore item (in a short lock)
13261311
p.lock.RLock()
13271312
id, exists := p.lookup.storeidOfBlob(vhash)
13281313
if !exists {
@@ -1342,16 +1327,22 @@ func (p *BlobPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.
13421327
log.Error("Blobs corrupted for traced transaction", "id", id, "err", err)
13431328
continue
13441329
}
1345-
// Fill anything requested, not just the current versioned hash
1346-
sidecar := item.BlobTxSidecar()
1347-
for j, blobhash := range item.BlobHashes() {
1348-
if idx, ok := index[blobhash]; ok {
1349-
blobs[idx] = &sidecar.Blobs[j]
1350-
proofs[idx] = &sidecar.Proofs[j]
1351-
}
1330+
sidecars[idx] = item.BlobTxSidecar()
1331+
}
1332+
return sidecars
1333+
}
1334+
1335+
func (p *BlobPool) HasBlobs(vhashes []common.Hash) bool {
1336+
for _, vhash := range vhashes {
1337+
// Retrieve the datastore item (in a short lock)
1338+
p.lock.RLock()
1339+
_, exists := p.lookup.storeidOfBlob(vhash)
1340+
p.lock.RUnlock()
1341+
if !exists {
1342+
return false
13521343
}
13531344
}
1354-
return blobs, proofs
1345+
return true
13551346
}
13561347

13571348
// Add inserts a set of blob transactions into the pool if they pass validation (both

core/txpool/blobpool/blobpool_test.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -417,8 +417,23 @@ func verifyBlobRetrievals(t *testing.T, pool *BlobPool) {
417417
for i := range testBlobVHashes {
418418
copy(hashes[i][:], testBlobVHashes[i][:])
419419
}
420-
blobs, proofs := pool.GetBlobs(hashes)
421-
420+
sidecars := pool.GetBlobs(hashes)
421+
var blobs []*kzg4844.Blob
422+
var proofs []*kzg4844.Proof
423+
for idx, sidecar := range sidecars {
424+
if sidecar == nil {
425+
blobs = append(blobs, nil)
426+
proofs = append(proofs, nil)
427+
continue
428+
}
429+
blobHashes := sidecar.BlobHashes()
430+
for i, hash := range blobHashes {
431+
if hash == hashes[idx] {
432+
blobs = append(blobs, &sidecar.Blobs[i])
433+
proofs = append(proofs, &sidecar.Proofs[i])
434+
}
435+
}
436+
}
422437
// Cross validate what we received vs what we wanted
423438
if len(blobs) != len(hashes) || len(proofs) != len(hashes) {
424439
t.Errorf("retrieved blobs/proofs size mismatch: have %d/%d, want %d", len(blobs), len(proofs), len(hashes))

core/txpool/legacypool/legacypool.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ import (
3535
"github.com/ethereum/go-ethereum/core/state"
3636
"github.com/ethereum/go-ethereum/core/txpool"
3737
"github.com/ethereum/go-ethereum/core/types"
38-
"github.com/ethereum/go-ethereum/crypto/kzg4844"
3938
"github.com/ethereum/go-ethereum/event"
4039
"github.com/ethereum/go-ethereum/log"
4140
"github.com/ethereum/go-ethereum/metrics"
@@ -1065,8 +1064,14 @@ func (pool *LegacyPool) GetMetadata(hash common.Hash) *txpool.TxMetadata {
10651064

10661065
// GetBlobs is not supported by the legacy transaction pool, it is just here to
10671066
// implement the txpool.SubPool interface.
1068-
func (pool *LegacyPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Proof) {
1069-
return nil, nil
1067+
func (pool *LegacyPool) GetBlobs(vhashes []common.Hash) []*types.BlobTxSidecar {
1068+
return nil
1069+
}
1070+
1071+
// HasBlobs is not supported by the legacy transaction pool, it is just here to
1072+
// implement the txpool.SubPool interface.
1073+
func (pool *LegacyPool) HasBlobs(vhashes []common.Hash) bool {
1074+
return false
10701075
}
10711076

10721077
// Has returns an indicator whether txpool has a transaction cached with the

core/txpool/subpool.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323
"github.com/ethereum/go-ethereum/common"
2424
"github.com/ethereum/go-ethereum/core"
2525
"github.com/ethereum/go-ethereum/core/types"
26-
"github.com/ethereum/go-ethereum/crypto/kzg4844"
2726
"github.com/ethereum/go-ethereum/event"
2827
"github.com/holiman/uint256"
2928
)
@@ -136,7 +135,11 @@ type SubPool interface {
136135
// GetBlobs returns a number of blobs are proofs for the given versioned hashes.
137136
// This is a utility method for the engine API, enabling consensus clients to
138137
// retrieve blobs from the pools directly instead of the network.
139-
GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Proof)
138+
GetBlobs(vhashes []common.Hash) []*types.BlobTxSidecar
139+
140+
// HasBlobs returns true if all blobs corresponding to the versioned hashes
141+
// are in the sub pool.
142+
HasBlobs(vhashes []common.Hash) bool
140143

141144
// ValidateTxBasics checks whether a transaction is valid according to the consensus
142145
// rules, but does not check state-dependent validation such as sufficient balance.

core/txpool/txpool.go

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import (
2626
"github.com/ethereum/go-ethereum/core"
2727
"github.com/ethereum/go-ethereum/core/state"
2828
"github.com/ethereum/go-ethereum/core/types"
29-
"github.com/ethereum/go-ethereum/crypto/kzg4844"
3029
"github.com/ethereum/go-ethereum/event"
3130
"github.com/ethereum/go-ethereum/log"
3231
"github.com/ethereum/go-ethereum/params"
@@ -311,17 +310,42 @@ func (p *TxPool) GetMetadata(hash common.Hash) *TxMetadata {
311310
// GetBlobs returns a number of blobs are proofs for the given versioned hashes.
312311
// This is a utility method for the engine API, enabling consensus clients to
313312
// retrieve blobs from the pools directly instead of the network.
314-
func (p *TxPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Proof) {
313+
func (p *TxPool) GetBlobs(vhashes []common.Hash) []*types.BlobTxSidecar {
315314
for _, subpool := range p.subpools {
316315
// It's an ugly to assume that only one pool will be capable of returning
317-
// anything meaningful for this call, but anythingh else requires merging
316+
// anything meaningful for this call, but anything else requires merging
318317
// partial responses and that's too annoying to do until we get a second
319318
// blobpool (probably never).
320-
if blobs, proofs := subpool.GetBlobs(vhashes); blobs != nil {
321-
return blobs, proofs
319+
if sidecars := subpool.GetBlobs(vhashes); sidecars != nil {
320+
return sidecars
322321
}
323322
}
324-
return nil, nil
323+
return nil
324+
}
325+
326+
// HasBlobs will return true if all the vhashes are available in the same subpool.
327+
func (p *TxPool) HasBlobs(vhashes []common.Hash) bool {
328+
for _, subpool := range p.subpools {
329+
// It's an ugly to assume that only one pool will be capable of returning
330+
// anything meaningful for this call, but anything else requires merging
331+
// partial responses and that's too annoying to do until we get a second
332+
// blobpool (probably never).
333+
if subpool.HasBlobs(vhashes) {
334+
return true
335+
}
336+
}
337+
return false
338+
}
339+
340+
// ValidateTxBasics checks whether a transaction is valid according to the consensus
341+
// rules, but does not check state-dependent validation such as sufficient balance.
342+
func (p *TxPool) ValidateTxBasics(tx *types.Transaction) error {
343+
for _, subpool := range p.subpools {
344+
if subpool.Filter(tx) {
345+
return subpool.ValidateTxBasics(tx)
346+
}
347+
}
348+
return fmt.Errorf("%w: received type %d", core.ErrTxTypeNotSupported, tx.Type())
325349
}
326350

327351
// Add enqueues a batch of transactions into the pool if they are valid. Due

core/txpool/validation.go

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,16 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
156156
if len(hashes) > maxBlobs {
157157
return fmt.Errorf("too many blobs in transaction: have %d, permitted %d", len(hashes), maxBlobs)
158158
}
159-
// Ensure commitments, proofs and hashes are valid
160-
if err := validateBlobSidecar(hashes, sidecar); err != nil {
161-
return err
159+
if opts.Config.IsOsaka(head.Number, head.Time) {
160+
// Ensure commitments, cell proofs and hashes are valid
161+
if err := validateBlobSidecarOsaka(hashes, sidecar); err != nil {
162+
return err
163+
}
164+
} else {
165+
// Ensure commitments, proofs and hashes are valid
166+
if err := validateBlobSidecar(hashes, sidecar); err != nil {
167+
return err
168+
}
162169
}
163170
}
164171
if tx.Type() == types.SetCodeTxType {
@@ -170,6 +177,9 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
170177
}
171178

172179
func validateBlobSidecar(hashes []common.Hash, sidecar *types.BlobTxSidecar) error {
180+
if sidecar.Version != 0 {
181+
return fmt.Errorf("invalid sidecar version pre-osaka: %v", sidecar.Version)
182+
}
173183
if len(sidecar.Blobs) != len(hashes) {
174184
return fmt.Errorf("invalid number of %d blobs compared to %d blob hashes", len(sidecar.Blobs), len(hashes))
175185
}
@@ -189,6 +199,35 @@ func validateBlobSidecar(hashes []common.Hash, sidecar *types.BlobTxSidecar) err
189199
return nil
190200
}
191201

202+
func validateBlobSidecarOsaka(hashes []common.Hash, sidecar *types.BlobTxSidecar) error {
203+
if sidecar.Version != 1 {
204+
return fmt.Errorf("invalid sidecar version post-osaka: %v", sidecar.Version)
205+
}
206+
if len(sidecar.Blobs) != len(hashes) {
207+
return fmt.Errorf("invalid number of %d blobs compared to %d blob hashes", len(sidecar.Blobs), len(hashes))
208+
}
209+
if len(sidecar.Commitments) != len(hashes) {
210+
return fmt.Errorf("invalid number of %d commitments compared to %d blob hashes", len(sidecar.Commitments), len(hashes))
211+
}
212+
if len(sidecar.Proofs) != len(hashes)*kzg4844.CellProofsPerBlob {
213+
return fmt.Errorf("invalid number of %d blob proofs expected %d", len(sidecar.Proofs), len(hashes)*kzg4844.CellProofsPerBlob)
214+
}
215+
if err := sidecar.ValidateBlobCommitmentHashes(hashes); err != nil {
216+
return err
217+
}
218+
// Blob commitments match with the hashes in the transaction, verify the
219+
// blobs themselves via KZG
220+
var blobs []*kzg4844.Blob
221+
for _, blob := range sidecar.Blobs {
222+
blobs = append(blobs, &blob)
223+
}
224+
225+
if err := kzg4844.VerifyCellProofs(blobs, sidecar.Commitments, sidecar.Proofs); err != nil {
226+
return err
227+
}
228+
return nil
229+
}
230+
192231
// ValidationOptionsWithState define certain differences between stateful transaction
193232
// validation across the different pools without having to duplicate those checks.
194233
type ValidationOptionsWithState struct {

0 commit comments

Comments
 (0)