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 }