github.com/ethereum/go-ethereum@v1.16.1/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 rnd = 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  				storageSize:          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  		rnd.Read(addr[:])
   197  
   198  		var (
   199  			execTip = uint256.NewInt(rnd.Uint64())
   200  			execFee = uint256.NewInt(rnd.Uint64())
   201  			blobFee = uint256.NewInt(rnd.Uint64())
   202  
   203  			basefeeJumps = dynamicFeeJumps(execFee)
   204  			blobfeeJumps = dynamicFeeJumps(blobFee)
   205  		)
   206  		index[addr] = []*blobTxMeta{{
   207  			id:                   uint64(i),
   208  			storageSize:          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(rnd.Uint64()), uint256.NewInt(rnd.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(rnd.Uint64())
   227  		blobfees[i] = uint256.NewInt(rnd.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  
   243  func BenchmarkPriceHeapOverflow25GB(b *testing.B) {
   244  	if testing.Short() {
   245  		b.Skip("Skipping in short-mode")
   246  	}
   247  	benchmarkPriceHeapOverflow(b, 25*1024*1024*1024)
   248  }
   249  func BenchmarkPriceHeapOverflow50GB(b *testing.B) {
   250  	if testing.Short() {
   251  		b.Skip("Skipping in short-mode")
   252  	}
   253  	benchmarkPriceHeapOverflow(b, 50*1024*1024*1024)
   254  }
   255  func BenchmarkPriceHeapOverflow100GB(b *testing.B) {
   256  	if testing.Short() {
   257  		b.Skip("Skipping in short-mode")
   258  	}
   259  	benchmarkPriceHeapOverflow(b, 100*1024*1024*1024)
   260  }
   261  
   262  func benchmarkPriceHeapOverflow(b *testing.B, datacap uint64) {
   263  	// Calculate how many unique transactions we can fit into the provided disk
   264  	// data cap
   265  	blobs := datacap / (params.BlobTxBytesPerFieldElement * params.BlobTxFieldElementsPerBlob)
   266  
   267  	// Create a random set of transactions with random fees. Use a separate account
   268  	// for each transaction to make it worse case.
   269  	index := make(map[common.Address][]*blobTxMeta)
   270  	for i := 0; i < int(blobs); i++ {
   271  		var addr common.Address
   272  		rnd.Read(addr[:])
   273  
   274  		var (
   275  			execTip = uint256.NewInt(rnd.Uint64())
   276  			execFee = uint256.NewInt(rnd.Uint64())
   277  			blobFee = uint256.NewInt(rnd.Uint64())
   278  
   279  			basefeeJumps = dynamicFeeJumps(execFee)
   280  			blobfeeJumps = dynamicFeeJumps(blobFee)
   281  		)
   282  		index[addr] = []*blobTxMeta{{
   283  			id:                   uint64(i),
   284  			storageSize:          128 * 1024,
   285  			nonce:                0,
   286  			execTipCap:           execTip,
   287  			execFeeCap:           execFee,
   288  			blobFeeCap:           blobFee,
   289  			basefeeJumps:         basefeeJumps,
   290  			blobfeeJumps:         blobfeeJumps,
   291  			evictionExecTip:      execTip,
   292  			evictionExecFeeJumps: basefeeJumps,
   293  			evictionBlobFeeJumps: blobfeeJumps,
   294  		}}
   295  	}
   296  	// Create a price heap and overflow it over and over
   297  	evict := newPriceHeap(uint256.NewInt(rnd.Uint64()), uint256.NewInt(rnd.Uint64()), index)
   298  	var (
   299  		addrs = make([]common.Address, b.N)
   300  		metas = make([]*blobTxMeta, b.N)
   301  	)
   302  	for i := 0; i < b.N; i++ {
   303  		rnd.Read(addrs[i][:])
   304  
   305  		var (
   306  			execTip = uint256.NewInt(rnd.Uint64())
   307  			execFee = uint256.NewInt(rnd.Uint64())
   308  			blobFee = uint256.NewInt(rnd.Uint64())
   309  
   310  			basefeeJumps = dynamicFeeJumps(execFee)
   311  			blobfeeJumps = dynamicFeeJumps(blobFee)
   312  		)
   313  		metas[i] = &blobTxMeta{
   314  			id:                   uint64(int(blobs) + i),
   315  			storageSize:          128 * 1024,
   316  			nonce:                0,
   317  			execTipCap:           execTip,
   318  			execFeeCap:           execFee,
   319  			blobFeeCap:           blobFee,
   320  			basefeeJumps:         basefeeJumps,
   321  			blobfeeJumps:         blobfeeJumps,
   322  			evictionExecTip:      execTip,
   323  			evictionExecFeeJumps: basefeeJumps,
   324  			evictionBlobFeeJumps: blobfeeJumps,
   325  		}
   326  	}
   327  	b.ResetTimer()
   328  	b.ReportAllocs()
   329  	for i := 0; i < b.N; i++ {
   330  		index[addrs[i]] = []*blobTxMeta{metas[i]}
   331  		heap.Push(evict, addrs[i])
   332  
   333  		drop := heap.Pop(evict)
   334  		delete(index, drop.(common.Address))
   335  	}
   336  }