mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-02-26 15:47:21 +00:00
eth/catalyst: implement getBlobsV2
This commit is contained in:
parent
35dd84ce29
commit
79385a4fda
16 changed files with 488 additions and 57 deletions
|
|
@ -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[:]))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
56
beacon/engine/types_test.go
Normal file
56
beacon/engine/types_test.go
Normal 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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
@ -1302,27 +1301,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 {
|
||||
|
|
@ -1342,16 +1327,24 @@ 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
|
||||
}
|
||||
|
||||
// AvailableBlobs returns the number of blobs that are available in the subpool.
|
||||
func (p *BlobPool) AvailableBlobs(vhashes []common.Hash) int {
|
||||
available := 0
|
||||
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 {
|
||||
available++
|
||||
}
|
||||
}
|
||||
return blobs, proofs
|
||||
return available
|
||||
}
|
||||
|
||||
// Add inserts a set of blob transactions into the pool if they pass validation (both
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
// AvailableBlobs is not supported by the legacy transaction pool, it is just here to
|
||||
// implement the txpool.SubPool interface.
|
||||
func (pool *LegacyPool) AvailableBlobs(vhashes []common.Hash) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Has returns an indicator whether txpool has a transaction cached with the
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
// AvailableBlobs returns number of blobs corresponding to the versioned hashes
|
||||
// that are available in the sub pool.
|
||||
AvailableBlobs(vhashes []common.Hash) int
|
||||
|
||||
// ValidateTxBasics checks whether a transaction is valid according to the consensus
|
||||
// rules, but does not check state-dependent validation such as sufficient balance.
|
||||
|
|
|
|||
|
|
@ -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,31 @@ 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
|
||||
}
|
||||
|
||||
// AvailableBlobs will return the number of vhashes that are available in the same subpool.
|
||||
func (p *TxPool) AvailableBlobs(vhashes []common.Hash) int {
|
||||
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 count := subpool.AvailableBlobs(vhashes); count != 0 {
|
||||
return count
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Add enqueues a batch of transactions into the pool if they are valid. Due
|
||||
|
|
|
|||
|
|
@ -156,9 +156,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 {
|
||||
|
|
@ -170,6 +177,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))
|
||||
}
|
||||
|
|
@ -189,6 +199,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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
package catalyst
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
|
@ -30,10 +31,12 @@ 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"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/miner"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
|
|
@ -92,7 +95,9 @@ var caps = []string{
|
|||
"engine_getPayloadV2",
|
||||
"engine_getPayloadV3",
|
||||
"engine_getPayloadV4",
|
||||
"engine_getPayloadV5",
|
||||
"engine_getBlobsV1",
|
||||
"engine_getBlobsV2",
|
||||
"engine_newPayloadV1",
|
||||
"engine_newPayloadV2",
|
||||
"engine_newPayloadV3",
|
||||
|
|
@ -112,6 +117,17 @@ var caps = []string{
|
|||
"engine_getClientVersionV1",
|
||||
}
|
||||
|
||||
var (
|
||||
// Number of blobs requested via getBlobsV2
|
||||
getBlobsRequestedCounter = metrics.NewRegisteredCounter("engine/getblobs/requested", nil)
|
||||
// Number of blobs requested via getBlobsV2 that are present in the blobpool
|
||||
getBlobsAvailableCounter = metrics.NewRegisteredCounter("engine/getblobs/available", nil)
|
||||
// Number of times getBlobsV2 responded with “hit”
|
||||
getBlobsV2RequestHit = metrics.NewRegisteredCounter("engine/getblobs/hit", nil)
|
||||
// Number of times getBlobsV2 responded with “miss”
|
||||
getBlobsV2RequestMiss = metrics.NewRegisteredCounter("engine/getblobs/miss", nil)
|
||||
)
|
||||
|
||||
type ConsensusAPI struct {
|
||||
eth *eth.Ethereum
|
||||
|
||||
|
|
@ -229,7 +245,7 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, pa
|
|||
return engine.STATUS_INVALID, attributesErr("missing withdrawals")
|
||||
case params.BeaconRoot == nil:
|
||||
return engine.STATUS_INVALID, attributesErr("missing beacon root")
|
||||
case !api.checkFork(params.Timestamp, forks.Cancun, forks.Prague):
|
||||
case !api.checkFork(params.Timestamp, forks.Cancun, forks.Prague, forks.Osaka):
|
||||
return engine.STATUS_INVALID, unsupportedForkErr("fcuV3 must only be called for cancun or prague payloads")
|
||||
}
|
||||
}
|
||||
|
|
@ -450,6 +466,14 @@ func (api *ConsensusAPI) GetPayloadV4(payloadID engine.PayloadID) (*engine.Execu
|
|||
return api.getPayload(payloadID, false)
|
||||
}
|
||||
|
||||
// GetPayloadV5 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)
|
||||
|
|
@ -464,14 +488,87 @@ 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)))
|
||||
}
|
||||
|
||||
available := api.eth.TxPool().AvailableBlobs(hashes)
|
||||
getBlobsRequestedCounter.Inc(int64(len(hashes)))
|
||||
getBlobsAvailableCounter.Inc(int64(available))
|
||||
// Optimization: check first if all blobs are available, if not, return empty response
|
||||
if available != len(hashes) {
|
||||
getBlobsV2RequestMiss.Inc(1)
|
||||
return nil, nil
|
||||
}
|
||||
getBlobsV2RequestHit.Inc(1)
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -544,7 +641,7 @@ func (api *ConsensusAPI) NewPayloadV4(params engine.ExecutableData, versionedHas
|
|||
return invalidStatus, paramsErr("nil beaconRoot post-cancun")
|
||||
case executionRequests == nil:
|
||||
return invalidStatus, paramsErr("nil executionRequests post-prague")
|
||||
case !api.checkFork(params.Timestamp, forks.Prague):
|
||||
case !api.checkFork(params.Timestamp, forks.Prague, forks.Osaka):
|
||||
return invalidStatus, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads")
|
||||
}
|
||||
requests := convertRequests(executionRequests)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue