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"
"sort"
"syscall"
"time"
2021-05-20 18:25:53 +00:00
"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/rawdb"
"github.com/ledgerwatch/erigon/core/state"
"github.com/ledgerwatch/erigon/core/types"
"github.com/ledgerwatch/erigon/core/vm"
"github.com/ledgerwatch/erigon/eth/stagedsync/stages"
"github.com/ledgerwatch/erigon/ethdb"
"github.com/ledgerwatch/erigon/log"
2020-03-11 15:54:09 +00:00
"github.com/spf13/cobra"
)
2020-03-25 20:18:46 +00:00
var (
2020-06-09 05:52:38 +00:00
historyfile string
nocheck bool
writeReceipts bool
2020-03-25 20:18:46 +00:00
)
2020-03-11 15:54:09 +00:00
func init ( ) {
withBlock ( checkChangeSetsCmd )
2021-04-19 07:25:26 +00:00
withDatadir ( 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)" )
2021-03-23 16:33:58 +00:00
checkChangeSetsCmd . Flags ( ) . BoolVar ( & writeReceipts , "writeReceipts" , false , "set to turn on writing receipts as the execution ongoing" )
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-03-23 16:33:58 +00:00
return CheckChangeSets ( genesis , block , chaindata , historyfile , nocheck , writeReceipts )
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.
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 )
}
2021-04-26 05:37:48 +00:00
historyTx , err1 := historyDb . RwKV ( ) . BeginRo ( context . Background ( ) )
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
2021-04-15 10:06:30 +00:00
rwtx , err := chainDb . RwKV ( ) . BeginRw ( context . Background ( ) )
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 ( )
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
}
2021-04-22 17:11:55 +00:00
blockHash , err := rawdb . ReadCanonicalHash ( rwtx , blockNum )
2021-03-23 16:33:58 +00:00
if err != nil {
return err
}
2021-05-03 20:01:01 +00:00
block := rawdb . ReadBlock ( rwtx , blockHash , blockNum )
2021-03-23 16:33:58 +00:00
if block == nil {
break
}
2021-04-26 05:37:48 +00:00
intraBlockState := state . New ( state . NewPlainKvState ( historyTx , block . NumberU64 ( ) - 1 ) )
2021-03-23 16:33:58 +00:00
csw := state . NewChangeSetWriterPlain ( nil /* db */ , block . NumberU64 ( ) - 1 )
var blockWriter state . StateWriter
if nocheck {
blockWriter = noOpWriter
} else {
blockWriter = csw
}
2021-04-22 17:11:55 +00:00
getHeader := func ( hash common . Hash , number uint64 ) * types . Header { return rawdb . ReadHeader ( rwtx , hash , number ) }
2021-04-07 05:38:43 +00:00
receipts , err1 := runBlock ( intraBlockState , noOpWriter , blockWriter , chainConfig , getHeader , block , vmConfig )
2021-03-23 16:33:58 +00:00
if err1 != nil {
return err1
}
if writeReceipts {
2021-04-22 17:11:37 +00:00
if chainConfig . IsByzantium ( block . Number ( ) . Uint64 ( ) ) {
2021-03-23 16:33:58 +00:00
receiptSha := types . DeriveSha ( receipts )
if receiptSha != block . Header ( ) . ReceiptHash {
return fmt . Errorf ( "mismatched receipt headers for block %d" , block . NumberU64 ( ) )
}
}
2021-04-15 10:06:30 +00:00
if err := rawdb . AppendReceipts ( rwtx , block . NumberU64 ( ) , receipts ) ; err != nil {
2021-03-23 16:33:58 +00:00
return err
}
}
if ! nocheck {
accountChanges , err := csw . GetAccountChanges ( )
if err != nil {
return err
}
sort . Sort ( accountChanges )
i := 0
match := true
2021-05-04 12:34:08 +00:00
err = changeset . Walk ( historyTx , dbutils . AccountChangeSetBucket , dbutils . EncodeBlockNumber ( blockNum ) , 8 * 8 , func ( blockN uint64 , k , v [ ] byte ) ( bool , error ) {
2021-03-23 16:33:58 +00:00
c := accountChanges . Changes [ i ]
if bytes . Equal ( c . Key , k ) && bytes . Equal ( c . Value , v ) {
i ++
return true , 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 )
return false , 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 )
2021-05-04 12:34:08 +00:00
err = changeset . Walk ( historyTx , dbutils . StorageChangeSetBucket , dbutils . EncodeBlockNumber ( blockNum ) , 8 * 8 , func ( blockN uint64 , k , v [ ] byte ) ( bool , error ) {
2021-03-23 16:33:58 +00:00
c := expectedStorageChanges . Changes [ i ]
i ++
if bytes . Equal ( c . Key , k ) && bytes . Equal ( c . Value , v ) {
return false , nil
}
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 )
return true , fmt . Errorf ( "check change set failed" )
} )
if err != nil {
return err
}
}
blockNum ++
if blockNum % 1000 == 0 {
log . Info ( "Checked" , "blocks" , blockNum )
}
// Check for interrupts
select {
case interrupt = <- interruptCh :
fmt . Println ( "interrupted, please wait for cleanup..." )
case <- commitEvery . C :
if writeReceipts {
2021-04-15 10:06:30 +00:00
log . Info ( "Committing receipts" , "up to block" , block . NumberU64 ( ) )
if err = rwtx . Commit ( ) ; err != nil {
return err
}
rwtx , err = chainDb . RwKV ( ) . BeginRw ( context . Background ( ) )
if err != nil {
2021-03-23 16:33:58 +00:00
return err
}
2021-04-26 05:37:48 +00:00
historyTx . Rollback ( )
historyTx , err = historyDb . RwKV ( ) . BeginRo ( context . Background ( ) )
if err != nil {
return err
}
2021-03-23 16:33:58 +00:00
}
default :
}
}
if writeReceipts {
2021-04-15 10:06:30 +00:00
log . Info ( "Committing final receipts" , "batch size" )
if err = rwtx . Commit ( ) ; err != nil {
2021-03-23 16:33:58 +00:00
return err
}
}
log . Info ( "Checked" , "blocks" , blockNum , "next time specify --block" , blockNum , "duration" , time . Since ( startTime ) )
return nil
}