mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-23 13:16:42 +00:00
add: validation logic with new type BlobTxCellSidecar
This commit is contained in:
parent
da8a204517
commit
86df16d6e6
5 changed files with 166 additions and 31 deletions
|
|
@ -1164,7 +1164,15 @@ func (p *BlobPool) checkDelegationLimit(tx *types.Transaction) error {
|
|||
|
||||
// validateTx checks whether a transaction is valid according to the consensus
|
||||
// rules and adheres to some heuristic limits of the local node (price and size).
|
||||
func (p *BlobPool) validateTx(tx *types.Transaction) error {
|
||||
func (p *BlobPool) validateTx(tx *types.Transaction, cellSidecars *types.BlobTxCellSidecar) error {
|
||||
|
||||
if err := txpool.ValidateBlobSidecar(tx, cellSidecars, p.head, &txpool.ValidationOptions{
|
||||
Config: p.chain.Config(),
|
||||
MaxBlobCount: maxBlobsPerTx,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := p.ValidateTxBasics(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -1443,7 +1451,11 @@ func (p *BlobPool) Add(txs []*types.Transaction, sync bool) []error {
|
|||
adds = make([]*types.Transaction, 0, len(txs))
|
||||
)
|
||||
for i, tx := range txs {
|
||||
errs[i] = p.add(tx)
|
||||
if errs[i] != nil {
|
||||
continue
|
||||
}
|
||||
cellSidecar := tx.BlobTxSidecar().ToBlobTxCellSidecar()
|
||||
errs[i] = p.add(tx.WithoutBlobTxSidecar(), cellSidecar)
|
||||
if errs[i] == nil {
|
||||
adds = append(adds, tx.WithoutBlobTxSidecar())
|
||||
}
|
||||
|
|
@ -1457,7 +1469,7 @@ func (p *BlobPool) Add(txs []*types.Transaction, sync bool) []error {
|
|||
|
||||
// add inserts a new blob transaction into the pool if it passes validation (both
|
||||
// consensus validity and pool restrictions).
|
||||
func (p *BlobPool) add(tx *types.Transaction) (err error) {
|
||||
func (p *BlobPool) add(tx *types.Transaction, cellSidecar *types.BlobTxCellSidecar) (err error) {
|
||||
// The blob pool blocks on adding a transaction. This is because blob txs are
|
||||
// only even pulled from the network, so this method will act as the overload
|
||||
// protection for fetches.
|
||||
|
|
@ -1471,7 +1483,7 @@ func (p *BlobPool) add(tx *types.Transaction) (err error) {
|
|||
}(time.Now())
|
||||
|
||||
// Ensure the transaction is valid from all perspectives
|
||||
if err := p.validateTx(tx); err != nil {
|
||||
if err := p.validateTx(tx, cellSidecar); err != nil {
|
||||
log.Trace("Transaction validation failed", "hash", tx.Hash(), "err", err)
|
||||
switch {
|
||||
case errors.Is(err, txpool.ErrUnderpriced):
|
||||
|
|
@ -1513,6 +1525,10 @@ func (p *BlobPool) add(tx *types.Transaction) (err error) {
|
|||
}
|
||||
}()
|
||||
}
|
||||
|
||||
//todo(healthykim) remove this and seperate database or introduce list
|
||||
blobSidecar := cellSidecar.ToBlobTxSidecar()
|
||||
tx = tx.WithBlobTxSidecar(blobSidecar)
|
||||
// Transaction permitted into the pool from a nonce and cost perspective,
|
||||
// insert it into the database and update the indices
|
||||
blob, err := rlp.EncodeToBytes(tx)
|
||||
|
|
|
|||
|
|
@ -1323,7 +1323,7 @@ func TestBlobCountLimit(t *testing.T) {
|
|||
|
||||
// Check that first succeeds second fails.
|
||||
if errs[0] != nil {
|
||||
t.Fatalf("expected tx with 7 blobs to succeed")
|
||||
t.Fatalf("expected tx with 7 blobs to succeed, got: %v", errs[0])
|
||||
}
|
||||
if !errors.Is(errs[1], txpool.ErrTxBlobLimitExceeded) {
|
||||
t.Fatalf("expected tx with 8 blobs to fail, got: %v", errs[1])
|
||||
|
|
@ -1752,7 +1752,7 @@ func TestAdd(t *testing.T) {
|
|||
// Add each transaction one by one, verifying the pool internals in between
|
||||
for j, add := range tt.adds {
|
||||
signed, _ := types.SignNewTx(keys[add.from], types.LatestSigner(params.MainnetChainConfig), add.tx)
|
||||
if err := pool.add(signed); !errors.Is(err, add.err) {
|
||||
if err := pool.add(signed.WithoutBlobTxSidecar(), signed.BlobTxSidecar().ToBlobTxCellSidecar()); !errors.Is(err, add.err) {
|
||||
t.Errorf("test %d, tx %d: adding transaction error mismatch: have %v, want %v", i, j, err, add.err)
|
||||
}
|
||||
if add.err == nil {
|
||||
|
|
@ -2124,7 +2124,7 @@ func benchmarkPoolPending(b *testing.B, datacap uint64) {
|
|||
b.Fatal(err)
|
||||
}
|
||||
statedb.AddBalance(addr, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
|
||||
pool.add(tx)
|
||||
pool.add(tx.WithoutBlobTxSidecar(), tx.BlobTxSidecar().ToBlobTxCellSidecar())
|
||||
}
|
||||
statedb.Commit(0, true, false)
|
||||
defer pool.Close()
|
||||
|
|
|
|||
|
|
@ -63,9 +63,6 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
|||
if opts.Accept&(1<<tx.Type()) == 0 {
|
||||
return fmt.Errorf("%w: tx type %v not supported by this pool", core.ErrTxTypeNotSupported, tx.Type())
|
||||
}
|
||||
if blobCount := len(tx.BlobHashes()); blobCount > opts.MaxBlobCount {
|
||||
return fmt.Errorf("%w: blob count %v, limit %v", ErrTxBlobLimitExceeded, blobCount, opts.MaxBlobCount)
|
||||
}
|
||||
// Before performing any expensive validations, sanity check that the tx is
|
||||
// smaller than the maximum limit the pool can meaningfully handle
|
||||
if tx.Size() > opts.MaxSize {
|
||||
|
|
@ -139,9 +136,6 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
|||
if tx.GasTipCapIntCmp(opts.MinTip) < 0 {
|
||||
return fmt.Errorf("%w: gas tip cap %v, minimum needed %v", ErrTxGasPriceTooLow, tx.GasTipCap(), opts.MinTip)
|
||||
}
|
||||
if tx.Type() == types.BlobTxType {
|
||||
return validateBlobTx(tx, head, opts)
|
||||
}
|
||||
if tx.Type() == types.SetCodeTxType {
|
||||
if len(tx.SetCodeAuthorizations()) == 0 {
|
||||
return errors.New("set code tx must have at least one authorization tuple")
|
||||
|
|
@ -150,28 +144,26 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
|||
return nil
|
||||
}
|
||||
|
||||
// validateBlobTx implements the blob-transaction specific validations.
|
||||
func validateBlobTx(tx *types.Transaction, head *types.Header, opts *ValidationOptions) error {
|
||||
sidecar := tx.BlobTxSidecar()
|
||||
if sidecar == nil {
|
||||
return errors.New("missing sidecar in blob transaction")
|
||||
}
|
||||
func ValidateBlobSidecar(tx *types.Transaction, sidecar *types.BlobTxCellSidecar, head *types.Header, opts *ValidationOptions) error {
|
||||
// Ensure the blob fee cap satisfies the minimum blob gas price
|
||||
if tx.BlobGasFeeCapIntCmp(blobTxMinBlobGasPrice) < 0 {
|
||||
return fmt.Errorf("%w: blob fee cap %v, minimum needed %v", ErrTxGasPriceTooLow, tx.BlobGasFeeCap(), blobTxMinBlobGasPrice)
|
||||
}
|
||||
// Ensure the number of items in the blob transaction and various side
|
||||
// data match up before doing any expensive validations
|
||||
// Verify whether the blob count is consistent with other parts of the sidecar and the transaction
|
||||
blobCount := len(sidecar.Cells) / sidecar.CellIndices.OneCount()
|
||||
hashes := tx.BlobHashes()
|
||||
if len(hashes) == 0 {
|
||||
if blobCount == 0 {
|
||||
return errors.New("blobless blob transaction")
|
||||
}
|
||||
if len(hashes) > params.BlobTxMaxBlobs {
|
||||
return fmt.Errorf("too many blobs in transaction: have %d, permitted %d", len(hashes), params.BlobTxMaxBlobs)
|
||||
if blobCount != len(sidecar.Commitments) || blobCount != len(hashes) {
|
||||
return fmt.Errorf("invalid number of %d blobs compared to %d commitments and %d blob hashes", blobCount, len(sidecar.Commitments), len(tx.BlobHashes()))
|
||||
}
|
||||
if len(sidecar.Blobs) != len(hashes) {
|
||||
return fmt.Errorf("invalid number of %d blobs compared to %d blob hashes", len(sidecar.Blobs), len(hashes))
|
||||
|
||||
// Check whether the blob count does not exceed the max blob count
|
||||
if blobCount > opts.MaxBlobCount {
|
||||
return fmt.Errorf("%w: blob count %v, limit %v", ErrTxBlobLimitExceeded, blobCount, opts.MaxBlobCount)
|
||||
}
|
||||
|
||||
if err := sidecar.ValidateBlobCommitmentHashes(hashes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -182,29 +174,33 @@ func validateBlobTx(tx *types.Transaction, head *types.Header, opts *ValidationO
|
|||
return validateBlobSidecarLegacy(sidecar, hashes)
|
||||
}
|
||||
|
||||
func validateBlobSidecarLegacy(sidecar *types.BlobTxSidecar, hashes []common.Hash) error {
|
||||
func validateBlobSidecarLegacy(sidecar *types.BlobTxCellSidecar, hashes []common.Hash) error {
|
||||
if sidecar.Version != types.BlobSidecarVersion0 {
|
||||
return fmt.Errorf("invalid sidecar version pre-osaka: %v", sidecar.Version)
|
||||
}
|
||||
if len(sidecar.Proofs) != len(hashes) {
|
||||
return fmt.Errorf("invalid number of %d blob proofs expected %d", len(sidecar.Proofs), len(hashes))
|
||||
}
|
||||
for i := range sidecar.Blobs {
|
||||
if err := kzg4844.VerifyBlobProof(&sidecar.Blobs[i], sidecar.Commitments[i], sidecar.Proofs[i]); err != nil {
|
||||
blobs, err := kzg4844.RecoverBlobs(sidecar.Cells, sidecar.CellIndices.Indices())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range blobs {
|
||||
if err := kzg4844.VerifyBlobProof(&blobs[i], sidecar.Commitments[i], sidecar.Proofs[i]); err != nil {
|
||||
return fmt.Errorf("invalid blob %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateBlobSidecarOsaka(sidecar *types.BlobTxSidecar, hashes []common.Hash) error {
|
||||
func validateBlobSidecarOsaka(sidecar *types.BlobTxCellSidecar, hashes []common.Hash) error {
|
||||
if sidecar.Version != types.BlobSidecarVersion1 {
|
||||
return fmt.Errorf("invalid sidecar version post-osaka: %v", sidecar.Version)
|
||||
}
|
||||
if len(sidecar.Proofs) != len(hashes)*kzg4844.CellProofsPerBlob {
|
||||
return fmt.Errorf("invalid number of %d blob proofs expected %d", len(sidecar.Proofs), len(hashes)*kzg4844.CellProofsPerBlob)
|
||||
}
|
||||
return kzg4844.VerifyCellProofs(sidecar.Blobs, sidecar.Commitments, sidecar.Proofs)
|
||||
return kzg4844.VerifyCells(sidecar.Cells, sidecar.Commitments, sidecar.Proofs, sidecar.CellIndices.Indices())
|
||||
}
|
||||
|
||||
// ValidationOptionsWithState define certain differences between stateful transaction
|
||||
|
|
|
|||
85
core/types/custody_bitmap.go
Normal file
85
core/types/custody_bitmap.go
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
// `CustodyBitmap` is a bitmap to represent which custody index to store (little endian)
|
||||
type CustodyBitmap [16]byte
|
||||
|
||||
const CustodySize = 128
|
||||
|
||||
func (b CustodyBitmap) IsSet(i uint) bool {
|
||||
if i >= CustodySize {
|
||||
return false
|
||||
}
|
||||
index := i / 8
|
||||
offset := i % 8
|
||||
return ((b[index] >> offset) & 1) == 1
|
||||
}
|
||||
|
||||
// Set ith bit
|
||||
func (b *CustodyBitmap) Set(i uint) error {
|
||||
if i >= CustodySize {
|
||||
return errors.New("bit index out of range")
|
||||
}
|
||||
index := i / 8
|
||||
offset := i % 8
|
||||
b[index] |= 1 << offset
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clear ith bit
|
||||
func (b *CustodyBitmap) Clear(i uint) error {
|
||||
if i >= CustodySize {
|
||||
return errors.New("bit index out of range")
|
||||
}
|
||||
index := i / 8
|
||||
offset := i % 8
|
||||
b[index] &^= 1 << offset
|
||||
return nil
|
||||
}
|
||||
|
||||
// Number of bits set to 1
|
||||
func (b CustodyBitmap) OneCount() int {
|
||||
total := 0
|
||||
for _, byte := range b {
|
||||
total += bits.OnesCount8(uint8(byte))
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// Return bit indices set to 1, ascending order
|
||||
func (b CustodyBitmap) Indices() []uint64 {
|
||||
out := make([]uint64, 0, b.OneCount())
|
||||
for byteIdx, val := range b {
|
||||
v := val
|
||||
for v != 0 {
|
||||
tz := bits.TrailingZeros8(uint8(v)) // 0..7
|
||||
idx := uint64(byteIdx*8 + tz)
|
||||
out = append(out, idx)
|
||||
v &^= 1 << tz
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (b CustodyBitmap) SetIndices(indices []uint64) error {
|
||||
for _, i := range indices {
|
||||
if i >= CustodySize {
|
||||
return errors.New("bit index out of range")
|
||||
}
|
||||
byteIdx := i / 8
|
||||
bitOff := i % 8
|
||||
b[byteIdx] |= 1 << bitOff
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b CustodyBitmap) SetAll() CustodyBitmap {
|
||||
for i := 0; i < len(b); i++ {
|
||||
b[i] = 0xFF
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
|
@ -176,6 +176,44 @@ func (sc *BlobTxSidecar) Copy() *BlobTxSidecar {
|
|||
}
|
||||
}
|
||||
|
||||
func (sc *BlobTxSidecar) ToBlobTxCellSidecar() *BlobTxCellSidecar {
|
||||
cells, _ := kzg4844.ComputeCells(sc.Blobs)
|
||||
return &BlobTxCellSidecar{
|
||||
Version: sc.Version,
|
||||
Cells: cells,
|
||||
Commitments: sc.Commitments,
|
||||
Proofs: sc.Proofs,
|
||||
CellIndices: CustodyBitmap{}.SetAll(),
|
||||
}
|
||||
}
|
||||
|
||||
type BlobTxCellSidecar struct {
|
||||
Version byte
|
||||
Cells []kzg4844.Cell
|
||||
Commitments []kzg4844.Commitment
|
||||
Proofs []kzg4844.Proof
|
||||
CellIndices CustodyBitmap
|
||||
}
|
||||
|
||||
func (c *BlobTxCellSidecar) ToBlobTxSidecar() *BlobTxSidecar {
|
||||
blobs, _ := kzg4844.RecoverBlobs(c.Cells, c.CellIndices.Indices())
|
||||
return &BlobTxSidecar{
|
||||
Version: c.Version,
|
||||
Blobs: blobs,
|
||||
Commitments: c.Commitments,
|
||||
Proofs: c.Proofs,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateBlobCommitmentHashes checks whether the given hashes correspond to the
|
||||
// commitments in the sidecar
|
||||
func (c *BlobTxCellSidecar) ValidateBlobCommitmentHashes(hashes []common.Hash) error {
|
||||
sc := BlobTxSidecar{
|
||||
Commitments: c.Commitments,
|
||||
}
|
||||
return sc.ValidateBlobCommitmentHashes(hashes)
|
||||
}
|
||||
|
||||
// blobTxWithBlobs represents blob tx with its corresponding sidecar.
|
||||
// This is an interface because sidecars are versioned.
|
||||
type blobTxWithBlobs interface {
|
||||
|
|
|
|||
Loading…
Reference in a new issue