github.com/ethereum/go-ethereum@v1.14.4-0.20240516095835-473ee8fc07a3/core/txpool/blobpool/evictheap_test.go (about)

     1  // Copyright 2023 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package blobpool
    18  
    19  import (
    20  	"container/heap"
    21  	mrand "math/rand"
    22  	"testing"
    23  
    24  	"github.com/ethereum/go-ethereum/common"
    25  	"github.com/ethereum/go-ethereum/params"
    26  	"github.com/holiman/uint256"
    27  )
    28  
    29  var rand = mrand.New(mrand.NewSource(1))
    30  
    31  // verifyHeapInternals verifies that all accounts present in the index are also
    32  // present in the heap and internals are consistent across various indices.
    33  func verifyHeapInternals(t *testing.T, evict *evictHeap) {
    34  	t.Helper()
    35  
    36  	// Ensure that all accounts are present in the heap and no extras
    37  	seen := make(map[common.Address]struct{})
    38  	for i, addr := range evict.addrs {
    39  		seen[addr] = struct{}{}
    40  		if _, ok := (*evict.metas)[addr]; !ok {
    41  			t.Errorf("heap contains unexpected address at slot %d: %v", i, addr)
    42  		}
    43  	}
    44  	for addr := range *evict.metas {
    45  		if _, ok := seen[addr]; !ok {
    46  			t.Errorf("heap is missing required address %v", addr)
    47  		}
    48  	}
    49  	if len(evict.addrs) != len(*evict.metas) {
    50  		t.Errorf("heap size %d mismatches metadata size %d", len(evict.addrs), len(*evict.metas))
    51  	}
    52  	// Ensure that all accounts are present in the heap order index and no extras
    53  	have := make([]common.Address, len(evict.index))
    54  	for addr, i := range evict.index {
    55  		have[i] = addr
    56  	}
    57  	if len(have) != len(evict.addrs) {
    58  		t.Errorf("heap index size %d mismatches heap size %d", len(have), len(evict.addrs))
    59  	}
    60  	for i := 0; i < len(have) && i < len(evict.addrs); i++ {
    61  		if have[i] != evict.addrs[i] {
    62  			t.Errorf("heap index for slot %d mismatches: have %v, want %v", i, have[i], evict.addrs[i])
    63  		}
    64  	}
    65  }
    66  
    67  // Tests that the price heap can correctly sort its set of transactions based on
    68  // an input base- and blob fee.
    69  func TestPriceHeapSorting(t *testing.T) {
    70  	tests := []struct {
    71  		execTips []uint64
    72  		execFees []uint64
    73  		blobFees []uint64
    74  
    75  		basefee uint64
    76  		blobfee uint64
    77  
    78  		order []int
    79  	}{
    80  		// If everything is above the basefee and blobfee, order by miner tip
    81  		{
    82  			execTips: []uint64{1, 0, 2},
    83  			execFees: []uint64{1, 2, 3},
    84  			blobFees: []uint64{3, 2, 1},
    85  			basefee:  0,
    86  			blobfee:  0,
    87  			order:    []int{1, 0, 2},
    88  		},
    89  		// If only basefees are used (blob fee matches with network), return the
    90  		// ones the furthest below the current basefee, splitting same ones with
    91  		// the tip. Anything above the basefee should be split by tip.
    92  		{
    93  			execTips: []uint64{100, 50, 100, 50, 1, 2, 3},
    94  			execFees: []uint64{1000, 1000, 500, 500, 2000, 2000, 2000},
    95  			blobFees: []uint64{0, 0, 0, 0, 0, 0, 0},
    96  			basefee:  1999,
    97  			blobfee:  0,
    98  			order:    []int{3, 2, 1, 0, 4, 5, 6},
    99  		},
   100  		// If only blobfees are used (base fee matches with network), return the
   101  		// ones the furthest below the current blobfee, splitting same ones with
   102  		// the tip. Anything above the blobfee should be split by tip.
   103  		{
   104  			execTips: []uint64{100, 50, 100, 50, 1, 2, 3},
   105  			execFees: []uint64{0, 0, 0, 0, 0, 0, 0},
   106  			blobFees: []uint64{1000, 1000, 500, 500, 2000, 2000, 2000},
   107  			basefee:  0,
   108  			blobfee:  1999,
   109  			order:    []int{3, 2, 1, 0, 4, 5, 6},
   110  		},
   111  		// If both basefee and blobfee is specified, sort by the larger distance
   112  		// of the two from the current network conditions, splitting same (loglog)
   113  		// ones via the tip.
   114  		//
   115  		// Basefee: 1000
   116  		// Blobfee: 100
   117  		//
   118  		// Tx #0: (800, 80) - 2 jumps below both => priority -1
   119  		// Tx #1: (630, 63) - 4 jumps below both => priority -2
   120  		// Tx #2: (800, 63) - 2 jumps below basefee, 4 jumps below blobfee => priority -2 (blob penalty dominates)
   121  		// Tx #3: (630, 80) - 4 jumps below basefee, 2 jumps below blobfee => priority -2 (base penalty dominates)
   122  		//
   123  		// Txs 1, 2, 3 share the same priority, split via tip, prefer 0 as the best
   124  		{
   125  			execTips: []uint64{1, 2, 3, 4},
   126  			execFees: []uint64{800, 630, 800, 630},
   127  			blobFees: []uint64{80, 63, 63, 80},
   128  			basefee:  1000,
   129  			blobfee:  100,
   130  			order:    []int{1, 2, 3, 0},
   131  		},
   132  	}
   133  	for i, tt := range tests {
   134  		// Create an index of the transactions
   135  		index := make(map[common.Address][]*blobTxMeta)
   136  		for j := byte(0); j < byte(len(tt.execTips)); j++ {
   137  			addr := common.Address{j}
   138  
   139  			var (
   140  				execTip = uint256.NewInt(tt.execTips[j])
   141  				execFee = uint256.NewInt(tt.execFees[j])
   142  				blobFee = uint256.NewInt(tt.blobFees[j])
   143  
   144  				basefeeJumps = dynamicFeeJumps(execFee)
   145  				blobfeeJumps = dynamicFeeJumps(blobFee)
   146  			)
   147  			index[addr] = []*blobTxMeta{{
   148  				id:                   uint64(j),
   149  				size:                 128 * 1024,
   150  				nonce:                0,
   151  				execTipCap:           execTip,
   152  				execFeeCap:           execFee,
   153  				blobFeeCap:           blobFee,
   154  				basefeeJumps:         basefeeJumps,
   155  				blobfeeJumps:         blobfeeJumps,
   156  				evictionExecTip:      execTip,
   157  				evictionExecFeeJumps: basefeeJumps,
   158  				evictionBlobFeeJumps: blobfeeJumps,
   159  			}}
   160  		}
   161  		// Create a price heap and check the pop order
   162  		priceheap := newPriceHeap(uint256.NewInt(tt.basefee), uint256.NewInt(tt.blobfee), &index)
   163  		verifyHeapInternals(t, priceheap)
   164  
   165  		for j := 0; j < len(tt.order); j++ {
   166  			if next := heap.Pop(priceheap); int(next.(common.Address)[0]) != tt.order[j] {
   167  				t.Errorf("test %d, item %d: order mismatch: have %d, want %d", i, j, next.(common.Address)[0], tt.order[j])
   168  			} else {
   169  				delete(index, next.(common.Address)) // remove to simulate a correct pool for the test
   170  			}
   171  			verifyHeapInternals(t, priceheap)
   172  		}
   173  	}
   174  }
   175  
   176  // Benchmarks reheaping the entire set of accounts in the blob pool.
   177  func BenchmarkPriceHeapReinit1MB(b *testing.B)   { benchmarkPriceHeapReinit(b, 1024*1024) }
   178  func BenchmarkPriceHeapReinit10MB(b *testing.B)  { benchmarkPriceHeapReinit(b, 10*1024*1024) }
   179  func BenchmarkPriceHeapReinit100MB(b *testing.B) { benchmarkPriceHeapReinit(b, 100*1024*1024) }
   180  func BenchmarkPriceHeapReinit1GB(b *testing.B)   { benchmarkPriceHeapReinit(b, 1024*1024*1024) }
   181  func BenchmarkPriceHeapReinit10GB(b *testing.B)  { benchmarkPriceHeapReinit(b, 10*1024*1024*1024) }
   182  func BenchmarkPriceHeapReinit25GB(b *testing.B)  { benchmarkPriceHeapReinit(b, 25*1024*1024*1024) }
   183  func BenchmarkPriceHeapReinit50GB(b *testing.B)  { benchmarkPriceHeapReinit(b, 50*1024*1024*1024) }
   184  func BenchmarkPriceHeapReinit100GB(b *testing.B) { benchmarkPriceHeapReinit(b, 100*1024*1024*1024) }
   185  
   186  func benchmarkPriceHeapReinit(b *testing.B, datacap uint64) {
   187  	// Calculate how many unique transactions we can fit into the provided disk
   188  	// data cap
   189  	blobs := datacap / (params.BlobTxBytesPerFieldElement * params.BlobTxFieldElementsPerBlob)
   190  
   191  	// Create a random set of transactions with random fees. Use a separate account
   192  	// for each transaction to make it worse case.
   193  	index := make(map[common.Address][]*blobTxMeta)
   194  	for i := 0; i < int(blobs); i++ {
   195  		var addr common.Address
   196  		rand.Read(addr[:])
   197  
   198  		var (
   199  			execTip = uint256.NewInt(rand.Uint64())
   200  			execFee = uint256.NewInt(rand.Uint64())
   201  			blobFee = uint256.NewInt(rand.Uint64())
   202  
   203  			basefeeJumps = dynamicFeeJumps(execFee)
   204  			blobfeeJumps = dynamicFeeJumps(blobFee)
   205  		)
   206  		index[addr] = []*blobTxMeta{{
   207  			id:                   uint64(i),
   208  			size:                 128 * 1024,
   209  			nonce:                0,
   210  			execTipCap:           execTip,
   211  			execFeeCap:           execFee,
   212  			blobFeeCap:           blobFee,
   213  			basefeeJumps:         basefeeJumps,
   214  			blobfeeJumps:         blobfeeJumps,
   215  			evictionExecTip:      execTip,
   216  			evictionExecFeeJumps: basefeeJumps,
   217  			evictionBlobFeeJumps: blobfeeJumps,
   218  		}}
   219  	}
   220  	// Create a price heap and reinit it over and over
   221  	heap := newPriceHeap(uint256.NewInt(rand.Uint64()), uint256.NewInt(rand.Uint64()), &index)
   222  
   223  	basefees := make([]*uint256.Int, b.N)
   224  	blobfees := make([]*uint256.Int, b.N)
   225  	for i := 0; i < b.N; i++ {
   226  		basefees[i] = uint256.NewInt(rand.Uint64())
   227  		blobfees[i] = uint256.NewInt(rand.Uint64())
   228  	}
   229  	b.ResetTimer()
   230  	b.ReportAllocs()
   231  	for i := 0; i < b.N; i++ {
   232  		heap.reinit(basefees[i], blobfees[i], true)
   233  	}
   234  }
   235  
   236  // Benchmarks overflowing the heap over and over (add and then drop).
   237  func BenchmarkPriceHeapOverflow1MB(b *testing.B)   { benchmarkPriceHeapOverflow(b, 1024*1024) }
   238  func BenchmarkPriceHeapOverflow10MB(b *testing.B)  { benchmarkPriceHeapOverflow(b, 10*1024*1024) }
   239  func BenchmarkPriceHeapOverflow100MB(b *testing.B) { benchmarkPriceHeapOverflow(b, 100*1024*1024) }
   240  func BenchmarkPriceHeapOverflow1GB(b *testing.B)   { benchmarkPriceHeapOverflow(b, 1024*1024*1024) }
   241  func BenchmarkPriceHeapOverflow10GB(b *testing.B)  { benchmarkPriceHeapOverflow(b, 10*1024*1024*1024) }
   242  func BenchmarkPriceHeapOverflow25GB(b *testing.B)  { benchmarkPriceHeapOverflow(b, 25*1024*1024*1024) }
   243  func BenchmarkPriceHeapOverflow50GB(b *testing.B)  { benchmarkPriceHeapOverflow(b, 50*1024*1024*1024) }
   244  func BenchmarkPriceHeapOverflow100GB(b *testing.B) { benchmarkPriceHeapOverflow(b, 100*1024*1024*1024) }
   245  
   246  func benchmarkPriceHeapOverflow(b *testing.B, datacap uint64) {
   247  	// Calculate how many unique transactions we can fit into the provided disk
   248  	// data cap
   249  	blobs := datacap / (params.BlobTxBytesPerFieldElement * params.BlobTxFieldElementsPerBlob)
   250  
   251  	// Create a random set of transactions with random fees. Use a separate account
   252  	// for each transaction to make it worse case.
   253  	index := make(map[common.Address][]*blobTxMeta)
   254  	for i := 0; i < int(blobs); i++ {
   255  		var addr common.Address
   256  		rand.Read(addr[:])
   257  
   258  		var (
   259  			execTip = uint256.NewInt(rand.Uint64())
   260  			execFee = uint256.NewInt(rand.Uint64())
   261  			blobFee = uint256.NewInt(rand.Uint64())
   262  
   263  			basefeeJumps = dynamicFeeJumps(execFee)
   264  			blobfeeJumps = dynamicFeeJumps(blobFee)
   265  		)
   266  		index[addr] = []*blobTxMeta{{
   267  			id:                   uint64(i),
   268  			size:                 128 * 1024,
   269  			nonce:                0,
   270  			execTipCap:           execTip,
   271  			execFeeCap:           execFee,
   272  			blobFeeCap:           blobFee,
   273  			basefeeJumps:         basefeeJumps,
   274  			blobfeeJumps:         blobfeeJumps,
   275  			evictionExecTip:      execTip,
   276  			evictionExecFeeJumps: basefeeJumps,
   277  			evictionBlobFeeJumps: blobfeeJumps,
   278  		}}
   279  	}
   280  	// Create a price heap and overflow it over and over
   281  	evict := newPriceHeap(uint256.NewInt(rand.Uint64()), uint256.NewInt(rand.Uint64()), &index)
   282  	var (
   283  		addrs = make([]common.Address, b.N)
   284  		metas = make([]*blobTxMeta, b.N)
   285  	)
   286  	for i := 0; i < b.N; i++ {
   287  		rand.Read(addrs[i][:])
   288  
   289  		var (
   290  			execTip = uint256.NewInt(rand.Uint64())
   291  			execFee = uint256.NewInt(rand.Uint64())
   292  			blobFee = uint256.NewInt(rand.Uint64())
   293  
   294  			basefeeJumps = dynamicFeeJumps(execFee)
   295  			blobfeeJumps = dynamicFeeJumps(blobFee)
   296  		)
   297  		metas[i] = &blobTxMeta{
   298  			id:                   uint64(int(blobs) + i),
   299  			size:                 128 * 1024,
   300  			nonce:                0,
   301  			execTipCap:           execTip,
   302  			execFeeCap:           execFee,
   303  			blobFeeCap:           blobFee,
   304  			basefeeJumps:         basefeeJumps,
   305  			blobfeeJumps:         blobfeeJumps,
   306  			evictionExecTip:      execTip,
   307  			evictionExecFeeJumps: basefeeJumps,
   308  			evictionBlobFeeJumps: blobfeeJumps,
   309  		}
   310  	}
   311  	b.ResetTimer()
   312  	b.ReportAllocs()
   313  	for i := 0; i < b.N; i++ {
   314  		index[addrs[i]] = []*blobTxMeta{metas[i]}
   315  		heap.Push(evict, addrs[i])
   316  
   317  		drop := heap.Pop(evict)
   318  		delete(index, drop.(common.Address))
   319  	}
   320  }