diff --git a/.gitignore b/.gitignore index 56d22509a1..2fe83577d0 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ manifest.json secretsManagerConfig.json *-packr.go config*.json +polygon-edge bin/ diff --git a/command/genesis/genesis.go b/command/genesis/genesis.go index eb187498a2..4e70f7ad78 100644 --- a/command/genesis/genesis.go +++ b/command/genesis/genesis.go @@ -198,6 +198,14 @@ func setFlags(cmd *cobra.Command) { "event tracker starting block configuration, which is specified per contract address "+ "(format: :)", ) + + //Regenesis flag that allows to start from non-empty database + cmd.Flags().StringVar( + ¶ms.initialStateRoot, + trieRootFlag, + "", + "trie root from the corresponding triedb", + ) } // Allow list diff --git a/command/genesis/params.go b/command/genesis/params.go index aa85d3c220..9e28346048 100644 --- a/command/genesis/params.go +++ b/command/genesis/params.go @@ -85,6 +85,8 @@ type genesisParams struct { epochReward uint64 eventTrackerStartBlocks []string + initialStateRoot string + // allowlist contractDeployerAllowListAdmin []string contractDeployerAllowListEnabled []string diff --git a/command/genesis/polybft_params.go b/command/genesis/polybft_params.go index 188fb422f1..47b7cd1551 100644 --- a/command/genesis/polybft_params.go +++ b/command/genesis/polybft_params.go @@ -28,6 +28,7 @@ const ( blockTimeFlag = "block-time" bridgeFlag = "bridge-json-rpc" trackerStartBlocksFlag = "tracker-start-blocks" + trieRootFlag = "trieroot" defaultManifestPath = "./manifest.json" defaultEpochSize = uint64(10) @@ -94,6 +95,7 @@ func (p *genesisParams) generatePolyBftChainConfig(o command.OutputFormatter) er Bridge: bridge, ValidatorSetAddr: contracts.ValidatorSetContract, StateReceiverAddr: contracts.StateReceiverContract, + InitialTrieRoot: types.StringToHash(p.initialStateRoot), } chainConfig := &chain.Chain{ diff --git a/command/regenesis/cmd.go b/command/regenesis/cmd.go new file mode 100644 index 0000000000..f00c52a062 --- /dev/null +++ b/command/regenesis/cmd.go @@ -0,0 +1,23 @@ +package regenesis + +import ( + "github.com/spf13/cobra" +) + +var ( + params = ®enesisParams{} +) + +type regenesisParams struct { + TrieDBPath string + SnapshotTrieDBPath string + TrieRoot string +} + +func GetCommand() *cobra.Command { + genesisCMD := RegenesisCMD() + genesisCMD.AddCommand(GetRootCMD()) + genesisCMD.AddCommand(HistoryTestCmd()) + + return genesisCMD +} diff --git a/command/regenesis/get_root.go b/command/regenesis/get_root.go new file mode 100644 index 0000000000..facec0f0cc --- /dev/null +++ b/command/regenesis/get_root.go @@ -0,0 +1,65 @@ +package regenesis + +import ( + "fmt" + + "github.com/0xPolygon/polygon-edge/command" + "github.com/spf13/cobra" + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/jsonrpc" +) + +var ( + jsonRPCAddress string + blockNumber int64 +) + +/* +./polygon-edge regenesis getroot --rpc "http://localhost:10002" +*/ +func GetRootCMD() *cobra.Command { + getRootCmd := &cobra.Command{ + Use: "getroot", + Short: "returns state root of old chain", + } + getRootCmd.Run = func(cmd *cobra.Command, args []string) { + outputter := command.InitializeOutputter(getRootCmd) + defer outputter.WriteOutput() + + rpcClient, err := jsonrpc.NewClient(jsonRPCAddress) + if err != nil { + outputter.SetError(fmt.Errorf("connect to client error:%w", err)) + + return + } + + block, err := rpcClient.Eth().GetBlockByNumber(ethgo.BlockNumber(blockNumber), false) + if err != nil { + outputter.SetError(fmt.Errorf("get block error:%w", err)) + + return + } + + _, err = outputter.Write([]byte(fmt.Sprintf("state root %s for block %d\n", block.StateRoot, block.Number))) + if err != nil { + outputter.SetError(fmt.Errorf("get block error:%w", err)) + + return + } + } + + getRootCmd.Flags().StringVar( + &jsonRPCAddress, + "rpc", + "", + "the JSON RPC IP address for old chain", + ) + getRootCmd.Flags().Int64Var( + &blockNumber, + "block", + int64(ethgo.Latest), + "Block number of trie snapshot", + ) + + return getRootCmd +} diff --git a/command/regenesis/howtotest.md b/command/regenesis/howtotest.md new file mode 100644 index 0000000000..8a474a228c --- /dev/null +++ b/command/regenesis/howtotest.md @@ -0,0 +1,166 @@ +1) create cluster +```bash +scripts/cluster ibft +``` +2) check balance +```bash +curl -s -X POST --data '{"jsonrpc":"2.0", "method":"eth_getBalance", "params":["0x85da99c8a7c2c95964c8efd687e95e632fc533d6", "latest"], "id":1}' http://localhost:10002 + +{"jsonrpc":"2.0","id":1,"result":"0x3635c9adc5dea00000"} +``` + +3) get trie root +```bash +./polygon-edge regenesis getroot --rpc "http://localhost:10002" + +[Trie copy SUCCESS] +state root 0xf5ef1a28c82226effb90f4465180ec3469226747818579673f4be929f1cd8663 for block 38 + +``` + +4) make trie snapshot +```bash + +./polygon-edge regenesis --target-path ./trie_new --stateRoot 0xf5ef1a28c82226effb90f4465180ec3469226747818579673f4be929f1cd8663 --source-path ./test-chain-1/trie + +[Trie copy SUCCESS] + +``` + +5) remove old chain data +```bash +rm -rf test-chain-* +``` +6) create new validators +```bash +./polygon-edge polybft-secrets --insecure --data-dir test-chain- --num 4 + +[WARNING: INSECURE LOCAL SECRETS - SHOULD NOT BE RUN IN PRODUCTION] + +[SECRETS INIT] +Public key (address) = 0x467CaA6185461E4c518597dCE7DE497Fb98a5680 +BLS Public key = 197059fdc3a78bd4001d802481c5ff1d84870c0b37aef851c83522323bd80f6429751ddae63cf38d180a8d45a5b4bf5519f380d60d6eedf9ccd22c3f95fc5e3a1193155af6ff3ab9e2aea5beab3f52e5e364d2cb410d6108c92f9a8375aac73110b8526407691c7e92e5cfab984e9011202b0606dc2be942808554b848cce67b +Node ID = 16Uiu2HAmTN2YAviWyyG4A56Zz8gsVJhmksdojymS4pk54SZqVbGV + +[WARNING: INSECURE LOCAL SECRETS - SHOULD NOT BE RUN IN PRODUCTION] + +[SECRETS INIT] +Public key (address) = 0x33177bBAebcB20F8545864a83b9b6EE334e4f94D +BLS Public key = 12d82d172646703d298453cef6f4415ceab2052267d9ec300fd4742fa46fd8db0168b5e98372ae3efd52d647f9b356043163fc4f76182f1ef685faf5e53b1dba15b7d0d9cb9b7868592e02179255775618dbacafd384cffba95b647a5d84de9a27995bb8cc0766194f370ad5b274d1a53b9b8ab21a3dee2f4ee4f177f63fb1f0 +Node ID = 16Uiu2HAkvtXkr1Hsct3UGn19ULNb7jgzPvEPu6Fpdakco8P45648 + +[WARNING: INSECURE LOCAL SECRETS - SHOULD NOT BE RUN IN PRODUCTION] + +[SECRETS INIT] +Public key (address) = 0xf308dF858dA25c2e40485FfA2c037D98105FD254 +BLS Public key = 220efa87e71744f44e286230cf4ac95a419abdd6d33d1a578fee21387b841fa32184dd132c077adb8f46db0eb66333672e36b5f363778030475672defce0b5622bb0974424dd7babfdf722fe9eab75ab6e5e34e2ecfcea89420f757bf8adfeb7020f0f961ca946cdb2dde40413c5c0d48aa9f13182ec35b58c4052de466c0e25 +Node ID = 16Uiu2HAmJqYRtWGbepPjQMgWfXnFEkXuX4GKH8AdD1voTwHpKuFa + +[WARNING: INSECURE LOCAL SECRETS - SHOULD NOT BE RUN IN PRODUCTION] + +[SECRETS INIT] +Public key (address) = 0x7eC6b7fc98D988472AD1AC0cFcaC6DA993d865B0 +BLS Public key = 2b8188f4bc99a19d5476fc97d14e17231cfb80b205a9fc45261725edefb0195209d972e9c1ad7d3c52b8fa129637738a88203c92fe8aa70fd0998d00f6251cb403ee077ddb4192fac270fe321468fe209308ea7597288e2505ed819f551ed00510c61f3da60f6a83d6017cbaeb7590c44bd354415178bbb160701b12a72a35a6 +Node ID = 16Uiu2HAmEuYYyzQKpyVr2HVCG8Gqx5e5DLCi8LWY4TkFYvHYcWAq + +``` + +7) generate genesis manifest +```bash +./polygon-edge manifest +``` + +8) generate genesis file +```bash +./polygon-edge genesis --consensus polybft --validator-set-size=4 --bridge-json-rpc http://127.0.0.1:8545 \ +--block-gas-limit 10000000 \ +--epoch-size 10 --trieroot 0xf5ef1a28c82226effb90f4465180ec3469226747818579673f4be929f1cd8663 + +[GENESIS SUCCESS] +**** POLYBFT CONSENSUS PROTOCOL IS IN EXPERIMENTAL PHASE AND IS NOT FULLY PRODUCTION READY. YOU ARE USING IT AT YOUR OWN RISK. **** +Genesis written to ./genesis.json + +``` + +9) Try to start a new v0.7 chain +```bash + ./polygon-edge server --data-dir ./test-chain-1 --chain genesis.json --grpc-address :10000 --libp2p :30301 --jsonrpc :10002 --seal --log-level DEBUG & +./polygon-edge server --data-dir ./test-chain-2 --chain genesis.json --grpc-address :20000 --libp2p :30302 --jsonrpc :20002 --seal --log-level DEBUG & +./polygon-edge server --data-dir ./test-chain-3 --chain genesis.json --grpc-address :30000 --libp2p :30303 --jsonrpc :30002 --seal --log-level DEBUG & +./polygon-edge server --data-dir ./test-chain-4 --chain genesis.json --grpc-address :40000 --libp2p :30304 --jsonrpc :40002 --seal --log-level DEBUG & +wait + +[1] 2615 +[2] 2616 +[3] 2617 +[4] 2618 +2023-03-15T11:02:25.149+0400 [INFO] polygon.server: Data dir: path=./test-chain-1 +2023-03-15T11:02:25.149+0400 [DEBUG] polygon.server: DataDog profiler disabled, set DD_PROFILING_ENABLED env var to enable it. +2023-03-15T11:02:25.233+0400 [INFO] polygon.server: Data dir: path=./test-chain-3 +2023-03-15T11:02:25.251+0400 [DEBUG] polygon.server: DataDog profiler disabled, set DD_PROFILING_ENABLED env var to enable it. +invalid initial state root +[1] exit 1 ./polygon-edge server --data-dir ./test-chain-1 --chain genesis.json :10000 +2023-03-15T11:02:25.299+0400 [INFO] polygon.server: Data dir: path=./test-chain-2 +2023-03-15T11:02:25.302+0400 [DEBUG] polygon.server: DataDog profiler disabled, set DD_PROFILING_ENABLED env var to enable it. +2023-03-15T11:02:25.396+0400 [INFO] polygon.server: Data dir: path=./test-chain-4 +2023-03-15T11:02:25.413+0400 [DEBUG] polygon.server: DataDog profiler disabled, set DD_PROFILING_ENABLED env var to enable it. +invalid initial state root +[3] - exit 1 ./polygon-edge server --data-dir ./test-chain-3 --chain genesis.json :30000 +invalid initial state root +[2] - exit 1 ./polygon-edge server --data-dir ./test-chain-2 --chain genesis.json :20000 +invalid initial state root +[4] + exit 1 ./polygon-edge server --data-dir ./test-chain-4 --chain genesis.json :40000 +``` + +It fails, because we havent provided trie database with correct state trie. + +10) Copy snapshot trie to our data directory +```bash +rm -rf ./test-chain-1/trie +rm -rf ./test-chain-2/trie +rm -rf ./test-chain-3/trie +rm -rf ./test-chain-4/trie +cp -fR ./trie_new/ ./test-chain-1/trie/ +cp -fR ./trie_new/ ./test-chain-2/trie/ +cp -fR ./trie_new/ ./test-chain-3/trie/ +cp -fR ./trie_new/ ./test-chain-4/trie/ +``` + +11) run chain again +```bash + ./polygon-edge server --data-dir ./test-chain-1 --chain genesis.json --grpc-address :10000 --libp2p :30301 --jsonrpc :10002 --seal --log-level DEBUG & +./polygon-edge server --data-dir ./test-chain-2 --chain genesis.json --grpc-address :20000 --libp2p :30302 --jsonrpc :20002 --seal --log-level DEBUG & +./polygon-edge server --data-dir ./test-chain-3 --chain genesis.json --grpc-address :30000 --libp2p :30303 --jsonrpc :30002 --seal --log-level DEBUG & +./polygon-edge server --data-dir ./test-chain-4 --chain genesis.json --grpc-address :40000 --libp2p :30304 --jsonrpc :40002 --seal --log-level DEBUG & +wait + +[1] 2721 +[2] 2722 +[3] 2723 +[4] 2724 +2023-03-15T11:09:41.481+0400 [INFO] polygon.server: Data dir: path=./test-chain-2 +2023-03-15T11:09:41.481+0400 [DEBUG] polygon.server: DataDog profiler disabled, set DD_PROFILING_ENABLED env var to enable it. +2023-03-15T11:09:41.597+0400 [INFO] polygon.server: Data dir: path=./test-chain-1 +2023-03-15T11:09:41.597+0400 [DEBUG] polygon.server: DataDog profiler disabled, set DD_PROFILING_ENABLED env var to enable it. +2023-03-15T11:09:41.609+0400 [WARN] polygon: Initial state root checked and correct +2023-03-15T11:09:41.661+0400 [INFO] polygon.server: Data dir: path=./test-chain-4 +2023-03-15T11:09:41.661+0400 [DEBUG] polygon.server: DataDog profiler disabled, set DD_PROFILING_ENABLED env var to enable it. +2023-03-15T11:09:41.725+0400 [INFO] polygon.server: Data dir: path=./test-chain-3 +2023-03-15T11:09:41.725+0400 [DEBUG] polygon.server: DataDog profiler disabled, set DD_PROFILING_ENABLED env var to enable it. +2023-03-15T11:09:41.844+0400 [INFO] polygon.blockchain: genesis: hash=0x627b70a8abc294324808d9820015faa5e0616afca5fdc75528b03b703a461acd +2023-03-15T11:09:41.844+0400 [INFO] polygon.server.polybft: initializing polybft... +2023-03-15T11:09:41.951+0400 [WARN] polygon: Initial state root checked and correct +2023-03-15T11:09:42.101+0400 [WARN] polygon: Initial state root checked and correct +2023-03-15T11:09:42.254+0400 [WARN] polygon: Initial state root checked and correct +2023-03-15T11:09:42.445+0400 [INFO] polygon.blockchain: genesis: hash=0x627b70a8abc294324808d9820015faa5e0616afca5fdc75528b03b703a461acd +2023-03-15T11:09:42.445+0400 [INFO] polygon.server.polybft: initializing polybft... +2023-03-15T11:09:42.462+0400 [INFO] polygon.server.polybft.consensus_runtime: restartEpoch: block number=0 epoch=1 validators=4 firstBlockInEpoch=1 +... +``` + +12) check that balance of account on v0.6 is not 0 +```bash + curl -s -X POST --data '{"jsonrpc":"2.0", "method":"eth_getBalance", "params":["0x85da99c8a7c2c95964c8efd687e95e632fc533d6", "latest"], "id":1}' http://localhost:10002 + +{"jsonrpc":"2.0","id":1,"result":"0x3635c9adc5dea00000"}% +``` \ No newline at end of file diff --git a/command/regenesis/regenesis.go b/command/regenesis/regenesis.go new file mode 100644 index 0000000000..a0c79449aa --- /dev/null +++ b/command/regenesis/regenesis.go @@ -0,0 +1,112 @@ +package regenesis + +import ( + "bytes" + "fmt" + + "github.com/0xPolygon/polygon-edge/command" + itrie "github.com/0xPolygon/polygon-edge/state/immutable-trie" + "github.com/0xPolygon/polygon-edge/types" + "github.com/spf13/cobra" + "github.com/syndtr/goleveldb/leveldb" + "github.com/syndtr/goleveldb/leveldb/opt" +) + +/* +./polygon-edge regenesis --target-path ./trie_new \ +--stateRoot 0xf5ef1a28c82226effb90f4465180ec3469226747818579673f4be929f1cd8663 \ +--source-path ./test-chain-1/trie +*/ +func RegenesisCMD() *cobra.Command { + genesisCmd := &cobra.Command{ + Use: "regenesis", + Short: "Copies trie for specific block to a separate folder", + } + + genesisCmd.Flags().StringVar( + ¶ms.SnapshotTrieDBPath, + "target-path", + "", + "the directory of trie data of trie copy", + ) + genesisCmd.Flags().StringVar( + ¶ms.TrieDBPath, + "source-path", + "", + "the directory of trie data of old chain", + ) + genesisCmd.Flags().StringVar( + ¶ms.TrieRoot, + "stateRoot", + "", + "block state root of old chain", + ) + + outputter := command.InitializeOutputter(genesisCmd) + defer outputter.WriteOutput() + + genesisCmd.PreRun = func(cmd *cobra.Command, args []string) { + if params.SnapshotTrieDBPath == "" || params.TrieDBPath == "" || params.TrieRoot == "" { + outputter.SetError(fmt.Errorf("not enough arguments")) + + return + } + } + + genesisCmd.Run = func(cmd *cobra.Command, args []string) { + trieDB, err := leveldb.OpenFile(params.TrieDBPath, &opt.Options{ReadOnly: true}) + if err != nil { + outputter.SetError(fmt.Errorf("open trie trieDB error:%w", err)) + + return + } + defer trieDB.Close() + + snapshotDB, err := leveldb.OpenFile(params.SnapshotTrieDBPath, nil) + if err != nil { + outputter.SetError(fmt.Errorf("open snapshotDB error:%w", err)) + + return + } + defer snapshotDB.Close() + + snapshotStorage := itrie.NewKV(snapshotDB) + + err = itrie.CopyTrie(types.StringToHash(params.TrieRoot).Bytes(), itrie.NewKV(trieDB), snapshotStorage, nil, false) + if err != nil { + outputter.SetError(fmt.Errorf("copy trie error:%w", err)) + + return + } + + checkedHash, err := itrie.HashChecker(types.StringToHash(params.TrieRoot).Bytes(), snapshotStorage) + if err != nil { + outputter.SetError(fmt.Errorf("copy trie error:%w", err)) + + return + } + + if checkedHash != types.StringToHash(params.TrieRoot) { + outputter.SetError(fmt.Errorf("incorrect trie root error:%w", err)) + + return + } + + outputter.WriteCommandResult(&ReGenesisResult{}) + } + + return genesisCmd +} + +type ReGenesisResult struct { + Message string `json:"message"` +} + +func (r *ReGenesisResult) GetOutput() string { + var buffer bytes.Buffer + + buffer.WriteString("\n[Trie copy SUCCESS]\n") + buffer.WriteString(r.Message) + + return buffer.String() +} diff --git a/command/regenesis/test_on_history.go b/command/regenesis/test_on_history.go new file mode 100644 index 0000000000..9e454d1f49 --- /dev/null +++ b/command/regenesis/test_on_history.go @@ -0,0 +1,149 @@ +package regenesis + +import ( + "errors" + "fmt" + "time" + + leveldb2 "github.com/0xPolygon/polygon-edge/blockchain/storage/leveldb" + "github.com/0xPolygon/polygon-edge/command" + itrie "github.com/0xPolygon/polygon-edge/state/immutable-trie" + "github.com/0xPolygon/polygon-edge/types" + hclog "github.com/hashicorp/go-hclog" + "github.com/spf13/cobra" + "github.com/syndtr/goleveldb/leveldb" + "github.com/syndtr/goleveldb/leveldb/opt" + ldbstorage "github.com/syndtr/goleveldb/leveldb/storage" +) + +var ( + triePath string + chainPath string + toBlock uint64 + fromBlock uint64 +) + +/* +Run: ./polygon-edge regenesis history --triedb "path_to_triedb" --chaindb "path_to_blockchain_db" +*/ +func HistoryTestCmd() *cobra.Command { + historyTestCMD := &cobra.Command{ + Use: "history", + Short: "run history test", + } + + historyTestCMD.Flags().StringVar(&triePath, "triedb", "", "path to trie db") + historyTestCMD.Flags().StringVar(&chainPath, "chaindb", "", "path to chain db") + historyTestCMD.Flags().Uint64Var(&toBlock, "to", 0, "upper bound of regenesis test(default is head)") + historyTestCMD.Flags().Uint64Var(&fromBlock, "from", 0, "lower bound of regenesis test(default is 0)") + + historyTestCMD.Run = func(cmd *cobra.Command, args []string) { + outputter := command.InitializeOutputter(historyTestCMD) + defer outputter.WriteOutput() + + trieDB, err := leveldb.OpenFile(triePath, &opt.Options{ReadOnly: true}) + if err != nil { + outputter.SetError(err) + + return + } + + st, err := leveldb2.NewLevelDBStorage(chainPath, hclog.NewNullLogger()) + if err != nil { + outputter.SetError(err) + + return + } + + if toBlock == 0 { + var ok bool + + toBlock, ok = st.ReadHeadNumber() + if !ok { + outputter.SetError(errors.New("can't read head")) + + return + } + } + + _, err = outputter.Write([]byte(fmt.Sprintf("running test from %d to %d", toBlock, fromBlock))) + if err != nil { + outputter.SetError(err) + + return + } + + lastStateRoot := types.Hash{} + + for i := toBlock; i > fromBlock; i-- { + canonicalHash, ok := st.ReadCanonicalHash(i) + if !ok { + outputter.SetError(errors.New("can't read canonical hash")) + + return + } + + header, err := st.ReadHeader(canonicalHash) + if !ok { + outputter.SetError(fmt.Errorf("can't read header %w", err)) + + return + } + + if lastStateRoot == header.StateRoot { + //state root is the same,as in previous block + continue + } + + lastStateRoot = header.StateRoot + ldbStorageNew := ldbstorage.NewMemStorage() + + tmpDB, err := leveldb.Open(ldbStorageNew, nil) + if err != nil { + outputter.SetError(err) + + return + } + + tmpStorage := itrie.NewKV(tmpDB) + tt := time.Now() + + err = itrie.CopyTrie(header.StateRoot.Bytes(), itrie.NewKV(trieDB), tmpStorage, []byte{}, false) + if err != nil { + outputter.SetError(fmt.Errorf("copy trie for block %v returned error %w", i, err)) + + return + } + + hash, err := itrie.HashChecker(header.StateRoot.Bytes(), tmpStorage) + if err != nil { + outputter.SetError(fmt.Errorf("check trie root for block %v returned error %w", i, err)) + + return + } + + if hash != header.StateRoot { + outputter.SetError(fmt.Errorf("check trie root for block %v returned another hash"+ + "expected: %s, got: %s", i, header.StateRoot.String(), hash.String())) + + return + } + + err = tmpDB.Close() + if err != nil { + outputter.SetError(err) + + return + } + + _, err = outputter.Write([]byte(fmt.Sprintf("block %v checked successfully, time %v", i, time.Since(tt).String()))) + if err != nil { + outputter.SetError(err) + + return + } + } + } + + return historyTestCMD +} diff --git a/command/root/root.go b/command/root/root.go index e0ff39fc86..958400825e 100644 --- a/command/root/root.go +++ b/command/root/root.go @@ -17,6 +17,7 @@ import ( "github.com/0xPolygon/polygon-edge/command/polybft" "github.com/0xPolygon/polygon-edge/command/polybftmanifest" "github.com/0xPolygon/polygon-edge/command/polybftsecrets" + "github.com/0xPolygon/polygon-edge/command/regenesis" "github.com/0xPolygon/polygon-edge/command/rootchain" "github.com/0xPolygon/polygon-edge/command/secrets" "github.com/0xPolygon/polygon-edge/command/server" @@ -63,6 +64,7 @@ func (rc *RootCommand) registerSubCommands() { polybft.GetCommand(), polybftmanifest.GetCommand(), bridge.GetCommand(), + regenesis.GetCommand(), ) } diff --git a/consensus/ibft/fork/hooks_test.go b/consensus/ibft/fork/hooks_test.go index 06c5b06dc1..e650b3ee8a 100644 --- a/consensus/ibft/fork/hooks_test.go +++ b/consensus/ibft/fork/hooks_test.go @@ -17,7 +17,6 @@ import ( "github.com/0xPolygon/polygon-edge/validators/store" "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) type mockHeaderModifierStore struct { @@ -282,8 +281,8 @@ func newTestTransition( Forks: chain.AllForksEnabled, }, st, hclog.NewNullLogger()) - rootHash, err := ex.WriteGenesis(nil) - require.NoError(t, err) + rootHash, err := ex.WriteGenesis(nil, types.Hash{}) + assert.NoError(t, err) ex.GetHash = func(h *types.Header) state.GetHashByNumber { return func(i uint64) types.Hash { diff --git a/consensus/polybft/consensus_runtime.go b/consensus/polybft/consensus_runtime.go index 4b32c67751..a93732a4c8 100644 --- a/consensus/polybft/consensus_runtime.go +++ b/consensus/polybft/consensus_runtime.go @@ -382,12 +382,12 @@ func (c *consensusRuntime) restartEpoch(header *types.Header) (*epochMetadata, e systemState, err := c.getSystemState(header) if err != nil { - return nil, err + return nil, fmt.Errorf("get system state: %w", err) } epochNumber, err := systemState.GetEpoch() if err != nil { - return nil, err + return nil, fmt.Errorf("get epoch: %w", err) } if lastEpoch != nil { diff --git a/consensus/polybft/polybft_config.go b/consensus/polybft/polybft_config.go index 48c6b77ab8..5c846cd7cc 100644 --- a/consensus/polybft/polybft_config.go +++ b/consensus/polybft/polybft_config.go @@ -43,6 +43,7 @@ type PolyBFTConfig struct { // Address of the system contracts, as of now (testing) this is populated automatically during genesis ValidatorSetAddr types.Address `json:"validatorSetAddr"` StateReceiverAddr types.Address `json:"stateReceiverAddr"` + InitialTrieRoot types.Hash `json:"initialTrieRoot"` } // GetPolyBFTConfig deserializes provided chain config and returns PolyBFTConfig diff --git a/consensus/polybft/system_state_test.go b/consensus/polybft/system_state_test.go index 867fb899ae..5c4ba919f0 100644 --- a/consensus/polybft/system_state_test.go +++ b/consensus/polybft/system_state_test.go @@ -200,7 +200,7 @@ func newTestTransition(t *testing.T, alloc map[types.Address]*chain.GenesisAccou Forks: chain.AllForksEnabled, }, st, hclog.NewNullLogger()) - rootHash, err := ex.WriteGenesis(alloc) + rootHash, err := ex.WriteGenesis(alloc, types.Hash{}) require.NoError(t, err) ex.GetHash = func(h *types.Header) state.GetHashByNumber { diff --git a/e2e-polybft/e2e/migration_test.go b/e2e-polybft/e2e/migration_test.go new file mode 100644 index 0000000000..1b72ea931d --- /dev/null +++ b/e2e-polybft/e2e/migration_test.go @@ -0,0 +1,209 @@ +package e2e + +import ( + "os" + "path/filepath" + "testing" + "time" + + "github.com/syndtr/goleveldb/leveldb/opt" + + "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + frameworkpolybft "github.com/0xPolygon/polygon-edge/e2e-polybft/framework" + "github.com/0xPolygon/polygon-edge/e2e/framework" + itrie "github.com/0xPolygon/polygon-edge/state/immutable-trie" + "github.com/0xPolygon/polygon-edge/txrelayer" + "github.com/0xPolygon/polygon-edge/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/syndtr/goleveldb/leveldb" + "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/wallet" +) + +func TestMigration(t *testing.T) { + userKey, _ := wallet.GenerateKey() + userAddr := userKey.Address() + userKey2, _ := wallet.GenerateKey() + userAddr2 := userKey2.Address() + + initialBalance := ethgo.Ether(10) + srvs := framework.NewTestServers(t, 1, func(config *framework.TestServerConfig) { + config.SetConsensus(framework.ConsensusDev) + config.Premine(types.Address(userAddr), initialBalance) + }) + srv := srvs[0] + + rpcClient := srv.JSONRPC() + + // Fetch the balances before sending + balanceSender, err := rpcClient.Eth().GetBalance( + userAddr, + ethgo.Latest, + ) + assert.NoError(t, err) + assert.Equal(t, balanceSender.Cmp(initialBalance), 0) + + balanceReceiver, err := rpcClient.Eth().GetBalance( + userAddr2, + ethgo.Latest, + ) + assert.NoError(t, err) + + if balanceReceiver.Uint64() != 0 { + t.Fatal("balanceReceiver is not 0") + } + + relayer, err := txrelayer.NewTxRelayer(txrelayer.WithClient(rpcClient)) + require.NoError(t, err) + + //send transaction to user2 + sendAmount := ethgo.Gwei(10000) + receipt, err := relayer.SendTransaction(ðgo.Transaction{ + From: userAddr, + To: &userAddr2, + GasPrice: 1048576, + Gas: 1000000, + Value: sendAmount, + }, userKey) + assert.NoError(t, err) + assert.NotNil(t, receipt) + + receipt, err = relayer.SendTransaction(ðgo.Transaction{ + From: userAddr, + GasPrice: 1048576, + Gas: 1000000, + Input: contractsapi.TestWriteBlockMetadata.Bytecode, + }, userKey) + require.NoError(t, err) + require.NotNil(t, receipt) + require.Equal(t, uint64(types.ReceiptSuccess), receipt.Status) + + deployedContractBalance := receipt.ContractAddress + + initReceipt, err := ABITransaction(relayer, userKey, contractsapi.TestWriteBlockMetadata, receipt.ContractAddress, "init") + if err != nil { + t.Fatal(err) + } + + require.Equal(t, uint64(types.ReceiptSuccess), initReceipt.Status) + + // Fetch the balances after sending + balanceSender, err = rpcClient.Eth().GetBalance( + userAddr, + ethgo.Latest, + ) + assert.NoError(t, err) + + balanceReceiver, err = rpcClient.Eth().GetBalance( + userAddr2, + ethgo.Latest, + ) + assert.NoError(t, err) + assert.Equal(t, sendAmount, balanceReceiver) + + block, err := rpcClient.Eth().GetBlockByNumber(ethgo.Latest, true) + if err != nil { + t.Fatal(err) + } + + stateRoot := block.StateRoot + + path := filepath.Join(srvs[0].Config.RootDir, "trie") + srvs[0].Stop() + //hack for db closing. leveldb allow only one connection + time.Sleep(time.Second) + + tmpDir := t.TempDir() + defer os.RemoveAll(tmpDir) + + err = frameworkpolybft.RunEdgeCommand([]string{ + "regenesis", + "--stateRoot", block.StateRoot.String(), + "--source-path", path, + "--target-path", tmpDir, + }, os.Stdout) + if err != nil { + t.Fatal(err) + } + + db, err := leveldb.OpenFile(tmpDir, &opt.Options{ReadOnly: true}) + if err != nil { + t.Fatal(err) + } + + stateStorageNew := itrie.NewKV(db) + + copiedStateRoot, err := itrie.HashChecker(block.StateRoot.Bytes(), stateStorageNew) + if err != nil { + t.Fatal(err) + } + + require.Equal(t, types.Hash(stateRoot), copiedStateRoot) + + err = db.Close() + if err != nil { + t.Fatal(err) + } + + cluster := frameworkpolybft.NewTestCluster(t, 7, + frameworkpolybft.WithNonValidators(2), + frameworkpolybft.WithValidatorSnapshot(5), + frameworkpolybft.WithGenesisState(tmpDir, types.Hash(stateRoot)), + ) + defer cluster.Stop() + + require.NoError(t, cluster.WaitForBlock(5, 1*time.Minute)) + + senderBalanceAfterMigration, err := cluster.Servers[0].JSONRPC().Eth().GetBalance(userAddr, ethgo.Latest) + if err != nil { + t.Fatal(err) + } + + receiverBalanceAfterMigration, err := cluster.Servers[0].JSONRPC().Eth().GetBalance(userAddr2, ethgo.Latest) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, balanceSender, senderBalanceAfterMigration) + assert.Equal(t, balanceReceiver, receiverBalanceAfterMigration) + + deployedCode, err := cluster.Servers[0].JSONRPC().Eth().GetCode(deployedContractBalance, ethgo.Latest) + if err != nil { + t.Fatal(err) + } + + require.Equal(t, deployedCode, *types.EncodeBytes(contractsapi.TestWriteBlockMetadata.DeployedBytecode)) + require.NoError(t, cluster.WaitForBlock(10, 1*time.Minute)) + + //stop last node of validator and non-validator + cluster.Servers[4].Stop() + cluster.Servers[6].Stop() + + require.NoError(t, cluster.WaitForBlock(15, time.Minute)) + + //wait sync of that nodes + cluster.Servers[4].Start() + cluster.Servers[6].Start() + require.NoError(t, cluster.WaitForBlock(20, time.Minute)) + + //stop all nodes + for i := range cluster.Servers { + cluster.Servers[i].Stop() + } + + time.Sleep(time.Second) + + for i := range cluster.Servers { + cluster.Servers[i].Start() + } + + require.NoError(t, cluster.WaitForBlock(25, time.Minute)) + + // add new node + _, err = cluster.InitSecrets("test-chain-8", 1) + require.NoError(t, err) + + cluster.InitTestServer(t, 8, false, false) + require.NoError(t, cluster.WaitForBlock(33, time.Minute)) +} diff --git a/e2e-polybft/framework/test-cluster.go b/e2e-polybft/framework/test-cluster.go index dc1be5881b..544812a071 100644 --- a/e2e-polybft/framework/test-cluster.go +++ b/e2e-polybft/framework/test-cluster.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "fmt" "io" + "io/ioutil" "math/big" "os" "os/exec" @@ -92,6 +93,9 @@ type TestClusterConfig struct { NumBlockConfirmations uint64 + InitialTrieDB string + InitialStateRoot types.Hash + logsDirOnce sync.Once } @@ -195,6 +199,13 @@ func WithValidatorSnapshot(validatorsLen uint64) ClusterOption { } } +func WithGenesisState(databasePath string, stateRoot types.Hash) ClusterOption { + return func(h *TestClusterConfig) { + h.InitialTrieDB = databasePath + h.InitialStateRoot = stateRoot + } +} + func WithBootnodeCount(cnt int) ClusterOption { return func(h *TestClusterConfig) { h.BootnodeCount = cnt @@ -341,6 +352,7 @@ func NewTestCluster(t *testing.T, validatorsCount int, opts ...ClusterOption) *T "--epoch-size", strconv.Itoa(cluster.Config.EpochSize), "--epoch-reward", strconv.Itoa(cluster.Config.EpochReward), "--premine", "0x0000000000000000000000000000000000000000", + "--trieroot", cluster.Config.InitialStateRoot.String(), } if len(cluster.Config.Premine) != 0 { @@ -405,7 +417,14 @@ func (c *TestCluster) InitTestServer(t *testing.T, i int, isValidator bool, rela t.Helper() logLevel := os.Getenv(envLogLevel) + dataDir := c.Config.Dir(c.Config.ValidatorPrefix + strconv.Itoa(i)) + if c.Config.InitialTrieDB != "" { + err := CopyDir(c.Config.InitialTrieDB, filepath.Join(dataDir, "trie")) + if err != nil { + t.Fatal(err) + } + } srv := NewTestServer(t, c.Config, func(config *TestServerConfig) { config.DataDir = dataDir @@ -565,6 +584,11 @@ func runCommand(binary string, args []string, stdout io.Writer) error { return nil } +// RunEdgeCommand - calls a command line edge function +func RunEdgeCommand(args []string, stdout io.Writer) error { + return runCommand(resolveBinary(), args, stdout) +} + // InitSecrets initializes account(s) secrets with given prefix. // (secrets are being stored in the temp directory created by given e2e test execution) func (c *TestCluster) InitSecrets(prefix string, count int) ([]types.Address, error) { @@ -776,3 +800,24 @@ func sliceAddressToSliceString(addrs []types.Address) []string { return res } + +func CopyDir(source, destination string) error { + err := os.Mkdir(destination, 0755) + if err != nil { + return err + } + + return filepath.Walk(source, func(path string, info os.FileInfo, err error) error { + relPath := strings.Replace(path, source, "", 1) + if relPath == "" { + return nil + } + + data, err := ioutil.ReadFile(filepath.Join(source, relPath)) + if err != nil { + return err + } + + return ioutil.WriteFile(filepath.Join(destination, relPath), data, 0600) + }) +} diff --git a/server/server.go b/server/server.go index 956e0074e0..39f200f9d0 100644 --- a/server/server.go +++ b/server/server.go @@ -11,11 +11,12 @@ import ( "path/filepath" "time" + consensusPolyBFT "github.com/0xPolygon/polygon-edge/consensus/polybft" + "github.com/0xPolygon/polygon-edge/archive" "github.com/0xPolygon/polygon-edge/blockchain" "github.com/0xPolygon/polygon-edge/chain" "github.com/0xPolygon/polygon-edge/consensus" - "github.com/0xPolygon/polygon-edge/consensus/polybft" bls "github.com/0xPolygon/polygon-edge/consensus/polybft/signer" "github.com/0xPolygon/polygon-edge/consensus/polybft/statesyncrelayer" "github.com/0xPolygon/polygon-edge/consensus/polybft/wallet" @@ -83,11 +84,6 @@ type Server struct { stateSyncRelayer *statesyncrelayer.StateSyncRelayer } -var dirPaths = []string{ - "blockchain", - "trie", -} - // newFileLogger returns logger instance that writes all logs to a specified file. // If log file can't be created, it returns an error func newFileLogger(config *Config) (hclog.Logger, error) { @@ -146,6 +142,11 @@ func NewServer(config *Config) (*Server, error) { m.logger.Info("Data dir", "path", config.DataDir) + var dirPaths = []string{ + "blockchain", + "trie", + } + // Generate all the paths in the dataDir if err := common.SetupDataDir(config.DataDir, dirPaths, 0770); err != nil { return nil, fmt.Errorf("failed to create data directories: %w", err) @@ -209,12 +210,36 @@ func NewServer(config *Config) (*Server, error) { m.config.Chain.Params.ContractDeployerAllowList) } - // compute the genesis root state - genesisRoot, err := m.executor.WriteGenesis(config.Chain.Genesis.Alloc) + var initialStateRoot = types.ZeroHash + + if ConsensusType(engineName) == PolyBFTConsensus { + polyBFTConfig, err := consensusPolyBFT.GetPolyBFTConfig(config.Chain) + if err != nil { + return nil, err + } + + if polyBFTConfig.InitialTrieRoot != types.ZeroHash { + checkedInitialTrieRoot, err := itrie.HashChecker(polyBFTConfig.InitialTrieRoot.Bytes(), stateStorage) + if err != nil { + return nil, fmt.Errorf("error on state root verification %w", err) + } + + if checkedInitialTrieRoot != polyBFTConfig.InitialTrieRoot { + return nil, errors.New("invalid initial state root") + } + + logger.Info("Initial state root checked and correct") + + initialStateRoot = polyBFTConfig.InitialTrieRoot + } + } + + genesisRoot, err := m.executor.WriteGenesis(config.Chain.Genesis.Alloc, initialStateRoot) if err != nil { return nil, err } + // compute the genesis root state config.Chain.Genesis.StateRoot = genesisRoot // use the eip155 signer @@ -486,7 +511,7 @@ func (s *Server) setupRelayer() error { return fmt.Errorf("failed to create account from secret: %w", err) } - polyBFTConfig, err := polybft.GetPolyBFTConfig(s.config.Chain) + polyBFTConfig, err := consensusPolyBFT.GetPolyBFTConfig(s.config.Chain) if err != nil { return fmt.Errorf("failed to extract polybft config: %w", err) } diff --git a/state/executor.go b/state/executor.go index 67b927bf21..a9ff0c5c93 100644 --- a/state/executor.go +++ b/state/executor.go @@ -52,8 +52,24 @@ func NewExecutor(config *chain.Params, s State, logger hclog.Logger) *Executor { } } -func (e *Executor) WriteGenesis(alloc map[types.Address]*chain.GenesisAccount) (types.Hash, error) { - snap := e.state.NewSnapshot() +func (e *Executor) WriteGenesis( + alloc map[types.Address]*chain.GenesisAccount, + initialStateRoot types.Hash) (types.Hash, error) { + var ( + snap Snapshot + err error + ) + + if initialStateRoot == types.ZeroHash { + snap = e.state.NewSnapshot() + } else { + snap, err = e.state.NewSnapshotAt(initialStateRoot) + } + + if err != nil { + return types.Hash{}, err + } + txn := NewTxn(snap) config := e.config.Forks.At(0) diff --git a/state/immutable-trie/copy_trie.go b/state/immutable-trie/copy_trie.go new file mode 100644 index 0000000000..d4a286ed26 --- /dev/null +++ b/state/immutable-trie/copy_trie.go @@ -0,0 +1,230 @@ +package itrie + +import ( + "bytes" + "encoding/hex" + "errors" + "fmt" + + "github.com/0xPolygon/polygon-edge/crypto" + + "github.com/0xPolygon/polygon-edge/state" + "github.com/0xPolygon/polygon-edge/types" + "github.com/syndtr/goleveldb/leveldb" + "github.com/umbracle/fastrlp" +) + +var emptyCodeHash = crypto.Keccak256(nil) + +func getCustomNode(hash []byte, storage Storage) (Node, []byte, error) { + data, ok := storage.Get(hash) + if !ok { + return nil, nil, nil + } + + // NOTE. We dont need to make copies of the bytes because the nodes + // take the reference from data itself which is a safe copy. + p := parserPool.Get() + defer parserPool.Put(p) + + v, err := p.Parse(data) + if err != nil { + return nil, nil, err + } + + if v.Type() != fastrlp.TypeArray { + return nil, nil, fmt.Errorf("storage item should be an array") + } + + n, err := decodeNode(v, storage) + + return n, data, err +} + +func CopyTrie(nodeHash []byte, storage Storage, newStorage Storage, agg []byte, isStorage bool) error { + node, data, err := getCustomNode(nodeHash, storage) + if err != nil { + return err + } + + //copy whole bytes of nodes + newStorage.Put(nodeHash, data) + + return copyTrie(node, storage, newStorage, agg, isStorage) +} + +func copyTrie(node Node, storage Storage, newStorage Storage, agg []byte, isStorage bool) error { + switch n := node.(type) { + case nil: + return nil + case *FullNode: + if len(n.hash) > 0 { + return CopyTrie(n.hash, storage, newStorage, agg, isStorage) + } + + for i := range n.children { + if n.children[i] == nil { + continue + } + + err := copyTrie(n.children[i], storage, newStorage, append(agg, uint8(i)), isStorage) + if err != nil { + return err + } + } + + case *ValueNode: + //if node represens stored value, then we need to copy it + if n.hash { + return CopyTrie(n.buf, storage, newStorage, agg, isStorage) + } + + if !isStorage { + var account state.Account + if err := account.UnmarshalRlp(n.buf); err != nil { + return fmt.Errorf("cant parse account %s: %w", hex.EncodeToString(encodeCompact(agg)), err) + } else { + if account.CodeHash != nil && bytes.Equal(account.CodeHash, emptyCodeHash) == false { + code, ok := storage.GetCode(types.BytesToHash(account.CodeHash)) + if ok { + newStorage.SetCode(types.BytesToHash(account.CodeHash), code) + } else { + return fmt.Errorf("cant find code %s", hex.EncodeToString(account.CodeHash)) + } + } + + if account.Root != types.EmptyRootHash { + return CopyTrie(account.Root[:], storage, newStorage, nil, true) + } + } + } + + case *ShortNode: + if len(n.hash) > 0 { + return CopyTrie(n.hash, storage, newStorage, agg, isStorage) + } + + return copyTrie(n.child, storage, newStorage, append(agg, n.key...), isStorage) + } + + return nil +} + +func HashChecker(stateRoot []byte, storage Storage) (types.Hash, error) { + node, _, err := GetNode(stateRoot, storage) + if err != nil { + return types.Hash{}, err + } + + h, ok := hasherPool.Get().(*hasher) + if !ok { + return types.Hash{}, errors.New("cant get hasher") + } + + arena, _ := h.AcquireArena() + + val, err := hashChecker(node, h, arena, 0, storage) + if err != nil { + return types.Hash{}, err + } + + if val == nil { + return emptyStateHash, nil + } + + h.ReleaseArenas(0) + hasherPool.Put(h) + + return types.BytesToHash(val.Raw()), nil +} + +func hashChecker(node Node, h *hasher, a *fastrlp.Arena, d int, storage Storage) (*fastrlp.Value, error) { + var ( + val *fastrlp.Value + aa *fastrlp.Arena + idx int + ) + + switch n := node.(type) { + case nil: + return nil, nil + case *ValueNode: + if n.hash { + nd, _, err := GetNode(n.buf, storage) + if err != nil { + return nil, err + } + + return hashChecker(nd, h, a, d, storage) + } + + return a.NewCopyBytes(n.buf), nil + + case *ShortNode: + child, err := hashChecker(n.child, h, a, d+1, storage) + if err != nil { + return nil, err + } + + val = a.NewArray() + val.Set(a.NewBytes(encodeCompact(n.key))) + val.Set(child) + + case *FullNode: + val = a.NewArray() + + aa, idx = h.AcquireArena() + + for _, i := range n.children { + if i == nil { + val.Set(a.NewNull()) + } else { + v, err := hashChecker(i, h, aa, d+1, storage) + if err != nil { + return nil, err + } + val.Set(v) + } + } + + // Add the value + if n.value == nil { + val.Set(a.NewNull()) + } else { + v, err := hashChecker(n.value, h, a, d+1, storage) + if err != nil { + return nil, err + } + val.Set(v) + } + + default: + return nil, fmt.Errorf("unknown node type %T", node) + } + + if val.Len() < 32 { + return val, nil + } + + // marshal RLP value + h.buf = val.MarshalTo(h.buf[:0]) + + if aa != nil { + h.ReleaseArenas(idx) + } + + tmp := h.Hash(h.buf) + hh := node.SetHash(tmp) + + return a.NewCopyBytes(hh), nil +} + +func NewKV(db *leveldb.DB) *KVStorage { + return &KVStorage{db: db} +} + +func NewTrieWithRoot(root Node) *Trie { + return &Trie{ + root: root, + } +} diff --git a/state/immutable-trie/copy_trie_test.go b/state/immutable-trie/copy_trie_test.go new file mode 100644 index 0000000000..4d678f5eed --- /dev/null +++ b/state/immutable-trie/copy_trie_test.go @@ -0,0 +1,64 @@ +package itrie + +import ( + "testing" + + "github.com/syndtr/goleveldb/leveldb" + ldbstorage "github.com/syndtr/goleveldb/leveldb/storage" + "pgregory.net/rapid" +) + +func TestCompareModelOfTrieCopy(t *testing.T) { + t.Parallel() + rapid.Check(t, func(tt *rapid.T) { + ldbStorageOld := ldbstorage.NewMemStorage() + ldbStorageNew := ldbstorage.NewMemStorage() + ldb, err := leveldb.Open(ldbStorageOld, nil) + if err != nil { + t.Fatal(err) + } + defer ldb.Close() + + ldbNew, err := leveldb.Open(ldbStorageNew, nil) + if err != nil { + t.Fatal(err) + } + defer ldbNew.Close() + + kv := NewKV(ldb) + newKV := NewKV(ldbNew) + state := NewState(kv) + trie := state.newTrie() + tx := trie.Txn(kv) + + n := rapid.IntRange(1, 1000).Draw(tt, "n") + for i := 0; i < n; i++ { + key := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(tt, "key") + value := rapid.SliceOfN(rapid.Byte(), 10, 80).Draw(tt, "value") + tx.Insert(key, value) + } + + tx.Commit() + stateRoot := trie.Hash() + result, err := HashChecker(stateRoot.Bytes(), kv) + if err != nil { + t.Fatal(err) + } + if stateRoot != result { + t.Fatal("Hashes are not equal", stateRoot, result) + } + + err = CopyTrie(stateRoot.Bytes(), kv, newKV, []byte{}, false) + if err != nil { + t.Fatal(err) + } + + result, err = HashChecker(stateRoot.Bytes(), newKV) + if err != nil { + t.Error(err) + } + if stateRoot != result { + t.Error("Hashes are not equal", stateRoot, result) + } + }) +} diff --git a/state/immutable-trie/storage.go b/state/immutable-trie/storage.go index 6ae99e702a..e1df83378c 100644 --- a/state/immutable-trie/storage.go +++ b/state/immutable-trie/storage.go @@ -171,7 +171,7 @@ func (m *memBatch) Write() { // GetNode retrieves a node from storage func GetNode(root []byte, storage Storage) (Node, bool, error) { data, ok := storage.Get(root) - if !ok { + if !ok || len(data) == 0 { return nil, false, nil } diff --git a/validators/store/contract/contract_test.go b/validators/store/contract/contract_test.go index 92b34ee1c3..e164538126 100644 --- a/validators/store/contract/contract_test.go +++ b/validators/store/contract/contract_test.go @@ -17,7 +17,6 @@ import ( "github.com/hashicorp/go-hclog" lru "github.com/hashicorp/golang-lru" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) var ( @@ -85,8 +84,8 @@ func newTestTransition( Forks: chain.AllForksEnabled, }, st, hclog.NewNullLogger()) - rootHash, err := ex.WriteGenesis(nil) - require.NoError(t, err) + rootHash, err := ex.WriteGenesis(nil, types.Hash{}) + assert.NoError(t, err) ex.GetHash = func(h *types.Header) state.GetHashByNumber { return func(i uint64) types.Hash {