all: define constructor for BlobSidecar (#32213)
Some checks are pending
/ Linux Build (push) Waiting to run
/ Linux Build (arm) (push) Waiting to run
/ Windows Build (push) Waiting to run
/ Docker Image (push) Waiting to run

The main purpose of this change is to enforce the version setting when
constructing the blobSidecar, avoiding creating sidecar with wrong/default 
version tag.
This commit is contained in:
rjl493456442 2025-07-17 11:19:20 +08:00 committed by GitHub
parent a487729d83
commit 0dacfef8ac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 97 additions and 122 deletions

View file

@ -34,21 +34,13 @@ func TestBlobs(t *testing.T) {
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},
}
sidecarWithoutCellProofs := types.NewBlobTxSidecar(types.BlobSidecarVersion0, []kzg4844.Blob{*emptyBlob}, []kzg4844.Commitment{emptyBlobCommit}, []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,
}
sidecarWithCellProofs := types.NewBlobTxSidecar(types.BlobSidecarVersion0, []kzg4844.Blob{*emptyBlob}, []kzg4844.Commitment{emptyBlobCommit}, emptyCellProof)
env = BlockToExecutableData(block, common.Big0, []*types.BlobTxSidecar{sidecarWithCellProofs}, nil)
if len(env.BlobsBundle.Proofs) != 128 {
t.Fatalf("Expect 128 proofs in blobs bundle, got %v", len(env.BlobsBundle.Proofs))

View file

@ -879,11 +879,7 @@ func makeSidecar(data ...byte) *types.BlobTxSidecar {
commitments = append(commitments, c)
proofs = append(proofs, p)
}
return &types.BlobTxSidecar{
Blobs: blobs,
Commitments: commitments,
Proofs: proofs,
}
return types.NewBlobTxSidecar(types.BlobSidecarVersion0, blobs, commitments, proofs)
}
func (s *Suite) makeBlobTxs(count, blobs int, discriminator byte) (txs types.Transactions) {
@ -988,14 +984,10 @@ func (s *Suite) TestBlobViolations(t *utesting.T) {
// data has been modified to produce a different commitment hash.
func mangleSidecar(tx *types.Transaction) *types.Transaction {
sidecar := tx.BlobTxSidecar()
copy := types.BlobTxSidecar{
Blobs: append([]kzg4844.Blob{}, sidecar.Blobs...),
Commitments: append([]kzg4844.Commitment{}, sidecar.Commitments...),
Proofs: append([]kzg4844.Proof{}, sidecar.Proofs...),
}
cpy := sidecar.Copy()
// zero the first commitment to alter the sidecar hash
copy.Commitments[0] = kzg4844.Commitment{}
return tx.WithBlobTxSidecar(&copy)
cpy.Commitments[0] = kzg4844.Commitment{}
return tx.WithBlobTxSidecar(cpy)
}
func (s *Suite) TestBlobTxWithoutSidecar(t *utesting.T) {

View file

@ -238,11 +238,7 @@ func makeMultiBlobTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCa
BlobFeeCap: uint256.NewInt(blobFeeCap),
BlobHashes: blobHashes,
Value: uint256.NewInt(100),
Sidecar: &types.BlobTxSidecar{
Blobs: blobs,
Commitments: commitments,
Proofs: proofs,
},
Sidecar: types.NewBlobTxSidecar(types.BlobSidecarVersion0, blobs, commitments, proofs),
}
return types.MustSignNewTx(key, types.LatestSigner(params.MainnetChainConfig), blobtx)
}
@ -265,11 +261,7 @@ func makeUnsignedTxWithTestBlob(nonce uint64, gasTipCap uint64, gasFeeCap uint64
BlobFeeCap: uint256.NewInt(blobFeeCap),
BlobHashes: []common.Hash{testBlobVHashes[blobIdx]},
Value: uint256.NewInt(100),
Sidecar: &types.BlobTxSidecar{
Blobs: []kzg4844.Blob{*testBlobs[blobIdx]},
Commitments: []kzg4844.Commitment{testBlobCommits[blobIdx]},
Proofs: []kzg4844.Proof{testBlobProofs[blobIdx]},
},
Sidecar: types.NewBlobTxSidecar(types.BlobSidecarVersion0, []kzg4844.Blob{*testBlobs[blobIdx]}, []kzg4844.Commitment{testBlobCommits[blobIdx]}, []kzg4844.Proof{testBlobProofs[blobIdx]}),
}
}

View file

