From 076d00e9495cc487cbcb91457662f95db42f1ad6 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 13 Feb 2025 17:23:19 +0100 Subject: [PATCH 01/26] eth/catalyst: allow cancun payloads --- eth/catalyst/api.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 1e6981a76aff..4493c1fcbb7e 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -239,7 +239,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")) } } @@ -628,7 +628,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) From d9848ea6b279fad7b7baf776e4b9d0d16bb23189 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 11 Mar 2025 12:54:40 +0100 Subject: [PATCH 02/26] eth/catalyst: implement getBlobsV2 --- beacon/engine/types.go | 15 +++++++++++ crypto/kzg4844/kzg4844.go | 8 ++++++ eth/catalyst/api.go | 55 +++++++++++++++++++++++++++++++++++++-- go.mod | 1 + 4 files changed, 77 insertions(+), 2 deletions(-) diff --git a/beacon/engine/types.go b/beacon/engine/types.go index 3e52933a900d..c305507dbbb9 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -24,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" ) @@ -116,6 +117,7 @@ type BlobsBundleV1 struct { Commitments []hexutil.Bytes `json:"commitments"` Proofs []hexutil.Bytes `json:"proofs"` Blobs []hexutil.Bytes `json:"blobs"` + CellProofs []hexutil.Bytes `json:"cell_proofs"` } type BlobAndProofV1 struct { @@ -123,6 +125,11 @@ type BlobAndProofV1 struct { Proof hexutil.Bytes `json:"proof"` } +type BlobAndProofV2 struct { + Blob hexutil.Bytes `json:"blob"` + CellProofs []hexutil.Bytes `json:"cell_proofs"` +} + // JSON type overrides for ExecutionPayloadEnvelope. type executionPayloadEnvelopeMarshaling struct { BlockValue *hexutil.Big @@ -326,12 +333,20 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types. Commitments: make([]hexutil.Bytes, 0), Blobs: make([]hexutil.Bytes, 0), Proofs: make([]hexutil.Bytes, 0), + CellProofs: make([]hexutil.Bytes, 0), } for _, sidecar := range sidecars { 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][:])) + cellProofs, err := kzg4844.ComputeCells(&sidecar.Blobs[j]) + if err != nil { + panic(err) + } + for _, proof := range cellProofs { + bundle.CellProofs = append(bundle.CellProofs, hexutil.Bytes(proof[:])) + } } } diff --git a/crypto/kzg4844/kzg4844.go b/crypto/kzg4844/kzg4844.go index 0a2478cea001..1e1fc8ef8759 100644 --- a/crypto/kzg4844/kzg4844.go +++ b/crypto/kzg4844/kzg4844.go @@ -84,6 +84,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 +181,7 @@ 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) { + return ckzgComputeCells(blob) +} diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 4493c1fcbb7e..fbefbfe6b7bd 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -32,6 +32,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" @@ -511,7 +512,12 @@ func (api *ConsensusAPI) GetPayloadV3(payloadID engine.PayloadID) (*engine.Execu if !payloadID.Is(engine.PayloadV3) { return nil, engine.UnsupportedFork } - return api.getPayload(payloadID, false) + envelope, err := api.getPayload(payloadID, false) + if err != nil { + return nil, err + } + envelope.BlobsBundle.CellProofs = nil + return envelope, nil } // GetPayloadV4 returns a cached payload by id. @@ -519,7 +525,25 @@ func (api *ConsensusAPI) GetPayloadV4(payloadID engine.PayloadID) (*engine.Execu if !payloadID.Is(engine.PayloadV3) { return nil, engine.UnsupportedFork } - return api.getPayload(payloadID, false) + envelope, err := api.getPayload(payloadID, false) + if err != nil { + return nil, err + } + envelope.BlobsBundle.CellProofs = nil + return envelope, nil +} + +// 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 + } + envelope, err := api.getPayload(payloadID, false) + if err != nil { + return nil, err + } + envelope.BlobsBundle.Proofs = nil + return envelope, nil } func (api *ConsensusAPI) getPayload(payloadID engine.PayloadID, full bool) (*engine.ExecutionPayloadEnvelope, error) { @@ -550,6 +574,33 @@ func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProo 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))) + } + res := make([]*engine.BlobAndProofV2, len(hashes)) + + blobs, _ := api.eth.TxPool().GetBlobs(hashes) + for i := 0; i < len(blobs); i++ { + if blobs[i] != nil { + cellProofs, err := kzg4844.ComputeCells(blobs[i]) + if err != nil { + return nil, err + } + var proofs []hexutil.Bytes + for _, proof := range cellProofs { + proofs = append(proofs, hexutil.Bytes(proof[:])) + } + res[i] = &engine.BlobAndProofV2{ + Blob: (*blobs[i])[:], + CellProofs: proofs, + } + } + } + return res, nil +} + // NewPayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. func (api *ConsensusAPI) NewPayloadV1(params engine.ExecutableData) (engine.PayloadStatusV1, error) { if params.Withdrawals != nil { diff --git a/go.mod b/go.mod index 968268593793..3a165331fe0d 100644 --- a/go.mod +++ b/go.mod @@ -103,6 +103,7 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/deepmap/oapi-codegen v1.6.0 // indirect github.com/dlclark/regexp2 v1.7.0 // indirect + github.com/ethereum/c-kzg-4844/v2 v2.0.1 // indirect github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 // indirect github.com/getsentry/sentry-go v0.27.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect From 3691f5746e4dca5e781dca6a221b522f0e261eb3 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 11 Mar 2025 14:26:55 +0100 Subject: [PATCH 03/26] crypto: move from go-kzg-4844 to go-eth-kzg --- crypto/kzg4844/kzg4844.go | 5 ++++- go.mod | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/crypto/kzg4844/kzg4844.go b/crypto/kzg4844/kzg4844.go index 1e1fc8ef8759..264bba8ece3a 100644 --- a/crypto/kzg4844/kzg4844.go +++ b/crypto/kzg4844/kzg4844.go @@ -183,5 +183,8 @@ func IsValidVersionedHash(h []byte) bool { } func ComputeCells(blob *Blob) ([]Proof, error) { - return ckzgComputeCells(blob) + if useCKZG.Load() { + return ckzgComputeCells(blob) + } + return gokzgComputeCells(blob) } diff --git a/go.mod b/go.mod index 3a165331fe0d..b04e4da87896 100644 --- a/go.mod +++ b/go.mod @@ -101,6 +101,7 @@ require ( github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect github.com/consensys/bavard v0.1.27 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect + github.com/crate-crypto/go-eth-kzg v1.3.0 // indirect github.com/deepmap/oapi-codegen v1.6.0 // indirect github.com/dlclark/regexp2 v1.7.0 // indirect github.com/ethereum/c-kzg-4844/v2 v2.0.1 // indirect From aee852c647aacb8d901c72da183100e2b7ac1551 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 12 Mar 2025 15:17:06 +0100 Subject: [PATCH 04/26] eth/catalyst: implement getBlobsV2 --- eth/catalyst/api.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index fbefbfe6b7bd..d56be81cfa1f 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -94,7 +94,9 @@ var caps = []string{ "engine_getPayloadV2", "engine_getPayloadV3", "engine_getPayloadV4", + "engine_getPayloadV5", "engine_getBlobsV1", + "engine_getBlobsV2", "engine_newPayloadV1", "engine_newPayloadV2", "engine_newPayloadV3", From d05a127f34f5ff9e2b7d651111cd2851952b38a1 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 13 Mar 2025 11:16:16 +0100 Subject: [PATCH 05/26] beacon/engine: correct casing for cellProofs --- beacon/engine/types.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/beacon/engine/types.go b/beacon/engine/types.go index c305507dbbb9..8825b9c153d9 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -117,7 +117,7 @@ type BlobsBundleV1 struct { Commitments []hexutil.Bytes `json:"commitments"` Proofs []hexutil.Bytes `json:"proofs"` Blobs []hexutil.Bytes `json:"blobs"` - CellProofs []hexutil.Bytes `json:"cell_proofs"` + CellProofs []hexutil.Bytes `json:"cellProofs"` } type BlobAndProofV1 struct { @@ -127,7 +127,7 @@ type BlobAndProofV1 struct { type BlobAndProofV2 struct { Blob hexutil.Bytes `json:"blob"` - CellProofs []hexutil.Bytes `json:"cell_proofs"` + CellProofs []hexutil.Bytes `json:"cellProofs"` } // JSON type overrides for ExecutionPayloadEnvelope. From 8b7e1f058cf5a92824a5833ce31049a0225519bb Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 31 Mar 2025 17:29:17 +0200 Subject: [PATCH 06/26] core/types: compute cellproofs on transaction submission --- core/txpool/blobpool/blobpool.go | 10 ++++++---- core/txpool/blobpool/blobpool_test.go | 2 +- core/txpool/legacypool/legacypool.go | 4 ++-- core/txpool/subpool.go | 2 +- core/txpool/txpool.go | 8 ++++---- core/types/tx_blob.go | 16 ++++++++++++++++ eth/catalyst/api.go | 11 +++-------- 7 files changed, 33 insertions(+), 20 deletions(-) diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index e506da228d99..e0792d5ba4df 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -1298,11 +1298,12 @@ func (p *BlobPool) GetMetadata(hash common.Hash) *txpool.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 *BlobPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Proof) { +func (p *BlobPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Proof, [][]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)) + blobs = make([]*kzg4844.Blob, len(vhashes)) + proofs = make([]*kzg4844.Proof, len(vhashes)) + cell_proofs = make([][]kzg4844.Proof, len(vhashes)) ) index := make(map[common.Hash]int) for i, vhash := range vhashes { @@ -1341,10 +1342,11 @@ func (p *BlobPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844. if idx, ok := index[blobhash]; ok { blobs[idx] = &sidecar.Blobs[j] proofs[idx] = &sidecar.Proofs[j] + cell_proofs[idx] = sidecar.CellProofs[j] } } } - return blobs, proofs + return blobs, proofs, cell_proofs } // 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..ed4ef963aea6 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -417,7 +417,7 @@ func verifyBlobRetrievals(t *testing.T, pool *BlobPool) { for i := range testBlobVHashes { copy(hashes[i][:], testBlobVHashes[i][:]) } - blobs, proofs := pool.GetBlobs(hashes) + blobs, proofs, _ := pool.GetBlobs(hashes) // Cross validate what we received vs what we wanted if len(blobs) != len(hashes) || len(proofs) != len(hashes) { diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index affe44cf060f..20900a16cfc0 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -1065,8 +1065,8 @@ 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) ([]*kzg4844.Blob, []*kzg4844.Proof, [][]kzg4844.Proof) { + return nil, nil, nil } // 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..95607b9bc87e 100644 --- a/core/txpool/subpool.go +++ b/core/txpool/subpool.go @@ -136,7 +136,7 @@ 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) ([]*kzg4844.Blob, []*kzg4844.Proof, [][]kzg4844.Proof) // 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..4e62b5a8391b 100644 --- a/core/txpool/txpool.go +++ b/core/txpool/txpool.go @@ -311,17 +311,17 @@ 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) ([]*kzg4844.Blob, []*kzg4844.Proof, [][]kzg4844.Proof) { 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 // 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 blobs, proofs, cellProofs := subpool.GetBlobs(vhashes); blobs != nil { + return blobs, proofs, cellProofs } } - return nil, nil + return nil, nil, nil } // Add enqueues a batch of transactions into the pool if they are valid. Due diff --git a/core/types/tx_blob.go b/core/types/tx_blob.go index 9b1d53958fed..393ad849781e 100644 --- a/core/types/tx_blob.go +++ b/core/types/tx_blob.go @@ -58,6 +58,7 @@ type BlobTxSidecar struct { 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 + CellProofs [][]kzg4844.Proof // Cell proofs } // BlobHashes computes the blob hashes of the given blobs. @@ -157,10 +158,15 @@ func (tx *BlobTx) copy() TxData { cpy.S.Set(tx.S) } if tx.Sidecar != nil { + cellProofs := make([][]kzg4844.Proof, len(tx.Sidecar.CellProofs)) + for i := range tx.Sidecar.CellProofs { + cellProofs[i] = append([]kzg4844.Proof(nil), tx.Sidecar.CellProofs[i]...) + } cpy.Sidecar = &BlobTxSidecar{ Blobs: append([]kzg4844.Blob(nil), tx.Sidecar.Blobs...), Commitments: append([]kzg4844.Commitment(nil), tx.Sidecar.Commitments...), Proofs: append([]kzg4844.Proof(nil), tx.Sidecar.Proofs...), + CellProofs: cellProofs, } } return cpy @@ -252,10 +258,20 @@ func (tx *BlobTx) decode(input []byte) error { return err } *tx = *inner.BlobTx + // compute the cell proof + cellProofs := make([][]kzg4844.Proof, 0) + for i := range len(inner.Blobs) { + cellProof, err := kzg4844.ComputeCells(&inner.Blobs[i]) + if err != nil { + return err + } + cellProofs = append(cellProofs, cellProof) + } tx.Sidecar = &BlobTxSidecar{ Blobs: inner.Blobs, Commitments: inner.Commitments, Proofs: inner.Proofs, + CellProofs: cellProofs, } return nil } diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index d56be81cfa1f..40f4a456e876 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -32,7 +32,6 @@ 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" @@ -564,7 +563,7 @@ func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProo } res := make([]*engine.BlobAndProofV1, len(hashes)) - blobs, proofs := 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{ @@ -583,15 +582,11 @@ func (api *ConsensusAPI) GetBlobsV2(hashes []common.Hash) ([]*engine.BlobAndProo } res := make([]*engine.BlobAndProofV2, len(hashes)) - blobs, _ := api.eth.TxPool().GetBlobs(hashes) + blobs, _, cellProofs := api.eth.TxPool().GetBlobs(hashes) for i := 0; i < len(blobs); i++ { if blobs[i] != nil { - cellProofs, err := kzg4844.ComputeCells(blobs[i]) - if err != nil { - return nil, err - } var proofs []hexutil.Bytes - for _, proof := range cellProofs { + for _, proof := range cellProofs[i] { proofs = append(proofs, hexutil.Bytes(proof[:])) } res[i] = &engine.BlobAndProofV2{ From 41b0321f7d63b86589c3edd77c2f4e1e43e49947 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 31 Mar 2025 17:43:18 +0200 Subject: [PATCH 07/26] beacon/engine: don't compute cell proofs in miner --- beacon/engine/types.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/beacon/engine/types.go b/beacon/engine/types.go index 8825b9c153d9..034aaef5ac34 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -24,7 +24,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" ) @@ -340,11 +339,7 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types. 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][:])) - cellProofs, err := kzg4844.ComputeCells(&sidecar.Blobs[j]) - if err != nil { - panic(err) - } - for _, proof := range cellProofs { + for _, proof := range sidecar.CellProofs[j] { bundle.CellProofs = append(bundle.CellProofs, hexutil.Bytes(proof[:])) } } From d422284b67c4de0394f8ebc506096a2860e15d4b Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 4 Apr 2025 23:05:13 +0200 Subject: [PATCH 08/26] beacon/engine: rename cellProof --- beacon/engine/types.go | 11 ++++++----- eth/catalyst/api.go | 3 --- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/beacon/engine/types.go b/beacon/engine/types.go index 034aaef5ac34..d474ffe1d9ea 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -116,7 +116,6 @@ type BlobsBundleV1 struct { Commitments []hexutil.Bytes `json:"commitments"` Proofs []hexutil.Bytes `json:"proofs"` Blobs []hexutil.Bytes `json:"blobs"` - CellProofs []hexutil.Bytes `json:"cellProofs"` } type BlobAndProofV1 struct { @@ -332,15 +331,17 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types. Commitments: make([]hexutil.Bytes, 0), Blobs: make([]hexutil.Bytes, 0), Proofs: make([]hexutil.Bytes, 0), - CellProofs: make([]hexutil.Bytes, 0), } for _, sidecar := range sidecars { 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.CellProofs[j] { - bundle.CellProofs = append(bundle.CellProofs, hexutil.Bytes(proof[:])) + if len(sidecar.Proofs[j]) != 0 { + bundle.Proofs = append(bundle.Proofs, hexutil.Bytes(sidecar.Proofs[j][:])) + } else { + for _, proof := range sidecar.CellProofs[j] { + bundle.Proofs = append(bundle.Proofs, hexutil.Bytes(proof[:])) + } } } } diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 40f4a456e876..210b4cb4e42b 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -517,7 +517,6 @@ func (api *ConsensusAPI) GetPayloadV3(payloadID engine.PayloadID) (*engine.Execu if err != nil { return nil, err } - envelope.BlobsBundle.CellProofs = nil return envelope, nil } @@ -530,7 +529,6 @@ func (api *ConsensusAPI) GetPayloadV4(payloadID engine.PayloadID) (*engine.Execu if err != nil { return nil, err } - envelope.BlobsBundle.CellProofs = nil return envelope, nil } @@ -543,7 +541,6 @@ func (api *ConsensusAPI) GetPayloadV5(payloadID engine.PayloadID) (*engine.Execu if err != nil { return nil, err } - envelope.BlobsBundle.Proofs = nil return envelope, nil } From f5d35dab70b1f455fb5f6e66ebc6c4de313fe154 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Sat, 5 Apr 2025 11:52:00 +0200 Subject: [PATCH 09/26] go.mod: tidy --- go.mod | 2 -- 1 file changed, 2 deletions(-) diff --git a/go.mod b/go.mod index b04e4da87896..968268593793 100644 --- a/go.mod +++ b/go.mod @@ -101,10 +101,8 @@ require ( github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect github.com/consensys/bavard v0.1.27 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect - github.com/crate-crypto/go-eth-kzg v1.3.0 // indirect github.com/deepmap/oapi-codegen v1.6.0 // indirect github.com/dlclark/regexp2 v1.7.0 // indirect - github.com/ethereum/c-kzg-4844/v2 v2.0.1 // indirect github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 // indirect github.com/getsentry/sentry-go v0.27.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect From ed9324b98cc4bf78d761ed52912b98c813e2125f Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 7 Apr 2025 14:27:11 +0200 Subject: [PATCH 10/26] beacon/engine: fix cell proof logic --- beacon/engine/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon/engine/types.go b/beacon/engine/types.go index d474ffe1d9ea..d8f66163bac5 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -336,7 +336,7 @@ 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][:])) - if len(sidecar.Proofs[j]) != 0 { + if len(sidecar.CellProofs[j]) == 0 { bundle.Proofs = append(bundle.Proofs, hexutil.Bytes(sidecar.Proofs[j][:])) } else { for _, proof := range sidecar.CellProofs[j] { From 1c625667268d2e4082c4001aecd67b97bb9742ce Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 7 Apr 2025 14:35:03 +0200 Subject: [PATCH 11/26] beacon/engine: fix cell proof logic --- beacon/engine/types.go | 2 +- beacon/engine/types_test.go | 57 +++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 beacon/engine/types_test.go diff --git a/beacon/engine/types.go b/beacon/engine/types.go index d8f66163bac5..eb83ce130e83 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -336,7 +336,7 @@ 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][:])) - if len(sidecar.CellProofs[j]) == 0 { + if len(sidecar.CellProofs) == 0 { bundle.Proofs = append(bundle.Proofs, hexutil.Bytes(sidecar.Proofs[j][:])) } else { for _, proof := range sidecar.CellProofs[j] { diff --git a/beacon/engine/types_test.go b/beacon/engine/types_test.go new file mode 100644 index 000000000000..2fe06b6a40f6 --- /dev/null +++ b/beacon/engine/types_test.go @@ -0,0 +1,57 @@ +// 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: []kzg4844.Proof{emptyBlobProof}, + CellProofs: [][]kzg4844.Proof{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)) + } +} From 50b85d79faa86b91d67865cdd00f3ef4a0174512 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 8 Apr 2025 12:40:34 +0200 Subject: [PATCH 12/26] core/txpool: properly implement blobsv2 support --- beacon/engine/types.go | 10 ++-- beacon/engine/types_test.go | 3 +- core/txpool/blobpool/blobpool.go | 38 +++------------ core/txpool/blobpool/blobpool_test.go | 19 +++++++- core/txpool/legacypool/legacypool.go | 5 +- core/txpool/subpool.go | 3 +- core/txpool/txpool.go | 11 ++--- core/txpool/validation.go | 37 ++++++++++++-- core/types/tx_blob.go | 46 +++++++++++------- crypto/kzg4844/kzg4844.go | 2 + eth/catalyst/api.go | 69 ++++++++++++++++++++------- 11 files changed, 153 insertions(+), 90 deletions(-) diff --git a/beacon/engine/types.go b/beacon/engine/types.go index eb83ce130e83..a492067d412e 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -336,13 +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][:])) - if len(sidecar.CellProofs) == 0 { - bundle.Proofs = append(bundle.Proofs, hexutil.Bytes(sidecar.Proofs[j][:])) - } else { - for _, proof := range sidecar.CellProofs[j] { - bundle.Proofs = append(bundle.Proofs, hexutil.Bytes(proof[:])) - } - } + } + 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 index 2fe06b6a40f6..7ffdf4e81823 100644 --- a/beacon/engine/types_test.go +++ b/beacon/engine/types_test.go @@ -47,8 +47,7 @@ func TestBlobs(t *testing.T) { sidecarWithCellProofs := &types.BlobTxSidecar{ Blobs: []kzg4844.Blob{*emptyBlob}, Commitments: []kzg4844.Commitment{emptyBlobCommit}, - Proofs: []kzg4844.Proof{emptyBlobProof}, - CellProofs: [][]kzg4844.Proof{emptyCellProof}, + Proofs: emptyCellProof, } env = BlockToExecutableData(block, common.Big0, []*types.BlobTxSidecar{sidecarWithCellProofs}, nil) if len(env.BlobsBundle.Proofs) != 128 { diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index e0792d5ba4df..87aefb9756ac 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,28 +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, [][]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)) - cell_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 { @@ -1336,17 +1320,9 @@ 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] - cell_proofs[idx] = sidecar.CellProofs[j] - } - } + sidecars[idx] = item.BlobTxSidecar() } - return blobs, proofs, cell_proofs + return sidecars } // 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 ed4ef963aea6..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 20900a16cfc0..a69ff70251e0 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,8 @@ 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, [][]kzg4844.Proof) { - return nil, nil, nil +func (pool *LegacyPool) GetBlobs(vhashes []common.Hash) []*types.BlobTxSidecar { + return nil } // 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 95607b9bc87e..70595934a5cb 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,7 @@ 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, [][]kzg4844.Proof) + GetBlobs(vhashes []common.Hash) []*types.BlobTxSidecar // 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 4e62b5a8391b..5bc83bab58e2 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,17 @@ 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, [][]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, cellProofs := subpool.GetBlobs(vhashes); blobs != nil { - return blobs, proofs, cellProofs + if sidecars := subpool.GetBlobs(vhashes); sidecars != nil { + return sidecars } } - return nil, nil, nil + return nil } // 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..da4828eab854 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 { @@ -185,6 +192,30 @@ func validateBlobSidecar(hashes []common.Hash, sidecar *types.BlobTxSidecar) err return nil } +func validateBlobSidecarOsaka(hashes []common.Hash, sidecar *types.BlobTxSidecar) error { + 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)*kzg4844.CellProofsPerBlob != len(hashes) { + return fmt.Errorf("invalid number of %d blob proofs compared to %d blob hashes", len(sidecar.Proofs), len(hashes)) + } + 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 393ad849781e..742539b8b48c 100644 --- a/core/types/tx_blob.go +++ b/core/types/tx_blob.go @@ -58,7 +58,6 @@ type BlobTxSidecar struct { 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 - CellProofs [][]kzg4844.Proof // Cell proofs } // BlobHashes computes the blob hashes of the given blobs. @@ -71,6 +70,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 { @@ -111,6 +124,14 @@ type blobTxWithBlobs struct { Proofs []kzg4844.Proof } +type versionedBlobTxWithBlobs struct { + Version byte + BlobTx *BlobTx + 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{ @@ -158,15 +179,10 @@ func (tx *BlobTx) copy() TxData { cpy.S.Set(tx.S) } if tx.Sidecar != nil { - cellProofs := make([][]kzg4844.Proof, len(tx.Sidecar.CellProofs)) - for i := range tx.Sidecar.CellProofs { - cellProofs[i] = append([]kzg4844.Proof(nil), tx.Sidecar.CellProofs[i]...) - } cpy.Sidecar = &BlobTxSidecar{ Blobs: append([]kzg4844.Blob(nil), tx.Sidecar.Blobs...), Commitments: append([]kzg4844.Commitment(nil), tx.Sidecar.Commitments...), Proofs: append([]kzg4844.Proof(nil), tx.Sidecar.Proofs...), - CellProofs: cellProofs, } } return cpy @@ -252,26 +268,24 @@ 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 if err := rlp.DecodeBytes(input, &inner); err != nil { - return err - } - *tx = *inner.BlobTx - // compute the cell proof - cellProofs := make([][]kzg4844.Proof, 0) - for i := range len(inner.Blobs) { - cellProof, err := kzg4844.ComputeCells(&inner.Blobs[i]) - if err != nil { + var innerWithVersion versionedBlobTxWithBlobs + if err := rlp.DecodeBytes(input, &innerWithVersion); err != nil { return err } - cellProofs = append(cellProofs, cellProof) + inner.BlobTx = innerWithVersion.BlobTx + inner.Blobs = innerWithVersion.Blobs + inner.Commitments = innerWithVersion.Commitments + inner.Proofs = innerWithVersion.Proofs } + *tx = *inner.BlobTx tx.Sidecar = &BlobTxSidecar{ Blobs: inner.Blobs, Commitments: inner.Commitments, Proofs: inner.Proofs, - CellProofs: cellProofs, } return nil } diff --git a/crypto/kzg4844/kzg4844.go b/crypto/kzg4844/kzg4844.go index 264bba8ece3a..8e578bf79df0 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. diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 210b4cb4e42b..13b85fc454ae 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" @@ -558,14 +560,28 @@ 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 { + // 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][:], + } } } } @@ -577,18 +593,35 @@ func (api *ConsensusAPI) GetBlobsV2(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.BlobAndProofV2, len(hashes)) + var ( + res = make([]*engine.BlobAndProofV2, len(hashes)) + index = make(map[common.Hash]int) + sidecars = api.eth.TxPool().GetBlobs(hashes) + ) - blobs, _, cellProofs := api.eth.TxPool().GetBlobs(hashes) - for i := 0; i < len(blobs); i++ { - if blobs[i] != nil { - var proofs []hexutil.Bytes - for _, proof := range cellProofs[i] { - proofs = append(proofs, hexutil.Bytes(proof[:])) - } - res[i] = &engine.BlobAndProofV2{ - Blob: (*blobs[i])[:], - CellProofs: proofs, + for i, hash := range hashes { + index[hash] = i + } + for i, sidecar := range sidecars { + if res[i] != nil { + // already filled + continue + } + if len(sidecar.Blobs) != len(sidecar.Proofs)*kzg4844.CellProofsPerBlob { + return nil, errors.New("NORMAL PROOFS IN GETBLOBSV2, THIS SHOULD NEVER HAPPEN, PLEASE REPORT") + } + blobHashes := sidecar.BlobHashes() + for bIdx, hash := range blobHashes { + if idx, ok := index[hash]; ok { + proofs := sidecar.CellProofsAt(bIdx) + var cellProofs []hexutil.Bytes + for _, proof := range proofs { + cellProofs = append(cellProofs, proof[:]) + } + res[idx] = &engine.BlobAndProofV2{ + Blob: sidecar.Blobs[bIdx][:], + CellProofs: cellProofs, + } } } } From dd9bef2451c901b1f53d53247d1c950b0dc0109b Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 8 Apr 2025 13:19:18 +0200 Subject: [PATCH 13/26] miner: skip v1 transactions post-prague --- core/txpool/blobpool/blobpool.go | 13 +++++++++++++ core/txpool/legacypool/legacypool.go | 6 ++++++ core/txpool/subpool.go | 4 ++++ core/txpool/txpool.go | 25 +++++++++++++++++++++++++ eth/catalyst/api.go | 7 +++++++ miner/worker.go | 12 ++++++++++++ 6 files changed, 67 insertions(+) diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index 87aefb9756ac..2c50809716fe 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -1325,6 +1325,19 @@ func (p *BlobPool) GetBlobs(vhashes []common.Hash) []*types.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 true +} + // Add inserts a set of blob transactions into the pool if they pass validation (both // consensus validity and pool restrictions). // diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index a69ff70251e0..bf1014d9e0b4 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -1068,6 +1068,12 @@ 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 // given hash. func (pool *LegacyPool) Has(hash common.Hash) bool { diff --git a/core/txpool/subpool.go b/core/txpool/subpool.go index 70595934a5cb..90f1d5d9e519 100644 --- a/core/txpool/subpool.go +++ b/core/txpool/subpool.go @@ -137,6 +137,10 @@ type SubPool interface { // retrieve blobs from the pools directly instead of the network. 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. // This check is meant as a static check which can be performed without holding the diff --git a/core/txpool/txpool.go b/core/txpool/txpool.go index 5bc83bab58e2..666fdddcdb7d 100644 --- a/core/txpool/txpool.go +++ b/core/txpool/txpool.go @@ -323,6 +323,31 @@ func (p *TxPool) GetBlobs(vhashes []common.Hash) []*types.BlobTxSidecar { 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 // to the large transaction churn, add may postpone fully integrating the tx // to a later point to batch multiple ones together. diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 13b85fc454ae..1c5390e7e353 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -593,6 +593,13 @@ func (api *ConsensusAPI) GetBlobsV2(hashes []common.Hash) ([]*engine.BlobAndProo 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) diff --git a/miner/worker.go b/miner/worker.go index d80cb8913baf..342301524753 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,17 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran continue } + if miner.chainConfig.IsOsaka(env.header.Number, env.header.Time) { + if sidecar := tx.BlobTxSidecar(); sidecar != nil { + if len(sidecar.Blobs) != len(sidecar.Proofs)*kzg4844.CellProofsPerBlob { + log.Warn("Ignoring V1 blob transaction post-Osaka", "hash", ltx.Hash) + 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) From c1eebc2751205ffd73cc8f94d7abba9e441f5a82 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 8 Apr 2025 13:22:39 +0200 Subject: [PATCH 14/26] miner: skip v1 transactions post-prague --- miner/worker.go | 1 + 1 file changed, 1 insertion(+) diff --git a/miner/worker.go b/miner/worker.go index 342301524753..9a8612775df1 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -391,6 +391,7 @@ 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 len(sidecar.Blobs) != len(sidecar.Proofs)*kzg4844.CellProofsPerBlob { From efb7b58b071c0b60d7bd7b7dd0f3d15ee609bba1 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 8 Apr 2025 13:45:36 +0200 Subject: [PATCH 15/26] eth/catalyst: don't panic --- eth/catalyst/api.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 1c5390e7e353..65b0aca08e6b 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -614,6 +614,10 @@ func (api *ConsensusAPI) GetBlobsV2(hashes []common.Hash) ([]*engine.BlobAndProo // already filled continue } + if sidecar == nil { + // not found, return empty response + return nil, nil + } if len(sidecar.Blobs) != len(sidecar.Proofs)*kzg4844.CellProofsPerBlob { return nil, errors.New("NORMAL PROOFS IN GETBLOBSV2, THIS SHOULD NEVER HAPPEN, PLEASE REPORT") } From 6349f6afb52fbc5c858c90be9993b985caa19bca Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 8 Apr 2025 13:46:56 +0200 Subject: [PATCH 16/26] miner: happy lint --- miner/worker.go | 1 - 1 file changed, 1 deletion(-) diff --git a/miner/worker.go b/miner/worker.go index 9a8612775df1..7943b61bb8b3 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -400,7 +400,6 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran continue } } - } // Error may be ignored here. The error has already been checked From 3ef538502f82159a8facce6a282d53fe8277c64c Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 8 Apr 2025 14:51:26 +0200 Subject: [PATCH 17/26] core/types: fix rlp decoding --- core/txpool/validation.go | 6 ++++++ core/types/tx_blob.go | 32 +++++++++++++++++++++++--------- eth/catalyst/api.go | 2 +- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/core/txpool/validation.go b/core/txpool/validation.go index da4828eab854..a1adcf8138cb 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -173,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)) } @@ -193,6 +196,9 @@ func validateBlobSidecar(hashes []common.Hash, sidecar *types.BlobTxSidecar) err } 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)) } diff --git a/core/types/tx_blob.go b/core/types/tx_blob.go index 742539b8b48c..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 @@ -125,8 +126,8 @@ type blobTxWithBlobs struct { } type versionedBlobTxWithBlobs struct { - Version byte BlobTx *BlobTx + Version byte Blobs []kzg4844.Blob Commitments []kzg4844.Commitment Proofs []kzg4844.Proof @@ -240,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, @@ -269,20 +281,22 @@ func (tx *BlobTx) decode(input []byte) error { 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 { - var innerWithVersion versionedBlobTxWithBlobs - if err := rlp.DecodeBytes(input, &innerWithVersion); err != nil { + var innerV0 blobTxWithBlobs + if err := rlp.DecodeBytes(input, &innerV0); err != nil { return err } - inner.BlobTx = innerWithVersion.BlobTx - inner.Blobs = innerWithVersion.Blobs - inner.Commitments = innerWithVersion.Commitments - inner.Proofs = innerWithVersion.Proofs + 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/eth/catalyst/api.go b/eth/catalyst/api.go index 65b0aca08e6b..ef1e3aac4619 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -571,7 +571,7 @@ func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProo index[hash] = i } for i, sidecar := range sidecars { - if res[i] != nil { + if res[i] != nil && sidecar == nil { // already filled continue } From 284b63d7a19636c55d03590951d185acae917f63 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 8 Apr 2025 15:13:09 +0200 Subject: [PATCH 18/26] eth/catalyst: don't panic --- eth/catalyst/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index ef1e3aac4619..ca4a9386749b 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -571,7 +571,7 @@ func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProo index[hash] = i } for i, sidecar := range sidecars { - if res[i] != nil && sidecar == nil { + if res[i] != nil || sidecar == nil { // already filled continue } From 0a6e0a1cb3d3c037516b500edd1255d67fc374d5 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 8 Apr 2025 15:40:02 +0200 Subject: [PATCH 19/26] miner: compute cell proofs on demand --- miner/worker.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/miner/worker.go b/miner/worker.go index 7943b61bb8b3..b480e91e21b9 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -394,10 +394,18 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran // 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 len(sidecar.Blobs) != len(sidecar.Proofs)*kzg4844.CellProofsPerBlob { - log.Warn("Ignoring V1 blob transaction post-Osaka", "hash", ltx.Hash) - txs.Pop() - continue + 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 } } } From fb73b241b2078a5e517cf241da501fe1909d9b2b Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 8 Apr 2025 15:52:34 +0200 Subject: [PATCH 20/26] core/txpool: fix mining bug --- core/txpool/validation.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/txpool/validation.go b/core/txpool/validation.go index a1adcf8138cb..b0bbdbb969b8 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -202,7 +202,7 @@ func validateBlobSidecarOsaka(hashes []common.Hash, sidecar *types.BlobTxSidecar 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)*kzg4844.CellProofsPerBlob != len(hashes) { + if len(sidecar.Proofs) != len(hashes)*kzg4844.CellProofsPerBlob { return fmt.Errorf("invalid number of %d blob proofs compared to %d blob hashes", len(sidecar.Proofs), len(hashes)) } if err := sidecar.ValidateBlobCommitmentHashes(hashes); err != nil { From 5466600e35fab524d6dd0a13070c4d70b29b1d71 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 8 Apr 2025 16:16:39 +0200 Subject: [PATCH 21/26] core/txpool: better log --- core/txpool/validation.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/txpool/validation.go b/core/txpool/validation.go index b0bbdbb969b8..64e85a0e6e7c 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -203,7 +203,7 @@ func validateBlobSidecarOsaka(hashes []common.Hash, sidecar *types.BlobTxSidecar 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 compared to %d blob hashes", len(sidecar.Proofs), len(hashes)) + 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 From af6c6741b473a24cb8c2bfc6cfaf380bf59cb5c4 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 8 Apr 2025 16:57:09 +0200 Subject: [PATCH 22/26] eth/catalyst: no getblobsv2 error --- eth/catalyst/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index ca4a9386749b..cb69607e416d 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -618,7 +618,7 @@ func (api *ConsensusAPI) GetBlobsV2(hashes []common.Hash) ([]*engine.BlobAndProo // not found, return empty response return nil, nil } - if len(sidecar.Blobs) != len(sidecar.Proofs)*kzg4844.CellProofsPerBlob { + if len(sidecar.Proofs) != len(sidecar.Blobs)*kzg4844.CellProofsPerBlob { return nil, errors.New("NORMAL PROOFS IN GETBLOBSV2, THIS SHOULD NEVER HAPPEN, PLEASE REPORT") } blobHashes := sidecar.BlobHashes() From 80e92e556fc6b015c91611fd6b0444e4fa922501 Mon Sep 17 00:00:00 2001 From: Minhyuk Kim Date: Mon, 14 Apr 2025 01:46:22 +0900 Subject: [PATCH 23/26] Rename response field of GetBlobsV2 to --- beacon/engine/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon/engine/types.go b/beacon/engine/types.go index a492067d412e..76bfd22a2367 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -125,7 +125,7 @@ type BlobAndProofV1 struct { type BlobAndProofV2 struct { Blob hexutil.Bytes `json:"blob"` - CellProofs []hexutil.Bytes `json:"cellProofs"` + CellProofs []hexutil.Bytes `json:"proofs"` } // JSON type overrides for ExecutionPayloadEnvelope. From 479997f1ec9714495cb5d83fc411ca116147938e Mon Sep 17 00:00:00 2001 From: MariusVanDerWijden Date: Tue, 29 Apr 2025 16:12:54 +0200 Subject: [PATCH 24/26] crypto/kzg4844: fix rebase --- crypto/kzg4844/kzg4844.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crypto/kzg4844/kzg4844.go b/crypto/kzg4844/kzg4844.go index 8e578bf79df0..5e14fbbd5e08 100644 --- a/crypto/kzg4844/kzg4844.go +++ b/crypto/kzg4844/kzg4844.go @@ -186,7 +186,7 @@ func IsValidVersionedHash(h []byte) bool { func ComputeCells(blob *Blob) ([]Proof, error) { if useCKZG.Load() { - return ckzgComputeCells(blob) + return ckzgComputeCellProofs(blob) } - return gokzgComputeCells(blob) + return gokzgComputeCellProofs(blob) } From 4df10987d2922ddef7a6df56a8914ef84c195cbb Mon Sep 17 00:00:00 2001 From: MariusVanDerWijden Date: Tue, 29 Apr 2025 19:08:00 +0200 Subject: [PATCH 25/26] eth/catalyst: rework error message --- eth/catalyst/api.go | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index cb69607e416d..a24923f1046f 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -515,11 +515,7 @@ func (api *ConsensusAPI) GetPayloadV3(payloadID engine.PayloadID) (*engine.Execu if !payloadID.Is(engine.PayloadV3) { return nil, engine.UnsupportedFork } - envelope, err := api.getPayload(payloadID, false) - if err != nil { - return nil, err - } - return envelope, nil + return api.getPayload(payloadID, false) } // GetPayloadV4 returns a cached payload by id. @@ -527,11 +523,7 @@ func (api *ConsensusAPI) GetPayloadV4(payloadID engine.PayloadID) (*engine.Execu if !payloadID.Is(engine.PayloadV3) { return nil, engine.UnsupportedFork } - envelope, err := api.getPayload(payloadID, false) - if err != nil { - return nil, err - } - return envelope, nil + return api.getPayload(payloadID, false) } // GetPayloadV4 returns a cached payload by id. @@ -539,11 +531,7 @@ func (api *ConsensusAPI) GetPayloadV5(payloadID engine.PayloadID) (*engine.Execu if !payloadID.Is(engine.PayloadV3) { return nil, engine.UnsupportedFork } - envelope, err := api.getPayload(payloadID, false) - if err != nil { - return nil, err - } - return envelope, nil + return api.getPayload(payloadID, false) } func (api *ConsensusAPI) getPayload(payloadID engine.PayloadID, full bool) (*engine.ExecutionPayloadEnvelope, error) { @@ -618,8 +606,8 @@ func (api *ConsensusAPI) GetBlobsV2(hashes []common.Hash) ([]*engine.BlobAndProo // not found, return empty response return nil, nil } - if len(sidecar.Proofs) != len(sidecar.Blobs)*kzg4844.CellProofsPerBlob { - return nil, errors.New("NORMAL PROOFS IN GETBLOBSV2, THIS SHOULD NEVER HAPPEN, PLEASE REPORT") + 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 { From 8c220595bb91017afcc1b61458e7ef1ef3fa93df Mon Sep 17 00:00:00 2001 From: Derek Guenther Date: Fri, 9 May 2025 03:31:50 -0400 Subject: [PATCH 26/26] eth/catalyst: allow duplicate blob hashes (#31788) While running kurtosis and spamoor, I noticed occasional `null` entries coming back from `getBlobsV2`. After investigating, I found that spamoor can use the same blob data across multiple transactions, so with larger blob counts, there's an increased chance that a blob is included in a block more than once. As a result, previous indexes in the hash-to-index map in `getBlobsV2` would get overwritten. I changed the map from `map[common.Hash]int` to `map[common.Hash][]int` to handle this case. I decided to go this route because it's the smallest-possible fix, but it could also make sense to update `blobpool.GetBlobs` to de-duplicate the hashes. --- eth/catalyst/api.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index a24923f1046f..623a09c5852e 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -590,12 +590,12 @@ func (api *ConsensusAPI) GetBlobsV2(hashes []common.Hash) ([]*engine.BlobAndProo // pull up the blob hashes var ( res = make([]*engine.BlobAndProofV2, len(hashes)) - index = make(map[common.Hash]int) + index = make(map[common.Hash][]int) sidecars = api.eth.TxPool().GetBlobs(hashes) ) for i, hash := range hashes { - index[hash] = i + index[hash] = append(index[hash], i) } for i, sidecar := range sidecars { if res[i] != nil { @@ -611,15 +611,17 @@ func (api *ConsensusAPI) GetBlobsV2(hashes []common.Hash) ([]*engine.BlobAndProo } blobHashes := sidecar.BlobHashes() for bIdx, hash := range blobHashes { - if idx, ok := index[hash]; ok { + if idxes, ok := index[hash]; ok { proofs := sidecar.CellProofsAt(bIdx) var cellProofs []hexutil.Bytes for _, proof := range proofs { cellProofs = append(cellProofs, proof[:]) } - res[idx] = &engine.BlobAndProofV2{ - Blob: sidecar.Blobs[bIdx][:], - CellProofs: cellProofs, + for _, idx := range idxes { + res[idx] = &engine.BlobAndProofV2{ + Blob: sidecar.Blobs[bIdx][:], + CellProofs: cellProofs, + } } } }