erigon-pulse/cmd/state/stateless/check_change_sets.go
2020-10-30 08:41:35 +00:00

236 lines
6.6 KiB
Go

package stateless
import (
"bytes"
"context"
"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)
}
historyTx, err1 := historyDb.KV().Begin(context.Background(), nil, ethdb.RO)
if err1 != nil {
return err1
}
defer historyTx.Rollback()
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(historyTx, 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, vmConfig)
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 {
if err := rawdb.WriteReceipts(batch, block.NumberU64(), receipts); err != nil {
return err
}
if batch.BatchSize() >= batch.IdealBatchSize() {
log.Info("Committing receipts", "up to block", block.NumberU64(), "batch size", common.StorageSize(batch.BatchSize()))
if err := batch.CommitAndBegin(context.Background()); 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
}