diff --git a/beacon/engine/types.go b/beacon/engine/types.go index 3e52933a900d..76bfd22a2367 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -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[:])) } } diff --git a/beacon/engine/types_test.go b/beacon/engine/types_test.go new file mode 100644 index 000000000000..7ffdf4e81823 --- /dev/null +++ b/beacon/engine/types_test.go @@ -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 . + +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)) + } +} diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index e506da228d99..2c50809716fe 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -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 diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go index 0a323179a6af..060b241e251f 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -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)) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index affe44cf060f..bf1014d9e0b4 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -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 diff --git a/core/txpool/subpool.go b/core/txpool/subpool.go index 8cfc14f16407..90f1d5d9e519 100644 --- a/core/txpool/subpool.go +++ b/core/txpool/subpool.go @@ -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. diff --git a/core/txpool/txpool.go b/core/txpool/txpool.go index cc8f74c1b8e5..666fdddcdb7d 100644 --- a/core/txpool/txpool.go +++ b/core/txpool/txpool.go @@ -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 diff --git a/core/txpool/validation.go b/core/txpool/validation.go index e370f2ce84f7..64e85a0e6e7c 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -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,33 @@ 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.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 + for i := range sidecar.Blobs { + // TODO verify the cell proof here + _ = i + /* + if err := kzg4844.VerifyBlobProof(&sidecar.Blobs[i], sidecar.Commitments[i], sidecar.Proofs[i]); err != nil { + return fmt.Errorf("invalid blob %d: %v", i, err) + } + */ + } + return nil +} + // ValidationOptionsWithState define certain differences between stateful transaction // validation across the different pools without having to duplicate those checks. type ValidationOptionsWithState struct { diff --git a/core/types/tx_blob.go b/core/types/tx_blob.go index 9b1d53958fed..73832c7d3082 100644 --- a/core/types/tx_blob.go +++ b/core/types/tx_blob.go @@ -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, diff --git a/crypto/kzg4844/kzg4844.go b/crypto/kzg4844/kzg4844.go index 0a2478cea001..5e14fbbd5e08 100644 --- a/crypto/kzg4844/kzg4844.go +++ b/crypto/kzg4844/kzg4844.go @@ -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). @@ -177,3 +183,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) +} diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 1e6981a76aff..623a09c5852e 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -18,6 +18,7 @@ package catalyst import ( + "crypto/sha256" "errors" "fmt" "strconv" @@ -32,6 +33,7 @@ import ( "github.com/ethereum/go-ethereum/core/stateless" "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/eth" "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/internal/version" @@ -93,7 +95,9 @@ var caps = []string{ "engine_getPayloadV2", "engine_getPayloadV3", "engine_getPayloadV4", + "engine_getPayloadV5", "engine_getBlobsV1", + "engine_getBlobsV2", "engine_newPayloadV1", "engine_newPayloadV2", "engine_newPayloadV3", @@ -239,7 +243,7 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, pa if params.BeaconRoot == nil { return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("missing beacon root")) } - if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Cancun && api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Prague { + if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Cancun && api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Prague && api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Osaka { return engine.STATUS_INVALID, engine.UnsupportedFork.With(errors.New("forkchoiceUpdatedV3 must only be called for cancun payloads")) } } @@ -522,6 +526,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) @@ -536,14 +548,81 @@ 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 { + return nil, fmt.Errorf("GetBlobs queried V0 transaction: index %v, blobhashes %v", index, sidecar.BlobHashes()) + } + 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, + } + } } } } @@ -628,7 +707,7 @@ func (api *ConsensusAPI) NewPayloadV4(params engine.ExecutableData, versionedHas return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil executionRequests post-prague")) } - if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Prague { + if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Prague && api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Osaka { return engine.PayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("newPayloadV4 must only be called for prague payloads")) } requests := convertRequests(executionRequests) diff --git a/miner/worker.go b/miner/worker.go index d80cb8913baf..b480e91e21b9 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -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)