core/types: improve encoding/decoding

This commit is contained in:
Felix Lange 2025-06-06 13:21:48 +02:00
parent 41634e60ab
commit 61cc5d0580

View file

@ -19,6 +19,7 @@ package types
import ( import (
"bytes" "bytes"
"crypto/sha256" "crypto/sha256"
"errors"
"fmt" "fmt"
"math/big" "math/big"
@ -119,15 +120,21 @@ func (sc *BlobTxSidecar) ValidateBlobCommitmentHashes(hashes []common.Hash) erro
return nil return nil
} }
// blobTxWithBlobs is used for encoding of transactions when blobs are present. // blobTxWithBlobs represents blob tx with its corresponding sidecar.
type blobTxWithBlobs struct { // This is an interface because sidecars are versioned.
type blobTxWithBlobs interface {
tx() *BlobTx
assign(*BlobTxSidecar) error
}
type blobTxWithBlobsV0 struct {
BlobTx *BlobTx BlobTx *BlobTx
Blobs []kzg4844.Blob Blobs []kzg4844.Blob
Commitments []kzg4844.Commitment Commitments []kzg4844.Commitment
Proofs []kzg4844.Proof Proofs []kzg4844.Proof
} }
type versionedBlobTxWithBlobs struct { type blobTxWithBlobsV1 struct {
BlobTx *BlobTx BlobTx *BlobTx
Version byte Version byte
Blobs []kzg4844.Blob Blobs []kzg4844.Blob
@ -135,6 +142,33 @@ type versionedBlobTxWithBlobs struct {
Proofs []kzg4844.Proof Proofs []kzg4844.Proof
} }
func (btx *blobTxWithBlobsV0) tx() *BlobTx {
return btx.BlobTx
}
func (btx *blobTxWithBlobsV0) assign(sc *BlobTxSidecar) error {
sc.Version = 0
sc.Blobs = btx.Blobs
sc.Commitments = btx.Commitments
sc.Proofs = btx.Proofs
return nil
}
func (btx *blobTxWithBlobsV1) tx() *BlobTx {
return btx.BlobTx
}
func (btx *blobTxWithBlobsV1) assign(sc *BlobTxSidecar) error {
if btx.Version != 1 {
return fmt.Errorf("unsupported blob tx version %d", btx.Version)
}
sc.Version = 1
sc.Blobs = btx.Blobs
sc.Commitments = btx.Commitments
sc.Proofs = btx.Proofs
return nil
}
// copy creates a deep copy of the transaction data and initializes all fields. // copy creates a deep copy of the transaction data and initializes all fields.
func (tx *BlobTx) copy() TxData { func (tx *BlobTx) copy() TxData {
cpy := &BlobTx{ cpy := &BlobTx{
@ -240,69 +274,98 @@ func (tx *BlobTx) withSidecar(sideCar *BlobTxSidecar) *BlobTx {
} }
func (tx *BlobTx) encode(b *bytes.Buffer) error { func (tx *BlobTx) encode(b *bytes.Buffer) error {
if tx.Sidecar == nil { switch {
case tx.Sidecar == nil:
return rlp.Encode(b, tx) return rlp.Encode(b, tx)
}
// Encode a cell proof transaction case tx.Sidecar.Version == 0:
if tx.Sidecar.Version != 0 { return rlp.Encode(b, &blobTxWithBlobsV0{
inner := &versionedBlobTxWithBlobs{ BlobTx: tx,
Blobs: tx.Sidecar.Blobs,
Commitments: tx.Sidecar.Commitments,
Proofs: tx.Sidecar.Proofs,
})
case tx.Sidecar.Version == 1:
return rlp.Encode(b, &blobTxWithBlobsV1{
BlobTx: tx, BlobTx: tx,
Version: tx.Sidecar.Version, Version: tx.Sidecar.Version,
Blobs: tx.Sidecar.Blobs, Blobs: tx.Sidecar.Blobs,
Commitments: tx.Sidecar.Commitments, Commitments: tx.Sidecar.Commitments,
Proofs: tx.Sidecar.Proofs, Proofs: tx.Sidecar.Proofs,
} })
return rlp.Encode(b, inner)
default:
return errors.New("unsupported sidecar version")
} }
inner := &blobTxWithBlobs{
BlobTx: tx,
Blobs: tx.Sidecar.Blobs,
Commitments: tx.Sidecar.Commitments,
Proofs: tx.Sidecar.Proofs,
}
return rlp.Encode(b, inner)
} }
func (tx *BlobTx) decode(input []byte) error { func (tx *BlobTx) decode(input []byte) error {
// Here we need to support two formats: the network protocol encoding of the tx (with // Here we need to support two outer formats: the network protocol encoding of the tx
// blobs) or the canonical encoding without blobs. // (with blobs) or the canonical encoding without blobs.
// //
// The two encodings can be distinguished by checking whether the first element of the // The canonical encoding is just a list of fields:
// input list is itself a list. //
// [chainID, nonce, ...]
//
// The network encoding is a list where the first element is the tx in the canonical encoding,
// and the remaining elements are the 'sidecar':
//
// [[chainID, nonce, ...], ...]
//
// The two outer encodings can be distinguished by checking whether the first element
// of the input list is itself a list. If it's the canonical encoding, the first
// element is the chainID, which is a number.
outerList, _, err := rlp.SplitList(input) firstElem, _, err := rlp.SplitList(input)
if err != nil { if err != nil {
return err return err
} }
firstElemKind, _, _, err := rlp.Split(outerList) firstElemKind, _, secondElem, err := rlp.Split(firstElem)
if err != nil { if err != nil {
return err return err
} }
if firstElemKind != rlp.List { if firstElemKind != rlp.List {
// Blob tx without blobs.
return rlp.DecodeBytes(input, tx) return rlp.DecodeBytes(input, tx)
} }
// It's a tx with blobs. Try to decode it as version 0. // Now we know it's the network encoding with the blob sidecar. Here we again need to
var inner versionedBlobTxWithBlobs // support multiple encodings: legacy sidecars (v0) with a blob proof, and versioned
if err := rlp.DecodeBytes(input, &inner); err != nil { // sidecars.
var innerV0 blobTxWithBlobs //
if err := rlp.DecodeBytes(input, &innerV0); err != nil { // The legacy encoding is:
return err //
} // [tx, blobs, commitments, proofs]
inner.BlobTx = innerV0.BlobTx //
inner.Version = 0 // The versioned encoding is:
inner.Blobs = innerV0.Blobs //
inner.Commitments = innerV0.Commitments // [tx, version, blobs, ...]
inner.Proofs = innerV0.Proofs //
// We can tell the two apart by checking whether the second element is the version byte.
// For legacy sidecar the second element is a list of blobs.
secondElemKind, _, _, err := rlp.Split(secondElem)
if err != nil {
return err
} }
*tx = *inner.BlobTx var payload blobTxWithBlobs
tx.Sidecar = &BlobTxSidecar{ if secondElemKind == rlp.List {
Version: inner.Version, // No version byte: blob sidecar v0.
Blobs: inner.Blobs, payload = new(blobTxWithBlobsV0)
Commitments: inner.Commitments, } else {
Proofs: inner.Proofs, // It has a version byte. Decode as v1, version is checked by assign()
payload = new(blobTxWithBlobsV1)
} }
if err := rlp.DecodeBytes(input, payload); err != nil {
return err
}
sc := new(BlobTxSidecar)
if err := payload.assign(sc); err != nil {
return err
}
*tx = *payload.tx()
tx.Sidecar = sc
return nil return nil
} }