mirror of
https://gitlab.com/pulsechaincom/erigon-pulse.git
synced 2025-01-10 04:51:20 +00:00
0a31f5ac2a
Works around a flaw in the upgrade logic of the system contracts. Since they are updated directly, without first being self-destructed and then re-created, the usual incarnation logic does not get activated, and all historical records of the code of these contracts are retrieved as the most recent version. This problem will not exist in erigon3, but until then, a workaround will be used to access code of such contracts through a special structure, `SystemContractCodeLookup` Fixes https://github.com/ledgerwatch/erigon/issues/5865 Co-authored-by: Alexey Sharp <alexeysharp@Alexeys-iMac.local>
278 lines
7.9 KiB
Go
278 lines
7.9 KiB
Go
package commands
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"os/signal"
|
|
"path"
|
|
"sort"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/ledgerwatch/erigon-lib/kv"
|
|
kv2 "github.com/ledgerwatch/erigon-lib/kv/mdbx"
|
|
"github.com/ledgerwatch/erigon/common"
|
|
"github.com/ledgerwatch/erigon/common/changeset"
|
|
"github.com/ledgerwatch/erigon/common/dbutils"
|
|
"github.com/ledgerwatch/erigon/core"
|
|
"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"
|
|
"github.com/ledgerwatch/erigon/eth/stagedsync/stages"
|
|
"github.com/ledgerwatch/erigon/turbo/services"
|
|
"github.com/ledgerwatch/erigon/turbo/snapshotsync"
|
|
"github.com/ledgerwatch/log/v3"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var (
|
|
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 {
|
|
logger := log.New()
|
|
return CheckChangeSets(genesis, logger, block, chaindata, historyfile, nocheck)
|
|
},
|
|
}
|
|
|
|
// CheckChangeSets re-executes historical transactions in read-only mode
|
|
// and checks that their outputs match the database ChangeSets.
|
|
func CheckChangeSets(genesis *core.Genesis, logger log.Logger, blockNum uint64, chaindata string, historyfile string, nocheck 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
|
|
}()
|
|
|
|
db, err := kv2.NewMDBX(logger).Path(chaindata).Open()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var blockReader services.FullBlockReader
|
|
var allSnapshots *snapshotsync.RoSnapshots
|
|
useSnapshots := ethconfig.UseSnapshotsByChainName(chainConfig.ChainName) && snapshotsCli
|
|
if useSnapshots {
|
|
allSnapshots = snapshotsync.NewRoSnapshots(ethconfig.NewSnapCfg(true, false, true), path.Join(datadirCli, "snapshots"))
|
|
defer allSnapshots.Close()
|
|
if err := allSnapshots.ReopenFolder(); err != nil {
|
|
return fmt.Errorf("reopen snapshot segments: %w", err)
|
|
}
|
|
blockReader = snapshotsync.NewBlockReaderWithSnapshots(allSnapshots)
|
|
} else {
|
|
blockReader = snapshotsync.NewBlockReader()
|
|
}
|
|
chainDb := db
|
|
defer chainDb.Close()
|
|
historyDb := chainDb
|
|
if chaindata != historyfile {
|
|
historyDb = kv2.MustOpen(historyfile)
|
|
}
|
|
ctx := context.Background()
|
|
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()
|
|
|
|
execAt, err1 := stages.GetStageProgress(rwtx, stages.Execution)
|
|
if err1 != nil {
|
|
return err1
|
|
}
|
|
historyAt, err1 := stages.GetStageProgress(rwtx, stages.StorageHistoryIndex)
|
|
if err1 != nil {
|
|
return err1
|
|
}
|
|
|
|
commitEvery := time.NewTicker(30 * time.Second)
|
|
defer commitEvery.Stop()
|
|
|
|
engine := initConsensusEngine(chainConfig, logger, allSnapshots)
|
|
|
|
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 common.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)
|
|
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 = changeset.ForPrefix(historyTx, kv.AccountChangeSet, dbutils.EncodeBlockNumber(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++
|
|
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++
|
|
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 {
|
|
expectedStorageChanges = changeset.NewChangeSet()
|
|
}
|
|
sort.Sort(expectedStorageChanges)
|
|
match = true
|
|
err = changeset.ForPrefix(historyTx, kv.StorageChangeSet, dbutils.EncodeBlockNumber(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) {
|
|
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 {
|
|
log.Info("Checked", "blocks", blockNum)
|
|
}
|
|
|
|
// Check for interrupts
|
|
select {
|
|
case interrupt = <-interruptCh:
|
|
fmt.Println("interrupted, please wait for cleanup...")
|
|
default:
|
|
}
|
|
}
|
|
log.Info("Checked", "blocks", blockNum, "next time specify --block", blockNum, "duration", time.Since(startTime))
|
|
return nil
|
|
}
|