// VulcanizeDB // Copyright © 2020 Vulcanize // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . package validator_test import ( "math/big" "os" "path/filepath" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" cid "github.com/ipfs/go-cid/_rsrch/cidiface" "github.com/jmoiron/sqlx" "github.com/multiformats/go-multihash" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" validator "github.com/cerc-io/eth-ipfs-state-validator/v5/pkg" ) var ( blockNumber = uint64(1) contractAddr = common.HexToAddress("0xaE9BEa628c4Ce503DcFD7E305CaB4e29E7476592") slot0StorageValue = common.Hex2Bytes("94703c4b2bd70c169f5717101caee543299fc946c7") slot1StorageValue = common.Hex2Bytes("01") nullCodeHash = crypto.Keccak256Hash([]byte{}) emptyRootNode, _ = rlp.EncodeToBytes(&[]byte{}) emptyContractRoot = crypto.Keccak256Hash(emptyRootNode) stateBranchRootNode, _ = rlp.EncodeToBytes(&[]interface{}{ crypto.Keccak256(bankAccountLeafNode), []byte{}, []byte{}, []byte{}, []byte{}, crypto.Keccak256(minerAccountLeafNode), crypto.Keccak256(contractAccountLeafNode), []byte{}, []byte{}, []byte{}, []byte{}, []byte{}, crypto.Keccak256(account2LeafNode), []byte{}, crypto.Keccak256(account1LeafNode), []byte{}, []byte{}, }) stateRoot = crypto.Keccak256Hash(stateBranchRootNode) mockCode = []byte{1, 2, 3, 4, 5} codeHash = crypto.Keccak256Hash(mockCode) codePath = common.Hex2Bytes("6114658a74d9cc9f7acf2c5cd696c3494d7c344d78bfec3add0d91ec4e8d1c45") contractAccount, _ = rlp.EncodeToBytes(&types.StateAccount{ Nonce: 1, Balance: big.NewInt(0), CodeHash: codeHash.Bytes(), Root: crypto.Keccak256Hash(storageBranchRootNode), }) contractAccountLeafNode, _ = rlp.EncodeToBytes(&[]interface{}{ common.Hex2Bytes("3114658a74d9cc9f7acf2c5cd696c3494d7c344d78bfec3add0d91ec4e8d1c45"), contractAccount, }) minerAccount, _ = rlp.EncodeToBytes(&types.StateAccount{ Nonce: 0, Balance: big.NewInt(1000), CodeHash: nullCodeHash.Bytes(), Root: emptyContractRoot, }) minerAccountLeafNode, _ = rlp.EncodeToBytes(&[]interface{}{ common.Hex2Bytes("3380c7b7ae81a58eb98d9c78de4a1fd7fd9535fc953ed2be602daaa41767312a"), minerAccount, }) account1, _ = rlp.EncodeToBytes(&types.StateAccount{ Nonce: 2, Balance: big.NewInt(1000), CodeHash: nullCodeHash.Bytes(), Root: emptyContractRoot, }) account1LeafNode, _ = rlp.EncodeToBytes(&[]interface{}{ common.Hex2Bytes("3926db69aaced518e9b9f0f434a473e7174109c943548bb8f23be41ca76d9ad2"), account1, }) account2, _ = rlp.EncodeToBytes(&types.StateAccount{ Nonce: 0, Balance: big.NewInt(1000), CodeHash: nullCodeHash.Bytes(), Root: emptyContractRoot, }) account2LeafNode, _ = rlp.EncodeToBytes(&[]interface{}{ common.Hex2Bytes("3957f3e2f04a0764c3a0491b175f69926da61efbcc8f61fa1455fd2d2b4cdd45"), account2, }) bankAccount, _ = rlp.EncodeToBytes(&types.StateAccount{ Nonce: 2, Balance: big.NewInt(1000), CodeHash: nullCodeHash.Bytes(), Root: emptyContractRoot, }) bankAccountLeafNode, _ = rlp.EncodeToBytes(&[]interface{}{ common.Hex2Bytes("30bf49f440a1cd0527e4d06e2765654c0f56452257516d793a9b8d604dcfdf2a"), bankAccount, }) storageBranchRootNode, _ = rlp.EncodeToBytes(&[]interface{}{ []byte{}, []byte{}, crypto.Keccak256(slot0StorageLeafNode), []byte{}, []byte{}, []byte{}, []byte{}, []byte{}, []byte{}, []byte{}, []byte{}, crypto.Keccak256(slot1StorageLeafNode), []byte{}, []byte{}, []byte{}, []byte{}, []byte{}, }) storageRoot = crypto.Keccak256Hash(storageBranchRootNode) slot0StorageLeafNode, _ = rlp.EncodeToBytes(&[]interface{}{ common.Hex2Bytes("390decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563"), slot0StorageValue, }) slot1StorageLeafNode, _ = rlp.EncodeToBytes(&[]interface{}{ common.Hex2Bytes("310e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6"), slot1StorageValue, }) trieStateNodes = [][]byte{ stateBranchRootNode, bankAccountLeafNode, minerAccountLeafNode, contractAccountLeafNode, account1LeafNode, account2LeafNode, } trieStorageNodes = [][]byte{ storageBranchRootNode, slot0StorageLeafNode, slot1StorageLeafNode, } missingRootStateNodes = [][]byte{ bankAccountLeafNode, minerAccountLeafNode, contractAccountLeafNode, account1LeafNode, account2LeafNode, } missingRootStorageNodes = [][]byte{ slot0StorageLeafNode, slot1StorageLeafNode, } missingNodeStateNodes = [][]byte{ stateBranchRootNode, bankAccountLeafNode, minerAccountLeafNode, contractAccountLeafNode, account2LeafNode, } missingNodeStorageNodes = [][]byte{ storageBranchRootNode, slot1StorageLeafNode, } missingStateNodePath = common.Hex2Bytes("0e") missingStorageNodePath = common.Hex2Bytes("02") ) var ( v *validator.Validator db *sqlx.DB err error tmp string config = validator.Config{ Hostname: "localhost", Name: "cerc_testing", User: "vdbm", Password: "password", Port: 8077, } ) var _ = Describe("PG-IPFS Validator", func() { BeforeEach(func() { err = validator.LoadEnv(&config) Expect(err).ToNot(HaveOccurred()) db, err = sqlx.Connect("postgres", config.ConnString()) Expect(err).ToNot(HaveOccurred()) tmp, err = os.MkdirTemp("", "test_validator") Expect(err).ToNot(HaveOccurred()) params := validator.Params{Workers: 4, RecoveryFormat: filepath.Join(tmp, "recover_%s")} v = validator.NewPGIPFSValidator(db, params) }) AfterEach(func() { os.RemoveAll(tmp) v.Close() db.Close() }) Describe("ValidateTrie", func() { AfterEach(func() { err = ResetTestDB(db) Expect(err).ToNot(HaveOccurred()) }) It("Returns an error if the state root node is missing", func() { // we write code to ethdb, there should probably be an EthCode IPLD codec // but there isn't, and we don't need one here since blockstore keys are mh-derived loadTrie(missingRootStateNodes, trieStorageNodes, mockCode) err = v.ValidateTrie(stateRoot) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("missing trie node")) }) It("Returns an error if the storage root node is missing", func() { loadTrie(trieStateNodes, missingRootStorageNodes, mockCode) err = v.ValidateTrie(stateRoot) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("missing trie node")) }) It("Returns an error if the state trie is missing node(s)", func() { loadTrie(missingNodeStateNodes, trieStorageNodes, mockCode) err = v.ValidateTrie(stateRoot) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("missing trie node")) Expect(err.Error()).To(ContainSubstring("%x", missingStateNodePath)) }) It("Returns an error if the storage trie is missing node(s)", func() { loadTrie(trieStateNodes, missingNodeStorageNodes, mockCode) err = v.ValidateTrie(stateRoot) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("missing trie node")) Expect(err.Error()).To(ContainSubstring("%x", missingStorageNodePath)) }) It("Returns an error if contract code is missing", func() { loadTrie(trieStateNodes, trieStorageNodes) err = v.ValidateTrie(stateRoot) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("%x", codeHash)) Expect(err.Error()).To(ContainSubstring("%x", codePath)) }) It("Returns no error if the entire state (state trie and storage tries) can be validated", func() { loadTrie(trieStateNodes, trieStorageNodes, mockCode) err = v.ValidateTrie(stateRoot) Expect(err).ToNot(HaveOccurred()) }) }) Describe("ValidateStateTrie", func() { AfterEach(func() { err = ResetTestDB(db) Expect(err).ToNot(HaveOccurred()) }) It("Returns an error the state root node is missing", func() { loadTrie(missingRootStateNodes, nil) err = v.ValidateStateTrie(stateRoot) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("missing trie node")) }) It("Returns an error if the entire state trie cannot be validated", func() { loadTrie(missingNodeStateNodes, nil) err = v.ValidateStateTrie(stateRoot) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("missing trie node")) }) It("Returns no error if the entire state trie can be validated", func() { loadTrie(trieStateNodes, nil) err = v.ValidateStateTrie(stateRoot) Expect(err).ToNot(HaveOccurred()) }) }) Describe("ValidateStorageTrie", func() { AfterEach(func() { err = ResetTestDB(db) Expect(err).ToNot(HaveOccurred()) }) It("Returns an error the storage root node is missing", func() { loadTrie(nil, missingRootStorageNodes) err = v.ValidateStorageTrie(stateRoot, contractAddr, storageRoot) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("missing trie node")) }) It("Returns an error if the entire storage trie cannot be validated", func() { loadTrie(nil, missingNodeStorageNodes) err = v.ValidateStorageTrie(stateRoot, contractAddr, storageRoot) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("missing trie node")) }) It("Returns no error if the entire storage trie can be validated", func() { loadTrie(nil, trieStorageNodes) err = v.ValidateStorageTrie(stateRoot, contractAddr, storageRoot) Expect(err).ToNot(HaveOccurred()) }) }) }) func loadTrie(stateNodes, storageNodes [][]byte, contractCode ...[]byte) { tx, err := db.Beginx() Expect(err).ToNot(HaveOccurred()) for _, node := range stateNodes { err := PublishRaw(tx, cid.EthStateTrie, multihash.KECCAK_256, node, blockNumber) Expect(err).ToNot(HaveOccurred()) } for _, node := range storageNodes { err := PublishRaw(tx, cid.EthStorageTrie, multihash.KECCAK_256, node, blockNumber) Expect(err).ToNot(HaveOccurred()) } for _, code := range contractCode { err := PublishRaw(tx, cid.Raw, multihash.KECCAK_256, code, blockNumber) Expect(err).ToNot(HaveOccurred()) } err = tx.Commit() Expect(err).ToNot(HaveOccurred()) }