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-07-29 11:53:13 +00:00
"github.com/ledgerwatch/erigon-lib/kv"
kv2 "github.com/ledgerwatch/erigon-lib/kv/mdbx"
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"
2021-08-10 02:48:56 +00:00
"github.com/ledgerwatch/erigon/ethdb"
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 (
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-07-28 02:47:38 +00:00
logger := log . New ( )
return CheckChangeSets ( genesis , logger , 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.
2021-07-28 02:47:38 +00:00
func CheckChangeSets ( genesis * core . Genesis , logger log . Logger , blockNum uint64 , chaindata string , historyfile string , nocheck bool , writeReceipts 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
}
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
}
2021-07-07 16:15:49 +00:00
historyTx , err1 := historyDb . 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-07-07 16:15:49 +00:00
rwtx , err := chainDb . BeginRw ( context . Background ( ) )
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 ( )
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-27 07:37:23 +00:00
var block * types . Block
block , _ , err = rawdb . ReadBlockWithSenders ( rwtx , blockHash , blockNum )
if err != nil {
return err
}
2021-03-23 16:33:58 +00:00
if block == nil {
break
}
2021-07-24 07:14:11 +00:00
intraBlockState := state . New ( state . NewPlainState ( 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-08-10 02:48:56 +00:00
contractHasTEVM := ethdb . GetHasTEVM ( rwtx )
receipts , err1 := runBlock ( intraBlockState , noOpWriter , blockWriter , chainConfig , getHeader , contractHasTEVM , block , vmConfig )
2021-03-23 16:33:58 +00:00
if err1 != nil {
return err1
}
if writeReceipts {
2021-11-29 11:32:41 +00:00
if chainConfig . IsByzantium ( block . NumberU64 ( ) ) {
2021-03-23 16:33:58 +00:00
receiptSha := types . DeriveSha ( receipts )
2021-11-29 08:52:36 +00:00
if receiptSha != block . ReceiptHash ( ) {
2021-03-23 16:33:58 +00:00
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-09-22 00:54:29 +00:00
err = changeset . ForPrefix ( historyTx , kv . AccountChangeSet , dbutils . EncodeBlockNumber ( blockNum ) , func ( blockN uint64 , k , v [ ] byte ) 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 ++
2021-09-22 00:54:29 +00:00
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 )
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 {
expectedStorageChanges = changeset . NewChangeSet ( )
}
sort . Sort ( expectedStorageChanges )
2021-09-22 00:54:29 +00:00
err = changeset . ForPrefix ( historyTx , kv . StorageChangeSet , dbutils . EncodeBlockNumber ( blockNum ) , func ( blockN uint64 , k , v [ ] byte ) 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 ) {
2021-09-22 00:54:29 +00:00
return nil
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 )
2021-09-22 00:54:29 +00:00
return fmt . Errorf ( "check change set failed" )
2021-03-23 16:33:58 +00:00
} )
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
}
2021-07-07 16:15:49 +00:00
rwtx , err = chainDb . BeginRw ( context . Background ( ) )
2021-04-15 10:06:30 +00:00
if err != nil {
2021-03-23 16:33:58 +00:00
return err
}
2021-07-16 13:23:54 +00:00
defer rwtx . Rollback ( )
2021-04-26 05:37:48 +00:00
historyTx . Rollback ( )
2021-07-07 16:15:49 +00:00
historyTx , err = historyDb . BeginRo ( context . Background ( ) )
2021-04-26 05:37:48 +00:00
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
}