From 0cebfeb759da59d28fbaff4f95a23cf13d8d8905 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Thu, 26 Jul 2018 09:36:01 -0400 Subject: [PATCH] Merge pull request #453: Core/State Unit Tests --- Gopkg.lock | 137 +++++++++++++++++--- Gopkg.toml | 4 + Makefile | 95 +++++++++----- core/chain.go | 36 +++--- core/chain_test.go | 121 ++++++++++++++++++ core/ethdb_test.go | 147 ++++++++++++++++++++++ gometalinter.json | 9 ++ state/database.go | 23 ++-- state/database_test.go | 132 +++++++++++++++++++ state/test_utils.go | 23 ++++ state/trie.go | 30 +++-- state/trie_test.go | 259 ++++++++++++++++++++++++++++++++++++++ test/importer/importer.go | 1 + 13 files changed, 931 insertions(+), 86 deletions(-) create mode 100644 core/chain_test.go create mode 100644 core/ethdb_test.go create mode 100644 gometalinter.json create mode 100644 state/database_test.go create mode 100644 state/test_utils.go create mode 100644 state/trie_test.go diff --git a/Gopkg.lock b/Gopkg.lock index c0cedd10..396345f7 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -3,23 +3,30 @@ [[projects]] branch = "master" + digest = "1:9881039571249d9ff92d4a3383a015c233a4c172f6b5a3d6d71e6c4bfdb70efc" name = "github.com/aristanetworks/goarista" packages = ["monotime"] + pruneopts = "T" revision = "32f94db2e6faa2c7250286dfb4c7ad3dc0f3ead2" [[projects]] branch = "master" + digest = "1:cafb561ce87d0eaa309ad6853380d437df3c1142561c5afa700311825aa38df1" name = "github.com/btcsuite/btcd" packages = ["btcec"] + pruneopts = "T" revision = "fdfc19097e7ac6b57035062056f5b7b4638b8898" [[projects]] branch = "master" + digest = "1:20d11bf9fd5c6d8f7bb393229fdd981db8b4350aa9a05f28856b64640851c9b8" name = "github.com/btcsuite/btcutil" packages = ["bech32"] + pruneopts = "T" revision = "ab6388e0c60ae4834a1f57511e20c17b5f78be4b" [[projects]] + digest = "1:83f4d10d8cbc8174248ac57b9788ff94f9e3af1999e8708fc50f80b7b91949fb" name = "github.com/cosmos/cosmos-sdk" packages = [ "baseapp", @@ -27,24 +34,30 @@ "types", "version", "wire", - "x/auth" + "x/auth", ] + pruneopts = "T" revision = "1a1373cc220e402397ad536aee6b8f5b068914c6" version = "v0.21.0" [[projects]] + digest = "1:3aa953edddec96fd00285789ccd4a31efaff0a2979a3e35b77f5c19d5eaa37f7" name = "github.com/davecgh/go-spew" packages = ["spew"] + pruneopts = "T" revision = "346938d642f2ec3594ed81d874461961cd0faa76" version = "v1.1.0" [[projects]] branch = "master" + digest = "1:67d0b50be0549e610017cb91e0b0b745ec0cad7c613bc8e18ff2d1c1fc8825a7" name = "github.com/edsrzf/mmap-go" packages = ["."] + pruneopts = "T" revision = "0bce6a6887123b67a60366d2c9fe2dfb74289d2e" [[projects]] + digest = "1:24ae9f4a9d6e2bed9aca667af245834c9a80c39d5ae32e3fc99a2ace91287047" name = "github.com/ethereum/go-ethereum" packages = [ "common", @@ -74,34 +87,42 @@ "params", "rlp", "rpc", - "trie" + "trie", ] + pruneopts = "T" revision = "dea1ce052a10cd7d401a5c04f83f371a06fe293c" version = "v1.8.11" [[projects]] + digest = "1:b18534450f89f7007960ff1804d63fb0cc6e7d1989446fcb05d77fb24afc51fc" name = "github.com/go-kit/kit" packages = [ "log", "log/level", - "log/term" + "log/term", ] + pruneopts = "T" revision = "4dc7be5d2d12881735283bcab7352178e190fc71" version = "v0.6.0" [[projects]] + digest = "1:31a18dae27a29aa074515e43a443abfd2ba6deb6d69309d8d7ce789c45f34659" name = "github.com/go-logfmt/logfmt" packages = ["."] + pruneopts = "T" revision = "390ab7935ee28ec6b286364bba9b4dd6410cb3d5" version = "v0.3.0" [[projects]] + digest = "1:c4a2528ccbcabf90f9f3c464a5fc9e302d592861bbfd0b7135a7de8a943d0406" name = "github.com/go-stack/stack" packages = ["."] + pruneopts = "T" revision = "259ab82a6cad3992b4e21ff5cac294ccb06474bc" version = "v1.7.0" [[projects]] + digest = "1:2a1db9bae44464f781d3637b67df38e896c6e1b9c902e27d24ee9037cb50f23b" name = "github.com/gogo/protobuf" packages = [ "gogoproto", @@ -109,82 +130,123 @@ "proto", "protoc-gen-gogo/descriptor", "sortkeys", - "types" + "types", ] + pruneopts = "T" revision = "1adfc126b41513cc696b209667c8656ea7aac67c" version = "v1.0.0" [[projects]] + digest = "1:6f3df7b8eccb559fa1bda8dae71fdb5f24da5e9aec2696e21f19e6d24062602f" name = "github.com/golang/protobuf" packages = [ "proto", "ptypes", "ptypes/any", "ptypes/duration", - "ptypes/timestamp" + "ptypes/timestamp", ] + pruneopts = "T" revision = "925541529c1fa6821df4e44ce2723319eb2be768" version = "v1.0.0" [[projects]] branch = "master" + digest = "1:968462840e6d86b12990015ac6ab297c022ccde102953040724be1df0e9e6c96" name = "github.com/golang/snappy" packages = ["."] + pruneopts = "T" revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a" [[projects]] branch = "master" + digest = "1:cf296baa185baae04a9a7004efee8511d08e2f5f51d4cbe5375da89722d681db" name = "github.com/hashicorp/golang-lru" packages = [ ".", - "simplelru" + "simplelru", ] + pruneopts = "T" revision = "0fb14efe8c47ae851c0034ed7a448854d3d34cf3" [[projects]] + digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be" name = "github.com/inconshreveable/mousetrap" packages = ["."] + pruneopts = "T" revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75" version = "v1.0" [[projects]] branch = "master" + digest = "1:14f2079ea27e7c67ecdab4f35e774463abc4f9d1806b5d674c0594b52127ab1d" name = "github.com/jmhodges/levigo" packages = ["."] + pruneopts = "T" revision = "c42d9e0ca023e2198120196f842701bb4c55d7b9" [[projects]] branch = "master" + digest = "1:a64e323dc06b73892e5bb5d040ced475c4645d456038333883f58934abbf6f72" name = "github.com/kr/logfmt" packages = ["."] + pruneopts = "T" revision = "b84e30acd515aadc4b783ad4ff83aff3299bdfe0" [[projects]] + digest = "1:40e195917a951a8bf867cd05de2a46aaf1806c50cf92eebf4c16f78cd196f747" name = "github.com/pkg/errors" packages = ["."] + pruneopts = "T" revision = "645ef00459ed84a119197bfb8d8205042c6df63d" version = "v0.8.0" [[projects]] + digest = "1:22aa691fe0213cb5c07d103f9effebcb7ad04bee45a0ce5fe5369d0ca2ec3a1f" + name = "github.com/pmezard/go-difflib" + packages = ["difflib"] + pruneopts = "T" + revision = "792786c7400a136282c1664665ae0a8db921c6c2" + version = "v1.0.0" + +[[projects]] + digest = "1:540558c17f78ee4f056aa043cf3389c283b56754db79112a2d64172e80e685db" name = "github.com/rs/cors" packages = ["."] + pruneopts = "T" revision = "ca016a06a5753f8ba03029c0aa5e54afb1bf713f" version = "v1.4.0" [[projects]] + digest = "1:daab027a0bfb143afb503f7b63673bfa8d44f69ce9484c6d19b97957aadc1252" name = "github.com/spf13/cobra" packages = ["."] + pruneopts = "T" revision = "ef82de70bb3f60c65fb8eebacbb2d122ef517385" version = "v0.0.3" [[projects]] + digest = "1:6de2f73eb31e80d74f84ce1c861e4c0c8f00ca5fb41a25901f987e63a0647c28" name = "github.com/spf13/pflag" packages = ["."] + pruneopts = "T" revision = "583c0c0531f06d5278b7d917446061adc344b5cd" version = "v1.0.1" +[[projects]] + digest = "1:c7f05297d9ad389d81e6d764388d97c4b6a64665eff9fd2550fbdd8545430b80" + name = "github.com/stretchr/testify" + packages = [ + "assert", + "require", + ] + pruneopts = "T" + revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686" + version = "v1.2.2" + [[projects]] branch = "master" + digest = "1:9c39a878048f4a5468675b814fb7d2528d622f8c3612511ff0b5e2a48d451ad2" name = "github.com/syndtr/goleveldb" packages = [ "leveldb", @@ -198,33 +260,41 @@ "leveldb/opt", "leveldb/storage", "leveldb/table", - "leveldb/util" + "leveldb/util", ] + pruneopts = "T" revision = "c4c61651e9e37fa117f53c5a906d3b63090d8445" [[projects]] branch = "master" + digest = "1:8403202d034640f399279a4f735faabefeb6ee64bbcb03c9c93be1d4c7230382" name = "github.com/tendermint/ed25519" packages = [ ".", "edwards25519", - "extra25519" + "extra25519", ] + pruneopts = "T" revision = "d8387025d2b9d158cf4efb07e7ebf814bcce2057" [[projects]] + digest = "1:4431caadcd2cc6a245bf0f6f61884029f5cc70833a8cc458cd1ba4a578b18c71" name = "github.com/tendermint/go-amino" packages = ["."] + pruneopts = "T" revision = "2106ca61d91029c931fd54968c2bb02dc96b1412" version = "0.10.1" [[projects]] + digest = "1:f11b64e85907d8909a820d0cd793dff19dc268432626411398aa0e26e8a61338" name = "github.com/tendermint/iavl" packages = ["."] + pruneopts = "T" revision = "35f66e53d9b01e83b30de68b931f54b2477a94c9" version = "v0.9.2" [[projects]] + digest = "1:f056bee4848f0fff6c92e6b99d4972b175d8a8eec9700df30b143b2e6cc48ea8" name = "github.com/tendermint/tendermint" packages = [ "abci/server", @@ -238,13 +308,15 @@ "libs/log", "libs/pubsub", "libs/pubsub/query", - "types" + "types", ] + pruneopts = "T" revision = "5ff65274b84ea905787a48512cc3124385bddf2f" version = "v0.22.2" [[projects]] branch = "master" + digest = "1:2c4971d2da7bb27fa225a119dc96af2119dd096869c1228438a0b5fda5f6fe15" name = "golang.org/x/crypto" packages = [ "internal/subtle", @@ -253,12 +325,14 @@ "openpgp/errors", "poly1305", "ripemd160", - "salsa20/salsa" + "salsa20/salsa", ] + pruneopts = "T" revision = "a49355c7e3f8fe157a85be2f77e6e269a0f89602" [[projects]] branch = "master" + digest = "1:501f63fec0206818ab3dac086383bd2146841951c6bd6f3c4f72613f113f25fd" name = "golang.org/x/net" packages = [ "context", @@ -268,11 +342,13 @@ "idna", "internal/timeseries", "trace", - "websocket" + "websocket", ] + pruneopts = "T" revision = "d0887baf81f4598189d4e12a37c6da86f0bba4d0" [[projects]] + digest = "1:24db346d9931fe01f1e9a02aba78ba22c1ecd55bf0f79dd10ba5169719cf002d" name = "golang.org/x/text" packages = [ "collate", @@ -288,17 +364,21 @@ "unicode/bidi", "unicode/cldr", "unicode/norm", - "unicode/rangetable" + "unicode/rangetable", ] + pruneopts = "T" revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" version = "v0.3.0" [[projects]] + digest = "1:cfa1bbb9ee86ade0914bd5f8e8516386cf7d573957191ecb5163d8f6e023ca0c" name = "google.golang.org/genproto" packages = ["googleapis/rpc/status"] + pruneopts = "T" revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200" [[projects]] + digest = "1:1faab7c2380bc84698a62531c4af8c9475fbc7b3b1b2696f2f94feff97c47a49" name = "google.golang.org/grpc" packages = [ ".", @@ -323,32 +403,61 @@ "stats", "status", "tap", - "transport" + "transport", ] + pruneopts = "T" revision = "d11072e7ca9811b1100b80ca0269ac831f06d024" version = "v1.11.3" [[projects]] + digest = "1:3ccd10c863188cfe0d936fcfe6a055c95362e43af8e7039e33baade846928e74" name = "gopkg.in/fatih/set.v0" packages = ["."] + pruneopts = "T" revision = "57907de300222151a123d29255ed17f5ed43fad3" version = "v0.1.0" [[projects]] branch = "v2" + digest = "1:35056a4c53d0b725735422545c3c11bdc9007da2fdb644fee96f3a6b7c42c69f" name = "gopkg.in/karalabe/cookiejar.v2" packages = ["collections/prque"] + pruneopts = "T" revision = "8dcd6a7f4951f6ff3ee9cbb919a06d8925822e57" [[projects]] branch = "v2" + digest = "1:3d3f9391ab615be8655ae0d686a1564f3fec413979bb1aaf018bac1ec1bb1cc7" name = "gopkg.in/natefinch/npipe.v2" packages = ["."] + pruneopts = "T" revision = "c1b8fa8bdccecb0b8db834ee0b92fdbcfa606dd6" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "6c999da3cbcf5eac93468232147f2d8e372236a297ddd09275edb065f1bc23ae" + input-imports = [ + "github.com/cosmos/cosmos-sdk/baseapp", + "github.com/cosmos/cosmos-sdk/store", + "github.com/cosmos/cosmos-sdk/types", + "github.com/cosmos/cosmos-sdk/wire", + "github.com/ethereum/go-ethereum/common", + "github.com/ethereum/go-ethereum/common/math", + "github.com/ethereum/go-ethereum/consensus", + "github.com/ethereum/go-ethereum/consensus/ethash", + "github.com/ethereum/go-ethereum/consensus/misc", + "github.com/ethereum/go-ethereum/core", + "github.com/ethereum/go-ethereum/core/state", + "github.com/ethereum/go-ethereum/core/types", + "github.com/ethereum/go-ethereum/core/vm", + "github.com/ethereum/go-ethereum/ethdb", + "github.com/ethereum/go-ethereum/params", + "github.com/ethereum/go-ethereum/rlp", + "github.com/ethereum/go-ethereum/rpc", + "github.com/ethereum/go-ethereum/trie", + "github.com/hashicorp/golang-lru", + "github.com/stretchr/testify/require", + "github.com/tendermint/tendermint/libs/db", + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index dfa46868..de39b8ac 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -21,5 +21,9 @@ name = "github.com/tendermint/tendermint" version = "=0.22.2" +[[constraint]] + name = "github.com/stretchr/testify" + version = "=1.2.2" + [prune] go-tests = true diff --git a/Makefile b/Makefile index f7f71338..d68cdbee 100644 --- a/Makefile +++ b/Makefile @@ -20,26 +20,6 @@ DOCKER_IMAGE = tendermint/ethermint ETHERMINT_DAEMON_BINARY = emintd ETHERMINT_CLI_BINARY = emintcli -DEP = github.com/golang/dep/cmd/dep -GOLINT = github.com/tendermint/lint/golint -GOMETALINTER = gopkg.in/alecthomas/gometalinter.v2 -UNCONVERT = github.com/mdempsky/unconvert -INEFFASSIGN = github.com/gordonklaus/ineffassign -MISSPELL = github.com/client9/misspell/cmd/misspell -ERRCHECK = github.com/kisielk/errcheck -UNPARAM = mvdan.cc/unparam -GOCYCLO = github.com/alecthomas/gocyclo - -DEP_CHECK := $(shell command -v dep 2> /dev/null) -GOLINT_CHECK := $(shell command -v golint 2> /dev/null) -GOMETALINTER_CHECK := $(shell command -v gometalinter.v2 2> /dev/null) -UNCONVERT_CHECK := $(shell command -v unconvert 2> /dev/null) -INEFFASSIGN_CHECK := $(shell command -v ineffassign 2> /dev/null) -MISSPELL_CHECK := $(shell command -v misspell 2> /dev/null) -ERRCHECK_CHECK := $(shell command -v errcheck 2> /dev/null) -UNPARAM_CHECK := $(shell command -v unparam 2> /dev/null) -GOCYCLO_CHECK := $(shell command -v gocyclo 2> /dev/null) - all: tools deps install ####################### @@ -60,69 +40,93 @@ install: go install $(BUILD_FLAGS) ./cmd/ethermintcli clean: - rm -rf ./build ./vendor + @rm -rf ./build ./vendor update-tools: - @echo "Updating golang dependencies" + @echo "--> Updating golang dependencies" go get -u -v $(DEP) $(GOLINT) $(GOMETALINTER) $(UNCONVERT) $(INEFFASSIGN) $(MISSPELL) $(ERRCHECK) $(UNPARAM) $(GOCYCLO) ############################ ### Tools / Dependencies ### ############################ +########################################################## +### TODO: Move tool depedencies to a separate makefile ### +########################################################## + +DEP = github.com/golang/dep/cmd/dep +GOLINT = github.com/tendermint/lint/golint +GOMETALINTER = gopkg.in/alecthomas/gometalinter.v2 +UNCONVERT = github.com/mdempsky/unconvert +INEFFASSIGN = github.com/gordonklaus/ineffassign +MISSPELL = github.com/client9/misspell/cmd/misspell +ERRCHECK = github.com/kisielk/errcheck +UNPARAM = mvdan.cc/unparam +GOCYCLO = github.com/alecthomas/gocyclo + +DEP_CHECK := $(shell command -v dep 2> /dev/null) +GOLINT_CHECK := $(shell command -v golint 2> /dev/null) +GOMETALINTER_CHECK := $(shell command -v gometalinter.v2 2> /dev/null) +UNCONVERT_CHECK := $(shell command -v unconvert 2> /dev/null) +INEFFASSIGN_CHECK := $(shell command -v ineffassign 2> /dev/null) +MISSPELL_CHECK := $(shell command -v misspell 2> /dev/null) +ERRCHECK_CHECK := $(shell command -v errcheck 2> /dev/null) +UNPARAM_CHECK := $(shell command -v unparam 2> /dev/null) +GOCYCLO_CHECK := $(shell command -v gocyclo 2> /dev/null) + tools: ifdef DEP_CHECK @echo "Dep is already installed. Run 'make update-tools' to update." else - @echo "Installing dep" + @echo "--> Installing dep" go get -v $(DEP) endif ifdef GOLINT_CHECK @echo "Golint is already installed. Run 'make update-tools' to update." else - @echo "Installing golint" + @echo "--> Installing golint" go get -v $(GOLINT) endif ifdef GOMETALINTER_CHECK @echo "Gometalinter.v2 is already installed. Run 'make update-tools' to update." else - @echo "Installing gometalinter.v2" + @echo "--> Installing gometalinter.v2" go get -v $(GOMETALINTER) endif ifdef UNCONVERT_CHECK @echo "Unconvert is already installed. Run 'make update-tools' to update." else - @echo "Installing unconvert" + @echo "--> Installing unconvert" go get -v $(UNCONVERT) endif ifdef INEFFASSIGN_CHECK @echo "Ineffassign is already installed. Run 'make update-tools' to update." else - @echo "Installing ineffassign" + @echo "--> Installing ineffassign" go get -v $(INEFFASSIGN) endif ifdef MISSPELL_CHECK @echo "misspell is already installed. Run 'make update-tools' to update." else - @echo "Installing misspell" + @echo "--> Installing misspell" go get -v $(MISSPELL) endif ifdef ERRCHECK_CHECK @echo "errcheck is already installed. Run 'make update-tools' to update." else - @echo "Installing errcheck" + @echo "--> Installing errcheck" go get -v $(ERRCHECK) endif ifdef UNPARAM_CHECK @echo "unparam is already installed. Run 'make update-tools' to update." else - @echo "Installing unparam" + @echo "--> Installing unparam" go get -v $(UNPARAM) endif ifdef GOCYCLO_CHECK @echo "goyclo is already installed. Run 'make update-tools' to update." else - @echo "Installing goyclo" + @echo "--> Installing goyclo" go get -v $(GOCYCLO) endif @@ -135,6 +139,27 @@ deps: ### Testing / Misc. ### ####################### +TEST_PACKAGES=$(shell go list ./... | grep -v github.com/cosmos/ethermint/cmd/test) + +test: test-unit + +test-unit: + @go test -v $(TEST_PACKAGES) + +test-race: + @go test -v -race $(TEST_PACKAGES) + +test-cli: + @echo "NO CLI TESTS" + +test-lint: + @echo "--> Running gometalinter" + @gometalinter.v2 --config=gometalinter.json ./... + @!(gometalinter.v2 --disable-all --enable='errcheck' --vendor ./... | grep -v "client/") + @find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" | xargs gofmt -d -s + @dep status >/dev/null 2>&1 + @!(grep -n branch Gopkg.toml) + godocs: @echo "--> Wait a few seconds and visit http://localhost:6060/pkg/github.com/cosmos/ethermint" godoc -http=:6060 @@ -144,4 +169,10 @@ docker: docker tag ${DOCKER_IMAGE}:${DOCKER_TAG} ${DOCKER_IMAGE}:latest docker tag ${DOCKER_IMAGE}:${DOCKER_TAG} ${DOCKER_IMAGE}:${COMMIT_HASH} -.PHONY: build install update-tools tools deps godocs clean +format: + @echo "--> Formatting go files" + @find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" | xargs gofmt -w -s + @find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" | xargs misspell -w + +.PHONY: build install update-tools tools deps godocs clean format test-lint \ +test-cli test-race test-unit test diff --git a/core/chain.go b/core/chain.go index 741b6f2a..b7a7ddca 100644 --- a/core/chain.go +++ b/core/chain.go @@ -3,8 +3,8 @@ package core import ( "math/big" - ethcommon "github.com/ethereum/go-ethereum/common" - ethconsensus "github.com/ethereum/go-ethereum/consensus" + ethcmn "github.com/ethereum/go-ethereum/common" + ethcons "github.com/ethereum/go-ethereum/consensus" ethstate "github.com/ethereum/go-ethereum/core/state" ethtypes "github.com/ethereum/go-ethereum/core/types" ethrpc "github.com/ethereum/go-ethereum/rpc" @@ -20,7 +20,7 @@ import ( // NOTE: Ethermint will distribute the fees out to validators, so the structure // and functionality of this is a WIP and subject to change. type ChainContext struct { - Coinbase ethcommon.Address + Coinbase ethcmn.Address headersByNumber map[uint64]*ethtypes.Header } @@ -32,23 +32,25 @@ func NewChainContext() *ChainContext { // Engine implements Ethereum's core.ChainContext interface. As a ChainContext // implements the consensus.Engine interface, it is simply returned. -func (cc *ChainContext) Engine() ethconsensus.Engine { +func (cc *ChainContext) Engine() ethcons.Engine { return cc } +// SetHeader implements Ethereum's core.ChainContext interface. It sets the +// header for the given block number. func (cc *ChainContext) SetHeader(number uint64, header *ethtypes.Header) { cc.headersByNumber[number] = header } -// GetHeader implements Ethereum's core.ChainContext interface. It currently -// performs a no-op. +// GetHeader implements Ethereum's core.ChainContext interface. // // TODO: The Cosmos SDK supports retreiving such information in contexts and // multi-store, so this will be need to be integrated. -func (cc *ChainContext) GetHeader(parentHash ethcommon.Hash, number uint64) *ethtypes.Header { +func (cc *ChainContext) GetHeader(_ ethcmn.Hash, number uint64) *ethtypes.Header { if header, ok := cc.headersByNumber[number]; ok { return header } + return nil } @@ -58,7 +60,7 @@ func (cc *ChainContext) GetHeader(parentHash ethcommon.Hash, number uint64) *eth // // NOTE: Ethermint will distribute the fees out to validators, so the structure // and functionality of this is a WIP and subject to change. -func (cc *ChainContext) Author(_ *ethtypes.Header) (ethcommon.Address, error) { +func (cc *ChainContext) Author(_ *ethtypes.Header) (ethcmn.Address, error) { return cc.Coinbase, nil } @@ -67,13 +69,13 @@ func (cc *ChainContext) Author(_ *ethtypes.Header) (ethcommon.Address, error) { // // TODO: Do we need to support such RPC APIs? This will tie into a bigger // discussion on if we want to support web3. -func (cc *ChainContext) APIs(_ ethconsensus.ChainReader) []ethrpc.API { +func (cc *ChainContext) APIs(_ ethcons.ChainReader) []ethrpc.API { return nil } // CalcDifficulty implements Ethereum's core.ChainContext interface. It // currently performs a no-op. -func (cc *ChainContext) CalcDifficulty(_ ethconsensus.ChainReader, _ uint64, _ *ethtypes.Header) *big.Int { +func (cc *ChainContext) CalcDifficulty(_ ethcons.ChainReader, _ uint64, _ *ethtypes.Header) *big.Int { return nil } @@ -82,7 +84,7 @@ func (cc *ChainContext) CalcDifficulty(_ ethconsensus.ChainReader, _ uint64, _ * // // TODO: Figure out if this needs to be hooked up to any part of the ABCI? func (cc *ChainContext) Finalize( - _ ethconsensus.ChainReader, _ *ethtypes.Header, _ *ethstate.StateDB, + _ ethcons.ChainReader, _ *ethtypes.Header, _ *ethstate.StateDB, _ []*ethtypes.Transaction, _ []*ethtypes.Header, _ []*ethtypes.Receipt, ) (*ethtypes.Block, error) { return nil, nil @@ -92,7 +94,7 @@ func (cc *ChainContext) Finalize( // performs a no-op. // // TODO: Figure out if this needs to be hooked up to any part of the ABCI? -func (cc *ChainContext) Prepare(_ ethconsensus.ChainReader, _ *ethtypes.Header) error { +func (cc *ChainContext) Prepare(_ ethcons.ChainReader, _ *ethtypes.Header) error { return nil } @@ -100,7 +102,7 @@ func (cc *ChainContext) Prepare(_ ethconsensus.ChainReader, _ *ethtypes.Header) // performs a no-op. // // TODO: Figure out if this needs to be hooked up to any part of the ABCI? -func (cc *ChainContext) Seal(_ ethconsensus.ChainReader, _ *ethtypes.Block, _ <-chan struct{}) (*ethtypes.Block, error) { +func (cc *ChainContext) Seal(_ ethcons.ChainReader, _ *ethtypes.Block, _ <-chan struct{}) (*ethtypes.Block, error) { return nil, nil } @@ -109,7 +111,7 @@ func (cc *ChainContext) Seal(_ ethconsensus.ChainReader, _ *ethtypes.Block, _ <- // // TODO: Figure out if this needs to be hooked up to any part of the Cosmos SDK // handlers? -func (cc *ChainContext) VerifyHeader(_ ethconsensus.ChainReader, _ *ethtypes.Header, _ bool) error { +func (cc *ChainContext) VerifyHeader(_ ethcons.ChainReader, _ *ethtypes.Header, _ bool) error { return nil } @@ -118,7 +120,7 @@ func (cc *ChainContext) VerifyHeader(_ ethconsensus.ChainReader, _ *ethtypes.Hea // // TODO: Figure out if this needs to be hooked up to any part of the Cosmos SDK // handlers? -func (cc *ChainContext) VerifyHeaders(_ ethconsensus.ChainReader, _ []*ethtypes.Header, _ []bool) (chan<- struct{}, <-chan error) { +func (cc *ChainContext) VerifyHeaders(_ ethcons.ChainReader, _ []*ethtypes.Header, _ []bool) (chan<- struct{}, <-chan error) { return nil, nil } @@ -127,12 +129,12 @@ func (cc *ChainContext) VerifyHeaders(_ ethconsensus.ChainReader, _ []*ethtypes. // // TODO: Figure out if this needs to be hooked up to any part of the Cosmos SDK // handlers? -func (cc *ChainContext) VerifySeal(_ ethconsensus.ChainReader, _ *ethtypes.Header) error { +func (cc *ChainContext) VerifySeal(_ ethcons.ChainReader, _ *ethtypes.Header) error { return nil } // VerifyUncles implements Ethereum's core.ChainContext interface. It currently // performs a no-op. -func (cc *ChainContext) VerifyUncles(_ ethconsensus.ChainReader, _ *ethtypes.Block) error { +func (cc *ChainContext) VerifyUncles(_ ethcons.ChainReader, _ *ethtypes.Block) error { return nil } diff --git a/core/chain_test.go b/core/chain_test.go new file mode 100644 index 00000000..d0194352 --- /dev/null +++ b/core/chain_test.go @@ -0,0 +1,121 @@ +package core + +// NOTE: A bulk of these unit tests will change and evolve as the context and +// implementation of ChainConext evolves. + +import ( + "math/big" + "testing" + + ethcmn "github.com/ethereum/go-ethereum/common" + ethcons "github.com/ethereum/go-ethereum/consensus" + ethcore "github.com/ethereum/go-ethereum/core" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" +) + +func TestChainContextInterface(t *testing.T) { + require.Implements(t, (*ethcore.ChainContext)(nil), new(ChainContext)) + require.Implements(t, (*ethcons.Engine)(nil), new(ChainContext)) +} + +func TestNewChainContext(t *testing.T) { + cc := NewChainContext() + require.NotNil(t, cc.headersByNumber) +} + +func TestChainContextEngine(t *testing.T) { + cc := NewChainContext() + require.Equal(t, cc, cc.Engine()) +} + +func TestChainContextSetHeader(t *testing.T) { + cc := NewChainContext() + header := ðtypes.Header{ + Number: big.NewInt(64), + } + + cc.SetHeader(uint64(header.Number.Int64()), header) + require.Equal(t, header, cc.headersByNumber[uint64(header.Number.Int64())]) +} + +func TestChainContextGetHeader(t *testing.T) { + cc := NewChainContext() + header := ðtypes.Header{ + Number: big.NewInt(64), + } + + cc.SetHeader(uint64(header.Number.Int64()), header) + require.Equal(t, header, cc.GetHeader(ethcmn.Hash{}, uint64(header.Number.Int64()))) + require.Nil(t, cc.GetHeader(ethcmn.Hash{}, 0)) +} + +func TestChainContextAuthor(t *testing.T) { + cc := NewChainContext() + + cb, err := cc.Author(nil) + require.Nil(t, err) + require.Equal(t, cc.Coinbase, cb) +} + +func TestChainContextAPIs(t *testing.T) { + cc := NewChainContext() + require.Nil(t, cc.APIs(nil)) +} + +func TestChainContextCalcDifficulty(t *testing.T) { + cc := NewChainContext() + require.Nil(t, cc.CalcDifficulty(nil, 0, nil)) +} + +func TestChainContextFinalize(t *testing.T) { + cc := NewChainContext() + + block, err := cc.Finalize(nil, nil, nil, nil, nil, nil) + require.Nil(t, err) + require.Nil(t, block) +} + +func TestChainContextPrepare(t *testing.T) { + cc := NewChainContext() + + err := cc.Prepare(nil, nil) + require.Nil(t, err) +} + +func TestChainContextSeal(t *testing.T) { + cc := NewChainContext() + + block, err := cc.Seal(nil, nil, nil) + require.Nil(t, err) + require.Nil(t, block) +} + +func TestChainContextVerifyHeader(t *testing.T) { + cc := NewChainContext() + + err := cc.VerifyHeader(nil, nil, false) + require.Nil(t, err) +} + +func TestChainContextVerifyHeaders(t *testing.T) { + cc := NewChainContext() + + ch, err := cc.VerifyHeaders(nil, nil, []bool{false}) + require.Nil(t, err) + require.Nil(t, ch) +} + +func TestChainContextVerifySeal(t *testing.T) { + cc := NewChainContext() + + err := cc.VerifySeal(nil, nil) + require.Nil(t, err) +} + +func TestChainContextVerifyUncles(t *testing.T) { + cc := NewChainContext() + + err := cc.VerifyUncles(nil, nil) + require.Nil(t, err) +} diff --git a/core/ethdb_test.go b/core/ethdb_test.go new file mode 100644 index 00000000..881169b2 --- /dev/null +++ b/core/ethdb_test.go @@ -0,0 +1,147 @@ +package core + +// NOTE: A bulk of these unit tests will change and evolve as the context and +// implementation of ChainConext evolves. + +import ( + "fmt" + "testing" + + ethdb "github.com/ethereum/go-ethereum/ethdb" + "github.com/stretchr/testify/require" + dbm "github.com/tendermint/tendermint/libs/db" +) + +type ( + kvPair struct { + key, value []byte + } +) + +func newEthereumDB() *EthereumDB { + memDB := dbm.NewMemDB() + return &EthereumDB{CodeDB: memDB} +} + +func TestEthereumDBInterface(t *testing.T) { + require.Implements(t, (*ethdb.Database)(nil), new(EthereumDB)) + require.Implements(t, (*ethdb.Batch)(nil), new(EthereumDB)) +} + +func TestEthereumDBGet(t *testing.T) { + testEDB := newEthereumDB() + + testCases := []struct { + edb *EthereumDB + data *kvPair + key []byte + expectedValue []byte + }{ + { + edb: testEDB, + key: []byte("foo"), + expectedValue: nil, + }, + { + edb: testEDB, + data: &kvPair{[]byte("foo"), []byte("bar")}, + key: []byte("foo"), + expectedValue: []byte("bar"), + }, + } + + for i, tc := range testCases { + if tc.data != nil { + tc.edb.Put(tc.data.key, tc.data.value) + } + + value, err := tc.edb.Get(tc.key) + require.Nil(t, err, fmt.Sprintf("unexpected error: test case #%d", i)) + require.Equal(t, tc.expectedValue, value, fmt.Sprintf("unexpected result: test case #%d", i)) + } +} + +func TestEthereumDBHas(t *testing.T) { + testEDB := newEthereumDB() + + testCases := []struct { + edb *EthereumDB + data *kvPair + key []byte + expectedValue bool + }{ + { + edb: testEDB, + key: []byte("foo"), + expectedValue: false, + }, + { + edb: testEDB, + data: &kvPair{[]byte("foo"), []byte("bar")}, + key: []byte("foo"), + expectedValue: true, + }, + } + + for i, tc := range testCases { + if tc.data != nil { + tc.edb.Put(tc.data.key, tc.data.value) + } + + ok, err := tc.edb.Has(tc.key) + require.Nil(t, err, fmt.Sprintf("unexpected error: test case #%d", i)) + require.Equal(t, tc.expectedValue, ok, fmt.Sprintf("unexpected result: test case #%d", i)) + } +} + +func TestEthereumDBDelete(t *testing.T) { + testEDB := newEthereumDB() + + testCases := []struct { + edb *EthereumDB + data *kvPair + key []byte + }{ + { + edb: testEDB, + key: []byte("foo"), + }, + { + edb: testEDB, + data: &kvPair{[]byte("foo"), []byte("bar")}, + key: []byte("foo"), + }, + } + + for i, tc := range testCases { + if tc.data != nil { + tc.edb.Put(tc.data.key, tc.data.value) + } + + err := tc.edb.Delete(tc.key) + ok, _ := tc.edb.Has(tc.key) + require.Nil(t, err, fmt.Sprintf("unexpected error: test case #%d", i)) + require.False(t, ok, fmt.Sprintf("unexpected existence of key: test case #%d", i)) + } +} + +func TestEthereumDBNewBatch(t *testing.T) { + edb := newEthereumDB() + + batch := edb.NewBatch() + require.Equal(t, edb, batch) +} + +func TestEthereumDBValueSize(t *testing.T) { + edb := newEthereumDB() + + size := edb.ValueSize() + require.Equal(t, 0, size) +} + +func TestEthereumDBWrite(t *testing.T) { + edb := newEthereumDB() + + err := edb.Write() + require.Nil(t, err) +} diff --git a/gometalinter.json b/gometalinter.json new file mode 100644 index 00000000..124e28c1 --- /dev/null +++ b/gometalinter.json @@ -0,0 +1,9 @@ +{ + "Linters": { + "vet": "go tool vet -composites=false :PATH:LINE:MESSAGE" + }, + "Enable": ["golint", "vet", "ineffassign", "unparam", "unconvert", "misspell", "gocyclo"], + "Deadline": "500s", + "Vendor": true, + "Cyclo": 11 +} \ No newline at end of file diff --git a/state/database.go b/state/database.go index 637b3519..5e13ccc1 100644 --- a/state/database.go +++ b/state/database.go @@ -6,7 +6,7 @@ import ( "github.com/cosmos/ethermint/core" - ethcommon "github.com/ethereum/go-ethereum/common" + ethcmn "github.com/ethereum/go-ethereum/common" ethstate "github.com/ethereum/go-ethereum/core/state" ethtrie "github.com/ethereum/go-ethereum/trie" @@ -111,11 +111,9 @@ func (db *Database) LatestVersion() int64 { // // CONTRACT: The root parameter is not interpreted as a state root hash, but as // an encoding of an Cosmos SDK IAVL tree version. -func (db *Database) OpenTrie(root ethcommon.Hash) (ethstate.Trie, error) { - version := db.stateStore.LastCommitID().Version - +func (db *Database) OpenTrie(root ethcmn.Hash) (ethstate.Trie, error) { if !isRootEmpty(root) { - version = versionFromRootHash(root) + version := versionFromRootHash(root) if db.stateStore.LastCommitID().Version != version { if err := db.stateStore.LoadVersion(version); err != nil { @@ -152,7 +150,7 @@ func (db *Database) OpenTrie(root ethcommon.Hash) (ethstate.Trie, error) { // // CONTRACT: The root parameter is not interpreted as a state root hash, but as // an encoding of an IAVL tree version. -func (db *Database) OpenStorageTrie(addrHash, root ethcommon.Hash) (ethstate.Trie, error) { +func (db *Database) OpenStorageTrie(addrHash, root ethcmn.Hash) (ethstate.Trie, error) { // a contract storage trie does not need an accountCache, storageCache or // an Ethereum trie because it will not be used upon commitment. return &Trie{ @@ -175,17 +173,20 @@ func (db *Database) CopyTrie(ethstate.Trie) ethstate.Trie { // ContractCode implements Ethereum's state.Database interface. It will return // the contract byte code for a given code hash. It will not return an error. -func (db *Database) ContractCode(addrHash, codeHash ethcommon.Hash) ([]byte, error) { +func (db *Database) ContractCode(addrHash, codeHash ethcmn.Hash) ([]byte, error) { code := db.codeDB.Get(codeHash[:]) - db.codeSizeCache.Add(codeHash, len(code)) + if codeLen := len(code); codeLen != 0 { + db.codeSizeCache.Add(codeHash, codeLen) + } + return code, nil } // ContractCodeSize implements Ethereum's state.Database interface. It will // return the contract byte code size for a given code hash. It will not return // an error. -func (db *Database) ContractCodeSize(addrHash, codeHash ethcommon.Hash) (int, error) { +func (db *Database) ContractCodeSize(addrHash, codeHash ethcmn.Hash) (int, error) { if cached, ok := db.codeSizeCache.Get(codeHash); ok { return cached.(int), nil } @@ -209,6 +210,6 @@ func (db *Database) TrieDB() *ethtrie.Database { } // isRootEmpty returns true if a given root hash is empty or false otherwise. -func isRootEmpty(root ethcommon.Hash) bool { - return root == ethcommon.Hash{} +func isRootEmpty(root ethcmn.Hash) bool { + return root == ethcmn.Hash{} } diff --git a/state/database_test.go b/state/database_test.go new file mode 100644 index 00000000..906f457e --- /dev/null +++ b/state/database_test.go @@ -0,0 +1,132 @@ +package state + +import ( + "fmt" + "testing" + + ethcmn "github.com/ethereum/go-ethereum/common" + ethstate "github.com/ethereum/go-ethereum/core/state" + "github.com/stretchr/testify/require" + dbm "github.com/tendermint/tendermint/libs/db" +) + +func newDatabase() *Database { + memDB := dbm.NewMemDB() + db, _ := NewDatabase(memDB, memDB) + + return db +} + +func TestDatabaseInterface(t *testing.T) { + require.Implements(t, (*ethstate.Database)(nil), new(Database)) +} + +func TestDatabaseLatestVersion(t *testing.T) { + testDB := newDatabase() + + var version int64 + + version = testDB.LatestVersion() + require.Equal(t, int64(0), version) + + testDB.Commit() + version = testDB.LatestVersion() + require.Equal(t, int64(1), version) +} + +// func TestDatabaseOpenTrie(t *testing.T) { +// testDB := newDatabase() + +// testTrie, err := testDB.OpenTrie(rootHashFromVersion(0)) +// require.Nil(t, err) +// require.IsType(t, &Trie{}, testTrie) +// require.NotNil(t, testTrie.(*Trie).store) +// require.NotNil(t, testTrie.(*Trie).accountsCache) +// require.NotNil(t, testTrie.(*Trie).storageCache) +// require.NotNil(t, testTrie.(*Trie).ethTrieDB) +// require.False(t, testTrie.(*Trie).empty) + +// } + +func TestDatabaseCopyTrie(t *testing.T) { + // TODO: Implement once CopyTrie is implemented + t.SkipNow() +} + +func TestDatabaseContractCode(t *testing.T) { + testDB := newDatabase() + + testCases := []struct { + db *Database + data *code + codeHash ethcmn.Hash + expectedCode []byte + }{ + { + db: testDB, + codeHash: ethcmn.BytesToHash([]byte("code hash")), + expectedCode: nil, + }, + { + db: testDB, + data: &code{ethcmn.BytesToHash([]byte("code hash")), []byte("some awesome code")}, + codeHash: ethcmn.BytesToHash([]byte("code hash")), + expectedCode: []byte("some awesome code"), + }, + } + + for i, tc := range testCases { + if tc.data != nil { + tc.db.codeDB.Set(tc.data.hash[:], tc.data.blob) + } + + code, err := tc.db.ContractCode(ethcmn.Hash{}, tc.codeHash) + require.Nil(t, err, fmt.Sprintf("unexpected error: test case #%d", i)) + require.Equal(t, tc.expectedCode, code, fmt.Sprintf("unexpected result: test case #%d", i)) + } +} + +func TestDatabaseContractCodeSize(t *testing.T) { + testDB := newDatabase() + + testCases := []struct { + db *Database + data *code + codeHash ethcmn.Hash + expectedCodeLen int + }{ + { + db: testDB, + codeHash: ethcmn.BytesToHash([]byte("code hash")), + expectedCodeLen: 0, + }, + { + db: testDB, + data: &code{ethcmn.BytesToHash([]byte("code hash")), []byte("some awesome code")}, + codeHash: ethcmn.BytesToHash([]byte("code hash")), + expectedCodeLen: 17, + }, + { + db: testDB, + codeHash: ethcmn.BytesToHash([]byte("code hash")), + expectedCodeLen: 17, + }, + } + + for i, tc := range testCases { + if tc.data != nil { + tc.db.codeDB.Set(tc.data.hash[:], tc.data.blob) + } + + codeLen, err := tc.db.ContractCodeSize(ethcmn.Hash{}, tc.codeHash) + require.Nil(t, err, fmt.Sprintf("unexpected error: test case #%d", i)) + require.Equal(t, tc.expectedCodeLen, codeLen, fmt.Sprintf("unexpected result: test case #%d", i)) + } +} + +func TestDatabaseTrieDB(t *testing.T) { + testDB := newDatabase() + + db := testDB.TrieDB() + require.Equal(t, testDB.ethTrieDB, db) +} diff --git a/state/test_utils.go b/state/test_utils.go new file mode 100644 index 00000000..937d7caa --- /dev/null +++ b/state/test_utils.go @@ -0,0 +1,23 @@ +package state + +import ( + "math/rand" + "time" + + ethcmn "github.com/ethereum/go-ethereum/common" +) + +type ( + kvPair struct { + key, value []byte + } + + code struct { + hash ethcmn.Hash + blob []byte + } +) + +func init() { + rand.Seed(time.Now().UnixNano()) +} diff --git a/state/trie.go b/state/trie.go index 0574d11c..a29892ba 100644 --- a/state/trie.go +++ b/state/trie.go @@ -5,7 +5,7 @@ import ( "github.com/cosmos/cosmos-sdk/store" - ethcommon "github.com/ethereum/go-ethereum/common" + ethcmn "github.com/ethereum/go-ethereum/common" ethdb "github.com/ethereum/go-ethereum/ethdb" ethtrie "github.com/ethereum/go-ethereum/trie" ) @@ -39,7 +39,7 @@ type Trie struct { empty bool // root is the encoding of an IAVL tree root (version) - root ethcommon.Hash + root ethcmn.Hash ethTrieDB *ethtrie.Database } @@ -61,7 +61,7 @@ func (t *Trie) prefixKey(key []byte) []byte { // for key stored in the trie. The value bytes must not be modified by the // caller. func (t *Trie) TryGet(key []byte) ([]byte, error) { - if t.prefix != nil { + if t.IsStorageTrie() { key = t.prefixKey(key) } @@ -78,7 +78,7 @@ func (t *Trie) TryGet(key []byte) ([]byte, error) { func (t *Trie) TryUpdate(key, value []byte) error { t.empty = false - if t.prefix != nil { + if t.IsStorageTrie() { key = t.prefixKey(key) } @@ -93,7 +93,7 @@ func (t *Trie) TryUpdate(key, value []byte) error { // the IAVL tree. Since a CacheKVStore is used as the storage type, the keys // will be sorted giving us a deterministic ordering. func (t *Trie) TryDelete(key []byte) error { - if t.prefix != nil { + if t.IsStorageTrie() { key = t.prefixKey(key) } @@ -112,14 +112,14 @@ func (t *Trie) TryDelete(key []byte) error { // // CONTRACT: The root is an encoded IAVL tree version and each new commitment // increments the version by one. -func (t *Trie) Commit(_ ethtrie.LeafCallback) (ethcommon.Hash, error) { +func (t *Trie) Commit(_ ethtrie.LeafCallback) (ethcmn.Hash, error) { if t.empty { - return ethcommon.Hash{}, nil + return ethcmn.Hash{}, nil } newRoot := rootHashFromVersion(versionFromRootHash(t.root) + 1) - if t.prefix == nil { + if !t.IsStorageTrie() { if t.accountsCache != nil { t.accountsCache.Write() t.accountsCache = nil @@ -133,7 +133,7 @@ func (t *Trie) Commit(_ ethtrie.LeafCallback) (ethcommon.Hash, error) { // persist the mappings of codeHash => code for _, n := range t.ethTrieDB.Nodes() { if err := t.ethTrieDB.Commit(n, false); err != nil { - return ethcommon.Hash{}, err + return ethcmn.Hash{}, err } } } @@ -146,7 +146,7 @@ func (t *Trie) Commit(_ ethtrie.LeafCallback) (ethcommon.Hash, error) { // of the Trie which is an encoding of the underlying IAVL tree. // // CONTRACT: The root is an encoded IAVL tree version. -func (t *Trie) Hash() ethcommon.Hash { +func (t *Trie) Hash() ethcmn.Hash { return t.root } @@ -175,17 +175,23 @@ func (t *Trie) Prove(key []byte, fromLevel uint, proofDB ethdb.Putter) error { return nil } +// IsStorageTrie returns a boolean reflecting if the Trie is created for +// contract storage. +func (t *Trie) IsStorageTrie() bool { + return t.prefix != nil +} + // versionFromRootHash returns a Cosmos SDK IAVL version from an Ethereum state // root hash. // // CONTRACT: The encoded version is the eight MSB bytes of the root hash. -func versionFromRootHash(root ethcommon.Hash) int64 { +func versionFromRootHash(root ethcmn.Hash) int64 { return int64(binary.BigEndian.Uint64(root[:versionLen])) } // rootHashFromVersion returns a state root hash from a Cosmos SDK IAVL // version. -func rootHashFromVersion(version int64) (root ethcommon.Hash) { +func rootHashFromVersion(version int64) (root ethcmn.Hash) { binary.BigEndian.PutUint64(root[:versionLen], uint64(version)) return } diff --git a/state/trie_test.go b/state/trie_test.go new file mode 100644 index 00000000..8de2628e --- /dev/null +++ b/state/trie_test.go @@ -0,0 +1,259 @@ +package state + +import ( + "fmt" + "math/rand" + "testing" + + ethcmn "github.com/ethereum/go-ethereum/common" + ethstate "github.com/ethereum/go-ethereum/core/state" + "github.com/stretchr/testify/require" + dbm "github.com/tendermint/tendermint/libs/db" +) + +func newTestTrie() *Trie { + memDB := dbm.NewMemDB() + testDB, _ := NewDatabase(memDB, memDB) + testTrie, _ := testDB.OpenTrie(rootHashFromVersion(0)) + + return testTrie.(*Trie) +} + +func newTestPrefixTrie() *Trie { + memDB := dbm.NewMemDB() + testDB, _ := NewDatabase(memDB, memDB) + + prefix := make([]byte, ethcmn.HashLength) + rand.Read(prefix) + + testDB.OpenTrie(rootHashFromVersion(0)) + testTrie, _ := testDB.OpenStorageTrie(ethcmn.BytesToHash(prefix), rootHashFromVersion(0)) + + return testTrie.(*Trie) +} + +func TestTrieInterface(t *testing.T) { + require.Implements(t, (*ethstate.Trie)(nil), new(Trie)) +} + +func TestTrieTryGet(t *testing.T) { + testTrie := newTestTrie() + testPrefixTrie := newTestPrefixTrie() + + testCases := []struct { + trie *Trie + data *kvPair + key []byte + expectedValue []byte + }{ + { + trie: testTrie, + data: &kvPair{[]byte("foo"), []byte("bar")}, + key: []byte("foo"), + expectedValue: []byte("bar"), + }, + { + trie: testTrie, + key: []byte("baz"), + expectedValue: nil, + }, + { + trie: testPrefixTrie, + data: &kvPair{[]byte("foo"), []byte("bar")}, + key: []byte("foo"), + expectedValue: []byte("bar"), + }, + { + trie: testPrefixTrie, + key: []byte("baz"), + expectedValue: nil, + }, + } + + for i, tc := range testCases { + if tc.data != nil { + tc.trie.TryUpdate(tc.data.key, tc.data.value) + } + + value, err := tc.trie.TryGet(tc.key) + require.Nil(t, err, fmt.Sprintf("unexpected error: test case #%d", i)) + require.Equal(t, tc.expectedValue, value, fmt.Sprintf("unexpected value: test case #%d", i)) + } +} + +func TestTrieTryUpdate(t *testing.T) { + testTrie := newTestTrie() + testPrefixTrie := newTestPrefixTrie() + kv := &kvPair{[]byte("foo"), []byte("bar")} + + var err error + + err = testTrie.TryUpdate(kv.key, kv.value) + require.Nil(t, err) + + err = testPrefixTrie.TryUpdate(kv.key, kv.value) + require.Nil(t, err) +} + +func TestTrieTryDelete(t *testing.T) { + testTrie := newTestTrie() + testPrefixTrie := newTestPrefixTrie() + + testCases := []struct { + trie *Trie + data *kvPair + key []byte + }{ + { + trie: testTrie, + data: &kvPair{[]byte("foo"), []byte("bar")}, + key: []byte("foo"), + }, + { + trie: testTrie, + key: []byte("baz"), + }, + { + trie: testPrefixTrie, + data: &kvPair{[]byte("foo"), []byte("bar")}, + key: []byte("foo"), + }, + { + trie: testPrefixTrie, + key: []byte("baz"), + }, + } + + for i, tc := range testCases { + if tc.data != nil { + tc.trie.TryUpdate(tc.data.key, tc.data.value) + } + + err := tc.trie.TryDelete(tc.key) + value, _ := tc.trie.TryGet(tc.key) + require.Nil(t, err, fmt.Sprintf("unexpected error: test case #%d", i)) + require.Nil(t, value, fmt.Sprintf("unexpected value: test case #%d", i)) + } +} + +func TestTrieCommit(t *testing.T) { + testTrie := newTestTrie() + testPrefixTrie := newTestPrefixTrie() + + testCases := []struct { + trie *Trie + data *kvPair + code *code + expectedRoot ethcmn.Hash + }{ + { + trie: &Trie{empty: true}, + expectedRoot: ethcmn.Hash{}, + }, + { + trie: testTrie, + data: &kvPair{[]byte("foo"), []byte("bar")}, + expectedRoot: rootHashFromVersion(1), + }, + { + trie: testTrie, + data: &kvPair{[]byte("baz"), []byte("cat")}, + code: &code{ethcmn.BytesToHash([]byte("code hash")), []byte("code hash")}, + expectedRoot: rootHashFromVersion(2), + }, + { + trie: testTrie, + expectedRoot: rootHashFromVersion(3), + }, + { + trie: testPrefixTrie, + expectedRoot: rootHashFromVersion(0), + }, + { + trie: testPrefixTrie, + data: &kvPair{[]byte("foo"), []byte("bar")}, + expectedRoot: rootHashFromVersion(1), + }, + { + trie: testPrefixTrie, + expectedRoot: rootHashFromVersion(2), + }, + } + + for i, tc := range testCases { + if tc.data != nil { + tc.trie.TryUpdate(tc.data.key, tc.data.value) + } + if tc.code != nil { + tc.trie.ethTrieDB.Insert(tc.code.hash, tc.code.blob) + } + + root, err := tc.trie.Commit(nil) + require.Nil(t, err, fmt.Sprintf("unexpected error: test case #%d", i)) + require.Equal(t, tc.expectedRoot, root, fmt.Sprintf("unexpected root: test case #%d", i)) + } +} + +func TestTrieHash(t *testing.T) { + testTrie := newTestTrie() + testPrefixTrie := newTestPrefixTrie() + + testCases := []struct { + trie *Trie + data *kvPair + expectedRoot ethcmn.Hash + }{ + { + trie: testTrie, + expectedRoot: rootHashFromVersion(0), + }, + { + trie: testTrie, + data: &kvPair{[]byte("foo"), []byte("bar")}, + expectedRoot: rootHashFromVersion(1), + }, + { + trie: testPrefixTrie, + expectedRoot: rootHashFromVersion(0), + }, + { + trie: testPrefixTrie, + data: &kvPair{[]byte("foo"), []byte("bar")}, + expectedRoot: rootHashFromVersion(1), + }, + } + + for i, tc := range testCases { + if tc.data != nil { + tc.trie.TryUpdate(tc.data.key, tc.data.value) + tc.trie.Commit(nil) + } + + root := tc.trie.Hash() + require.Equal(t, tc.expectedRoot, root, fmt.Sprintf("unexpected root: test case #%d", i)) + } +} + +func TestTrieNodeIterator(t *testing.T) { + // TODO: Implement once NodeIterator is implemented + t.SkipNow() +} + +func TestTrieGetKey(t *testing.T) { + testTrie := newTestTrie() + testPrefixTrie := newTestPrefixTrie() + + var key []byte + expectedKey := []byte("foo") + + key = testTrie.GetKey(expectedKey) + require.Equal(t, expectedKey, key) + + key = testPrefixTrie.GetKey(expectedKey) + require.Equal(t, expectedKey, key) +} + +func TestTrieProve(t *testing.T) { + // TODO: Implement once Prove is implemented + t.SkipNow() +} diff --git a/test/importer/importer.go b/test/importer/importer.go index db08339b..700d69eb 100644 --- a/test/importer/importer.go +++ b/test/importer/importer.go @@ -40,6 +40,7 @@ type Importer struct { // Import performs an import given an Importer that has a Geth stateDB // implementation and a blockchain exported file. +// nolint func (imp *Importer) Import() { // only create genesis if it is a brand new database if imp.EthermintDB.LatestVersion() == 0 {