package stateless import ( "bytes" "fmt" "os" "os/signal" "runtime" "syscall" "time" "github.com/ledgerwatch/turbo-geth/common" "github.com/ledgerwatch/turbo-geth/common/changeset" "github.com/ledgerwatch/turbo-geth/consensus/ethash" "github.com/ledgerwatch/turbo-geth/core" "github.com/ledgerwatch/turbo-geth/core/rawdb" "github.com/ledgerwatch/turbo-geth/core/state" "github.com/ledgerwatch/turbo-geth/core/types" "github.com/ledgerwatch/turbo-geth/core/vm" "github.com/ledgerwatch/turbo-geth/ethdb" "github.com/ledgerwatch/turbo-geth/log" ) // CheckChangeSets re-executes historical transactions in read-only mode // and checks that their outputs match the database ChangeSets. func CheckChangeSets(genesis *core.Genesis, blockNum uint64, chaindata string, historyfile string, nocheck bool, writeReceipts bool) error { if len(historyfile) == 0 { historyfile = chaindata } startTime := time.Now() sigs := make(chan os.Signal, 1) interruptCh := make(chan bool, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) go func() { <-sigs interruptCh <- true }() chainDb := ethdb.MustOpen(chaindata) defer chainDb.Close() historyDb := chainDb if chaindata != historyfile { historyDb = ethdb.MustOpen(historyfile) } chainConfig := genesis.Config engine := ethash.NewFaker() vmConfig := vm.Config{} txCacher := core.NewTxSenderCacher(runtime.NumCPU()) bc, err := core.NewBlockChain(chainDb, nil, chainConfig, engine, vmConfig, nil, txCacher) if err != nil { return err } defer bc.Stop() noOpWriter := state.NewNoopWriter() interrupt := false batch := chainDb.NewBatch() defer batch.Rollback() for !interrupt { block := bc.GetBlockByNumber(blockNum) if block == nil { break } dbstate := state.NewPlainDBState(historyDb.KV(), block.NumberU64()-1) intraBlockState := state.New(dbstate) csw := state.NewChangeSetWriterPlain(block.NumberU64() - 1) var blockWriter state.StateWriter if nocheck { blockWriter = noOpWriter } else { blockWriter = csw } receipts, err1 := runBlock(intraBlockState, noOpWriter, blockWriter, chainConfig, bc, block) if err1 != nil { return err1 } if chainConfig.IsByzantium(block.Number()) { receiptSha := types.DeriveSha(receipts) if receiptSha != block.Header().ReceiptHash { return fmt.Errorf("mismatched receipt headers for block %d", block.NumberU64()) } } if writeReceipts { rawdb.WriteReceipts(batch, block.Hash(), block.NumberU64(), receipts) if batch.BatchSize() >= batch.IdealBatchSize() { log.Info("Committing receipts", "up to block", block.NumberU64(), "batch size", common.StorageSize(batch.BatchSize())) if err := batch.CommitAndBegin(); err != nil { return err } } } if !nocheck { accountChanges, err := csw.GetAccountChanges() if err != nil { return err } var expectedAccountChanges []byte expectedAccountChanges, err = changeset.EncodeAccountsPlain(accountChanges) if err != nil { return err } dbAccountChanges, err := ethdb.GetChangeSetByBlock(historyDb, false /* storage */, blockNum) if err != nil { return err } if !bytes.Equal(dbAccountChanges, expectedAccountChanges) { fmt.Printf("Unexpected account changes in block %d\nIn the database: ======================\n", blockNum) if err = changeset.AccountChangeSetPlainBytes(dbAccountChanges).Walk(func(k, v []byte) error { fmt.Printf("0x%x: %x\n", k, v) return nil }); err != nil { return err } fmt.Printf("Expected: ==========================\n") if err = changeset.AccountChangeSetPlainBytes(expectedAccountChanges).Walk(func(k, v []byte) error { fmt.Printf("0x%x %x\n", k, v) return nil }); err != nil { return err } return nil } expectedStorageChanges, err := csw.GetStorageChanges() if err != nil { return err } expectedtorageSerialized := make([]byte, 0) if expectedStorageChanges.Len() > 0 { expectedtorageSerialized, err = changeset.EncodeStoragePlain(expectedStorageChanges) if err != nil { return err } } dbStorageChanges, err := ethdb.GetChangeSetByBlock(historyDb, true /* storage */, blockNum) if err != nil { return err } equal := true if !bytes.Equal(dbStorageChanges, expectedtorageSerialized) { var addrs [][]byte var keys [][]byte var vals [][]byte if err = changeset.StorageChangeSetPlainBytes(dbStorageChanges).Walk(func(k, v []byte) error { addrs = append(addrs, common.CopyBytes(k[:common.AddressLength])) keys = append(keys, common.CopyBytes(k[common.AddressLength+common.IncarnationLength:])) vals = append(vals, common.CopyBytes(v)) return nil }); err != nil { return err } i := 0 if err = changeset.StorageChangeSetPlainBytes(expectedtorageSerialized).Walk(func(k, v []byte) error { if !equal { return nil } if i >= len(addrs) { equal = false return nil } if !bytes.Equal(k[:common.AddressLength], addrs[i]) { equal = false return nil } if !bytes.Equal(k[common.AddressLength+common.IncarnationLength:], keys[i]) { equal = false return nil } if !bytes.Equal(v, vals[i]) { equal = false return nil } i++ return nil }); err != nil { return err } } if !equal { fmt.Printf("Unexpected storage changes in block %d\nIn the database: ======================\n", blockNum) if err = changeset.StorageChangeSetPlainBytes(dbStorageChanges).Walk(func(k, v []byte) error { fmt.Printf("0x%x: [%x]\n", k, v) return nil }); err != nil { return err } fmt.Printf("Expected: ==========================\n") if err = changeset.StorageChangeSetPlainBytes(expectedtorageSerialized).Walk(func(k, v []byte) error { fmt.Printf("0x%x: [%x]\n", k, v) return nil }); err != nil { return err } return nil } } blockNum++ if blockNum%1000 == 0 { log.Info("Checked", "blocks", blockNum) } // Check for interrupts select { case interrupt = <-interruptCh: fmt.Println("interrupted, please wait for cleanup...") default: } } if writeReceipts { log.Info("Committing final receipts", "batch size", common.StorageSize(batch.BatchSize())) if _, err := batch.Commit(); err != nil { return err } } log.Info("Checked", "blocks", blockNum, "next time specify --block", blockNum, "duration", time.Since(startTime)) return nil }