eth/catalyst: implement getBlobsV2

This commit is contained in:
Marius van der Wijden 2025-03-11 12:54:40 +01:00 committed by MariusVanDerWijden
parent 42655146e0
commit c192c9dc08
16 changed files with 478 additions and 55 deletions

View file

@ -123,6 +123,11 @@ type BlobAndProofV1 struct {
Proof hexutil.Bytes `json:"proof"`
}
type BlobAndProofV2 struct {
Blob hexutil.Bytes `json:"blob"`
CellProofs []hexutil.Bytes `json:"proofs"`
}
// JSON type overrides for ExecutionPayloadEnvelope.
type executionPayloadEnvelopeMarshaling struct {
BlockValue *hexutil.Big
@ -331,7 +336,9 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.
for j := range sidecar.Blobs {
bundle.Blobs = append(bundle.Blobs, hexutil.Bytes(sidecar.Blobs[j][:]))
bundle.Commitments = append(bundle.Commitments, hexutil.Bytes(sidecar.Commitments[j][:]))
bundle.Proofs = append(bundle.Proofs, hexutil.Bytes(sidecar.Proofs[j][:]))
}
for _, proof := range sidecar.Proofs {
bundle.Proofs = append(bundle.Proofs, hexutil.Bytes(proof[:]))
}
}

View file

@ -0,0 +1,56 @@
// Copyright 2025 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package engine
import (
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
)
func TestBlobs(t *testing.T) {
var (
emptyBlob = new(kzg4844.Blob)
emptyBlobCommit, _ = kzg4844.BlobToCommitment(emptyBlob)
emptyBlobProof, _ = kzg4844.ComputeBlobProof(emptyBlob, emptyBlobCommit)
emptyCellProof, _ = kzg4844.ComputeCells(emptyBlob)
)
header := types.Header{}
block := types.NewBlock(&header, &types.Body{}, nil, nil)
sidecarWithoutCellProofs := &types.BlobTxSidecar{
Blobs: []kzg4844.Blob{*emptyBlob},
Commitments: []kzg4844.Commitment{emptyBlobCommit},
Proofs: []kzg4844.Proof{emptyBlobProof},
}
env := BlockToExecutableData(block, common.Big0, []*types.BlobTxSidecar{sidecarWithoutCellProofs}, nil)
if len(env.BlobsBundle.Proofs) != 1 {
t.Fatalf("Expect 1 proof in blobs bundle, got %v", len(env.BlobsBundle.Proofs))
}
sidecarWithCellProofs := &types.BlobTxSidecar{
Blobs: []kzg4844.Blob{*emptyBlob},
Commitments: []kzg4844.Commitment{emptyBlobCommit},
Proofs: emptyCellProof,
}
env = BlockToExecutableData(block, common.Big0, []*types.BlobTxSidecar{sidecarWithCellProofs}, nil)
if len(env.BlobsBundle.Proofs) != 128 {
t.Fatalf("Expect 128 proofs in blobs bundle, got %v", len(env.BlobsBundle.Proofs))
}
}

View file

