Skip to content

Commit c192c9d

Browse files
eth/catalyst: implement getBlobsV2
1 parent 4265514 commit c192c9d

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"
@@ -1295,27 +1294,13 @@ func (p *BlobPool) GetMetadata(hash common.Hash) *txpool.TxMetadata {
12951294
}
12961295
}
12971296

1298-
// GetBlobs returns a number of blobs are proofs for the given versioned hashes.
1297+
// GetBlobs returns a number of blobs and proofs for the given versioned hashes.
12991298
// This is a utility method for the engine API, enabling consensus clients to
13001299
// retrieve blobs from the pools directly instead of the network.
1301-
func (p *BlobPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Proof) {
1302-
// Create a map of the blob hash to indices for faster fills
1303-
var (
1304-
blobs = make([]*kzg4844.Blob, len(vhashes))
1305-
proofs = make([]*kzg4844.Proof, len(vhashes))
1306-
)
1307-
index := make(map[common.Hash]int)
1308-
for i, vhash := range vhashes {
1309-
index[vhash] = i
1310-
}
1311-
// Iterate over the blob hashes, pulling transactions that fill it. Take care
1312-
// to also fill anything else the transaction might include (probably will).
1313-
for i, vhash := range vhashes {
1314-
// If already filled by a previous fetch, skip
1315-
if blobs[i] != nil {
1316-
continue
1317-
}
1318-
// Unfilled, retrieve the datastore item (in a short lock)
1300+
func (p *BlobPool) GetBlobs(vhashes []common.Hash) []*types.BlobTxSidecar {
1301+
sidecars := make([]*types.BlobTxSidecar, len(vhashes))
1302+
for idx, vhash := range vhashes {
1303+
// Retrieve the datastore item (in a short lock)
13191304
p.lock.RLock()
13201305
id, exists := p.lookup.storeidOfBlob(vhash)
13211306
if !exists {
@@ -1335,16 +1320,22 @@ func (p *BlobPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.
13351320
log.Error("Blobs corrupted for traced transaction", "id", id, "err", err)
13361321
continue
13371322
}
1338-
// Fill anything requested, not just the current versioned hash
1339-
sidecar := item.BlobTxSidecar()
1340-
for j, blobhash := range item.BlobHashes() {
1341-
if idx, ok := index[blobhash]; ok {
1342-
blobs[idx] = &sidecar.Blobs[j]
1343-
proofs[idx] = &sidecar.Proofs[j]
1344-
}
1323+
sidecars[idx] = item.BlobTxSidecar()
1324+
}
1325+
return sidecars
1326+
}
1327+
1328+
func (p *BlobPool) HasBlobs(vhashes []common.Hash) bool {
1329+
for _, vhash := range vhashes {
1330+
// Retrieve the datastore item (in a short lock)
1331+
p.lock.RLock()
1332+
_, exists := p.lookup.storeidOfBlob(vhash)
1333+
p.lock.RUnlock()
1334+
if !exists {
1335+
return false
13451336
}
13461337
}
1347-
return blobs, proofs
1338+
return true
13481339
}
13491340

13501341
// 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
@@ -152,9 +152,16 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
152152
if len(hashes) > maxBlobs {
153153
return fmt.Errorf("too many blobs in transaction: have %d, permitted %d", len(hashes), maxBlobs)
154154
}
155-
// Ensure commitments, proofs and hashes are valid
156-
if err := validateBlobSidecar(hashes, sidecar); err != nil {
157-
return err
155+
if opts.Config.IsOsaka(head.Number, head.Time) {
156+
// Ensure commitments, cell proofs and hashes are valid
157+
if err := validateBlobSidecarOsaka(hashes, sidecar); err != nil {
158+
return err
159+
}
160+
} else {
161+
// Ensure commitments, proofs and hashes are valid
162+
if err := validateBlobSidecar(hashes, sidecar); err != nil {
163+
return err
164+
}
158165
}
159166
}
160167
if tx.Type() == types.SetCodeTxType {
@@ -166,6 +173,9 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
166173
}
167174

168175
func validateBlobSidecar(hashes []common.Hash, sidecar *types.BlobTxSidecar) error {
176+
if sidecar.Version != 0 {
177+
return fmt.Errorf("invalid sidecar version pre-osaka: %v", sidecar.Version)
178+
}
169179
if len(sidecar.Blobs) != len(hashes) {
170180
return fmt.Errorf("invalid number of %d blobs compared to %d blob hashes", len(sidecar.Blobs), len(hashes))
171181
}
@@ -185,6 +195,35 @@ func validateBlobSidecar(hashes []common.Hash, sidecar *types.BlobTxSidecar) err
185195
return nil
186196
}
187197

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

0 commit comments

Comments
 (0)