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 (
"bytes"
"crypto/sha256"
"errors"
"fmt"
"math/big"
@ -119,15 +120,21 @@ func (sc *BlobTxSidecar) ValidateBlobCommitmentHashes(hashes []common.Hash) erro
return nil
}
// blobTxWithBlobs is used for encoding of transactions when blobs are present.
type blobTxWithBlobs struct {
// blobTxWithBlobs represents blob tx with its corresponding sidecar.
// This is an interface because sidecars are versioned.
type blobTxWithBlobs interface {
tx() *BlobTx
assign(*BlobTxSidecar) error
}
type blobTxWithBlobsV0 struct {
BlobTx *BlobTx
Blobs []kzg4844.Blob
Commitments []kzg4844.Commitment
Proofs []kzg4844.Proof
}
type versionedBlobTxWithBlobs struct {
type blobTxWithBlobsV1 struct {
BlobTx *BlobTx
Version byte
Blobs []kzg4844.Blob
@ -135,6 +142,33 @@ type versionedBlobTxWithBlobs struct {
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.
func (tx *BlobTx) copy() TxData {
cpy := &BlobTx{
@ -240,69 +274,98 @@ func (tx *BlobTx) withSidecar(sideCar *BlobTxSidecar) *BlobTx {
}
func (tx *BlobTx) encode(b *bytes.Buffer) error {
if tx.Sidecar == nil {
switch {
case tx.Sidecar == nil:
return rlp.Encode(b, tx)
}
// Encode a cell proof transaction
if tx.Sidecar.Version != 0 {
inner := &versionedBlobTxWithBlobs{
case tx.Sidecar.Version == 0:
return rlp.Encode(b, &blobTxWithBlobsV0{
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,
Version: tx.Sidecar.Version,
Blobs: tx.Sidecar.Blobs,
Commitments: tx.Sidecar.Commitments,
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 {
// Here we need to support two formats: the network protocol encoding of the tx (with
// blobs) or the canonical encoding without blobs.
// Here we need to support two outer formats: the network protocol encoding of the tx
// (with blobs) or the canonical encoding without blobs.
//
// The two encodings can be distinguished by checking whether the first element of the
// input list is itself a list.
// The canonical encoding is just a list of fields:
//
// [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 {
return err
}
firstElemKind, _, _, err := rlp.Split(outerList)
firstElemKind, _, secondElem, err := rlp.Split(firstElem)
if err != nil {
return err
}
if firstElemKind != rlp.List {
// Blob tx without blobs.
return rlp.DecodeBytes(input, tx)
}
// 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 innerV0 blobTxWithBlobs
if err := rlp.DecodeBytes(input, &innerV0); err != nil {
return err
}
inner.BlobTx = innerV0.BlobTx
inner.Version = 0
inner.Blobs = innerV0.Blobs
inner.Commitments = innerV0.Commitments
inner.Proofs = innerV0.Proofs
// Now we know it's the network encoding with the blob sidecar. Here we again need to
// support multiple encodings: legacy sidecars (v0) with a blob proof, and versioned
// sidecars.
//
// The legacy encoding is:
//
// [tx, blobs, commitments, proofs]
//
// The versioned encoding is:
//
// [tx, version, blobs, ...]
//
// 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
tx.Sidecar = &BlobTxSidecar{
Version: inner.Version,
Blobs: inner.Blobs,
Commitments: inner.Commitments,
Proofs: inner.Proofs,
var payload blobTxWithBlobs
if secondElemKind == rlp.List {
// No version byte: blob sidecar v0.
payload = new(blobTxWithBlobsV0)
} else {
// 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
}