@ -36,7 +36,6 @@ import (
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
@ -1295,27 +1294,13 @@ func (p *BlobPool) GetMetadata(hash common.Hash) *txpool.TxMetadata {
}
}
// GetBlobs returns a number of blobs are proofs for the given versioned hashes.
// GetBlobs returns a number of blobs and proofs for the given versioned hashes.
// This is a utility method for the engine API, enabling consensus clients to
// retrieve blobs from the pools directly instead of the network.
func (p *BlobPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Proof) {
// Create a map of the blob hash to indices for faster fills
var (
blobs = make([]*kzg4844.Blob, len(vhashes))
proofs = make([]*kzg4844.Proof, len(vhashes))
)
index := make(map[common.Hash]int)
for i, vhash := range vhashes {
index[vhash] = i
}
// Iterate over the blob hashes, pulling transactions that fill it. Take care
// to also fill anything else the transaction might include (probably will).
for i, vhash := range vhashes {
// If already filled by a previous fetch, skip
if blobs[i] != nil {
continue
}
// Unfilled, retrieve the datastore item (in a short lock)
func (p *BlobPool) GetBlobs(vhashes []common.Hash) []*types.BlobTxSidecar {
sidecars := make([]*types.BlobTxSidecar, len(vhashes))
for idx, vhash := range vhashes {
// Retrieve the datastore item (in a short lock)
p.lock.RLock()
id, exists := p.lookup.storeidOfBlob(vhash)
if !exists {
@ -1335,16 +1320,22 @@ func (p *BlobPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.
log.Error("Blobs corrupted for traced transaction", "id", id, "err", err)
continue
}
// Fill anything requested, not just the current versioned hash
sidecar := item.BlobTxSidecar()
for j, blobhash := range item.BlobHashes() {
if idx, ok := index[blobhash]; ok {
blobs[idx] = &sidecar.Blobs[j]
proofs[idx] = &sidecar.Proofs[j]
}
sidecars[idx] = item.BlobTxSidecar()
}
return sidecars
}
func (p *BlobPool) HasBlobs(vhashes []common.Hash) bool {
for _, vhash := range vhashes {
// Retrieve the datastore item (in a short lock)
p.lock.RLock()
_, exists := p.lookup.storeidOfBlob(vhash)
p.lock.RUnlock()
if !exists {
return false
}
}
return blobs, proofs
return true
}
// Add inserts a set of blob transactions into the pool if they pass validation (both

View file

@ -417,8 +417,23 @@ func verifyBlobRetrievals(t *testing.T, pool *BlobPool) {
for i := range testBlobVHashes {
copy(hashes[i][:], testBlobVHashes[i][:])
}
blobs, proofs := pool.GetBlobs(hashes)
sidecars := pool.GetBlobs(hashes)
var blobs []*kzg4844.Blob
var proofs []*kzg4844.Proof
for idx, sidecar := range sidecars {
if sidecar == nil {
blobs = append(blobs, nil)
proofs = append(proofs, nil)
continue
}
blobHashes := sidecar.BlobHashes()
for i, hash := range blobHashes {
if hash == hashes[idx] {
blobs = append(blobs, &sidecar.Blobs[i])
proofs = append(proofs, &sidecar.Proofs[i])
}
}
}
// Cross validate what we received vs what we wanted
if len(blobs) != len(hashes) || len(proofs) != len(hashes) {
t.Errorf("retrieved blobs/proofs size mismatch: have %d/%d, want %d", len(blobs), len(proofs), len(hashes))

View file

@ -35,7 +35,6 @@ import (
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
@ -1065,8 +1064,14 @@ func (pool *LegacyPool) GetMetadata(hash common.Hash) *txpool.TxMetadata {
// GetBlobs is not supported by the legacy transaction pool, it is just here to
// implement the txpool.SubPool interface.
func (pool *LegacyPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Proof) {
return nil, nil
func (pool *LegacyPool) GetBlobs(vhashes []common.Hash) []*types.BlobTxSidecar {
return nil
}
// HasBlobs is not supported by the legacy transaction pool, it is just here to
// implement the txpool.SubPool interface.
func (pool *LegacyPool) HasBlobs(vhashes []common.Hash) bool {
return false
}
// Has returns an indicator whether txpool has a transaction cached with the

View file

@ -23,7 +23,6 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/event"
"github.com/holiman/uint256"
)
@ -136,7 +135,11 @@ type SubPool interface {
// GetBlobs returns a number of blobs are proofs for the given versioned hashes.
// This is a utility method for the engine API, enabling consensus clients to
// retrieve blobs from the pools directly instead of the network.
GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Proof)
GetBlobs(vhashes []common.Hash) []*types.BlobTxSidecar
// HasBlobs returns true if all blobs corresponding to the versioned hashes
// are in the sub pool.
HasBlobs(vhashes []common.Hash) bool
// ValidateTxBasics checks whether a transaction is valid according to the consensus
// rules, but does not check state-dependent validation such as sufficient balance.

View file

@ -26,7 +26,6 @@ import (
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
@ -311,17 +310,42 @@ func (p *TxPool) GetMetadata(hash common.Hash) *TxMetadata {
// GetBlobs returns a number of blobs are proofs for the given versioned hashes.
// This is a utility method for the engine API, enabling consensus clients to
// retrieve blobs from the pools directly instead of the network.
func (p *TxPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Proof) {
func (p *TxPool) GetBlobs(vhashes []common.Hash) []*types.BlobTxSidecar {
for _, subpool := range p.subpools {
// It's an ugly to assume that only one pool will be capable of returning
// anything meaningful for this call, but anythingh else requires merging
// anything meaningful for this call, but anything else requires merging
// partial responses and that's too annoying to do until we get a second
// blobpool (probably never).
if blobs, proofs := subpool.GetBlobs(vhashes); blobs != nil {
return blobs, proofs
if sidecars := subpool.GetBlobs(vhashes); sidecars != nil {
return sidecars
}
}
return nil, nil
return nil
}
// HasBlobs will return true if all the vhashes are available in the same subpool.
func (p *TxPool) HasBlobs(vhashes []common.Hash) bool {
for _, subpool := range p.subpools {
// It's an ugly to assume that only one pool will be capable of returning
// anything meaningful for this call, but anything else requires merging
// partial responses and that's too annoying to do until we get a second
// blobpool (probably never).
if subpool.HasBlobs(vhashes) {
return true
}
}
return false
}
// ValidateTxBasics checks whether a transaction is valid according to the consensus
// rules, but does not check state-dependent validation such as sufficient balance.
func (p *TxPool) ValidateTxBasics(tx *types.Transaction) error {
for _, subpool := range p.subpools {
if subpool.Filter(tx) {
return subpool.ValidateTxBasics(tx)
}
}
return fmt.Errorf("%w: received type %d", core.ErrTxTypeNotSupported, tx.Type())
}
// Add enqueues a batch of transactions into the pool if they are valid. Due

View file

@ -152,9 +152,16 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
if len(hashes) > maxBlobs {
return fmt.Errorf("too many blobs in transaction: have %d, permitted %d", len(hashes), maxBlobs)
}
// Ensure commitments, proofs and hashes are valid
if err := validateBlobSidecar(hashes, sidecar); err != nil {
return err
if opts.Config.IsOsaka(head.Number, head.Time) {
// Ensure commitments, cell proofs and hashes are valid
if err := validateBlobSidecarOsaka(hashes, sidecar); err != nil {
return err
}
} else {
// Ensure commitments, proofs and hashes are valid
if err := validateBlobSidecar(hashes, sidecar); err != nil {
return err
}
}
}
if tx.Type() == types.SetCodeTxType {
@ -166,6 +173,9 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
}
func validateBlobSidecar(hashes []common.Hash, sidecar *types.BlobTxSidecar) error {
if sidecar.Version != 0 {
return fmt.Errorf("invalid sidecar version pre-osaka: %v", sidecar.Version)
}
if len(sidecar.Blobs) != len(hashes) {
return fmt.Errorf("invalid number of %d blobs compared to %d blob hashes", len(sidecar.Blobs), len(hashes))
}
@ -185,6 +195,35 @@ func validateBlobSidecar(hashes []common.Hash, sidecar *types.BlobTxSidecar) err
return nil
}
func validateBlobSidecarOsaka(hashes []common.Hash, sidecar *types.BlobTxSidecar) error {
if sidecar.Version != 1 {
return fmt.Errorf("invalid sidecar version post-osaka: %v", sidecar.Version)
}
if len(sidecar.Blobs) != len(hashes) {
return fmt.Errorf("invalid number of %d blobs compared to %d blob hashes", len(sidecar.Blobs), len(hashes))
}
if len(sidecar.Commitments) != len(hashes) {
return fmt.Errorf("invalid number of %d commitments compared to %d blob hashes", len(sidecar.Commitments), len(hashes))
}
if len(sidecar.Proofs) != len(hashes)*kzg4844.CellProofsPerBlob {
return fmt.Errorf("invalid number of %d blob proofs expected %d", len(sidecar.Proofs), len(hashes)*kzg4844.CellProofsPerBlob)
}
if err := sidecar.ValidateBlobCommitmentHashes(hashes); err != nil {
return err
}
// Blob commitments match with the hashes in the transaction, verify the
// blobs themselves via KZG
var blobs []*kzg4844.Blob
for _, blob := range sidecar.Blobs {
blobs = append(blobs, &blob)
}
if err := kzg4844.VerifyCellProofs(blobs, sidecar.Commitments, sidecar.Proofs); err != nil {
return err
}
return nil
}
// ValidationOptionsWithState define certain differences between stateful transaction
// validation across the different pools without having to duplicate those checks.
type ValidationOptionsWithState struct {

View file

@ -55,6 +55,7 @@ type BlobTx struct {
// BlobTxSidecar contains the blobs of a blob transaction.
type BlobTxSidecar struct {
Version byte // Version
Blobs []kzg4844.Blob // Blobs needed by the blob pool
Commitments []kzg4844.Commitment // Commitments needed by the blob pool
Proofs []kzg4844.Proof // Proofs needed by the blob pool
@ -70,6 +71,20 @@ func (sc *BlobTxSidecar) BlobHashes() []common.Hash {
return h
}
// CellProofsAt returns the cell proofs for blob with index idx.
func (sc *BlobTxSidecar) CellProofsAt(idx int) []kzg4844.Proof {
var cellProofs []kzg4844.Proof
for i := range kzg4844.CellProofsPerBlob {
index := idx*kzg4844.CellProofsPerBlob + i
if index > len(sc.Proofs) {
return nil
}
proof := sc.Proofs[index]
cellProofs = append(cellProofs, proof)
}
return cellProofs
}
// encodedSize computes the RLP size of the sidecar elements. This does NOT return the
// encoded size of the BlobTxSidecar, it's just a helper for tx.Size().
func (sc *BlobTxSidecar) encodedSize() uint64 {
@ -110,6 +125,14 @@ type blobTxWithBlobs struct {
Proofs []kzg4844.Proof
}
type versionedBlobTxWithBlobs struct {
BlobTx *BlobTx
Version byte
Blobs []kzg4844.Blob
Commitments []kzg4844.Commitment
Proofs []kzg4844.Proof
}
// copy creates a deep copy of the transaction data and initializes all fields.
func (tx *BlobTx) copy() TxData {
cpy := &BlobTx{
@ -218,6 +241,17 @@ func (tx *BlobTx) encode(b *bytes.Buffer) error {
if tx.Sidecar == nil {
return rlp.Encode(b, tx)
}
// Encode a cell proof transaction
if tx.Sidecar.Version != 0 {
inner := &versionedBlobTxWithBlobs{
BlobTx: tx,
Version: tx.Sidecar.Version,
Blobs: tx.Sidecar.Blobs,
Commitments: tx.Sidecar.Commitments,
Proofs: tx.Sidecar.Proofs,
}
return rlp.Encode(b, inner)
}
inner := &blobTxWithBlobs{
BlobTx: tx,
Blobs: tx.Sidecar.Blobs,
@ -246,13 +280,23 @@ func (tx *BlobTx) decode(input []byte) error {
if firstElemKind != rlp.List {
return rlp.DecodeBytes(input, tx)
}
// It's a tx with blobs.
var inner blobTxWithBlobs
// It's a tx with blobs. Try to decode it as version 0.
var inner versionedBlobTxWithBlobs
if err := rlp.DecodeBytes(input, &inner); err != nil {
return err
var innerV0 blobTxWithBlobs
if err := rlp.DecodeBytes(input, &innerV0); err != nil {
return err
}
inner.BlobTx = innerV0.BlobTx
inner.Version = 0
inner.Blobs = innerV0.Blobs
inner.Commitments = innerV0.Commitments
inner.Proofs = innerV0.Proofs
}
*tx = *inner.BlobTx
tx.Sidecar = &BlobTxSidecar{
Version: inner.Version,
Blobs: inner.Blobs,
Commitments: inner.Commitments,
Proofs: inner.Proofs,

View file

@ -34,6 +34,8 @@ var (
blobT = reflect.TypeOf(Blob{})
commitmentT = reflect.TypeOf(Commitment{})
proofT = reflect.TypeOf(Proof{})
CellProofsPerBlob = 128
)
// Blob represents a 4844 data blob.
@ -84,6 +86,10 @@ type Claim [32]byte
// useCKZG controls whether the cryptography should use the Go or C backend.
var useCKZG atomic.Bool
func init() {
UseCKZG(true)
}
// UseCKZG can be called to switch the default Go implementation of KZG to the C
// library if for some reason the user wishes to do so (e.g. consensus bug in one
// or the other).
@ -149,6 +155,16 @@ func VerifyBlobProof(blob *Blob, commitment Commitment, proof Proof) error {
return gokzgVerifyBlobProof(blob, commitment, proof)
}
// VerifyCellProofs verifies a batch of proofs corresponding to the blobs and commitments.
// Expects length of blobs and commitments to be equal.
// Expects length of proofs be 128 * length of blobs.
func VerifyCellProofs(blobs []*Blob, commitments []Commitment, proofs []Proof) error {
if useCKZG.Load() {
return ckzgVerifyCellProofBatch(blobs, commitments, proofs)
}
return gokzgVerifyCellProofBatch(blobs, commitments, proofs)
}
// ComputeCellProofs returns the KZG cell proofs that are used to verify the blob against
// the commitment.
//
@ -177,3 +193,10 @@ func CalcBlobHashV1(hasher hash.Hash, commit *Commitment) (vh [32]byte) {
func IsValidVersionedHash(h []byte) bool {
return len(h) == 32 && h[0] == 0x01
}
func ComputeCells(blob *Blob) ([]Proof, error) {
if useCKZG.Load() {
return ckzgComputeCellProofs(blob)
}
return gokzgComputeCellProofs(blob)
}

View file

@ -149,3 +149,44 @@ func ckzgComputeCellProofs(blob *Blob) ([]Proof, error) {
}
return p, nil
}
// ckzgVerifyCellProofs verifies that the blob data corresponds to the provided commitment.
func ckzgVerifyCellProofBatch(blobs []*Blob, commitments []Commitment, cellProofs []Proof) error {
ckzgIniter.Do(ckzgInit)
var (
proofs = make([]ckzg4844.Bytes48, len(cellProofs))
commits = make([]ckzg4844.Bytes48, 0, len(cellProofs))
cellIndices = make([]uint64, 0, len(cellProofs))
cells = make([]ckzg4844.Cell, 0, len(cellProofs))
)
// Copy over the cell proofs
for i, proof := range cellProofs {
proofs[i] = (ckzg4844.Bytes48)(proof)
}
// Blow up the commitments to be the same length as the proofs
for _, commitment := range commitments {
for range gokzg4844.CellsPerExtBlob {
commits = append(commits, (ckzg4844.Bytes48)(commitment))
}
}
// Compute the cells and cell indices
for _, blob := range blobs {
cellsI, err := ckzg4844.ComputeCells((*ckzg4844.Blob)(blob))
if err != nil {
return err
}
cells = append(cells, cellsI[:]...)
for idx := range len(cellsI) {
cellIndices = append(cellIndices, uint64(idx))
}
}
valid, err := ckzg4844.VerifyCellKZGProofBatch(commits, cellIndices, cells, proofs)
if err != nil {
return err
}
if !valid {
return errors.New("invalid proof")
}
return nil
}

View file

@ -61,6 +61,11 @@ func ckzgVerifyBlobProof(blob *Blob, commitment Commitment, proof Proof) error {
panic("unsupported platform")
}
// ckzgVerifyCellProofBatch verifies that the blob data corresponds to the provided commitment.
func ckzgVerifyCellProofBatch(blobs []*Blob, commitments []Commitment, proof []Proof) error {
panic("unsupported platform")
}
// ckzgComputeCellProofs returns the KZG cell proofs that are used to verify the blob against
// the commitment.
//

View file

@ -114,3 +114,37 @@ func gokzgComputeCellProofs(blob *Blob) ([]Proof, error) {
}
return p, nil
}
// gokzgVerifyCellProofs verifies that the blob data corresponds to the provided commitment.
func gokzgVerifyCellProofBatch(blobs []*Blob, commitments []Commitment, cellProofs []Proof) error {
gokzgIniter.Do(gokzgInit)
var (
proofs = make([]gokzg4844.KZGProof, len(cellProofs))
commits = make([]gokzg4844.KZGCommitment, 0, len(cellProofs))
cellIndices = make([]uint64, 0, len(cellProofs))
cells = make([]*gokzg4844.Cell, 0, len(cellProofs))
)
// Copy over the cell proofs
for i, proof := range cellProofs {
proofs[i] = gokzg4844.KZGProof(proof)
}
// Blow up the commitments to be the same length as the proofs
for _, commitment := range commitments {
for range gokzg4844.CellsPerExtBlob {
commits = append(commits, gokzg4844.KZGCommitment(commitment))
}
}
// Compute the cell and cell indices
for _, blob := range blobs {
cellsI, err := context.ComputeCells((*gokzg4844.Blob)(blob), 2)
if err != nil {
return err
}
cells = append(cells, cellsI[:]...)
for idx := range len(cellsI) {
cellIndices = append(cellIndices, uint64(idx))
}
}
return context.VerifyCellKZGProofBatch(commits, cellIndices, cells[:], proofs)
}

View file

@ -193,3 +193,39 @@ func benchmarkVerifyBlobProof(b *testing.B, ckzg bool) {
VerifyBlobProof(blob, commitment, proof)
}
}
func TestCKZGCells(t *testing.T) { testKZGCells(t, true) }
func TestGoKZGCells(t *testing.T) { testKZGCells(t, false) }
func testKZGCells(t *testing.T, ckzg bool) {
if ckzg && !ckzgAvailable {
t.Skip("CKZG unavailable in this test build")
}
defer func(old bool) { useCKZG.Store(old) }(useCKZG.Load())
useCKZG.Store(ckzg)
blob1 := randBlob()
blob2 := randBlob()
commitment1, err := BlobToCommitment(blob1)
if err != nil {
t.Fatalf("failed to create KZG commitment from blob: %v", err)
}
commitment2, err := BlobToCommitment(blob2)
if err != nil {
t.Fatalf("failed to create KZG commitment from blob: %v", err)
}
proofs1, err := ComputeCellProofs(blob1)
if err != nil {
t.Fatalf("failed to create KZG proof at point: %v", err)
}
proofs2, err := ComputeCellProofs(blob2)
if err != nil {
t.Fatalf("failed to create KZG proof at point: %v", err)
}
proofs := append(proofs1, proofs2...)
if err := VerifyCellProofs([]*Blob{blob1, blob2}, []Commitment{commitment1, commitment2}, proofs); err != nil {
t.Fatalf("failed to verify KZG proof at point: %v", err)
}
}

View file

@ -18,6 +18,7 @@
package catalyst
import (
"crypto/sha256"
"errors"
"fmt"
"strconv"
@ -29,6 +30,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/internal/version"
@ -91,7 +93,9 @@ var caps = []string{
"engine_getPayloadV2",
"engine_getPayloadV3",
"engine_getPayloadV4",
"engine_getPayloadV5",
"engine_getBlobsV1",
"engine_getBlobsV2",
"engine_newPayloadV1",
"engine_newPayloadV2",
"engine_newPayloadV3",
@ -456,6 +460,14 @@ func (api *ConsensusAPI) GetPayloadV4(payloadID engine.PayloadID) (*engine.Execu
return api.getPayload(payloadID, false)
}
// GetPayloadV4 returns a cached payload by id.
func (api *ConsensusAPI) GetPayloadV5(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) {
if !payloadID.Is(engine.PayloadV3) {
return nil, engine.UnsupportedFork
}
return api.getPayload(payloadID, false)
}
func (api *ConsensusAPI) getPayload(payloadID engine.PayloadID, full bool) (*engine.ExecutionPayloadEnvelope, error) {
log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID)
data := api.localBlocks.get(payloadID, full)
@ -470,14 +482,82 @@ func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProo
if len(hashes) > 128 {
return nil, engine.TooLargeRequest.With(fmt.Errorf("requested blob count too large: %v", len(hashes)))
}
res := make([]*engine.BlobAndProofV1, len(hashes))
var (
res = make([]*engine.BlobAndProofV1, len(hashes))
hasher = sha256.New()
index = make(map[common.Hash]int)
sidecars = api.eth.TxPool().GetBlobs(hashes)
)
blobs, proofs := api.eth.TxPool().GetBlobs(hashes)
for i := 0; i < len(blobs); i++ {
if blobs[i] != nil {
res[i] = &engine.BlobAndProofV1{
Blob: (*blobs[i])[:],
Proof: (*proofs[i])[:],
for i, hash := range hashes {
index[hash] = i
}
for i, sidecar := range sidecars {
if res[i] != nil || sidecar == nil {
// already filled
continue
}
for cIdx, commitment := range sidecar.Commitments {
computed := kzg4844.CalcBlobHashV1(hasher, &commitment)
if idx, ok := index[computed]; ok {
res[idx] = &engine.BlobAndProofV1{
Blob: sidecar.Blobs[cIdx][:],
Proof: sidecar.Proofs[cIdx][:],
}
}
}
}
return res, nil
}
// GetBlobsV2 returns a blob from the transaction pool.
func (api *ConsensusAPI) GetBlobsV2(hashes []common.Hash) ([]*engine.BlobAndProofV2, error) {
if len(hashes) > 128 {
return nil, engine.TooLargeRequest.With(fmt.Errorf("requested blob count too large: %v", len(hashes)))
}
// Optimization: check first if all blobs are available, if not, return empty response
if !api.eth.TxPool().HasBlobs(hashes) {
return nil, nil
}
// pull up the blob hashes
var (
res = make([]*engine.BlobAndProofV2, len(hashes))
index = make(map[common.Hash][]int)
sidecars = api.eth.TxPool().GetBlobs(hashes)
)
for i, hash := range hashes {
index[hash] = append(index[hash], i)
}
for i, sidecar := range sidecars {
if res[i] != nil {
// already filled
continue
}
if sidecar == nil {
// not found, return empty response
return nil, nil
}
if sidecar.Version != 1 {
log.Info("GetBlobs queried V0 transaction: index %v, blobhashes %v", index, sidecar.BlobHashes())
return nil, nil
}
blobHashes := sidecar.BlobHashes()
for bIdx, hash := range blobHashes {
if idxes, ok := index[hash]; ok {
proofs := sidecar.CellProofsAt(bIdx)
var cellProofs []hexutil.Bytes
for _, proof := range proofs {
cellProofs = append(cellProofs, proof[:])
}
for _, idx := range idxes {
res[idx] = &engine.BlobAndProofV2{
Blob: sidecar.Blobs[bIdx][:],
CellProofs: cellProofs,
}
}
}
}
}

View file

@ -32,6 +32,7 @@ import (
"github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
@ -390,6 +391,25 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran
continue
}
// Make sure all transactions after osaka have cell proofs
if miner.chainConfig.IsOsaka(env.header.Number, env.header.Time) {
if sidecar := tx.BlobTxSidecar(); sidecar != nil {
if sidecar.Version == 0 {
log.Warn("Encountered Version 0 transaction post-Osaka, recompute proofs", "hash", ltx.Hash)
sidecar.Proofs = make([]kzg4844.Proof, 0)
for _, blob := range sidecar.Blobs {
cellProofs, err := kzg4844.ComputeCells(&blob)
if err != nil {
panic(err)
}
sidecar.Proofs = append(sidecar.Proofs, cellProofs...)
}
//txs.Pop()
//continue
}
}
}
// Error may be ignored here. The error has already been checked
// during transaction acceptance in the transaction pool.
from, _ := types.Sender(env.signer, tx)