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 }