Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions core/txpool/blobpool/blobpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -1298,6 +1298,13 @@ func (p *BlobPool) GetMetadata(hash common.Hash) *txpool.TxMetadata {
}

// GetBlobs returns a number of blobs and proofs for the given versioned hashes.
// Blobpool must place responses in the order given in the request, using null
// for any missing blobs.
//
// For instance, if the request is [A_versioned_hash, B_versioned_hash,
// C_versioned_hash] and blobpool has data for blobs A and C, but doesn't have
// data for B, the response MUST be [A, null, C].
//
// 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, version byte) ([]*kzg4844.Blob, []kzg4844.Commitment, [][]kzg4844.Proof, error) {
Expand All @@ -1317,12 +1324,13 @@ func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte) ([]*kzg4844.Blo
if _, ok := filled[vhash]; ok {
continue
}
// Retrieve the corresponding blob tx with the vhash
// Retrieve the corresponding blob tx with the vhash, skip blob resolution
// if it's not found locally and place the null instead.
p.lock.RLock()
txID, exists := p.lookup.storeidOfBlob(vhash)
p.lock.RUnlock()
if !exists {
return nil, nil, nil, fmt.Errorf("blob with vhash %x is not found", vhash)
continue
}
data, err := p.store.Get(txID)
if err != nil {
Expand Down
123 changes: 85 additions & 38 deletions core/txpool/blobpool/blobpool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"fmt"
"math"
"math/big"
"math/rand"
"os"
"path/filepath"
"reflect"
Expand All @@ -41,6 +42,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/internal/testrand"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/holiman/billy"
Expand Down Expand Up @@ -1814,10 +1816,10 @@ func TestGetBlobs(t *testing.T) {
}

cases := []struct {
start int
limit int
version byte
expErr bool
start int
limit int
fillRandom bool
version byte
}{
{
start: 0, limit: 6,
Expand All @@ -1827,6 +1829,14 @@ func TestGetBlobs(t *testing.T) {
start: 0, limit: 6,
version: types.BlobSidecarVersion1,
},
{
start: 0, limit: 6, fillRandom: true,
version: types.BlobSidecarVersion0,
},
{
start: 0, limit: 6, fillRandom: true,
version: types.BlobSidecarVersion1,
},
{
start: 3, limit: 9,
version: types.BlobSidecarVersion0,
Expand All @@ -1835,6 +1845,14 @@ func TestGetBlobs(t *testing.T) {
start: 3, limit: 9,
version: types.BlobSidecarVersion1,
},
{
start: 3, limit: 9, fillRandom: true,
version: types.BlobSidecarVersion0,
},
{
start: 3, limit: 9, fillRandom: true,
version: types.BlobSidecarVersion1,
},
{
start: 3, limit: 15,
version: types.BlobSidecarVersion0,
Expand All @@ -1843,6 +1861,14 @@ func TestGetBlobs(t *testing.T) {
start: 3, limit: 15,
version: types.BlobSidecarVersion1,
},
{
start: 3, limit: 15, fillRandom: true,
version: types.BlobSidecarVersion0,
},
{
start: 3, limit: 15, fillRandom: true,
version: types.BlobSidecarVersion1,
},
{
start: 0, limit: 18,
version: types.BlobSidecarVersion0,
Expand All @@ -1852,58 +1878,79 @@ func TestGetBlobs(t *testing.T) {
version: types.BlobSidecarVersion1,
},
{
start: 18, limit: 20,
start: 0, limit: 18, fillRandom: true,
version: types.BlobSidecarVersion0,
expErr: true,
},
{
start: 0, limit: 18, fillRandom: true,
version: types.BlobSidecarVersion1,
},
}
for i, c := range cases {
var vhashes []common.Hash
var (
vhashes []common.Hash
filled = make(map[int]struct{})
)
if c.fillRandom {
filled[len(vhashes)] = struct{}{}
vhashes = append(vhashes, testrand.Hash())
}
for j := c.start; j < c.limit; j++ {
vhashes = append(vhashes, testBlobVHashes[j])
if c.fillRandom && rand.Intn(2) == 0 {
filled[len(vhashes)] = struct{}{}
vhashes = append(vhashes, testrand.Hash())
}
}
if c.fillRandom {
filled[len(vhashes)] = struct{}{}
vhashes = append(vhashes, testrand.Hash())
}
blobs, _, proofs, err := pool.GetBlobs(vhashes, c.version)
if err != nil {
t.Errorf("Unexpected error for case %d, %v", i, err)
}

if c.expErr {
if err == nil {
t.Errorf("Unexpected return, want error for case %d", i)
// Cross validate what we received vs what we wanted
length := c.limit - c.start
wantLen := length + len(filled)
if len(blobs) != wantLen || len(proofs) != wantLen {
t.Errorf("retrieved blobs/proofs size mismatch: have %d/%d, want %d", len(blobs), len(proofs), wantLen)
continue
}

var unknown int
for j := 0; j < len(blobs); j++ {
if _, exist := filled[j]; exist {
if blobs[j] != nil || proofs[j] != nil {
t.Errorf("Unexpected blob and proof, item %d", j)
}
unknown++
continue
}
} else {
if err != nil {
t.Errorf("Unexpected error for case %d, %v", i, err)
// If an item is missing, but shouldn't, error
if blobs[j] == nil || proofs[j] == nil {
t.Errorf("tracked blob retrieval failed: item %d, hash %x", j, vhashes[j])
continue
}
// Cross validate what we received vs what we wanted
length := c.limit - c.start
if len(blobs) != length || len(proofs) != length {
t.Errorf("retrieved blobs/proofs size mismatch: have %d/%d, want %d", len(blobs), len(proofs), length)
// Item retrieved, make sure the blob matches the expectation
if *blobs[j] != *testBlobs[c.start+j-unknown] {
t.Errorf("retrieved blob mismatch: item %d, hash %x", j, vhashes[j])
continue
}
for j := 0; j < len(blobs); j++ {
// If an item is missing, but shouldn't, error
if blobs[j] == nil || proofs[j] == nil {
t.Errorf("tracked blob retrieval failed: item %d, hash %x", j, vhashes[j])
continue
// Item retrieved, make sure the proof matches the expectation
if c.version == types.BlobSidecarVersion0 {
if proofs[j][0] != testBlobProofs[c.start+j-unknown] {
t.Errorf("retrieved proof mismatch: item %d, hash %x", j, vhashes[j])
}
// Item retrieved, make sure the blob matches the expectation
if *blobs[j] != *testBlobs[c.start+j] {
t.Errorf("retrieved blob mismatch: item %d, hash %x", j, vhashes[j])
continue
}
// Item retrieved, make sure the proof matches the expectation
if c.version == types.BlobSidecarVersion0 {
if proofs[j][0] != testBlobProofs[c.start+j] {
t.Errorf("retrieved proof mismatch: item %d, hash %x", j, vhashes[j])
}
} else {
want, _ := kzg4844.ComputeCellProofs(blobs[j])
if !reflect.DeepEqual(want, proofs[j]) {
t.Errorf("retrieved proof mismatch: item %d, hash %x", j, vhashes[j])
}
} else {
want, _ := kzg4844.ComputeCellProofs(blobs[j])
if !reflect.DeepEqual(want, proofs[j]) {
t.Errorf("retrieved proof mismatch: item %d, hash %x", j, vhashes[j])
}
}
}
}

pool.Close()
}

Expand Down
57 changes: 57 additions & 0 deletions eth/catalyst/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,26 @@ func (api *ConsensusAPI) getPayload(payloadID engine.PayloadID, full bool) (*eng
}

// GetBlobsV1 returns a blob from the transaction pool.
//
// Specification:
//
// Given an array of blob versioned hashes client software MUST respond with an
// array of BlobAndProofV1 objects with matching versioned hashes, respecting the
// order of versioned hashes in the input array.
//
// Client software MUST place responses in the order given in the request, using
// null for any missing blobs. For instance:
//
// if the request is [A_versioned_hash, B_versioned_hash, C_versioned_hash] and
// client software has data for blobs A and C, but doesn't have data for B, the
// response MUST be [A, null, C].
//
// Client software MUST support request sizes of at least 128 blob versioned hashes.
// The client MUST return -38004: Too large request error if the number of requested
// blobs is too large.
//
// Client software MAY return an array of all null entries if syncing or otherwise
// unable to serve blob pool data.
func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProofV1, error) {
if len(hashes) > 128 {
return nil, engine.TooLargeRequest.With(fmt.Errorf("requested blob count too large: %v", len(hashes)))
Expand All @@ -468,6 +488,10 @@ func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProo
}
res := make([]*engine.BlobAndProofV1, len(hashes))
for i := 0; i < len(blobs); i++ {
// Skip the non-existing blob
if blobs[i] == nil {
continue
}
res[i] = &engine.BlobAndProofV1{
Blob: blobs[i][:],
Proof: proofs[i][0][:],
Expand All @@ -477,6 +501,33 @@ func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProo
}

// GetBlobsV2 returns a blob from the transaction pool.
//
// Specification:
// Refer to the specification for engine_getBlobsV1 with changes of the following:
//
// Given an array of blob versioned hashes client software MUST respond with an
// array of BlobAndProofV2 objects with matching versioned hashes, respecting
// the order of versioned hashes in the input array.
//
// Client software MUST return null in case of any missing or older version blobs.
// For instance,
//
// - if the request is [A_versioned_hash, B_versioned_hash, C_versioned_hash] and
// client software has data for blobs A and C, but doesn't have data for B, the
// response MUST be null.
//
// - if the request is [A_versioned_hash_for_blob_with_blob_proof], the response
// MUST be null as well.
//
// Note, geth internally make the conversion from old version to new one, so the
// data will be returned normally.
//
// Client software MUST support request sizes of at least 128 blob versioned
// hashes. The client MUST return -38004: Too large request error if the number
// of requested blobs is too large.
//
// Client software MUST return null if syncing or otherwise unable to serve
// blob pool data.
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)))
Expand All @@ -498,6 +549,12 @@ func (api *ConsensusAPI) GetBlobsV2(hashes []common.Hash) ([]*engine.BlobAndProo
}
res := make([]*engine.BlobAndProofV2, len(hashes))
for i := 0; i < len(blobs); i++ {
// the blob is missing, return null as response. It should
// be caught by `AvailableBlobs` though, perhaps data race
// occurs between two calls.
if blobs[i] == nil {
return nil, nil
}
var cellProofs []hexutil.Bytes
for _, proof := range proofs[i] {
cellProofs = append(cellProofs, proof[:])
Expand Down
Loading