From fb9f6a7cdca48369404e01ec3f0adaff04e5e1a7 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Wed, 4 Jul 2018 19:38:20 -0400 Subject: [PATCH] [WIP] structure/dep refactor --- Gopkg.lock | 327 ------------------------- Gopkg.toml | 48 ++-- copied.go | 4 +- core/chain.go | 124 ++++++++++ core/ethdb.go | 65 +++++ main.go | 603 +++++++++++++--------------------------------- state/database.go | 179 ++++++++++++++ state/trie.go | 156 ++++++++++++ 8 files changed, 707 insertions(+), 799 deletions(-) delete mode 100644 Gopkg.lock create mode 100644 core/chain.go create mode 100644 core/ethdb.go create mode 100644 state/database.go create mode 100644 state/trie.go diff --git a/Gopkg.lock b/Gopkg.lock deleted file mode 100644 index 33fb3711..00000000 --- a/Gopkg.lock +++ /dev/null @@ -1,327 +0,0 @@ -# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. - - -[[projects]] - branch = "master" - name = "github.com/aristanetworks/goarista" - packages = ["monotime"] - revision = "59944ff78bc1de686b0aba1444dfd380f48f03d4" - -[[projects]] - branch = "master" - name = "github.com/btcsuite/btcd" - packages = ["btcec"] - revision = "86fed781132ac890ee03e906e4ecd5d6fa180c64" - -[[projects]] - name = "github.com/cosmos/cosmos-sdk" - packages = [ - "store", - "types", - "wire" - ] - revision = "cf46be225b7f3853e36af0feac04e29188cd4c95" - version = "v0.17.5" - -[[projects]] - name = "github.com/davecgh/go-spew" - packages = ["spew"] - revision = "346938d642f2ec3594ed81d874461961cd0faa76" - version = "v1.1.0" - -[[projects]] - branch = "master" - name = "github.com/edsrzf/mmap-go" - packages = ["."] - revision = "0bce6a6887123b67a60366d2c9fe2dfb74289d2e" - -[[projects]] - name = "github.com/ethereum/go-ethereum" - packages = [ - "common", - "common/bitutil", - "common/hexutil", - "common/math", - "common/mclock", - "consensus", - "consensus/ethash", - "consensus/misc", - "core", - "core/rawdb", - "core/state", - "core/types", - "core/vm", - "crypto", - "crypto/bn256", - "crypto/bn256/cloudflare", - "crypto/bn256/google", - "crypto/secp256k1", - "crypto/sha3", - "ethdb", - "event", - "log", - "metrics", - "p2p/netutil", - "params", - "rlp", - "rpc", - "trie" - ] - revision = "dea1ce052a10cd7d401a5c04f83f371a06fe293c" - version = "v1.8.11" - -[[projects]] - name = "github.com/go-kit/kit" - packages = [ - "log", - "log/level", - "log/term" - ] - revision = "4dc7be5d2d12881735283bcab7352178e190fc71" - version = "v0.6.0" - -[[projects]] - name = "github.com/go-logfmt/logfmt" - packages = ["."] - revision = "390ab7935ee28ec6b286364bba9b4dd6410cb3d5" - version = "v0.3.0" - -[[projects]] - name = "github.com/go-stack/stack" - packages = ["."] - revision = "259ab82a6cad3992b4e21ff5cac294ccb06474bc" - version = "v1.7.0" - -[[projects]] - name = "github.com/gogo/protobuf" - packages = [ - "gogoproto", - "jsonpb", - "proto", - "protoc-gen-gogo/descriptor", - "sortkeys", - "types" - ] - revision = "1adfc126b41513cc696b209667c8656ea7aac67c" - version = "v1.0.0" - -[[projects]] - name = "github.com/golang/protobuf" - packages = [ - "proto", - "ptypes", - "ptypes/any", - "ptypes/duration", - "ptypes/timestamp" - ] - revision = "925541529c1fa6821df4e44ce2723319eb2be768" - version = "v1.0.0" - -[[projects]] - branch = "master" - name = "github.com/golang/snappy" - packages = ["."] - revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a" - -[[projects]] - branch = "master" - name = "github.com/hashicorp/golang-lru" - packages = [ - ".", - "simplelru" - ] - revision = "0fb14efe8c47ae851c0034ed7a448854d3d34cf3" - -[[projects]] - branch = "master" - name = "github.com/jmhodges/levigo" - packages = ["."] - revision = "c42d9e0ca023e2198120196f842701bb4c55d7b9" - -[[projects]] - branch = "master" - name = "github.com/kr/logfmt" - packages = ["."] - revision = "b84e30acd515aadc4b783ad4ff83aff3299bdfe0" - -[[projects]] - name = "github.com/pkg/errors" - packages = ["."] - revision = "645ef00459ed84a119197bfb8d8205042c6df63d" - version = "v0.8.0" - -[[projects]] - name = "github.com/rs/cors" - packages = ["."] - revision = "ca016a06a5753f8ba03029c0aa5e54afb1bf713f" - version = "v1.4.0" - -[[projects]] - branch = "master" - name = "github.com/syndtr/goleveldb" - packages = [ - "leveldb", - "leveldb/cache", - "leveldb/comparer", - "leveldb/errors", - "leveldb/filter", - "leveldb/iterator", - "leveldb/journal", - "leveldb/memdb", - "leveldb/opt", - "leveldb/storage", - "leveldb/table", - "leveldb/util" - ] - revision = "e2150783cd35f5b607daca48afd8c57ec54cc995" - -[[projects]] - name = "github.com/tendermint/abci" - packages = ["types"] - revision = "78a8905690ef54f9d57e3b2b0ee7ad3a04ef3f1f" - version = "v0.10.3" - -[[projects]] - branch = "master" - name = "github.com/tendermint/ed25519" - packages = [ - ".", - "edwards25519", - "extra25519" - ] - revision = "d8387025d2b9d158cf4efb07e7ebf814bcce2057" - -[[projects]] - name = "github.com/tendermint/go-amino" - packages = ["."] - revision = "ed62928576cfcaf887209dc96142cd79cdfff389" - version = "0.9.9" - -[[projects]] - name = "github.com/tendermint/go-crypto" - packages = ["."] - revision = "915416979bf70efa4bcbf1c6cd5d64c5fff9fc19" - version = "v0.6.2" - -[[projects]] - name = "github.com/tendermint/iavl" - packages = [ - ".", - "sha256truncated" - ] - revision = "c9206995e8f948e99927f5084a88a7e94ca256da" - version = "v0.8.0-rc0" - -[[projects]] - name = "github.com/tendermint/tmlibs" - packages = [ - "common", - "db", - "log", - "merkle" - ] - revision = "692f1d86a6e2c0efa698fd1e4541b68c74ffaf38" - version = "v0.8.4" - -[[projects]] - branch = "master" - name = "golang.org/x/crypto" - packages = [ - "nacl/secretbox", - "openpgp/armor", - "openpgp/errors", - "poly1305", - "ripemd160", - "salsa20/salsa" - ] - revision = "8ac0e0d97ce45cd83d1d7243c060cb8461dda5e9" - -[[projects]] - branch = "master" - name = "golang.org/x/net" - packages = [ - "context", - "http/httpguts", - "http2", - "http2/hpack", - "idna", - "internal/timeseries", - "trace", - "websocket" - ] - revision = "db08ff08e8622530d9ed3a0e8ac279f6d4c02196" - -[[projects]] - name = "golang.org/x/text" - packages = [ - "collate", - "collate/build", - "internal/colltab", - "internal/gen", - "internal/tag", - "internal/triegen", - "internal/ucd", - "language", - "secure/bidirule", - "transform", - "unicode/bidi", - "unicode/cldr", - "unicode/norm", - "unicode/rangetable" - ] - revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" - version = "v0.3.0" - -[[projects]] - name = "google.golang.org/genproto" - packages = ["googleapis/rpc/status"] - revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200" - -[[projects]] - name = "google.golang.org/grpc" - packages = [ - ".", - "balancer", - "codes", - "connectivity", - "credentials", - "grpclb/grpc_lb_v1/messages", - "grpclog", - "internal", - "keepalive", - "metadata", - "naming", - "peer", - "resolver", - "stats", - "status", - "tap", - "transport" - ] - revision = "5b3c4e850e90a4cf6a20ebd46c8b32a0a3afcb9e" - version = "v1.7.5" - -[[projects]] - name = "gopkg.in/fatih/set.v0" - packages = ["."] - revision = "57907de300222151a123d29255ed17f5ed43fad3" - version = "v0.1.0" - -[[projects]] - branch = "v2" - name = "gopkg.in/karalabe/cookiejar.v2" - packages = ["collections/prque"] - revision = "8dcd6a7f4951f6ff3ee9cbb919a06d8925822e57" - -[[projects]] - branch = "v2" - name = "gopkg.in/natefinch/npipe.v2" - packages = ["."] - revision = "c1b8fa8bdccecb0b8db834ee0b92fdbcfa606dd6" - -[solve-meta] - analyzer-name = "dep" - analyzer-version = 1 - inputs-digest = "c70e98edb879cb6bb6dbea237d8b58d872aea54a8217f895afdcf8efdd962601" - solver-name = "gps-cdcl" - solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index ce7e025b..9f8dde8b 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -1,41 +1,23 @@ -# Gopkg.toml example -# -# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md -# for detailed Gopkg.toml documentation. -# -# required = ["github.com/user/thing/cmd/thing"] -# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] -# -# [[constraint]] -# name = "github.com/user/project" -# version = "1.0.0" -# -# [[constraint]] -# name = "github.com/user/project2" -# branch = "dev" -# source = "github.com/myfork/project2" -# -# [[override]] -# name = "github.com/x/y" -# version = "2.4.0" -# -# [prune] -# non-go = false -# go-tests = true -# unused-packages = true +[[constraint]] + name = "github.com/cosmos/cosmos-sdk" + version = "0.19.0" -[[override]] - name = "github.com/tendermint/iavl" - version = "0.8.0-rc0" +[[constraint]] + name = "github.com/ethereum/go-ethereum" + version = "1.8.11" + +[[constraint]] + name = "github.com/tendermint/tendermint" + version = "0.22.0-rc2" + +[[constraint]] + name = "github.com/golang/protobuf" + version = "~1.0.0" [[override]] name = "google.golang.org/genproto" revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200" -#[[constraint]] -# name = "github.com/cosmos/cosmos-sdk" -# version = "0.18.0" - - [prune] go-tests = true + unused-packages = true diff --git a/copied.go b/copied.go index 68134f00..4d23bae3 100644 --- a/copied.go +++ b/copied.go @@ -6,10 +6,10 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/consensus/ethash" "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/consensus/ethash" "github.com/ethereum/go-ethereum/params" ) @@ -58,7 +58,7 @@ type StructLogRes struct { Storage *map[string]string `json:"storage,omitempty"` } - // formatLogs formats EVM returned structured logs for json output +// formatLogs formats EVM returned structured logs for json output func FormatLogs(logs []vm.StructLog) []StructLogRes { formatted := make([]StructLogRes, len(logs)) for index, trace := range logs { diff --git a/core/chain.go b/core/chain.go new file mode 100644 index 00000000..924cc770 --- /dev/null +++ b/core/chain.go @@ -0,0 +1,124 @@ +package core + +import ( + "math/big" + + ethcommon "github.com/ethereum/go-ethereum/common" + ethconsensus "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" +) + +// ChainContext implements Ethereum's core.ChainContext and consensus.Engine +// interfaces. It is needed in order to apply and process Ethereum +// transactions. There should only be a single implementation in Ethermint. For +// the purposes of Ethermint, it should be support retrieving headers and +// consensus parameters from the current blockchain to be used during +// transaction processing. +// +// 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 +} + +// 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 { + return cc +} + +// GetHeader implements Ethereum's core.ChainContext interface. It currently +// performs a no-op. +// +// 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(ethcommon.Hash, uint64) *ethtypes.Header { + return nil +} + +// Author implements Ethereum's consensus.Engine interface. It is responsible +// for returned the address of the validtor to receive any fees. This function +// is only invoked if the given author in the ApplyTransaction call is nil. +// +// 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) { + return cc.coinbase, nil +} + +// APIs implements Ethereum's core.ChainContext interface. It currently +// performs a no-op. +// +// 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 { + 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 { + return nil +} + +// Finalize implements Ethereum's core.ChainContext interface. It currently +// performs a no-op. +// +// 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, + _ []*ethtypes.Transaction, _ []*ethtypes.Header, _ []*ethtypes.Receipt, +) (*ethtypes.Block, error) { + return nil, nil +} + +// Prepare implements Ethereum's core.ChainContext interface. It currently +// 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 { + return nil +} + +// Seal implements Ethereum's core.ChainContext interface. It currently +// 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) { + return nil, nil +} + +// VerifyHeader implements Ethereum's core.ChainContext interface. It currently +// performs a no-op. +// +// 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 { + return nil +} + +// VerifyHeaders implements Ethereum's core.ChainContext interface. It +// currently performs a no-op. +// +// 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) { + return nil, nil +} + +// VerifySeal implements Ethereum's core.ChainContext interface. It currently +// performs a no-op. +// +// 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 { + return nil +} + +// VerifyUncles implements Ethereum's core.ChainContext interface. It currently +// performs a no-op. +func (cc *ChainContext) VerifyUncles(_ ethconsensus.ChainReader, _ *ethtypes.Block) error { + return nil +} diff --git a/core/ethdb.go b/core/ethdb.go new file mode 100644 index 00000000..80a17982 --- /dev/null +++ b/core/ethdb.go @@ -0,0 +1,65 @@ +package core + +import ( + ethdb "github.com/ethereum/go-ethereum/ethdb" + dbm "github.com/tendermint/tendermint/libs/db" +) + +// EthereumDB implements Ethereum's ethdb.Database and ethdb.Batch interfaces. +// It will be used to facilitate persistence of codeHash => code mappings. +type EthereumDB struct { + codeDB dbm.DB +} + +// Put implements Ethereum's ethdb.Putter interface. It wraps the database +// write operation supported by both batches and regular databases. +func (edb *EthereumDB) Put(key []byte, value []byte) error { + edb.codeDB.Set(key, value) + return nil +} + +// Get implements Ethereum's ethdb.Database interface. It returns a value for a +// given key. +func (edb *EthereumDB) Get(key []byte) ([]byte, error) { + return edb.codeDB.Get(key), nil +} + +// Has implements Ethereum's ethdb.Database interface. It returns a boolean +// determining if the underlying database has the given key or not. +func (edb *EthereumDB) Has(key []byte) (bool, error) { + return edb.codeDB.Has(key), nil +} + +// Delete implements Ethereum's ethdb.Database interface. It removes a given +// key from the underlying database. +func (edb *EthereumDB) Delete(key []byte) error { + edb.codeDB.Delete(key) + return nil +} + +// Close implements Ethereum's ethdb.Database interface. It closes the +// underlying database. +func (edb *EthereumDB) Close() { + edb.codeDB.Close() +} + +// NewBatch implements Ethereum's ethdb.Database interface. It returns a new +// Batch object used for batch database operations. +func (edb *EthereumDB) NewBatch() ethdb.Batch { + return edb +} + +// ValueSize implements Ethereum's ethdb.Database interface. It performs a +// no-op. +func (edb *EthereumDB) ValueSize() int { + return 0 +} + +// Write implements Ethereum's ethdb.Database interface. It performs a no-op. +func (edb *EthereumDB) Write() error { + return nil +} + +// Reset implements Ethereum's ethdb.Database interface. It performs a no-op. +func (edb *EthereumDB) Reset() { +} diff --git a/main.go b/main.go index 256722ac..02c34f1b 100644 --- a/main.go +++ b/main.go @@ -1,443 +1,172 @@ +// The implementation below is to be considered highly unstable and a continual +// WIP. It is a means to replicate and test replaying Ethereum transactions +// using the Cosmos SDK and the EVM. The ultimate result will be what is known +// as Ethermint. package main -import ( - "bytes" - "encoding/binary" - "encoding/json" - "fmt" - "io" - "math/big" - "os" +// eth_common "github.com/ethereum/go-ethereum/common" +// eth_misc "github.com/ethereum/go-ethereum/consensus/misc" +// eth_core "github.com/ethereum/go-ethereum/core" +// eth_state "github.com/ethereum/go-ethereum/core/state" +// eth_types "github.com/ethereum/go-ethereum/core/types" +// eth_vm "github.com/ethereum/go-ethereum/core/vm" +// eth_params "github.com/ethereum/go-ethereum/params" +// eth_rlp "github.com/ethereum/go-ethereum/rlp" +// dbm "github.com/tendermint/tendermint/libs/db" - eth_common "github.com/ethereum/go-ethereum/common" - eth_core "github.com/ethereum/go-ethereum/core" - eth_state "github.com/ethereum/go-ethereum/core/state" - eth_types "github.com/ethereum/go-ethereum/core/types" - eth_vm "github.com/ethereum/go-ethereum/core/vm" - eth_rlp "github.com/ethereum/go-ethereum/rlp" - eth_ethdb "github.com/ethereum/go-ethereum/ethdb" - eth_params "github.com/ethereum/go-ethereum/params" - eth_rpc "github.com/ethereum/go-ethereum/rpc" - eth_trie "github.com/ethereum/go-ethereum/trie" - eth_consensus "github.com/ethereum/go-ethereum/consensus" - eth_misc "github.com/ethereum/go-ethereum/consensus/misc" - - dbm "github.com/tendermint/tmlibs/db" - "github.com/cosmos/cosmos-sdk/store" - "github.com/cosmos/cosmos-sdk/types" -) - -var ( - // Key for the sub-store with Ethereum accounts - AccountsKey = types.NewKVStoreKey("account") - // Key for the sub-store with storage data of Ethereum contracts - StorageKey = types.NewKVStoreKey("storage") - // Key for the sub-store with the code for contracts - CodeKey = types.NewKVStoreKey("code") -) - -type CommitHashPreimage struct { - VersionId int64 - Prefix []byte -} - -var miner501 = eth_common.HexToAddress("0x35e8e5dC5FBd97c5b421A80B596C030a2Be2A04D") - -// Implementation of eth_state.Database -type OurDatabase struct { - stateStore store.CommitMultiStore // For the history of accounts - // Also, for the history of contract data (effects of SSTORE instruction) - accountsCache store.CacheKVStore - storageCache store.CacheKVStore - codeDb dbm.DB // Mapping [codeHash] -> - tracing bool - trieDbDummy *eth_trie.Database -} - -func OurNewDatabase(stateDb, codeDb dbm.DB) (*OurDatabase, error) { - od := &OurDatabase{} - od.stateStore = store.NewCommitMultiStore(stateDb) - od.stateStore.MountStoreWithDB(AccountsKey, types.StoreTypeIAVL, nil) - od.stateStore.MountStoreWithDB(StorageKey, types.StoreTypeIAVL, nil) - if err := od.stateStore.LoadLatestVersion(); err != nil { - return nil, err - } - od.codeDb = codeDb - od.trieDbDummy = eth_trie.NewDatabase(&OurEthDb{codeDb: codeDb}) - return od, nil -} - -// root is not interpreted as a hash, but as an encoding of version -func (od *OurDatabase) OpenTrie(root eth_common.Hash) (eth_state.Trie, error) { - // Look up version id to use - hasData := root != (eth_common.Hash{}) - versionId := od.stateStore.LastCommitID().Version - if hasData { - // First 8 bytes encode version - versionId = int64(binary.BigEndian.Uint64(root[:8])) - if od.stateStore.LastCommitID().Version != versionId { - if err := od.stateStore.LoadVersion(versionId); err != nil { - return nil, err - } - od.accountsCache = nil - } - } - if od.accountsCache == nil { - od.accountsCache = store.NewCacheKVStore(od.stateStore.GetCommitKVStore(AccountsKey)) - od.storageCache = store.NewCacheKVStore(od.stateStore.GetCommitKVStore(StorageKey)) - } - return &OurTrie{od: od, st: od.accountsCache, prefix: nil, hasData: hasData}, nil -} - -func (od *OurDatabase) OpenStorageTrie(addrHash, root eth_common.Hash) (eth_state.Trie, error) { - hasData := root != (eth_common.Hash{}) - return &OurTrie{od:od, st: od.storageCache, prefix: addrHash[:], hasData: hasData}, nil -} - -func (od *OurDatabase) CopyTrie(eth_state.Trie) eth_state.Trie { - return nil -} - -func (od *OurDatabase) ContractCode(addrHash, codeHash eth_common.Hash) ([]byte, error) { - code := od.codeDb.Get(codeHash[:]) - return code, nil -} - -func (od *OurDatabase) ContractCodeSize(addrHash, codeHash eth_common.Hash) (int, error) { - code := od.codeDb.Get(codeHash[:]) - return len(code), nil -} - -func (od *OurDatabase) TrieDB() *eth_trie.Database { - return od.trieDbDummy -} - -// Implementation of state.Trie from go-ethereum -type OurTrie struct { - od *OurDatabase - // This is essentially part of the KVStore for a specific prefix - st store.KVStore - prefix []byte - hasData bool -} - -func (ot *OurTrie) makePrefix(key []byte) []byte { - kk := make([]byte, len(ot.prefix)+len(key)) - copy(kk, ot.prefix) - copy(kk[len(ot.prefix):], key) - return kk -} - -func (ot *OurTrie) TryGet(key []byte) ([]byte, error) { - if ot.prefix == nil { - return ot.st.Get(key), nil - } - return ot.st.Get(ot.makePrefix(key)), nil -} - -func (ot *OurTrie) TryUpdate(key, value []byte) error { - ot.hasData = true - if ot.prefix == nil { - ot.st.Set(key, value) - return nil - } - ot.st.Set(ot.makePrefix(key), value) - return nil -} - -func (ot *OurTrie) TryDelete(key []byte) error { - if ot.prefix == nil { - ot.st.Delete(key) - return nil - } - ot.st.Delete(ot.makePrefix(key)) - return nil -} - -func (ot *OurTrie) Commit(onleaf eth_trie.LeafCallback) (eth_common.Hash, error) { - if !ot.hasData { - return eth_common.Hash{}, nil - } - var commitHash eth_common.Hash - // We assume here that the next committed version will be od.stateStore.LastCommitID().Version+1 - binary.BigEndian.PutUint64(commitHash[:8], uint64(ot.od.stateStore.LastCommitID().Version+1)) - if ot.prefix == nil { - if ot.od.accountsCache != nil { - ot.od.accountsCache.Write() - ot.od.accountsCache = nil - } - if ot.od.storageCache != nil { - ot.od.storageCache.Write() - ot.od.storageCache = nil - } - // Enumerate cached nodes from trie.Database - for _, n := range ot.od.trieDbDummy.Nodes() { - if err := ot.od.trieDbDummy.Commit(n, false); err != nil { - return eth_common.Hash{}, err - } - } - } - return commitHash, nil -} - -func (ot *OurTrie) Hash() eth_common.Hash { - return eth_common.Hash{} -} - -func (ot *OurTrie) NodeIterator(startKey []byte) eth_trie.NodeIterator { - return nil -} - -func (ot *OurTrie) GetKey([]byte) []byte { - return nil -} - -func (ot *OurTrie) Prove(key []byte, fromLevel uint, proofDb eth_ethdb.Putter) error { - return nil -} - -// Dummy implementation of core.ChainContext and consensus Engine from go-ethereum -type OurChainContext struct { - coinbase eth_common.Address // This is where the transaction fees will go -} - -func (occ *OurChainContext) Engine() eth_consensus.Engine { - return occ -} - -func (occ *OurChainContext) GetHeader(eth_common.Hash, uint64) *eth_types.Header { - return nil -} - -func (occ *OurChainContext) Author(header *eth_types.Header) (eth_common.Address, error) { - return occ.coinbase, nil -} - -func (occ *OurChainContext) APIs(chain eth_consensus.ChainReader) []eth_rpc.API { - return nil -} - -func (occ *OurChainContext) CalcDifficulty(chain eth_consensus.ChainReader, time uint64, parent *eth_types.Header) *big.Int { - return nil -} - -func (occ *OurChainContext) Finalize(chain eth_consensus.ChainReader, header *eth_types.Header, state *eth_state.StateDB, txs []*eth_types.Transaction, - uncles []*eth_types.Header, receipts []*eth_types.Receipt) (*eth_types.Block, error) { - return nil, nil -} - -func (occ *OurChainContext) Prepare(chain eth_consensus.ChainReader, header *eth_types.Header) error { - return nil -} - -func (occ *OurChainContext) Seal(chain eth_consensus.ChainReader, block *eth_types.Block, stop <-chan struct{}) (*eth_types.Block, error) { - return nil, nil -} - -func (occ *OurChainContext) VerifyHeader(chain eth_consensus.ChainReader, header *eth_types.Header, seal bool) error { - return nil -} - -func (occ *OurChainContext) VerifyHeaders(chain eth_consensus.ChainReader, headers []*eth_types.Header, seals []bool) (chan<- struct{}, <-chan error) { - return nil, nil -} - -func (occ *OurChainContext) VerifySeal(chain eth_consensus.ChainReader, header *eth_types.Header) error { - return nil -} - -func (occ *OurChainContext) VerifyUncles(chain eth_consensus.ChainReader, block *eth_types.Block) error { - return nil -} - -// Implementation of ethdb.Database and ethdb.Batch from go-ethereum -type OurEthDb struct { - codeDb dbm.DB -} - -func (oedb *OurEthDb) Put(key []byte, value []byte) error { - oedb.codeDb.Set(key, value) - return nil -} - -func (oedb *OurEthDb) Get(key []byte) ([]byte, error) { - return nil, nil -} - -func (oedb *OurEthDb) Has(key []byte) (bool, error) { - return false, nil -} - -func (oedb *OurEthDb) Delete(key []byte) error { - return nil -} - -func (oedb *OurEthDb) Close() { -} - -func (oedb *OurEthDb) NewBatch() eth_ethdb.Batch { - return oedb -} - -func (oedb *OurEthDb) ValueSize() int { - return 0 -} - -func (oedb *OurEthDb) Write() error { - return nil -} - -func (oedb *OurEthDb) Reset() { -} +// var ( +// // TODO: Document... +// miner501 = eth_common.HexToAddress("0x35e8e5dC5FBd97c5b421A80B596C030a2Be2A04D") +// ) func main() { - stateDb := dbm.NewDB("state" /* name */, dbm.MemDBBackend, "" /* dir */) - codeDb := dbm.NewDB("code" /* name */, dbm.MemDBBackend, "" /* dir */) - d, err := OurNewDatabase(stateDb, codeDb) - if err != nil { - panic(err) - } - fmt.Printf("Instantiating state.StateDB\n") - // With empty root hash, i.e. empty state - statedb, err := eth_state.New(eth_common.Hash{}, d) - if err != nil { - panic(err) - } - g := eth_core.DefaultGenesisBlock() - for addr, account := range g.Alloc { - statedb.AddBalance(addr, account.Balance) - statedb.SetCode(addr, account.Code) - statedb.SetNonce(addr, account.Nonce) - for key, value := range account.Storage { - statedb.SetState(addr, key, value) - } - } + // stateDb := dbm.NewDB("state" /* name */, dbm.MemDBBackend, "" /* dir */) + // codeDB := dbm.NewDB("code" /* name */, dbm.MemDBBackend, "" /* dir */) + // d, err := OurNewDatabase(stateDb, codeDB) + // if err != nil { + // panic(err) + // } + // fmt.Printf("Instantiating state.StateDB\n") + // // With empty root hash, i.e. empty state + // statedb, err := eth_state.New(eth_common.Hash{}, d) + // if err != nil { + // panic(err) + // } + // g := eth_core.DefaultGenesisBlock() + // for addr, account := range g.Alloc { + // statedb.AddBalance(addr, account.Balance) + // statedb.SetCode(addr, account.Code) + // statedb.SetNonce(addr, account.Nonce) + // for key, value := range account.Storage { + // statedb.SetState(addr, key, value) + // } + // } - // One of the genesis account having 200 ETH - b := statedb.GetBalance(eth_common.HexToAddress("0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0")) - fmt.Printf("Balance: %s\n", b) - genesis_root, err := statedb.Commit(false /* deleteEmptyObjects */) - if err != nil { - panic(err) - } - commitID := d.stateStore.Commit() - fmt.Printf("CommitID after genesis: %v\n", commitID) - fmt.Printf("Genesis state root hash: %x\n", genesis_root[:]) - // File with blockchain data exported from geth by using "geth expordb" command - input, err := os.Open("/Users/alexeyakhunov/mygit/blockchain") - if err != nil { - panic(err) - } - defer input.Close() - // Ethereum mainnet config - chainConfig := eth_params.MainnetChainConfig - stream := eth_rlp.NewStream(input, 0) - var block eth_types.Block - n := 0 - var root500 eth_common.Hash // Root hash after block 500 - var root501 eth_common.Hash // Root hash after block 501 - prev_root := genesis_root - d.tracing = true - chainContext := &OurChainContext{} - vmConfig := eth_vm.Config{} - for { - if err = stream.Decode(&block); err == io.EOF { - err = nil // Clear it - break - } else if err != nil { - panic(fmt.Errorf("at block %d: %v", block.NumberU64(), err)) - } - // don't import first block - if block.NumberU64() == 0 { - continue - } - header := block.Header() - chainContext.coinbase = header.Coinbase - statedb, err := eth_state.New(prev_root, d) - if err != nil { - panic(fmt.Errorf("at block %d: %v", block.NumberU64(), err)) - } - var ( - receipts eth_types.Receipts - usedGas = new(uint64) - allLogs []*eth_types.Log - gp = new(eth_core.GasPool).AddGas(block.GasLimit()) - ) - if chainConfig.DAOForkSupport && chainConfig.DAOForkBlock != nil && chainConfig.DAOForkBlock.Cmp(block.Number()) == 0 { - eth_misc.ApplyDAOHardFork(statedb) - } - for i, tx := range block.Transactions() { - statedb.Prepare(tx.Hash(), block.Hash(), i) - var h eth_common.Hash = tx.Hash() - if bytes.Equal(h[:], eth_common.FromHex("0xc438cfcc3b74a28741bda361032f1c6362c34aa0e1cedff693f31ec7d6a12717")) { - vmConfig.Tracer = eth_vm.NewStructLogger(ð_vm.LogConfig{}) - vmConfig.Debug = true - } - receipt, _, err := eth_core.ApplyTransaction(chainConfig, chainContext, nil, gp, statedb, header, tx, usedGas, vmConfig) - if vmConfig.Tracer != nil { - w, err := os.Create("structlogs.txt") - if err != nil { - panic(err) - } - encoder := json.NewEncoder(w) - logs := FormatLogs(vmConfig.Tracer.(*eth_vm.StructLogger).StructLogs()) - if err := encoder.Encode(logs); err != nil { - panic(err) - } - if err := w.Close(); err != nil { - panic(err) - } - vmConfig.Debug = false - vmConfig.Tracer = nil - } - if err != nil { - panic(fmt.Errorf("at block %d, tx %x: %v", block.NumberU64(), tx.Hash(), err)) - } - receipts = append(receipts, receipt) - allLogs = append(allLogs, receipt.Logs...) - } - // Apply mining rewards to the statedb - accumulateRewards(chainConfig, statedb, header, block.Uncles()) - // Commit block - prev_root, err = statedb.Commit(chainConfig.IsEIP158(block.Number()) /* deleteEmptyObjects */) - if err != nil { - panic(fmt.Errorf("at block %d: %v", block.NumberU64(), err)) - } - //fmt.Printf("State root after block %d: %x\n", block.NumberU64(), prev_root) - d.stateStore.Commit() - //fmt.Printf("CommitID after block %d: %v\n", block.NumberU64(), commitID) - switch block.NumberU64() { - case 500: - root500 = prev_root - case 501: - root501 = prev_root - } - n++ - if n % 10000 == 0 { - fmt.Printf("Processed %d blocks\n", n) - } - if n >= 100000 { - break - } - } - fmt.Printf("Processed %d blocks\n", n) - d.tracing = true - genesis_state, err := eth_state.New(genesis_root, d) - fmt.Printf("Balance of one of the genesis investors: %s\n", genesis_state.GetBalance(eth_common.HexToAddress("0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0"))) - //miner501 := eth_common.HexToAddress("0x35e8e5dC5FBd97c5b421A80B596C030a2Be2A04D") // Miner of the block 501 - // Try to create a new statedb from root of the block 500 - fmt.Printf("root500: %x\n", root500[:]) - state500, err := eth_state.New(root500, d) - if err != nil { - panic(err) - } - miner501_balance_at_500 := state500.GetBalance(miner501) - state501, err := eth_state.New(root501, d) - if err != nil { - panic(err) - } - miner501_balance_at_501 := state501.GetBalance(miner501) - fmt.Printf("Investor's balance after block 500: %d\n", state500.GetBalance(eth_common.HexToAddress("0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0"))) - fmt.Printf("Miner of block 501's balance after block 500: %d\n", miner501_balance_at_500) - fmt.Printf("Miner of block 501's balance after block 501: %d\n", miner501_balance_at_501) -} \ No newline at end of file + // // One of the genesis account having 200 ETH + // b := statedb.GetBalance(eth_common.HexToAddress("0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0")) + // fmt.Printf("Balance: %s\n", b) + // genesis_root, err := statedb.Commit(false /* deleteEmptyObjects */) + // if err != nil { + // panic(err) + // } + // commitID := d.stateStore.Commit() + // fmt.Printf("CommitID after genesis: %v\n", commitID) + // fmt.Printf("Genesis state root hash: %x\n", genesis_root[:]) + // // File with blockchain data exported from geth by using "geth expordb" command + // input, err := os.Open("/Users/alexeyakhunov/mygit/blockchain") + // if err != nil { + // panic(err) + // } + // defer input.Close() + // // Ethereum mainnet config + // chainConfig := eth_params.MainnetChainConfig + // stream := eth_rlp.NewStream(input, 0) + // var block eth_types.Block + // n := 0 + // var root500 eth_common.Hash // Root hash after block 500 + // var root501 eth_common.Hash // Root hash after block 501 + // prev_root := genesis_root + // d.tracing = true + // chainContext := &OurChainContext{} + // vmConfig := eth_vm.Config{} + // for { + // if err = stream.Decode(&block); err == io.EOF { + // err = nil // Clear it + // break + // } else if err != nil { + // panic(fmt.Errorf("at block %d: %v", block.NumberU64(), err)) + // } + // // don't import first block + // if block.NumberU64() == 0 { + // continue + // } + // header := block.Header() + // chainContext.coinbase = header.Coinbase + // statedb, err := eth_state.New(prev_root, d) + // if err != nil { + // panic(fmt.Errorf("at block %d: %v", block.NumberU64(), err)) + // } + // var ( + // receipts eth_types.Receipts + // usedGas = new(uint64) + // allLogs []*eth_types.Log + // gp = new(eth_core.GasPool).AddGas(block.GasLimit()) + // ) + // if chainConfig.DAOForkSupport && chainConfig.DAOForkBlock != nil && chainConfig.DAOForkBlock.Cmp(block.Number()) == 0 { + // eth_misc.ApplyDAOHardFork(statedb) + // } + // for i, tx := range block.Transactions() { + // statedb.Prepare(tx.Hash(), block.Hash(), i) + // var h eth_common.Hash = tx.Hash() + // if bytes.Equal(h[:], eth_common.FromHex("0xc438cfcc3b74a28741bda361032f1c6362c34aa0e1cedff693f31ec7d6a12717")) { + // vmConfig.Tracer = eth_vm.NewStructLogger(ð_vm.LogConfig{}) + // vmConfig.Debug = true + // } + // receipt, _, err := eth_core.ApplyTransaction(chainConfig, chainContext, nil, gp, statedb, header, tx, usedGas, vmConfig) + // if vmConfig.Tracer != nil { + // w, err := os.Create("structlogs.txt") + // if err != nil { + // panic(err) + // } + // encoder := json.NewEncoder(w) + // logs := FormatLogs(vmConfig.Tracer.(*eth_vm.StructLogger).StructLogs()) + // if err := encoder.Encode(logs); err != nil { + // panic(err) + // } + // if err := w.Close(); err != nil { + // panic(err) + // } + // vmConfig.Debug = false + // vmConfig.Tracer = nil + // } + // if err != nil { + // panic(fmt.Errorf("at block %d, tx %x: %v", block.NumberU64(), tx.Hash(), err)) + // } + // receipts = append(receipts, receipt) + // allLogs = append(allLogs, receipt.Logs...) + // } + // // Apply mining rewards to the statedb + // accumulateRewards(chainConfig, statedb, header, block.Uncles()) + // // Commit block + // prev_root, err = statedb.Commit(chainConfig.IsEIP158(block.Number()) /* deleteEmptyObjects */) + // if err != nil { + // panic(fmt.Errorf("at block %d: %v", block.NumberU64(), err)) + // } + // //fmt.Printf("State root after block %d: %x\n", block.NumberU64(), prev_root) + // d.stateStore.Commit() + // //fmt.Printf("CommitID after block %d: %v\n", block.NumberU64(), commitID) + // switch block.NumberU64() { + // case 500: + // root500 = prev_root + // case 501: + // root501 = prev_root + // } + // n++ + // if n%10000 == 0 { + // fmt.Printf("Processed %d blocks\n", n) + // } + // if n >= 100000 { + // break + // } + // } + // fmt.Printf("Processed %d blocks\n", n) + // d.tracing = true + // genesis_state, err := eth_state.New(genesis_root, d) + // fmt.Printf("Balance of one of the genesis investors: %s\n", genesis_state.GetBalance(eth_common.HexToAddress("0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0"))) + // //miner501 := eth_common.HexToAddress("0x35e8e5dC5FBd97c5b421A80B596C030a2Be2A04D") // Miner of the block 501 + // // Try to create a new statedb from root of the block 500 + // fmt.Printf("root500: %x\n", root500[:]) + // state500, err := eth_state.New(root500, d) + // if err != nil { + // panic(err) + // } + // miner501_balance_at_500 := state500.GetBalance(miner501) + // state501, err := eth_state.New(root501, d) + // if err != nil { + // panic(err) + // } + // miner501_balance_at_501 := state501.GetBalance(miner501) + // fmt.Printf("Investor's balance after block 500: %d\n", state500.GetBalance(eth_common.HexToAddress("0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0"))) + // fmt.Printf("Miner of block 501's balance after block 500: %d\n", miner501_balance_at_500) + // fmt.Printf("Miner of block 501's balance after block 501: %d\n", miner501_balance_at_501) +} diff --git a/state/database.go b/state/database.go new file mode 100644 index 00000000..57ab780f --- /dev/null +++ b/state/database.go @@ -0,0 +1,179 @@ +package state + +import ( + "encoding/binary" + + "github.com/cosmos/cosmos-sdk/store" + "github.com/cosmos/cosmos-sdk/types" + ethcommon "github.com/ethereum/go-ethereum/common" + ethstate "github.com/ethereum/go-ethereum/core/state" + ethtrie "github.com/ethereum/go-ethereum/trie" + "github.com/ledgerwatch/ethermint/core" + dbm "github.com/tendermint/tendermint/libs/db" +) + +var ( + // AccountsKey is the key used for storing Ethereum accounts in the Cosmos + // SDK multi-store. + AccountsKey = types.NewKVStoreKey("account") + + // StorageKey is the key used for storing Ethereum contract storage in the + // Cosmos SDK multi-store. + StorageKey = types.NewKVStoreKey("storage") + + // CodeKey is the key used for storing Ethereum contract code in the Cosmos + // SDK multi-store. + CodeKey = types.NewKVStoreKey("code") +) + +// Database implements the Ethereum state.Database interface. +type Database struct { + // stateStore will be used for the history of accounts (balance, nonce, + // storage root hash, code hash) and for the history of contract data + // (effects of SSTORE instruction). + stateStore store.CommitMultiStore + accountsCache store.CacheKVStore + storageCache store.CacheKVStore + + // codeDB is a mapping of codeHash => code + // + // NOTE: This database will store the information in memory until is it + // committed, using the function Commit. This function is called outside of + // the ApplyTransaction function, therefore in Ethermint we need to make + // sure this commit is invoked somewhere after each block or whatever the + // appropriate time for it. + codeDB dbm.DB + ethTrieDB *ethtrie.Database + + // TODO: Do we need this? + tracing bool +} + +// NewDatabase returns a reference to an initialized Database type which +// implements Ethereum's state.Database interface. An error is returned if the +// latest state failed to load. The underlying storage structure is defined by +// the Cosmos SDK IAVL tree. +func NewDatabase(stateDB, codeDB dbm.DB) (*Database, error) { + // Initialize an implementation of Ethereum state.Database and create a + // Cosmos SDK multi-store. + db := &Database{ + stateStore: store.NewCommitMultiStore(stateDB), + } + + // Create the underlying multi-store stores that will persist account and + // account storage data. + db.stateStore.MountStoreWithDB(AccountsKey, types.StoreTypeIAVL, nil) + db.stateStore.MountStoreWithDB(StorageKey, types.StoreTypeIAVL, nil) + + // Load the latest account state from the Cosmos SDK multi-store. + if err := db.stateStore.LoadLatestVersion(); err != nil { + return nil, err + } + + // Set the persistent Cosmos SDK Database and initialize an Ethereum + // trie.Database using an EthereumDB as the underlying implementation of + // the ethdb.Database interface. It will be used to facilitate persistence + // of contract byte code when committing state. + db.codeDB = codeDB + db.ethTrieDB = ethtrie.NewDatabase(&core.EthereumDB{codeDB: codeDB}) + + return db, nil +} + +// OpenTrie implements Ethereum's state.Database interface. It returns a Trie +// type which implements the Ethereum state.Trie interface. It us used for +// storage of accounts. An error is returned if state cannot load for a +// given version. The account cache is reset if the state is successfully +// loaded and the version is not the latest. +// +// 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 := d.stateStore.LastCommitID().Version + + if !isRootEmpty(root) { + version = versionFromRootHash(root) + + if db.stateStore.LastCommitID().Version != version { + if err := db.stateStore.LoadVersion(version); err != nil { + return nil, err + } + + d.accountsCache = nil + } + } + + // reset the cache if the state was loaded for an older version ID + if db.accountsCache == nil { + d.accountsCache = store.NewCacheKVStore(d.stateStore.GetCommitKVStore(AccountsKey)) + d.storageCache = store.NewCacheKVStore(d.stateStore.GetCommitKVStore(StorageKey)) + } + + return &Trie{ + od: db, + st: od.accountsCache, + prefix: nil, + empty: isRootEmpty(root), + }, nil +} + +// OpenStorageTrie implements Ethereum's state.Database interface. It returns +// a Trie type which implements the Ethereum state.Trie interface. It is used +// for storage of accounts storage (state). +// +// NOTE: It is assumed that the account state has already been loaded via +// OpenTrie. +// +// 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) { + return &Trie{ + od: d, + st: d.storageCache, + prefix: addrHash.Bytes(), + empty: isRootEmpty(root), + }, nil +} + +// CopyTrie implements Ethereum's state.Database interface. For now, it +// performs a no-op as the underlying Cosmos SDK IAVL tree does not support +// such an operation. +// +// TODO: Does the IAVL tree need to support this operation? If so, why and +// how? +func (db *Database) CopyTrie(ethstate.Trie) ethstate.Trie { + return nil +} + +// 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) { + return d.codeDB.Get(codeHash[:]), 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) { + return len(d.codeDB.Get(codeHash[:])), nil +} + +// TrieDB implements Ethereum's state.Database interface. It returns Ethereum's +// trie.Database low level trie database used for data storage. In the context +// of Ethermint, it'll be used to solely store mappings of codeHash => code. +func (db *Database) TrieDB() *ethtrie.Database { + return d.ethTrieDB +} + +// isRootEmpty returns true if a given root hash is empty or false otherwise. +func isRootEmpty(root ethcommon.Hash) bool { + return root == ethcommon.Hash{} +} + +// versionFromRootHash returns an 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 { + return int64(binary.BigEndian.Uint64(root[:8])) +} diff --git a/state/trie.go b/state/trie.go new file mode 100644 index 00000000..306523cd --- /dev/null +++ b/state/trie.go @@ -0,0 +1,156 @@ +package state + +import ( + "encoding/binary" + + "github.com/cosmos/cosmos-sdk/store" + ethcommon "github.com/ethereum/go-ethereum/common" + ethdb "github.com/ethereum/go-ethereum/ethdb" + ethtrie "github.com/ethereum/go-ethereum/trie" +) + +// Trie implements the Ethereum state.Trie interface. +type Trie struct { + // // db is an implementation of Ethereum's state.Database. It will provide a + // // means to persist accounts and contract storage to a persistent + // // multi-store. + // db *Database + + // Store is an IAVL KV store that is part of a larger store except it used + // for a specific prefix. + store store.KVStore + + // prefix is a static prefix used for persistence operations where the + // storage data is a contract state. This is to prevent key collisions + // since the IAVL tree is used for all contract state. + prefix []byte + + // empty reflects if there exists any data in the tree + empty bool + + // root is the encoding of an IAVL tree root (version) + root ethcommon.Hash +} + +// prefixKey returns a composite key composed of a static prefix and a given +// key. This is used in situations where the storage data is contract state and +// the underlying structure to store said state is a single IAVL tree. To +// prevent collision, a static prefix is used. +func (t *Trie) prefixKey(key []byte) []byte { + compositeKey := make([]byte, len(t.prefix)+len(key)) + + copy(compositeKey, t.prefix) + copy(compositeKey[len(t.prefix):], key) + + return compositeKey +} + +// TryGet implements the Ethereum state.Trie interface. It returns the value +// 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 { + key = t.prefixKey(key) + } + + return t.store.Get(key), nil +} + +// TryUpdate implements the Ethereum state.Trie interface. It associates a +// given key with a value in the trie. Subsequent calls to Get will return a +// value. It also marks the tree as not empty. +// +// CONTRACT: The order of insertions must be deterministic due to the nature of +// 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) TryUpdate(key, value []byte) error { + t.empty = false + + if t.prefix != nil { + key = t.prefixKey(key) + } + + t.store.Set(key, value) + return nil +} + +// TryDelete implements the Ethereum state.Trie interface. It removes any +// existing value for a given key from the trie. +// +// CONTRACT: The order of deletions must be deterministic due to the nature of +// 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 { + key = t.makePrefix(key) + } + + t.store.Delete(key) + return nil +} + +// Commit implements the Ethereum state.Trie interface. TODO: ... +// +// CONTRACT: The root is an encoded IAVL tree version. +func (t *Trie) Commit(_ ethtrie.LeafCallback) (ethcommon.Hash, error) { + if t.empty { + return ethcommon.Hash{}, nil + } + + var root ethcommon.Hash + + // We assume that the next committed version will be the od.stateStore.LastCommitID().Version+1 + binary.BigEndian.PutUint64(commitHash[:8], uint64(t.od.stateStore.LastCommitID().Version+1)) + + if t.prefix == nil { + if t.od.accountsCache != nil { + t.od.accountsCache.Write() + t.od.accountsCache = nil + } + if t.od.storageCache != nil { + t.od.storageCache.Write() + t.od.storageCache = nil + } + // Enumerate cached nodes from trie.Database + for _, n := range t.od.trieDbDummy.Nodes() { + if err := t.od.trieDbDummy.Commit(n, false); err != nil { + return eth_common.Hash{}, err + } + } + } + + t.root = root + return root, nil +} + +// Hash implements the Ethereum state.Trie interface. It returns the state root +// 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 { + return t.root +} + +// NodeIterator implements the Ethereum state.Trie interface. Such a node +// iterator is used primarily for the implementation of RPC API functions. It +// performs a no-op. +// +// TODO: Determine if we need to implement such functionality for an IAVL tree. +// This will ultimately be related to if we want to support web3. +func (t *Trie) NodeIterator(startKey []byte) ethtrie.NodeIterator { + return nil, fffsadf +} + +// GetKey implements the Ethereum state.Trie interface. Since the IAVL does not +// need to store preimages of keys, a simply identity can be returned. +func (t *Trie) GetKey(key []byte) []byte { + return key +} + +// Prove implements the Ethereum state.Trie interface. It writes a Merkle proof +// to a ethdb.Putter, proofDB, for a given key starting at fromLevel. +// +// TODO: Determine how to use the Cosmos SDK to provide such proof. +func (t *Trie) Prove(key []byte, fromLevel uint, proofDB ethdb.Putter) error { + return nil +}