mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-23 05:06:43 +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
|
// 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).
|
// 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 {
|
if err := p.ValidateTxBasics(tx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -1443,7 +1451,11 @@ func (p *BlobPool) Add(txs []*types.Transaction, sync bool) []error {
|
||||||
adds = make([]*types.Transaction, 0, len(txs))
|
adds = make([]*types.Transaction, 0, len(txs))
|
||||||
)
|
)
|
||||||
for i, tx := range 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 {
|
if errs[i] == nil {
|
||||||
adds = append(adds, tx.WithoutBlobTxSidecar())
|
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
|
// add inserts a new blob transaction into the pool if it passes validation (both
|
||||||
// consensus validity and pool restrictions).
|
// 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
|
// 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
|
// only even pulled from the network, so this method will act as the overload
|
||||||
// protection for fetches.
|
// protection for fetches.
|
||||||
|
|
@ -1471,7 +1483,7 @@ func (p *BlobPool) add(tx *types.Transaction) (err error) {
|
||||||
}(time.Now())
|
}(time.Now())
|
||||||
|
|
||||||
// Ensure the transaction is valid from all perspectives
|
// 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)
|
log.Trace("Transaction validation failed", "hash", tx.Hash(), "err", err)
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, txpool.ErrUnderpriced):
|
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,
|
// Transaction permitted into the pool from a nonce and cost perspective,
|
||||||
// insert it into the database and update the indices
|
// insert it into the database and update the indices
|
||||||
blob, err := rlp.EncodeToBytes(tx)
|
blob, err := rlp.EncodeToBytes(tx)
|
||||||
|
|
|
||||||
|
|
@ -1323,7 +1323,7 @@ func TestBlobCountLimit(t *testing.T) {
|
||||||
|
|
||||||
// Check that first succeeds second fails.
|
// Check that first succeeds second fails.
|
||||||
if errs[0] != nil {
|
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) {
|
if !errors.Is(errs[1], txpool.ErrTxBlobLimitExceeded) {
|
||||||
t.Fatalf("expected tx with 8 blobs to fail, got: %v", errs[1])
|
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
|
// Add each transaction one by one, verifying the pool internals in between
|
||||||
for j, add := range tt.adds {
|
for j, add := range tt.adds {
|
||||||
signed, _ := types.SignNewTx(keys[add.from], types.LatestSigner(params.MainnetChainConfig), add.tx)
|
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)
|
t.Errorf("test %d, tx %d: adding transaction error mismatch: have %v, want %v", i, j, err, add.err)
|
||||||
}
|
}
|
||||||
if add.err == nil {
|
if add.err == nil {
|
||||||
|
|
@ -2124,7 +2124,7 @@ func benchmarkPoolPending(b *testing.B, datacap uint64) {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
statedb.AddBalance(addr, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
|
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)
|
statedb.Commit(0, true, false)
|
||||||
defer pool.Close()
|
defer pool.Close()
|
||||||
|
|
|
||||||
|
|
@ -63,9 +63,6 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
||||||
if opts.Accept&(1<<tx.Type()) == 0 {
|
if opts.Accept&(1<<tx.Type()) == 0 {
|
||||||
return fmt.Errorf("%w: tx type %v not supported by this pool", core.ErrTxTypeNotSupported, tx.Type())
|
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
|
// Before performing any expensive validations, sanity check that the tx is
|
||||||
// smaller than the maximum limit the pool can meaningfully handle
|
// smaller than the maximum limit the pool can meaningfully handle
|
||||||
if tx.Size() > opts.MaxSize {
|
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 {
|
if tx.GasTipCapIntCmp(opts.MinTip) < 0 {
|
||||||
return fmt.Errorf("%w: gas tip cap %v, minimum needed %v", ErrTxGasPriceTooLow, tx.GasTipCap(), opts.MinTip)
|
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 tx.Type() == types.SetCodeTxType {
|
||||||
if len(tx.SetCodeAuthorizations()) == 0 {
|
if len(tx.SetCodeAuthorizations()) == 0 {
|
||||||
return errors.New("set code tx must have at least one authorization tuple")
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateBlobTx implements the blob-transaction specific validations.
|
func ValidateBlobSidecar(tx *types.Transaction, sidecar *types.BlobTxCellSidecar, head *types.Header, opts *ValidationOptions) error {
|
||||||
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")
|
|
||||||
}
|
|
||||||
// Ensure the blob fee cap satisfies the minimum blob gas price
|
// Ensure the blob fee cap satisfies the minimum blob gas price
|
||||||
if tx.BlobGasFeeCapIntCmp(blobTxMinBlobGasPrice) < 0 {
|
if tx.BlobGasFeeCapIntCmp(blobTxMinBlobGasPrice) < 0 {
|
||||||
return fmt.Errorf("%w: blob fee cap %v, minimum needed %v", ErrTxGasPriceTooLow, tx.BlobGasFeeCap(), blobTxMinBlobGasPrice)
|
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
|
// Verify whether the blob count is consistent with other parts of the sidecar and the transaction
|
||||||
// data match up before doing any expensive validations
|
blobCount := len(sidecar.Cells) / sidecar.CellIndices.OneCount()
|
||||||
hashes := tx.BlobHashes()
|
hashes := tx.BlobHashes()
|
||||||
if len(hashes) == 0 {
|
if blobCount == 0 {
|
||||||
return errors.New("blobless blob transaction")
|
return errors.New("blobless blob transaction")
|
||||||
}
|
}
|
||||||
if len(hashes) > params.BlobTxMaxBlobs {
|
if blobCount != len(sidecar.Commitments) || blobCount != len(hashes) {
|
||||||
return fmt.Errorf("too many blobs in transaction: have %d, permitted %d", len(hashes), params.BlobTxMaxBlobs)
|
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 {
|
if err := sidecar.ValidateBlobCommitmentHashes(hashes); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -182,29 +174,33 @@ func validateBlobTx(tx *types.Transaction, head *types.Header, opts *ValidationO
|
||||||
return validateBlobSidecarLegacy(sidecar, hashes)
|
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 {
|
if sidecar.Version != types.BlobSidecarVersion0 {
|
||||||
return fmt.Errorf("invalid sidecar version pre-osaka: %v", sidecar.Version)
|
return fmt.Errorf("invalid sidecar version pre-osaka: %v", sidecar.Version)
|
||||||
}
|
}
|
||||||
if len(sidecar.Proofs) != len(hashes) {
|
if len(sidecar.Proofs) != len(hashes) {
|
||||||
return fmt.Errorf("invalid number of %d blob proofs expected %d", 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 {
|
blobs, err := kzg4844.RecoverBlobs(sidecar.Cells, sidecar.CellIndices.Indices())
|
||||||
if err := kzg4844.VerifyBlobProof(&sidecar.Blobs[i], sidecar.Commitments[i], sidecar.Proofs[i]); err != nil {
|
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 fmt.Errorf("invalid blob %d: %v", i, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
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 {
|
if sidecar.Version != types.BlobSidecarVersion1 {
|
||||||
return fmt.Errorf("invalid sidecar version post-osaka: %v", sidecar.Version)
|
return fmt.Errorf("invalid sidecar version post-osaka: %v", sidecar.Version)
|
||||||
}
|
}
|
||||||
if len(sidecar.Proofs) != len(hashes)*kzg4844.CellProofsPerBlob {
|
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 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
|
// 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.
|
// blobTxWithBlobs represents blob tx with its corresponding sidecar.
|
||||||
// This is an interface because sidecars are versioned.
|
// This is an interface because sidecars are versioned.
|
||||||
type blobTxWithBlobs interface {
|
type blobTxWithBlobs interface {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue