Remove old deprecated state files
This commit is contained in:
parent
c0489c86da
commit
f82443080b
@ -1,208 +0,0 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/cosmos/ethermint/types"
|
||||
|
||||
ethcmn "github.com/ethereum/go-ethereum/common"
|
||||
ethstate "github.com/ethereum/go-ethereum/core/state"
|
||||
ethtrie "github.com/ethereum/go-ethereum/trie"
|
||||
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
)
|
||||
|
||||
// TODO: This functionality and implementation may be deprecated
|
||||
|
||||
var (
|
||||
// CodeKey is the key used for storing Ethereum contract code in the Cosmos
|
||||
// SDK multi-store.
|
||||
CodeKey = sdk.NewKVStoreKey("code")
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultStoreCacheSize defines the default number of key/value pairs for
|
||||
// the state stored in memory.
|
||||
DefaultStoreCacheSize = 1024 * 1024
|
||||
|
||||
// codeSizeCacheSize is the number of codehash to size associations to
|
||||
// keep in cached memory. This is to address any DoS attempts on
|
||||
// EXTCODESIZE calls.
|
||||
codeSizeCacheSize = 100000
|
||||
)
|
||||
|
||||
// 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 contains mappings 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
|
||||
|
||||
// codeSizeCache contains an LRU cache of a specified capacity to cache
|
||||
// EXTCODESIZE calls.
|
||||
codeSizeCache *lru.Cache
|
||||
|
||||
storeCache *lru.Cache
|
||||
|
||||
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(stateStore store.CommitMultiStore, codeDB dbm.DB, storeCacheSize int) (*Database, error) {
|
||||
db := &Database{stateStore: stateStore}
|
||||
|
||||
// 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(&EthereumDB{CodeDB: codeDB})
|
||||
|
||||
var err error
|
||||
|
||||
if db.codeSizeCache, err = lru.New(codeSizeCacheSize); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if db.storeCache, err = lru.New(storeCacheSize); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// LatestVersion returns the latest version of the underlying mult-store.
|
||||
func (db *Database) LatestVersion() int64 {
|
||||
return db.stateStore.LastCommitID().Version
|
||||
}
|
||||
|
||||
// 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 ethcmn.Hash) (ethstate.Trie, error) {
|
||||
if !isRootEmpty(root) {
|
||||
version := versionFromRootHash(root)
|
||||
|
||||
if db.stateStore.LastCommitID().Version != version {
|
||||
if err := db.stateStore.LoadVersion(version); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
db.accountsCache = nil
|
||||
}
|
||||
}
|
||||
|
||||
if db.accountsCache == nil {
|
||||
db.accountsCache = store.NewCacheKVStore(db.stateStore.GetCommitKVStore(types.StoreKeyAccount))
|
||||
db.storageCache = store.NewCacheKVStore(db.stateStore.GetCommitKVStore(types.StoreKeyStorage))
|
||||
}
|
||||
|
||||
return &Trie{
|
||||
store: db.accountsCache,
|
||||
accountsCache: db.accountsCache,
|
||||
storageCache: db.storageCache,
|
||||
storeCache: db.storeCache,
|
||||
ethTrieDB: db.ethTrieDB,
|
||||
empty: isRootEmpty(root),
|
||||
root: rootHashFromVersion(db.stateStore.LastCommitID().Version),
|
||||
}, 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 contract storage (state). Also, this trie is never committed
|
||||
// separately as all the data is in a single multi-store and is committed when
|
||||
// the account IAVL tree is committed.
|
||||
//
|
||||
// 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 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{
|
||||
store: db.storageCache,
|
||||
storeCache: db.storeCache,
|
||||
prefix: addrHash.Bytes(),
|
||||
empty: isRootEmpty(root),
|
||||
root: rootHashFromVersion(db.stateStore.LastCommitID().Version),
|
||||
}, 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 ethcmn.Hash) ([]byte, error) {
|
||||
code := db.codeDB.Get(codeHash[:])
|
||||
|
||||
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 ethcmn.Hash) (int, error) {
|
||||
if cached, ok := db.codeSizeCache.Get(codeHash); ok {
|
||||
return cached.(int), nil
|
||||
}
|
||||
|
||||
code, err := db.ContractCode(addrHash, codeHash)
|
||||
return len(code), err
|
||||
}
|
||||
|
||||
// Commit commits the underlying Cosmos SDK multi-store returning the commit
|
||||
// ID.
|
||||
func (db *Database) Commit() sdk.CommitID {
|
||||
return db.stateStore.Commit()
|
||||
}
|
||||
|
||||
// TrieDB implements Ethereum's state.Database interface. It returns Ethereum's
|
||||
// trie.Database low level trie database used for contract state storage. In
|
||||
// the context of Ethermint, it'll be used to solely store mappings of
|
||||
// codeHash => code.
|
||||
func (db *Database) TrieDB() *ethtrie.Database {
|
||||
return db.ethTrieDB
|
||||
}
|
||||
|
||||
// isRootEmpty returns true if a given root hash is empty or false otherwise.
|
||||
func isRootEmpty(root ethcmn.Hash) bool {
|
||||
return root == ethcmn.Hash{}
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
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"
|
||||
)
|
||||
|
||||
func TestDatabaseInterface(t *testing.T) {
|
||||
require.Implements(t, (*ethstate.Database)(nil), new(Database))
|
||||
}
|
||||
|
||||
func TestDatabaseLatestVersion(t *testing.T) {
|
||||
var version int64
|
||||
|
||||
testDB := newTestDatabase()
|
||||
|
||||
version = testDB.LatestVersion()
|
||||
require.Equal(t, int64(0), version)
|
||||
|
||||
testDB.Commit()
|
||||
version = testDB.LatestVersion()
|
||||
require.Equal(t, int64(1), version)
|
||||
}
|
||||
|
||||
func TestDatabaseCopyTrie(t *testing.T) {
|
||||
// TODO: Implement once CopyTrie is implemented
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
func TestDatabaseContractCode(t *testing.T) {
|
||||
testDB := newTestDatabase()
|
||||
|
||||
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 := newTestDatabase()
|
||||
|
||||
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 := newTestDatabase()
|
||||
|
||||
db := testDB.TrieDB()
|
||||
require.Equal(t, testDB.ethTrieDB, db)
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
package state
|
||||
|
||||
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() {
|
||||
}
|
@ -1,141 +0,0 @@
|
||||
package state
|
||||
|
||||
// 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"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/ethermint/types"
|
||||
ethcmn "github.com/ethereum/go-ethereum/common"
|
||||
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
)
|
||||
|
||||
type (
|
||||
kvPair struct {
|
||||
key, value []byte
|
||||
}
|
||||
|
||||
code struct {
|
||||
hash ethcmn.Hash
|
||||
blob []byte
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
func newTestDatabase() *Database {
|
||||
memDB := dbm.NewMemDB()
|
||||
|
||||
cms := store.NewCommitMultiStore(memDB)
|
||||
cms.SetPruning(sdk.PruneNothing)
|
||||
cms.MountStoreWithDB(types.StoreKeyAccount, sdk.StoreTypeIAVL, nil)
|
||||
cms.MountStoreWithDB(types.StoreKeyStorage, sdk.StoreTypeIAVL, nil)
|
||||
|
||||
testDB, err := NewDatabase(cms, memDB, 100)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to create database: %v", err))
|
||||
}
|
||||
|
||||
testDB.stateStore.LoadLatestVersion()
|
||||
|
||||
return testDB
|
||||
}
|
210
state/trie.go
210
state/trie.go
@ -1,210 +0,0 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
|
||||
ethcmn "github.com/ethereum/go-ethereum/common"
|
||||
ethdb "github.com/ethereum/go-ethereum/ethdb"
|
||||
ethtrie "github.com/ethereum/go-ethereum/trie"
|
||||
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
)
|
||||
|
||||
// TODO: This functionality and implementation may be deprecated
|
||||
|
||||
const (
|
||||
versionLen = 8
|
||||
)
|
||||
|
||||
// Trie implements the Ethereum state.Trie interface.
|
||||
type Trie struct {
|
||||
// accountsCache contains all the accounts in memory to persit when
|
||||
// committing the trie. A CacheKVStore is used to provide deterministic
|
||||
// ordering.
|
||||
accountsCache store.CacheKVStore
|
||||
// storageCache contains all the contract storage in memory to persit when
|
||||
// committing the trie. A CacheKVStore is used to provide deterministic
|
||||
// ordering.
|
||||
storageCache store.CacheKVStore
|
||||
|
||||
storeCache *lru.Cache
|
||||
|
||||
// Store is an IAVL KV store that is part of a larger store except it used
|
||||
// for a specific prefix. It will either be an accountsCache or a
|
||||
// storageCache.
|
||||
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 ethcmn.Hash
|
||||
|
||||
ethTrieDB *ethtrie.Database
|
||||
}
|
||||
|
||||
// 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.IsStorageTrie() {
|
||||
key = t.prefixKey(key)
|
||||
}
|
||||
keyStr := string(key)
|
||||
if cached, ok := t.storeCache.Get(keyStr); ok {
|
||||
return cached.([]byte), nil
|
||||
}
|
||||
value := t.store.Get(key)
|
||||
t.storeCache.Add(keyStr, value)
|
||||
return value, 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.IsStorageTrie() {
|
||||
key = t.prefixKey(key)
|
||||
}
|
||||
|
||||
t.store.Set(key, value)
|
||||
t.storeCache.Add(string(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.IsStorageTrie() {
|
||||
key = t.prefixKey(key)
|
||||
}
|
||||
|
||||
t.store.Delete(key)
|
||||
t.storeCache.Remove(string(key))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Commit implements the Ethereum state.Trie interface. It persists transient
|
||||
// state. State is held by a merkelized multi-store IAVL tree. Commitment will
|
||||
// only occur through an account trie, in other words, when the prefix of the
|
||||
// trie is nil. In such a case, if either the accountCache or the storageCache
|
||||
// are not nil, they are persisted. In addition, all the mappings of
|
||||
// codeHash => code are also persisted. All these operations are performed in a
|
||||
// deterministic order. Transient state is built up in a CacheKVStore. Finally,
|
||||
// a root hash is returned or an error if any operation fails.
|
||||
//
|
||||
// CONTRACT: The root is an encoded IAVL tree version and each new commitment
|
||||
// increments the version by one.
|
||||
func (t *Trie) Commit(_ ethtrie.LeafCallback) (ethcmn.Hash, error) {
|
||||
if t.empty {
|
||||
return ethcmn.Hash{}, nil
|
||||
}
|
||||
|
||||
newRoot := rootHashFromVersion(versionFromRootHash(t.root) + 1)
|
||||
|
||||
if !t.IsStorageTrie() {
|
||||
if t.accountsCache != nil {
|
||||
t.accountsCache.Write()
|
||||
t.accountsCache = nil
|
||||
}
|
||||
|
||||
if t.storageCache != nil {
|
||||
t.storageCache.Write()
|
||||
t.storageCache = nil
|
||||
}
|
||||
|
||||
// persist the mappings of codeHash => code
|
||||
for _, n := range t.ethTrieDB.Nodes() {
|
||||
if err := t.ethTrieDB.Commit(n, false); err != nil {
|
||||
return ethcmn.Hash{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t.root = newRoot
|
||||
return newRoot, 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() ethcmn.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
|
||||
}
|
||||
|
||||
// GetKey implements the Ethereum state.Trie interface. Since the IAVL does not
|
||||
// need to store preimages of keys, a simple 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 integrate this with Cosmos SDK to provide such
|
||||
// proofs.
|
||||
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 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 ethcmn.Hash) {
|
||||
binary.BigEndian.PutUint64(root[:versionLen], uint64(version))
|
||||
return
|
||||
}
|
@ -1,256 +0,0 @@
|
||||
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"
|
||||
)
|
||||
|
||||
func newTestTrie() *Trie {
|
||||
testDB := newTestDatabase()
|
||||
testTrie, _ := testDB.OpenTrie(rootHashFromVersion(0))
|
||||
|
||||
return testTrie.(*Trie)
|
||||
}
|
||||
|
||||
func newTestPrefixTrie() *Trie {
|
||||
testDB := newTestDatabase()
|
||||
|
||||
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.InsertBlob(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()
|
||||
}
|
Loading…
Reference in New Issue
Block a user