mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-08 07:58:40 +00:00
add Flush() function called in tx fetcher
This commit is contained in:
parent
feff09edc5
commit
a44f886891
9 changed files with 242 additions and 257 deletions
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/txpool"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
|
@ -68,6 +69,8 @@ type BlobBuffer struct {
|
||||||
addToPool func(*BlobTxForPool) error
|
addToPool func(*BlobTxForPool) error
|
||||||
validateTx func(*types.Transaction) error
|
validateTx func(*types.Transaction) error
|
||||||
dropPeer func(string)
|
dropPeer func(string)
|
||||||
|
|
||||||
|
completed []*BlobTxForPool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBlobBuffer(validateTx func(*types.Transaction) error, addToPool func(*BlobTxForPool) error, dropPeer func(string)) *BlobBuffer {
|
func NewBlobBuffer(validateTx func(*types.Transaction) error, addToPool func(*BlobTxForPool) error, dropPeer func(string)) *BlobBuffer {
|
||||||
|
|
@ -80,47 +83,56 @@ func NewBlobBuffer(validateTx func(*types.Transaction) error, addToPool func(*Bl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flush adds all completed entries to the pool and returns the hashes
|
||||||
|
// and errors for any that failed add.
|
||||||
|
func (b *BlobBuffer) Flush() ([]common.Hash, []error) {
|
||||||
|
var errs []error
|
||||||
|
var txs []common.Hash
|
||||||
|
for _, ptx := range b.completed {
|
||||||
|
if err := b.addToPool(ptx); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
txs = append(txs, ptx.Tx.Hash())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return txs, errs
|
||||||
|
}
|
||||||
|
|
||||||
// AddTx buffers a blob transaction (without blobs) from an ETH/72 peer.
|
// AddTx buffers a blob transaction (without blobs) from an ETH/72 peer.
|
||||||
// If cells are already buffered, verification and pool insertion are attempted.
|
// If cells are already buffered, verification and pool insertion are attempted.
|
||||||
func (b *BlobBuffer) AddTx(tx *types.Transaction, peer string) error {
|
func (b *BlobBuffer) AddTx(txs []*types.Transaction, peer string) []error {
|
||||||
defer b.updateMetrics()()
|
defer b.updateMetrics()()
|
||||||
|
|
||||||
// First remove any timed-out entries.
|
// First remove any timed-out entries.
|
||||||
b.evict()
|
b.evict()
|
||||||
|
|
||||||
hash := tx.Hash()
|
errs := make([]error, len(txs))
|
||||||
sidecar := tx.BlobTxSidecar()
|
for i, tx := range txs {
|
||||||
if sidecar == nil {
|
hash := tx.Hash()
|
||||||
return fmt.Errorf("blob transaction without sidecar")
|
sidecar := tx.BlobTxSidecar()
|
||||||
|
if sidecar == nil {
|
||||||
|
errs[i] = fmt.Errorf("blob transaction without sidecar")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// tx validation (basic w/o lock)
|
||||||
|
// error will be handled by tx fetcher
|
||||||
|
if err := b.validateTx(tx); err != nil {
|
||||||
|
errs[i] = err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if entry, ok := b.cells[hash]; ok {
|
||||||
|
b.add(hash, tx, entry)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
blobBufferTxFirstCounter.Inc(1)
|
||||||
|
b.txs[hash] = &txEntry{tx: tx, peer: peer, added: time.Now()}
|
||||||
}
|
}
|
||||||
// tx validation
|
return errs
|
||||||
if err := b.validateTx(tx); err != nil {
|
|
||||||
log.Warn("Transaction validation failed, dropping peer", "peer", peer, "err", err)
|
|
||||||
b.dropPeer(peer)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// vhash check
|
|
||||||
if err := sidecar.ValidateBlobCommitmentHashes(tx.BlobHashes()); err != nil {
|
|
||||||
log.Warn("Commitment hash mismatch, dropping peer", "peer", peer, "err", err)
|
|
||||||
b.dropPeer(peer)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// proof count check
|
|
||||||
if len(sidecar.Proofs) < len(sidecar.Commitments)*kzg4844.CellProofsPerBlob {
|
|
||||||
b.dropPeer(peer)
|
|
||||||
return fmt.Errorf("insufficient proofs in sidecar")
|
|
||||||
}
|
|
||||||
if entry, ok := b.cells[hash]; ok {
|
|
||||||
return b.add(hash, tx, entry)
|
|
||||||
}
|
|
||||||
blobBufferTxFirstCounter.Inc(1)
|
|
||||||
b.txs[hash] = &txEntry{tx: tx, peer: peer, added: time.Now()}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddCells buffers per-peer cell deliveries from the blob fetcher.
|
// AddCells buffers per-peer cell deliveries from the blob fetcher.
|
||||||
// If the transaction is already buffered, verification and pool insertion are attempted.
|
// If the transaction is already buffered, verification and pool insertion are attempted.
|
||||||
func (b *BlobBuffer) AddCells(hash common.Hash, deliveries map[string]*PeerDelivery, custody *types.CustodyBitmap) error {
|
func (b *BlobBuffer) AddCells(hash common.Hash, deliveries map[string]*PeerDelivery, custody *types.CustodyBitmap) {
|
||||||
defer b.updateMetrics()()
|
defer b.updateMetrics()()
|
||||||
|
|
||||||
// First remove any timed-out entries.
|
// First remove any timed-out entries.
|
||||||
|
|
@ -132,15 +144,13 @@ func (b *BlobBuffer) AddCells(hash common.Hash, deliveries map[string]*PeerDeliv
|
||||||
added: time.Now(),
|
added: time.Now(),
|
||||||
}
|
}
|
||||||
if txe, ok := b.txs[hash]; ok {
|
if txe, ok := b.txs[hash]; ok {
|
||||||
return b.add(hash, txe.tx, b.cells[hash])
|
b.add(hash, txe.tx, b.cells[hash])
|
||||||
}
|
}
|
||||||
blobBufferCellsFirstCounter.Inc(1)
|
blobBufferCellsFirstCounter.Inc(1)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: this is very strange
|
|
||||||
// add verifies cells per-peer, sorts them, and adds to the pool.
|
// add verifies cells per-peer, sorts them, and adds to the pool.
|
||||||
func (b *BlobBuffer) add(hash common.Hash, tx *types.Transaction, cells *cellEntry) error {
|
func (b *BlobBuffer) add(hash common.Hash, tx *types.Transaction, cells *cellEntry) {
|
||||||
sidecar := tx.BlobTxSidecar()
|
sidecar := tx.BlobTxSidecar()
|
||||||
|
|
||||||
// Per-peer cell verification
|
// Per-peer cell verification
|
||||||
|
|
@ -148,7 +158,6 @@ func (b *BlobBuffer) add(hash common.Hash, tx *types.Transaction, cells *cellEnt
|
||||||
b.dropPeers(badPeers)
|
b.dropPeers(badPeers)
|
||||||
delete(b.cells, hash)
|
delete(b.cells, hash)
|
||||||
delete(b.txs, hash)
|
delete(b.txs, hash)
|
||||||
return fmt.Errorf("cell verification failed")
|
|
||||||
}
|
}
|
||||||
blobCount := len(tx.BlobHashes())
|
blobCount := len(tx.BlobHashes())
|
||||||
sorted, custody := sortCells(cells, blobCount)
|
sorted, custody := sortCells(cells, blobCount)
|
||||||
|
|
@ -165,10 +174,9 @@ func (b *BlobBuffer) add(hash common.Hash, tx *types.Transaction, cells *cellEnt
|
||||||
CellSidecar: &cellSidecar,
|
CellSidecar: &cellSidecar,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := b.addToPool(pooledTx)
|
b.completed = append(b.completed, pooledTx)
|
||||||
delete(b.cells, hash)
|
delete(b.cells, hash)
|
||||||
delete(b.txs, hash)
|
delete(b.txs, hash)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BlobBuffer) HasTx(hash common.Hash) bool {
|
func (b *BlobBuffer) HasTx(hash common.Hash) bool {
|
||||||
|
|
@ -221,12 +229,20 @@ func (b *BlobBuffer) updateMetrics() func() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// verifyCells verifies each peer's cells against the sidecar.
|
// verifyCells verifies each peer's cells against the sidecar by treating each
|
||||||
|
// per-peer delivery as a mini BlobTxCellSidecar and reusing txpool.ValidateCells.
|
||||||
// Returns the list of peers whose cells failed verification.
|
// Returns the list of peers whose cells failed verification.
|
||||||
func (b *BlobBuffer) verifyCells(entry *cellEntry, sidecar *types.BlobTxSidecar) []string {
|
func (b *BlobBuffer) verifyCells(entry *cellEntry, sidecar *types.BlobTxSidecar) []string {
|
||||||
var badPeers []string
|
var badPeers []string
|
||||||
for peer, delivery := range entry.deliveries {
|
for peer, delivery := range entry.deliveries {
|
||||||
if err := verifyPeerCells(delivery, sidecar); err != nil {
|
perPeer := &types.BlobTxCellSidecar{
|
||||||
|
Version: sidecar.Version,
|
||||||
|
Cells: delivery.Cells,
|
||||||
|
Commitments: sidecar.Commitments,
|
||||||
|
Proofs: sidecar.Proofs,
|
||||||
|
Custody: types.NewCustodyBitmap(delivery.Indices),
|
||||||
|
}
|
||||||
|
if err := txpool.ValidateCells(perPeer); err != nil {
|
||||||
log.Debug("Cell verification failed", "peer", peer, "err", err)
|
log.Debug("Cell verification failed", "peer", peer, "err", err)
|
||||||
badPeers = append(badPeers, peer)
|
badPeers = append(badPeers, peer)
|
||||||
}
|
}
|
||||||
|
|
@ -234,28 +250,6 @@ func (b *BlobBuffer) verifyCells(entry *cellEntry, sidecar *types.BlobTxSidecar)
|
||||||
return badPeers
|
return badPeers
|
||||||
}
|
}
|
||||||
|
|
||||||
// verifyPeerCells verifies a single peer's cells against the sidecar proofs.
|
|
||||||
// delivery.Cells is blob-major: [blob0_cell0..blob0_cellN, blob1_cell0..blob1_cellN, ...]
|
|
||||||
func verifyPeerCells(delivery *PeerDelivery, sidecar *types.BlobTxSidecar) error {
|
|
||||||
cellsPerBlob := len(delivery.Indices)
|
|
||||||
blobCount := len(delivery.Cells) / cellsPerBlob
|
|
||||||
if blobCount == 0 || blobCount != len(sidecar.Commitments) {
|
|
||||||
return fmt.Errorf("blob count mismatch: delivery %d, commitments %d", blobCount, len(sidecar.Commitments))
|
|
||||||
}
|
|
||||||
// Extract proofs corresponding to this peer's cell indices
|
|
||||||
var proofs []kzg4844.Proof
|
|
||||||
for blobIdx := 0; blobIdx < blobCount; blobIdx++ {
|
|
||||||
for _, cellIdx := range delivery.Indices {
|
|
||||||
proofIdx := blobIdx*kzg4844.CellProofsPerBlob + int(cellIdx)
|
|
||||||
if proofIdx >= len(sidecar.Proofs) {
|
|
||||||
return fmt.Errorf("proof index out of range: %d", proofIdx)
|
|
||||||
}
|
|
||||||
proofs = append(proofs, sidecar.Proofs[proofIdx])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return kzg4844.VerifyCells(delivery.Cells, sidecar.Commitments, proofs, delivery.Indices)
|
|
||||||
}
|
|
||||||
|
|
||||||
// sortCells merges all per-peer deliveries into a single flat cell array
|
// sortCells merges all per-peer deliveries into a single flat cell array
|
||||||
// sorted by custody index.
|
// sorted by custody index.
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,9 @@ import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
|
||||||
"github.com/holiman/uint256"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// makeV1Tx creates a V1 blob transaction with cell proofs, then strips blobs
|
// makeV1Tx creates a V1 blob transaction with cell proofs, then strips blobs
|
||||||
|
|
@ -95,7 +92,7 @@ func TestAddTxThenCells(t *testing.T) {
|
||||||
tx := makeV1Tx(t, 0, blobCount, 0, key)
|
tx := makeV1Tx(t, 0, blobCount, 0, key)
|
||||||
hash := tx.Hash()
|
hash := tx.Hash()
|
||||||
|
|
||||||
if err := buf.AddTx(tx, "peerA"); err != nil {
|
if err := buf.AddTx([]*types.Transaction{tx}, "peerA")[0]; err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if !buf.HasTx(hash) {
|
if !buf.HasTx(hash) {
|
||||||
|
|
@ -109,9 +106,7 @@ func TestAddTxThenCells(t *testing.T) {
|
||||||
delivery := makePeerDelivery(t, 0, blobCount, dataIndices)
|
delivery := makePeerDelivery(t, 0, blobCount, dataIndices)
|
||||||
custody := types.NewCustodyBitmap(dataIndices)
|
custody := types.NewCustodyBitmap(dataIndices)
|
||||||
|
|
||||||
if err := buf.AddCells(hash, map[string]*PeerDelivery{"peerB": delivery}, &custody); err != nil {
|
buf.AddCells(hash, map[string]*PeerDelivery{"peerB": delivery}, &custody)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if buf.HasTx(hash) || buf.HasCells(hash) {
|
if buf.HasTx(hash) || buf.HasCells(hash) {
|
||||||
t.Fatal("buffer should be empty after add")
|
t.Fatal("buffer should be empty after add")
|
||||||
}
|
}
|
||||||
|
|
@ -132,14 +127,12 @@ func TestAddCellsThenTx(t *testing.T) {
|
||||||
delivery := makePeerDelivery(t, 0, blobCount, dataIndices)
|
delivery := makePeerDelivery(t, 0, blobCount, dataIndices)
|
||||||
custody := types.NewCustodyBitmap(dataIndices)
|
custody := types.NewCustodyBitmap(dataIndices)
|
||||||
|
|
||||||
if err := buf.AddCells(hash, map[string]*PeerDelivery{"peerB": delivery}, &custody); err != nil {
|
buf.AddCells(hash, map[string]*PeerDelivery{"peerB": delivery}, &custody)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if !buf.HasCells(hash) {
|
if !buf.HasCells(hash) {
|
||||||
t.Fatal("cells should be buffered")
|
t.Fatal("cells should be buffered")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := buf.AddTx(tx, "peerA"); err != nil {
|
if err := buf.AddTx([]*types.Transaction{tx}, "peerA")[0]; err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if buf.HasTx(hash) || buf.HasCells(hash) {
|
if buf.HasTx(hash) || buf.HasCells(hash) {
|
||||||
|
|
@ -154,7 +147,7 @@ func TestMultiPeerDelivery(t *testing.T) {
|
||||||
|
|
||||||
tx := makeV1Tx(t, 0, blobCount, 0, key)
|
tx := makeV1Tx(t, 0, blobCount, 0, key)
|
||||||
hash := tx.Hash()
|
hash := tx.Hash()
|
||||||
buf.AddTx(tx, "peerA")
|
buf.AddTx([]*types.Transaction{tx}, "peerA")
|
||||||
|
|
||||||
indicesA := []uint64{0, 2, 4, 6}
|
indicesA := []uint64{0, 2, 4, 6}
|
||||||
indicesB := []uint64{1, 3, 5, 7}
|
indicesB := []uint64{1, 3, 5, 7}
|
||||||
|
|
@ -164,12 +157,10 @@ func TestMultiPeerDelivery(t *testing.T) {
|
||||||
allIndices := append(indicesA, indicesB...)
|
allIndices := append(indicesA, indicesB...)
|
||||||
custody := types.NewCustodyBitmap(allIndices)
|
custody := types.NewCustodyBitmap(allIndices)
|
||||||
|
|
||||||
if err := buf.AddCells(hash, map[string]*PeerDelivery{
|
buf.AddCells(hash, map[string]*PeerDelivery{
|
||||||
"peerB": deliveryA,
|
"peerB": deliveryA,
|
||||||
"peerC": deliveryB,
|
"peerC": deliveryB,
|
||||||
}, &custody); err != nil {
|
}, &custody)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if buf.HasTx(hash) || buf.HasCells(hash) {
|
if buf.HasTx(hash) || buf.HasCells(hash) {
|
||||||
t.Fatal("buffer should be empty after add")
|
t.Fatal("buffer should be empty after add")
|
||||||
}
|
}
|
||||||
|
|
@ -188,7 +179,7 @@ func TestBadCell(t *testing.T) {
|
||||||
|
|
||||||
tx := makeV1Tx(t, 0, blobCount, 0, key)
|
tx := makeV1Tx(t, 0, blobCount, 0, key)
|
||||||
hash := tx.Hash()
|
hash := tx.Hash()
|
||||||
buf.AddTx(tx, "peerA")
|
buf.AddTx([]*types.Transaction{tx}, "peerA")
|
||||||
|
|
||||||
goodDelivery := makePeerDelivery(t, 0, blobCount, []uint64{0, 1, 2, 3})
|
goodDelivery := makePeerDelivery(t, 0, blobCount, []uint64{0, 1, 2, 3})
|
||||||
badDelivery := makePeerDelivery(t, 0, blobCount, []uint64{4, 5, 6, 7})
|
badDelivery := makePeerDelivery(t, 0, blobCount, []uint64{4, 5, 6, 7})
|
||||||
|
|
@ -201,13 +192,10 @@ func TestBadCell(t *testing.T) {
|
||||||
allIndices := []uint64{0, 1, 2, 3, 4, 5, 6, 7}
|
allIndices := []uint64{0, 1, 2, 3, 4, 5, 6, 7}
|
||||||
custody := types.NewCustodyBitmap(allIndices)
|
custody := types.NewCustodyBitmap(allIndices)
|
||||||
|
|
||||||
err := buf.AddCells(hash, map[string]*PeerDelivery{
|
buf.AddCells(hash, map[string]*PeerDelivery{
|
||||||
"peerB": goodDelivery,
|
"peerB": goodDelivery,
|
||||||
"peerC": badDelivery,
|
"peerC": badDelivery,
|
||||||
}, &custody)
|
}, &custody)
|
||||||
if err == nil {
|
|
||||||
t.Fatal("expected error from bad cells")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(dropped) != 1 || dropped[0] != "peerC" {
|
if len(dropped) != 1 || dropped[0] != "peerC" {
|
||||||
t.Fatalf("only peerC should have been dropped, got: %v", dropped)
|
t.Fatalf("only peerC should have been dropped, got: %v", dropped)
|
||||||
|
|
@ -216,42 +204,3 @@ func TestBadCell(t *testing.T) {
|
||||||
t.Fatal("buffer should be empty after bad cell drop")
|
t.Fatal("buffer should be empty after bad cell drop")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBadTx(t *testing.T) {
|
|
||||||
key, _ := crypto.GenerateKey()
|
|
||||||
|
|
||||||
var dropped []string
|
|
||||||
buf := NewBlobBuffer(
|
|
||||||
func(tx *types.Transaction) error { return nil },
|
|
||||||
func(ptx *BlobTxForPool) error { return nil },
|
|
||||||
func(peer string) { dropped = append(dropped, peer) },
|
|
||||||
)
|
|
||||||
|
|
||||||
blobtx := &types.BlobTx{
|
|
||||||
ChainID: uint256.MustFromBig(params.MainnetChainConfig.ChainID),
|
|
||||||
Nonce: 0,
|
|
||||||
GasTipCap: uint256.NewInt(1),
|
|
||||||
GasFeeCap: uint256.NewInt(1),
|
|
||||||
Gas: 21000,
|
|
||||||
BlobFeeCap: uint256.NewInt(1),
|
|
||||||
BlobHashes: []common.Hash{testBlobVHashes[0]},
|
|
||||||
Value: uint256.NewInt(100),
|
|
||||||
Sidecar: types.NewBlobTxSidecar(types.BlobSidecarVersion1,
|
|
||||||
nil,
|
|
||||||
[]kzg4844.Commitment{testBlobCommits[1]},
|
|
||||||
testBlobCellProofs[1],
|
|
||||||
),
|
|
||||||
}
|
|
||||||
tx := types.MustSignNewTx(key, types.LatestSigner(params.MainnetChainConfig), blobtx)
|
|
||||||
|
|
||||||
err := buf.AddTx(tx, "peerA")
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("expected error from commitment mismatch")
|
|
||||||
}
|
|
||||||
if len(dropped) != 1 || dropped[0] != "peerA" {
|
|
||||||
t.Fatalf("only peerA should have been dropped, got: %v", dropped)
|
|
||||||
}
|
|
||||||
if buf.HasTx(tx.Hash()) {
|
|
||||||
t.Fatal("tx should not be buffered")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ type fetchStatus struct {
|
||||||
|
|
||||||
type BlobFetcherFunctions struct {
|
type BlobFetcherFunctions struct {
|
||||||
HasPayload func(common.Hash) bool
|
HasPayload func(common.Hash) bool
|
||||||
AddCells func(common.Hash, map[string]*PeerCellDelivery, *types.CustodyBitmap) error
|
AddCells func(common.Hash, map[string]*PeerCellDelivery, *types.CustodyBitmap)
|
||||||
FetchPayloads func(string, []common.Hash, *types.CustodyBitmap) error
|
FetchPayloads func(string, []common.Hash, *types.CustodyBitmap) error
|
||||||
DropPeer func(string)
|
DropPeer func(string)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,9 +47,14 @@ func makeTestCellSidecar(blobCount int) *types.BlobTxCellSidecar {
|
||||||
proofs = append(proofs, cellProofs...)
|
proofs = append(proofs, cellProofs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
sidecar, _ := types.NewBlobTxSidecar(types.BlobSidecarVersion1, blobs, commitments, proofs).ToBlobTxCellSidecar()
|
cells, _ := kzg4844.ComputeCells(blobs)
|
||||||
|
return &types.BlobTxCellSidecar{
|
||||||
return sidecar
|
Version: types.BlobSidecarVersion1,
|
||||||
|
Cells: cells,
|
||||||
|
Commitments: commitments,
|
||||||
|
Proofs: proofs,
|
||||||
|
Custody: *types.CustodyBitmapAll,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func selectCells(cells []kzg4844.Cell, custody *types.CustodyBitmap) []kzg4844.Cell {
|
func selectCells(cells []kzg4844.Cell, custody *types.CustodyBitmap) []kzg4844.Cell {
|
||||||
|
|
@ -149,9 +154,7 @@ func TestBlobFetcherFullFetch(t *testing.T) {
|
||||||
return NewBlobFetcher(
|
return NewBlobFetcher(
|
||||||
BlobFetcherFunctions{
|
BlobFetcherFunctions{
|
||||||
HasPayload: func(common.Hash) bool { return false },
|
HasPayload: func(common.Hash) bool { return false },
|
||||||
AddCells: func(common.Hash, map[string]*PeerCellDelivery, *types.CustodyBitmap) error {
|
AddCells: func(common.Hash, map[string]*PeerCellDelivery, *types.CustodyBitmap) {},
|
||||||
return nil
|
|
||||||
},
|
|
||||||
FetchPayloads: func(string, []common.Hash, *types.CustodyBitmap) error {
|
FetchPayloads: func(string, []common.Hash, *types.CustodyBitmap) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
|
@ -241,10 +244,8 @@ func TestBlobFetcherPartialFetch(t *testing.T) {
|
||||||
init: func() *BlobFetcher {
|
init: func() *BlobFetcher {
|
||||||
return NewBlobFetcher(
|
return NewBlobFetcher(
|
||||||
BlobFetcherFunctions{
|
BlobFetcherFunctions{
|
||||||
HasPayload: func(common.Hash) bool { return false },
|
HasPayload: func(common.Hash) bool { return false }, AddCells: func(common.Hash, map[string]*PeerCellDelivery, *types.CustodyBitmap) {},
|
||||||
AddCells: func(common.Hash, map[string]*PeerCellDelivery, *types.CustodyBitmap) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
FetchPayloads: func(string, []common.Hash, *types.CustodyBitmap) error {
|
FetchPayloads: func(string, []common.Hash, *types.CustodyBitmap) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
|
@ -338,10 +339,8 @@ func TestBlobFetcherFullDelivery(t *testing.T) {
|
||||||
init: func() *BlobFetcher {
|
init: func() *BlobFetcher {
|
||||||
return NewBlobFetcher(
|
return NewBlobFetcher(
|
||||||
BlobFetcherFunctions{
|
BlobFetcherFunctions{
|
||||||
HasPayload: func(common.Hash) bool { return false },
|
HasPayload: func(common.Hash) bool { return false }, AddCells: func(common.Hash, map[string]*PeerCellDelivery, *types.CustodyBitmap) {},
|
||||||
AddCells: func(common.Hash, map[string]*PeerCellDelivery, *types.CustodyBitmap) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
FetchPayloads: func(string, []common.Hash, *types.CustodyBitmap) error {
|
FetchPayloads: func(string, []common.Hash, *types.CustodyBitmap) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
|
@ -388,10 +387,8 @@ func TestBlobFetcherPartialDelivery(t *testing.T) {
|
||||||
init: func() *BlobFetcher {
|
init: func() *BlobFetcher {
|
||||||
return NewBlobFetcher(
|
return NewBlobFetcher(
|
||||||
BlobFetcherFunctions{
|
BlobFetcherFunctions{
|
||||||
HasPayload: func(common.Hash) bool { return false },
|
HasPayload: func(common.Hash) bool { return false }, AddCells: func(common.Hash, map[string]*PeerCellDelivery, *types.CustodyBitmap) {},
|
||||||
AddCells: func(common.Hash, map[string]*PeerCellDelivery, *types.CustodyBitmap) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
FetchPayloads: func(string, []common.Hash, *types.CustodyBitmap) error {
|
FetchPayloads: func(string, []common.Hash, *types.CustodyBitmap) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
|
@ -526,10 +523,8 @@ func TestBlobFetcherAvailabilityTimeout(t *testing.T) {
|
||||||
init: func() *BlobFetcher {
|
init: func() *BlobFetcher {
|
||||||
return NewBlobFetcher(
|
return NewBlobFetcher(
|
||||||
BlobFetcherFunctions{
|
BlobFetcherFunctions{
|
||||||
HasPayload: func(common.Hash) bool { return false },
|
HasPayload: func(common.Hash) bool { return false }, AddCells: func(common.Hash, map[string]*PeerCellDelivery, *types.CustodyBitmap) {},
|
||||||
AddCells: func(common.Hash, map[string]*PeerCellDelivery, *types.CustodyBitmap) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
FetchPayloads: func(string, []common.Hash, *types.CustodyBitmap) error {
|
FetchPayloads: func(string, []common.Hash, *types.CustodyBitmap) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
|
@ -570,10 +565,8 @@ func TestBlobFetcherPeerDrop(t *testing.T) {
|
||||||
init: func() *BlobFetcher {
|
init: func() *BlobFetcher {
|
||||||
return NewBlobFetcher(
|
return NewBlobFetcher(
|
||||||
BlobFetcherFunctions{
|
BlobFetcherFunctions{
|
||||||
HasPayload: func(common.Hash) bool { return false },
|
HasPayload: func(common.Hash) bool { return false }, AddCells: func(common.Hash, map[string]*PeerCellDelivery, *types.CustodyBitmap) {},
|
||||||
AddCells: func(common.Hash, map[string]*PeerCellDelivery, *types.CustodyBitmap) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
FetchPayloads: func(string, []common.Hash, *types.CustodyBitmap) error {
|
FetchPayloads: func(string, []common.Hash, *types.CustodyBitmap) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
|
@ -649,10 +642,8 @@ func TestBlobFetcherFetchTimeout(t *testing.T) {
|
||||||
init: func() *BlobFetcher {
|
init: func() *BlobFetcher {
|
||||||
return NewBlobFetcher(
|
return NewBlobFetcher(
|
||||||
BlobFetcherFunctions{
|
BlobFetcherFunctions{
|
||||||
HasPayload: func(common.Hash) bool { return false },
|
HasPayload: func(common.Hash) bool { return false }, AddCells: func(common.Hash, map[string]*PeerCellDelivery, *types.CustodyBitmap) {},
|
||||||
AddCells: func(common.Hash, map[string]*PeerCellDelivery, *types.CustodyBitmap) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
FetchPayloads: func(string, []common.Hash, *types.CustodyBitmap) error {
|
FetchPayloads: func(string, []common.Hash, *types.CustodyBitmap) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
|
@ -1040,7 +1031,7 @@ func TestMultiBlobDeliveryVerification(t *testing.T) {
|
||||||
return NewBlobFetcher(
|
return NewBlobFetcher(
|
||||||
BlobFetcherFunctions{
|
BlobFetcherFunctions{
|
||||||
HasPayload: func(common.Hash) bool { return false },
|
HasPayload: func(common.Hash) bool { return false },
|
||||||
AddCells: func(h common.Hash, deliveries map[string]*PeerCellDelivery, custody *types.CustodyBitmap) error {
|
AddCells: func(h common.Hash, deliveries map[string]*PeerCellDelivery, custody *types.CustodyBitmap) {
|
||||||
// Verify each peer's delivered cells pass KZG cell proof verification
|
// Verify each peer's delivered cells pass KZG cell proof verification
|
||||||
for _, d := range deliveries {
|
for _, d := range deliveries {
|
||||||
var cellProofs []kzg4844.Proof
|
var cellProofs []kzg4844.Proof
|
||||||
|
|
@ -1050,11 +1041,7 @@ func TestMultiBlobDeliveryVerification(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
verifyErr = kzg4844.VerifyCells(d.Cells, sidecar.Commitments, cellProofs, d.Indices)
|
verifyErr = kzg4844.VerifyCells(d.Cells, sidecar.Commitments, cellProofs, d.Indices)
|
||||||
if verifyErr != nil {
|
|
||||||
return verifyErr
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
FetchPayloads: func(string, []common.Hash, *types.CustodyBitmap) error {
|
FetchPayloads: func(string, []common.Hash, *types.CustodyBitmap) error {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,11 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common/mclock"
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/txpool"
|
"github.com/ethereum/go-ethereum/core/txpool"
|
||||||
|
"github.com/ethereum/go-ethereum/core/txpool/blobpool"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -180,10 +183,12 @@ type TxFetcher struct {
|
||||||
alternates map[common.Hash]map[string]struct{} // In-flight transaction alternate origins if retrieval fails
|
alternates map[common.Hash]map[string]struct{} // In-flight transaction alternate origins if retrieval fails
|
||||||
|
|
||||||
// Callbacks
|
// Callbacks
|
||||||
validateMeta func(common.Hash, byte) error // Validate a tx metadata based on the local txpool
|
validateMeta func(common.Hash, byte) error // Validate a tx metadata based on the local txpool
|
||||||
addTxs func(string, []*types.Transaction) []error // Insert a batch of transactions into local txpool
|
addTxs func([]*types.Transaction) []error // Insert a batch of transactions into local txpool
|
||||||
fetchTxs func(string, []common.Hash) error // Retrieves a set of txs from a remote peer
|
fetchTxs func(string, []common.Hash) error // Retrieves a set of txs from a remote peer
|
||||||
dropPeer func(string) // Drops a peer in case of announcement violation
|
dropPeer func(string) // Drops a peer in case of announcement violation
|
||||||
|
|
||||||
|
buffer *blobpool.BlobBuffer
|
||||||
|
|
||||||
step chan struct{} // Notification channel when the fetcher loop iterates
|
step chan struct{} // Notification channel when the fetcher loop iterates
|
||||||
clock mclock.Clock // Monotonic clock or simulated clock for tests
|
clock mclock.Clock // Monotonic clock or simulated clock for tests
|
||||||
|
|
@ -194,16 +199,17 @@ type TxFetcher struct {
|
||||||
// NewTxFetcher creates a transaction fetcher to retrieve transaction
|
// NewTxFetcher creates a transaction fetcher to retrieve transaction
|
||||||
// based on hash announcements.
|
// based on hash announcements.
|
||||||
// Chain can be nil to disable on-chain checks.
|
// Chain can be nil to disable on-chain checks.
|
||||||
func NewTxFetcher(chain *core.BlockChain, validateMeta func(common.Hash, byte) error, addTxs func(string, []*types.Transaction) []error, fetchTxs func(string, []common.Hash) error, dropPeer func(string)) *TxFetcher {
|
func NewTxFetcher(chain *core.BlockChain, validateMeta func(common.Hash, byte) error, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error,
|
||||||
return NewTxFetcherForTests(chain, validateMeta, addTxs, fetchTxs, dropPeer, mclock.System{}, time.Now, nil)
|
dropPeer func(string), buffer *blobpool.BlobBuffer) *TxFetcher {
|
||||||
|
return NewTxFetcherForTests(chain, validateMeta, addTxs, fetchTxs, dropPeer, buffer, mclock.System{}, time.Now, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTxFetcherForTests is a testing method to mock out the realtime clock with
|
// NewTxFetcherForTests is a testing method to mock out the realtime clock with
|
||||||
// a simulated version and the internal randomness with a deterministic one.
|
// a simulated version and the internal randomness with a deterministic one.
|
||||||
// Chain can be nil to disable on-chain checks.
|
// Chain can be nil to disable on-chain checks.
|
||||||
func NewTxFetcherForTests(
|
func NewTxFetcherForTests(
|
||||||
chain *core.BlockChain, validateMeta func(common.Hash, byte) error, addTxs func(string, []*types.Transaction) []error, fetchTxs func(string, []common.Hash) error, dropPeer func(string),
|
chain *core.BlockChain, validateMeta func(common.Hash, byte) error, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error, dropPeer func(string),
|
||||||
clock mclock.Clock, realTime func() time.Time, rand *mrand.Rand) *TxFetcher {
|
buffer *blobpool.BlobBuffer, clock mclock.Clock, realTime func() time.Time, rand *mrand.Rand) *TxFetcher {
|
||||||
return &TxFetcher{
|
return &TxFetcher{
|
||||||
notify: make(chan *txAnnounce),
|
notify: make(chan *txAnnounce),
|
||||||
cleanup: make(chan *txDelivery),
|
cleanup: make(chan *txDelivery),
|
||||||
|
|
@ -224,6 +230,7 @@ func NewTxFetcherForTests(
|
||||||
addTxs: addTxs,
|
addTxs: addTxs,
|
||||||
fetchTxs: fetchTxs,
|
fetchTxs: fetchTxs,
|
||||||
dropPeer: dropPeer,
|
dropPeer: dropPeer,
|
||||||
|
buffer: buffer,
|
||||||
clock: clock,
|
clock: clock,
|
||||||
realTime: realTime,
|
realTime: realTime,
|
||||||
rand: rand,
|
rand: rand,
|
||||||
|
|
@ -312,26 +319,36 @@ func (f *TxFetcher) isKnownUnderpriced(hash common.Hash) bool {
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type deliveryMetrics struct {
|
||||||
|
inMeter *metrics.Meter
|
||||||
|
knownMeter *metrics.Meter
|
||||||
|
underpricedMeter *metrics.Meter
|
||||||
|
otherRejectMeter *metrics.Meter
|
||||||
|
}
|
||||||
|
|
||||||
// Enqueue imports a batch of received transaction into the transaction pool
|
// Enqueue imports a batch of received transaction into the transaction pool
|
||||||
// and the fetcher. This method may be called by both transaction broadcasts and
|
// and the fetcher. This method may be called by both transaction broadcasts and
|
||||||
// direct request replies. The differentiation is important so the fetcher can
|
// direct request replies. The differentiation is important so the fetcher can
|
||||||
// re-schedule missing transactions as soon as possible.
|
// re-schedule missing transactions as soon as possible.
|
||||||
func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool) error {
|
func (f *TxFetcher) Enqueue(peer string, version uint, txs []*types.Transaction, direct bool) error {
|
||||||
var (
|
var violation error
|
||||||
inMeter = txReplyInMeter
|
|
||||||
knownMeter = txReplyKnownMeter
|
metrics := deliveryMetrics{
|
||||||
underpricedMeter = txReplyUnderpricedMeter
|
inMeter: txReplyInMeter,
|
||||||
otherRejectMeter = txReplyOtherRejectMeter
|
knownMeter: txReplyKnownMeter,
|
||||||
violation error
|
underpricedMeter: txReplyUnderpricedMeter,
|
||||||
)
|
otherRejectMeter: txReplyOtherRejectMeter,
|
||||||
|
}
|
||||||
if !direct {
|
if !direct {
|
||||||
inMeter = txBroadcastInMeter
|
metrics = deliveryMetrics{
|
||||||
knownMeter = txBroadcastKnownMeter
|
inMeter: txBroadcastInMeter,
|
||||||
underpricedMeter = txBroadcastUnderpricedMeter
|
knownMeter: txBroadcastKnownMeter,
|
||||||
otherRejectMeter = txBroadcastOtherRejectMeter
|
underpricedMeter: txBroadcastUnderpricedMeter,
|
||||||
|
otherRejectMeter: txBroadcastOtherRejectMeter,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Keep track of all the propagated transactions
|
// Keep track of all the propagated transactions
|
||||||
inMeter.Mark(int64(len(txs)))
|
metrics.inMeter.Mark(int64(len(txs)))
|
||||||
|
|
||||||
// Push all the transactions into the pool, tracking underpriced ones to avoid
|
// Push all the transactions into the pool, tracking underpriced ones to avoid
|
||||||
// re-requesting them and dropping the peer in case of malicious transfers.
|
// re-requesting them and dropping the peer in case of malicious transfers.
|
||||||
|
|
@ -345,38 +362,35 @@ func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool)
|
||||||
if end > len(txs) {
|
if end > len(txs) {
|
||||||
end = len(txs)
|
end = len(txs)
|
||||||
}
|
}
|
||||||
var (
|
|
||||||
duplicate int64
|
|
||||||
underpriced int64
|
|
||||||
otherreject int64
|
|
||||||
)
|
|
||||||
batch := txs[i:end]
|
batch := txs[i:end]
|
||||||
|
var (
|
||||||
for j, err := range f.addTxs(peer, batch) {
|
poolTxs []*types.Transaction
|
||||||
// Track the transaction hash if the price is too low for us.
|
blobTxs []*types.Transaction
|
||||||
// Avoid re-request this transaction when we receive another
|
)
|
||||||
// announcement.
|
if version >= eth.ETH72 {
|
||||||
if errors.Is(err, txpool.ErrUnderpriced) || errors.Is(err, txpool.ErrReplaceUnderpriced) || errors.Is(err, txpool.ErrTxGasPriceTooLow) {
|
for _, tx := range batch {
|
||||||
f.underpriced.Add(batch[j].Hash(), batch[j].Time())
|
if tx.Type() == types.BlobTxType {
|
||||||
|
blobTxs = append(blobTxs, tx)
|
||||||
|
} else {
|
||||||
|
poolTxs = append(poolTxs, tx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Track a few interesting failure types
|
} else {
|
||||||
switch {
|
poolTxs = batch
|
||||||
case err == nil: // Noop, but need to handle to not count these
|
}
|
||||||
|
batch = append(poolTxs, blobTxs...)
|
||||||
|
errs := append(f.addTxs(poolTxs), f.buffer.AddTx(blobTxs, peer)...)
|
||||||
|
|
||||||
case errors.Is(err, txpool.ErrAlreadyKnown):
|
hashes := make([]common.Hash, len(batch))
|
||||||
duplicate++
|
for j := range batch {
|
||||||
|
hashes[j] = batch[j].Hash()
|
||||||
case errors.Is(err, txpool.ErrUnderpriced) || errors.Is(err, txpool.ErrReplaceUnderpriced) || errors.Is(err, txpool.ErrTxGasPriceTooLow):
|
}
|
||||||
underpriced++
|
for j, err := range errs {
|
||||||
|
if errors.Is(err, txpool.ErrKZGVerificationError) || errors.Is(err, txpool.ErrSidecarFormatError) {
|
||||||
case errors.Is(err, txpool.ErrKZGVerificationError) || errors.Is(err, txpool.ErrSidecarFormatError):
|
|
||||||
// KZG verification failed, terminate transaction processing immediately.
|
// KZG verification failed, terminate transaction processing immediately.
|
||||||
// Since KZG verification is computationally expensive, this acts as a
|
// Since KZG verification is computationally expensive, this acts as a
|
||||||
// defensive measure against potential DoS attacks.
|
// defensive measure against potential DoS attacks.
|
||||||
violation = err
|
violation = err
|
||||||
|
|
||||||
default:
|
|
||||||
otherreject++
|
|
||||||
}
|
}
|
||||||
added = append(added, batch[j].Hash())
|
added = append(added, batch[j].Hash())
|
||||||
metas = append(metas, txMetadata{
|
metas = append(metas, txMetadata{
|
||||||
|
|
@ -389,15 +403,7 @@ func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
knownMeter.Mark(duplicate)
|
f.handleAddErrors(hashes, errs, metrics)
|
||||||
underpricedMeter.Mark(underpriced)
|
|
||||||
otherRejectMeter.Mark(otherreject)
|
|
||||||
|
|
||||||
// If 'other reject' is >25% of the deliveries in any batch, sleep a bit.
|
|
||||||
if otherreject > int64((len(batch)+3)/4) {
|
|
||||||
log.Debug("Peer delivering stale or invalid transactions", "peer", peer, "rejected", otherreject)
|
|
||||||
time.Sleep(200 * time.Millisecond)
|
|
||||||
}
|
|
||||||
// If we encountered a protocol violation, disconnect this peer.
|
// If we encountered a protocol violation, disconnect this peer.
|
||||||
if violation != nil {
|
if violation != nil {
|
||||||
break
|
break
|
||||||
|
|
@ -411,6 +417,42 @@ func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *TxFetcher) handleAddErrors(txs []common.Hash, errs []error, metrics deliveryMetrics) {
|
||||||
|
var (
|
||||||
|
duplicate int64
|
||||||
|
underpriced int64
|
||||||
|
otherreject int64
|
||||||
|
)
|
||||||
|
for i, err := range errs {
|
||||||
|
// Track a few interesting failure types
|
||||||
|
switch {
|
||||||
|
case err == nil: // Noop, but need to handle to not count these
|
||||||
|
|
||||||
|
case errors.Is(err, txpool.ErrAlreadyKnown):
|
||||||
|
duplicate++
|
||||||
|
|
||||||
|
// Track the transaction hash if the price is too low for us.
|
||||||
|
// Avoid re-request this transaction when we receive another
|
||||||
|
// announcement.
|
||||||
|
case errors.Is(err, txpool.ErrUnderpriced) || errors.Is(err, txpool.ErrReplaceUnderpriced) || errors.Is(err, txpool.ErrTxGasPriceTooLow):
|
||||||
|
f.underpriced.Add(txs[i], f.realTime())
|
||||||
|
underpriced++
|
||||||
|
|
||||||
|
default:
|
||||||
|
otherreject++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
metrics.knownMeter.Mark(duplicate)
|
||||||
|
metrics.underpricedMeter.Mark(underpriced)
|
||||||
|
metrics.otherRejectMeter.Mark(otherreject)
|
||||||
|
|
||||||
|
// If 'other reject' is >25% of the deliveries in any batch, sleep a bit.
|
||||||
|
if otherreject > int64((len(txs)+3)/4) {
|
||||||
|
log.Debug("Peer delivering stale or invalid transactions", "rejected", otherreject)
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Drop should be called when a peer disconnects. It cleans up all the internal
|
// Drop should be called when a peer disconnects. It cleans up all the internal
|
||||||
// data structures of the given node.
|
// data structures of the given node.
|
||||||
func (f *TxFetcher) Drop(peer string) error {
|
func (f *TxFetcher) Drop(peer string) error {
|
||||||
|
|
@ -456,6 +498,14 @@ func (f *TxFetcher) loop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
txs, errs := f.buffer.Flush()
|
||||||
|
f.handleAddErrors(txs, errs, deliveryMetrics{
|
||||||
|
inMeter: txReplyInMeter,
|
||||||
|
knownMeter: txReplyKnownMeter,
|
||||||
|
underpricedMeter: txReplyUnderpricedMeter,
|
||||||
|
otherRejectMeter: txReplyOtherRejectMeter,
|
||||||
|
})
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case ann := <-f.notify:
|
case ann := <-f.notify:
|
||||||
// Drop part of the new announcements if there are too many accumulated.
|
// Drop part of the new announcements if there are too many accumulated.
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,11 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/mclock"
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
"github.com/ethereum/go-ethereum/core/txpool"
|
"github.com/ethereum/go-ethereum/core/txpool"
|
||||||
|
"github.com/ethereum/go-ethereum/core/txpool/blobpool"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
@ -60,9 +62,10 @@ type doTxNotify struct {
|
||||||
sizes []uint32
|
sizes []uint32
|
||||||
}
|
}
|
||||||
type doTxEnqueue struct {
|
type doTxEnqueue struct {
|
||||||
peer string
|
peer string
|
||||||
txs []*types.Transaction
|
version uint
|
||||||
direct bool
|
txs []*types.Transaction
|
||||||
|
direct bool
|
||||||
}
|
}
|
||||||
type doWait struct {
|
type doWait struct {
|
||||||
time time.Duration
|
time time.Duration
|
||||||
|
|
@ -87,17 +90,28 @@ type txFetcherTest struct {
|
||||||
steps []interface{}
|
steps []interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newTestBlobBuffer returns a BlobBuffer with no-op callbacks for tests that
|
||||||
|
// don't exercise blob handling but still need a non-nil buffer.
|
||||||
|
func newTestBlobBuffer() *blobpool.BlobBuffer {
|
||||||
|
return blobpool.NewBlobBuffer(
|
||||||
|
func(*types.Transaction) error { return nil },
|
||||||
|
func(*blobpool.BlobTxForPool) error { return nil },
|
||||||
|
func(string) {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// newTestTxFetcher creates a tx fetcher with noop callbacks, simulated clock,
|
// newTestTxFetcher creates a tx fetcher with noop callbacks, simulated clock,
|
||||||
// and deterministic randomness.
|
// and deterministic randomness.
|
||||||
func newTestTxFetcher() *TxFetcher {
|
func newTestTxFetcher() *TxFetcher {
|
||||||
return NewTxFetcher(
|
return NewTxFetcher(
|
||||||
nil,
|
nil,
|
||||||
func(common.Hash, byte) error { return nil },
|
func(common.Hash, byte) error { return nil },
|
||||||
func(_ string, txs []*types.Transaction) []error {
|
func(txs []*types.Transaction) []error {
|
||||||
return make([]error, len(txs))
|
return make([]error, len(txs))
|
||||||
},
|
},
|
||||||
func(string, []common.Hash) error { return nil },
|
func(string, []common.Hash) error { return nil },
|
||||||
nil,
|
nil,
|
||||||
|
newTestBlobBuffer(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1172,7 +1186,7 @@ func TestTransactionFetcherUnderpricedDedup(t *testing.T) {
|
||||||
testTransactionFetcherParallel(t, txFetcherTest{
|
testTransactionFetcherParallel(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: func() *TxFetcher {
|
||||||
f := newTestTxFetcher()
|
f := newTestTxFetcher()
|
||||||
f.addTxs = func(_ string, txs []*types.Transaction) []error {
|
f.addTxs = func(txs []*types.Transaction) []error {
|
||||||
errs := make([]error, len(txs))
|
errs := make([]error, len(txs))
|
||||||
for i := 0; i < len(errs); i++ {
|
for i := 0; i < len(errs); i++ {
|
||||||
if i%3 == 0 {
|
if i%3 == 0 {
|
||||||
|
|
@ -1270,7 +1284,7 @@ func TestTransactionFetcherUnderpricedDoSProtection(t *testing.T) {
|
||||||
testTransactionFetcher(t, txFetcherTest{
|
testTransactionFetcher(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: func() *TxFetcher {
|
||||||
f := newTestTxFetcher()
|
f := newTestTxFetcher()
|
||||||
f.addTxs = func(_ string, txs []*types.Transaction) []error {
|
f.addTxs = func(txs []*types.Transaction) []error {
|
||||||
errs := make([]error, len(txs))
|
errs := make([]error, len(txs))
|
||||||
for i := 0; i < len(errs); i++ {
|
for i := 0; i < len(errs); i++ {
|
||||||
errs[i] = txpool.ErrUnderpriced
|
errs[i] = txpool.ErrUnderpriced
|
||||||
|
|
@ -1787,7 +1801,7 @@ func TestTransactionProtocolViolation(t *testing.T) {
|
||||||
testTransactionFetcherParallel(t, txFetcherTest{
|
testTransactionFetcherParallel(t, txFetcherTest{
|
||||||
init: func() *TxFetcher {
|
init: func() *TxFetcher {
|
||||||
f := newTestTxFetcher()
|
f := newTestTxFetcher()
|
||||||
f.addTxs = func(_ string, txs []*types.Transaction) []error {
|
f.addTxs = func(txs []*types.Transaction) []error {
|
||||||
var errs []error
|
var errs []error
|
||||||
for range txs {
|
for range txs {
|
||||||
errs = append(errs, txpool.ErrKZGVerificationError)
|
errs = append(errs, txpool.ErrKZGVerificationError)
|
||||||
|
|
@ -1899,7 +1913,7 @@ func testTransactionFetcher(t *testing.T, tt txFetcherTest) {
|
||||||
}
|
}
|
||||||
|
|
||||||
case doTxEnqueue:
|
case doTxEnqueue:
|
||||||
if err := fetcher.Enqueue(step.peer, step.txs, step.direct); err != nil {
|
if err := fetcher.Enqueue(step.peer, step.version, step.txs, step.direct); err != nil {
|
||||||
t.Errorf("step %d: %v", i, err)
|
t.Errorf("step %d: %v", i, err)
|
||||||
}
|
}
|
||||||
<-wait // Fetcher needs to process this, wait until it's done
|
<-wait // Fetcher needs to process this, wait until it's done
|
||||||
|
|
@ -2194,7 +2208,7 @@ func TestTransactionForgotten(t *testing.T) {
|
||||||
fetcher := NewTxFetcherForTests(
|
fetcher := NewTxFetcherForTests(
|
||||||
nil,
|
nil,
|
||||||
func(common.Hash, byte) error { return nil },
|
func(common.Hash, byte) error { return nil },
|
||||||
func(_ string, txs []*types.Transaction) []error {
|
func(txs []*types.Transaction) []error {
|
||||||
errs := make([]error, len(txs))
|
errs := make([]error, len(txs))
|
||||||
for i := 0; i < len(errs); i++ {
|
for i := 0; i < len(errs); i++ {
|
||||||
errs[i] = txpool.ErrUnderpriced
|
errs[i] = txpool.ErrUnderpriced
|
||||||
|
|
@ -2203,6 +2217,7 @@ func TestTransactionForgotten(t *testing.T) {
|
||||||
},
|
},
|
||||||
func(string, []common.Hash) error { return nil },
|
func(string, []common.Hash) error { return nil },
|
||||||
func(string) {},
|
func(string) {},
|
||||||
|
newTestBlobBuffer(),
|
||||||
mockClock,
|
mockClock,
|
||||||
mockTime,
|
mockTime,
|
||||||
rand.New(rand.NewSource(0)), // Use fixed seed for deterministic behavior
|
rand.New(rand.NewSource(0)), // Use fixed seed for deterministic behavior
|
||||||
|
|
@ -2219,7 +2234,7 @@ func TestTransactionForgotten(t *testing.T) {
|
||||||
tx2.SetTime(now)
|
tx2.SetTime(now)
|
||||||
|
|
||||||
// Initial state: both transactions should be marked as underpriced
|
// Initial state: both transactions should be marked as underpriced
|
||||||
if err := fetcher.Enqueue("peer", []*types.Transaction{tx1, tx2}, false); err != nil {
|
if err := fetcher.Enqueue("peer", eth.ETH70, []*types.Transaction{tx1, tx2}, false); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if !fetcher.isKnownUnderpriced(tx1.Hash()) {
|
if !fetcher.isKnownUnderpriced(tx1.Hash()) {
|
||||||
|
|
@ -2268,7 +2283,7 @@ func TestTransactionForgotten(t *testing.T) {
|
||||||
|
|
||||||
// Re-enqueue tx1 with updated timestamp
|
// Re-enqueue tx1 with updated timestamp
|
||||||
tx1.SetTime(mockTime())
|
tx1.SetTime(mockTime())
|
||||||
if err := fetcher.Enqueue("peer", []*types.Transaction{tx1}, false); err != nil {
|
if err := fetcher.Enqueue("peer", eth.ETH70, []*types.Transaction{tx1}, false); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if !fetcher.isKnownUnderpriced(tx1.Hash()) {
|
if !fetcher.isKnownUnderpriced(tx1.Hash()) {
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,6 @@ type handler struct {
|
||||||
downloader *downloader.Downloader
|
downloader *downloader.Downloader
|
||||||
txFetcher *fetcher.TxFetcher
|
txFetcher *fetcher.TxFetcher
|
||||||
blobFetcher *fetcher.BlobFetcher
|
blobFetcher *fetcher.BlobFetcher
|
||||||
blobBuffer *blobpool.BlobBuffer
|
|
||||||
peers *peerSet
|
peers *peerSet
|
||||||
txBroadcastKey [16]byte
|
txBroadcastKey [16]byte
|
||||||
|
|
||||||
|
|
@ -190,33 +189,13 @@ func newHandler(config *handlerConfig) (*handler, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct the blob buffer for assembling blob txs from separate tx and cell deliveries.
|
// Construct the blob buffer for assembling blob txs from separate tx and cell deliveries.
|
||||||
h.blobBuffer = blobpool.NewBlobBuffer(h.blobpool.ValidateTxBasics, h.blobpool.AddPooledTx, h.removePeer)
|
blobBuffer := blobpool.NewBlobBuffer(h.blobpool.ValidateTxBasics, h.blobpool.AddPooledTx, h.removePeer)
|
||||||
|
|
||||||
addTxs := func(peer string, txs []*types.Transaction) []error {
|
addTxs := func(txs []*types.Transaction) []error {
|
||||||
errs := make([]error, len(txs))
|
return h.txpool.Add(txs, false)
|
||||||
p := h.peers.peer(peer)
|
|
||||||
isETH72 := p != nil && p.Version() >= eth.ETH72
|
|
||||||
|
|
||||||
var poolTxs []*types.Transaction
|
|
||||||
var index []int
|
|
||||||
for i, tx := range txs {
|
|
||||||
if isETH72 && tx.Type() == types.BlobTxType {
|
|
||||||
errs[i] = h.blobBuffer.AddTx(tx, peer)
|
|
||||||
} else {
|
|
||||||
poolTxs = append(poolTxs, tx)
|
|
||||||
index = append(index, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(poolTxs) > 0 {
|
|
||||||
poolErrs := h.txpool.Add(poolTxs, false)
|
|
||||||
for j, idx := range index {
|
|
||||||
errs[idx] = poolErrs[j]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errs
|
|
||||||
}
|
}
|
||||||
validateMeta := func(tx common.Hash, kind byte) error {
|
validateMeta := func(tx common.Hash, kind byte) error {
|
||||||
if h.txpool.Has(tx) || h.blobBuffer.HasTx(tx) {
|
if h.txpool.Has(tx) || blobBuffer.HasTx(tx) {
|
||||||
return txpool.ErrAlreadyKnown
|
return txpool.ErrAlreadyKnown
|
||||||
}
|
}
|
||||||
if !h.txpool.FilterType(kind) {
|
if !h.txpool.FilterType(kind) {
|
||||||
|
|
@ -224,7 +203,7 @@ func newHandler(config *handlerConfig) (*handler, error) {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
h.txFetcher = fetcher.NewTxFetcher(h.chain, validateMeta, addTxs, fetchTx, h.removePeer)
|
h.txFetcher = fetcher.NewTxFetcher(h.chain, validateMeta, addTxs, fetchTx, h.removePeer, blobBuffer)
|
||||||
|
|
||||||
// Construct the blob fetcher for cell-based blob data availability
|
// Construct the blob fetcher for cell-based blob data availability
|
||||||
blobCallbacks := fetcher.BlobFetcherFunctions{
|
blobCallbacks := fetcher.BlobFetcherFunctions{
|
||||||
|
|
@ -236,14 +215,14 @@ func newHandler(config *handlerConfig) (*handler, error) {
|
||||||
return p.RequestPayload(hashes, cells)
|
return p.RequestPayload(hashes, cells)
|
||||||
},
|
},
|
||||||
HasPayload: func(hash common.Hash) bool {
|
HasPayload: func(hash common.Hash) bool {
|
||||||
return h.blobpool.Has(hash) || h.blobBuffer.HasCells(hash)
|
return h.blobpool.Has(hash) || blobBuffer.HasCells(hash)
|
||||||
},
|
},
|
||||||
AddCells: func(hash common.Hash, deliveries map[string]*fetcher.PeerCellDelivery, custody *types.CustodyBitmap) error {
|
AddCells: func(hash common.Hash, deliveries map[string]*fetcher.PeerCellDelivery, custody *types.CustodyBitmap) {
|
||||||
converted := make(map[string]*blobpool.PeerDelivery, len(deliveries))
|
converted := make(map[string]*blobpool.PeerDelivery, len(deliveries))
|
||||||
for peer, d := range deliveries {
|
for peer, d := range deliveries {
|
||||||
converted[peer] = &blobpool.PeerDelivery{Cells: d.Cells, Indices: d.Indices}
|
converted[peer] = &blobpool.PeerDelivery{Cells: d.Cells, Indices: d.Indices}
|
||||||
}
|
}
|
||||||
return h.blobBuffer.AddCells(hash, converted, custody)
|
blobBuffer.AddCells(hash, converted, custody)
|
||||||
},
|
},
|
||||||
DropPeer: h.removePeer,
|
DropPeer: h.removePeer,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ func (h *ethHandler) Handle(peer *eth.Peer, packet eth.Packet) error {
|
||||||
if err := handleTransactions(peer, txs, true); err != nil {
|
if err := handleTransactions(peer, txs, true); err != nil {
|
||||||
return fmt.Errorf("Transactions: %v", err)
|
return fmt.Errorf("Transactions: %v", err)
|
||||||
}
|
}
|
||||||
return h.txFetcher.Enqueue(peer.ID(), txs, false)
|
return h.txFetcher.Enqueue(peer.ID(), peer.Version(), txs, false)
|
||||||
|
|
||||||
case *eth.PooledTransactionsPacket:
|
case *eth.PooledTransactionsPacket:
|
||||||
txs, err := packet.List.Items()
|
txs, err := packet.List.Items()
|
||||||
|
|
@ -91,7 +91,7 @@ func (h *ethHandler) Handle(peer *eth.Peer, packet eth.Packet) error {
|
||||||
if err := handleTransactions(peer, txs, false); err != nil {
|
if err := handleTransactions(peer, txs, false); err != nil {
|
||||||
return fmt.Errorf("PooledTransactions: %v", err)
|
return fmt.Errorf("PooledTransactions: %v", err)
|
||||||
}
|
}
|
||||||
return h.txFetcher.Enqueue(peer.ID(), txs, true)
|
return h.txFetcher.Enqueue(peer.ID(), peer.Version(), txs, true)
|
||||||
|
|
||||||
case *eth.CellsResponse:
|
case *eth.CellsResponse:
|
||||||
return h.blobFetcher.Enqueue(peer.ID(), packet.Hashes, packet.Cells, packet.Mask)
|
return h.blobFetcher.Enqueue(peer.ID(), packet.Hashes, packet.Cells, packet.Mask)
|
||||||
|
|
|
||||||
|
|
@ -25,22 +25,28 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/mclock"
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
|
"github.com/ethereum/go-ethereum/core/txpool/blobpool"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/eth/fetcher"
|
"github.com/ethereum/go-ethereum/eth/fetcher"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
peers []string
|
peers []string
|
||||||
txs []*types.Transaction
|
peerVersions map[string]uint
|
||||||
|
txs []*types.Transaction
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Random is nice, but we need it deterministic
|
// Random is nice, but we need it deterministic
|
||||||
rand := rand.New(rand.NewSource(0x3a29))
|
rand := rand.New(rand.NewSource(0x3a29))
|
||||||
|
|
||||||
|
supportedVersions := []uint{eth.ETH69, eth.ETH70, eth.ETH72}
|
||||||
peers = make([]string, 10)
|
peers = make([]string, 10)
|
||||||
|
peerVersions = make(map[string]uint, len(peers))
|
||||||
for i := 0; i < len(peers); i++ {
|
for i := 0; i < len(peers); i++ {
|
||||||
peers[i] = fmt.Sprintf("Peer #%d", i)
|
peers[i] = fmt.Sprintf("Peer #%d", i)
|
||||||
|
peerVersions[peers[i]] = supportedVersions[i%len(supportedVersions)]
|
||||||
}
|
}
|
||||||
txs = make([]*types.Transaction, 65536) // We need to bump enough to hit all the limits
|
txs = make([]*types.Transaction, 65536) // We need to bump enough to hit all the limits
|
||||||
for i := 0; i < len(txs); i++ {
|
for i := 0; i < len(txs); i++ {
|
||||||
|
|
@ -80,11 +86,16 @@ func fuzz(input []byte) int {
|
||||||
f := fetcher.NewTxFetcherForTests(
|
f := fetcher.NewTxFetcherForTests(
|
||||||
nil,
|
nil,
|
||||||
func(common.Hash, byte) error { return nil },
|
func(common.Hash, byte) error { return nil },
|
||||||
func(_ string, txs []*types.Transaction) []error {
|
func(txs []*types.Transaction) []error {
|
||||||
return make([]error, len(txs))
|
return make([]error, len(txs))
|
||||||
},
|
},
|
||||||
func(string, []common.Hash) error { return nil },
|
func(string, []common.Hash) error { return nil },
|
||||||
nil,
|
nil,
|
||||||
|
blobpool.NewBlobBuffer(
|
||||||
|
func(*types.Transaction) error { return nil },
|
||||||
|
func(*blobpool.BlobTxForPool) error { return nil },
|
||||||
|
func(string) {},
|
||||||
|
),
|
||||||
clock,
|
clock,
|
||||||
func() time.Time {
|
func() time.Time {
|
||||||
nanoTime := int64(clock.Now())
|
nanoTime := int64(clock.Now())
|
||||||
|
|
@ -180,7 +191,7 @@ func fuzz(input []byte) int {
|
||||||
if verbose {
|
if verbose {
|
||||||
fmt.Println("Enqueue", peer, deliverIdxs, direct)
|
fmt.Println("Enqueue", peer, deliverIdxs, direct)
|
||||||
}
|
}
|
||||||
if err := f.Enqueue(peer, deliveries, direct); err != nil {
|
if err := f.Enqueue(peer, peerVersions[peer], deliveries, direct); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue