github.com/ledgerwatch/erigon-lib@v1.0.0/state/aggregator_test.go (about) 1 package state 2 3 import ( 4 "context" 5 "encoding/binary" 6 "fmt" 7 "math/rand" 8 "os" 9 "path" 10 "path/filepath" 11 "sync/atomic" 12 "testing" 13 "time" 14 15 "github.com/holiman/uint256" 16 "github.com/ledgerwatch/erigon-lib/common/background" 17 "github.com/ledgerwatch/log/v3" 18 "github.com/stretchr/testify/require" 19 20 "github.com/ledgerwatch/erigon-lib/commitment" 21 "github.com/ledgerwatch/erigon-lib/common" 22 "github.com/ledgerwatch/erigon-lib/common/length" 23 "github.com/ledgerwatch/erigon-lib/compress" 24 "github.com/ledgerwatch/erigon-lib/kv" 25 "github.com/ledgerwatch/erigon-lib/kv/mdbx" 26 ) 27 28 func testDbAndAggregator(t *testing.T, aggStep uint64) (string, kv.RwDB, *Aggregator) { 29 t.Helper() 30 path := t.TempDir() 31 logger := log.New() 32 db := mdbx.NewMDBX(logger).InMem(filepath.Join(path, "db4")).WithTableCfg(func(defaultBuckets kv.TableCfg) kv.TableCfg { 33 return kv.ChaindataTablesCfg 34 }).MustOpen() 35 t.Cleanup(db.Close) 36 agg, err := NewAggregator(filepath.Join(path, "e4"), filepath.Join(path, "e4tmp"), aggStep, CommitmentModeDirect, commitment.VariantHexPatriciaTrie, logger) 37 require.NoError(t, err) 38 return path, db, agg 39 } 40 41 func TestAggregator_WinAccess(t *testing.T) { 42 _, db, agg := testDbAndAggregator(t, 100) 43 defer agg.Close() 44 45 tx, err := db.BeginRwNosync(context.Background()) 46 require.NoError(t, err) 47 defer func() { 48 if tx != nil { 49 tx.Rollback() 50 } 51 }() 52 agg.SetTx(tx) 53 54 agg.StartWrites() 55 56 rnd := rand.New(rand.NewSource(time.Now().UnixNano())) 57 for txNum := uint64(1); txNum <= 100; txNum++ { 58 agg.SetTxNum(txNum) 59 60 addr := make([]byte, length.Addr) 61 n, err := rnd.Read(addr) 62 require.NoError(t, err) 63 require.EqualValues(t, length.Addr, n) 64 65 buf := EncodeAccountBytes(1, uint256.NewInt(uint64(rand.Intn(10e9))), nil, 0) 66 err = agg.UpdateAccountData(addr, buf) 67 require.NoError(t, err) 68 69 var v [8]byte 70 binary.BigEndian.PutUint64(v[:], txNum) 71 require.NoError(t, err) 72 require.NoError(t, agg.FinishTx()) 73 } 74 agg.FinishWrites() 75 76 require.NoError(t, err) 77 err = tx.Commit() 78 require.NoError(t, err) 79 tx = nil 80 } 81 82 func TestAggregator_Merge(t *testing.T) { 83 _, db, agg := testDbAndAggregator(t, 1000) 84 defer agg.Close() 85 86 tx, err := db.BeginRwNosync(context.Background()) 87 require.NoError(t, err) 88 defer func() { 89 if tx != nil { 90 tx.Rollback() 91 } 92 }() 93 agg.SetTx(tx) 94 95 agg.StartWrites() 96 97 txs := uint64(10000) 98 rnd := rand.New(rand.NewSource(time.Now().UnixNano())) 99 100 // keys are encodings of numbers 1..31 101 // each key changes value on every txNum which is multiple of the key 102 var maxWrite, otherMaxWrite uint64 103 for txNum := uint64(1); txNum <= txs; txNum++ { 104 agg.SetTxNum(txNum) 105 106 addr, loc := make([]byte, length.Addr), make([]byte, length.Hash) 107 108 n, err := rnd.Read(addr) 109 require.NoError(t, err) 110 require.EqualValues(t, length.Addr, n) 111 112 n, err = rnd.Read(loc) 113 require.NoError(t, err) 114 require.EqualValues(t, length.Hash, n) 115 //keys[txNum-1] = append(addr, loc...) 116 117 buf := EncodeAccountBytes(1, uint256.NewInt(0), nil, 0) 118 err = agg.UpdateAccountData(addr, buf) 119 require.NoError(t, err) 120 121 err = agg.WriteAccountStorage(addr, loc, []byte{addr[0], loc[0]}) 122 require.NoError(t, err) 123 124 var v [8]byte 125 binary.BigEndian.PutUint64(v[:], txNum) 126 if txNum%135 == 0 { 127 err = agg.UpdateCommitmentData([]byte("otherroothash"), v[:]) 128 otherMaxWrite = txNum 129 } else { 130 err = agg.UpdateCommitmentData([]byte("roothash"), v[:]) 131 maxWrite = txNum 132 } 133 require.NoError(t, err) 134 require.NoError(t, agg.FinishTx()) 135 } 136 agg.FinishWrites() 137 require.NoError(t, err) 138 err = tx.Commit() 139 require.NoError(t, err) 140 tx = nil 141 142 // Check the history 143 roTx, err := db.BeginRo(context.Background()) 144 require.NoError(t, err) 145 defer roTx.Rollback() 146 147 dc := agg.MakeContext() 148 149 v, err := dc.ReadCommitment([]byte("roothash"), roTx) 150 require.NoError(t, err) 151 152 require.EqualValues(t, maxWrite, binary.BigEndian.Uint64(v[:])) 153 154 v, err = dc.ReadCommitment([]byte("otherroothash"), roTx) 155 require.NoError(t, err) 156 dc.Close() 157 158 require.EqualValues(t, otherMaxWrite, binary.BigEndian.Uint64(v[:])) 159 } 160 161 // here we create a bunch of updates for further aggregation. 162 // FinishTx should merge underlying files several times 163 // Expected that: 164 // - we could close first aggregator and open another with previous data still available 165 // - new aggregator SeekCommitment must return txNum equal to amount of total txns 166 func TestAggregator_RestartOnDatadir(t *testing.T) { 167 logger := log.New() 168 aggStep := uint64(50) 169 path, db, agg := testDbAndAggregator(t, aggStep) 170 171 tx, err := db.BeginRw(context.Background()) 172 require.NoError(t, err) 173 defer func() { 174 if tx != nil { 175 tx.Rollback() 176 } 177 }() 178 agg.SetTx(tx) 179 agg.StartWrites() 180 181 var latestCommitTxNum uint64 182 183 rnd := rand.New(rand.NewSource(time.Now().Unix())) 184 185 txs := (aggStep / 2) * 19 186 t.Logf("step=%d tx_count=%d", aggStep, txs) 187 var aux [8]byte 188 // keys are encodings of numbers 1..31 189 // each key changes value on every txNum which is multiple of the key 190 var maxWrite uint64 191 for txNum := uint64(1); txNum <= txs; txNum++ { 192 agg.SetTxNum(txNum) 193 binary.BigEndian.PutUint64(aux[:], txNum) 194 195 addr, loc := make([]byte, length.Addr), make([]byte, length.Hash) 196 n, err := rnd.Read(addr) 197 require.NoError(t, err) 198 require.EqualValues(t, length.Addr, n) 199 200 n, err = rnd.Read(loc) 201 require.NoError(t, err) 202 require.EqualValues(t, length.Hash, n) 203 //keys[txNum-1] = append(addr, loc...) 204 205 buf := EncodeAccountBytes(1, uint256.NewInt(0), nil, 0) 206 err = agg.UpdateAccountData(addr, buf) 207 require.NoError(t, err) 208 209 err = agg.WriteAccountStorage(addr, loc, []byte{addr[0], loc[0]}) 210 require.NoError(t, err) 211 212 err = agg.UpdateCommitmentData([]byte("key"), aux[:]) 213 require.NoError(t, err) 214 maxWrite = txNum 215 216 require.NoError(t, agg.FinishTx()) 217 } 218 agg.FinishWrites() 219 agg.Close() 220 221 err = tx.Commit() 222 require.NoError(t, err) 223 tx = nil 224 225 // Start another aggregator on same datadir 226 anotherAgg, err := NewAggregator(filepath.Join(path, "e4"), filepath.Join(path, "e4tmp"), aggStep, CommitmentModeDirect, commitment.VariantHexPatriciaTrie, logger) 227 require.NoError(t, err) 228 require.NoError(t, anotherAgg.ReopenFolder()) 229 230 defer anotherAgg.Close() 231 232 rwTx, err := db.BeginRw(context.Background()) 233 require.NoError(t, err) 234 defer func() { 235 if rwTx != nil { 236 rwTx.Rollback() 237 } 238 }() 239 240 anotherAgg.SetTx(rwTx) 241 startTx := anotherAgg.EndTxNumMinimax() 242 _, sstartTx, err := anotherAgg.SeekCommitment() 243 require.NoError(t, err) 244 require.GreaterOrEqual(t, sstartTx, startTx) 245 require.GreaterOrEqual(t, sstartTx, latestCommitTxNum) 246 _ = sstartTx 247 rwTx.Rollback() 248 rwTx = nil 249 250 // Check the history 251 roTx, err := db.BeginRo(context.Background()) 252 require.NoError(t, err) 253 defer roTx.Rollback() 254 255 dc := anotherAgg.MakeContext() 256 v, err := dc.ReadCommitment([]byte("key"), roTx) 257 require.NoError(t, err) 258 dc.Close() 259 260 require.EqualValues(t, maxWrite, binary.BigEndian.Uint64(v[:])) 261 } 262 263 func TestAggregator_RestartOnFiles(t *testing.T) { 264 logger := log.New() 265 aggStep := uint64(100) 266 267 path, db, agg := testDbAndAggregator(t, aggStep) 268 269 tx, err := db.BeginRw(context.Background()) 270 require.NoError(t, err) 271 defer func() { 272 if tx != nil { 273 tx.Rollback() 274 } 275 }() 276 agg.SetTx(tx) 277 agg.StartWrites() 278 279 txs := aggStep * 5 280 t.Logf("step=%d tx_count=%d\n", aggStep, txs) 281 282 rnd := rand.New(rand.NewSource(0)) 283 keys := make([][]byte, txs) 284 285 for txNum := uint64(1); txNum <= txs; txNum++ { 286 agg.SetTxNum(txNum) 287 288 addr, loc := make([]byte, length.Addr), make([]byte, length.Hash) 289 n, err := rnd.Read(addr) 290 require.NoError(t, err) 291 require.EqualValues(t, length.Addr, n) 292 293 n, err = rnd.Read(loc) 294 require.NoError(t, err) 295 require.EqualValues(t, length.Hash, n) 296 297 buf := EncodeAccountBytes(txNum, uint256.NewInt(1000000000000), nil, 0) 298 err = agg.UpdateAccountData(addr, buf[:]) 299 require.NoError(t, err) 300 301 err = agg.WriteAccountStorage(addr, loc, []byte{addr[0], loc[0]}) 302 require.NoError(t, err) 303 304 keys[txNum-1] = append(addr, loc...) 305 306 err = agg.FinishTx() 307 require.NoError(t, err) 308 } 309 agg.FinishWrites() 310 311 err = tx.Commit() 312 require.NoError(t, err) 313 tx = nil 314 db.Close() 315 agg.Close() 316 317 require.NoError(t, os.RemoveAll(filepath.Join(path, "db4"))) 318 319 newDb, err := mdbx.NewMDBX(logger).InMem(filepath.Join(path, "db4")).WithTableCfg(func(defaultBuckets kv.TableCfg) kv.TableCfg { 320 return kv.ChaindataTablesCfg 321 }).Open() 322 require.NoError(t, err) 323 t.Cleanup(newDb.Close) 324 325 newTx, err := newDb.BeginRw(context.Background()) 326 require.NoError(t, err) 327 defer newTx.Rollback() 328 329 newAgg, err := NewAggregator(path, path, aggStep, CommitmentModeDirect, commitment.VariantHexPatriciaTrie, logger) 330 require.NoError(t, err) 331 require.NoError(t, newAgg.ReopenFolder()) 332 333 newAgg.SetTx(newTx) 334 newAgg.StartWrites() 335 336 _, latestTx, err := newAgg.SeekCommitment() 337 require.NoError(t, err) 338 t.Logf("seek to latest_tx=%d", latestTx) 339 340 ctx := newAgg.defaultCtx 341 miss := uint64(0) 342 for i, key := range keys { 343 if uint64(i+1) >= txs-aggStep { 344 continue // finishtx always stores last agg step in db which we deleted, so missing values which were not aggregated is expected 345 } 346 stored, err := ctx.ReadAccountData(key[:length.Addr], newTx) 347 require.NoError(t, err) 348 if len(stored) == 0 { 349 miss++ 350 fmt.Printf("%x [%d/%d]", key, miss, i+1) // txnum starts from 1 351 continue 352 } 353 354 nonce, _, _ := DecodeAccountBytes(stored) 355 require.EqualValues(t, i+1, nonce) 356 357 storedV, err := ctx.ReadAccountStorage(key[:length.Addr], key[length.Addr:], newTx) 358 require.NoError(t, err) 359 require.EqualValues(t, key[0], storedV[0]) 360 require.EqualValues(t, key[length.Addr], storedV[1]) 361 } 362 newAgg.FinishWrites() 363 ctx.Close() 364 newAgg.Close() 365 366 require.NoError(t, err) 367 } 368 369 func TestAggregator_ReplaceCommittedKeys(t *testing.T) { 370 aggStep := uint64(500) 371 372 _, db, agg := testDbAndAggregator(t, aggStep) 373 t.Cleanup(agg.Close) 374 375 tx, err := db.BeginRw(context.Background()) 376 require.NoError(t, err) 377 defer func() { 378 if tx != nil { 379 tx.Rollback() 380 } 381 }() 382 agg.SetTx(tx) 383 defer agg.StartWrites().FinishWrites() 384 385 var latestCommitTxNum uint64 386 commit := func(txn uint64) error { 387 err = tx.Commit() 388 require.NoError(t, err) 389 tx, err = db.BeginRw(context.Background()) 390 require.NoError(t, err) 391 t.Logf("commit to db txn=%d", txn) 392 393 atomic.StoreUint64(&latestCommitTxNum, txn) 394 agg.SetTx(tx) 395 return nil 396 } 397 398 roots := agg.AggregatedRoots() 399 txs := (aggStep) * StepsInBiggestFile 400 t.Logf("step=%d tx_count=%d", aggStep, txs) 401 402 rnd := rand.New(rand.NewSource(0)) 403 keys := make([][]byte, txs/2) 404 405 for txNum := uint64(1); txNum <= txs/2; txNum++ { 406 agg.SetTxNum(txNum) 407 408 addr, loc := make([]byte, length.Addr), make([]byte, length.Hash) 409 n, err := rnd.Read(addr) 410 require.NoError(t, err) 411 require.EqualValues(t, length.Addr, n) 412 413 n, err = rnd.Read(loc) 414 require.NoError(t, err) 415 require.EqualValues(t, length.Hash, n) 416 keys[txNum-1] = append(addr, loc...) 417 418 buf := EncodeAccountBytes(1, uint256.NewInt(0), nil, 0) 419 err = agg.UpdateAccountData(addr, buf) 420 require.NoError(t, err) 421 422 err = agg.WriteAccountStorage(addr, loc, []byte{addr[0], loc[0]}) 423 require.NoError(t, err) 424 425 err = agg.FinishTx() 426 require.NoError(t, err) 427 select { 428 case <-roots: 429 require.NoError(t, commit(txNum)) 430 default: 431 continue 432 } 433 } 434 435 half := txs / 2 436 for txNum := txs/2 + 1; txNum <= txs; txNum++ { 437 agg.SetTxNum(txNum) 438 439 addr, loc := keys[txNum-1-half][:length.Addr], keys[txNum-1-half][length.Addr:] 440 441 err = agg.WriteAccountStorage(addr, loc, []byte{addr[0], loc[0]}) 442 require.NoError(t, err) 443 444 err = agg.FinishTx() 445 require.NoError(t, err) 446 } 447 448 err = tx.Commit() 449 tx = nil 450 451 tx, err = db.BeginRw(context.Background()) 452 require.NoError(t, err) 453 454 ctx := agg.defaultCtx 455 for _, key := range keys { 456 storedV, err := ctx.ReadAccountStorage(key[:length.Addr], key[length.Addr:], tx) 457 require.NoError(t, err) 458 require.EqualValues(t, key[0], storedV[0]) 459 require.EqualValues(t, key[length.Addr], storedV[1]) 460 } 461 require.NoError(t, err) 462 } 463 464 func Test_EncodeCommitmentState(t *testing.T) { 465 cs := commitmentState{ 466 txNum: rand.Uint64(), 467 trieState: make([]byte, 1024), 468 } 469 n, err := rand.Read(cs.trieState) 470 require.NoError(t, err) 471 require.EqualValues(t, len(cs.trieState), n) 472 473 buf, err := cs.Encode() 474 require.NoError(t, err) 475 require.NotEmpty(t, buf) 476 477 var dec commitmentState 478 err = dec.Decode(buf) 479 require.NoError(t, err) 480 require.EqualValues(t, cs.txNum, dec.txNum) 481 require.EqualValues(t, cs.trieState, dec.trieState) 482 } 483 484 func Test_BtreeIndex_Seek(t *testing.T) { 485 tmp := t.TempDir() 486 logger := log.New() 487 488 keyCount, M := 120000, 1024 489 dataPath := generateCompressedKV(t, tmp, 52, 180 /*val size*/, keyCount, logger) 490 defer os.RemoveAll(tmp) 491 492 indexPath := path.Join(tmp, filepath.Base(dataPath)+".bti") 493 err := BuildBtreeIndex(dataPath, indexPath, logger) 494 require.NoError(t, err) 495 496 bt, err := OpenBtreeIndex(indexPath, dataPath, uint64(M)) 497 require.NoError(t, err) 498 require.EqualValues(t, bt.KeyCount(), keyCount) 499 500 keys, err := pivotKeysFromKV(dataPath) 501 require.NoError(t, err) 502 503 for i := 0; i < len(keys); i++ { 504 cur, err := bt.Seek(keys[i]) 505 require.NoErrorf(t, err, "i=%d", i) 506 require.EqualValues(t, keys[i], cur.key) 507 require.NotEmptyf(t, cur.Value(), "i=%d", i) 508 // require.EqualValues(t, uint64(i), cur.Value()) 509 } 510 for i := 1; i < len(keys); i++ { 511 alt := common.Copy(keys[i]) 512 for j := len(alt) - 1; j >= 0; j-- { 513 if alt[j] > 0 { 514 alt[j] -= 1 515 break 516 } 517 } 518 cur, err := bt.Seek(keys[i]) 519 require.NoError(t, err) 520 require.EqualValues(t, keys[i], cur.Key()) 521 } 522 523 bt.Close() 524 } 525 526 func pivotKeysFromKV(dataPath string) ([][]byte, error) { 527 decomp, err := compress.NewDecompressor(dataPath) 528 if err != nil { 529 return nil, err 530 } 531 532 getter := decomp.MakeGetter() 533 getter.Reset(0) 534 535 key := make([]byte, 0, 64) 536 537 listing := make([][]byte, 0, 1000) 538 539 for getter.HasNext() { 540 if len(listing) > 100000 { 541 break 542 } 543 key, _ := getter.Next(key[:0]) 544 listing = append(listing, common.Copy(key)) 545 getter.Skip() 546 } 547 decomp.Close() 548 549 return listing, nil 550 } 551 552 func generateCompressedKV(tb testing.TB, tmp string, keySize, valueSize, keyCount int, logger log.Logger) string { 553 tb.Helper() 554 555 args := BtIndexWriterArgs{ 556 IndexFile: path.Join(tmp, fmt.Sprintf("%dk.bt", keyCount/1000)), 557 TmpDir: tmp, 558 KeyCount: 12, 559 } 560 561 iw, err := NewBtIndexWriter(args, logger) 562 require.NoError(tb, err) 563 564 defer iw.Close() 565 rnd := rand.New(rand.NewSource(0)) 566 values := make([]byte, valueSize) 567 568 dataPath := path.Join(tmp, fmt.Sprintf("%dk.kv", keyCount/1000)) 569 comp, err := compress.NewCompressor(context.Background(), "cmp", dataPath, tmp, compress.MinPatternScore, 1, log.LvlDebug, logger) 570 require.NoError(tb, err) 571 572 for i := 0; i < keyCount; i++ { 573 key := make([]byte, keySize) 574 n, err := rnd.Read(key[:]) 575 require.EqualValues(tb, keySize, n) 576 binary.BigEndian.PutUint64(key[keySize-8:], uint64(i)) 577 require.NoError(tb, err) 578 err = comp.AddWord(key[:]) 579 require.NoError(tb, err) 580 581 n, err = rnd.Read(values[:rnd.Intn(valueSize)+1]) 582 require.NoError(tb, err) 583 584 err = comp.AddWord(values[:n]) 585 require.NoError(tb, err) 586 } 587 588 err = comp.Compress() 589 require.NoError(tb, err) 590 comp.Close() 591 592 decomp, err := compress.NewDecompressor(dataPath) 593 require.NoError(tb, err) 594 595 getter := decomp.MakeGetter() 596 getter.Reset(0) 597 598 var pos uint64 599 key := make([]byte, keySize) 600 for i := 0; i < keyCount; i++ { 601 if !getter.HasNext() { 602 tb.Fatalf("not enough values at %d", i) 603 break 604 } 605 606 keys, _ := getter.Next(key[:0]) 607 err = iw.AddKey(keys[:], pos) 608 609 pos, _ = getter.Skip() 610 require.NoError(tb, err) 611 } 612 decomp.Close() 613 614 require.NoError(tb, iw.Build()) 615 iw.Close() 616 617 return decomp.FilePath() 618 } 619 620 func Test_InitBtreeIndex(t *testing.T) { 621 logger := log.New() 622 tmp := t.TempDir() 623 624 keyCount, M := 100, uint64(4) 625 compPath := generateCompressedKV(t, tmp, 52, 300, keyCount, logger) 626 decomp, err := compress.NewDecompressor(compPath) 627 require.NoError(t, err) 628 defer decomp.Close() 629 630 err = BuildBtreeIndexWithDecompressor(tmp+".bt", decomp, &background.Progress{}, tmp, logger) 631 require.NoError(t, err) 632 633 bt, err := OpenBtreeIndexWithDecompressor(tmp+".bt", M, decomp) 634 require.NoError(t, err) 635 require.EqualValues(t, bt.KeyCount(), keyCount) 636 bt.Close() 637 }