@ -185,7 +185,7 @@ func validateBlobTx(tx *types.Transaction, head *types.Header, opts *ValidationO
}
func validateBlobSidecarLegacy(sidecar *types.BlobTxSidecar, hashes []common.Hash) error {
if sidecar.Version != 0 {
if sidecar.Version != types.BlobSidecarVersion0 {
return fmt.Errorf("invalid sidecar version pre-osaka: %v", sidecar.Version)
}
if len(sidecar.Proofs) != len(hashes) {
@ -200,7 +200,7 @@ func validateBlobSidecarLegacy(sidecar *types.BlobTxSidecar, hashes []common.Has
}
func validateBlobSidecarOsaka(sidecar *types.BlobTxSidecar, hashes []common.Hash) error {
if sidecar.Version != 1 {
if sidecar.Version != types.BlobSidecarVersion1 {
return fmt.Errorf("invalid sidecar version post-osaka: %v", sidecar.Version)
}
if len(sidecar.Proofs) != len(hashes)*kzg4844.CellProofsPerBlob {

View file

@ -31,6 +31,18 @@ import (
"github.com/holiman/uint256"
)
const (
// BlobSidecarVersion0 includes a single proof for verifying the entire blob
// against its commitment. Used when the full blob is available and needs to
// be checked as a whole.
BlobSidecarVersion0 = byte(0)
// BlobSidecarVersion1 includes multiple cell proofs for verifying specific
// blob elements (cells). Used in scenarios like data availability sampling,
// where only portions of the blob are verified individually.
BlobSidecarVersion1 = byte(1)
)
// BlobTx represents an EIP-4844 transaction.
type BlobTx struct {
ChainID *uint256.Int
@ -63,6 +75,16 @@ type BlobTxSidecar struct {
Proofs []kzg4844.Proof // Proofs needed by the blob pool
}
// NewBlobTxSidecar initialises the BlobTxSidecar object with the provided parameters.
func NewBlobTxSidecar(version byte, blobs []kzg4844.Blob, commitments []kzg4844.Commitment, proofs []kzg4844.Proof) *BlobTxSidecar {
return &BlobTxSidecar{
Version: version,
Blobs: blobs,
Commitments: commitments,
Proofs: proofs,
}
}
// BlobHashes computes the blob hashes of the given blobs.
func (sc *BlobTxSidecar) BlobHashes() []common.Hash {
hasher := sha256.New()
@ -76,7 +98,7 @@ func (sc *BlobTxSidecar) BlobHashes() []common.Hash {
// CellProofsAt returns the cell proofs for blob with index idx.
// This method is only valid for sidecars with version 1.
func (sc *BlobTxSidecar) CellProofsAt(idx int) ([]kzg4844.Proof, error) {
if sc.Version != 1 {
if sc.Version != BlobSidecarVersion1 {
return nil, fmt.Errorf("cell proof unsupported, version: %d", sc.Version)
}
if idx < 0 || idx >= len(sc.Blobs) {
@ -89,6 +111,25 @@ func (sc *BlobTxSidecar) CellProofsAt(idx int) ([]kzg4844.Proof, error) {
return sc.Proofs[index : index+kzg4844.CellProofsPerBlob], nil
}
// ToV1 converts the BlobSidecar to version 1, attaching the cell proofs.
func (sc *BlobTxSidecar) ToV1() error {
if sc.Version == BlobSidecarVersion1 {
return nil
}
if sc.Version == BlobSidecarVersion0 {
sc.Proofs = make([]kzg4844.Proof, 0, len(sc.Blobs)*kzg4844.CellProofsPerBlob)
for _, blob := range sc.Blobs {
cellProofs, err := kzg4844.ComputeCellProofs(&blob)
if err != nil {
return err
}
sc.Proofs = append(sc.Proofs, cellProofs...)
}
sc.Version = BlobSidecarVersion1
}
return nil
}
// 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 {
@ -121,6 +162,19 @@ func (sc *BlobTxSidecar) ValidateBlobCommitmentHashes(hashes []common.Hash) erro
return nil
}
// Copy returns a deep-copied BlobTxSidecar object.
func (sc *BlobTxSidecar) Copy() *BlobTxSidecar {
return &BlobTxSidecar{
Version: sc.Version,
// The element of these slice is fix-size byte array,
// therefore slices.Clone will actually deep copy by value.
Blobs: slices.Clone(sc.Blobs),
Commitments: slices.Clone(sc.Commitments),
Proofs: slices.Clone(sc.Proofs),
}
}
// blobTxWithBlobs represents blob tx with its corresponding sidecar.
// This is an interface because sidecars are versioned.
type blobTxWithBlobs interface {
@ -148,7 +202,7 @@ func (btx *blobTxWithBlobsV0) tx() *BlobTx {
}
func (btx *blobTxWithBlobsV0) assign(sc *BlobTxSidecar) error {
sc.Version = 0
sc.Version = BlobSidecarVersion0
sc.Blobs = btx.Blobs
sc.Commitments = btx.Commitments
sc.Proofs = btx.Proofs
@ -160,10 +214,10 @@ func (btx *blobTxWithBlobsV1) tx() *BlobTx {
}
func (btx *blobTxWithBlobsV1) assign(sc *BlobTxSidecar) error {
if btx.Version != 1 {
if btx.Version != BlobSidecarVersion1 {
return fmt.Errorf("unsupported blob tx version %d", btx.Version)
}
sc.Version = 1
sc.Version = BlobSidecarVersion1
sc.Blobs = btx.Blobs
sc.Commitments = btx.Commitments
sc.Proofs = btx.Proofs
@ -217,12 +271,7 @@ func (tx *BlobTx) copy() TxData {
cpy.S.Set(tx.S)
}
if tx.Sidecar != nil {
cpy.Sidecar = &BlobTxSidecar{
Version: tx.Sidecar.Version,
Blobs: slices.Clone(tx.Sidecar.Blobs),
Commitments: slices.Clone(tx.Sidecar.Commitments),
Proofs: slices.Clone(tx.Sidecar.Proofs),
}
cpy.Sidecar = tx.Sidecar.Copy()
}
return cpy
}
@ -280,7 +329,7 @@ func (tx *BlobTx) encode(b *bytes.Buffer) error {
case tx.Sidecar == nil:
return rlp.Encode(b, tx)
case tx.Sidecar.Version == 0:
case tx.Sidecar.Version == BlobSidecarVersion0:
return rlp.Encode(b, &blobTxWithBlobsV0{
BlobTx: tx,
Blobs: tx.Sidecar.Blobs,
@ -288,7 +337,7 @@ func (tx *BlobTx) encode(b *bytes.Buffer) error {
Proofs: tx.Sidecar.Proofs,
})
case tx.Sidecar.Version == 1:
case tx.Sidecar.Version == BlobSidecarVersion1:
return rlp.Encode(b, &blobTxWithBlobsV1{
BlobTx: tx,
Version: tx.Sidecar.Version,

View file

@ -87,11 +87,7 @@ func createEmptyBlobTx(key *ecdsa.PrivateKey, withSidecar bool) *Transaction {
}
func createEmptyBlobTxInner(withSidecar bool) *BlobTx {
sidecar := &BlobTxSidecar{
Blobs: []kzg4844.Blob{*emptyBlob},
Commitments: []kzg4844.Commitment{emptyBlobCommit},
Proofs: []kzg4844.Proof{emptyBlobProof},
}
sidecar := NewBlobTxSidecar(BlobSidecarVersion0, []kzg4844.Blob{*emptyBlob}, []kzg4844.Commitment{emptyBlobCommit}, []kzg4844.Proof{emptyBlobProof})
blobtx := &BlobTx{
ChainID: uint256.NewInt(1),
Nonce: 5,

View file

@ -527,10 +527,10 @@ 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)))
}
available := api.eth.BlobTxPool().AvailableBlobs(hashes)
getBlobsRequestedCounter.Inc(int64(len(hashes)))
getBlobsAvailableCounter.Inc(int64(available))
// Optimization: check first if all blobs are available, if not, return empty response
if available != len(hashes) {
getBlobsV2RequestMiss.Inc(1)
@ -557,7 +557,7 @@ func (api *ConsensusAPI) GetBlobsV2(hashes []common.Hash) ([]*engine.BlobAndProo
// not found, return empty response
return nil, nil
}
if sidecar.Version != 1 {
if sidecar.Version != types.BlobSidecarVersion1 {
log.Info("GetBlobs queried V0 transaction: index %v, blobhashes %v", index, sidecar.BlobHashes())
return nil, nil
}
@ -566,9 +566,7 @@ func (api *ConsensusAPI) GetBlobsV2(hashes []common.Hash) ([]*engine.BlobAndProo
if idxes, ok := index[hash]; ok {
proofs, err := sidecar.CellProofsAt(bIdx)
if err != nil {
// TODO @rjl @marius we should return an error
log.Info("Failed to get cell proof", "err", err)
return nil, nil
return nil, engine.InvalidParams.With(err)
}
var cellProofs []hexutil.Bytes
for _, proof := range proofs {

View file

@ -1511,13 +1511,8 @@ func TestBlockToPayloadWithBlobs(t *testing.T) {
}
txs = append(txs, types.NewTx(&inner))
sidecars := []*types.BlobTxSidecar{
{
Blobs: make([]kzg4844.Blob, 1),
Commitments: make([]kzg4844.Commitment, 1),
Proofs: make([]kzg4844.Proof, 1),
},
}
sidecar := types.NewBlobTxSidecar(types.BlobSidecarVersion0, make([]kzg4844.Blob, 1), make([]kzg4844.Commitment, 1), make([]kzg4844.Proof, 1))
sidecars := []*types.BlobTxSidecar{sidecar}
block := types.NewBlock(&header, &types.Body{Transactions: txs}, nil, trie.NewStackTrie(nil))
envelope := engine.BlockToExecutableData(block, nil, sidecars, nil)

View file

@ -661,11 +661,7 @@ func testGetPooledTransaction(t *testing.T, blobTx bool) {
To: testAddr,
BlobHashes: []common.Hash{emptyBlobHash},
BlobFeeCap: uint256.MustFromBig(common.Big1),
Sidecar: &types.BlobTxSidecar{
Blobs: emptyBlobs,
Commitments: []kzg4844.Commitment{emptyBlobCommit},
Proofs: []kzg4844.Proof{emptyBlobProof},
},
Sidecar: types.NewBlobTxSidecar(types.BlobSidecarVersion0, emptyBlobs, []kzg4844.Commitment{emptyBlobCommit}, []kzg4844.Proof{emptyBlobProof}),
})
if err != nil {
t.Fatal(err)

View file

@ -83,11 +83,7 @@ func newBlobTx(sim *Backend, key *ecdsa.PrivateKey) (*types.Transaction, error)
To: addr,
AccessList: nil,
BlobHashes: []common.Hash{testBlobVHash},
Sidecar: &types.BlobTxSidecar{
Blobs: []kzg4844.Blob{*testBlob},
Commitments: []kzg4844.Commitment{testBlobCommit},
Proofs: []kzg4844.Proof{testBlobProof},
},
Sidecar: types.NewBlobTxSidecar(types.BlobSidecarVersion0, []kzg4844.Blob{*testBlob}, []kzg4844.Commitment{testBlobCommit}, []kzg4844.Proof{testBlobProof}),
})
return types.SignTx(tx, types.LatestSignerForChainID(chainid), key)
}

View file

@ -1603,11 +1603,7 @@ func (api *TransactionAPI) SignTransaction(ctx context.Context, args Transaction
// no longer retains the blobs, only the blob hashes. In this step, we need
// to put back the blob(s).
if args.IsEIP4844() {
signed = signed.WithBlobTxSidecar(&types.BlobTxSidecar{
Blobs: args.Blobs,
Commitments: args.Commitments,
Proofs: args.Proofs,
})
signed = signed.WithBlobTxSidecar(types.NewBlobTxSidecar(types.BlobSidecarVersion0, args.Blobs, args.Commitments, args.Proofs))
}
data, err := signed.MarshalBinary()
if err != nil {

View file

@ -2758,12 +2758,8 @@ func TestFillBlobTransaction(t *testing.T) {
Proofs: []kzg4844.Proof{emptyBlobProof},
},
want: &result{
Hashes: []common.Hash{emptyBlobHash},
Sidecar: &types.BlobTxSidecar{
Blobs: emptyBlobs,
Commitments: []kzg4844.Commitment{emptyBlobCommit},
Proofs: []kzg4844.Proof{emptyBlobProof},
},
Hashes: []common.Hash{emptyBlobHash},
Sidecar: types.NewBlobTxSidecar(types.BlobSidecarVersion0, emptyBlobs, []kzg4844.Commitment{emptyBlobCommit}, []kzg4844.Proof{emptyBlobProof}),
},
},
{
@ -2778,12 +2774,8 @@ func TestFillBlobTransaction(t *testing.T) {
Proofs: []kzg4844.Proof{emptyBlobProof},
},
want: &result{
Hashes: []common.Hash{emptyBlobHash},
Sidecar: &types.BlobTxSidecar{
Blobs: emptyBlobs,
Commitments: []kzg4844.Commitment{emptyBlobCommit},
Proofs: []kzg4844.Proof{emptyBlobProof},
},
Hashes: []common.Hash{emptyBlobHash},
Sidecar: types.NewBlobTxSidecar(types.BlobSidecarVersion0, emptyBlobs, []kzg4844.Commitment{emptyBlobCommit}, []kzg4844.Proof{emptyBlobProof}),
},
},
{
@ -2808,12 +2800,8 @@ func TestFillBlobTransaction(t *testing.T) {
Blobs: emptyBlobs,
},
want: &result{
Hashes: []common.Hash{emptyBlobHash},
Sidecar: &types.BlobTxSidecar{
Blobs: emptyBlobs,
Commitments: []kzg4844.Commitment{emptyBlobCommit},
Proofs: []kzg4844.Proof{emptyBlobProof},
},
Hashes: []common.Hash{emptyBlobHash},
Sidecar: types.NewBlobTxSidecar(types.BlobSidecarVersion0, emptyBlobs, []kzg4844.Commitment{emptyBlobCommit}, []kzg4844.Proof{emptyBlobProof}),
},
},
}

View file

@ -527,11 +527,8 @@ func (args *TransactionArgs) ToTransaction(defaultType int) *types.Transaction {
BlobFeeCap: uint256.MustFromBig((*big.Int)(args.BlobFeeCap)),
}
if args.Blobs != nil {
data.(*types.BlobTx).Sidecar = &types.BlobTxSidecar{
Blobs: args.Blobs,
Commitments: args.Commitments,
Proofs: args.Proofs,
}
// TODO(rjl493456442, marius) support V1
data.(*types.BlobTx).Sidecar = types.NewBlobTxSidecar(types.BlobSidecarVersion0, args.Blobs, args.Commitments, args.Proofs)
}
case types.DynamicFeeTxType:

View file

@ -32,7 +32,6 @@ 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"
@ -430,17 +429,13 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran
// Make sure all transactions after osaka have cell proofs
if isOsaka {
if sidecar := tx.BlobTxSidecar(); sidecar != nil {
if sidecar.Version == 0 {
if sidecar.Version == types.BlobSidecarVersion0 {
log.Info("Including blob tx with v0 sidecar, recomputing proofs", "hash", ltx.Hash)
sidecar.Proofs = make([]kzg4844.Proof, 0, len(sidecar.Blobs)*kzg4844.CellProofsPerBlob)
for _, blob := range sidecar.Blobs {
cellProofs, err := kzg4844.ComputeCellProofs(&blob)
if err != nil {
panic(err)
}
sidecar.Proofs = append(sidecar.Proofs, cellProofs...)
if err := sidecar.ToV1(); err != nil {
txs.Pop()
log.Warn("Failed to recompute cell proofs", "hash", ltx.Hash, "err", err)
continue
}
sidecar.Version = 1
}
}
}

View file

@ -167,11 +167,8 @@ func (args *SendTxArgs) ToTransaction() (*types.Transaction, error) {
BlobFeeCap: uint256.MustFromBig((*big.Int)(args.BlobFeeCap)),
}
if args.Blobs != nil {
data.(*types.BlobTx).Sidecar = &types.BlobTxSidecar{
Blobs: args.Blobs,
Commitments: args.Commitments,
Proofs: args.Proofs,
}
// TODO(rjl493456442, marius) support V1
data.(*types.BlobTx).Sidecar = types.NewBlobTxSidecar(types.BlobSidecarVersion0, args.Blobs, args.Commitments, args.Proofs)
}
case args.MaxFeePerGas != nil:
@ -222,7 +219,6 @@ func (args *SendTxArgs) validateTxSidecar() error {
return nil
}
n := len(args.Blobs)
// Assume user provides either only blobs (w/o hashes), or
// blobs together with commitments and proofs.
if args.Commitments == nil && args.Proofs != nil {
@ -232,6 +228,7 @@ func (args *SendTxArgs) validateTxSidecar() error {
}
// len(blobs) == len(commitments) == len(proofs) == len(hashes)
n := len(args.Blobs)
if args.Commitments != nil && len(args.Commitments) != n {
return fmt.Errorf("number of blobs and commitments mismatch (have=%d, want=%d)", len(args.Commitments), n)
}

View file

@ -129,11 +129,7 @@ func TestBlobTxs(t *testing.T) {
BlobFeeCap: uint256.NewInt(700),
BlobHashes: []common.Hash{hash},
Value: uint256.NewInt(100),
Sidecar: &types.BlobTxSidecar{
Blobs: []kzg4844.Blob{blob},
Commitments: []kzg4844.Commitment{commitment},
Proofs: []kzg4844.Proof{proof},
},
Sidecar: types.NewBlobTxSidecar(types.BlobSidecarVersion0, []kzg4844.Blob{blob}, []kzg4844.Commitment{commitment}, []kzg4844.Proof{proof}),
}
tx := types.NewTx(b)
data, err := json.Marshal(tx)