erigon-pulse/cmd/state/commands/check_change_sets.go

296 lines
8.8 KiB
Go
Raw Normal View History

package commands
import (
"bytes"
"context"
"fmt"
"os"
"os/signal"
"path"
"sort"
"syscall"
"time"
2023-09-01 07:31:18 +00:00
"github.com/ledgerwatch/log/v3"
"github.com/spf13/cobra"
chain2 "github.com/ledgerwatch/erigon-lib/chain"
libcommon "github.com/ledgerwatch/erigon-lib/common"
2023-09-01 07:31:18 +00:00
"github.com/ledgerwatch/erigon-lib/common/datadir"
"github.com/ledgerwatch/erigon-lib/common/hexutility"
2021-07-29 11:53:13 +00:00
"github.com/ledgerwatch/erigon-lib/kv"
kv2 "github.com/ledgerwatch/erigon-lib/kv/mdbx"
2022-12-19 08:38:54 +00:00
"github.com/ledgerwatch/erigon-lib/kv/temporal/historyv2"
"github.com/ledgerwatch/erigon/turbo/snapshotsync/freezeblocks"
2023-09-01 07:31:18 +00:00
"github.com/ledgerwatch/erigon/consensus"
"github.com/ledgerwatch/erigon/core/state"
"github.com/ledgerwatch/erigon/core/systemcontracts"
"github.com/ledgerwatch/erigon/core/types"
"github.com/ledgerwatch/erigon/core/vm"
"github.com/ledgerwatch/erigon/eth/ethconfig"
2023-09-01 07:31:18 +00:00
"github.com/ledgerwatch/erigon/eth/ethconsensusconfig"
"github.com/ledgerwatch/erigon/eth/stagedsync/stages"
2023-09-01 07:31:18 +00:00
"github.com/ledgerwatch/erigon/node/nodecfg"
"github.com/ledgerwatch/erigon/params"
"github.com/ledgerwatch/erigon/turbo/debug"
2023-09-01 07:31:18 +00:00
"github.com/ledgerwatch/erigon/turbo/services"
)
var (
2023-06-03 08:54:27 +00:00
historyfile string
nocheck bool
)
func init() {
withBlock(checkChangeSetsCmd)
withDataDir(checkChangeSetsCmd)
withSnapshotBlocks(checkChangeSetsCmd)
checkChangeSetsCmd.Flags().StringVar(&historyfile, "historyfile", "", "path to the file where the changesets and history are expected to be. If omitted, the same as <datadir>/erion/chaindata")
checkChangeSetsCmd.Flags().BoolVar(&nocheck, "nocheck", false, "set to turn off the changeset checking and only execute transaction (for performance testing)")
rootCmd.AddCommand(checkChangeSetsCmd)
}
var checkChangeSetsCmd = &cobra.Command{
Use: "checkChangeSets",
Short: "Re-executes historical transactions in read-only mode and checks that their outputs match the database ChangeSets",
RunE: func(cmd *cobra.Command, args []string) error {
2023-07-09 07:04:20 +00:00
logger := debug.SetupCobra(cmd, "check_change_sets")
return CheckChangeSets(cmd.Context(), genesis, block, chaindata, historyfile, nocheck, logger)
},
}
// CheckChangeSets re-executes historical transactions in read-only mode
// and checks that their outputs match the database ChangeSets.
func CheckChangeSets(ctx context.Context, genesis *types.Genesis, blockNum uint64, chaindata string, historyfile string, nocheck bool, logger log.Logger) 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
}()
db, err := kv2.NewMDBX(logger).Path(chaindata).Open(ctx)
if err != nil {
return err
}
allSnapshots := freezeblocks.NewRoSnapshots(ethconfig.NewSnapCfg(true, false, true), path.Join(datadirCli, "snapshots"), logger)
defer allSnapshots.Close()
if err := allSnapshots.ReopenFolder(); err != nil {
return fmt.Errorf("reopen snapshot segments: %w", err)
}
blockReader := freezeblocks.NewBlockReader(allSnapshots, nil /* BorSnapshots */)
chainDb := db
defer chainDb.Close()
historyDb := chainDb
if chaindata != historyfile {
historyDb = kv2.MustOpen(historyfile)
}
historyTx, err1 := historyDb.BeginRo(ctx)
if err1 != nil {
return err1
}
defer historyTx.Rollback()
chainConfig := genesis.Config
vmConfig := vm.Config{}
noOpWriter := state.NewNoopWriter()
interrupt := false
rwtx, err := chainDb.BeginRw(ctx)
if err != nil {
return err
}
defer rwtx.Rollback()
2021-04-22 17:11:55 +00:00
execAt, err1 := stages.GetStageProgress(rwtx, stages.Execution)
if err1 != nil {
return err1
}
2021-04-22 17:11:55 +00:00
historyAt, err1 := stages.GetStageProgress(rwtx, stages.StorageHistoryIndex)
if err1 != nil {
return err1
}
commitEvery := time.NewTicker(30 * time.Second)
defer commitEvery.Stop()
engine := initConsensusEngine(ctx, chainConfig, allSnapshots, blockReader, logger)
for !interrupt {
if blockNum > execAt {
log.Warn(fmt.Sprintf("Force stop: because trying to check blockNumber=%d higher than Exec stage=%d", blockNum, execAt))
break
}
if blockNum > historyAt {
log.Warn(fmt.Sprintf("Force stop: because trying to check blockNumber=%d higher than History stage=%d", blockNum, historyAt))
break
}
blockHash, err := blockReader.CanonicalHash(ctx, historyTx, blockNum)
if err != nil {
return err
}
var b *types.Block
b, _, err = blockReader.BlockWithSenders(ctx, historyTx, blockHash, blockNum)
if err != nil {
return err
}
if b == nil {
break
}
reader := state.NewPlainState(historyTx, blockNum, systemcontracts.SystemContractCodeLookup[chainConfig.ChainName])
//reader.SetTrace(blockNum == uint64(block))
intraBlockState := state.New(reader)
csw := state.NewChangeSetWriterPlain(nil /* db */, blockNum)
var blockWriter state.StateWriter
if nocheck {
blockWriter = noOpWriter
} else {
blockWriter = csw
}
getHeader := func(hash libcommon.Hash, number uint64) *types.Header {
h, e := blockReader.Header(ctx, rwtx, hash, number)
if e != nil {
panic(e)
}
return h
}
receipts, err1 := runBlock(engine, intraBlockState, noOpWriter, blockWriter, chainConfig, getHeader, b, vmConfig, blockNum == block, logger)
if err1 != nil {
return err1
}
if chainConfig.IsByzantium(blockNum) {
receiptSha := types.DeriveSha(receipts)
if receiptSha != b.ReceiptHash() {
return fmt.Errorf("mismatched receipt headers for block %d", blockNum)
}
}
if !nocheck {
accountChanges, err := csw.GetAccountChanges()
if err != nil {
return err
}
sort.Sort(accountChanges)
i := 0
match := true
err = historyv2.ForPrefix(historyTx, kv.AccountChangeSet, hexutility.EncodeTs(blockNum), func(blockN uint64, k, v []byte) error {
if i >= len(accountChanges.Changes) {
if len(v) != 0 {
fmt.Printf("Unexpected account changes in block %d\n", blockNum)
fmt.Printf("In the database: ======================\n")
fmt.Printf("%d: 0x%x: %x\n", i, k, v)
match = false
}
i++
return nil
}
c := accountChanges.Changes[i]
if bytes.Equal(c.Key, k) && bytes.Equal(c.Value, v) {
i++
2021-09-22 00:54:29 +00:00
return nil
}
if len(v) == 0 {
return nil
}
match = false
fmt.Printf("Unexpected account changes in block %d\n", blockNum)
fmt.Printf("In the database: ======================\n")
fmt.Printf("%d: 0x%x: %x\n", i, k, v)
fmt.Printf("Expected: ==========================\n")
fmt.Printf("%d: 0x%x %x\n", i, c.Key, c.Value)
i++
2021-09-22 00:54:29 +00:00
return nil
})
if err != nil {
return err
}
if !match {
return fmt.Errorf("check change set failed")
}
i = 0
expectedStorageChanges, err := csw.GetStorageChanges()
if err != nil {
return err
}
if expectedStorageChanges == nil {
2022-12-19 08:38:54 +00:00
expectedStorageChanges = historyv2.NewChangeSet()
}
sort.Sort(expectedStorageChanges)
match = true
err = historyv2.ForPrefix(historyTx, kv.StorageChangeSet, hexutility.EncodeTs(blockNum), func(blockN uint64, k, v []byte) error {
if i >= len(expectedStorageChanges.Changes) {
fmt.Printf("Unexpected storage changes in block %d\nIn the database: ======================\n", blockNum)
fmt.Printf("0x%x: %x\n", k, v)
match = false
i++
return nil
}
c := expectedStorageChanges.Changes[i]
i++
if bytes.Equal(c.Key, k) && bytes.Equal(c.Value, v) {
2021-09-22 00:54:29 +00:00
return nil
}
match = false
fmt.Printf("Unexpected storage changes in block %d\nIn the database: ======================\n", blockNum)
fmt.Printf("0x%x: %x\n", k, v)
fmt.Printf("Expected: ==========================\n")
fmt.Printf("0x%x %x\n", c.Key, c.Value)
i++
return nil
})
if err != nil {
return err
}
if !match {
return fmt.Errorf("check change set failed")
}
}
blockNum++
if blockNum%1000 == 0 {
logger.Info("Checked", "blocks", blockNum)
}
// Check for interrupts
select {
case interrupt = <-interruptCh:
fmt.Println("interrupted, please wait for cleanup...")
default:
}
}
logger.Info("Checked", "blocks", blockNum, "next time specify --block", blockNum, "duration", time.Since(startTime))
return nil
}
2023-09-01 07:31:18 +00:00
func initConsensusEngine(ctx context.Context, cc *chain2.Config, snapshots *freezeblocks.RoSnapshots, blockReader services.FullBlockReader, logger log.Logger) (engine consensus.Engine) {
2023-09-01 07:31:18 +00:00
config := ethconfig.Defaults
var consensusConfig interface{}
if cc.Clique != nil {
consensusConfig = params.CliqueSnapshot
} else if cc.Aura != nil {
consensusConfig = &config.Aura
} else if cc.Bor != nil {
consensusConfig = &config.Bor
} else {
consensusConfig = &config.Ethash
}
return ethconsensusconfig.CreateConsensusEngine(ctx, &nodecfg.Config{Dirs: datadir.New(datadirCli)}, cc, consensusConfig, config.Miner.Notify, config.Miner.Noverify, nil /* heimdallClient */, config.WithoutHeimdall, blockReader, true /* readonly */, logger)
2023-09-01 07:31:18 +00:00
}