2020-03-11 15:54:09 +00:00
package commands
import (
2021-03-23 16:33:58 +00:00
"bytes"
"context"
"fmt"
"os"
"os/signal"
2022-05-14 19:17:23 +00:00
"path"
2021-03-23 16:33:58 +00:00
"sort"
"syscall"
"time"
2022-12-19 08:38:54 +00:00
common2 "github.com/ledgerwatch/erigon-lib/common"
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"
2021-05-20 18:25:53 +00:00
"github.com/ledgerwatch/erigon/common"
"github.com/ledgerwatch/erigon/core"
"github.com/ledgerwatch/erigon/core/state"
2022-12-10 22:41:04 +00:00
"github.com/ledgerwatch/erigon/core/systemcontracts"
2021-05-20 18:25:53 +00:00
"github.com/ledgerwatch/erigon/core/types"
"github.com/ledgerwatch/erigon/core/vm"
2022-05-14 19:17:23 +00:00
"github.com/ledgerwatch/erigon/eth/ethconfig"
2021-05-20 18:25:53 +00:00
"github.com/ledgerwatch/erigon/eth/stagedsync/stages"
2022-05-26 03:31:06 +00:00
"github.com/ledgerwatch/erigon/turbo/services"
2022-05-14 19:17:23 +00:00
"github.com/ledgerwatch/erigon/turbo/snapshotsync"
2021-07-29 10:23:23 +00:00
"github.com/ledgerwatch/log/v3"
2020-03-11 15:54:09 +00:00
"github.com/spf13/cobra"
)
2020-03-25 20:18:46 +00:00
var (
2022-06-04 22:06:30 +00:00
historyfile string
nocheck bool
2020-03-25 20:18:46 +00:00
)
2020-03-11 15:54:09 +00:00
func init ( ) {
withBlock ( checkChangeSetsCmd )
2022-02-22 17:39:48 +00:00
withDataDir ( checkChangeSetsCmd )
2022-05-14 19:17:23 +00:00
withSnapshotBlocks ( checkChangeSetsCmd )
2021-05-26 10:35:39 +00:00
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" )
2020-03-25 20:18:46 +00:00
checkChangeSetsCmd . Flags ( ) . BoolVar ( & nocheck , "nocheck" , false , "set to turn off the changeset checking and only execute transaction (for performance testing)" )
2020-03-11 15:54:09 +00:00
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 {
2021-07-28 02:47:38 +00:00
logger := log . New ( )
2022-06-04 22:06:30 +00:00
return CheckChangeSets ( genesis , logger , block , chaindata , historyfile , nocheck )
2020-03-11 15:54:09 +00:00
} ,
}
2021-03-23 16:33:58 +00:00
// CheckChangeSets re-executes historical transactions in read-only mode
// and checks that their outputs match the database ChangeSets.
2022-06-04 22:06:30 +00:00
func CheckChangeSets ( genesis * core . Genesis , logger log . Logger , blockNum uint64 , chaindata string , historyfile string , nocheck bool ) error {
2021-03-23 16:33:58 +00:00
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
} ( )
2021-07-28 02:47:38 +00:00
db , err := kv2 . NewMDBX ( logger ) . Path ( chaindata ) . Open ( )
2021-05-27 07:37:23 +00:00
if err != nil {
return err
}
2022-07-31 20:54:23 +00:00
var blockReader services . FullBlockReader
var allSnapshots * snapshotsync . RoSnapshots
useSnapshots := ethconfig . UseSnapshotsByChainName ( chainConfig . ChainName ) && snapshotsCli
if useSnapshots {
2022-10-21 06:45:20 +00:00
allSnapshots = snapshotsync . NewRoSnapshots ( ethconfig . NewSnapCfg ( true , false , true ) , path . Join ( datadirCli , "snapshots" ) )
2022-07-31 20:54:23 +00:00
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 ( )
}
2021-07-28 02:47:38 +00:00
chainDb := db
2021-03-23 16:33:58 +00:00
defer chainDb . Close ( )
historyDb := chainDb
if chaindata != historyfile {
2021-06-19 08:21:53 +00:00
historyDb = kv2 . MustOpen ( historyfile )
2021-03-23 16:33:58 +00:00
}
2022-05-14 19:17:23 +00:00
ctx := context . Background ( )
historyTx , err1 := historyDb . BeginRo ( ctx )
2021-03-23 16:33:58 +00:00
if err1 != nil {
return err1
}
defer historyTx . Rollback ( )
chainConfig := genesis . Config
vmConfig := vm . Config { }
noOpWriter := state . NewNoopWriter ( )
interrupt := false
2022-05-14 19:17:23 +00:00
rwtx , err := chainDb . BeginRw ( ctx )
2021-04-15 10:06:30 +00:00
if err != nil {
return err
}
defer rwtx . Rollback ( )
2021-03-23 16:33:58 +00:00
2021-04-22 17:11:55 +00:00
execAt , err1 := stages . GetStageProgress ( rwtx , stages . Execution )
2021-03-23 16:33:58 +00:00
if err1 != nil {
return err1
}
2021-04-22 17:11:55 +00:00
historyAt , err1 := stages . GetStageProgress ( rwtx , stages . StorageHistoryIndex )
2021-03-23 16:33:58 +00:00
if err1 != nil {
return err1
}
commitEvery := time . NewTicker ( 30 * time . Second )
defer commitEvery . Stop ( )
2022-05-14 19:17:23 +00:00
2022-12-30 05:44:54 +00:00
engine := initConsensusEngine ( chainConfig , allSnapshots )
2022-05-14 19:17:23 +00:00
2021-03-23 16:33:58 +00:00
for ! interrupt {
2021-04-26 05:37:48 +00:00
2021-03-23 16:33:58 +00:00
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
}
2022-05-14 19:17:23 +00:00
blockHash , err := blockReader . CanonicalHash ( ctx , historyTx , blockNum )
2021-03-23 16:33:58 +00:00
if err != nil {
return err
}
2022-02-12 15:48:38 +00:00
var b * types . Block
2022-05-14 19:17:23 +00:00
b , _ , err = blockReader . BlockWithSenders ( ctx , historyTx , blockHash , blockNum )
2021-05-27 07:37:23 +00:00
if err != nil {
return err
}
2022-02-12 15:48:38 +00:00
if b == nil {
2021-03-23 16:33:58 +00:00
break
}
2022-12-10 22:41:04 +00:00
reader := state . NewPlainState ( historyTx , blockNum , systemcontracts . SystemContractCodeLookup [ chainConfig . ChainName ] )
2022-07-31 20:54:23 +00:00
//reader.SetTrace(blockNum == uint64(block))
2022-02-12 15:48:38 +00:00
intraBlockState := state . New ( reader )
2022-02-16 08:38:12 +00:00
csw := state . NewChangeSetWriterPlain ( nil /* db */ , blockNum )
2021-03-23 16:33:58 +00:00
var blockWriter state . StateWriter
if nocheck {
blockWriter = noOpWriter
} else {
blockWriter = csw
}
2022-06-04 22:06:30 +00:00
getHeader := func ( hash common . Hash , number uint64 ) * types . Header {
h , e := blockReader . Header ( ctx , rwtx , hash , number )
if e != nil {
panic ( e )
}
return h
}
2022-09-01 18:49:29 +00:00
receipts , err1 := runBlock ( engine , intraBlockState , noOpWriter , blockWriter , chainConfig , getHeader , b , vmConfig , blockNum == block )
2021-03-23 16:33:58 +00:00
if err1 != nil {
return err1
}
2022-06-04 22:06:30 +00:00
if chainConfig . IsByzantium ( blockNum ) {
receiptSha := types . DeriveSha ( receipts )
if receiptSha != b . ReceiptHash ( ) {
return fmt . Errorf ( "mismatched receipt headers for block %d" , blockNum )
2021-03-23 16:33:58 +00:00
}
}
if ! nocheck {
accountChanges , err := csw . GetAccountChanges ( )
if err != nil {
return err
}
sort . Sort ( accountChanges )
i := 0
match := true
2022-12-19 08:38:54 +00:00
err = historyv2 . ForPrefix ( historyTx , kv . AccountChangeSet , common2 . EncodeTs ( blockNum ) , func ( blockN uint64 , k , v [ ] byte ) error {
2022-06-04 15:54:22 +00:00
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
}
2021-03-23 16:33:58 +00:00
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
2021-03-23 16:33:58 +00:00
}
2022-06-04 15:54:22 +00:00
if len ( v ) == 0 {
return nil
}
2021-03-23 16:33:58 +00:00
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 )
2022-06-04 15:54:22 +00:00
i ++
2021-09-22 00:54:29 +00:00
return nil
2021-03-23 16:33:58 +00:00
} )
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 ( )
2021-03-23 16:33:58 +00:00
}
sort . Sort ( expectedStorageChanges )
2022-06-04 15:54:22 +00:00
match = true
2022-12-19 08:38:54 +00:00
err = historyv2 . ForPrefix ( historyTx , kv . StorageChangeSet , common2 . EncodeTs ( blockNum ) , func ( blockN uint64 , k , v [ ] byte ) error {
2022-06-04 15:54:22 +00:00
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
}
2021-03-23 16:33:58 +00:00
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
2021-03-23 16:33:58 +00:00
}
2022-06-04 15:54:22 +00:00
match = false
2021-03-23 16:33:58 +00:00
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 )
2022-06-04 15:54:22 +00:00
i ++
return nil
2021-03-23 16:33:58 +00:00
} )
if err != nil {
return err
}
2022-06-04 15:54:22 +00:00
if ! match {
return fmt . Errorf ( "check change set failed" )
}
2021-03-23 16:33:58 +00:00
}
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
}