From 097dc4083c49c42e95b3133da549314cd091cf0e Mon Sep 17 00:00:00 2001 From: Pathorn Date: Wed, 9 Oct 2019 13:28:07 +0700 Subject: [PATCH 01/80] experiment --- Makefile | 13 ++++--- app.go | 63 ++++++++++++++++++++++++++++---- cmd/loom/db/evm.go | 2 +- cmd/loom/loom.go | 4 +- evm/loomevm.go | 38 ++++++++----------- {evm => store}/loomethdb.go | 63 +++++++++++++++++++------------- {evm => store}/loomethdb_test.go | 4 +- 7 files changed, 119 insertions(+), 68 deletions(-) rename {evm => store}/loomethdb.go (80%) rename {evm => store}/loomethdb_test.go (99%) diff --git a/Makefile b/Makefile index 1342d60436..a7bd4e65ea 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ GO_LOOM_GIT_REV = HEAD # Specifies the loomnetwork/transfer-gateway branch/revision to use. TG_GIT_REV = HEAD # loomnetwork/go-ethereum loomchain branch -ETHEREUM_GIT_REV = 1fb6138d017a4309105d91f187c126cf979c93f9 +ETHEREUM_GIT_REV = in-memory-statedb # use go-plugin we get 'timeout waiting for connection info' error HASHICORP_GIT_REV = f4c3476bd38585f9ec669d10ed1686abd52b9961 LEVIGO_GIT_REV = c42d9e0ca023e2198120196f842701bb4c55d7b9 @@ -197,14 +197,15 @@ $(BINANCE_TGORACLE_DIR): git clone -q git@github.com:loomnetwork/binance-tgoracle.git $@ cd $(BINANCE_TGORACLE_DIR) && git checkout master && git pull && git checkout $(BINANCE_TG_GIT_REV) -validators-tool: $(TRANSFER_GATEWAY_DIR) - go build -tags gateway -o e2e/validators-tool $(PKG)/e2e/cmd - -deps: $(PLUGIN_DIR) $(GO_ETHEREUM_DIR) $(SSHA3_DIR) - # Temp workaround for https://github.com/prometheus/procfs/issues/221 +$(PROMETHEUS_PROCFS_DIR): git clone -q git@github.com:prometheus/procfs $(PROMETHEUS_PROCFS_DIR) cd $(PROMETHEUS_PROCFS_DIR) && git checkout master && git pull && git checkout d3b299e382e6acf1baa852560d862eca4ff643c8 +validators-tool: $(TRANSFER_GATEWAY_DIR) + go build -tags gateway -o e2e/validators-tool $(PKG)/e2e/cmd + +deps: $(PLUGIN_DIR) $(GO_ETHEREUM_DIR) $(SSHA3_DIR) $(PROMETHEUS_PROCFS_DIR) + go get \ golang.org/x/crypto/ed25519 \ google.golang.org/grpc \ diff --git a/app.go b/app.go index d50489b538..58989f103e 100644 --- a/app.go +++ b/app.go @@ -7,6 +7,8 @@ import ( "fmt" "time" + gcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/trie" "github.com/loomnetwork/go-loom/config" "github.com/loomnetwork/go-loom/util" "github.com/loomnetwork/loomchain/eth/utils" @@ -39,6 +41,7 @@ type ReadOnlyState interface { Config() *cctypes.Config EnabledFeatures() []string GetMinBuildNumber() uint64 + GetTrieDB() *trie.Database } type State interface { @@ -59,6 +62,7 @@ type StoreState struct { validators loom.ValidatorSet getValidatorSet GetValidatorSet config *cctypes.Config + trieDB *trie.Database } var _ = State(&StoreState{}) @@ -100,6 +104,11 @@ func (s *StoreState) WithOnChainConfig(config *cctypes.Config) *StoreState { return s } +func (s *StoreState) WithTrieDB(trieDB *trie.Database) *StoreState { + s.trieDB = trieDB + return s +} + func (s *StoreState) Range(prefix []byte) plugin.RangeData { return s.store.Range(prefix) } @@ -140,11 +149,20 @@ func (s *StoreState) Context() context.Context { return s.ctx } +func (s *StoreState) GetTrieDB() *trie.Database { + return s.trieDB +} + const ( featurePrefix = "feature" MinBuildKey = "minbuild" ) +var ( + vmPrefix = []byte("vm") + rootKey = []byte("vmroot") +) + func featureKey(featureName string) []byte { return util.PrefixKey([]byte(featurePrefix), []byte(featureName)) } @@ -233,6 +251,7 @@ func (s *StoreState) WithContext(ctx context.Context) State { ctx: ctx, validators: s.validators, getValidatorSet: s.getValidatorSet, + trieDB: s.trieDB, } } @@ -243,6 +262,7 @@ func (s *StoreState) WithPrefix(prefix []byte) State { ctx: s.ctx, validators: s.validators, getValidatorSet: s.getValidatorSet, + trieDB: s.trieDB, } } @@ -362,6 +382,8 @@ type Application struct { config *cctypes.Config childTxRefs []evmaux.ChildTxRef // links Tendermint txs to EVM txs ReceiptsVersion int32 + TrieDB *trie.Database + lastSavedEVMRoot []byte } var _ abci.Application = &Application{} @@ -505,7 +527,7 @@ func (a *Application) BeginBlock(req abci.RequestBeginBlock) abci.ResponseBeginB a.curBlockHeader, a.curBlockHash, a.GetValidatorSet, - ).WithOnChainConfig(a.config) + ).WithOnChainConfig(a.config).WithTrieDB(a.TrieDB) contractUpkeepHandler, err := a.CreateContractUpkeepHandler(upkeepState) if err != nil { panic(err) @@ -525,7 +547,7 @@ func (a *Application) BeginBlock(req abci.RequestBeginBlock) abci.ResponseBeginB a.curBlockHeader, nil, a.GetValidatorSet, - ).WithOnChainConfig(a.config) + ).WithOnChainConfig(a.config).WithTrieDB(a.TrieDB) validatorManager, err := a.CreateValidatorManager(state) if err != registry.ErrNotFound { @@ -593,7 +615,7 @@ func (a *Application) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock { a.curBlockHeader, nil, a.GetValidatorSet, - ).WithOnChainConfig(a.config) + ).WithOnChainConfig(a.config).WithTrieDB(a.TrieDB) validatorManager, err := a.CreateValidatorManager(state) if err != registry.ErrNotFound { @@ -648,7 +670,7 @@ func (a *Application) CheckTx(txBytes []byte) abci.ResponseCheckTx { a.curBlockHeader, a.curBlockHash, a.GetValidatorSet, - ).WithOnChainConfig(a.config) + ).WithOnChainConfig(a.config).WithTrieDB(a.TrieDB) // Receipts & events generated in CheckTx must be discarded since the app state changes they // reflect aren't persisted. @@ -685,7 +707,7 @@ func (a *Application) DeliverTx(txBytes []byte) abci.ResponseDeliverTx { a.curBlockHeader, a.curBlockHash, a.GetValidatorSet, - ).WithOnChainConfig(a.config) + ).WithOnChainConfig(a.config).WithTrieDB(a.TrieDB) var r abci.ResponseDeliverTx @@ -718,7 +740,7 @@ func (a *Application) processTx(storeTx store.KVStoreTx, txBytes []byte, isCheck a.curBlockHeader, a.curBlockHash, a.GetValidatorSet, - ).WithOnChainConfig(a.config) + ).WithOnChainConfig(a.config).WithTrieDB(a.TrieDB) receiptHandler := a.ReceiptHandlerProvider.Store() defer receiptHandler.DiscardCurrentReceipt() @@ -771,7 +793,7 @@ func (a *Application) deliverTx2(storeTx store.KVStoreTx, txBytes []byte) abci.R a.curBlockHeader, a.curBlockHash, a.GetValidatorSet, - ).WithOnChainConfig(a.config) + ).WithOnChainConfig(a.config).WithTrieDB(a.TrieDB) receiptHandler := a.ReceiptHandlerProvider.Store() defer receiptHandler.DiscardCurrentReceipt() @@ -829,6 +851,31 @@ func (a *Application) Commit() abci.ResponseCommit { committedBlockCount.With(lvs...).Add(1) commitBlockLatency.With(lvs...).Observe(time.Since(begin).Seconds()) }(time.Now()) + + state := NewStoreState( + context.Background(), + a.Store, + a.curBlockHeader, + a.curBlockHash, + a.GetValidatorSet, + ) + cfg := state.Config() + + curHeight := a.curBlockHeader.GetHeight() + flushInterval := cfg.GetAppStore().GetIAVLFlushInterval() + if flushInterval == 0 || uint64(curHeight)%flushInterval == 0 { + ethDB := store.NewLoomEthDB(state, nil) + evmRoot := state.Get(util.PrefixKey(vmPrefix, rootKey)) + if len(evmRoot) > 0 && bytes.Compare(a.lastSavedEVMRoot, evmRoot) != 0 { + rootHash := gcommon.BytesToHash(evmRoot) + a.TrieDB.SetDiskDB(ethDB) + if err := a.TrieDB.Commit(rootHash, false); err != nil { + panic(err) + } + a.lastSavedEVMRoot = evmRoot + } + } + appHash, _, err := a.Store.SaveVersion() if err != nil { panic(err) @@ -893,5 +940,5 @@ func (a *Application) ReadOnlyState() State { a.lastBlockHeader, nil, // TODO: last block hash! a.GetValidatorSet, - ) + ).WithTrieDB(a.TrieDB) } diff --git a/cmd/loom/db/evm.go b/cmd/loom/db/evm.go index fdda84a2d9..381001b489 100644 --- a/cmd/loom/db/evm.go +++ b/cmd/loom/db/evm.go @@ -285,7 +285,7 @@ func newDumpEVMStateFromEvmDB() *cobra.Command { nil, ) - srcStateDB := gstate.NewDatabase(evm.NewLoomEthdb(state, nil)) + srcStateDB := gstate.NewDatabase(store.NewLoomEthDB(state, nil)) srcStateDBTrie, err := srcStateDB.OpenTrie(evmRoot) if err != nil { fmt.Printf("cannot open trie, %s\n", evmRoot.Hex()) diff --git a/cmd/loom/loom.go b/cmd/loom/loom.go index cc8d860bca..36291971b3 100644 --- a/cmd/loom/loom.go +++ b/cmd/loom/loom.go @@ -19,6 +19,7 @@ import ( "github.com/prometheus/client_golang/prometheus/push" "github.com/tendermint/tendermint/libs/db" + "github.com/ethereum/go-ethereum/trie" kitprometheus "github.com/go-kit/kit/metrics/prometheus" "github.com/gogo/protobuf/proto" "github.com/loomnetwork/go-loom" @@ -836,7 +837,7 @@ func loadApp( return evm.NewLoomVm(state, eventHandler, receiptHandlerProvider.Writer(), createABM, cfg.EVMDebugEnabled), nil }) } - evm.LogEthDbBatch = cfg.LogEthDbBatch + store.LogEthDBBatch = cfg.LogEthDbBatch deployTxHandler := &vm.DeployTxHandler{ Manager: vmManager, @@ -1126,6 +1127,7 @@ func loadApp( GetValidatorSet: getValidatorSet, EvmAuxStore: evmAuxStore, ReceiptsVersion: cfg.ReceiptsVersion, + TrieDB: trie.NewDatabase(nil), }, nil } diff --git a/evm/loomevm.go b/evm/loomevm.go index 3d3ae3ff3f..2f4695485c 100644 --- a/evm/loomevm.go +++ b/evm/loomevm.go @@ -22,6 +22,7 @@ import ( "github.com/loomnetwork/loomchain/features" "github.com/loomnetwork/loomchain/receipts" "github.com/loomnetwork/loomchain/receipts/handler" + "github.com/loomnetwork/loomchain/store" "github.com/loomnetwork/loomchain/vm" "github.com/pkg/errors" ) @@ -38,12 +39,6 @@ type StateDB interface { Commit(bool) (common.Hash, error) } -type ethdbLogContext struct { - blockHeight int64 - contractAddr loom.Address - callerAddr loom.Address -} - // TODO: this doesn't need to be exported, rename to loomEvmWithState type LoomEvm struct { *Evm @@ -54,21 +49,26 @@ type LoomEvm struct { // TODO: this doesn't need to be exported, rename to newLoomEvmWithState func NewLoomEvm( loomState loomchain.State, accountBalanceManager AccountBalanceManager, - logContext *ethdbLogContext, debug bool, + logContext *store.EthDBLogContext, debug bool, ) (*LoomEvm, error) { p := new(LoomEvm) - p.db = NewLoomEthdb(loomState, logContext) + p.db = store.NewLoomEthDB(loomState, logContext) oldRoot, err := p.db.Get(rootKey) if err != nil { return nil, err } + trieDB := loomState.GetTrieDB() + trieDB.SetDiskDB(p.db) + sdb := state.NewDatabase(p.db) + sdb.SetTrieDB(trieDB) + var abm *evmAccountBalanceManager if accountBalanceManager != nil { abm = newEVMAccountBalanceManager(accountBalanceManager, loomState.Block().ChainID) - p.sdb, err = newLoomStateDB(abm, common.BytesToHash(oldRoot), state.NewDatabase(p.db)) + p.sdb, err = newLoomStateDB(abm, common.BytesToHash(oldRoot), sdb) } else { - p.sdb, err = state.New(common.BytesToHash(oldRoot), state.NewDatabase(p.db)) + p.sdb, err = state.New(common.BytesToHash(oldRoot), sdb) } if err != nil { return nil, err @@ -83,9 +83,9 @@ func (levm LoomEvm) Commit() (common.Hash, error) { if err != nil { return root, err } - if err := levm.sdb.Database().TrieDB().Commit(root, false); err != nil { - return root, err - } + // if err := levm.sdb.Database().TrieDB().Commit(root, false); err != nil { + // return root, err + // } if err := levm.db.Put(rootKey, root[:]); err != nil { return root, err } @@ -146,11 +146,7 @@ func (lvm LoomVm) accountBalanceManager(readOnly bool) AccountBalanceManager { } func (lvm LoomVm) Create(caller loom.Address, code []byte, value *loom.BigUInt) ([]byte, loom.Address, error) { - logContext := ðdbLogContext{ - blockHeight: lvm.state.Block().Height, - contractAddr: loom.Address{}, - callerAddr: caller, - } + logContext := store.NewEthDBLogContext(lvm.state.Block().Height, loom.Address{}, caller) levm, err := NewLoomEvm(lvm.state, lvm.accountBalanceManager(false), logContext, lvm.debug) if err != nil { return nil, loom.Address{}, err @@ -209,11 +205,7 @@ func (lvm LoomVm) Create(caller loom.Address, code []byte, value *loom.BigUInt) } func (lvm LoomVm) Call(caller, addr loom.Address, input []byte, value *loom.BigUInt) ([]byte, error) { - logContext := ðdbLogContext{ - blockHeight: lvm.state.Block().Height, - contractAddr: addr, - callerAddr: caller, - } + logContext := store.NewEthDBLogContext(lvm.state.Block().Height, addr, caller) levm, err := NewLoomEvm(lvm.state, lvm.accountBalanceManager(false), logContext, lvm.debug) if err != nil { return nil, err diff --git a/evm/loomethdb.go b/store/loomethdb.go similarity index 80% rename from evm/loomethdb.go rename to store/loomethdb.go index 9d5024f333..c920f6a83b 100644 --- a/evm/loomethdb.go +++ b/store/loomethdb.go @@ -1,6 +1,4 @@ -// +build evm - -package evm +package store import ( "bytes" @@ -11,53 +9,66 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" - "github.com/loomnetwork/loomchain" - "github.com/loomnetwork/loomchain/store" + loom "github.com/loomnetwork/go-loom" ) var ( - LogEthDbBatch = true + LogEthDBBatch = true logger log.Logger loggerStarted = false ) +type EthDBLogContext struct { + blockHeight int64 + contractAddr loom.Address + callerAddr loom.Address +} + +func NewEthDBLogContext(height int64, contractAddr loom.Address, callerAddr loom.Address) *EthDBLogContext { + return &EthDBLogContext{ + blockHeight: height, + contractAddr: contractAddr, + callerAddr: callerAddr, + } +} + // implements ethdb.Database -type LoomEthdb struct { - state store.KVStore +type LoomEthDB struct { + state KVStore lock sync.RWMutex - logContext *ethdbLogContext + logContext *EthDBLogContext } -func NewLoomEthdb(_state loomchain.State, logContext *ethdbLogContext) *LoomEthdb { - p := new(LoomEthdb) - p.state = store.PrefixKVStore(vmPrefix, _state) - p.logContext = logContext - return p +func NewLoomEthDB(_state KVStore, logContext *EthDBLogContext) *LoomEthDB { + return &LoomEthDB{ + state: PrefixKVStore(vmPrefix, _state), + logContext: logContext, + } } -func (s *LoomEthdb) Put(key []byte, value []byte) error { +func (s *LoomEthDB) Put(key []byte, value []byte) error { s.state.Set(key, value) return nil } -func (s *LoomEthdb) Get(key []byte) ([]byte, error) { +func (s *LoomEthDB) Get(key []byte) ([]byte, error) { return s.state.Get(key), nil } -func (s *LoomEthdb) Has(key []byte) (bool, error) { +func (s *LoomEthDB) Has(key []byte) (bool, error) { return s.state.Has(key), nil } -func (s *LoomEthdb) Delete(key []byte) error { +func (s *LoomEthDB) Delete(key []byte) error { s.state.Delete(key) return nil } -func (s *LoomEthdb) Close() { +func (s *LoomEthDB) Close() { } -func (s *LoomEthdb) NewBatch() ethdb.Batch { - if LogEthDbBatch { +func (s *LoomEthDB) NewBatch() ethdb.Batch { + if LogEthDBBatch { return s.NewLogBatch(s.logContext) } else { newBatch := new(batch) @@ -75,7 +86,7 @@ type kvPair struct { type batch struct { cache []kvPair - parentStore *LoomEthdb + parentStore *LoomEthDB size int } @@ -132,7 +143,7 @@ func (b *batch) Dump(logger *log.Logger) { } } -type LogParams struct { +type EthDBLogParams struct { LogFilename string LogFlags int LogReset bool @@ -148,7 +159,7 @@ type LogParams struct { type LogBatch struct { batch batch - params LogParams + params EthDBLogParams } const batchHeaderWithContext = ` @@ -170,12 +181,12 @@ const batchHeader = ` ` -func (s *LoomEthdb) NewLogBatch(logContext *ethdbLogContext) ethdb.Batch { +func (s *LoomEthDB) NewLogBatch(logContext *EthDBLogContext) ethdb.Batch { b := new(LogBatch) b.batch = *new(batch) b.batch.parentStore = s b.batch.Reset() - b.params = LogParams{ + b.params = EthDBLogParams{ LogFilename: "ethdb-batch.log", LogFlags: 0, LogReset: true, diff --git a/evm/loomethdb_test.go b/store/loomethdb_test.go similarity index 99% rename from evm/loomethdb_test.go rename to store/loomethdb_test.go index 2fbab564c8..04ceef5f8c 100644 --- a/evm/loomethdb_test.go +++ b/store/loomethdb_test.go @@ -1,6 +1,4 @@ -// +build evm - -package evm +package store import ( "bytes" From ea8a66bce34057294d4b57bef2da193b943868a8 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Wed, 9 Oct 2019 14:19:50 +0700 Subject: [PATCH 02/80] fix unit tests --- app.go | 9 +++++---- evm/evm_test.go | 6 +++++- plugin/fake_context.go | 6 +++++- plugin/vm_test.go | 6 +++++- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/app.go b/app.go index 58989f103e..ea873edc71 100644 --- a/app.go +++ b/app.go @@ -860,19 +860,20 @@ func (a *Application) Commit() abci.ResponseCommit { a.GetValidatorSet, ) cfg := state.Config() - curHeight := a.curBlockHeader.GetHeight() flushInterval := cfg.GetAppStore().GetIAVLFlushInterval() + + // Only commit Patricia tree every N blocks if flushInterval == 0 || uint64(curHeight)%flushInterval == 0 { - ethDB := store.NewLoomEthDB(state, nil) evmRoot := state.Get(util.PrefixKey(vmPrefix, rootKey)) if len(evmRoot) > 0 && bytes.Compare(a.lastSavedEVMRoot, evmRoot) != 0 { - rootHash := gcommon.BytesToHash(evmRoot) + ethDB := store.NewLoomEthDB(state, nil) a.TrieDB.SetDiskDB(ethDB) - if err := a.TrieDB.Commit(rootHash, false); err != nil { + if err := a.TrieDB.Commit(gcommon.BytesToHash(evmRoot), false); err != nil { panic(err) } a.lastSavedEVMRoot = evmRoot + a.TrieDB = trie.NewDatabase(ethDB) } } diff --git a/evm/evm_test.go b/evm/evm_test.go index 217d2c3f5d..01e13c4fd6 100644 --- a/evm/evm_test.go +++ b/evm/evm_test.go @@ -13,6 +13,8 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" ethvm "github.com/ethereum/go-ethereum/core/vm" @@ -40,7 +42,9 @@ func mockState() loomchain.State { header := abci.Header{} header.Height = BlockHeight header.Time = blockTime - return loomchain.NewStoreState(context.Background(), store.NewMemStore(), header, nil, nil) + memDB := store.NewMemStore() + trieDB := trie.NewDatabase(store.NewLoomEthDB(memDB, nil)) + return loomchain.NewStoreState(context.Background(), memDB, header, nil, nil).WithTrieDB(trieDB) } func TestProcessDeployTx(t *testing.T) { diff --git a/plugin/fake_context.go b/plugin/fake_context.go index 84ddb384fe..82eff53326 100644 --- a/plugin/fake_context.go +++ b/plugin/fake_context.go @@ -6,11 +6,14 @@ import ( "context" "time" + "github.com/ethereum/go-ethereum/trie" + loom "github.com/loomnetwork/go-loom" "github.com/loomnetwork/go-loom/plugin" "github.com/loomnetwork/go-loom/types" "github.com/loomnetwork/loomchain" levm "github.com/loomnetwork/loomchain/evm" + "github.com/loomnetwork/loomchain/store" abci "github.com/tendermint/tendermint/abci/types" ) @@ -34,7 +37,8 @@ func CreateFakeContextWithEVM(caller, address loom.Address) *FakeContextWithEVM Time: block.Time.Unix(), }, ) - state := loomchain.NewStoreState(context.Background(), ctx, block, nil, nil) + trieDB := trie.NewDatabase(store.NewLoomEthDB(ctx, nil)) + state := loomchain.NewStoreState(context.Background(), ctx, block, nil, nil).WithTrieDB(trieDB) return &FakeContextWithEVM{ FakeContext: ctx, State: state, diff --git a/plugin/vm_test.go b/plugin/vm_test.go index f089fab0e3..51a6b4fdd7 100644 --- a/plugin/vm_test.go +++ b/plugin/vm_test.go @@ -10,6 +10,8 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" proto "github.com/gogo/protobuf/proto" @@ -137,7 +139,9 @@ func TestPluginVMContractContextCaller(t *testing.T) { Height: int64(34), Time: time.Unix(123456789, 0), } - state := loomchain.NewStoreState(context.Background(), store.NewMemStore(), block, nil, nil) + memDB := store.NewMemStore() + trieDB := trie.NewDatabase(store.NewLoomEthDB(memDB, nil)) + state := loomchain.NewStoreState(context.Background(), memDB, block, nil, nil).WithTrieDB(trieDB) createRegistry, err := registry.NewRegistryFactory(registry.LatestRegistryVersion) require.NoError(t, err) From de298e0ef8038af4863117b5e766dfd6fdc6aba2 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Wed, 9 Oct 2019 14:41:11 +0700 Subject: [PATCH 03/80] only save committed evmroot --- app.go | 9 +++++++++ store/evmstore.go | 6 +++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/app.go b/app.go index ea873edc71..38928a6377 100644 --- a/app.go +++ b/app.go @@ -161,12 +161,20 @@ const ( var ( vmPrefix = []byte("vm") rootKey = []byte("vmroot") + // This is the prefix of versioning Patricia roots + evmRootPrefix = []byte("evmroot") ) func featureKey(featureName string) []byte { return util.PrefixKey([]byte(featurePrefix), []byte(featureName)) } +func evmRootKey(blockHeight int64) []byte { + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, uint64(blockHeight)) + return util.PrefixKey(vmPrefix, []byte(evmRootPrefix), b) +} + func (s *StoreState) EnabledFeatures() []string { featuresFromState := s.Range([]byte(featurePrefix)) enabledFeatures := make([]string, 0, len(featuresFromState)) @@ -874,6 +882,7 @@ func (a *Application) Commit() abci.ResponseCommit { } a.lastSavedEVMRoot = evmRoot a.TrieDB = trie.NewDatabase(ethDB) + a.Store.Set(evmRootKey(curHeight), evmRoot) } } diff --git a/store/evmstore.go b/store/evmstore.go index cc01297bd3..823ecd3630 100644 --- a/store/evmstore.go +++ b/store/evmstore.go @@ -195,9 +195,9 @@ func (s *EvmStore) Commit(version int64) []byte { currentRoot = defaultRoot } // save Patricia root of EVM state only if it changes - if !bytes.Equal(currentRoot, s.lastSavedRoot) { - s.Set(evmRootKey(version), currentRoot) - } + // if !bytes.Equal(currentRoot, s.lastSavedRoot) { + // s.Set(evmRootKey(version), currentRoot) + // } s.rootCache.Add(version, currentRoot) From 5fda6e4fb5fd0408aaa303905b1b20dc63843216 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Wed, 9 Oct 2019 14:54:13 +0700 Subject: [PATCH 04/80] allow flush interval for evm state to be configured in loom.yaml --- app.go | 13 ++++++++++--- cmd/loom/loom.go | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/app.go b/app.go index 38928a6377..f8bc1b5abc 100644 --- a/app.go +++ b/app.go @@ -391,6 +391,7 @@ type Application struct { childTxRefs []evmaux.ChildTxRef // links Tendermint txs to EVM txs ReceiptsVersion int32 TrieDB *trie.Database + FlushInterval int64 // commit Patricia trie to disk every N blocks lastSavedEVMRoot []byte } @@ -867,12 +868,18 @@ func (a *Application) Commit() abci.ResponseCommit { a.curBlockHash, a.GetValidatorSet, ) - cfg := state.Config() curHeight := a.curBlockHeader.GetHeight() - flushInterval := cfg.GetAppStore().GetIAVLFlushInterval() + + flushInterval := a.FlushInterval + if flushInterval == 0 { + cfg := state.Config() + flushInterval = int64(cfg.GetAppStore().GetIAVLFlushInterval()) + } else if flushInterval == -1 { + flushInterval = 0 + } // Only commit Patricia tree every N blocks - if flushInterval == 0 || uint64(curHeight)%flushInterval == 0 { + if flushInterval == 0 || curHeight%flushInterval == 0 { evmRoot := state.Get(util.PrefixKey(vmPrefix, rootKey)) if len(evmRoot) > 0 && bytes.Compare(a.lastSavedEVMRoot, evmRoot) != 0 { ethDB := store.NewLoomEthDB(state, nil) diff --git a/cmd/loom/loom.go b/cmd/loom/loom.go index 36291971b3..41aa22b84c 100644 --- a/cmd/loom/loom.go +++ b/cmd/loom/loom.go @@ -1128,6 +1128,7 @@ func loadApp( EvmAuxStore: evmAuxStore, ReceiptsVersion: cfg.ReceiptsVersion, TrieDB: trie.NewDatabase(nil), + FlushInterval: cfg.AppStore.IAVLFlushInterval, }, nil } From 8c04bfc802d82404f88ecff6e862f6c184b03cc2 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Wed, 9 Oct 2019 15:27:00 +0700 Subject: [PATCH 05/80] fix unit test --- store/evmstore_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/store/evmstore_test.go b/store/evmstore_test.go index b141051195..c5d0081e68 100644 --- a/store/evmstore_test.go +++ b/store/evmstore_test.go @@ -43,7 +43,7 @@ func (t *EvmStoreTestSuite) TestEvmStoreRangeAndCommit() { evmStore.Set([]byte("SSSSS"), []byte("SSSSS")) evmStore.Set([]byte("vvvvv"), []byte("vvv")) dataRange = evmStore.Range(nil) - require.Equal(107, len(dataRange)) + require.Equal(106, len(dataRange)) evmStore.Commit(2) evmStore.Set([]byte("SSSSS"), []byte("S1")) ret := evmStore.Get([]byte("SSSSS")) @@ -51,12 +51,12 @@ func (t *EvmStoreTestSuite) TestEvmStoreRangeAndCommit() { evmStore.Delete([]byte("SSSSS")) evmStore.Delete([]byte("hello1")) dataRange = evmStore.Range(nil) - require.Equal(105, len(dataRange)) + require.Equal(104, len(dataRange)) evmStore.Commit(3) evmStore.Delete([]byte("SSSSS")) evmStore.Delete([]byte("hello1")) dataRange = evmStore.Range(nil) - require.Equal(105, len(dataRange)) + require.Equal(104, len(dataRange)) } func (t *EvmStoreTestSuite) TestEvmStoreBasicMethods() { From 9180eb1ac03c46180398c63f20f46a54dcc84ba9 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Wed, 9 Oct 2019 15:44:06 +0700 Subject: [PATCH 06/80] fix unit test --- store/multi_writer_app_store_test.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/store/multi_writer_app_store_test.go b/store/multi_writer_app_store_test.go index f08d244716..9da4a161ca 100644 --- a/store/multi_writer_app_store_test.go +++ b/store/multi_writer_app_store_test.go @@ -184,7 +184,7 @@ func (m *MultiWriterAppStoreTestSuite) TestMultiWriterAppStoreSnapShotRange() { snapshot = store.GetSnapshot() rangeData = snapshot.Range(vmPrefix) - require.Equal(4+1, len(rangeData)) // +1 for evm root stored by EVM store + require.Equal(4, len(rangeData)) require.Equal(0, bytes.Compare(snapshot.Get(vmPrefixKey("abcd")), []byte("hello"))) require.Equal(0, bytes.Compare(snapshot.Get(vmPrefixKey("abcde")), []byte("world"))) require.Equal(0, bytes.Compare(snapshot.Get(vmPrefixKey("evmStore")), []byte("yes"))) @@ -196,7 +196,7 @@ func (m *MultiWriterAppStoreTestSuite) TestMultiWriterAppStoreSnapShotRange() { snapshot = store.GetSnapshot() rangeData = snapshot.Range(vmPrefix) - require.Equal(4+1, len(rangeData)) // +1 for evm root stored by EVM store + require.Equal(4, len(rangeData)) require.Equal(0, bytes.Compare(snapshot.Get(vmPrefixKey("abcd")), []byte("hello"))) require.Equal(0, bytes.Compare(snapshot.Get(vmPrefixKey("abcde")), []byte("world"))) require.Equal(0, bytes.Compare(snapshot.Get(vmPrefixKey("evmStore")), []byte("yes"))) @@ -242,8 +242,7 @@ func (m *MultiWriterAppStoreTestSuite) TestMultiWriterAppStoreSaveVersion() { store.Delete(vmPrefixKey("gg")) dataRange := store.Range(vmPrefix) - require.Equal(6+1, len(dataRange)) // +1 is for the evm root that written by the EVM store itself - + require.Equal(6, len(dataRange)) _, version, err = store.SaveVersion() require.Equal(int64(2), version) require.NoError(err) From 4193068c4e36726d194bf756a6338159f43ced8d Mon Sep 17 00:00:00 2001 From: Pathorn Date: Wed, 9 Oct 2019 16:32:38 +0700 Subject: [PATCH 07/80] save default root to evm.db --- store/evmstore.go | 9 +++++---- store/evmstore_test.go | 6 +++--- store/multi_writer_app_store_test.go | 6 +++--- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/store/evmstore.go b/store/evmstore.go index 823ecd3630..66226ba496 100644 --- a/store/evmstore.go +++ b/store/evmstore.go @@ -194,10 +194,11 @@ func (s *EvmStore) Commit(version int64) []byte { if bytes.Equal(currentRoot, []byte{}) { currentRoot = defaultRoot } - // save Patricia root of EVM state only if it changes - // if !bytes.Equal(currentRoot, s.lastSavedRoot) { - // s.Set(evmRootKey(version), currentRoot) - // } + + // only save default root, new valid root will be saved in app.Commit() + if !bytes.Equal(currentRoot, s.lastSavedRoot) && bytes.Equal(currentRoot, defaultRoot) { + s.Set(evmRootKey(version), currentRoot) + } s.rootCache.Add(version, currentRoot) diff --git a/store/evmstore_test.go b/store/evmstore_test.go index c5d0081e68..c3ecafa5ac 100644 --- a/store/evmstore_test.go +++ b/store/evmstore_test.go @@ -43,7 +43,7 @@ func (t *EvmStoreTestSuite) TestEvmStoreRangeAndCommit() { evmStore.Set([]byte("SSSSS"), []byte("SSSSS")) evmStore.Set([]byte("vvvvv"), []byte("vvv")) dataRange = evmStore.Range(nil) - require.Equal(106, len(dataRange)) + require.Equal(106+1, len(dataRange)) // +1 default evm root key evmStore.Commit(2) evmStore.Set([]byte("SSSSS"), []byte("S1")) ret := evmStore.Get([]byte("SSSSS")) @@ -51,12 +51,12 @@ func (t *EvmStoreTestSuite) TestEvmStoreRangeAndCommit() { evmStore.Delete([]byte("SSSSS")) evmStore.Delete([]byte("hello1")) dataRange = evmStore.Range(nil) - require.Equal(104, len(dataRange)) + require.Equal(104+1, len(dataRange)) // +1 default evm root key evmStore.Commit(3) evmStore.Delete([]byte("SSSSS")) evmStore.Delete([]byte("hello1")) dataRange = evmStore.Range(nil) - require.Equal(104, len(dataRange)) + require.Equal(104+1, len(dataRange)) // +1 default evm root key } func (t *EvmStoreTestSuite) TestEvmStoreBasicMethods() { diff --git a/store/multi_writer_app_store_test.go b/store/multi_writer_app_store_test.go index 9da4a161ca..5ea33e2f64 100644 --- a/store/multi_writer_app_store_test.go +++ b/store/multi_writer_app_store_test.go @@ -184,7 +184,7 @@ func (m *MultiWriterAppStoreTestSuite) TestMultiWriterAppStoreSnapShotRange() { snapshot = store.GetSnapshot() rangeData = snapshot.Range(vmPrefix) - require.Equal(4, len(rangeData)) + require.Equal(4+1, len(rangeData)) // +1 for evm root stored by EVM store require.Equal(0, bytes.Compare(snapshot.Get(vmPrefixKey("abcd")), []byte("hello"))) require.Equal(0, bytes.Compare(snapshot.Get(vmPrefixKey("abcde")), []byte("world"))) require.Equal(0, bytes.Compare(snapshot.Get(vmPrefixKey("evmStore")), []byte("yes"))) @@ -196,7 +196,7 @@ func (m *MultiWriterAppStoreTestSuite) TestMultiWriterAppStoreSnapShotRange() { snapshot = store.GetSnapshot() rangeData = snapshot.Range(vmPrefix) - require.Equal(4, len(rangeData)) + require.Equal(4+1, len(rangeData)) // +1 for evm root stored by EVM store require.Equal(0, bytes.Compare(snapshot.Get(vmPrefixKey("abcd")), []byte("hello"))) require.Equal(0, bytes.Compare(snapshot.Get(vmPrefixKey("abcde")), []byte("world"))) require.Equal(0, bytes.Compare(snapshot.Get(vmPrefixKey("evmStore")), []byte("yes"))) @@ -242,7 +242,7 @@ func (m *MultiWriterAppStoreTestSuite) TestMultiWriterAppStoreSaveVersion() { store.Delete(vmPrefixKey("gg")) dataRange := store.Range(vmPrefix) - require.Equal(6, len(dataRange)) + require.Equal(6+1, len(dataRange)) _, version, err = store.SaveVersion() require.Equal(int64(2), version) require.NoError(err) From a4863cfd9e4cf22f0bb181a34540f05aa2c2331e Mon Sep 17 00:00:00 2001 From: Pathorn Date: Wed, 9 Oct 2019 17:51:54 +0700 Subject: [PATCH 08/80] address PR comment --- app.go | 11 ++++++++--- evm/loomevm.go | 9 ++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app.go b/app.go index f8bc1b5abc..14e27d3c9c 100644 --- a/app.go +++ b/app.go @@ -8,6 +8,7 @@ import ( "time" gcommon "github.com/ethereum/go-ethereum/common" + gstate "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/trie" "github.com/loomnetwork/go-loom/config" "github.com/loomnetwork/go-loom/util" @@ -41,7 +42,7 @@ type ReadOnlyState interface { Config() *cctypes.Config EnabledFeatures() []string GetMinBuildNumber() uint64 - GetTrieDB() *trie.Database + GetEVMStateDB() gstate.Database } type State interface { @@ -149,8 +150,12 @@ func (s *StoreState) Context() context.Context { return s.ctx } -func (s *StoreState) GetTrieDB() *trie.Database { - return s.trieDB +func (s *StoreState) GetEVMStateDB() gstate.Database { + ethDB := store.NewLoomEthDB(s, nil) + s.trieDB.SetDiskDB(ethDB) + evmStateDB := gstate.NewDatabase(ethDB) + evmStateDB.SetTrieDB(s.trieDB) + return evmStateDB } const ( diff --git a/evm/loomevm.go b/evm/loomevm.go index 2f4695485c..615624911a 100644 --- a/evm/loomevm.go +++ b/evm/loomevm.go @@ -58,17 +58,12 @@ func NewLoomEvm( return nil, err } - trieDB := loomState.GetTrieDB() - trieDB.SetDiskDB(p.db) - sdb := state.NewDatabase(p.db) - sdb.SetTrieDB(trieDB) - var abm *evmAccountBalanceManager if accountBalanceManager != nil { abm = newEVMAccountBalanceManager(accountBalanceManager, loomState.Block().ChainID) - p.sdb, err = newLoomStateDB(abm, common.BytesToHash(oldRoot), sdb) + p.sdb, err = newLoomStateDB(abm, common.BytesToHash(oldRoot), loomState.GetEVMStateDB()) } else { - p.sdb, err = state.New(common.BytesToHash(oldRoot), sdb) + p.sdb, err = state.New(common.BytesToHash(oldRoot), loomState.GetEVMStateDB()) } if err != nil { return nil, err From 2af31addbd054404348393d448ab42c120ebf301 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Wed, 9 Oct 2019 18:14:52 +0700 Subject: [PATCH 09/80] move EVMStateDB from readonlystate to state --- app.go | 4 ++-- evm/loomevm.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app.go b/app.go index 14e27d3c9c..dbac5d0cc0 100644 --- a/app.go +++ b/app.go @@ -42,7 +42,6 @@ type ReadOnlyState interface { Config() *cctypes.Config EnabledFeatures() []string GetMinBuildNumber() uint64 - GetEVMStateDB() gstate.Database } type State interface { @@ -54,6 +53,7 @@ type State interface { SetFeature(string, bool) SetMinBuildNumber(uint64) ChangeConfigSetting(name, value string) error + EVMStateDB() gstate.Database } type StoreState struct { @@ -150,7 +150,7 @@ func (s *StoreState) Context() context.Context { return s.ctx } -func (s *StoreState) GetEVMStateDB() gstate.Database { +func (s *StoreState) EVMStateDB() gstate.Database { ethDB := store.NewLoomEthDB(s, nil) s.trieDB.SetDiskDB(ethDB) evmStateDB := gstate.NewDatabase(ethDB) diff --git a/evm/loomevm.go b/evm/loomevm.go index 615624911a..0f10f395f1 100644 --- a/evm/loomevm.go +++ b/evm/loomevm.go @@ -61,9 +61,9 @@ func NewLoomEvm( var abm *evmAccountBalanceManager if accountBalanceManager != nil { abm = newEVMAccountBalanceManager(accountBalanceManager, loomState.Block().ChainID) - p.sdb, err = newLoomStateDB(abm, common.BytesToHash(oldRoot), loomState.GetEVMStateDB()) + p.sdb, err = newLoomStateDB(abm, common.BytesToHash(oldRoot), loomState.EVMStateDB()) } else { - p.sdb, err = state.New(common.BytesToHash(oldRoot), loomState.GetEVMStateDB()) + p.sdb, err = state.New(common.BytesToHash(oldRoot), loomState.EVMStateDB()) } if err != nil { return nil, err From 28376ff5e8a8f6ca75c5c4760fe47e297f97d4bd Mon Sep 17 00:00:00 2001 From: Pathorn Date: Fri, 11 Oct 2019 16:47:00 +0700 Subject: [PATCH 10/80] refactor --- app.go | 57 +++--------------- app_test.go | 2 +- .../sample_go_contract_test.go | 2 +- cmd/loom/db/evm.go | 14 ++--- cmd/loom/loom.go | 39 +++++++------ evm/evm_test.go | 8 +-- evm/loomevm.go | 26 +++++---- evm/noevm.go | 2 + integration_tests/ethcoin_evm_test.go | 6 +- plugin/fake_context.go | 10 +--- plugin/vm.go | 12 +++- plugin/vm_test.go | 8 +-- rpc/query_server.go | 9 +-- store/evmstore.go | 58 ++++++++++++++++--- store/evmstore_test.go | 8 +-- store/loomethdb.go | 4 +- store/multi_writer_app_store_test.go | 2 +- 17 files changed, 141 insertions(+), 126 deletions(-) diff --git a/app.go b/app.go index dbac5d0cc0..539c8b3625 100644 --- a/app.go +++ b/app.go @@ -7,7 +7,6 @@ import ( "fmt" "time" - gcommon "github.com/ethereum/go-ethereum/common" gstate "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/trie" "github.com/loomnetwork/go-loom/config" @@ -53,7 +52,6 @@ type State interface { SetFeature(string, bool) SetMinBuildNumber(uint64) ChangeConfigSetting(name, value string) error - EVMStateDB() gstate.Database } type StoreState struct { @@ -105,11 +103,6 @@ func (s *StoreState) WithOnChainConfig(config *cctypes.Config) *StoreState { return s } -func (s *StoreState) WithTrieDB(trieDB *trie.Database) *StoreState { - s.trieDB = trieDB - return s -} - func (s *StoreState) Range(prefix []byte) plugin.RangeData { return s.store.Range(prefix) } @@ -384,6 +377,7 @@ type Application struct { EventHandler ReceiptHandlerProvider EvmAuxStore *evmaux.EvmAuxStore + EvmStore *store.EvmStore blockindex.BlockIndexStore CreateValidatorManager ValidatorsManagerFactoryFunc CreateChainConfigManager ChainConfigManagerFactoryFunc @@ -541,7 +535,7 @@ func (a *Application) BeginBlock(req abci.RequestBeginBlock) abci.ResponseBeginB a.curBlockHeader, a.curBlockHash, a.GetValidatorSet, - ).WithOnChainConfig(a.config).WithTrieDB(a.TrieDB) + ).WithOnChainConfig(a.config) contractUpkeepHandler, err := a.CreateContractUpkeepHandler(upkeepState) if err != nil { panic(err) @@ -561,7 +555,7 @@ func (a *Application) BeginBlock(req abci.RequestBeginBlock) abci.ResponseBeginB a.curBlockHeader, nil, a.GetValidatorSet, - ).WithOnChainConfig(a.config).WithTrieDB(a.TrieDB) + ).WithOnChainConfig(a.config) validatorManager, err := a.CreateValidatorManager(state) if err != registry.ErrNotFound { @@ -629,7 +623,7 @@ func (a *Application) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock { a.curBlockHeader, nil, a.GetValidatorSet, - ).WithOnChainConfig(a.config).WithTrieDB(a.TrieDB) + ).WithOnChainConfig(a.config) validatorManager, err := a.CreateValidatorManager(state) if err != registry.ErrNotFound { @@ -684,7 +678,7 @@ func (a *Application) CheckTx(txBytes []byte) abci.ResponseCheckTx { a.curBlockHeader, a.curBlockHash, a.GetValidatorSet, - ).WithOnChainConfig(a.config).WithTrieDB(a.TrieDB) + ).WithOnChainConfig(a.config) // Receipts & events generated in CheckTx must be discarded since the app state changes they // reflect aren't persisted. @@ -721,7 +715,7 @@ func (a *Application) DeliverTx(txBytes []byte) abci.ResponseDeliverTx { a.curBlockHeader, a.curBlockHash, a.GetValidatorSet, - ).WithOnChainConfig(a.config).WithTrieDB(a.TrieDB) + ).WithOnChainConfig(a.config) var r abci.ResponseDeliverTx @@ -754,7 +748,7 @@ func (a *Application) processTx(storeTx store.KVStoreTx, txBytes []byte, isCheck a.curBlockHeader, a.curBlockHash, a.GetValidatorSet, - ).WithOnChainConfig(a.config).WithTrieDB(a.TrieDB) + ).WithOnChainConfig(a.config) receiptHandler := a.ReceiptHandlerProvider.Store() defer receiptHandler.DiscardCurrentReceipt() @@ -807,7 +801,7 @@ func (a *Application) deliverTx2(storeTx store.KVStoreTx, txBytes []byte) abci.R a.curBlockHeader, a.curBlockHash, a.GetValidatorSet, - ).WithOnChainConfig(a.config).WithTrieDB(a.TrieDB) + ).WithOnChainConfig(a.config) receiptHandler := a.ReceiptHandlerProvider.Store() defer receiptHandler.DiscardCurrentReceipt() @@ -865,39 +859,6 @@ func (a *Application) Commit() abci.ResponseCommit { committedBlockCount.With(lvs...).Add(1) commitBlockLatency.With(lvs...).Observe(time.Since(begin).Seconds()) }(time.Now()) - - state := NewStoreState( - context.Background(), - a.Store, - a.curBlockHeader, - a.curBlockHash, - a.GetValidatorSet, - ) - curHeight := a.curBlockHeader.GetHeight() - - flushInterval := a.FlushInterval - if flushInterval == 0 { - cfg := state.Config() - flushInterval = int64(cfg.GetAppStore().GetIAVLFlushInterval()) - } else if flushInterval == -1 { - flushInterval = 0 - } - - // Only commit Patricia tree every N blocks - if flushInterval == 0 || curHeight%flushInterval == 0 { - evmRoot := state.Get(util.PrefixKey(vmPrefix, rootKey)) - if len(evmRoot) > 0 && bytes.Compare(a.lastSavedEVMRoot, evmRoot) != 0 { - ethDB := store.NewLoomEthDB(state, nil) - a.TrieDB.SetDiskDB(ethDB) - if err := a.TrieDB.Commit(gcommon.BytesToHash(evmRoot), false); err != nil { - panic(err) - } - a.lastSavedEVMRoot = evmRoot - a.TrieDB = trie.NewDatabase(ethDB) - a.Store.Set(evmRootKey(curHeight), evmRoot) - } - } - appHash, _, err := a.Store.SaveVersion() if err != nil { panic(err) @@ -962,5 +923,5 @@ func (a *Application) ReadOnlyState() State { a.lastBlockHeader, nil, // TODO: last block hash! a.GetValidatorSet, - ).WithTrieDB(a.TrieDB) + ) } diff --git a/app_test.go b/app_test.go index 9b3e04f435..7ece2cfb20 100644 --- a/app_test.go +++ b/app_test.go @@ -60,7 +60,7 @@ func mockMultiWriterStore(flushInterval int64) (*store.MultiWriterAppStore, erro return nil, err } memDb, _ = db.LoadMemDB() - evmStore := store.NewEvmStore(memDb, 100) + evmStore := store.NewEvmStore(memDb, 100, 0) multiWriterStore, err := store.NewMultiWriterAppStore(iavlStore, evmStore, false) if err != nil { return nil, err diff --git a/builtin/plugins/sample_go_contract/sample_go_contract_test.go b/builtin/plugins/sample_go_contract/sample_go_contract_test.go index b125518992..76b3ae85ba 100644 --- a/builtin/plugins/sample_go_contract/sample_go_contract_test.go +++ b/builtin/plugins/sample_go_contract/sample_go_contract_test.go @@ -59,7 +59,7 @@ func deployContractToEVM(ctx *plugin.FakeContextWithEVM, filename string, caller byteCode := common.FromHex(string(hexByteCode)) byteCode, err = hex.DecodeString(string(hexByteCode)) - vm := evm.NewLoomVm(ctx.State, nil, nil, nil, false) + vm := evm.NewLoomVm(ctx.State, nil, nil, nil, nil, false) _, contractAddr, err = vm.Create(caller, byteCode, loom.NewBigUIntFromInt(0)) if err != nil { return contractAddr, err diff --git a/cmd/loom/db/evm.go b/cmd/loom/db/evm.go index 381001b489..3e4a5c42ff 100644 --- a/cmd/loom/db/evm.go +++ b/cmd/loom/db/evm.go @@ -107,8 +107,8 @@ func newDumpEVMStateCommand() *cobra.Command { return err } } - - vm, err := evm.NewLoomEvm(state, accountBalanceManager, nil, false) + // TODO: this needs EVMStore + vm, err := evm.NewLoomEvm(state, nil, accountBalanceManager, nil, false) if err != nil { return err } @@ -154,7 +154,7 @@ func newDumpEVMStateMultiWriterAppStoreCommand() *cobra.Command { if err != nil { return err } - evmStore := store.NewEvmStore(evmDB, 100) + evmStore := store.NewEvmStore(evmDB, 100, cfg.AppStore.IAVLFlushInterval) if err := evmStore.LoadVersion(iavlStore.Version()); err != nil { return err } @@ -220,8 +220,8 @@ func newDumpEVMStateMultiWriterAppStoreCommand() *cobra.Command { return err } } - - vm, err := evm.NewLoomEvm(state, accountBalanceManager, nil, false) + // TODO: Fix this, it needs EVMStore + vm, err := evm.NewLoomEvm(state, nil, accountBalanceManager, nil, false) if err != nil { return err } @@ -262,7 +262,7 @@ func newDumpEVMStateFromEvmDB() *cobra.Command { return err } - evmStore := store.NewEvmStore(evmDB, 100) + evmStore := store.NewEvmStore(evmDB, 100, cfg.AppStore.IAVLFlushInterval) if err := evmStore.LoadVersion(appHeight); err != nil { return err } @@ -364,7 +364,7 @@ func newGetEvmHeightCommand() *cobra.Command { } defer db.Close() - evmStore := store.NewEvmStore(db, 100) + evmStore := store.NewEvmStore(db, 100, 0) if err := evmStore.LoadVersion(math.MaxInt64); err != nil { return err } diff --git a/cmd/loom/loom.go b/cmd/loom/loom.go index 41aa22b84c..2daf60dd33 100644 --- a/cmd/loom/loom.go +++ b/cmd/loom/loom.go @@ -607,12 +607,15 @@ func destroyBlockIndexDB(cfg *config.Config) error { return nil } -func loadAppStore(cfg *config.Config, logger *loom.Logger, targetVersion int64) (store.VersionedKVStore, error) { +func loadAppStore( + cfg *config.Config, logger *loom.Logger, targetVersion int64, +) (store.VersionedKVStore, *store.EvmStore, error) { db, err := cdb.LoadDB( - cfg.DBBackend, cfg.DBName, cfg.RootPath(), cfg.DBBackendConfig.CacheSizeMegs, cfg.DBBackendConfig.WriteBufferMegs, cfg.Metrics.Database, + cfg.DBBackend, cfg.DBName, cfg.RootPath(), cfg.DBBackendConfig.CacheSizeMegs, + cfg.DBBackendConfig.WriteBufferMegs, cfg.Metrics.Database, ) if err != nil { - return nil, err + return nil, nil, err } if cfg.AppStore.CompactOnLoad { @@ -626,6 +629,7 @@ func loadAppStore(cfg *config.Config, logger *loom.Logger, targetVersion int64) } var appStore store.VersionedKVStore + var evmStore *store.EvmStore if cfg.AppStore.Version == 1 { // TODO: cleanup these hardcoded numbers if cfg.AppStore.PruneInterval > int64(0) { logger.Info("Loading Pruning IAVL Store") @@ -637,49 +641,49 @@ func loadAppStore(cfg *config.Config, logger *loom.Logger, targetVersion int64) FlushInterval: cfg.AppStore.IAVLFlushInterval, }) if err != nil { - return nil, err + return nil, nil, err } } else { logger.Info("Loading IAVL Store") appStore, err = store.NewIAVLStore(db, cfg.AppStore.MaxVersions, targetVersion, cfg.AppStore.IAVLFlushInterval) if err != nil { - return nil, err + return nil, nil, err } } } else if cfg.AppStore.Version == 3 { logger.Info("Loading Multi-Writer App Store") iavlStore, err := store.NewIAVLStore(db, cfg.AppStore.MaxVersions, targetVersion, cfg.AppStore.IAVLFlushInterval) if err != nil { - return nil, err + return nil, nil, err } - evmStore, err := loadEvmStore(cfg, iavlStore.Version()) + evmStore, err = loadEvmStore(cfg, iavlStore.Version()) if err != nil { - return nil, err + return nil, nil, err } appStore, err = store.NewMultiWriterAppStore(iavlStore, evmStore, cfg.AppStore.SaveEVMStateToIAVL) if err != nil { - return nil, err + return nil, nil, err } } else { - return nil, errors.New("Invalid AppStore.Version config setting") + return nil, nil, errors.New("Invalid AppStore.Version config setting") } if cfg.LogStateDB { appStore, err = store.NewLogStore(appStore) if err != nil { - return nil, err + return nil, nil, err } } if cfg.CachingStoreConfig.CachingEnabled { appStore, err = store.NewVersionedCachingStore(appStore, cfg.CachingStoreConfig, appStore.Version()) if err != nil { - return nil, err + return nil, nil, err } logger.Info("VersionedCachingStore enabled") } - return appStore, nil + return appStore, evmStore, nil } func loadEventStore(cfg *config.Config, logger *loom.Logger) (store.EventStore, error) { @@ -710,7 +714,7 @@ func loadEvmStore(cfg *config.Config, targetVersion int64) (*store.EvmStore, err if err != nil { return nil, err } - evmStore := store.NewEvmStore(db, evmStoreCfg.NumCachedRoots) + evmStore := store.NewEvmStore(db, evmStoreCfg.NumCachedRoots, cfg.AppStore.IAVLFlushInterval) if err := evmStore.LoadVersion(targetVersion); err != nil { return nil, err } @@ -726,8 +730,7 @@ func loadApp( ) (*loomchain.Application, error) { logger := log.Root - appStore, err := loadAppStore(cfg, log.Default, appHeight) - + appStore, evmStore, err := loadAppStore(cfg, log.Default, appHeight) if err != nil { return nil, err } @@ -834,7 +837,7 @@ func loadApp( return nil, err } } - return evm.NewLoomVm(state, eventHandler, receiptHandlerProvider.Writer(), createABM, cfg.EVMDebugEnabled), nil + return evm.NewLoomVm(state, evmStore, eventHandler, receiptHandlerProvider.Writer(), createABM, cfg.EVMDebugEnabled), nil }) } store.LogEthDBBatch = cfg.LogEthDbBatch @@ -1126,6 +1129,7 @@ func loadApp( EventStore: eventStore, GetValidatorSet: getValidatorSet, EvmAuxStore: evmAuxStore, + EvmStore: evmStore, ReceiptsVersion: cfg.ReceiptsVersion, TrieDB: trie.NewDatabase(nil), FlushInterval: cfg.AppStore.IAVLFlushInterval, @@ -1276,6 +1280,7 @@ func initQueryService( EventStore: app.EventStore, AuthCfg: cfg.Auth, EvmAuxStore: app.EvmAuxStore, + EvmStore: app.EvmStore, } bus := &rpc.QueryEventBus{ Subs: *app.EventHandler.SubscriptionSet(), diff --git a/evm/evm_test.go b/evm/evm_test.go index 01e13c4fd6..5f2091ce62 100644 --- a/evm/evm_test.go +++ b/evm/evm_test.go @@ -13,16 +13,14 @@ import ( "testing" "time" - "github.com/ethereum/go-ethereum/trie" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" ethvm "github.com/ethereum/go-ethereum/core/vm" "github.com/loomnetwork/go-loom" "github.com/loomnetwork/loomchain" "github.com/loomnetwork/loomchain/features" - "github.com/loomnetwork/loomchain/store" lvm "github.com/loomnetwork/loomchain/vm" + "github.com/loomnetwork/tendermint/libs/db" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" ) @@ -42,9 +40,7 @@ func mockState() loomchain.State { header := abci.Header{} header.Height = BlockHeight header.Time = blockTime - memDB := store.NewMemStore() - trieDB := trie.NewDatabase(store.NewLoomEthDB(memDB, nil)) - return loomchain.NewStoreState(context.Background(), memDB, header, nil, nil).WithTrieDB(trieDB) + return loomchain.NewStoreState(context.Background(), db.NewMemDB(), header, nil, nil) } func TestProcessDeployTx(t *testing.T) { diff --git a/evm/loomevm.go b/evm/loomevm.go index 0f10f395f1..35a736e5fb 100644 --- a/evm/loomevm.go +++ b/evm/loomevm.go @@ -48,22 +48,25 @@ type LoomEvm struct { // TODO: this doesn't need to be exported, rename to newLoomEvmWithState func NewLoomEvm( - loomState loomchain.State, accountBalanceManager AccountBalanceManager, + loomState loomchain.State, evmStore *store.EvmStore, accountBalanceManager AccountBalanceManager, logContext *store.EthDBLogContext, debug bool, ) (*LoomEvm, error) { p := new(LoomEvm) - p.db = store.NewLoomEthDB(loomState, logContext) + ethDB := store.NewLoomEthDB(evmStore, logContext) + p.db = ethDB oldRoot, err := p.db.Get(rootKey) if err != nil { return nil, err } + stateDB := state.NewDatabase(ethDB) + stateDB.SetTrieDB(evmStore.TrieDB()) var abm *evmAccountBalanceManager if accountBalanceManager != nil { abm = newEVMAccountBalanceManager(accountBalanceManager, loomState.Block().ChainID) - p.sdb, err = newLoomStateDB(abm, common.BytesToHash(oldRoot), loomState.EVMStateDB()) + p.sdb, err = newLoomStateDB(abm, common.BytesToHash(oldRoot), stateDB) } else { - p.sdb, err = state.New(common.BytesToHash(oldRoot), loomState.EVMStateDB()) + p.sdb, err = state.New(common.BytesToHash(oldRoot), stateDB) } if err != nil { return nil, err @@ -106,13 +109,14 @@ var LoomVmFactory = func(state loomchain.State) (vm.VM, error) { nil, ) receiptHandler := receiptHandlerProvider.Writer() - return NewLoomVm(state, eventHandler, receiptHandler, nil, debug), nil + return NewLoomVm(state, nil, eventHandler, receiptHandler, nil, debug), nil } // LoomVm implements the loomchain/vm.VM interface using the EVM. // TODO: rename to LoomEVM type LoomVm struct { state loomchain.State + evmStore *store.EvmStore receiptHandler loomchain.WriteReceiptHandler createABM AccountBalanceManagerFactoryFunc debug bool @@ -120,6 +124,7 @@ type LoomVm struct { func NewLoomVm( loomState loomchain.State, + evmStore *store.EvmStore, eventHandler loomchain.EventHandler, receiptHandler loomchain.WriteReceiptHandler, createABM AccountBalanceManagerFactoryFunc, @@ -127,6 +132,7 @@ func NewLoomVm( ) vm.VM { return &LoomVm{ state: loomState, + evmStore: evmStore, receiptHandler: receiptHandler, createABM: createABM, debug: debug, @@ -142,7 +148,7 @@ func (lvm LoomVm) accountBalanceManager(readOnly bool) AccountBalanceManager { func (lvm LoomVm) Create(caller loom.Address, code []byte, value *loom.BigUInt) ([]byte, loom.Address, error) { logContext := store.NewEthDBLogContext(lvm.state.Block().Height, loom.Address{}, caller) - levm, err := NewLoomEvm(lvm.state, lvm.accountBalanceManager(false), logContext, lvm.debug) + levm, err := NewLoomEvm(lvm.state, lvm.evmStore, lvm.accountBalanceManager(false), logContext, lvm.debug) if err != nil { return nil, loom.Address{}, err } @@ -201,7 +207,7 @@ func (lvm LoomVm) Create(caller loom.Address, code []byte, value *loom.BigUInt) func (lvm LoomVm) Call(caller, addr loom.Address, input []byte, value *loom.BigUInt) ([]byte, error) { logContext := store.NewEthDBLogContext(lvm.state.Block().Height, addr, caller) - levm, err := NewLoomEvm(lvm.state, lvm.accountBalanceManager(false), logContext, lvm.debug) + levm, err := NewLoomEvm(lvm.state, lvm.evmStore, lvm.accountBalanceManager(false), logContext, lvm.debug) if err != nil { return nil, err } @@ -249,7 +255,7 @@ func (lvm LoomVm) Call(caller, addr loom.Address, input []byte, value *loom.BigU } func (lvm LoomVm) StaticCall(caller, addr loom.Address, input []byte) ([]byte, error) { - levm, err := NewLoomEvm(lvm.state, lvm.accountBalanceManager(true), nil, lvm.debug) + levm, err := NewLoomEvm(lvm.state, lvm.evmStore, lvm.accountBalanceManager(true), nil, lvm.debug) if err != nil { return nil, err } @@ -257,7 +263,7 @@ func (lvm LoomVm) StaticCall(caller, addr loom.Address, input []byte) ([]byte, e } func (lvm LoomVm) GetCode(addr loom.Address) ([]byte, error) { - levm, err := NewLoomEvm(lvm.state, nil, nil, lvm.debug) + levm, err := NewLoomEvm(lvm.state, lvm.evmStore, nil, nil, lvm.debug) if err != nil { return nil, err } @@ -265,7 +271,7 @@ func (lvm LoomVm) GetCode(addr loom.Address) ([]byte, error) { } func (lvm LoomVm) GetStorageAt(addr loom.Address, key []byte) ([]byte, error) { - levm, err := NewLoomEvm(lvm.state, nil, nil, lvm.debug) + levm, err := NewLoomEvm(lvm.state, lvm.evmStore, nil, nil, lvm.debug) if err != nil { return nil, err } diff --git a/evm/noevm.go b/evm/noevm.go index 80427c9327..95956810ca 100644 --- a/evm/noevm.go +++ b/evm/noevm.go @@ -4,6 +4,7 @@ package evm import ( "github.com/loomnetwork/loomchain" + "github.com/loomnetwork/loomchain/store" lvm "github.com/loomnetwork/loomchain/vm" ) @@ -16,6 +17,7 @@ const EVMEnabled = false func NewLoomVm( loomState loomchain.State, + evmStore *store.EvmStore, eventHandler loomchain.EventHandler, receiptHandler loomchain.WriteReceiptHandler, createABM AccountBalanceManagerFactoryFunc, diff --git a/integration_tests/ethcoin_evm_test.go b/integration_tests/ethcoin_evm_test.go index fbbd24b9f2..01b1351485 100644 --- a/integration_tests/ethcoin_evm_test.go +++ b/integration_tests/ethcoin_evm_test.go @@ -208,7 +208,7 @@ func (c *ethCoinIntegrationTestHelper) callEVM( if err != nil { return err } - vm := evm.NewLoomVm(ctx.State, nil, nil, ctx.AccountBalanceManager, false) + vm := evm.NewLoomVm(ctx.State, nil, nil, nil, ctx.AccountBalanceManager, false) _, err = vm.Call(ctx.Message().Sender, c.Address, input, loom.NewBigUIntFromInt(0)) if err != nil { return err @@ -223,7 +223,7 @@ func (c *ethCoinIntegrationTestHelper) staticCallEVM( if err != nil { return err } - vm := evm.NewLoomVm(ctx.State, nil, nil, ctx.AccountBalanceManager, false) + vm := evm.NewLoomVm(ctx.State, nil, nil, nil, ctx.AccountBalanceManager, false) output, err := vm.StaticCall(ctx.Message().Sender, c.Address, input) if err != nil { return err @@ -239,7 +239,7 @@ func deployContractToEVM(ctx *plugin.FakeContextWithEVM, filename string, caller } byteCode := common.FromHex(string(hexByteCode)) - vm := evm.NewLoomVm(ctx.State, nil, nil, nil, false) + vm := evm.NewLoomVm(ctx.State, nil, nil, nil, nil, false) _, contractAddr, err = vm.Create(caller, byteCode, loom.NewBigUIntFromInt(0)) if err != nil { return contractAddr, err diff --git a/plugin/fake_context.go b/plugin/fake_context.go index 82eff53326..f553c31e23 100644 --- a/plugin/fake_context.go +++ b/plugin/fake_context.go @@ -6,14 +6,11 @@ import ( "context" "time" - "github.com/ethereum/go-ethereum/trie" - loom "github.com/loomnetwork/go-loom" "github.com/loomnetwork/go-loom/plugin" "github.com/loomnetwork/go-loom/types" "github.com/loomnetwork/loomchain" levm "github.com/loomnetwork/loomchain/evm" - "github.com/loomnetwork/loomchain/store" abci "github.com/tendermint/tendermint/abci/types" ) @@ -37,8 +34,7 @@ func CreateFakeContextWithEVM(caller, address loom.Address) *FakeContextWithEVM Time: block.Time.Unix(), }, ) - trieDB := trie.NewDatabase(store.NewLoomEthDB(ctx, nil)) - state := loomchain.NewStoreState(context.Background(), ctx, block, nil, nil).WithTrieDB(trieDB) + state := loomchain.NewStoreState(context.Background(), ctx, block, nil, nil) return &FakeContextWithEVM{ FakeContext: ctx, State: state, @@ -107,7 +103,7 @@ func (c *FakeContextWithEVM) CallEVM(addr loom.Address, input []byte, value *loo if c.useAccountBalanceManager { createABM = c.AccountBalanceManager } - vm := levm.NewLoomVm(c.State, nil, nil, createABM, false) + vm := levm.NewLoomVm(c.State, nil, nil, nil, createABM, false) return vm.Call(c.ContractAddress(), addr, input, value) } @@ -116,7 +112,7 @@ func (c *FakeContextWithEVM) StaticCallEVM(addr loom.Address, input []byte) ([]b if c.useAccountBalanceManager { createABM = c.AccountBalanceManager } - vm := levm.NewLoomVm(c.State, nil, nil, createABM, false) + vm := levm.NewLoomVm(c.State, nil, nil, nil, createABM, false) return vm.StaticCall(c.ContractAddress(), addr, input) } diff --git a/plugin/vm.go b/plugin/vm.go index 4a12c17d47..c072e3cac1 100644 --- a/plugin/vm.go +++ b/plugin/vm.go @@ -19,6 +19,7 @@ import ( "github.com/loomnetwork/loomchain/auth" levm "github.com/loomnetwork/loomchain/evm" "github.com/loomnetwork/loomchain/registry" + "github.com/loomnetwork/loomchain/store" "github.com/loomnetwork/loomchain/vm" "github.com/pkg/errors" ) @@ -36,6 +37,7 @@ var ( type PluginVM struct { Loader Loader State loomchain.State + EvmStore *store.EvmStore Registry registry.Registry EventHandler loomchain.EventHandler logger *loom.Logger @@ -69,6 +71,11 @@ func NewPluginVM( var _ vm.VM = &PluginVM{} +func (vm *PluginVM) WithEvmStore(evmStore *store.EvmStore) *PluginVM { + vm.EvmStore = evmStore + return vm +} + func (vm *PluginVM) CreateContractContext( caller, addr loom.Address, @@ -196,7 +203,7 @@ func (vm *PluginVM) CallEVM(caller, addr loom.Address, input []byte, value *loom return nil, err } } - evm := levm.NewLoomVm(vm.State, vm.EventHandler, vm.receiptWriter, createABM, false) + evm := levm.NewLoomVm(vm.State, vm.EvmStore, vm.EventHandler, vm.receiptWriter, createABM, false) return evm.Call(caller, addr, input, value) } @@ -209,7 +216,8 @@ func (vm *PluginVM) StaticCallEVM(caller, addr loom.Address, input []byte) ([]by return nil, err } } - evm := levm.NewLoomVm(vm.State, vm.EventHandler, vm.receiptWriter, createABM, false) + + evm := levm.NewLoomVm(vm.State, vm.EvmStore, vm.EventHandler, vm.receiptWriter, createABM, false) return evm.StaticCall(caller, addr, input) } diff --git a/plugin/vm_test.go b/plugin/vm_test.go index 51a6b4fdd7..3a052e0a72 100644 --- a/plugin/vm_test.go +++ b/plugin/vm_test.go @@ -10,8 +10,6 @@ import ( "testing" "time" - "github.com/ethereum/go-ethereum/trie" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" proto "github.com/gogo/protobuf/proto" @@ -139,14 +137,12 @@ func TestPluginVMContractContextCaller(t *testing.T) { Height: int64(34), Time: time.Unix(123456789, 0), } - memDB := store.NewMemStore() - trieDB := trie.NewDatabase(store.NewLoomEthDB(memDB, nil)) - state := loomchain.NewStoreState(context.Background(), memDB, block, nil, nil).WithTrieDB(trieDB) + state := loomchain.NewStoreState(context.Background(), store.NewMemStore(), block, nil, nil) createRegistry, err := registry.NewRegistryFactory(registry.LatestRegistryVersion) require.NoError(t, err) vm := NewPluginVM(loader, state, createRegistry(state), &fakeEventHandler{}, nil, nil, nil, nil) - evm := levm.NewLoomVm(state, nil, nil, nil, false) + evm := levm.NewLoomVm(state, nil, nil, nil, nil, false) // Deploy contracts owner := loom.RootAddress("chain") diff --git a/rpc/query_server.go b/rpc/query_server.go index 628b593243..63e650230c 100755 --- a/rpc/query_server.go +++ b/rpc/query_server.go @@ -127,6 +127,7 @@ type QueryServer struct { RPCListenAddress string store.BlockStore *evmaux.EvmAuxStore + *store.EvmStore blockindex.BlockIndexStore EventStore store.EventStore AuthCfg *auth.Config @@ -271,7 +272,7 @@ func (s *QueryServer) queryEvm(caller, contract loom.Address, query []byte) ([]b return nil, err } } - vm := levm.NewLoomVm(snapshot, nil, nil, createABM, false) + vm := levm.NewLoomVm(snapshot, s.EvmStore, nil, nil, createABM, false) return vm.StaticCall(callerAddr, contract, query) } @@ -312,7 +313,7 @@ func (s *QueryServer) GetEvmCode(contract string) ([]byte, error) { snapshot := s.StateProvider.ReadOnlyState() defer snapshot.Release() - vm := levm.NewLoomVm(snapshot, nil, nil, nil, false) + vm := levm.NewLoomVm(snapshot, s.EvmStore, nil, nil, nil, false) return vm.GetCode(contractAddr) } @@ -326,7 +327,7 @@ func (s *QueryServer) EthGetCode(address eth.Data, block eth.BlockHeight) (eth.D snapshot := s.StateProvider.ReadOnlyState() defer snapshot.Release() - evm := levm.NewLoomVm(snapshot, nil, nil, nil, false) + evm := levm.NewLoomVm(snapshot, s.EvmStore, nil, nil, nil, false) code, err := evm.GetCode(addr) if err != nil { return "", errors.Wrapf(err, "getting evm code for %v", address) @@ -1094,7 +1095,7 @@ func (s *QueryServer) EthGetStorageAt(local eth.Data, position string, block eth return "", errors.Wrapf(err, "unable to get storage at height %v", block) } - evm := levm.NewLoomVm(snapshot, nil, nil, nil, false) + evm := levm.NewLoomVm(snapshot, s.EvmStore, nil, nil, nil, false) storage, err := evm.GetStorageAt(address, ethcommon.HexToHash(position).Bytes()) if err != nil { return "", errors.Wrapf(err, "failed to get EVM storage at %v", address.Local.String()) diff --git a/store/evmstore.go b/store/evmstore.go index 66226ba496..71f28f546a 100644 --- a/store/evmstore.go +++ b/store/evmstore.go @@ -6,6 +6,9 @@ import ( "sort" "time" + "github.com/ethereum/go-ethereum/trie" + + gcommon "github.com/ethereum/go-ethereum/common" "github.com/go-kit/kit/metrics" kitprometheus "github.com/go-kit/kit/metrics/prometheus" lru "github.com/hashicorp/golang-lru" @@ -58,19 +61,24 @@ type EvmStore struct { lastSavedRoot []byte rootCache *lru.Cache version int64 + trieDB *trie.Database + flushInterval int64 } // NewEvmStore returns a new instance of the store backed by the given DB. -func NewEvmStore(evmDB db.DBWrapper, numCachedRoots int) *EvmStore { +func NewEvmStore(evmDB db.DBWrapper, numCachedRoots int, flushInterval int64) *EvmStore { rootCache, err := lru.New(numCachedRoots) if err != nil { panic(err) } evmStore := &EvmStore{ - evmDB: evmDB, - cache: make(map[string]cacheItem), - rootCache: rootCache, + evmDB: evmDB, + cache: make(map[string]cacheItem), + rootCache: rootCache, + flushInterval: flushInterval, } + ethDB := NewLoomEthDB(evmStore, nil) + evmStore.trieDB = trie.NewDatabase(ethDB) return evmStore } @@ -195,9 +203,37 @@ func (s *EvmStore) Commit(version int64) []byte { currentRoot = defaultRoot } - // only save default root, new valid root will be saved in app.Commit() - if !bytes.Equal(currentRoot, s.lastSavedRoot) && bytes.Equal(currentRoot, defaultRoot) { - s.Set(evmRootKey(version), currentRoot) + flushInterval := s.flushInterval + + // TODO: Rather than loading the on-chain config here the flush interval override should be passed + // in as a parameter to SaveVersion(). + if flushInterval == 0 { + cfg, err := LoadOnChainConfig(s) + if err != nil { + panic(errors.Wrap(err, "failed to load on-chain config")) + } + if cfg.GetAppStore().GetIAVLFlushInterval() != 0 { + flushInterval = int64(cfg.GetAppStore().GetIAVLFlushInterval()) + } + } else if flushInterval == -1 { + flushInterval = 0 + } + + // Only commit Patricia tree every N blocks + if flushInterval == 0 || version%flushInterval == 0 { + if len(currentRoot) > 0 && !bytes.Equal(defaultRoot, currentRoot) { + if err := s.trieDB.Commit(gcommon.BytesToHash(currentRoot), false); err != nil { + panic(err) + } + ethDB := NewLoomEthDB(s, nil) + s.trieDB = trie.NewDatabase(ethDB) + s.Set(evmRootKey(version), currentRoot) + } + + // We have to save default root + if bytes.Equal(defaultRoot, currentRoot) { + s.Set(evmRootKey(version), currentRoot) + } } s.rootCache.Add(version, currentRoot) @@ -241,6 +277,14 @@ func (s *EvmStore) Version() ([]byte, int64) { return s.rootHash, s.version } +func (s *EvmStore) TrieDB() *trie.Database { + return s.trieDB +} + +func (s *EvmStore) SetTrieDB(trieDB *trie.Database) { + s.trieDB = trieDB +} + func (s *EvmStore) getLastSavedRoot(targetVersion int64) ([]byte, int64) { start := util.PrefixKey(vmPrefix, evmRootPrefix) end := prefixRangeEnd(evmRootKey(targetVersion)) diff --git a/store/evmstore_test.go b/store/evmstore_test.go index c3ecafa5ac..ebb6bc1682 100644 --- a/store/evmstore_test.go +++ b/store/evmstore_test.go @@ -24,7 +24,7 @@ func (t *EvmStoreTestSuite) TestEvmStoreRangeAndCommit() { require := t.Require() evmDb, err := db.LoadMemDB() require.NoError(err) - evmStore := NewEvmStore(evmDb, 100) + evmStore := NewEvmStore(evmDb, 100, 0) for i := 0; i <= 100; i++ { key := []byte(fmt.Sprintf("Key%d", i)) evmStore.Set(key, key) @@ -64,7 +64,7 @@ func (t *EvmStoreTestSuite) TestEvmStoreBasicMethods() { // Test Get|Set|Has|Delete methods evmDb, err := db.LoadMemDB() require.NoError(err) - evmStore := NewEvmStore(evmDb, 100) + evmStore := NewEvmStore(evmDb, 100, 0) key1 := []byte("hello") key2 := []byte("hello2") value1 := []byte("world") @@ -92,7 +92,7 @@ func (t *EvmStoreTestSuite) TestEvmStoreRangePrefix() { // Test Range Prefix evmDb, err := db.LoadMemDB() require.NoError(err) - evmStore := NewEvmStore(evmDb, 100) + evmStore := NewEvmStore(evmDb, 100, 0) for i := 0; i <= 100; i++ { key := []byte(fmt.Sprintf("Key%d", i)) evmStore.Set(key, key) @@ -143,7 +143,7 @@ func (t *EvmStoreTestSuite) TestLoadVersionEvmStore() { evmDb.Set(evmRootKey(100), []byte{100}) evmDb.Set(evmRootKey(200), []byte{200}) - evmStore := NewEvmStore(evmDb, 100) + evmStore := NewEvmStore(evmDb, 100, 0) err = evmStore.LoadVersion(500) require.NoError(err) root, version := evmStore.Version() diff --git a/store/loomethdb.go b/store/loomethdb.go index c920f6a83b..552f8e01bf 100644 --- a/store/loomethdb.go +++ b/store/loomethdb.go @@ -39,9 +39,9 @@ type LoomEthDB struct { logContext *EthDBLogContext } -func NewLoomEthDB(_state KVStore, logContext *EthDBLogContext) *LoomEthDB { +func NewLoomEthDB(evmStore KVStore, logContext *EthDBLogContext) *LoomEthDB { return &LoomEthDB{ - state: PrefixKVStore(vmPrefix, _state), + state: PrefixKVStore(vmPrefix, evmStore), logContext: logContext, } } diff --git a/store/multi_writer_app_store_test.go b/store/multi_writer_app_store_test.go index 5ea33e2f64..ece6f79f34 100644 --- a/store/multi_writer_app_store_test.go +++ b/store/multi_writer_app_store_test.go @@ -342,7 +342,7 @@ func mockMultiWriterStore(flushInterval int64) (*MultiWriterAppStore, error) { return nil, err } memDb, _ = db.LoadMemDB() - evmStore := NewEvmStore(memDb, 100) + evmStore := NewEvmStore(memDb, 100, 0) multiWriterStore, err := NewMultiWriterAppStore(iavlStore, evmStore, false) if err != nil { return nil, err From 393c94e698332601358fb54365f382db9840df03 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Fri, 11 Oct 2019 16:52:42 +0700 Subject: [PATCH 11/80] clean up --- app.go | 27 --------------------------- cmd/loom/loom.go | 3 --- 2 files changed, 30 deletions(-) diff --git a/app.go b/app.go index 539c8b3625..c774bff1e7 100644 --- a/app.go +++ b/app.go @@ -7,7 +7,6 @@ import ( "fmt" "time" - gstate "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/trie" "github.com/loomnetwork/go-loom/config" "github.com/loomnetwork/go-loom/util" @@ -143,36 +142,15 @@ func (s *StoreState) Context() context.Context { return s.ctx } -func (s *StoreState) EVMStateDB() gstate.Database { - ethDB := store.NewLoomEthDB(s, nil) - s.trieDB.SetDiskDB(ethDB) - evmStateDB := gstate.NewDatabase(ethDB) - evmStateDB.SetTrieDB(s.trieDB) - return evmStateDB -} - const ( featurePrefix = "feature" MinBuildKey = "minbuild" ) -var ( - vmPrefix = []byte("vm") - rootKey = []byte("vmroot") - // This is the prefix of versioning Patricia roots - evmRootPrefix = []byte("evmroot") -) - func featureKey(featureName string) []byte { return util.PrefixKey([]byte(featurePrefix), []byte(featureName)) } -func evmRootKey(blockHeight int64) []byte { - b := make([]byte, 8) - binary.BigEndian.PutUint64(b, uint64(blockHeight)) - return util.PrefixKey(vmPrefix, []byte(evmRootPrefix), b) -} - func (s *StoreState) EnabledFeatures() []string { featuresFromState := s.Range([]byte(featurePrefix)) enabledFeatures := make([]string, 0, len(featuresFromState)) @@ -257,7 +235,6 @@ func (s *StoreState) WithContext(ctx context.Context) State { ctx: ctx, validators: s.validators, getValidatorSet: s.getValidatorSet, - trieDB: s.trieDB, } } @@ -268,7 +245,6 @@ func (s *StoreState) WithPrefix(prefix []byte) State { ctx: s.ctx, validators: s.validators, getValidatorSet: s.getValidatorSet, - trieDB: s.trieDB, } } @@ -389,9 +365,6 @@ type Application struct { config *cctypes.Config childTxRefs []evmaux.ChildTxRef // links Tendermint txs to EVM txs ReceiptsVersion int32 - TrieDB *trie.Database - FlushInterval int64 // commit Patricia trie to disk every N blocks - lastSavedEVMRoot []byte } var _ abci.Application = &Application{} diff --git a/cmd/loom/loom.go b/cmd/loom/loom.go index 2daf60dd33..61b3e9be5b 100644 --- a/cmd/loom/loom.go +++ b/cmd/loom/loom.go @@ -19,7 +19,6 @@ import ( "github.com/prometheus/client_golang/prometheus/push" "github.com/tendermint/tendermint/libs/db" - "github.com/ethereum/go-ethereum/trie" kitprometheus "github.com/go-kit/kit/metrics/prometheus" "github.com/gogo/protobuf/proto" "github.com/loomnetwork/go-loom" @@ -1131,8 +1130,6 @@ func loadApp( EvmAuxStore: evmAuxStore, EvmStore: evmStore, ReceiptsVersion: cfg.ReceiptsVersion, - TrieDB: trie.NewDatabase(nil), - FlushInterval: cfg.AppStore.IAVLFlushInterval, }, nil } From 15a22640df99c23d899cb01e87a06ac39d9de414 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Fri, 11 Oct 2019 17:28:44 +0700 Subject: [PATCH 12/80] update tg test --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a7bd4e65ea..f7605e78f2 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ BINANCE_TGORACLE_DIR=$(GOPATH)/src/$(PKG_BINANCE_TGORACLE) # specific commit. GO_LOOM_GIT_REV = HEAD # Specifies the loomnetwork/transfer-gateway branch/revision to use. -TG_GIT_REV = HEAD +TG_GIT_REV = in-memory-evmstate # loomnetwork/go-ethereum loomchain branch ETHEREUM_GIT_REV = in-memory-statedb # use go-plugin we get 'timeout waiting for connection info' error From 0ca6a892f29661a73b0a0f21f4453dba49c960ec Mon Sep 17 00:00:00 2001 From: Pathorn Date: Fri, 11 Oct 2019 17:38:10 +0700 Subject: [PATCH 13/80] revert change --- evm/evm_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evm/evm_test.go b/evm/evm_test.go index 5f2091ce62..217d2c3f5d 100644 --- a/evm/evm_test.go +++ b/evm/evm_test.go @@ -19,8 +19,8 @@ import ( "github.com/loomnetwork/go-loom" "github.com/loomnetwork/loomchain" "github.com/loomnetwork/loomchain/features" + "github.com/loomnetwork/loomchain/store" lvm "github.com/loomnetwork/loomchain/vm" - "github.com/loomnetwork/tendermint/libs/db" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" ) @@ -40,7 +40,7 @@ func mockState() loomchain.State { header := abci.Header{} header.Height = BlockHeight header.Time = blockTime - return loomchain.NewStoreState(context.Background(), db.NewMemDB(), header, nil, nil) + return loomchain.NewStoreState(context.Background(), store.NewMemStore(), header, nil, nil) } func TestProcessDeployTx(t *testing.T) { From 7f92b444fab0a1fe2914cfe67ba9953d3c7445cd Mon Sep 17 00:00:00 2001 From: Pathorn Date: Thu, 17 Oct 2019 13:15:55 +0700 Subject: [PATCH 14/80] only save default root once --- store/evmstore.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/store/evmstore.go b/store/evmstore.go index 71f28f546a..bde2dc7219 100644 --- a/store/evmstore.go +++ b/store/evmstore.go @@ -221,7 +221,7 @@ func (s *EvmStore) Commit(version int64) []byte { // Only commit Patricia tree every N blocks if flushInterval == 0 || version%flushInterval == 0 { - if len(currentRoot) > 0 && !bytes.Equal(defaultRoot, currentRoot) { + if !bytes.Equal(defaultRoot, currentRoot) && !bytes.Equal(currentRoot, s.lastSavedRoot) { if err := s.trieDB.Commit(gcommon.BytesToHash(currentRoot), false); err != nil { panic(err) } @@ -231,7 +231,7 @@ func (s *EvmStore) Commit(version int64) []byte { } // We have to save default root - if bytes.Equal(defaultRoot, currentRoot) { + if bytes.Equal(defaultRoot, currentRoot) && !bytes.Equal(currentRoot, s.lastSavedRoot) { s.Set(evmRootKey(version), currentRoot) } } From 749052c8228f677c84cdb32d5eb33c211d8e7f2b Mon Sep 17 00:00:00 2001 From: Pathorn Date: Thu, 17 Oct 2019 14:44:03 +0700 Subject: [PATCH 15/80] remove legacy evm dump commands and clean up --- app.go | 2 - .../sample_go_contract_test.go | 3 +- cmd/loom/db/db.go | 2 - cmd/loom/db/evm.go | 236 +----------------- evm/loomevm.go | 3 - plugin/fake_context.go | 22 +- 6 files changed, 23 insertions(+), 245 deletions(-) diff --git a/app.go b/app.go index c774bff1e7..ff3a10baf1 100644 --- a/app.go +++ b/app.go @@ -7,7 +7,6 @@ import ( "fmt" "time" - "github.com/ethereum/go-ethereum/trie" "github.com/loomnetwork/go-loom/config" "github.com/loomnetwork/go-loom/util" "github.com/loomnetwork/loomchain/eth/utils" @@ -60,7 +59,6 @@ type StoreState struct { validators loom.ValidatorSet getValidatorSet GetValidatorSet config *cctypes.Config - trieDB *trie.Database } var _ = State(&StoreState{}) diff --git a/builtin/plugins/sample_go_contract/sample_go_contract_test.go b/builtin/plugins/sample_go_contract/sample_go_contract_test.go index 76b3ae85ba..bd14e05fc4 100644 --- a/builtin/plugins/sample_go_contract/sample_go_contract_test.go +++ b/builtin/plugins/sample_go_contract/sample_go_contract_test.go @@ -58,8 +58,7 @@ func deployContractToEVM(ctx *plugin.FakeContextWithEVM, filename string, caller } byteCode := common.FromHex(string(hexByteCode)) byteCode, err = hex.DecodeString(string(hexByteCode)) - - vm := evm.NewLoomVm(ctx.State, nil, nil, nil, nil, false) + vm := evm.NewLoomVm(ctx.State, ctx.EvmStore, nil, nil, nil, false) _, contractAddr, err = vm.Create(caller, byteCode, loom.NewBigUIntFromInt(0)) if err != nil { return contractAddr, err diff --git a/cmd/loom/db/db.go b/cmd/loom/db/db.go index 49b5751cc0..e292f2d6f5 100644 --- a/cmd/loom/db/db.go +++ b/cmd/loom/db/db.go @@ -14,8 +14,6 @@ func NewDBCommand() *cobra.Command { cmd.AddCommand( newPruneDBCommand(), newCompactDBCommand(), - newDumpEVMStateCommand(), - newDumpEVMStateMultiWriterAppStoreCommand(), newDumpEVMStateFromEvmDB(), newGetEvmHeightCommand(), newGetAppHeightCommand(), diff --git a/cmd/loom/db/evm.go b/cmd/loom/db/evm.go index 3e4a5c42ff..4fb486649d 100644 --- a/cmd/loom/db/evm.go +++ b/cmd/loom/db/evm.go @@ -3,7 +3,6 @@ package db import ( - "context" "fmt" "math" "path" @@ -14,235 +13,18 @@ import ( gstate "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" - "github.com/loomnetwork/loomchain" "github.com/loomnetwork/loomchain/cmd/loom/common" cdb "github.com/loomnetwork/loomchain/db" - "github.com/loomnetwork/loomchain/events" - "github.com/loomnetwork/loomchain/evm" - "github.com/loomnetwork/loomchain/log" - "github.com/loomnetwork/loomchain/plugin" - "github.com/loomnetwork/loomchain/receipts" - registry "github.com/loomnetwork/loomchain/registry/factory" "github.com/loomnetwork/loomchain/store" "github.com/spf13/cobra" - abci "github.com/tendermint/tendermint/abci/types" - dbm "github.com/tendermint/tendermint/libs/db" ) -func newDumpEVMStateCommand() *cobra.Command { - var appHeight int64 - - cmd := &cobra.Command{ - Use: "evm-dump", - Short: "Dumps EVM state stored at a specific block height", - RunE: func(cmd *cobra.Command, args []string) error { - cfg, err := common.ParseConfig() - if err != nil { - return err - } - - db, err := dbm.NewGoLevelDB(cfg.DBName, cfg.RootPath()) - if err != nil { - return err - } - appStore, err := store.NewIAVLStore(db, 0, appHeight, 0) - if err != nil { - return err - } - - eventHandler := loomchain.NewDefaultEventHandler(events.NewLogEventDispatcher()) - - regVer, err := registry.RegistryVersionFromInt(cfg.RegistryVersion) - if err != nil { - return err - } - createRegistry, err := registry.NewRegistryFactory(regVer) - if err != nil { - return err - } - - receiptHandlerProvider := receipts.NewReceiptHandlerProvider( - eventHandler, - cfg.EVMPersistentTxReceiptsMax, - nil, - ) - - // TODO: This should use snapshot obtained from appStore.ReadOnlyState() - storeTx := store.WrapAtomic(appStore).BeginTx() - state := loomchain.NewStoreState( - context.Background(), - storeTx, - abci.Header{ - Height: appStore.Version(), - }, - // it is possible to load the block hash from the TM block store, but probably don't - // need it for just dumping the EVM state - nil, - nil, - ) - - var newABMFactory plugin.NewAccountBalanceManagerFactoryFunc - if evm.EVMEnabled && cfg.EVMAccountsEnabled { - newABMFactory = plugin.NewAccountBalanceManagerFactory - } - - var accountBalanceManager evm.AccountBalanceManager - if newABMFactory != nil { - pvm := plugin.NewPluginVM( - common.NewDefaultContractsLoader(cfg), - state, - createRegistry(state), - eventHandler, - log.Default, - newABMFactory, - receiptHandlerProvider.Writer(), - receiptHandlerProvider.Reader(), - ) - createABM, err := newABMFactory(pvm) - if err != nil { - return err - } - accountBalanceManager = createABM(true) - if err != nil { - return err - } - } - // TODO: this needs EVMStore - vm, err := evm.NewLoomEvm(state, nil, accountBalanceManager, nil, false) - if err != nil { - return err - } - - fmt.Printf("\n--- EVM state at app height %d ---\n%s\n", appStore.Version(), string(vm.RawDump())) - return nil - }, - } - - cmdFlags := cmd.Flags() - cmdFlags.Int64Var(&appHeight, "app-height", 0, "Dump EVM state as it was the specified app height") - return cmd -} - -func newDumpEVMStateMultiWriterAppStoreCommand() *cobra.Command { - var appHeight int64 - var evmDBName string - cmd := &cobra.Command{ - Use: "evm-dump-2", - Short: "Dumps EVM state stored at a specific block height from MultiWriterAppStore", - RunE: func(cmd *cobra.Command, args []string) error { - cfg, err := common.ParseConfig() - if err != nil { - return err - } - - db, err := dbm.NewGoLevelDB(cfg.DBName, cfg.RootPath()) - if err != nil { - return err - } - evmDB, err := cdb.LoadDB( - "goleveldb", - evmDBName, - cfg.RootPath(), - 256, - 4, - false, - ) - if err != nil { - return err - } - iavlStore, err := store.NewIAVLStore(db, 0, appHeight, 0) - if err != nil { - return err - } - evmStore := store.NewEvmStore(evmDB, 100, cfg.AppStore.IAVLFlushInterval) - if err := evmStore.LoadVersion(iavlStore.Version()); err != nil { - return err - } - - appStore, err := store.NewMultiWriterAppStore(iavlStore, evmStore, false) - if err != nil { - return err - } - eventHandler := loomchain.NewDefaultEventHandler(events.NewLogEventDispatcher()) - - regVer, err := registry.RegistryVersionFromInt(cfg.RegistryVersion) - if err != nil { - return err - } - createRegistry, err := registry.NewRegistryFactory(regVer) - if err != nil { - return err - } - - receiptHandlerProvider := receipts.NewReceiptHandlerProvider( - eventHandler, - cfg.EVMPersistentTxReceiptsMax, - nil, - ) - - // TODO: This should use snapshot obtained from appStore.ReadOnlyState() - storeTx := store.WrapAtomic(appStore).BeginTx() - state := loomchain.NewStoreState( - context.Background(), - storeTx, - abci.Header{ - Height: appStore.Version(), - }, - // it is possible to load the block hash from the TM block store, but probably don't - // need it for just dumping the EVM state - nil, - nil, - ) - - var newABMFactory plugin.NewAccountBalanceManagerFactoryFunc - if evm.EVMEnabled && cfg.EVMAccountsEnabled { - newABMFactory = plugin.NewAccountBalanceManagerFactory - } - - var accountBalanceManager evm.AccountBalanceManager - if newABMFactory != nil { - pvm := plugin.NewPluginVM( - common.NewDefaultContractsLoader(cfg), - state, - createRegistry(state), - eventHandler, - log.Default, - newABMFactory, - receiptHandlerProvider.Writer(), - receiptHandlerProvider.Reader(), - ) - createABM, err := newABMFactory(pvm) - if err != nil { - return err - } - accountBalanceManager = createABM(true) - if err != nil { - return err - } - } - // TODO: Fix this, it needs EVMStore - vm, err := evm.NewLoomEvm(state, nil, accountBalanceManager, nil, false) - if err != nil { - return err - } - - fmt.Printf("\n--- EVM state at app height %d ---\n%s\n", appStore.Version(), string(vm.RawDump())) - return nil - }, - } - - cmdFlags := cmd.Flags() - cmdFlags.Int64Var(&appHeight, "app-height", 0, "Dump EVM state as it was the specified app height") - cmdFlags.StringVar(&evmDBName, "evmdb-name", "evm", "Name of EVM state database") - return cmd -} - func newDumpEVMStateFromEvmDB() *cobra.Command { var appHeight int64 var evmDBName string var dumpStorageTrie bool cmd := &cobra.Command{ - Use: "evm-dump-3", + Use: "evm-dump", Short: "Dumps EVM state stored at a specific block height from evm.db", RunE: func(cmd *cobra.Command, args []string) error { cfg, err := common.ParseConfig() @@ -271,21 +53,7 @@ func newDumpEVMStateFromEvmDB() *cobra.Command { fmt.Printf("version: %d, root: %x\n", version, root) - // TODO: This should use snapshot obtained from appStore.ReadOnlyState() - storeTx := store.WrapAtomic(evmStore).BeginTx() - state := loomchain.NewStoreState( - context.Background(), - storeTx, - abci.Header{ - Height: appHeight, - }, - // it is possible to load the block hash from the TM block store, but probably don't - // need it for just dumping the EVM state - nil, - nil, - ) - - srcStateDB := gstate.NewDatabase(store.NewLoomEthDB(state, nil)) + srcStateDB := gstate.NewDatabase(store.NewLoomEthDB(evmStore, nil)) srcStateDBTrie, err := srcStateDB.OpenTrie(evmRoot) if err != nil { fmt.Printf("cannot open trie, %s\n", evmRoot.Hex()) diff --git a/evm/loomevm.go b/evm/loomevm.go index 35a736e5fb..612f8d8f68 100644 --- a/evm/loomevm.go +++ b/evm/loomevm.go @@ -81,9 +81,6 @@ func (levm LoomEvm) Commit() (common.Hash, error) { if err != nil { return root, err } - // if err := levm.sdb.Database().TrieDB().Commit(root, false); err != nil { - // return root, err - // } if err := levm.db.Put(rootKey, root[:]); err != nil { return root, err } diff --git a/plugin/fake_context.go b/plugin/fake_context.go index f553c31e23..a2842c5c15 100644 --- a/plugin/fake_context.go +++ b/plugin/fake_context.go @@ -10,7 +10,9 @@ import ( "github.com/loomnetwork/go-loom/plugin" "github.com/loomnetwork/go-loom/types" "github.com/loomnetwork/loomchain" + cdb "github.com/loomnetwork/loomchain/db" levm "github.com/loomnetwork/loomchain/evm" + "github.com/loomnetwork/loomchain/store" abci "github.com/tendermint/tendermint/abci/types" ) @@ -18,6 +20,7 @@ import ( type FakeContextWithEVM struct { *plugin.FakeContext State loomchain.State + EvmStore *store.EvmStore useAccountBalanceManager bool } @@ -35,9 +38,18 @@ func CreateFakeContextWithEVM(caller, address loom.Address) *FakeContextWithEVM }, ) state := loomchain.NewStoreState(context.Background(), ctx, block, nil, nil) + evmDB, err := cdb.LoadDB("memdb", "", "", 256, 4, false) + if err != nil { + panic(err) + } + evmStore := store.NewEvmStore(evmDB, 100, 0) + if err := evmStore.LoadVersion(0); err != nil { + panic(err) + } return &FakeContextWithEVM{ FakeContext: ctx, State: state, + EvmStore: evmStore, } } @@ -46,6 +58,7 @@ func (c *FakeContextWithEVM) WithValidators(validators []*types.Validator) *Fake FakeContext: c.FakeContext.WithValidators(validators), State: c.State, useAccountBalanceManager: c.useAccountBalanceManager, + EvmStore: c.EvmStore, } } @@ -54,6 +67,7 @@ func (c *FakeContextWithEVM) WithBlock(header loom.BlockHeader) *FakeContextWith FakeContext: c.FakeContext.WithBlock(header), State: c.State, useAccountBalanceManager: c.useAccountBalanceManager, + EvmStore: c.EvmStore, } } @@ -62,6 +76,7 @@ func (c *FakeContextWithEVM) WithSender(caller loom.Address) *FakeContextWithEVM FakeContext: c.FakeContext.WithSender(caller), State: c.State, useAccountBalanceManager: c.useAccountBalanceManager, + EvmStore: c.EvmStore, } } @@ -70,6 +85,7 @@ func (c *FakeContextWithEVM) WithAddress(addr loom.Address) *FakeContextWithEVM FakeContext: c.FakeContext.WithAddress(addr), State: c.State, useAccountBalanceManager: c.useAccountBalanceManager, + EvmStore: c.EvmStore, } } @@ -79,6 +95,7 @@ func (c *FakeContextWithEVM) WithFeature(name string, value bool) *FakeContextWi FakeContext: c.FakeContext, State: c.State, useAccountBalanceManager: c.useAccountBalanceManager, + EvmStore: c.EvmStore, } } @@ -87,6 +104,7 @@ func (c *FakeContextWithEVM) WithAccountBalanceManager(enable bool) *FakeContext FakeContext: c.FakeContext, State: c.State, useAccountBalanceManager: enable, + EvmStore: c.EvmStore, } } @@ -103,7 +121,7 @@ func (c *FakeContextWithEVM) CallEVM(addr loom.Address, input []byte, value *loo if c.useAccountBalanceManager { createABM = c.AccountBalanceManager } - vm := levm.NewLoomVm(c.State, nil, nil, nil, createABM, false) + vm := levm.NewLoomVm(c.State, c.EvmStore, nil, nil, createABM, false) return vm.Call(c.ContractAddress(), addr, input, value) } @@ -112,7 +130,7 @@ func (c *FakeContextWithEVM) StaticCallEVM(addr loom.Address, input []byte) ([]b if c.useAccountBalanceManager { createABM = c.AccountBalanceManager } - vm := levm.NewLoomVm(c.State, nil, nil, nil, createABM, false) + vm := levm.NewLoomVm(c.State, c.EvmStore, nil, nil, createABM, false) return vm.StaticCall(c.ContractAddress(), addr, input) } From 88aafca4d4c4c0e915a42ebf7b4ffd197a2173d0 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Thu, 17 Oct 2019 17:15:45 +0700 Subject: [PATCH 16/80] fix unit tests --- evm/loomevm.go | 8 +++++++- evm/test_cryptozombies.go | 10 ---------- integration_tests/ethcoin_evm_test.go | 6 +++--- plugin/fake_context.go | 3 --- plugin/vm_test.go | 9 ++++++++- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/evm/loomevm.go b/evm/loomevm.go index 612f8d8f68..a0b94db20f 100644 --- a/evm/loomevm.go +++ b/evm/loomevm.go @@ -18,6 +18,7 @@ import ( ptypes "github.com/loomnetwork/go-loom/plugin/types" "github.com/loomnetwork/loomchain" "github.com/loomnetwork/loomchain/auth" + cdb "github.com/loomnetwork/loomchain/db" "github.com/loomnetwork/loomchain/events" "github.com/loomnetwork/loomchain/features" "github.com/loomnetwork/loomchain/receipts" @@ -106,7 +107,12 @@ var LoomVmFactory = func(state loomchain.State) (vm.VM, error) { nil, ) receiptHandler := receiptHandlerProvider.Writer() - return NewLoomVm(state, nil, eventHandler, receiptHandler, nil, debug), nil + evmDB, err := cdb.LoadDB("memdb", "", "", 256, 4, false) + if err != nil { + panic(err) + } + evmStore := store.NewEvmStore(evmDB, 100, 0) + return NewLoomVm(state, evmStore, eventHandler, receiptHandler, nil, debug), nil } // LoomVm implements the loomchain/vm.VM interface using the EVM. diff --git a/evm/test_cryptozombies.go b/evm/test_cryptozombies.go index d0bf1bb02c..d30080b2cd 100644 --- a/evm/test_cryptozombies.go +++ b/evm/test_cryptozombies.go @@ -82,15 +82,9 @@ func testCryptoZombiesUpdateState(t *testing.T, state loomchain.State, caller lo vm, _ := manager.InitVM(lvm.VMType_PLUGIN, state) kittyAddr := deployContract(t, vm, motherKat, kittyData.Bytecode, kittyData.RuntimeBytecode) - vm, _ = manager.InitVM(lvm.VMType_PLUGIN, state) zOwnershipAddr := deployContract(t, vm, caller, zOwnershipData.Bytecode, zOwnershipData.RuntimeBytecode) - - vm, _ = manager.InitVM(lvm.VMType_PLUGIN, state) checkKitty(t, vm, caller, kittyAddr, kittyData) - vm, _ = manager.InitVM(lvm.VMType_PLUGIN, state) makeZombie(t, vm, caller, zOwnershipAddr, zOwnershipData, "EEK") - - vm, _ = manager.InitVM(lvm.VMType_PLUGIN, state) greedyZombie := getZombies(t, vm, caller, zOwnershipAddr, zOwnershipData, 0) // greedy zombie should look like: //{ @@ -107,12 +101,8 @@ func testCryptoZombiesUpdateState(t *testing.T, state loomchain.State, caller lo t.Error("Wrong dna for greedy zombie") } - vm, _ = manager.InitVM(lvm.VMType_PLUGIN, state) setKittyAddress(t, vm, caller, kittyAddr, zOwnershipAddr, zOwnershipData) - vm, _ = manager.InitVM(lvm.VMType_PLUGIN, state) zombieFeed(t, vm, caller, zOwnershipAddr, zOwnershipData, 0, 67) - - vm, _ = manager.InitVM(lvm.VMType_PLUGIN, state) newZombie := getZombies(t, vm, caller, zOwnershipAddr, zOwnershipData, 1) // New zombie should look like //{ diff --git a/integration_tests/ethcoin_evm_test.go b/integration_tests/ethcoin_evm_test.go index 01b1351485..3138aa466f 100644 --- a/integration_tests/ethcoin_evm_test.go +++ b/integration_tests/ethcoin_evm_test.go @@ -208,7 +208,7 @@ func (c *ethCoinIntegrationTestHelper) callEVM( if err != nil { return err } - vm := evm.NewLoomVm(ctx.State, nil, nil, nil, ctx.AccountBalanceManager, false) + vm := evm.NewLoomVm(ctx.State, ctx.EvmStore, nil, nil, ctx.AccountBalanceManager, false) _, err = vm.Call(ctx.Message().Sender, c.Address, input, loom.NewBigUIntFromInt(0)) if err != nil { return err @@ -223,7 +223,7 @@ func (c *ethCoinIntegrationTestHelper) staticCallEVM( if err != nil { return err } - vm := evm.NewLoomVm(ctx.State, nil, nil, nil, ctx.AccountBalanceManager, false) + vm := evm.NewLoomVm(ctx.State, ctx.EvmStore, nil, nil, ctx.AccountBalanceManager, false) output, err := vm.StaticCall(ctx.Message().Sender, c.Address, input) if err != nil { return err @@ -239,7 +239,7 @@ func deployContractToEVM(ctx *plugin.FakeContextWithEVM, filename string, caller } byteCode := common.FromHex(string(hexByteCode)) - vm := evm.NewLoomVm(ctx.State, nil, nil, nil, nil, false) + vm := evm.NewLoomVm(ctx.State, ctx.EvmStore, nil, nil, nil, false) _, contractAddr, err = vm.Create(caller, byteCode, loom.NewBigUIntFromInt(0)) if err != nil { return contractAddr, err diff --git a/plugin/fake_context.go b/plugin/fake_context.go index a2842c5c15..d34d0fd763 100644 --- a/plugin/fake_context.go +++ b/plugin/fake_context.go @@ -43,9 +43,6 @@ func CreateFakeContextWithEVM(caller, address loom.Address) *FakeContextWithEVM panic(err) } evmStore := store.NewEvmStore(evmDB, 100, 0) - if err := evmStore.LoadVersion(0); err != nil { - panic(err) - } return &FakeContextWithEVM{ FakeContext: ctx, State: state, diff --git a/plugin/vm_test.go b/plugin/vm_test.go index 3a052e0a72..874d81fdce 100644 --- a/plugin/vm_test.go +++ b/plugin/vm_test.go @@ -19,6 +19,7 @@ import ( ptypes "github.com/loomnetwork/go-loom/plugin/types" "github.com/loomnetwork/go-loom/testdata" "github.com/loomnetwork/loomchain" + cdb "github.com/loomnetwork/loomchain/db" "github.com/loomnetwork/loomchain/eth/subs" "github.com/loomnetwork/loomchain/events" levm "github.com/loomnetwork/loomchain/evm" @@ -140,9 +141,15 @@ func TestPluginVMContractContextCaller(t *testing.T) { state := loomchain.NewStoreState(context.Background(), store.NewMemStore(), block, nil, nil) createRegistry, err := registry.NewRegistryFactory(registry.LatestRegistryVersion) require.NoError(t, err) + evmDB, err := cdb.LoadDB("memdb", "", "", 256, 4, false) + if err != nil { + panic(err) + } + evmStore := store.NewEvmStore(evmDB, 100, 0) vm := NewPluginVM(loader, state, createRegistry(state), &fakeEventHandler{}, nil, nil, nil, nil) - evm := levm.NewLoomVm(state, nil, nil, nil, nil, false) + vm = vm.WithEvmStore(evmStore) + evm := levm.NewLoomVm(state, evmStore, nil, nil, nil, false) // Deploy contracts owner := loom.RootAddress("chain") From 8853c38f930bb0cde8a8dd29ddd0a608c3049848 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Thu, 17 Oct 2019 17:56:38 +0700 Subject: [PATCH 17/80] fix a unit test --- evm/evm_test.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/evm/evm_test.go b/evm/evm_test.go index 217d2c3f5d..6a93e3cac0 100644 --- a/evm/evm_test.go +++ b/evm/evm_test.go @@ -209,17 +209,11 @@ func TestGlobals(t *testing.T) { vm, _ := manager.InitVM(lvm.VMType_EVM, state) abiGP, gPAddr := deploySolContract(t, caller, "GlobalProperties", vm) - vm, _ = manager.InitVM(lvm.VMType_EVM, state) testNow(t, abiGP, caller, gPAddr, vm) - vm, _ = manager.InitVM(lvm.VMType_EVM, state) testBlockTimeStamp(t, abiGP, caller, gPAddr, vm) - vm, _ = manager.InitVM(lvm.VMType_EVM, state) testBlockNumber(t, abiGP, caller, gPAddr, vm) - vm, _ = manager.InitVM(lvm.VMType_EVM, state) testTxOrigin(t, abiGP, caller, gPAddr, vm) - vm, _ = manager.InitVM(lvm.VMType_EVM, state) testMsgSender(t, abiGP, caller, gPAddr, vm) - vm, _ = manager.InitVM(lvm.VMType_EVM, state) testMsgValue(t, abiGP, caller, gPAddr, vm) } From 2861d52d19f7844cc00ad3c697cb8fc6f39e9211 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Thu, 17 Oct 2019 19:07:05 +0700 Subject: [PATCH 18/80] add evmstore to pluginvm --- cmd/loom/loom.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/loom/loom.go b/cmd/loom/loom.go index 008720c064..7d8be7ef59 100644 --- a/cmd/loom/loom.go +++ b/cmd/loom/loom.go @@ -813,7 +813,7 @@ func loadApp( newABMFactory, receiptHandlerProvider.Writer(), receiptHandlerProvider.Reader(), - ), nil + ).WithEvmStore(evmStore), nil }) if evm.EVMEnabled { From 819883393b4eeb404c363c82cf825e3ef596a22e Mon Sep 17 00:00:00 2001 From: Pathorn Date: Fri, 18 Oct 2019 15:08:47 +0700 Subject: [PATCH 19/80] simplify test --- plugin/vm_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugin/vm_test.go b/plugin/vm_test.go index 874d81fdce..3911184a5c 100644 --- a/plugin/vm_test.go +++ b/plugin/vm_test.go @@ -147,8 +147,7 @@ func TestPluginVMContractContextCaller(t *testing.T) { } evmStore := store.NewEvmStore(evmDB, 100, 0) - vm := NewPluginVM(loader, state, createRegistry(state), &fakeEventHandler{}, nil, nil, nil, nil) - vm = vm.WithEvmStore(evmStore) + vm := NewPluginVM(loader, state, createRegistry(state), &fakeEventHandler{}, nil, nil, nil, nil).WithEvmStore(evmStore) evm := levm.NewLoomVm(state, evmStore, nil, nil, nil, false) // Deploy contracts From 1b7540c79547bba26b2dd6402277d95dad080619 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Fri, 1 Nov 2019 13:36:07 +0700 Subject: [PATCH 20/80] simplify commit EVM root logic --- store/evmstore.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/store/evmstore.go b/store/evmstore.go index bde2dc7219..6f48c079bf 100644 --- a/store/evmstore.go +++ b/store/evmstore.go @@ -227,12 +227,13 @@ func (s *EvmStore) Commit(version int64) []byte { } ethDB := NewLoomEthDB(s, nil) s.trieDB = trie.NewDatabase(ethDB) - s.Set(evmRootKey(version), currentRoot) } - // We have to save default root - if bytes.Equal(defaultRoot, currentRoot) && !bytes.Equal(currentRoot, s.lastSavedRoot) { + // We don't commit empty root but we need to save default root ([]byte{1}) as a placeholder of empty root + // So the node won't get EVM root mismatch during the EVM root checking + if !bytes.Equal(currentRoot, s.lastSavedRoot) { s.Set(evmRootKey(version), currentRoot) + s.lastSavedRoot = currentRoot } } @@ -248,7 +249,6 @@ func (s *EvmStore) Commit(version int64) []byte { } batch.Write() s.cache = make(map[string]cacheItem) - s.lastSavedRoot = currentRoot s.version = version return currentRoot } From 0b70a216d12c43e74c75d178b7e6f7c2dd20613e Mon Sep 17 00:00:00 2001 From: Pathorn Date: Mon, 4 Nov 2019 15:08:32 +0700 Subject: [PATCH 21/80] address PR comment --- app.go | 12 ++++++++++++ cmd/loom/loom.go | 2 +- evm/loomevm.go | 22 +++++++++++----------- evm/noevm.go | 3 +-- plugin/vm.go | 11 +++++------ plugin/vm_test.go | 6 ++++-- rpc/query_server.go | 16 ++++++++++++---- store/evmstore.go | 27 ++++++++++++++++++++++----- 8 files changed, 68 insertions(+), 31 deletions(-) diff --git a/app.go b/app.go index 552f23b276..63c7b2040a 100644 --- a/app.go +++ b/app.go @@ -8,6 +8,8 @@ import ( "fmt" "time" + "github.com/ethereum/go-ethereum/trie" + "github.com/loomnetwork/go-loom/config" "github.com/loomnetwork/go-loom/util" "github.com/loomnetwork/loomchain/eth/utils" @@ -53,6 +55,16 @@ type State interface { ChangeConfigSetting(name, value string) error } +type ReadOnlyEVMState interface { + store.KVReader + TrieDB() *trie.Database +} + +type EVMState interface { + ReadOnlyEVMState + store.KVWriter +} + type StoreState struct { ctx context.Context store store.KVStore diff --git a/cmd/loom/loom.go b/cmd/loom/loom.go index fe2dc7dad8..68133de5a7 100644 --- a/cmd/loom/loom.go +++ b/cmd/loom/loom.go @@ -814,7 +814,7 @@ func loadApp( newABMFactory, receiptHandlerProvider.Writer(), receiptHandlerProvider.Reader(), - ).WithEvmStore(evmStore), nil + ).WithEVMState(evmStore), nil }) if evm.EVMEnabled { diff --git a/evm/loomevm.go b/evm/loomevm.go index a0b94db20f..0be5361518 100644 --- a/evm/loomevm.go +++ b/evm/loomevm.go @@ -49,18 +49,18 @@ type LoomEvm struct { // TODO: this doesn't need to be exported, rename to newLoomEvmWithState func NewLoomEvm( - loomState loomchain.State, evmStore *store.EvmStore, accountBalanceManager AccountBalanceManager, + loomState loomchain.State, evmState loomchain.EVMState, accountBalanceManager AccountBalanceManager, logContext *store.EthDBLogContext, debug bool, ) (*LoomEvm, error) { p := new(LoomEvm) - ethDB := store.NewLoomEthDB(evmStore, logContext) + ethDB := store.NewLoomEthDB(evmState, logContext) p.db = ethDB oldRoot, err := p.db.Get(rootKey) if err != nil { return nil, err } stateDB := state.NewDatabase(ethDB) - stateDB.SetTrieDB(evmStore.TrieDB()) + stateDB.SetTrieDB(evmState.TrieDB()) var abm *evmAccountBalanceManager if accountBalanceManager != nil { @@ -119,7 +119,7 @@ var LoomVmFactory = func(state loomchain.State) (vm.VM, error) { // TODO: rename to LoomEVM type LoomVm struct { state loomchain.State - evmStore *store.EvmStore + evmState loomchain.EVMState receiptHandler loomchain.WriteReceiptHandler createABM AccountBalanceManagerFactoryFunc debug bool @@ -127,7 +127,7 @@ type LoomVm struct { func NewLoomVm( loomState loomchain.State, - evmStore *store.EvmStore, + evmState loomchain.EVMState, eventHandler loomchain.EventHandler, receiptHandler loomchain.WriteReceiptHandler, createABM AccountBalanceManagerFactoryFunc, @@ -135,7 +135,7 @@ func NewLoomVm( ) vm.VM { return &LoomVm{ state: loomState, - evmStore: evmStore, + evmState: evmState, receiptHandler: receiptHandler, createABM: createABM, debug: debug, @@ -151,7 +151,7 @@ func (lvm LoomVm) accountBalanceManager(readOnly bool) AccountBalanceManager { func (lvm LoomVm) Create(caller loom.Address, code []byte, value *loom.BigUInt) ([]byte, loom.Address, error) { logContext := store.NewEthDBLogContext(lvm.state.Block().Height, loom.Address{}, caller) - levm, err := NewLoomEvm(lvm.state, lvm.evmStore, lvm.accountBalanceManager(false), logContext, lvm.debug) + levm, err := NewLoomEvm(lvm.state, lvm.evmState, lvm.accountBalanceManager(false), logContext, lvm.debug) if err != nil { return nil, loom.Address{}, err } @@ -210,7 +210,7 @@ func (lvm LoomVm) Create(caller loom.Address, code []byte, value *loom.BigUInt) func (lvm LoomVm) Call(caller, addr loom.Address, input []byte, value *loom.BigUInt) ([]byte, error) { logContext := store.NewEthDBLogContext(lvm.state.Block().Height, addr, caller) - levm, err := NewLoomEvm(lvm.state, lvm.evmStore, lvm.accountBalanceManager(false), logContext, lvm.debug) + levm, err := NewLoomEvm(lvm.state, lvm.evmState, lvm.accountBalanceManager(false), logContext, lvm.debug) if err != nil { return nil, err } @@ -258,7 +258,7 @@ func (lvm LoomVm) Call(caller, addr loom.Address, input []byte, value *loom.BigU } func (lvm LoomVm) StaticCall(caller, addr loom.Address, input []byte) ([]byte, error) { - levm, err := NewLoomEvm(lvm.state, lvm.evmStore, lvm.accountBalanceManager(true), nil, lvm.debug) + levm, err := NewLoomEvm(lvm.state, lvm.evmState, lvm.accountBalanceManager(true), nil, lvm.debug) if err != nil { return nil, err } @@ -266,7 +266,7 @@ func (lvm LoomVm) StaticCall(caller, addr loom.Address, input []byte) ([]byte, e } func (lvm LoomVm) GetCode(addr loom.Address) ([]byte, error) { - levm, err := NewLoomEvm(lvm.state, lvm.evmStore, nil, nil, lvm.debug) + levm, err := NewLoomEvm(lvm.state, lvm.evmState, nil, nil, lvm.debug) if err != nil { return nil, err } @@ -274,7 +274,7 @@ func (lvm LoomVm) GetCode(addr loom.Address) ([]byte, error) { } func (lvm LoomVm) GetStorageAt(addr loom.Address, key []byte) ([]byte, error) { - levm, err := NewLoomEvm(lvm.state, lvm.evmStore, nil, nil, lvm.debug) + levm, err := NewLoomEvm(lvm.state, lvm.evmState, nil, nil, lvm.debug) if err != nil { return nil, err } diff --git a/evm/noevm.go b/evm/noevm.go index 95956810ca..d2e8ca4382 100644 --- a/evm/noevm.go +++ b/evm/noevm.go @@ -4,7 +4,6 @@ package evm import ( "github.com/loomnetwork/loomchain" - "github.com/loomnetwork/loomchain/store" lvm "github.com/loomnetwork/loomchain/vm" ) @@ -17,7 +16,7 @@ const EVMEnabled = false func NewLoomVm( loomState loomchain.State, - evmStore *store.EvmStore, + evmState loomchain.EVMState, eventHandler loomchain.EventHandler, receiptHandler loomchain.WriteReceiptHandler, createABM AccountBalanceManagerFactoryFunc, diff --git a/plugin/vm.go b/plugin/vm.go index c072e3cac1..8006438821 100644 --- a/plugin/vm.go +++ b/plugin/vm.go @@ -19,7 +19,6 @@ import ( "github.com/loomnetwork/loomchain/auth" levm "github.com/loomnetwork/loomchain/evm" "github.com/loomnetwork/loomchain/registry" - "github.com/loomnetwork/loomchain/store" "github.com/loomnetwork/loomchain/vm" "github.com/pkg/errors" ) @@ -37,7 +36,7 @@ var ( type PluginVM struct { Loader Loader State loomchain.State - EvmStore *store.EvmStore + EvmState loomchain.EVMState Registry registry.Registry EventHandler loomchain.EventHandler logger *loom.Logger @@ -71,8 +70,8 @@ func NewPluginVM( var _ vm.VM = &PluginVM{} -func (vm *PluginVM) WithEvmStore(evmStore *store.EvmStore) *PluginVM { - vm.EvmStore = evmStore +func (vm *PluginVM) WithEVMState(evmState loomchain.EVMState) *PluginVM { + vm.EvmState = evmState return vm } @@ -203,7 +202,7 @@ func (vm *PluginVM) CallEVM(caller, addr loom.Address, input []byte, value *loom return nil, err } } - evm := levm.NewLoomVm(vm.State, vm.EvmStore, vm.EventHandler, vm.receiptWriter, createABM, false) + evm := levm.NewLoomVm(vm.State, vm.EvmState, vm.EventHandler, vm.receiptWriter, createABM, false) return evm.Call(caller, addr, input, value) } @@ -217,7 +216,7 @@ func (vm *PluginVM) StaticCallEVM(caller, addr loom.Address, input []byte) ([]by } } - evm := levm.NewLoomVm(vm.State, vm.EvmStore, vm.EventHandler, vm.receiptWriter, createABM, false) + evm := levm.NewLoomVm(vm.State, vm.EvmState, vm.EventHandler, vm.receiptWriter, createABM, false) return evm.StaticCall(caller, addr, input) } diff --git a/plugin/vm_test.go b/plugin/vm_test.go index 3911184a5c..11646aeedd 100644 --- a/plugin/vm_test.go +++ b/plugin/vm_test.go @@ -146,9 +146,11 @@ func TestPluginVMContractContextCaller(t *testing.T) { panic(err) } evmStore := store.NewEvmStore(evmDB, 100, 0) + version := state.Block().Height + evmSnapshot := evmStore.GetSnapshot(version).(*store.EvmStoreSnapshot) - vm := NewPluginVM(loader, state, createRegistry(state), &fakeEventHandler{}, nil, nil, nil, nil).WithEvmStore(evmStore) - evm := levm.NewLoomVm(state, evmStore, nil, nil, nil, false) + vm := NewPluginVM(loader, state, createRegistry(state), &fakeEventHandler{}, nil, nil, nil, nil).WithEVMState(evmSnapshot) + evm := levm.NewLoomVm(state, evmSnapshot, nil, nil, nil, false) // Deploy contracts owner := loom.RootAddress("chain") diff --git a/rpc/query_server.go b/rpc/query_server.go index 653874f963..4ad47a4596 100755 --- a/rpc/query_server.go +++ b/rpc/query_server.go @@ -269,7 +269,9 @@ func (s *QueryServer) queryEvm(state loomchain.State, caller, contract loom.Addr return nil, err } } - vm := levm.NewLoomVm(state, s.EvmStore, nil, nil, createABM, false) + version := state.Block().Height + evmSnapshot := s.EvmStore.GetSnapshot(version).(*store.EvmStoreSnapshot) + vm := levm.NewLoomVm(state, evmSnapshot, nil, nil, createABM, false) return vm.StaticCall(callerAddr, contract, query) } @@ -314,7 +316,9 @@ func (s *QueryServer) GetEvmCode(contract string) ([]byte, error) { snapshot := s.StateProvider.ReadOnlyState() defer snapshot.Release() - vm := levm.NewLoomVm(snapshot, s.EvmStore, nil, nil, nil, false) + version := snapshot.Block().Height + evmSnapshot := s.EvmStore.GetSnapshot(version).(*store.EvmStoreSnapshot) + vm := levm.NewLoomVm(snapshot, evmSnapshot, nil, nil, nil, false) return vm.GetCode(contractAddr) } @@ -328,7 +332,9 @@ func (s *QueryServer) EthGetCode(address eth.Data, block eth.BlockHeight) (eth.D snapshot := s.StateProvider.ReadOnlyState() defer snapshot.Release() - evm := levm.NewLoomVm(snapshot, s.EvmStore, nil, nil, nil, false) + version := snapshot.Block().Height + evmSnapshot := s.EvmStore.GetSnapshot(version).(*store.EvmStoreSnapshot) + evm := levm.NewLoomVm(snapshot, evmSnapshot, nil, nil, nil, false) code, err := evm.GetCode(addr) if err != nil { return "", errors.Wrapf(err, "getting evm code for %v", address) @@ -1103,7 +1109,9 @@ func (s *QueryServer) EthGetStorageAt(local eth.Data, position string, block eth return "", errors.Wrapf(err, "unable to get storage at height %v", block) } - evm := levm.NewLoomVm(snapshot, s.EvmStore, nil, nil, nil, false) + version := snapshot.Block().Height + evmSnapshot := s.EvmStore.GetSnapshot(version).(*store.EvmStoreSnapshot) + evm := levm.NewLoomVm(snapshot, evmSnapshot, nil, nil, nil, false) storage, err := evm.GetStorageAt(address, ethcommon.HexToHash(position).Bytes()) if err != nil { return "", errors.Wrapf(err, "failed to get EVM storage at %v", address.Local.String()) diff --git a/store/evmstore.go b/store/evmstore.go index 6f48c079bf..d0e987cea9 100644 --- a/store/evmstore.go +++ b/store/evmstore.go @@ -281,10 +281,6 @@ func (s *EvmStore) TrieDB() *trie.Database { return s.trieDB } -func (s *EvmStore) SetTrieDB(trieDB *trie.Database) { - s.trieDB = trieDB -} - func (s *EvmStore) getLastSavedRoot(targetVersion int64) ([]byte, int64) { start := util.PrefixKey(vmPrefix, evmRootPrefix) end := prefixRangeEnd(evmRootKey(targetVersion)) @@ -315,15 +311,20 @@ func (s *EvmStore) GetSnapshot(version int64) db.Snapshot { } func NewEvmStoreSnapshot(snapshot db.Snapshot, rootHash []byte) *EvmStoreSnapshot { - return &EvmStoreSnapshot{ + evmSnapshot := &EvmStoreSnapshot{ Snapshot: snapshot, rootHash: rootHash, } + ethDB := NewLoomEthDB(evmSnapshot, nil) + // This should be read only database + evmSnapshot.trieDB = trie.NewDatabase(ethDB) + return evmSnapshot } type EvmStoreSnapshot struct { db.Snapshot rootHash []byte + trieDB *trie.Database } func (s *EvmStoreSnapshot) Get(key []byte) []byte { @@ -342,6 +343,22 @@ func (s *EvmStoreSnapshot) Has(key []byte) bool { return s.Snapshot.Has(key) } +func (s *EvmStoreSnapshot) TrieDB() *trie.Database { + return s.trieDB +} + +func (s *EvmStoreSnapshot) Delete(key []byte) { + panic("EvmStoreSnapshot does not implement delete") +} + +func (s *EvmStoreSnapshot) Set(key, val []byte) { + panic("EvmStoreSnapshot does not implement set") +} + +func (s *EvmStoreSnapshot) Range(key []byte) plugin.RangeData { + panic("EvmStoreSnapshot does not implement range") +} + func remove(keys []string, key string) []string { for i, value := range keys { if value == key { From 3bfac5b862f73e6f5af94ab33483c01f19bead6e Mon Sep 17 00:00:00 2001 From: Pathorn Date: Mon, 4 Nov 2019 16:58:53 +0700 Subject: [PATCH 22/80] fix unit test --- plugin/vm_test.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugin/vm_test.go b/plugin/vm_test.go index 11646aeedd..0f68cda503 100644 --- a/plugin/vm_test.go +++ b/plugin/vm_test.go @@ -146,11 +146,9 @@ func TestPluginVMContractContextCaller(t *testing.T) { panic(err) } evmStore := store.NewEvmStore(evmDB, 100, 0) - version := state.Block().Height - evmSnapshot := evmStore.GetSnapshot(version).(*store.EvmStoreSnapshot) - vm := NewPluginVM(loader, state, createRegistry(state), &fakeEventHandler{}, nil, nil, nil, nil).WithEVMState(evmSnapshot) - evm := levm.NewLoomVm(state, evmSnapshot, nil, nil, nil, false) + vm := NewPluginVM(loader, state, createRegistry(state), &fakeEventHandler{}, nil, nil, nil, nil).WithEVMState(evmStore) + evm := levm.NewLoomVm(state, evmStore, nil, nil, nil, false) // Deploy contracts owner := loom.RootAddress("chain") From e6ab7ca7396c4191927f12374b233d913656630a Mon Sep 17 00:00:00 2001 From: Pathorn Date: Wed, 6 Nov 2019 11:44:00 +0700 Subject: [PATCH 23/80] address PR comment --- rpc/query_server.go | 8 ++++---- store/evmstore.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/rpc/query_server.go b/rpc/query_server.go index 4ad47a4596..51f1de73cb 100755 --- a/rpc/query_server.go +++ b/rpc/query_server.go @@ -270,7 +270,7 @@ func (s *QueryServer) queryEvm(state loomchain.State, caller, contract loom.Addr } } version := state.Block().Height - evmSnapshot := s.EvmStore.GetSnapshot(version).(*store.EvmStoreSnapshot) + evmSnapshot := s.EvmStore.GetSnapshot(version) vm := levm.NewLoomVm(state, evmSnapshot, nil, nil, createABM, false) return vm.StaticCall(callerAddr, contract, query) } @@ -317,7 +317,7 @@ func (s *QueryServer) GetEvmCode(contract string) ([]byte, error) { defer snapshot.Release() version := snapshot.Block().Height - evmSnapshot := s.EvmStore.GetSnapshot(version).(*store.EvmStoreSnapshot) + evmSnapshot := s.EvmStore.GetSnapshot(version) vm := levm.NewLoomVm(snapshot, evmSnapshot, nil, nil, nil, false) return vm.GetCode(contractAddr) } @@ -333,7 +333,7 @@ func (s *QueryServer) EthGetCode(address eth.Data, block eth.BlockHeight) (eth.D defer snapshot.Release() version := snapshot.Block().Height - evmSnapshot := s.EvmStore.GetSnapshot(version).(*store.EvmStoreSnapshot) + evmSnapshot := s.EvmStore.GetSnapshot(version) evm := levm.NewLoomVm(snapshot, evmSnapshot, nil, nil, nil, false) code, err := evm.GetCode(addr) if err != nil { @@ -1110,7 +1110,7 @@ func (s *QueryServer) EthGetStorageAt(local eth.Data, position string, block eth } version := snapshot.Block().Height - evmSnapshot := s.EvmStore.GetSnapshot(version).(*store.EvmStoreSnapshot) + evmSnapshot := s.EvmStore.GetSnapshot(version) evm := levm.NewLoomVm(snapshot, evmSnapshot, nil, nil, nil, false) storage, err := evm.GetStorageAt(address, ethcommon.HexToHash(position).Bytes()) if err != nil { diff --git a/store/evmstore.go b/store/evmstore.go index d0e987cea9..6de220ed73 100644 --- a/store/evmstore.go +++ b/store/evmstore.go @@ -298,7 +298,7 @@ func (s *EvmStore) getLastSavedRoot(targetVersion int64) ([]byte, int64) { return nil, 0 } -func (s *EvmStore) GetSnapshot(version int64) db.Snapshot { +func (s *EvmStore) GetSnapshot(version int64) *EvmStoreSnapshot { var targetRoot []byte // Expect cache to be almost 100% hit since cache miss yields extremely poor performance val, exist := s.rootCache.Get(version) From 89f3fffc37ed50c0a2886d81981bfca883526163 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Thu, 7 Nov 2019 16:35:37 +0700 Subject: [PATCH 24/80] commit evm state in application commit --- app.go | 47 +++++++++++++++++++++++++++++++++++---------- cmd/loom/loom.go | 13 +++++++++++++ evm/loom_statedb.go | 6 +----- evm/loomevm.go | 42 ++++++++-------------------------------- store/evmstore.go | 2 -- 5 files changed, 59 insertions(+), 51 deletions(-) diff --git a/app.go b/app.go index 63c7b2040a..ccfdde1a7f 100644 --- a/app.go +++ b/app.go @@ -16,6 +16,7 @@ import ( "github.com/loomnetwork/loomchain/features" "github.com/loomnetwork/loomchain/registry" + gstate "github.com/ethereum/go-ethereum/core/state" "github.com/go-kit/kit/metrics" kitprometheus "github.com/go-kit/kit/metrics/prometheus" "github.com/loomnetwork/go-loom" @@ -53,6 +54,7 @@ type State interface { SetFeature(string, bool) SetMinBuildNumber(uint64) ChangeConfigSetting(name, value string) error + EVMState() *gstate.StateDB } type ReadOnlyEVMState interface { @@ -72,6 +74,7 @@ type StoreState struct { validators loom.ValidatorSet getValidatorSet GetValidatorSet config *cctypes.Config + evmState *gstate.StateDB } var _ = State(&StoreState{}) @@ -113,6 +116,11 @@ func (s *StoreState) WithOnChainConfig(config *cctypes.Config) *StoreState { return s } +func (s *StoreState) WithEVMState(evmState *gstate.StateDB) *StoreState { + s.evmState = evmState + return s +} + func (s *StoreState) Range(prefix []byte) plugin.RangeData { return s.store.Range(prefix) } @@ -153,11 +161,20 @@ func (s *StoreState) Context() context.Context { return s.ctx } +func (s *StoreState) EVMState() *gstate.StateDB { + return s.evmState +} + const ( featurePrefix = "feature" MinBuildKey = "minbuild" ) +var ( + vmPrefix = []byte("vm") + vmRootKey = util.PrefixKey(vmPrefix, []byte("vmroot")) +) + func featureKey(featureName string) []byte { return util.PrefixKey([]byte(featurePrefix), []byte(featureName)) } @@ -246,6 +263,7 @@ func (s *StoreState) WithContext(ctx context.Context) State { ctx: ctx, validators: s.validators, getValidatorSet: s.getValidatorSet, + evmState: s.evmState, } } @@ -256,6 +274,7 @@ func (s *StoreState) WithPrefix(prefix []byte) State { ctx: s.ctx, validators: s.validators, getValidatorSet: s.getValidatorSet, + evmState: s.evmState, } } @@ -376,6 +395,7 @@ type Application struct { config *cctypes.Config childTxRefs []evmaux.ChildTxRef // links Tendermint txs to EVM txs ReceiptsVersion int32 + EVMState *gstate.StateDB } var _ abci.Application = &Application{} @@ -519,7 +539,7 @@ func (a *Application) BeginBlock(req abci.RequestBeginBlock) abci.ResponseBeginB a.curBlockHeader, a.curBlockHash, a.GetValidatorSet, - ).WithOnChainConfig(a.config) + ).WithOnChainConfig(a.config).WithEVMState(a.EVMState) contractUpkeepHandler, err := a.CreateContractUpkeepHandler(upkeepState) if err != nil { panic(err) @@ -539,7 +559,7 @@ func (a *Application) BeginBlock(req abci.RequestBeginBlock) abci.ResponseBeginB a.curBlockHeader, nil, a.GetValidatorSet, - ).WithOnChainConfig(a.config) + ).WithOnChainConfig(a.config).WithEVMState(a.EVMState) validatorManager, err := a.CreateValidatorManager(state) if err != registry.ErrNotFound { @@ -607,7 +627,7 @@ func (a *Application) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock { a.curBlockHeader, nil, a.GetValidatorSet, - ).WithOnChainConfig(a.config) + ).WithOnChainConfig(a.config).WithEVMState(a.EVMState) validatorManager, err := a.CreateValidatorManager(state) if err != registry.ErrNotFound { @@ -662,7 +682,7 @@ func (a *Application) CheckTx(txBytes []byte) abci.ResponseCheckTx { a.curBlockHeader, a.curBlockHash, a.GetValidatorSet, - ).WithOnChainConfig(a.config) + ).WithOnChainConfig(a.config).WithEVMState(a.EVMState) // Receipts & events generated in CheckTx must be discarded since the app state changes they // reflect aren't persisted. @@ -699,7 +719,7 @@ func (a *Application) DeliverTx(txBytes []byte) abci.ResponseDeliverTx { a.curBlockHeader, a.curBlockHash, a.GetValidatorSet, - ).WithOnChainConfig(a.config) + ).WithOnChainConfig(a.config).WithEVMState(a.EVMState) var r abci.ResponseDeliverTx @@ -732,7 +752,7 @@ func (a *Application) processTx(storeTx store.KVStoreTx, txBytes []byte, isCheck a.curBlockHeader, a.curBlockHash, a.GetValidatorSet, - ).WithOnChainConfig(a.config) + ).WithOnChainConfig(a.config).WithEVMState(a.EVMState) receiptHandler := a.ReceiptHandlerProvider.Store() defer receiptHandler.DiscardCurrentReceipt() @@ -785,7 +805,7 @@ func (a *Application) deliverTx2(storeTx store.KVStoreTx, txBytes []byte) abci.R a.curBlockHeader, a.curBlockHash, a.GetValidatorSet, - ).WithOnChainConfig(a.config) + ).WithOnChainConfig(a.config).WithEVMState(a.EVMState) receiptHandler := a.ReceiptHandlerProvider.Store() defer receiptHandler.DiscardCurrentReceipt() @@ -837,18 +857,25 @@ func (a *Application) deliverTx2(storeTx store.KVStoreTx, txBytes []byte) abci.R // Commit commits the current block func (a *Application) Commit() abci.ResponseCommit { + height := a.curBlockHeader.GetHeight() var err error defer func(begin time.Time) { lvs := []string{"method", "Commit", "error", fmt.Sprint(err != nil)} committedBlockCount.With(lvs...).Add(1) commitBlockLatency.With(lvs...).Observe(time.Since(begin).Seconds()) }(time.Now()) - appHash, _, err := a.Store.SaveVersion() + + // Commit EVM state + evmStateRoot, err := a.EVMState.Commit(true) if err != nil { panic(err) } + a.EvmStore.Set(vmRootKey, evmStateRoot[:]) - height := a.curBlockHeader.GetHeight() + appHash, _, err := a.Store.SaveVersion() + if err != nil { + panic(err) + } if err := a.EvmAuxStore.SaveChildTxRefs(a.childTxRefs); err != nil { // TODO: consider panic instead @@ -907,5 +934,5 @@ func (a *Application) ReadOnlyState() State { a.lastBlockHeader, nil, // TODO: last block hash! a.GetValidatorSet, - ) + ).WithEVMState(a.EVMState) } diff --git a/cmd/loom/loom.go b/cmd/loom/loom.go index 68133de5a7..3cab319ffc 100644 --- a/cmd/loom/loom.go +++ b/cmd/loom/loom.go @@ -19,6 +19,7 @@ import ( "github.com/prometheus/client_golang/prometheus/push" "github.com/tendermint/tendermint/libs/db" + "github.com/ethereum/go-ethereum/core/state" kitprometheus "github.com/go-kit/kit/metrics/prometheus" "github.com/gogo/protobuf/proto" "github.com/loomnetwork/go-loom" @@ -40,6 +41,7 @@ import ( "github.com/loomnetwork/loomchain/receipts/leveldb" "github.com/prometheus/client_golang/prometheus" + gcommon "github.com/ethereum/go-ethereum/common" "github.com/loomnetwork/loomchain/chainconfig" chaincfgcmd "github.com/loomnetwork/loomchain/cmd/loom/chainconfig" "github.com/loomnetwork/loomchain/cmd/loom/common" @@ -817,6 +819,7 @@ func loadApp( ).WithEVMState(evmStore), nil }) + var evmState *state.StateDB if evm.EVMEnabled { vmManager.Register(vm.VMType_EVM, func(state loomchain.State) (vm.VM, error) { var createABM evm.AccountBalanceManagerFactoryFunc @@ -839,6 +842,15 @@ func loadApp( } return evm.NewLoomVm(state, evmStore, eventHandler, receiptHandlerProvider.Writer(), createABM, cfg.EVMDebugEnabled), nil }) + + ethDB := store.NewLoomEthDB(evmStore, nil) + evmRoot, _ := evmStore.Version() + stateDB := state.NewDatabase(ethDB) + stateDB.SetTrieDB(evmStore.TrieDB()) + evmState, err = state.New(gcommon.BytesToHash(evmRoot), stateDB) + if err != nil { + return nil, err + } } store.LogEthDBBatch = cfg.LogEthDbBatch @@ -1138,6 +1150,7 @@ func loadApp( EvmAuxStore: evmAuxStore, EvmStore: evmStore, ReceiptsVersion: cfg.ReceiptsVersion, + EVMState: evmState, }, nil } diff --git a/evm/loom_statedb.go b/evm/loom_statedb.go index e1357fac67..4ac88b4054 100644 --- a/evm/loom_statedb.go +++ b/evm/loom_statedb.go @@ -16,11 +16,7 @@ type LoomStateDB struct { abm *evmAccountBalanceManager } -func newLoomStateDB(abm *evmAccountBalanceManager, root common.Hash, db state.Database) (*LoomStateDB, error) { - sdb, err := state.New(root, db) - if err != nil { - return nil, err - } +func newLoomStateDB(abm *evmAccountBalanceManager, sdb *state.StateDB) (*LoomStateDB, error) { return &LoomStateDB{ StateDB: sdb, abm: abm, diff --git a/evm/loomevm.go b/evm/loomevm.go index 0be5361518..99f4d57919 100644 --- a/evm/loomevm.go +++ b/evm/loomevm.go @@ -52,42 +52,22 @@ func NewLoomEvm( loomState loomchain.State, evmState loomchain.EVMState, accountBalanceManager AccountBalanceManager, logContext *store.EthDBLogContext, debug bool, ) (*LoomEvm, error) { - p := new(LoomEvm) - ethDB := store.NewLoomEthDB(evmState, logContext) - p.db = ethDB - oldRoot, err := p.db.Get(rootKey) - if err != nil { - return nil, err - } - stateDB := state.NewDatabase(ethDB) - stateDB.SetTrieDB(evmState.TrieDB()) - + p := &LoomEvm{} var abm *evmAccountBalanceManager + var err error if accountBalanceManager != nil { abm = newEVMAccountBalanceManager(accountBalanceManager, loomState.Block().ChainID) - p.sdb, err = newLoomStateDB(abm, common.BytesToHash(oldRoot), stateDB) + p.sdb, err = newLoomStateDB(abm, loomState.EVMState()) + if err != nil { + return nil, err + } } else { - p.sdb, err = state.New(common.BytesToHash(oldRoot), stateDB) - } - if err != nil { - return nil, err + p.sdb = loomState.EVMState() } - p.Evm = NewEvm(p.sdb, loomState, abm, debug) return p, nil } -func (levm LoomEvm) Commit() (common.Hash, error) { - root, err := levm.sdb.Commit(true) - if err != nil { - return root, err - } - if err := levm.db.Put(rootKey, root[:]); err != nil { - return root, err - } - return root, err -} - func (levm LoomEvm) RawDump() []byte { d := levm.sdb.RawDump() output, err := json.MarshalIndent(d, "", " ") @@ -156,9 +136,6 @@ func (lvm LoomVm) Create(caller loom.Address, code []byte, value *loom.BigUInt) return nil, loom.Address{}, err } bytecode, addr, err := levm.Create(caller, code, value) - if err == nil { - _, err = levm.Commit() - } var txHash []byte if lvm.receiptHandler != nil { @@ -214,10 +191,7 @@ func (lvm LoomVm) Call(caller, addr loom.Address, input []byte, value *loom.BigU if err != nil { return nil, err } - _, err = levm.Call(caller, addr, input, value) - if err == nil { - _, err = levm.Commit() - } + levm.Call(caller, addr, input, value) var txHash []byte if lvm.receiptHandler != nil { diff --git a/store/evmstore.go b/store/evmstore.go index 6de220ed73..7f932d1276 100644 --- a/store/evmstore.go +++ b/store/evmstore.go @@ -225,8 +225,6 @@ func (s *EvmStore) Commit(version int64) []byte { if err := s.trieDB.Commit(gcommon.BytesToHash(currentRoot), false); err != nil { panic(err) } - ethDB := NewLoomEthDB(s, nil) - s.trieDB = trie.NewDatabase(ethDB) } // We don't commit empty root but we need to save default root ([]byte{1}) as a placeholder of empty root From b4226048dc0a66c0f320a476086d5c13690fedec Mon Sep 17 00:00:00 2001 From: Pathorn Date: Thu, 7 Nov 2019 17:18:12 +0700 Subject: [PATCH 25/80] refactor --- app.go | 32 +++++++------------ .../sample_go_contract_test.go | 2 +- cmd/loom/loom.go | 5 +-- evm/loomevm.go | 23 ++++--------- evm/noevm.go | 1 - integration_tests/ethcoin_evm_test.go | 6 ++-- plugin/fake_context.go | 4 +-- plugin/vm.go | 10 ++---- plugin/vm_test.go | 10 ++---- rpc/query_server.go | 20 +++++------- store/evmstore.go | 9 ++++++ 11 files changed, 49 insertions(+), 73 deletions(-) diff --git a/app.go b/app.go index ccfdde1a7f..85ea2c5ce8 100644 --- a/app.go +++ b/app.go @@ -8,15 +8,14 @@ import ( "fmt" "time" - "github.com/ethereum/go-ethereum/trie" - + gstate "github.com/ethereum/go-ethereum/core/state" "github.com/loomnetwork/go-loom/config" "github.com/loomnetwork/go-loom/util" "github.com/loomnetwork/loomchain/eth/utils" "github.com/loomnetwork/loomchain/features" "github.com/loomnetwork/loomchain/registry" - gstate "github.com/ethereum/go-ethereum/core/state" + gcommon "github.com/ethereum/go-ethereum/common" "github.com/go-kit/kit/metrics" kitprometheus "github.com/go-kit/kit/metrics/prometheus" "github.com/loomnetwork/go-loom" @@ -57,16 +56,6 @@ type State interface { EVMState() *gstate.StateDB } -type ReadOnlyEVMState interface { - store.KVReader - TrieDB() *trie.Database -} - -type EVMState interface { - ReadOnlyEVMState - store.KVWriter -} - type StoreState struct { ctx context.Context store store.KVStore @@ -170,11 +159,6 @@ const ( MinBuildKey = "minbuild" ) -var ( - vmPrefix = []byte("vm") - vmRootKey = util.PrefixKey(vmPrefix, []byte("vmroot")) -) - func featureKey(featureName string) []byte { return util.PrefixKey([]byte(featurePrefix), []byte(featureName)) } @@ -870,7 +854,7 @@ func (a *Application) Commit() abci.ResponseCommit { if err != nil { panic(err) } - a.EvmStore.Set(vmRootKey, evmStateRoot[:]) + a.EvmStore.SetVMRootKey(evmStateRoot[:]) appHash, _, err := a.Store.SaveVersion() if err != nil { @@ -926,6 +910,14 @@ func (a *Application) height() int64 { return a.Store.Version() + 1 } func (a *Application) ReadOnlyState() State { + root := a.EvmStore.GetVMRootKey() + ethDB := store.NewLoomEthDB(a.EvmStore.GetSnapshot(a.lastBlockHeader.Height), nil) + stateDB := gstate.NewDatabase(ethDB) + stateDB.SetTrieDB(a.EVMState.Database().TrieDB()) + snapshotEVMState, err := gstate.New(gcommon.BytesToHash(root), stateDB) + if err != nil { + panic(err) + } // TODO: the store snapshot should be created atomically, otherwise the block header might // not match the state... need to figure out why this hasn't spectacularly failed already return NewStoreStateSnapshot( @@ -934,5 +926,5 @@ func (a *Application) ReadOnlyState() State { a.lastBlockHeader, nil, // TODO: last block hash! a.GetValidatorSet, - ).WithEVMState(a.EVMState) + ).WithEVMState(snapshotEVMState) } diff --git a/builtin/plugins/sample_go_contract/sample_go_contract_test.go b/builtin/plugins/sample_go_contract/sample_go_contract_test.go index bd14e05fc4..18593c84d6 100644 --- a/builtin/plugins/sample_go_contract/sample_go_contract_test.go +++ b/builtin/plugins/sample_go_contract/sample_go_contract_test.go @@ -58,7 +58,7 @@ func deployContractToEVM(ctx *plugin.FakeContextWithEVM, filename string, caller } byteCode := common.FromHex(string(hexByteCode)) byteCode, err = hex.DecodeString(string(hexByteCode)) - vm := evm.NewLoomVm(ctx.State, ctx.EvmStore, nil, nil, nil, false) + vm := evm.NewLoomVm(ctx.State, nil, nil, nil, false) _, contractAddr, err = vm.Create(caller, byteCode, loom.NewBigUIntFromInt(0)) if err != nil { return contractAddr, err diff --git a/cmd/loom/loom.go b/cmd/loom/loom.go index 3cab319ffc..50e3262aca 100644 --- a/cmd/loom/loom.go +++ b/cmd/loom/loom.go @@ -816,7 +816,7 @@ func loadApp( newABMFactory, receiptHandlerProvider.Writer(), receiptHandlerProvider.Reader(), - ).WithEVMState(evmStore), nil + ), nil }) var evmState *state.StateDB @@ -840,7 +840,7 @@ func loadApp( return nil, err } } - return evm.NewLoomVm(state, evmStore, eventHandler, receiptHandlerProvider.Writer(), createABM, cfg.EVMDebugEnabled), nil + return evm.NewLoomVm(state, eventHandler, receiptHandlerProvider.Writer(), createABM, cfg.EVMDebugEnabled), nil }) ethDB := store.NewLoomEthDB(evmStore, nil) @@ -1299,6 +1299,7 @@ func initQueryService( EvmAuxStore: app.EvmAuxStore, EvmStore: app.EvmStore, Web3Cfg: cfg.Web3, + EVMState: app.EVMState, } bus := &rpc.QueryEventBus{ Subs: *app.EventHandler.SubscriptionSet(), diff --git a/evm/loomevm.go b/evm/loomevm.go index 99f4d57919..91c8c05a06 100644 --- a/evm/loomevm.go +++ b/evm/loomevm.go @@ -18,7 +18,6 @@ import ( ptypes "github.com/loomnetwork/go-loom/plugin/types" "github.com/loomnetwork/loomchain" "github.com/loomnetwork/loomchain/auth" - cdb "github.com/loomnetwork/loomchain/db" "github.com/loomnetwork/loomchain/events" "github.com/loomnetwork/loomchain/features" "github.com/loomnetwork/loomchain/receipts" @@ -49,7 +48,7 @@ type LoomEvm struct { // TODO: this doesn't need to be exported, rename to newLoomEvmWithState func NewLoomEvm( - loomState loomchain.State, evmState loomchain.EVMState, accountBalanceManager AccountBalanceManager, + loomState loomchain.State, accountBalanceManager AccountBalanceManager, logContext *store.EthDBLogContext, debug bool, ) (*LoomEvm, error) { p := &LoomEvm{} @@ -87,19 +86,13 @@ var LoomVmFactory = func(state loomchain.State) (vm.VM, error) { nil, ) receiptHandler := receiptHandlerProvider.Writer() - evmDB, err := cdb.LoadDB("memdb", "", "", 256, 4, false) - if err != nil { - panic(err) - } - evmStore := store.NewEvmStore(evmDB, 100, 0) - return NewLoomVm(state, evmStore, eventHandler, receiptHandler, nil, debug), nil + return NewLoomVm(state, eventHandler, receiptHandler, nil, debug), nil } // LoomVm implements the loomchain/vm.VM interface using the EVM. // TODO: rename to LoomEVM type LoomVm struct { state loomchain.State - evmState loomchain.EVMState receiptHandler loomchain.WriteReceiptHandler createABM AccountBalanceManagerFactoryFunc debug bool @@ -107,7 +100,6 @@ type LoomVm struct { func NewLoomVm( loomState loomchain.State, - evmState loomchain.EVMState, eventHandler loomchain.EventHandler, receiptHandler loomchain.WriteReceiptHandler, createABM AccountBalanceManagerFactoryFunc, @@ -115,7 +107,6 @@ func NewLoomVm( ) vm.VM { return &LoomVm{ state: loomState, - evmState: evmState, receiptHandler: receiptHandler, createABM: createABM, debug: debug, @@ -131,7 +122,7 @@ func (lvm LoomVm) accountBalanceManager(readOnly bool) AccountBalanceManager { func (lvm LoomVm) Create(caller loom.Address, code []byte, value *loom.BigUInt) ([]byte, loom.Address, error) { logContext := store.NewEthDBLogContext(lvm.state.Block().Height, loom.Address{}, caller) - levm, err := NewLoomEvm(lvm.state, lvm.evmState, lvm.accountBalanceManager(false), logContext, lvm.debug) + levm, err := NewLoomEvm(lvm.state, lvm.accountBalanceManager(false), logContext, lvm.debug) if err != nil { return nil, loom.Address{}, err } @@ -187,7 +178,7 @@ func (lvm LoomVm) Create(caller loom.Address, code []byte, value *loom.BigUInt) func (lvm LoomVm) Call(caller, addr loom.Address, input []byte, value *loom.BigUInt) ([]byte, error) { logContext := store.NewEthDBLogContext(lvm.state.Block().Height, addr, caller) - levm, err := NewLoomEvm(lvm.state, lvm.evmState, lvm.accountBalanceManager(false), logContext, lvm.debug) + levm, err := NewLoomEvm(lvm.state, lvm.accountBalanceManager(false), logContext, lvm.debug) if err != nil { return nil, err } @@ -232,7 +223,7 @@ func (lvm LoomVm) Call(caller, addr loom.Address, input []byte, value *loom.BigU } func (lvm LoomVm) StaticCall(caller, addr loom.Address, input []byte) ([]byte, error) { - levm, err := NewLoomEvm(lvm.state, lvm.evmState, lvm.accountBalanceManager(true), nil, lvm.debug) + levm, err := NewLoomEvm(lvm.state, lvm.accountBalanceManager(true), nil, lvm.debug) if err != nil { return nil, err } @@ -240,7 +231,7 @@ func (lvm LoomVm) StaticCall(caller, addr loom.Address, input []byte) ([]byte, e } func (lvm LoomVm) GetCode(addr loom.Address) ([]byte, error) { - levm, err := NewLoomEvm(lvm.state, lvm.evmState, nil, nil, lvm.debug) + levm, err := NewLoomEvm(lvm.state, nil, nil, lvm.debug) if err != nil { return nil, err } @@ -248,7 +239,7 @@ func (lvm LoomVm) GetCode(addr loom.Address) ([]byte, error) { } func (lvm LoomVm) GetStorageAt(addr loom.Address, key []byte) ([]byte, error) { - levm, err := NewLoomEvm(lvm.state, lvm.evmState, nil, nil, lvm.debug) + levm, err := NewLoomEvm(lvm.state, nil, nil, lvm.debug) if err != nil { return nil, err } diff --git a/evm/noevm.go b/evm/noevm.go index d2e8ca4382..80427c9327 100644 --- a/evm/noevm.go +++ b/evm/noevm.go @@ -16,7 +16,6 @@ const EVMEnabled = false func NewLoomVm( loomState loomchain.State, - evmState loomchain.EVMState, eventHandler loomchain.EventHandler, receiptHandler loomchain.WriteReceiptHandler, createABM AccountBalanceManagerFactoryFunc, diff --git a/integration_tests/ethcoin_evm_test.go b/integration_tests/ethcoin_evm_test.go index 3138aa466f..fbbd24b9f2 100644 --- a/integration_tests/ethcoin_evm_test.go +++ b/integration_tests/ethcoin_evm_test.go @@ -208,7 +208,7 @@ func (c *ethCoinIntegrationTestHelper) callEVM( if err != nil { return err } - vm := evm.NewLoomVm(ctx.State, ctx.EvmStore, nil, nil, ctx.AccountBalanceManager, false) + vm := evm.NewLoomVm(ctx.State, nil, nil, ctx.AccountBalanceManager, false) _, err = vm.Call(ctx.Message().Sender, c.Address, input, loom.NewBigUIntFromInt(0)) if err != nil { return err @@ -223,7 +223,7 @@ func (c *ethCoinIntegrationTestHelper) staticCallEVM( if err != nil { return err } - vm := evm.NewLoomVm(ctx.State, ctx.EvmStore, nil, nil, ctx.AccountBalanceManager, false) + vm := evm.NewLoomVm(ctx.State, nil, nil, ctx.AccountBalanceManager, false) output, err := vm.StaticCall(ctx.Message().Sender, c.Address, input) if err != nil { return err @@ -239,7 +239,7 @@ func deployContractToEVM(ctx *plugin.FakeContextWithEVM, filename string, caller } byteCode := common.FromHex(string(hexByteCode)) - vm := evm.NewLoomVm(ctx.State, ctx.EvmStore, nil, nil, nil, false) + vm := evm.NewLoomVm(ctx.State, nil, nil, nil, false) _, contractAddr, err = vm.Create(caller, byteCode, loom.NewBigUIntFromInt(0)) if err != nil { return contractAddr, err diff --git a/plugin/fake_context.go b/plugin/fake_context.go index d34d0fd763..2836fbc427 100644 --- a/plugin/fake_context.go +++ b/plugin/fake_context.go @@ -118,7 +118,7 @@ func (c *FakeContextWithEVM) CallEVM(addr loom.Address, input []byte, value *loo if c.useAccountBalanceManager { createABM = c.AccountBalanceManager } - vm := levm.NewLoomVm(c.State, c.EvmStore, nil, nil, createABM, false) + vm := levm.NewLoomVm(c.State, nil, nil, createABM, false) return vm.Call(c.ContractAddress(), addr, input, value) } @@ -127,7 +127,7 @@ func (c *FakeContextWithEVM) StaticCallEVM(addr loom.Address, input []byte) ([]b if c.useAccountBalanceManager { createABM = c.AccountBalanceManager } - vm := levm.NewLoomVm(c.State, c.EvmStore, nil, nil, createABM, false) + vm := levm.NewLoomVm(c.State, nil, nil, createABM, false) return vm.StaticCall(c.ContractAddress(), addr, input) } diff --git a/plugin/vm.go b/plugin/vm.go index 8006438821..33089ba884 100644 --- a/plugin/vm.go +++ b/plugin/vm.go @@ -36,7 +36,6 @@ var ( type PluginVM struct { Loader Loader State loomchain.State - EvmState loomchain.EVMState Registry registry.Registry EventHandler loomchain.EventHandler logger *loom.Logger @@ -70,11 +69,6 @@ func NewPluginVM( var _ vm.VM = &PluginVM{} -func (vm *PluginVM) WithEVMState(evmState loomchain.EVMState) *PluginVM { - vm.EvmState = evmState - return vm -} - func (vm *PluginVM) CreateContractContext( caller, addr loom.Address, @@ -202,7 +196,7 @@ func (vm *PluginVM) CallEVM(caller, addr loom.Address, input []byte, value *loom return nil, err } } - evm := levm.NewLoomVm(vm.State, vm.EvmState, vm.EventHandler, vm.receiptWriter, createABM, false) + evm := levm.NewLoomVm(vm.State, vm.EventHandler, vm.receiptWriter, createABM, false) return evm.Call(caller, addr, input, value) } @@ -216,7 +210,7 @@ func (vm *PluginVM) StaticCallEVM(caller, addr loom.Address, input []byte) ([]by } } - evm := levm.NewLoomVm(vm.State, vm.EvmState, vm.EventHandler, vm.receiptWriter, createABM, false) + evm := levm.NewLoomVm(vm.State, vm.EventHandler, vm.receiptWriter, createABM, false) return evm.StaticCall(caller, addr, input) } diff --git a/plugin/vm_test.go b/plugin/vm_test.go index 0f68cda503..f089fab0e3 100644 --- a/plugin/vm_test.go +++ b/plugin/vm_test.go @@ -19,7 +19,6 @@ import ( ptypes "github.com/loomnetwork/go-loom/plugin/types" "github.com/loomnetwork/go-loom/testdata" "github.com/loomnetwork/loomchain" - cdb "github.com/loomnetwork/loomchain/db" "github.com/loomnetwork/loomchain/eth/subs" "github.com/loomnetwork/loomchain/events" levm "github.com/loomnetwork/loomchain/evm" @@ -141,14 +140,9 @@ func TestPluginVMContractContextCaller(t *testing.T) { state := loomchain.NewStoreState(context.Background(), store.NewMemStore(), block, nil, nil) createRegistry, err := registry.NewRegistryFactory(registry.LatestRegistryVersion) require.NoError(t, err) - evmDB, err := cdb.LoadDB("memdb", "", "", 256, 4, false) - if err != nil { - panic(err) - } - evmStore := store.NewEvmStore(evmDB, 100, 0) - vm := NewPluginVM(loader, state, createRegistry(state), &fakeEventHandler{}, nil, nil, nil, nil).WithEVMState(evmStore) - evm := levm.NewLoomVm(state, evmStore, nil, nil, nil, false) + vm := NewPluginVM(loader, state, createRegistry(state), &fakeEventHandler{}, nil, nil, nil, nil) + evm := levm.NewLoomVm(state, nil, nil, nil, false) // Deploy contracts owner := loom.RootAddress("chain") diff --git a/rpc/query_server.go b/rpc/query_server.go index 51f1de73cb..9ae7b27ed0 100755 --- a/rpc/query_server.go +++ b/rpc/query_server.go @@ -7,6 +7,8 @@ import ( "strconv" "strings" + "github.com/ethereum/go-ethereum/core/state" + "github.com/gorilla/websocket" "github.com/gogo/protobuf/proto" @@ -132,6 +134,7 @@ type QueryServer struct { EventStore store.EventStore AuthCfg *auth.Config Web3Cfg *eth.Web3Config + EVMState *state.StateDB } var _ QueryService = &QueryServer{} @@ -269,9 +272,8 @@ func (s *QueryServer) queryEvm(state loomchain.State, caller, contract loom.Addr return nil, err } } - version := state.Block().Height - evmSnapshot := s.EvmStore.GetSnapshot(version) - vm := levm.NewLoomVm(state, evmSnapshot, nil, nil, createABM, false) + + vm := levm.NewLoomVm(state, nil, nil, createABM, false) return vm.StaticCall(callerAddr, contract, query) } @@ -316,9 +318,7 @@ func (s *QueryServer) GetEvmCode(contract string) ([]byte, error) { snapshot := s.StateProvider.ReadOnlyState() defer snapshot.Release() - version := snapshot.Block().Height - evmSnapshot := s.EvmStore.GetSnapshot(version) - vm := levm.NewLoomVm(snapshot, evmSnapshot, nil, nil, nil, false) + vm := levm.NewLoomVm(snapshot, nil, nil, nil, false) return vm.GetCode(contractAddr) } @@ -332,9 +332,7 @@ func (s *QueryServer) EthGetCode(address eth.Data, block eth.BlockHeight) (eth.D snapshot := s.StateProvider.ReadOnlyState() defer snapshot.Release() - version := snapshot.Block().Height - evmSnapshot := s.EvmStore.GetSnapshot(version) - evm := levm.NewLoomVm(snapshot, evmSnapshot, nil, nil, nil, false) + evm := levm.NewLoomVm(snapshot, nil, nil, nil, false) code, err := evm.GetCode(addr) if err != nil { return "", errors.Wrapf(err, "getting evm code for %v", address) @@ -1109,9 +1107,7 @@ func (s *QueryServer) EthGetStorageAt(local eth.Data, position string, block eth return "", errors.Wrapf(err, "unable to get storage at height %v", block) } - version := snapshot.Block().Height - evmSnapshot := s.EvmStore.GetSnapshot(version) - evm := levm.NewLoomVm(snapshot, evmSnapshot, nil, nil, nil, false) + evm := levm.NewLoomVm(snapshot, nil, nil, nil, false) storage, err := evm.GetStorageAt(address, ethcommon.HexToHash(position).Bytes()) if err != nil { return "", errors.Wrapf(err, "failed to get EVM storage at %v", address.Local.String()) diff --git a/store/evmstore.go b/store/evmstore.go index 7f932d1276..af81b8cf91 100644 --- a/store/evmstore.go +++ b/store/evmstore.go @@ -279,6 +279,15 @@ func (s *EvmStore) TrieDB() *trie.Database { return s.trieDB } +// GetVMRootKey returns the lastest root of EVM state trie +func (s *EvmStore) GetVMRootKey() []byte { + return s.Get(util.PrefixKey(vmPrefix, rootKey)) +} + +func (s *EvmStore) SetVMRootKey(root []byte) { + s.Set(util.PrefixKey(vmPrefix, rootKey), root) +} + func (s *EvmStore) getLastSavedRoot(targetVersion int64) ([]byte, int64) { start := util.PrefixKey(vmPrefix, evmRootPrefix) end := prefixRangeEnd(evmRootKey(targetVersion)) From c03f7fa797fa971ed220dd52e457352260d94bea Mon Sep 17 00:00:00 2001 From: Pathorn Date: Thu, 7 Nov 2019 17:28:20 +0700 Subject: [PATCH 26/80] revert tg branch --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f7605e78f2..a7bd4e65ea 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ BINANCE_TGORACLE_DIR=$(GOPATH)/src/$(PKG_BINANCE_TGORACLE) # specific commit. GO_LOOM_GIT_REV = HEAD # Specifies the loomnetwork/transfer-gateway branch/revision to use. -TG_GIT_REV = in-memory-evmstate +TG_GIT_REV = HEAD # loomnetwork/go-ethereum loomchain branch ETHEREUM_GIT_REV = in-memory-statedb # use go-plugin we get 'timeout waiting for connection info' error From 5e3a4ab6c183724fcf3fd278c6a6a61ec449463f Mon Sep 17 00:00:00 2001 From: Pathorn Date: Thu, 7 Nov 2019 17:44:32 +0700 Subject: [PATCH 27/80] fix unit test --- plugin/fake_context.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/plugin/fake_context.go b/plugin/fake_context.go index 2836fbc427..fc4dc16605 100644 --- a/plugin/fake_context.go +++ b/plugin/fake_context.go @@ -6,6 +6,8 @@ import ( "context" "time" + gcommon "github.com/ethereum/go-ethereum/common" + gstate "github.com/ethereum/go-ethereum/core/state" loom "github.com/loomnetwork/go-loom" "github.com/loomnetwork/go-loom/plugin" "github.com/loomnetwork/go-loom/types" @@ -20,7 +22,6 @@ import ( type FakeContextWithEVM struct { *plugin.FakeContext State loomchain.State - EvmStore *store.EvmStore useAccountBalanceManager bool } @@ -43,10 +44,15 @@ func CreateFakeContextWithEVM(caller, address loom.Address) *FakeContextWithEVM panic(err) } evmStore := store.NewEvmStore(evmDB, 100, 0) + ethDB := store.NewLoomEthDB(evmStore, nil) + evmRoot, _ := evmStore.Version() + stateDB := gstate.NewDatabase(ethDB) + stateDB.SetTrieDB(evmStore.TrieDB()) + evmState, _ := gstate.New(gcommon.BytesToHash(evmRoot), stateDB) + return &FakeContextWithEVM{ FakeContext: ctx, - State: state, - EvmStore: evmStore, + State: state.WithEVMState(evmState), } } @@ -55,7 +61,6 @@ func (c *FakeContextWithEVM) WithValidators(validators []*types.Validator) *Fake FakeContext: c.FakeContext.WithValidators(validators), State: c.State, useAccountBalanceManager: c.useAccountBalanceManager, - EvmStore: c.EvmStore, } } @@ -64,7 +69,6 @@ func (c *FakeContextWithEVM) WithBlock(header loom.BlockHeader) *FakeContextWith FakeContext: c.FakeContext.WithBlock(header), State: c.State, useAccountBalanceManager: c.useAccountBalanceManager, - EvmStore: c.EvmStore, } } @@ -73,7 +77,6 @@ func (c *FakeContextWithEVM) WithSender(caller loom.Address) *FakeContextWithEVM FakeContext: c.FakeContext.WithSender(caller), State: c.State, useAccountBalanceManager: c.useAccountBalanceManager, - EvmStore: c.EvmStore, } } @@ -82,7 +85,6 @@ func (c *FakeContextWithEVM) WithAddress(addr loom.Address) *FakeContextWithEVM FakeContext: c.FakeContext.WithAddress(addr), State: c.State, useAccountBalanceManager: c.useAccountBalanceManager, - EvmStore: c.EvmStore, } } @@ -92,7 +94,6 @@ func (c *FakeContextWithEVM) WithFeature(name string, value bool) *FakeContextWi FakeContext: c.FakeContext, State: c.State, useAccountBalanceManager: c.useAccountBalanceManager, - EvmStore: c.EvmStore, } } @@ -101,7 +102,6 @@ func (c *FakeContextWithEVM) WithAccountBalanceManager(enable bool) *FakeContext FakeContext: c.FakeContext, State: c.State, useAccountBalanceManager: enable, - EvmStore: c.EvmStore, } } From 95f91e110ed57c11d38d63c23b81adc7443dce14 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Thu, 7 Nov 2019 19:09:50 +0700 Subject: [PATCH 28/80] fix broken tests --- e2e/eth-test-1-loom.yaml | 3 ++- e2e/eth_test.go | 6 +++--- evm/evm_test.go | 15 ++++++++++++++- evm/loomevm.go | 2 +- plugin/vm_test.go | 14 +++++++++++++- 5 files changed, 33 insertions(+), 7 deletions(-) diff --git a/e2e/eth-test-1-loom.yaml b/e2e/eth-test-1-loom.yaml index 382cafce07..f7d6933235 100644 --- a/e2e/eth-test-1-loom.yaml +++ b/e2e/eth-test-1-loom.yaml @@ -1,3 +1,4 @@ AppStore: - Version: 1 + Version: 3 +DPOSVersion: 2 CreateEmptyBlocks: false \ No newline at end of file diff --git a/e2e/eth_test.go b/e2e/eth_test.go index e223cb0251..b2f94ed5c1 100644 --- a/e2e/eth_test.go +++ b/e2e/eth_test.go @@ -18,10 +18,10 @@ func TestEthJSONRPC2(t *testing.T) { }{ {"blockNumber", "eth-1-test.toml", 1, 1, "empty-genesis.json", "eth-test-1-loom.yaml"}, {"ethPolls", "eth-2-test.toml", 1, 1, "empty-genesis.json", "eth-test-2-loom.yaml"}, - {"getBlockByNumber", "eth-3-test.toml", 1, 1, "empty-genesis.json", "eth-test-1-loom.yaml"}, - {"getBlockTransactionCountByNumber", "eth-4-test.toml", 1, 1, "empty-genesis.json", "eth-test-1-loom.yaml"}, + {"getBlockByNumber", "eth-3-test.toml", 1, 1, "dposv2-genesis.json", "eth-test-1-loom.yaml"}, + {"getBlockTransactionCountByNumber", "eth-4-test.toml", 1, 1, "dposv2-genesis.json", "eth-test-1-loom.yaml"}, {"getLogs", "eth-5-test.toml", 1, 4, "empty-genesis.json", "eth-test-2-loom.yaml"}, - {"go-getBlockByNumber", "eth-6-test.toml", 1, 3, "coin.genesis.json", "eth-test-1-loom.yaml"}, + {"go-getBlockByNumber", "eth-6-test.toml", 1, 3, "dposv2-genesis.json", "eth-test-1-loom.yaml"}, } for _, test := range tests { diff --git a/evm/evm_test.go b/evm/evm_test.go index 6a93e3cac0..5d26faf19b 100644 --- a/evm/evm_test.go +++ b/evm/evm_test.go @@ -23,6 +23,9 @@ import ( lvm "github.com/loomnetwork/loomchain/vm" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" + + gcommon "github.com/ethereum/go-ethereum/common" + gstate "github.com/ethereum/go-ethereum/core/state" ) const ( @@ -40,7 +43,17 @@ func mockState() loomchain.State { header := abci.Header{} header.Height = BlockHeight header.Time = blockTime - return loomchain.NewStoreState(context.Background(), store.NewMemStore(), header, nil, nil) + return loomchain.NewStoreState(context.Background(), store.NewMemStore(), header, nil, nil).WithEVMState(mockEVMState()) +} + +func mockEVMState() *gstate.StateDB { + ethDB := store.NewLoomEthDB(store.NewMemStore(), nil) + stateDB := gstate.NewDatabase(ethDB) + evmState, err := gstate.New(gcommon.BytesToHash(nil), stateDB) + if err != nil { + panic(err) + } + return evmState } func TestProcessDeployTx(t *testing.T) { diff --git a/evm/loomevm.go b/evm/loomevm.go index 91c8c05a06..6cb522c46b 100644 --- a/evm/loomevm.go +++ b/evm/loomevm.go @@ -182,7 +182,7 @@ func (lvm LoomVm) Call(caller, addr loom.Address, input []byte, value *loom.BigU if err != nil { return nil, err } - levm.Call(caller, addr, input, value) + _, err = levm.Call(caller, addr, input, value) var txHash []byte if lvm.receiptHandler != nil { diff --git a/plugin/vm_test.go b/plugin/vm_test.go index f089fab0e3..2ea934448a 100644 --- a/plugin/vm_test.go +++ b/plugin/vm_test.go @@ -12,6 +12,8 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" + gcommon "github.com/ethereum/go-ethereum/common" + gstate "github.com/ethereum/go-ethereum/core/state" proto "github.com/gogo/protobuf/proto" "github.com/loomnetwork/go-loom" loom_plugin "github.com/loomnetwork/go-loom/plugin" @@ -122,6 +124,16 @@ func (c *VMTestContract) CheckQueryCaller(ctx contract.StaticContext, args *test return &testdata.StaticCallResult{}, nil } +func mockEVMState() *gstate.StateDB { + ethDB := store.NewLoomEthDB(store.NewMemStore(), nil) + stateDB := gstate.NewDatabase(ethDB) + evmState, err := gstate.New(gcommon.BytesToHash(nil), stateDB) + if err != nil { + panic(err) + } + return evmState +} + func TestPluginVMContractContextCaller(t *testing.T) { fc1 := &VMTestContract{t: t, Name: "fakecontract1"} @@ -137,7 +149,7 @@ func TestPluginVMContractContextCaller(t *testing.T) { Height: int64(34), Time: time.Unix(123456789, 0), } - state := loomchain.NewStoreState(context.Background(), store.NewMemStore(), block, nil, nil) + state := loomchain.NewStoreState(context.Background(), store.NewMemStore(), block, nil, nil).WithEVMState(mockEVMState()) createRegistry, err := registry.NewRegistryFactory(registry.LatestRegistryVersion) require.NoError(t, err) From 2e268ef04c057e928994761f6a133f5bcbbedec6 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Fri, 8 Nov 2019 12:53:49 +0700 Subject: [PATCH 29/80] add dposv2-genesis --- e2e/dposv2-genesis.json | 102 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 e2e/dposv2-genesis.json diff --git a/e2e/dposv2-genesis.json b/e2e/dposv2-genesis.json new file mode 100644 index 0000000000..ac639bf0e7 --- /dev/null +++ b/e2e/dposv2-genesis.json @@ -0,0 +1,102 @@ +{ + "contracts": [ + { + "vm": "plugin", + "format": "plugin", + "name": "coin", + "location": "coin:1.0.0", + "init": null + }, + { + "vm": "plugin", + "format": "plugin", + "name": "dposV2", + "location": "dposV2:2.0.0", + "init": { + "params": { + "validatorCount": "21", + "electionCycleLength": "604800" + }, + "validators": [ + { + "pubKey": "dMI2nJa3ZOxU3yFYNVRYarPOda5b19qZdGENG6yFVVk=", + "power": "10" + } + ] + } + }, + { + "vm": "plugin", + "format": "plugin", + "name": "addressmapper", + "location": "addressmapper:0.1.0", + "init": null + }, + { + "vm": "plugin", + "format": "plugin", + "name": "chainconfig", + "location": "chainconfig:1.0.0", + "init": { + "owner": { + "chainId": "default", + "local": "8ebnLFSTiXXZuVhl8mQJRL8kwJk=" + }, + "features": [ + { + "name": "dpos:v3.1", + "status": "WAITING" + }, + { + "name": "chaincfg:v1.1", + "status": "WAITING" + }, + { + "name": "chaincfg:v1.2", + "status": "WAITING" + }, + { + "name": "chaincfg:v1.3", + "status": "WAITING" + }, + { + "name": "receipts:v2", + "status": "WAITING" + }, + { + "name": "receipts:v3.4", + "status": "WAITING" + }, + { + "name": "receipts:v3.1", + "status": "WAITING" + }, + { + "name": "coin:v1.1", + "status": "WAITING" + }, + { + "name": "appstore:v3.1", + "status": "WAITING" + }, + { + "name": "auth:sigtx:eth", + "status": "WAITING" + }, + { + "name": "tx:eth", + "status": "WAITING" + }, + { + "name": "tx:check-value", + "status": "WAITING" + }, + { + "name": "evm:constantinople", + "status": "WAITING" + } + ] + } + } + ] +} From 82c887db2b54040a11a20839ed36b91154efe729 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Fri, 8 Nov 2019 15:10:18 +0700 Subject: [PATCH 30/80] reset logs after each tx --- app.go | 5 +---- evm/loomevm.go | 3 +++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app.go b/app.go index 85ea2c5ce8..6993ae8c13 100644 --- a/app.go +++ b/app.go @@ -911,10 +911,7 @@ func (a *Application) height() int64 { } func (a *Application) ReadOnlyState() State { root := a.EvmStore.GetVMRootKey() - ethDB := store.NewLoomEthDB(a.EvmStore.GetSnapshot(a.lastBlockHeader.Height), nil) - stateDB := gstate.NewDatabase(ethDB) - stateDB.SetTrieDB(a.EVMState.Database().TrieDB()) - snapshotEVMState, err := gstate.New(gcommon.BytesToHash(root), stateDB) + snapshotEVMState, err := gstate.New(gcommon.BytesToHash(root), a.EVMState.Database()) if err != nil { panic(err) } diff --git a/evm/loomevm.go b/evm/loomevm.go index 6cb522c46b..782907a60d 100644 --- a/evm/loomevm.go +++ b/evm/loomevm.go @@ -36,6 +36,7 @@ type StateDB interface { ethvm.StateDB Database() state.Database Logs() []*types.Log + ResetLogs() Commit(bool) (common.Hash, error) } @@ -135,6 +136,7 @@ func (lvm LoomVm) Create(caller loom.Address, code []byte, value *loom.BigUInt) events = lvm.receiptHandler.GetEventsFromLogs( levm.sdb.Logs(), lvm.state.Block().Height, caller, addr, code, ) + levm.sdb.ResetLogs() } if !lvm.state.FeatureEnabled(features.EvmTxReceiptsVersion3_4, false) { @@ -191,6 +193,7 @@ func (lvm LoomVm) Call(caller, addr loom.Address, input []byte, value *loom.BigU events = lvm.receiptHandler.GetEventsFromLogs( levm.sdb.Logs(), lvm.state.Block().Height, caller, addr, input, ) + levm.sdb.ResetLogs() } if !lvm.state.FeatureEnabled(features.EvmTxReceiptsVersion3_4, false) { From 51c7eeb83e8a5efeed2d0ea693008d2dfc73d11a Mon Sep 17 00:00:00 2001 From: Pathorn Date: Fri, 8 Nov 2019 16:26:40 +0700 Subject: [PATCH 31/80] remove unused code --- store/evmstore.go | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/store/evmstore.go b/store/evmstore.go index af81b8cf91..ef06d31c4c 100644 --- a/store/evmstore.go +++ b/store/evmstore.go @@ -322,16 +322,12 @@ func NewEvmStoreSnapshot(snapshot db.Snapshot, rootHash []byte) *EvmStoreSnapsho Snapshot: snapshot, rootHash: rootHash, } - ethDB := NewLoomEthDB(evmSnapshot, nil) - // This should be read only database - evmSnapshot.trieDB = trie.NewDatabase(ethDB) return evmSnapshot } type EvmStoreSnapshot struct { db.Snapshot rootHash []byte - trieDB *trie.Database } func (s *EvmStoreSnapshot) Get(key []byte) []byte { @@ -350,22 +346,6 @@ func (s *EvmStoreSnapshot) Has(key []byte) bool { return s.Snapshot.Has(key) } -func (s *EvmStoreSnapshot) TrieDB() *trie.Database { - return s.trieDB -} - -func (s *EvmStoreSnapshot) Delete(key []byte) { - panic("EvmStoreSnapshot does not implement delete") -} - -func (s *EvmStoreSnapshot) Set(key, val []byte) { - panic("EvmStoreSnapshot does not implement set") -} - -func (s *EvmStoreSnapshot) Range(key []byte) plugin.RangeData { - panic("EvmStoreSnapshot does not implement range") -} - func remove(keys []string, key string) []string { for i, value := range keys { if value == key { From 792072e57a5c6d3f33c8354b163504d1f96315f2 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Mon, 11 Nov 2019 14:23:36 +0700 Subject: [PATCH 32/80] reset state after commit --- app.go | 1 + 1 file changed, 1 insertion(+) diff --git a/app.go b/app.go index 6993ae8c13..9e316e6a45 100644 --- a/app.go +++ b/app.go @@ -855,6 +855,7 @@ func (a *Application) Commit() abci.ResponseCommit { panic(err) } a.EvmStore.SetVMRootKey(evmStateRoot[:]) + a.EVMState.Reset(evmStateRoot) appHash, _, err := a.Store.SaveVersion() if err != nil { From 24de62541cf00393537e02f6af4ffe686a7dcf16 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Thu, 21 Nov 2019 13:51:32 +0700 Subject: [PATCH 33/80] commit state every tx --- app.go | 3 ++- evm/loomevm.go | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app.go b/app.go index 392ea45c6d..33097e9e24 100644 --- a/app.go +++ b/app.go @@ -840,7 +840,7 @@ func (a *Application) deliverTx2(storeTx store.KVStoreTx, txBytes []byte) abci.R // Commit commits the current block func (a *Application) Commit() abci.ResponseCommit { - height := a.curBlockHeader.GetHeight() + var err error defer func(begin time.Time) { lvs := []string{"method", "Commit", "error", fmt.Sprint(err != nil)} @@ -861,6 +861,7 @@ func (a *Application) Commit() abci.ResponseCommit { panic(err) } + height := a.curBlockHeader.GetHeight() if err := a.EvmAuxStore.SaveChildTxRefs(a.childTxRefs); err != nil { // TODO: consider panic instead log.Error("Failed to save Tendermint -> EVM tx hash refs", "height", height, "err", err) diff --git a/evm/loomevm.go b/evm/loomevm.go index 782907a60d..362008cdbc 100644 --- a/evm/loomevm.go +++ b/evm/loomevm.go @@ -128,6 +128,9 @@ func (lvm LoomVm) Create(caller loom.Address, code []byte, value *loom.BigUInt) return nil, loom.Address{}, err } bytecode, addr, err := levm.Create(caller, code, value) + if err != nil { + _, err = levm.sdb.Commit(true) + } var txHash []byte if lvm.receiptHandler != nil { @@ -185,6 +188,9 @@ func (lvm LoomVm) Call(caller, addr loom.Address, input []byte, value *loom.BigU return nil, err } _, err = levm.Call(caller, addr, input, value) + if err != nil { + _, err = levm.sdb.Commit(true) + } var txHash []byte if lvm.receiptHandler != nil { From fca3e54ff153b534de230b26ae88e986252f9a20 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Thu, 21 Nov 2019 13:58:53 +0700 Subject: [PATCH 34/80] fix condition --- evm/loomevm.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evm/loomevm.go b/evm/loomevm.go index 362008cdbc..c8b15f17f4 100644 --- a/evm/loomevm.go +++ b/evm/loomevm.go @@ -128,7 +128,7 @@ func (lvm LoomVm) Create(caller loom.Address, code []byte, value *loom.BigUInt) return nil, loom.Address{}, err } bytecode, addr, err := levm.Create(caller, code, value) - if err != nil { + if err == nil { _, err = levm.sdb.Commit(true) } @@ -188,7 +188,7 @@ func (lvm LoomVm) Call(caller, addr loom.Address, input []byte, value *loom.BigU return nil, err } _, err = levm.Call(caller, addr, input, value) - if err != nil { + if err == nil { _, err = levm.sdb.Commit(true) } From dd7f2d0e2cabb9344ffb4c99bd6465faa8e65685 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Fri, 22 Nov 2019 13:58:30 +0700 Subject: [PATCH 35/80] address PR comment --- cmd/loom/loom.go | 2 -- evm/loomevm.go | 6 ------ rpc/query_server.go | 4 ---- store/evmstore.go | 2 +- 4 files changed, 1 insertion(+), 13 deletions(-) diff --git a/cmd/loom/loom.go b/cmd/loom/loom.go index 6a3aad1f4f..3a190f1a92 100644 --- a/cmd/loom/loom.go +++ b/cmd/loom/loom.go @@ -1299,9 +1299,7 @@ func initQueryService( EventStore: app.EventStore, AuthCfg: cfg.Auth, EvmAuxStore: app.EvmAuxStore, - EvmStore: app.EvmStore, Web3Cfg: cfg.Web3, - EVMState: app.EVMState, DPOSCfg: cfg.DPOS, } bus := &rpc.QueryEventBus{ diff --git a/evm/loomevm.go b/evm/loomevm.go index c8b15f17f4..782907a60d 100644 --- a/evm/loomevm.go +++ b/evm/loomevm.go @@ -128,9 +128,6 @@ func (lvm LoomVm) Create(caller loom.Address, code []byte, value *loom.BigUInt) return nil, loom.Address{}, err } bytecode, addr, err := levm.Create(caller, code, value) - if err == nil { - _, err = levm.sdb.Commit(true) - } var txHash []byte if lvm.receiptHandler != nil { @@ -188,9 +185,6 @@ func (lvm LoomVm) Call(caller, addr loom.Address, input []byte, value *loom.BigU return nil, err } _, err = levm.Call(caller, addr, input, value) - if err == nil { - _, err = levm.sdb.Commit(true) - } var txHash []byte if lvm.receiptHandler != nil { diff --git a/rpc/query_server.go b/rpc/query_server.go index b41c6eb9ee..da1dacdc7e 100755 --- a/rpc/query_server.go +++ b/rpc/query_server.go @@ -8,8 +8,6 @@ import ( "strings" "time" - "github.com/ethereum/go-ethereum/core/state" - "github.com/gorilla/websocket" "github.com/gogo/protobuf/proto" @@ -132,12 +130,10 @@ type QueryServer struct { RPCListenAddress string store.BlockStore *evmaux.EvmAuxStore - *store.EvmStore blockindex.BlockIndexStore EventStore store.EventStore AuthCfg *auth.Config Web3Cfg *eth.Web3Config - EVMState *state.StateDB totalStakedAmount *totalStakedAmount DPOSCfg *config.DPOSConfig } diff --git a/store/evmstore.go b/store/evmstore.go index ef06d31c4c..bda6be799b 100644 --- a/store/evmstore.go +++ b/store/evmstore.go @@ -281,7 +281,7 @@ func (s *EvmStore) TrieDB() *trie.Database { // GetVMRootKey returns the lastest root of EVM state trie func (s *EvmStore) GetVMRootKey() []byte { - return s.Get(util.PrefixKey(vmPrefix, rootKey)) + return s.rootHash } func (s *EvmStore) SetVMRootKey(root []byte) { From 1ec9fa0d548a55789fa176984a1ed4c275c65a9e Mon Sep 17 00:00:00 2001 From: Pathorn Date: Fri, 22 Nov 2019 16:13:51 +0700 Subject: [PATCH 36/80] address PR comment --- app.go | 8 +------- store/evmstore.go | 9 +++++++++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app.go b/app.go index 33097e9e24..ab69579e40 100644 --- a/app.go +++ b/app.go @@ -15,7 +15,6 @@ import ( "github.com/loomnetwork/loomchain/features" "github.com/loomnetwork/loomchain/registry" - gcommon "github.com/ethereum/go-ethereum/common" "github.com/go-kit/kit/metrics" kitprometheus "github.com/go-kit/kit/metrics/prometheus" "github.com/loomnetwork/go-loom" @@ -929,11 +928,6 @@ func (a *Application) height() int64 { } func (a *Application) ReadOnlyState() State { - root := a.EvmStore.GetVMRootKey() - snapshotEVMState, err := gstate.New(gcommon.BytesToHash(root), a.EVMState.Database()) - if err != nil { - panic(err) - } // TODO: the store snapshot should be created atomically, otherwise the block header might // not match the state... need to figure out why this hasn't spectacularly failed already return NewStoreStateSnapshot( @@ -942,5 +936,5 @@ func (a *Application) ReadOnlyState() State { a.lastBlockHeader, nil, // TODO: last block hash! a.GetValidatorSet, - ).WithEVMState(snapshotEVMState) + ).WithEVMState(a.EvmStore.GetSnapshot(a.lastBlockHeader.Height).StateDB(a.EVMState.Database())) } diff --git a/store/evmstore.go b/store/evmstore.go index bda6be799b..8bbf40c7d4 100644 --- a/store/evmstore.go +++ b/store/evmstore.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum/trie" gcommon "github.com/ethereum/go-ethereum/common" + gstate "github.com/ethereum/go-ethereum/core/state" "github.com/go-kit/kit/metrics" kitprometheus "github.com/go-kit/kit/metrics/prometheus" lru "github.com/hashicorp/golang-lru" @@ -346,6 +347,14 @@ func (s *EvmStoreSnapshot) Has(key []byte) bool { return s.Snapshot.Has(key) } +func (s *EvmStoreSnapshot) StateDB(db gstate.Database) *gstate.StateDB { + stateDB, err := gstate.New(gcommon.BytesToHash(s.rootHash), db) + if err != nil { + panic(err) + } + return stateDB +} + func remove(keys []string, key string) []string { for i, value := range keys { if value == key { From 8bb11f08b818938967c90db2fffc1b091e4fa08d Mon Sep 17 00:00:00 2001 From: Pathorn Date: Fri, 22 Nov 2019 19:00:35 +0700 Subject: [PATCH 37/80] use clone version of evm state for checktx --- app.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.go b/app.go index ab69579e40..2ca84f4259 100644 --- a/app.go +++ b/app.go @@ -671,7 +671,7 @@ func (a *Application) CheckTx(txBytes []byte) abci.ResponseCheckTx { a.curBlockHeader, a.curBlockHash, a.GetValidatorSet, - ).WithOnChainConfig(a.config).WithEVMState(a.EVMState) + ).WithOnChainConfig(a.config).WithEVMState(a.EVMState.Copy()) // Receipts & events generated in CheckTx must be discarded since the app state changes they // reflect aren't persisted. From 00475d4df7a16844450c3aacd46f882e5be8259b Mon Sep 17 00:00:00 2001 From: Pathorn Date: Sun, 24 Nov 2019 03:11:11 +0700 Subject: [PATCH 38/80] finalise state after each tx --- evm/loomevm.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/evm/loomevm.go b/evm/loomevm.go index 782907a60d..640070d3a7 100644 --- a/evm/loomevm.go +++ b/evm/loomevm.go @@ -38,6 +38,7 @@ type StateDB interface { Logs() []*types.Log ResetLogs() Commit(bool) (common.Hash, error) + Finalise(deleteEmptyObjects bool) } // TODO: this doesn't need to be exported, rename to loomEvmWithState @@ -128,6 +129,7 @@ func (lvm LoomVm) Create(caller loom.Address, code []byte, value *loom.BigUInt) return nil, loom.Address{}, err } bytecode, addr, err := levm.Create(caller, code, value) + levm.sdb.Finalise(true) var txHash []byte if lvm.receiptHandler != nil { @@ -185,6 +187,7 @@ func (lvm LoomVm) Call(caller, addr loom.Address, input []byte, value *loom.BigU return nil, err } _, err = levm.Call(caller, addr, input, value) + levm.sdb.Finalise(true) var txHash []byte if lvm.receiptHandler != nil { From 43068fe962a387306ba7219761f029117f7bce08 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Sun, 24 Nov 2019 03:20:17 +0700 Subject: [PATCH 39/80] finalize state if tx succeeds --- evm/loomevm.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/evm/loomevm.go b/evm/loomevm.go index 640070d3a7..d0ce2ea2fe 100644 --- a/evm/loomevm.go +++ b/evm/loomevm.go @@ -129,7 +129,9 @@ func (lvm LoomVm) Create(caller loom.Address, code []byte, value *loom.BigUInt) return nil, loom.Address{}, err } bytecode, addr, err := levm.Create(caller, code, value) - levm.sdb.Finalise(true) + if err == nil { + levm.sdb.Finalise(true) + } var txHash []byte if lvm.receiptHandler != nil { @@ -187,7 +189,9 @@ func (lvm LoomVm) Call(caller, addr loom.Address, input []byte, value *loom.BigU return nil, err } _, err = levm.Call(caller, addr, input, value) - levm.sdb.Finalise(true) + if err == nil { + levm.sdb.Finalise(true) + } var txHash []byte if lvm.receiptHandler != nil { From cbebc158923494223969015d36286bd458d8afcf Mon Sep 17 00:00:00 2001 From: Pathorn Date: Sun, 24 Nov 2019 04:07:56 +0700 Subject: [PATCH 40/80] revert finalise --- evm/loomevm.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/evm/loomevm.go b/evm/loomevm.go index d0ce2ea2fe..782907a60d 100644 --- a/evm/loomevm.go +++ b/evm/loomevm.go @@ -38,7 +38,6 @@ type StateDB interface { Logs() []*types.Log ResetLogs() Commit(bool) (common.Hash, error) - Finalise(deleteEmptyObjects bool) } // TODO: this doesn't need to be exported, rename to loomEvmWithState @@ -129,9 +128,6 @@ func (lvm LoomVm) Create(caller loom.Address, code []byte, value *loom.BigUInt) return nil, loom.Address{}, err } bytecode, addr, err := levm.Create(caller, code, value) - if err == nil { - levm.sdb.Finalise(true) - } var txHash []byte if lvm.receiptHandler != nil { @@ -189,9 +185,6 @@ func (lvm LoomVm) Call(caller, addr loom.Address, input []byte, value *loom.BigU return nil, err } _, err = levm.Call(caller, addr, input, value) - if err == nil { - levm.sdb.Finalise(true) - } var txHash []byte if lvm.receiptHandler != nil { From e47e0ceaa4d74b1541bfc93c7aba8f46a5ebac23 Mon Sep 17 00:00:00 2001 From: pathorn Date: Sun, 24 Nov 2019 05:50:30 +0000 Subject: [PATCH 41/80] revert snapshot if tx fails --- evm/loomevm.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/evm/loomevm.go b/evm/loomevm.go index 782907a60d..7e92ed14dc 100644 --- a/evm/loomevm.go +++ b/evm/loomevm.go @@ -127,7 +127,11 @@ func (lvm LoomVm) Create(caller loom.Address, code []byte, value *loom.BigUInt) if err != nil { return nil, loom.Address{}, err } + snapshot := lvm.state.EVMState().Snapshot() bytecode, addr, err := levm.Create(caller, code, value) + if err != nil { + lvm.state.EVMState().RevertToSnapshot(snapshot) + } var txHash []byte if lvm.receiptHandler != nil { @@ -184,7 +188,11 @@ func (lvm LoomVm) Call(caller, addr loom.Address, input []byte, value *loom.BigU if err != nil { return nil, err } + snapshot := lvm.state.EVMState().Snapshot() _, err = levm.Call(caller, addr, input, value) + if err != nil { + lvm.state.EVMState().RevertToSnapshot(snapshot) + } var txHash []byte if lvm.receiptHandler != nil { From 5abbc7616c07ea415aa73734692ab2ef8f1fe99f Mon Sep 17 00:00:00 2001 From: Pathorn Date: Mon, 25 Nov 2019 14:43:11 +0700 Subject: [PATCH 42/80] address PR comment --- app.go | 4 +++- store/evmstore.go | 5 ++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app.go b/app.go index 2ca84f4259..781bd976d3 100644 --- a/app.go +++ b/app.go @@ -665,13 +665,15 @@ func (a *Application) CheckTx(txBytes []byte) abci.ResponseCheckTx { storeTx := store.WrapAtomic(a.Store).BeginTx() defer storeTx.Rollback() + snapshot := a.EVMState.Snapshot() + defer a.EVMState.RevertToSnapshot(snapshot) state := NewStoreState( context.Background(), storeTx, a.curBlockHeader, a.curBlockHash, a.GetValidatorSet, - ).WithOnChainConfig(a.config).WithEVMState(a.EVMState.Copy()) + ).WithOnChainConfig(a.config).WithEVMState(a.EVMState) // Receipts & events generated in CheckTx must be discarded since the app state changes they // reflect aren't persisted. diff --git a/store/evmstore.go b/store/evmstore.go index 8bbf40c7d4..31780bf1cf 100644 --- a/store/evmstore.go +++ b/store/evmstore.go @@ -286,7 +286,7 @@ func (s *EvmStore) GetVMRootKey() []byte { } func (s *EvmStore) SetVMRootKey(root []byte) { - s.Set(util.PrefixKey(vmPrefix, rootKey), root) + s.rootHash = root } func (s *EvmStore) getLastSavedRoot(targetVersion int64) ([]byte, int64) { @@ -319,11 +319,10 @@ func (s *EvmStore) GetSnapshot(version int64) *EvmStoreSnapshot { } func NewEvmStoreSnapshot(snapshot db.Snapshot, rootHash []byte) *EvmStoreSnapshot { - evmSnapshot := &EvmStoreSnapshot{ + return &EvmStoreSnapshot{ Snapshot: snapshot, rootHash: rootHash, } - return evmSnapshot } type EvmStoreSnapshot struct { From 3d6d1c829fa7851dfa3f606b99e7f7723c55f513 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Mon, 25 Nov 2019 14:51:15 +0700 Subject: [PATCH 43/80] copying state instead of reverting --- app.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app.go b/app.go index 781bd976d3..2ca84f4259 100644 --- a/app.go +++ b/app.go @@ -665,15 +665,13 @@ func (a *Application) CheckTx(txBytes []byte) abci.ResponseCheckTx { storeTx := store.WrapAtomic(a.Store).BeginTx() defer storeTx.Rollback() - snapshot := a.EVMState.Snapshot() - defer a.EVMState.RevertToSnapshot(snapshot) state := NewStoreState( context.Background(), storeTx, a.curBlockHeader, a.curBlockHash, a.GetValidatorSet, - ).WithOnChainConfig(a.config).WithEVMState(a.EVMState) + ).WithOnChainConfig(a.config).WithEVMState(a.EVMState.Copy()) // Receipts & events generated in CheckTx must be discarded since the app state changes they // reflect aren't persisted. From c4679f34ea53e1bece7ab2f212bd6ad0405c513a Mon Sep 17 00:00:00 2001 From: Pathorn Date: Mon, 25 Nov 2019 19:11:49 +0700 Subject: [PATCH 44/80] address PR comments --- app.go | 65 +++++++++++++++++++++++++++++++++++------- cmd/loom/loom.go | 5 ++-- evm/loomevm.go | 39 ++++++++++++++++++------- plugin/fake_context.go | 6 +++- store/evmstore.go | 12 ++++++++ 5 files changed, 103 insertions(+), 24 deletions(-) diff --git a/app.go b/app.go index 2ca84f4259..2f21b0d8dd 100644 --- a/app.go +++ b/app.go @@ -8,6 +8,7 @@ import ( "fmt" "time" + gcommon "github.com/ethereum/go-ethereum/common" gstate "github.com/ethereum/go-ethereum/core/state" "github.com/loomnetwork/go-loom/config" "github.com/loomnetwork/go-loom/util" @@ -52,7 +53,48 @@ type State interface { SetFeature(string, bool) SetMinBuildNumber(uint64) ChangeConfigSetting(name, value string) error - EVMState() *gstate.StateDB + EVMState() *EVMState +} + +type EVMState struct { + sdb *gstate.StateDB + evmStore *store.EvmStore +} + +func NewEVMState(evmStore *store.EvmStore, sdb *gstate.StateDB) *EVMState { + return &EVMState{ + evmStore: evmStore, + sdb: sdb, + } +} + +func (s *EVMState) Commit() error { + evmStateRoot, err := s.sdb.Commit(true) + if err != nil { + return err + } + s.evmStore.SetVMRootKey(evmStateRoot[:]) + s.sdb.Reset(evmStateRoot) + return nil +} + +func (s *EVMState) GetSnapshot(version int64) (*EVMState, error) { + stateDB, err := gstate.New(gcommon.BytesToHash(s.evmStore.GetRootAt(version)), s.sdb.Database()) + if err != nil { + return nil, err + } + return &EVMState{sdb: stateDB, evmStore: s.evmStore}, nil +} + +func (s *EVMState) Clone() *EVMState { + return &EVMState{ + evmStore: s.evmStore, + sdb: s.sdb.Copy(), + } +} + +func (s *EVMState) StateDB() *gstate.StateDB { + return s.sdb } type StoreState struct { @@ -62,7 +104,7 @@ type StoreState struct { validators loom.ValidatorSet getValidatorSet GetValidatorSet config *cctypes.Config - evmState *gstate.StateDB + evmState *EVMState } var _ = State(&StoreState{}) @@ -104,7 +146,7 @@ func (s *StoreState) WithOnChainConfig(config *cctypes.Config) *StoreState { return s } -func (s *StoreState) WithEVMState(evmState *gstate.StateDB) *StoreState { +func (s *StoreState) WithEVMState(evmState *EVMState) *StoreState { s.evmState = evmState return s } @@ -149,7 +191,7 @@ func (s *StoreState) Context() context.Context { return s.ctx } -func (s *StoreState) EVMState() *gstate.StateDB { +func (s *StoreState) EVMState() *EVMState { return s.evmState } @@ -383,7 +425,7 @@ type Application struct { config *cctypes.Config childTxRefs []evmaux.ChildTxRef // links Tendermint txs to EVM txs ReceiptsVersion int32 - EVMState *gstate.StateDB + EVMState *EVMState committedTxs []CommittedTx } @@ -671,7 +713,7 @@ func (a *Application) CheckTx(txBytes []byte) abci.ResponseCheckTx { a.curBlockHeader, a.curBlockHash, a.GetValidatorSet, - ).WithOnChainConfig(a.config).WithEVMState(a.EVMState.Copy()) + ).WithOnChainConfig(a.config).WithEVMState(a.EVMState.Clone()) // Receipts & events generated in CheckTx must be discarded since the app state changes they // reflect aren't persisted. @@ -848,12 +890,9 @@ func (a *Application) Commit() abci.ResponseCommit { }(time.Now()) // Commit EVM state - evmStateRoot, err := a.EVMState.Commit(true) - if err != nil { + if err := a.EVMState.Commit(); err != nil { panic(err) } - a.EvmStore.SetVMRootKey(evmStateRoot[:]) - a.EVMState.Reset(evmStateRoot) appHash, _, err := a.Store.SaveVersion() if err != nil { @@ -928,6 +967,10 @@ func (a *Application) height() int64 { } func (a *Application) ReadOnlyState() State { + evmStateSnapshot, err := a.EVMState.GetSnapshot(a.lastBlockHeader.Height) + if err != nil { + panic(err) + } // TODO: the store snapshot should be created atomically, otherwise the block header might // not match the state... need to figure out why this hasn't spectacularly failed already return NewStoreStateSnapshot( @@ -936,5 +979,5 @@ func (a *Application) ReadOnlyState() State { a.lastBlockHeader, nil, // TODO: last block hash! a.GetValidatorSet, - ).WithEVMState(a.EvmStore.GetSnapshot(a.lastBlockHeader.Height).StateDB(a.EVMState.Database())) + ).WithEVMState(evmStateSnapshot) } diff --git a/cmd/loom/loom.go b/cmd/loom/loom.go index d71dca03ce..86de87870d 100644 --- a/cmd/loom/loom.go +++ b/cmd/loom/loom.go @@ -819,7 +819,7 @@ func loadApp( ), nil }) - var evmState *state.StateDB + var evmState *loomchain.EVMState if evm.EVMEnabled { vmManager.Register(vm.VMType_EVM, func(state loomchain.State) (vm.VM, error) { var createABM evm.AccountBalanceManagerFactoryFunc @@ -847,10 +847,11 @@ func loadApp( evmRoot, _ := evmStore.Version() stateDB := state.NewDatabase(ethDB) stateDB.SetTrieDB(evmStore.TrieDB()) - evmState, err = state.New(gcommon.BytesToHash(evmRoot), stateDB) + sdb, err := state.New(gcommon.BytesToHash(evmRoot), stateDB) if err != nil { return nil, err } + evmState = loomchain.NewEVMState(evmStore, sdb) } store.LogEthDBBatch = cfg.LogEthDbBatch diff --git a/evm/loomevm.go b/evm/loomevm.go index 7e92ed14dc..4fc42e6164 100644 --- a/evm/loomevm.go +++ b/evm/loomevm.go @@ -57,12 +57,12 @@ func NewLoomEvm( var err error if accountBalanceManager != nil { abm = newEVMAccountBalanceManager(accountBalanceManager, loomState.Block().ChainID) - p.sdb, err = newLoomStateDB(abm, loomState.EVMState()) + p.sdb, err = newLoomStateDB(abm, loomState.EVMState().StateDB()) if err != nil { return nil, err } } else { - p.sdb = loomState.EVMState() + p.sdb = loomState.EVMState().StateDB() } p.Evm = NewEvm(p.sdb, loomState, abm, debug) return p, nil @@ -127,20 +127,30 @@ func (lvm LoomVm) Create(caller loom.Address, code []byte, value *loom.BigUInt) if err != nil { return nil, loom.Address{}, err } - snapshot := lvm.state.EVMState().Snapshot() + stateDB := lvm.state.EVMState().StateDB() + lastLogsIndex := len(stateDB.Logs()) + // evm.Create changes Nonce even though tx fails + // To prevent any state change from error tx, create a snapshot and revert EVM state if tx fails + snapshot := stateDB.Snapshot() bytecode, addr, err := levm.Create(caller, code, value) if err != nil { - lvm.state.EVMState().RevertToSnapshot(snapshot) + stateDB.RevertToSnapshot(snapshot) } var txHash []byte if lvm.receiptHandler != nil { var events []*ptypes.EventData if err == nil { + allLogs := levm.sdb.Logs() + var addedLogs []*types.Log + for index, log := range allLogs { + if index >= lastLogsIndex { + addedLogs = append(addedLogs, log) + } + } events = lvm.receiptHandler.GetEventsFromLogs( - levm.sdb.Logs(), lvm.state.Block().Height, caller, addr, code, + addedLogs, lvm.state.Block().Height, caller, addr, code, ) - levm.sdb.ResetLogs() } if !lvm.state.FeatureEnabled(features.EvmTxReceiptsVersion3_4, false) { @@ -188,20 +198,29 @@ func (lvm LoomVm) Call(caller, addr loom.Address, input []byte, value *loom.BigU if err != nil { return nil, err } - snapshot := lvm.state.EVMState().Snapshot() + stateDB := lvm.state.EVMState().StateDB() + lastLogsIndex := len(stateDB.Logs()) + // To prevent any state change from error tx, create a snapshot and revert EVM state if tx fails + snapshot := stateDB.Snapshot() _, err = levm.Call(caller, addr, input, value) if err != nil { - lvm.state.EVMState().RevertToSnapshot(snapshot) + stateDB.RevertToSnapshot(snapshot) } var txHash []byte if lvm.receiptHandler != nil { var events []*ptypes.EventData if err == nil { + allLogs := stateDB.Logs() + var addedLogs []*types.Log + for index, log := range allLogs { + if index >= lastLogsIndex { + addedLogs = append(addedLogs, log) + } + } events = lvm.receiptHandler.GetEventsFromLogs( - levm.sdb.Logs(), lvm.state.Block().Height, caller, addr, input, + stateDB.Logs(), lvm.state.Block().Height, caller, addr, input, ) - levm.sdb.ResetLogs() } if !lvm.state.FeatureEnabled(features.EvmTxReceiptsVersion3_4, false) { diff --git a/plugin/fake_context.go b/plugin/fake_context.go index fc4dc16605..7ceefdf466 100644 --- a/plugin/fake_context.go +++ b/plugin/fake_context.go @@ -48,7 +48,11 @@ func CreateFakeContextWithEVM(caller, address loom.Address) *FakeContextWithEVM evmRoot, _ := evmStore.Version() stateDB := gstate.NewDatabase(ethDB) stateDB.SetTrieDB(evmStore.TrieDB()) - evmState, _ := gstate.New(gcommon.BytesToHash(evmRoot), stateDB) + sdb, err := gstate.New(gcommon.BytesToHash(evmRoot), stateDB) + if err != nil { + panic(err) + } + evmState := loomchain.NewEVMState(evmStore, sdb) return &FakeContextWithEVM{ FakeContext: ctx, diff --git a/store/evmstore.go b/store/evmstore.go index 31780bf1cf..f287e9a642 100644 --- a/store/evmstore.go +++ b/store/evmstore.go @@ -306,6 +306,18 @@ func (s *EvmStore) getLastSavedRoot(targetVersion int64) ([]byte, int64) { return nil, 0 } +func (s *EvmStore) GetRootAt(version int64) []byte { + var targetRoot []byte + // Expect cache to be almost 100% hit since cache miss yields extremely poor performance + val, exist := s.rootCache.Get(version) + if exist { + targetRoot = val.([]byte) + } else { + targetRoot, _ = s.getLastSavedRoot(version) + } + return targetRoot +} + func (s *EvmStore) GetSnapshot(version int64) *EvmStoreSnapshot { var targetRoot []byte // Expect cache to be almost 100% hit since cache miss yields extremely poor performance From bb65d193ee062397c3b82f86ce03fdaab1d23eb9 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Tue, 26 Nov 2019 00:31:10 +0700 Subject: [PATCH 45/80] fix unit tests --- evm/evm_test.go | 8 +++++--- plugin/vm_test.go | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/evm/evm_test.go b/evm/evm_test.go index 5d26faf19b..41af9510b3 100644 --- a/evm/evm_test.go +++ b/evm/evm_test.go @@ -18,6 +18,7 @@ import ( ethvm "github.com/ethereum/go-ethereum/core/vm" "github.com/loomnetwork/go-loom" "github.com/loomnetwork/loomchain" + "github.com/loomnetwork/loomchain/db" "github.com/loomnetwork/loomchain/features" "github.com/loomnetwork/loomchain/store" lvm "github.com/loomnetwork/loomchain/vm" @@ -46,14 +47,15 @@ func mockState() loomchain.State { return loomchain.NewStoreState(context.Background(), store.NewMemStore(), header, nil, nil).WithEVMState(mockEVMState()) } -func mockEVMState() *gstate.StateDB { +func mockEVMState() *loomchain.EVMState { ethDB := store.NewLoomEthDB(store.NewMemStore(), nil) stateDB := gstate.NewDatabase(ethDB) - evmState, err := gstate.New(gcommon.BytesToHash(nil), stateDB) + sdb, err := gstate.New(gcommon.BytesToHash(nil), stateDB) if err != nil { panic(err) } - return evmState + memDb, _ := db.LoadMemDB() + return loomchain.NewEVMState(store.NewEvmStore(memDb, 100, 0), sdb) } func TestProcessDeployTx(t *testing.T) { diff --git a/plugin/vm_test.go b/plugin/vm_test.go index 2ea934448a..214af51159 100644 --- a/plugin/vm_test.go +++ b/plugin/vm_test.go @@ -21,6 +21,7 @@ import ( ptypes "github.com/loomnetwork/go-loom/plugin/types" "github.com/loomnetwork/go-loom/testdata" "github.com/loomnetwork/loomchain" + "github.com/loomnetwork/loomchain/db" "github.com/loomnetwork/loomchain/eth/subs" "github.com/loomnetwork/loomchain/events" levm "github.com/loomnetwork/loomchain/evm" @@ -124,14 +125,15 @@ func (c *VMTestContract) CheckQueryCaller(ctx contract.StaticContext, args *test return &testdata.StaticCallResult{}, nil } -func mockEVMState() *gstate.StateDB { +func mockEVMState() *loomchain.EVMState { ethDB := store.NewLoomEthDB(store.NewMemStore(), nil) stateDB := gstate.NewDatabase(ethDB) - evmState, err := gstate.New(gcommon.BytesToHash(nil), stateDB) + sdb, err := gstate.New(gcommon.BytesToHash(nil), stateDB) if err != nil { panic(err) } - return evmState + memDb, _ := db.LoadMemDB() + return loomchain.NewEVMState(store.NewEvmStore(memDb, 100, 0), sdb) } func TestPluginVMContractContextCaller(t *testing.T) { From ed9b1bb3d7575a8bd74b78acbe1c5d3d9b1c6dc6 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Tue, 26 Nov 2019 11:32:45 +0700 Subject: [PATCH 46/80] fix truffle test --- evm/loomevm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/loomevm.go b/evm/loomevm.go index 4fc42e6164..c20e5c2038 100644 --- a/evm/loomevm.go +++ b/evm/loomevm.go @@ -219,7 +219,7 @@ func (lvm LoomVm) Call(caller, addr loom.Address, input []byte, value *loom.BigU } } events = lvm.receiptHandler.GetEventsFromLogs( - stateDB.Logs(), lvm.state.Block().Height, caller, addr, input, + addedLogs, lvm.state.Block().Height, caller, addr, input, ) } From 9f8b531f89cf588499ee92fe1b999355d9d7ac86 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Thu, 28 Nov 2019 13:16:27 +0700 Subject: [PATCH 47/80] address PR comments --- app.go | 54 ++++++----------------------------------- cmd/loom/loom.go | 9 +------ evm/evm_test.go | 11 ++------- evm/loomevm.go | 23 ++++++------------ plugin/fake_context.go | 10 +------- plugin/vm_test.go | 10 ++------ registry/registry.pb.go | 6 ++--- store/evmstore.go | 9 ------- 8 files changed, 24 insertions(+), 108 deletions(-) diff --git a/app.go b/app.go index 2f21b0d8dd..b270fd46cd 100644 --- a/app.go +++ b/app.go @@ -6,10 +6,10 @@ import ( "encoding/binary" "encoding/hex" "fmt" + "sync/atomic" "time" + "unsafe" - gcommon "github.com/ethereum/go-ethereum/common" - gstate "github.com/ethereum/go-ethereum/core/state" "github.com/loomnetwork/go-loom/config" "github.com/loomnetwork/go-loom/util" "github.com/loomnetwork/loomchain/eth/utils" @@ -56,47 +56,6 @@ type State interface { EVMState() *EVMState } -type EVMState struct { - sdb *gstate.StateDB - evmStore *store.EvmStore -} - -func NewEVMState(evmStore *store.EvmStore, sdb *gstate.StateDB) *EVMState { - return &EVMState{ - evmStore: evmStore, - sdb: sdb, - } -} - -func (s *EVMState) Commit() error { - evmStateRoot, err := s.sdb.Commit(true) - if err != nil { - return err - } - s.evmStore.SetVMRootKey(evmStateRoot[:]) - s.sdb.Reset(evmStateRoot) - return nil -} - -func (s *EVMState) GetSnapshot(version int64) (*EVMState, error) { - stateDB, err := gstate.New(gcommon.BytesToHash(s.evmStore.GetRootAt(version)), s.sdb.Database()) - if err != nil { - return nil, err - } - return &EVMState{sdb: stateDB, evmStore: s.evmStore}, nil -} - -func (s *EVMState) Clone() *EVMState { - return &EVMState{ - evmStore: s.evmStore, - sdb: s.sdb.Copy(), - } -} - -func (s *EVMState) StateDB() *gstate.StateDB { - return s.sdb -} - type StoreState struct { ctx context.Context store store.KVStore @@ -403,7 +362,7 @@ type CommittedTx struct { } type Application struct { - lastBlockHeader abci.Header + lastBlockHeader unsafe.Pointer curBlockHeader abci.Header curBlockHash []byte Store store.VersionedKVStore @@ -914,7 +873,7 @@ func (a *Application) Commit() abci.ResponseCommit { // Update the last block header before emitting events in case the subscribers attempt to access // the latest committed state as soon as they receive an event. - a.lastBlockHeader = a.curBlockHeader + atomic.StorePointer(&a.lastBlockHeader, unsafe.Pointer(&a.curBlockHeader)) go func(height int64, blockHeader abci.Header, committedTxs []CommittedTx) { if err := a.EventHandler.EmitBlockTx(uint64(height), blockHeader.Time); err != nil { @@ -967,7 +926,8 @@ func (a *Application) height() int64 { } func (a *Application) ReadOnlyState() State { - evmStateSnapshot, err := a.EVMState.GetSnapshot(a.lastBlockHeader.Height) + lastBlockHeader := (*abci.Header)(atomic.LoadPointer(&a.lastBlockHeader)) + evmStateSnapshot, err := a.EVMState.GetSnapshot(lastBlockHeader.Height) if err != nil { panic(err) } @@ -976,7 +936,7 @@ func (a *Application) ReadOnlyState() State { return NewStoreStateSnapshot( nil, a.Store.GetSnapshot(), - a.lastBlockHeader, + *lastBlockHeader, nil, // TODO: last block hash! a.GetValidatorSet, ).WithEVMState(evmStateSnapshot) diff --git a/cmd/loom/loom.go b/cmd/loom/loom.go index 86de87870d..f098036459 100644 --- a/cmd/loom/loom.go +++ b/cmd/loom/loom.go @@ -19,7 +19,6 @@ import ( "github.com/prometheus/client_golang/prometheus/push" "github.com/tendermint/tendermint/libs/db" - "github.com/ethereum/go-ethereum/core/state" kitprometheus "github.com/go-kit/kit/metrics/prometheus" "github.com/gogo/protobuf/proto" "github.com/loomnetwork/go-loom" @@ -41,7 +40,6 @@ import ( "github.com/loomnetwork/loomchain/receipts/leveldb" "github.com/prometheus/client_golang/prometheus" - gcommon "github.com/ethereum/go-ethereum/common" "github.com/loomnetwork/loomchain/chainconfig" chaincfgcmd "github.com/loomnetwork/loomchain/cmd/loom/chainconfig" "github.com/loomnetwork/loomchain/cmd/loom/common" @@ -843,15 +841,10 @@ func loadApp( return evm.NewLoomVm(state, eventHandler, receiptHandlerProvider.Writer(), createABM, cfg.EVMDebugEnabled), nil }) - ethDB := store.NewLoomEthDB(evmStore, nil) - evmRoot, _ := evmStore.Version() - stateDB := state.NewDatabase(ethDB) - stateDB.SetTrieDB(evmStore.TrieDB()) - sdb, err := state.New(gcommon.BytesToHash(evmRoot), stateDB) + evmState, err = loomchain.NewEVMState(evmStore) if err != nil { return nil, err } - evmState = loomchain.NewEVMState(evmStore, sdb) } store.LogEthDBBatch = cfg.LogEthDbBatch diff --git a/evm/evm_test.go b/evm/evm_test.go index 41af9510b3..667baf4a87 100644 --- a/evm/evm_test.go +++ b/evm/evm_test.go @@ -18,15 +18,11 @@ import ( ethvm "github.com/ethereum/go-ethereum/core/vm" "github.com/loomnetwork/go-loom" "github.com/loomnetwork/loomchain" - "github.com/loomnetwork/loomchain/db" "github.com/loomnetwork/loomchain/features" "github.com/loomnetwork/loomchain/store" lvm "github.com/loomnetwork/loomchain/vm" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" - - gcommon "github.com/ethereum/go-ethereum/common" - gstate "github.com/ethereum/go-ethereum/core/state" ) const ( @@ -48,14 +44,11 @@ func mockState() loomchain.State { } func mockEVMState() *loomchain.EVMState { - ethDB := store.NewLoomEthDB(store.NewMemStore(), nil) - stateDB := gstate.NewDatabase(ethDB) - sdb, err := gstate.New(gcommon.BytesToHash(nil), stateDB) + evmState, err := loomchain.NewEVMState(store.NewEvmStore(memDb, 100, 0)) if err != nil { panic(err) } - memDb, _ := db.LoadMemDB() - return loomchain.NewEVMState(store.NewEvmStore(memDb, 100, 0), sdb) + return evmState } func TestProcessDeployTx(t *testing.T) { diff --git a/evm/loomevm.go b/evm/loomevm.go index c20e5c2038..1e39d33f11 100644 --- a/evm/loomevm.go +++ b/evm/loomevm.go @@ -36,7 +36,6 @@ type StateDB interface { ethvm.StateDB Database() state.Database Logs() []*types.Log - ResetLogs() Commit(bool) (common.Hash, error) } @@ -127,7 +126,7 @@ func (lvm LoomVm) Create(caller loom.Address, code []byte, value *loom.BigUInt) if err != nil { return nil, loom.Address{}, err } - stateDB := lvm.state.EVMState().StateDB() + stateDB := levm.sdb lastLogsIndex := len(stateDB.Logs()) // evm.Create changes Nonce even though tx fails // To prevent any state change from error tx, create a snapshot and revert EVM state if tx fails @@ -141,12 +140,9 @@ func (lvm LoomVm) Create(caller loom.Address, code []byte, value *loom.BigUInt) if lvm.receiptHandler != nil { var events []*ptypes.EventData if err == nil { - allLogs := levm.sdb.Logs() - var addedLogs []*types.Log - for index, log := range allLogs { - if index >= lastLogsIndex { - addedLogs = append(addedLogs, log) - } + addedLogs := stateDB.Logs() + if len(addedLogs) > 0 { + addedLogs = addedLogs[lastLogsIndex:] } events = lvm.receiptHandler.GetEventsFromLogs( addedLogs, lvm.state.Block().Height, caller, addr, code, @@ -198,7 +194,7 @@ func (lvm LoomVm) Call(caller, addr loom.Address, input []byte, value *loom.BigU if err != nil { return nil, err } - stateDB := lvm.state.EVMState().StateDB() + stateDB := levm.sdb lastLogsIndex := len(stateDB.Logs()) // To prevent any state change from error tx, create a snapshot and revert EVM state if tx fails snapshot := stateDB.Snapshot() @@ -211,12 +207,9 @@ func (lvm LoomVm) Call(caller, addr loom.Address, input []byte, value *loom.BigU if lvm.receiptHandler != nil { var events []*ptypes.EventData if err == nil { - allLogs := stateDB.Logs() - var addedLogs []*types.Log - for index, log := range allLogs { - if index >= lastLogsIndex { - addedLogs = append(addedLogs, log) - } + addedLogs := stateDB.Logs() + if len(addedLogs) > 0 { + addedLogs = addedLogs[lastLogsIndex:] } events = lvm.receiptHandler.GetEventsFromLogs( addedLogs, lvm.state.Block().Height, caller, addr, input, diff --git a/plugin/fake_context.go b/plugin/fake_context.go index 7ceefdf466..2db97990ee 100644 --- a/plugin/fake_context.go +++ b/plugin/fake_context.go @@ -6,8 +6,6 @@ import ( "context" "time" - gcommon "github.com/ethereum/go-ethereum/common" - gstate "github.com/ethereum/go-ethereum/core/state" loom "github.com/loomnetwork/go-loom" "github.com/loomnetwork/go-loom/plugin" "github.com/loomnetwork/go-loom/types" @@ -44,16 +42,10 @@ func CreateFakeContextWithEVM(caller, address loom.Address) *FakeContextWithEVM panic(err) } evmStore := store.NewEvmStore(evmDB, 100, 0) - ethDB := store.NewLoomEthDB(evmStore, nil) - evmRoot, _ := evmStore.Version() - stateDB := gstate.NewDatabase(ethDB) - stateDB.SetTrieDB(evmStore.TrieDB()) - sdb, err := gstate.New(gcommon.BytesToHash(evmRoot), stateDB) + evmState, err := loomchain.NewEVMState(evmStore) if err != nil { panic(err) } - evmState := loomchain.NewEVMState(evmStore, sdb) - return &FakeContextWithEVM{ FakeContext: ctx, State: state.WithEVMState(evmState), diff --git a/plugin/vm_test.go b/plugin/vm_test.go index 214af51159..aa9c072a50 100644 --- a/plugin/vm_test.go +++ b/plugin/vm_test.go @@ -12,8 +12,6 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" - gcommon "github.com/ethereum/go-ethereum/common" - gstate "github.com/ethereum/go-ethereum/core/state" proto "github.com/gogo/protobuf/proto" "github.com/loomnetwork/go-loom" loom_plugin "github.com/loomnetwork/go-loom/plugin" @@ -21,7 +19,6 @@ import ( ptypes "github.com/loomnetwork/go-loom/plugin/types" "github.com/loomnetwork/go-loom/testdata" "github.com/loomnetwork/loomchain" - "github.com/loomnetwork/loomchain/db" "github.com/loomnetwork/loomchain/eth/subs" "github.com/loomnetwork/loomchain/events" levm "github.com/loomnetwork/loomchain/evm" @@ -126,14 +123,11 @@ func (c *VMTestContract) CheckQueryCaller(ctx contract.StaticContext, args *test } func mockEVMState() *loomchain.EVMState { - ethDB := store.NewLoomEthDB(store.NewMemStore(), nil) - stateDB := gstate.NewDatabase(ethDB) - sdb, err := gstate.New(gcommon.BytesToHash(nil), stateDB) + evmState, err := loomchain.NewEVMState(store.NewEvmStore(memDb, 100, 0)) if err != nil { panic(err) } - memDb, _ := db.LoadMemDB() - return loomchain.NewEVMState(store.NewEvmStore(memDb, 100, 0), sdb) + return evmState } func TestPluginVMContractContextCaller(t *testing.T) { diff --git a/registry/registry.pb.go b/registry/registry.pb.go index c242c69144..827364dffb 100644 --- a/registry/registry.pb.go +++ b/registry/registry.pb.go @@ -32,7 +32,7 @@ func (m *Record) Reset() { *m = Record{} } func (m *Record) String() string { return proto.CompactTextString(m) } func (*Record) ProtoMessage() {} func (*Record) Descriptor() ([]byte, []int) { - return fileDescriptor_registry_8f33b09e6c22f9ce, []int{0} + return fileDescriptor_registry_db594c69f58cdc2f, []int{0} } func (m *Record) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_Record.Unmarshal(m, b) @@ -78,10 +78,10 @@ func init() { } func init() { - proto.RegisterFile("github.com/loomnetwork/loomchain/registry/registry.proto", fileDescriptor_registry_8f33b09e6c22f9ce) + proto.RegisterFile("github.com/loomnetwork/loomchain/registry/registry.proto", fileDescriptor_registry_db594c69f58cdc2f) } -var fileDescriptor_registry_8f33b09e6c22f9ce = []byte{ +var fileDescriptor_registry_db594c69f58cdc2f = []byte{ // 163 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xb2, 0x48, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0xcf, 0xc9, 0xcf, 0xcf, 0xcd, 0x4b, 0x2d, 0x29, 0xcf, diff --git a/store/evmstore.go b/store/evmstore.go index f287e9a642..eae8d3c2bd 100644 --- a/store/evmstore.go +++ b/store/evmstore.go @@ -9,7 +9,6 @@ import ( "github.com/ethereum/go-ethereum/trie" gcommon "github.com/ethereum/go-ethereum/common" - gstate "github.com/ethereum/go-ethereum/core/state" "github.com/go-kit/kit/metrics" kitprometheus "github.com/go-kit/kit/metrics/prometheus" lru "github.com/hashicorp/golang-lru" @@ -358,14 +357,6 @@ func (s *EvmStoreSnapshot) Has(key []byte) bool { return s.Snapshot.Has(key) } -func (s *EvmStoreSnapshot) StateDB(db gstate.Database) *gstate.StateDB { - stateDB, err := gstate.New(gcommon.BytesToHash(s.rootHash), db) - if err != nil { - panic(err) - } - return stateDB -} - func remove(keys []string, key string) []string { for i, value := range keys { if value == key { From 7c6443238dff2a16e02b265fc54dfb35ddc37bb7 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Thu, 28 Nov 2019 13:56:29 +0700 Subject: [PATCH 48/80] commit missing file --- evm_state.go | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 evm_state.go diff --git a/evm_state.go b/evm_state.go new file mode 100644 index 0000000000..a569790f1e --- /dev/null +++ b/evm_state.go @@ -0,0 +1,57 @@ +package loomchain + +import ( + gcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + gstate "github.com/ethereum/go-ethereum/core/state" + "github.com/loomnetwork/loomchain/store" +) + +type EVMState struct { + sdb *gstate.StateDB + evmStore *store.EvmStore +} + +func NewEVMState(evmStore *store.EvmStore) (*EVMState, error) { + ethDB := store.NewLoomEthDB(evmStore, nil) + stateDB := state.NewDatabase(ethDB).WithTrieDB(evmStore.TrieDB()) + evmRoot, _ := evmStore.Version() + sdb, err := state.New(gcommon.BytesToHash(evmRoot), stateDB) + if err != nil { + return nil, err + } + return &EVMState{ + evmStore: evmStore, + sdb: sdb, + }, nil +} + +func (s *EVMState) Commit() error { + evmStateRoot, err := s.sdb.Commit(true) + if err != nil { + return err + } + s.evmStore.SetVMRootKey(evmStateRoot[:]) + // Clear out old state data such as logs and cache to free up memory + s.sdb.Reset(evmStateRoot) + return nil +} + +func (s *EVMState) GetSnapshot(version int64) (*EVMState, error) { + stateDB, err := gstate.New(gcommon.BytesToHash(s.evmStore.GetRootAt(version)), s.sdb.Database()) + if err != nil { + return nil, err + } + return &EVMState{sdb: stateDB, evmStore: s.evmStore}, nil +} + +func (s *EVMState) Clone() *EVMState { + return &EVMState{ + evmStore: s.evmStore, + sdb: s.sdb.Copy(), + } +} + +func (s *EVMState) StateDB() *gstate.StateDB { + return s.sdb +} From cc2b16c600bce04f79287b017f5724a98bd13fa5 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Thu, 28 Nov 2019 14:11:53 +0700 Subject: [PATCH 49/80] fix linter error --- evm/evm_test.go | 2 ++ plugin/vm_test.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/evm/evm_test.go b/evm/evm_test.go index 667baf4a87..97a2875efb 100644 --- a/evm/evm_test.go +++ b/evm/evm_test.go @@ -18,6 +18,7 @@ import ( ethvm "github.com/ethereum/go-ethereum/core/vm" "github.com/loomnetwork/go-loom" "github.com/loomnetwork/loomchain" + "github.com/loomnetwork/loomchain/db" "github.com/loomnetwork/loomchain/features" "github.com/loomnetwork/loomchain/store" lvm "github.com/loomnetwork/loomchain/vm" @@ -44,6 +45,7 @@ func mockState() loomchain.State { } func mockEVMState() *loomchain.EVMState { + memDb, _ := db.LoadMemDB() evmState, err := loomchain.NewEVMState(store.NewEvmStore(memDb, 100, 0)) if err != nil { panic(err) diff --git a/plugin/vm_test.go b/plugin/vm_test.go index aa9c072a50..151117ae61 100644 --- a/plugin/vm_test.go +++ b/plugin/vm_test.go @@ -19,6 +19,7 @@ import ( ptypes "github.com/loomnetwork/go-loom/plugin/types" "github.com/loomnetwork/go-loom/testdata" "github.com/loomnetwork/loomchain" + "github.com/loomnetwork/loomchain/db" "github.com/loomnetwork/loomchain/eth/subs" "github.com/loomnetwork/loomchain/events" levm "github.com/loomnetwork/loomchain/evm" @@ -123,6 +124,7 @@ func (c *VMTestContract) CheckQueryCaller(ctx contract.StaticContext, args *test } func mockEVMState() *loomchain.EVMState { + memDb, _ := db.LoadMemDB() evmState, err := loomchain.NewEVMState(store.NewEvmStore(memDb, 100, 0)) if err != nil { panic(err) From 388b818e559b078e81bf0961584508b4d10f4a31 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Mon, 2 Dec 2019 13:00:07 +0700 Subject: [PATCH 50/80] address PR comment --- app.go | 3 ++- registry/registry.pb.go | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app.go b/app.go index b270fd46cd..dc91c058f9 100644 --- a/app.go +++ b/app.go @@ -873,7 +873,8 @@ func (a *Application) Commit() abci.ResponseCommit { // Update the last block header before emitting events in case the subscribers attempt to access // the latest committed state as soon as they receive an event. - atomic.StorePointer(&a.lastBlockHeader, unsafe.Pointer(&a.curBlockHeader)) + curBlockHeader := a.curBlockHeader + atomic.StorePointer(&a.lastBlockHeader, unsafe.Pointer(&curBlockHeader)) go func(height int64, blockHeader abci.Header, committedTxs []CommittedTx) { if err := a.EventHandler.EmitBlockTx(uint64(height), blockHeader.Time); err != nil { diff --git a/registry/registry.pb.go b/registry/registry.pb.go index 827364dffb..c242c69144 100644 --- a/registry/registry.pb.go +++ b/registry/registry.pb.go @@ -32,7 +32,7 @@ func (m *Record) Reset() { *m = Record{} } func (m *Record) String() string { return proto.CompactTextString(m) } func (*Record) ProtoMessage() {} func (*Record) Descriptor() ([]byte, []int) { - return fileDescriptor_registry_db594c69f58cdc2f, []int{0} + return fileDescriptor_registry_8f33b09e6c22f9ce, []int{0} } func (m *Record) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_Record.Unmarshal(m, b) @@ -78,10 +78,10 @@ func init() { } func init() { - proto.RegisterFile("github.com/loomnetwork/loomchain/registry/registry.proto", fileDescriptor_registry_db594c69f58cdc2f) + proto.RegisterFile("github.com/loomnetwork/loomchain/registry/registry.proto", fileDescriptor_registry_8f33b09e6c22f9ce) } -var fileDescriptor_registry_db594c69f58cdc2f = []byte{ +var fileDescriptor_registry_8f33b09e6c22f9ce = []byte{ // 163 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xb2, 0x48, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0xcf, 0xc9, 0xcf, 0xcf, 0xcd, 0x4b, 0x2d, 0x29, 0xcf, From e022db912568dd6be6607b12fd719d098a55bb0e Mon Sep 17 00:00:00 2001 From: Vadim Macagon Date: Mon, 2 Dec 2019 13:15:44 +0700 Subject: [PATCH 51/80] Simplify --- evm_state.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/evm_state.go b/evm_state.go index a569790f1e..999fc91a98 100644 --- a/evm_state.go +++ b/evm_state.go @@ -1,22 +1,20 @@ package loomchain import ( - gcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" - gstate "github.com/ethereum/go-ethereum/core/state" "github.com/loomnetwork/loomchain/store" ) +// EVMState contains the mutable EVM state. type EVMState struct { - sdb *gstate.StateDB + sdb *state.StateDB evmStore *store.EvmStore } func NewEVMState(evmStore *store.EvmStore) (*EVMState, error) { - ethDB := store.NewLoomEthDB(evmStore, nil) - stateDB := state.NewDatabase(ethDB).WithTrieDB(evmStore.TrieDB()) evmRoot, _ := evmStore.Version() - sdb, err := state.New(gcommon.BytesToHash(evmRoot), stateDB) + sdb, err := state.New(common.BytesToHash(evmRoot), state.NewDatabaseWithTrieDB(evmStore.TrieDB())) if err != nil { return nil, err } @@ -26,6 +24,7 @@ func NewEVMState(evmStore *store.EvmStore) (*EVMState, error) { }, nil } +// Commit writes the state changes that occurred since the previous commit to the underlying store. func (s *EVMState) Commit() error { evmStateRoot, err := s.sdb.Commit(true) if err != nil { @@ -37,8 +36,10 @@ func (s *EVMState) Commit() error { return nil } +// GetSnapshot returns the EVMState instance containing the state as it was at the given version. +// NOTE: Do not call Commit on the returned instance. func (s *EVMState) GetSnapshot(version int64) (*EVMState, error) { - stateDB, err := gstate.New(gcommon.BytesToHash(s.evmStore.GetRootAt(version)), s.sdb.Database()) + stateDB, err := state.New(common.BytesToHash(s.evmStore.GetRootAt(version)), s.sdb.Database()) if err != nil { return nil, err } @@ -52,6 +53,6 @@ func (s *EVMState) Clone() *EVMState { } } -func (s *EVMState) StateDB() *gstate.StateDB { +func (s *EVMState) StateDB() *state.StateDB { return s.sdb } From e2c471ad9ba5387d21f0b5a2f37dab586f422e9b Mon Sep 17 00:00:00 2001 From: Vadim Macagon Date: Mon, 2 Dec 2019 14:58:29 +0700 Subject: [PATCH 52/80] Tweaks --- app.go | 3 +-- store/evmstore.go | 15 +-------------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/app.go b/app.go index dc91c058f9..38096d2848 100644 --- a/app.go +++ b/app.go @@ -362,7 +362,7 @@ type CommittedTx struct { } type Application struct { - lastBlockHeader unsafe.Pointer + lastBlockHeader unsafe.Pointer // *abci.Header curBlockHeader abci.Header curBlockHash []byte Store store.VersionedKVStore @@ -840,7 +840,6 @@ func (a *Application) deliverTx2(storeTx store.KVStoreTx, txBytes []byte) abci.R // Commit commits the current block func (a *Application) Commit() abci.ResponseCommit { - var err error defer func(begin time.Time) { lvs := []string{"method", "Commit", "error", fmt.Sprint(err != nil)} diff --git a/store/evmstore.go b/store/evmstore.go index eae8d3c2bd..e1f2029197 100644 --- a/store/evmstore.go +++ b/store/evmstore.go @@ -279,11 +279,6 @@ func (s *EvmStore) TrieDB() *trie.Database { return s.trieDB } -// GetVMRootKey returns the lastest root of EVM state trie -func (s *EvmStore) GetVMRootKey() []byte { - return s.rootHash -} - func (s *EvmStore) SetVMRootKey(root []byte) { s.rootHash = root } @@ -318,15 +313,7 @@ func (s *EvmStore) GetRootAt(version int64) []byte { } func (s *EvmStore) GetSnapshot(version int64) *EvmStoreSnapshot { - var targetRoot []byte - // Expect cache to be almost 100% hit since cache miss yields extremely poor performance - val, exist := s.rootCache.Get(version) - if exist { - targetRoot = val.([]byte) - } else { - targetRoot, _ = s.getLastSavedRoot(version) - } - return NewEvmStoreSnapshot(s.evmDB.GetSnapshot(), targetRoot) + return NewEvmStoreSnapshot(s.evmDB.GetSnapshot(), s.GetRootAt(version)) } func NewEvmStoreSnapshot(snapshot db.Snapshot, rootHash []byte) *EvmStoreSnapshot { From b4a243e2498795c72305239a66b677d637d6bc3f Mon Sep 17 00:00:00 2001 From: Pathorn Date: Mon, 2 Dec 2019 18:42:48 +0700 Subject: [PATCH 53/80] make evmstore thread-safe by removing its cache --- store/evmstore.go | 62 ++--------------------------------------------- 1 file changed, 2 insertions(+), 60 deletions(-) diff --git a/store/evmstore.go b/store/evmstore.go index e1f2029197..8cddd03df6 100644 --- a/store/evmstore.go +++ b/store/evmstore.go @@ -56,7 +56,6 @@ func getVersionFromEvmRootKey(key []byte) (int64, error) { // EvmStore persists EVM state to a DB. type EvmStore struct { evmDB db.DBWrapper - cache map[string]cacheItem rootHash []byte lastSavedRoot []byte rootCache *lru.Cache @@ -73,7 +72,6 @@ func NewEvmStore(evmDB db.DBWrapper, numCachedRoots int, flushInterval int64) *E } evmStore := &EvmStore{ evmDB: evmDB, - cache: make(map[string]cacheItem), rootCache: rootCache, flushInterval: flushInterval, } @@ -82,13 +80,6 @@ func NewEvmStore(evmDB db.DBWrapper, numCachedRoots int, flushInterval int64) *E return evmStore } -func (s *EvmStore) setCache(key, val []byte, deleted bool) { - s.cache[string(key)] = cacheItem{ - Value: val, - Deleted: deleted, - } -} - // Range iterates in-order over the keys in the store prefixed by the given prefix. // TODO (VM): This needs a proper review, other than tests there is no code that really makes use of // this function, only place it's called is from MultiWriterAppStore.Range but only when @@ -110,21 +101,6 @@ func (s *EvmStore) Range(prefix []byte) plugin.RangeData { } } - // Update range cache with data in cache - for key, c := range s.cache { - if util.HasPrefix([]byte(key), prefix) || len(prefix) == 0 { - if c.Deleted { - rangeCacheKeys = remove(rangeCacheKeys, key) - rangeCache[key] = nil - continue - } - if _, ok := rangeCache[key]; !ok { - rangeCacheKeys = append(rangeCacheKeys, string(key)) - } - rangeCache[key] = c.Value - } - } - // Make Range return root hash (vmvmroot) from EvmStore.rootHash if _, exist := rangeCache[string(rootHashKey)]; exist { rangeCache[string(rootHashKey)] = s.rootHash @@ -154,41 +130,19 @@ func (s *EvmStore) Range(prefix []byte) plugin.RangeData { } func (s *EvmStore) Has(key []byte) bool { - // EvmStore always has Patricia root - if bytes.Equal(key, rootHashKey) { - return true - } - if item, ok := s.cache[string(key)]; ok { - return !item.Deleted - } return s.evmDB.Has(key) } func (s *EvmStore) Get(key []byte) []byte { - if bytes.Equal(key, rootHashKey) { - return s.rootHash - } - - if item, ok := s.cache[string(key)]; ok { - return item.Value - } return s.evmDB.Get(key) } func (s *EvmStore) Delete(key []byte) { - if bytes.Equal(key, rootHashKey) { - s.rootHash = nil - } else { - s.setCache(key, nil, true) - } + s.evmDB.Delete(key) } func (s *EvmStore) Set(key, val []byte) { - if bytes.Equal(key, rootHashKey) { - s.rootHash = val - } else { - s.setCache(key, val, false) - } + s.evmDB.Set(key, val) } func (s *EvmStore) Commit(version int64) []byte { @@ -236,23 +190,11 @@ func (s *EvmStore) Commit(version int64) []byte { } s.rootCache.Add(version, currentRoot) - - batch := s.evmDB.NewBatch() - for key, item := range s.cache { - if !item.Deleted { - batch.Set([]byte(key), item.Value) - } else { - batch.Delete([]byte(key)) - } - } - batch.Write() - s.cache = make(map[string]cacheItem) s.version = version return currentRoot } func (s *EvmStore) LoadVersion(targetVersion int64) error { - s.cache = make(map[string]cacheItem) // find the last saved root root, version := s.getLastSavedRoot(targetVersion) if bytes.Equal(root, defaultRoot) { From 5c51fc6ed10ec8c3d8f5975b2ddf45a6a012454a Mon Sep 17 00:00:00 2001 From: Pathorn Date: Mon, 2 Dec 2019 19:58:08 +0700 Subject: [PATCH 54/80] fix unit test --- store/evmstore.go | 2 + store/multi_writer_app_store_test.go | 231 ++++++++++----------------- 2 files changed, 88 insertions(+), 145 deletions(-) diff --git a/store/evmstore.go b/store/evmstore.go index 8cddd03df6..13537c18bf 100644 --- a/store/evmstore.go +++ b/store/evmstore.go @@ -254,10 +254,12 @@ func (s *EvmStore) GetRootAt(version int64) []byte { return targetRoot } +// TODO: Get rid of this function. EvmStore does not provide snapshot anymore but EVMState does. func (s *EvmStore) GetSnapshot(version int64) *EvmStoreSnapshot { return NewEvmStoreSnapshot(s.evmDB.GetSnapshot(), s.GetRootAt(version)) } +// TODO: Get rid of EvmStoreSnapshot. EvmStore does not provide snapshot anymore but EVMState does. func NewEvmStoreSnapshot(snapshot db.Snapshot, rootHash []byte) *EvmStoreSnapshot { return &EvmStoreSnapshot{ Snapshot: snapshot, diff --git a/store/multi_writer_app_store_test.go b/store/multi_writer_app_store_test.go index ece6f79f34..587e80e3e0 100644 --- a/store/multi_writer_app_store_test.go +++ b/store/multi_writer_app_store_test.go @@ -4,14 +4,16 @@ import ( "bytes" "testing" - "github.com/gogo/protobuf/proto" - "github.com/loomnetwork/go-loom/config" "github.com/loomnetwork/go-loom/util" "github.com/loomnetwork/loomchain/db" "github.com/loomnetwork/loomchain/log" "github.com/stretchr/testify/suite" ) +var ( + nonePrefix = []byte("none") +) + type MultiWriterAppStoreTestSuite struct { suite.Suite } @@ -31,24 +33,24 @@ func (m *MultiWriterAppStoreTestSuite) TestEnableDisableMultiWriterAppStore() { // vm keys should be written to both the IAVL & EVM store store.Set(evmDBFeatureKey, []byte{}) - store.Set(vmPrefixKey("abcd"), []byte("hello")) - store.Set(vmPrefixKey("abcde"), []byte("world")) - store.Set(vmPrefixKey("evmStore"), []byte("yes")) - store.Set(vmPrefixKey("aaaa"), []byte("yes")) + store.Set(nonePrefixKey("abcd"), []byte("hello")) + store.Set(nonePrefixKey("abcde"), []byte("world")) + store.Set(nonePrefixKey("evmStore"), []byte("yes")) + store.Set(nonePrefixKey("aaaa"), []byte("yes")) store.Set([]byte("abcd"), []byte("NewData")) - rangeData := store.Range(vmPrefix) + rangeData := store.Range(nonePrefix) require.Equal(4, len(rangeData)) require.True(store.Has([]byte("abcd"))) // vm keys should now only be written to the EVM store store.Set(evmDBFeatureKey, []byte{1}) - store.Set(vmPrefixKey("gg"), []byte("world")) - store.Set(vmPrefixKey("dd"), []byte("yes")) - store.Set(vmPrefixKey("vv"), []byte("yes")) + store.Set(nonePrefixKey("gg"), []byte("world")) + store.Set(nonePrefixKey("dd"), []byte("yes")) + store.Set(nonePrefixKey("vv"), []byte("yes")) store.Set([]byte("dcba"), []byte("MoreData")) - rangeData = store.Range(vmPrefix) + rangeData = store.Range(nonePrefix) require.Equal(7, len(rangeData)) require.True(store.Has([]byte("abcd"))) require.True(store.Has([]byte("dcba"))) @@ -61,34 +63,34 @@ func (m *MultiWriterAppStoreTestSuite) TestMultiWriterAppStoreDelete() { // vm keys should be written to both the IAVL & EVM store store.Set(evmDBFeatureKey, []byte{}) - store.Set(vmPrefixKey("abcd"), []byte("hello")) - store.Set(vmPrefixKey("abcde"), []byte("world")) - store.Set(vmPrefixKey("evmStore"), []byte("yes")) - store.Set(vmPrefixKey("aaaa"), []byte("yes")) + store.Set(nonePrefixKey("abcd"), []byte("hello")) + store.Set(nonePrefixKey("abcde"), []byte("world")) + store.Set(nonePrefixKey("evmStore"), []byte("yes")) + store.Set(nonePrefixKey("aaaa"), []byte("yes")) store.Set([]byte("vmroot"), []byte("SSSSSSSSSSSSS")) store.Set([]byte("abcd"), []byte("NewData")) - store.Delete(vmPrefixKey("abcd")) - require.False(store.Has(vmPrefixKey("abcd"))) + store.Delete(nonePrefixKey("abcd")) + require.False(store.Has(nonePrefixKey("abcd"))) - rangeData := store.Range(vmPrefix) + rangeData := store.Range(nonePrefix) require.Equal(3, len(rangeData)) require.True(store.Has([]byte("vmroot"))) require.True(store.Has([]byte("abcd"))) // vm keys should be written to the EVM store store.Set(evmDBFeatureKey, []byte{1}) - rangeData = store.Range(vmPrefix) + rangeData = store.Range(nonePrefix) require.Equal(3, len(rangeData)) require.Equal([]byte("SSSSSSSSSSSSS"), store.Get([]byte("vmroot"))) - store.Set(vmPrefixKey("gg"), []byte("world")) - store.Set(vmPrefixKey("dd"), []byte("yes")) - store.Set(vmPrefixKey("vv"), []byte("yes")) - store.Delete(vmPrefixKey("vv")) - require.False(store.Has(vmPrefixKey("vv"))) + store.Set(nonePrefixKey("gg"), []byte("world")) + store.Set(nonePrefixKey("dd"), []byte("yes")) + store.Set(nonePrefixKey("vv"), []byte("yes")) + store.Delete(nonePrefixKey("vv")) + require.False(store.Has(nonePrefixKey("vv"))) - rangeData = store.Range(vmPrefix) + rangeData = store.Range(nonePrefix) require.Equal(5, len(rangeData)) require.True(store.Has([]byte("abcd"))) } @@ -99,31 +101,31 @@ func (m *MultiWriterAppStoreTestSuite) TestMultiWriterAppStoreSnapShot() { require.NoError(err) store.Set(evmDBFeatureKey, []byte{1}) - store.Set(vmPrefixKey("abcd"), []byte("hello")) - store.Set(vmPrefixKey("abcde"), []byte("world")) - store.Set(vmPrefixKey("evmStore"), []byte("yes")) - store.Set(vmPrefixKey("aaaa"), []byte("yes")) + store.Set(nonePrefixKey("abcd"), []byte("hello")) + store.Set(nonePrefixKey("abcde"), []byte("world")) + store.Set(nonePrefixKey("evmStore"), []byte("yes")) + store.Set(nonePrefixKey("aaaa"), []byte("yes")) store.Set([]byte("ssssvvv"), []byte("SSSSSSSSSSSSS")) store.Set([]byte("abcd"), []byte("NewData")) _, _, err = store.SaveVersion() require.NoError(err) - store.Set(vmPrefixKey("abcd"), []byte("hellooooooo")) - store.Set(vmPrefixKey("abcde"), []byte("vvvvvvvvv")) + store.Set(nonePrefixKey("abcd"), []byte("hellooooooo")) + store.Set(nonePrefixKey("abcde"), []byte("vvvvvvvvv")) store.Set([]byte("abcd"), []byte("asdfasdf")) snapshot := store.GetSnapshot() - require.Equal([]byte("hello"), snapshot.Get(vmPrefixKey("abcd"))) + require.Equal([]byte("hello"), snapshot.Get(nonePrefixKey("abcd"))) require.Equal([]byte("NewData"), snapshot.Get([]byte("abcd"))) - require.Equal([]byte("world"), snapshot.Get(vmPrefixKey("abcde"))) + require.Equal([]byte("world"), snapshot.Get(nonePrefixKey("abcde"))) _, _, err = store.SaveVersion() require.NoError(err) snapshot = store.GetSnapshot() require.Equal([]byte("asdfasdf"), snapshot.Get([]byte("abcd"))) - require.Equal([]byte("hellooooooo"), snapshot.Get(vmPrefixKey("abcd"))) - require.Equal([]byte("vvvvvvvvv"), snapshot.Get(vmPrefixKey("abcde"))) + require.Equal([]byte("hellooooooo"), snapshot.Get(nonePrefixKey("abcd"))) + require.Equal([]byte("vvvvvvvvv"), snapshot.Get(nonePrefixKey("abcde"))) } func (m *MultiWriterAppStoreTestSuite) TestMultiWriterAppStoreSnapShotFlushInterval() { @@ -167,52 +169,52 @@ func (m *MultiWriterAppStoreTestSuite) TestMultiWriterAppStoreSnapShotRange() { require.NoError(err) store.Set(evmDBFeatureKey, []byte{1}) - store.Set(vmPrefixKey("abcd"), []byte("hello")) - store.Set(vmPrefixKey("abcde"), []byte("world")) - store.Set(vmPrefixKey("evmStore"), []byte("yes")) - store.Set(vmPrefixKey("aaaa"), []byte("yes")) + store.Set(nonePrefixKey("abcd"), []byte("hello")) + store.Set(nonePrefixKey("abcde"), []byte("world")) + store.Set(nonePrefixKey("evmStore"), []byte("yes")) + store.Set(nonePrefixKey("aaaa"), []byte("yes")) store.Set([]byte("ssssvvv"), []byte("SSSSSSSSSSSSS")) store.Set([]byte("abcd"), []byte("NewData")) store.Set([]byte("uuuu"), []byte("SSSSSSSSSSSSS")) store.Set([]byte("sssss"), []byte("NewData")) snapshot := store.GetSnapshot() - rangeData := snapshot.Range(vmPrefix) + rangeData := snapshot.Range(nonePrefix) require.Equal(0, len(rangeData)) _, _, err = store.SaveVersion() require.NoError(err) snapshot = store.GetSnapshot() - rangeData = snapshot.Range(vmPrefix) - require.Equal(4+1, len(rangeData)) // +1 for evm root stored by EVM store - require.Equal(0, bytes.Compare(snapshot.Get(vmPrefixKey("abcd")), []byte("hello"))) - require.Equal(0, bytes.Compare(snapshot.Get(vmPrefixKey("abcde")), []byte("world"))) - require.Equal(0, bytes.Compare(snapshot.Get(vmPrefixKey("evmStore")), []byte("yes"))) - require.Equal(0, bytes.Compare(snapshot.Get(vmPrefixKey("aaaa")), []byte("yes"))) + rangeData = snapshot.Range(nonePrefix) + require.Equal(4, len(rangeData)) + require.Equal(0, bytes.Compare(snapshot.Get(nonePrefixKey("abcd")), []byte("hello"))) + require.Equal(0, bytes.Compare(snapshot.Get(nonePrefixKey("abcde")), []byte("world"))) + require.Equal(0, bytes.Compare(snapshot.Get(nonePrefixKey("evmStore")), []byte("yes"))) + require.Equal(0, bytes.Compare(snapshot.Get(nonePrefixKey("aaaa")), []byte("yes"))) // Modifications shouldn't be visible in the snapshot until the next SaveVersion() - store.Delete(vmPrefixKey("abcd")) + store.Delete(nonePrefixKey("abcd")) store.Delete([]byte("ssssvvv")) snapshot = store.GetSnapshot() - rangeData = snapshot.Range(vmPrefix) - require.Equal(4+1, len(rangeData)) // +1 for evm root stored by EVM store - require.Equal(0, bytes.Compare(snapshot.Get(vmPrefixKey("abcd")), []byte("hello"))) - require.Equal(0, bytes.Compare(snapshot.Get(vmPrefixKey("abcde")), []byte("world"))) - require.Equal(0, bytes.Compare(snapshot.Get(vmPrefixKey("evmStore")), []byte("yes"))) - require.Equal(0, bytes.Compare(snapshot.Get(vmPrefixKey("aaaa")), []byte("yes"))) + rangeData = snapshot.Range(nonePrefix) + require.Equal(4, len(rangeData)) + require.Equal(0, bytes.Compare(snapshot.Get(nonePrefixKey("abcd")), []byte("hello"))) + require.Equal(0, bytes.Compare(snapshot.Get(nonePrefixKey("abcde")), []byte("world"))) + require.Equal(0, bytes.Compare(snapshot.Get(nonePrefixKey("evmStore")), []byte("yes"))) + require.Equal(0, bytes.Compare(snapshot.Get(nonePrefixKey("aaaa")), []byte("yes"))) _, _, err = store.SaveVersion() require.NoError(err) snapshot = store.GetSnapshot() - rangeData = snapshot.Range(vmPrefix) - require.Equal(3+1, len(rangeData)) // +1 for evm root stored by EVM store - require.Equal(0, len(snapshot.Get(vmPrefixKey("abcd")))) // has been deleted - require.Equal(0, len(snapshot.Get([]byte("ssssvvv")))) // has been deleted - require.Equal(0, bytes.Compare(snapshot.Get(vmPrefixKey("abcde")), []byte("world"))) - require.Equal(0, bytes.Compare(snapshot.Get(vmPrefixKey("evmStore")), []byte("yes"))) - require.Equal(0, bytes.Compare(snapshot.Get(vmPrefixKey("aaaa")), []byte("yes"))) + rangeData = snapshot.Range(nonePrefix) + require.Equal(3, len(rangeData)) + require.Equal(0, len(snapshot.Get(nonePrefixKey("abcd")))) // has been deleted + require.Equal(0, len(snapshot.Get([]byte("ssssvvv")))) // has been deleted + require.Equal(0, bytes.Compare(snapshot.Get(nonePrefixKey("abcde")), []byte("world"))) + require.Equal(0, bytes.Compare(snapshot.Get(nonePrefixKey("evmStore")), []byte("yes"))) + require.Equal(0, bytes.Compare(snapshot.Get(nonePrefixKey("aaaa")), []byte("yes"))) } func (m *MultiWriterAppStoreTestSuite) TestMultiWriterAppStoreSaveVersion() { @@ -222,95 +224,34 @@ func (m *MultiWriterAppStoreTestSuite) TestMultiWriterAppStoreSaveVersion() { // vm keys should be written to the EVM store store.Set(evmDBFeatureKey, []byte{1}) - store.Set(vmPrefixKey("abcd"), []byte("hello")) - store.Set(vmPrefixKey("abcde"), []byte("world")) - store.Set(vmPrefixKey("evmStore"), []byte("yes")) - store.Set(vmPrefixKey("aaaa"), []byte("yes")) + store.Set(nonePrefixKey("abcd"), []byte("hello")) + store.Set(nonePrefixKey("abcde"), []byte("world")) + store.Set(nonePrefixKey("evmStore"), []byte("yes")) + store.Set(nonePrefixKey("aaaa"), []byte("yes")) store.Set([]byte("abcd"), []byte("NewData")) store.Set([]byte("evmStore"), []byte("iavlStore")) - store.Set(vmPrefixKey("gg"), []byte("world")) - store.Set(vmPrefixKey("dd"), []byte("yes")) - store.Set(vmPrefixKey("vv"), []byte("yes")) + store.Set(nonePrefixKey("gg"), []byte("world")) + store.Set(nonePrefixKey("dd"), []byte("yes")) + store.Set(nonePrefixKey("vv"), []byte("yes")) _, version, err := store.SaveVersion() require.Equal(int64(1), version) require.NoError(err) - require.Equal([]byte("hello"), store.Get(vmPrefixKey("abcd"))) + require.Equal([]byte("hello"), store.Get(nonePrefixKey("abcd"))) require.Equal([]byte("NewData"), store.Get([]byte("abcd"))) - require.True(store.Has(vmPrefixKey("gg"))) - store.Delete(vmPrefixKey("gg")) + require.True(store.Has(nonePrefixKey("gg"))) + store.Delete(nonePrefixKey("gg")) - dataRange := store.Range(vmPrefix) - require.Equal(6+1, len(dataRange)) + dataRange := store.Range(nonePrefix) + require.Equal(6, len(dataRange)) _, version, err = store.SaveVersion() require.Equal(int64(2), version) require.NoError(err) - require.Equal([]byte("hello"), store.Get(vmPrefixKey("abcd"))) + require.Equal([]byte("hello"), store.Get(nonePrefixKey("abcd"))) require.Equal([]byte("NewData"), store.Get([]byte("abcd"))) - require.False(store.Has(vmPrefixKey("gg"))) -} - -func (m *MultiWriterAppStoreTestSuite) TestPruningEvmKeys() { - require := m.Require() - store, err := mockMultiWriterStore(10) - require.NoError(err) - - // write some vm keys to iavl store - iavlStore := store.appStore - iavlStore.Set(vmPrefixKey("abcde"), []byte("world")) - iavlStore.Set(vmPrefixKey("aaaa"), []byte("yes")) - iavlStore.Set(vmPrefixKey("abcd"), []byte("NewData")) - iavlStore.Set(vmPrefixKey("evmStore"), []byte("iavlStore")) - iavlStore.Set(vmPrefixKey("gg"), []byte("world")) - iavlStore.Set(vmPrefixKey("dd"), []byte("yes")) - iavlStore.Set(vmPrefixKey("vv"), []byte("yes")) - _, version, err := store.SaveVersion() - require.Equal(int64(1), version) - require.NoError(err) - - newStore, err := NewMultiWriterAppStore(iavlStore, store.evmStore, false) - require.NoError(err) - - rangeData := iavlStore.Range([]byte("vm")) - require.Equal(7, len(rangeData)) - - // prune max 5 vm keys per block - cfg := config.DefaultConfig() - cfg.AppStore.NumEvmKeysToPrune = 5 - configBytes, err := proto.Marshal(cfg) - require.NoError(err) - newStore.Set([]byte(configKey), configBytes) - - // prune VM keys - // NOTE: only 3 vm keys will actually get pruned due to the quirkiness of RangeWithLimit - _, version, err = newStore.SaveVersion() - require.Equal(int64(2), version) - require.NoError(err) - - // expect number of vm keys to be 7-3 = 4 - rangeData = iavlStore.Range([]byte("vm")) - require.Equal(4, len(rangeData)) - - // prune VM keys - // NOTE: once again only 3 vm keys will get pruned - _, version, err = newStore.SaveVersion() - require.Equal(int64(3), version) - require.NoError(err) - - // expect number of vm keys to be 4-3 = 1 - rangeData = iavlStore.Range([]byte("vm")) - require.Equal(1, len(rangeData)) - - // prune VM keys - _, version, err = newStore.SaveVersion() - require.Equal(int64(4), version) - require.NoError(err) - - // all the VM keys should be gone now - rangeData = iavlStore.Range([]byte("vm")) - require.Equal(0, len(rangeData)) + require.False(store.Has(nonePrefixKey("gg"))) } func (m *MultiWriterAppStoreTestSuite) TestIAVLRangeWithlimit() { @@ -320,18 +261,18 @@ func (m *MultiWriterAppStoreTestSuite) TestIAVLRangeWithlimit() { // write some vm keys to iavl store iavlStore := store.appStore - iavlStore.Set(vmPrefixKey("abcde"), []byte("world")) - iavlStore.Set(vmPrefixKey("aaaa"), []byte("yes")) - iavlStore.Set(vmPrefixKey("abcd"), []byte("NewData")) - iavlStore.Set(vmPrefixKey("evmStore"), []byte("iavlStore")) - iavlStore.Set(vmPrefixKey("gg"), []byte("world")) - iavlStore.Set(vmPrefixKey("dd"), []byte("yes")) - iavlStore.Set(vmPrefixKey("vv"), []byte("yes")) + iavlStore.Set(nonePrefixKey("abcde"), []byte("world")) + iavlStore.Set(nonePrefixKey("aaaa"), []byte("yes")) + iavlStore.Set(nonePrefixKey("abcd"), []byte("NewData")) + iavlStore.Set(nonePrefixKey("evmStore"), []byte("iavlStore")) + iavlStore.Set(nonePrefixKey("gg"), []byte("world")) + iavlStore.Set(nonePrefixKey("dd"), []byte("yes")) + iavlStore.Set(nonePrefixKey("vv"), []byte("yes")) _, _, err = store.SaveVersion() require.NoError(err) // only 4 VM keys will be returned due to the quirkiness of RangeWithLimit - rangeData := iavlStore.RangeWithLimit([]byte("vm"), 5) + rangeData := iavlStore.RangeWithLimit(nonePrefix, 5) require.Equal(4, len(rangeData)) } @@ -350,6 +291,6 @@ func mockMultiWriterStore(flushInterval int64) (*MultiWriterAppStore, error) { return multiWriterStore, nil } -func vmPrefixKey(key string) []byte { - return util.PrefixKey([]byte("vm"), []byte(key)) +func nonePrefixKey(key string) []byte { + return util.PrefixKey([]byte("none"), []byte(key)) } From e7d93a18294458edf414243fee2010e1bf9ac75e Mon Sep 17 00:00:00 2001 From: Pathorn Date: Mon, 2 Dec 2019 20:26:16 +0700 Subject: [PATCH 55/80] add snapshot truffle test --- e2e/tests/truffle/test/EvmSnapshot.js | 92 +++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 e2e/tests/truffle/test/EvmSnapshot.js diff --git a/e2e/tests/truffle/test/EvmSnapshot.js b/e2e/tests/truffle/test/EvmSnapshot.js new file mode 100644 index 0000000000..ffd26d260b --- /dev/null +++ b/e2e/tests/truffle/test/EvmSnapshot.js @@ -0,0 +1,92 @@ +const { + waitForXBlocks, + ethGetTransactionCount +} = require('./helpers') +const Web3 = require('web3') +const fs = require('fs') +const path = require('path') +const { + SpeculativeNonceTxMiddleware, + SignedTxMiddleware, + Client, + EthersSigner, + createDefaultTxMiddleware, + Address, + LocalAddress, + CryptoUtils, + LoomProvider, + Contracts +} = require('loom-js') +const ethers = require('ethers').ethers + +const NonceTestContract = artifacts.require('NonceTestContract'); + +// web3 functions called using truffle objects use the loomProvider +// web3 functions called uisng we3js access the loom QueryInterface directly +contract('TestEvmSnapshot', async (accounts) => { + // This test is not provider dependent so just run it with Loom Truffle provider + if (process.env.TRUFFLE_PROVIDER === 'hdwallet') { + return + } + + let contract, from, nodeAddr + + beforeEach(async () => { + nodeAddr = fs.readFileSync(path.join(process.env.CLUSTER_DIR, '0', 'node_rpc_addr'), 'utf-8').trim() + + const client = new Client('default', `ws://${nodeAddr}/websocket`, `ws://${nodeAddr}/queryws`) + client.on('error', msg => { + console.error('Error on connect to client', msg) + console.warn('Please verify if loom cluster is running') + }) + const privKey = CryptoUtils.generatePrivateKey() + const pubKey = CryptoUtils.publicKeyFromPrivateKey(privKey) + client.txMiddleware = createDefaultTxMiddleware(client, privKey); + + const setupMiddlewareFn = function (client, privateKey) { + const publicKey = CryptoUtils.publicKeyFromPrivateKey(privateKey) + return [new SpeculativeNonceTxMiddleware(publicKey, client), new SignedTxMiddleware(privateKey)] + } + const loomProvider = new LoomProvider(client, privKey, setupMiddlewareFn) + const web3 = new Web3(loomProvider) + + // Create a mapping between a DAppChain account & an Ethereum account so that + // ethGetTransactionCount can resolve the Ethereum address it's given to a DAppChain address + const localAddr = new Address(client.chainId, LocalAddress.fromPublicKey(pubKey)); + const addressMapper = await Contracts.AddressMapper.createAsync(client, localAddr); + const ethAccount = web3.eth.accounts.create(); + const ethWallet = new ethers.Wallet(ethAccount.privateKey); + await addressMapper.addIdentityMappingAsync( + localAddr, + new Address('eth', LocalAddress.fromHexString(ethAccount.address)), + new EthersSigner(ethWallet) + ); + from = ethAccount.address + + const nonceTestContract = await NonceTestContract.deployed() + contract = new web3.eth.Contract( + NonceTestContract._json.abi, + nonceTestContract.address, + // contract calls go through LoomProvider, which expect the sender address to be + // a local address (not an eth address) + { + from: localAddr.local.toString() + } + ); + }) + it('SnapshotTest', async () => { + for (var i = 0; i < 50; i++) { + contract.methods.set(7777).send().then() + contract.methods.get().call().then() + } + + for (var i = 0; i < 50; i++) { + contract.methods.set(8888).send().then() + contract.methods.get().call().then() + } + await waitForXBlocks(nodeAddr, 5) + await contract.methods.set(9999).send().then() + assert.equal(await contract.methods.get().call(), 9999) + }); + +}); \ No newline at end of file From 85765603acce56a43167439fc4db74ac14e61693 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Tue, 3 Dec 2019 12:15:59 +0700 Subject: [PATCH 56/80] use level db batch in ethdb --- store/evmstore.go | 5 +++++ store/loomethdb.go | 25 ++++++++++++++++--------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/store/evmstore.go b/store/evmstore.go index 13537c18bf..adeb78f9ff 100644 --- a/store/evmstore.go +++ b/store/evmstore.go @@ -17,6 +17,7 @@ import ( "github.com/loomnetwork/loomchain/db" "github.com/pkg/errors" stdprometheus "github.com/prometheus/client_golang/prometheus" + dbm "github.com/tendermint/tendermint/libs/db" ) var ( @@ -80,6 +81,10 @@ func NewEvmStore(evmDB db.DBWrapper, numCachedRoots int, flushInterval int64) *E return evmStore } +func (s *EvmStore) NewBatch() dbm.Batch { + return s.evmDB.NewBatch() +} + // Range iterates in-order over the keys in the store prefixed by the given prefix. // TODO (VM): This needs a proper review, other than tests there is no code that really makes use of // this function, only place it's called is from MultiWriterAppStore.Range but only when diff --git a/store/loomethdb.go b/store/loomethdb.go index 552f8e01bf..c4c2afdadd 100644 --- a/store/loomethdb.go +++ b/store/loomethdb.go @@ -10,6 +10,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" loom "github.com/loomnetwork/go-loom" + "github.com/loomnetwork/go-loom/util" ) var ( @@ -34,33 +35,37 @@ func NewEthDBLogContext(height int64, contractAddr loom.Address, callerAddr loom // implements ethdb.Database type LoomEthDB struct { - state KVStore + evmstore *EvmStore lock sync.RWMutex logContext *EthDBLogContext } -func NewLoomEthDB(evmStore KVStore, logContext *EthDBLogContext) *LoomEthDB { +func NewLoomEthDB(evmstore *EvmStore, logContext *EthDBLogContext) *LoomEthDB { return &LoomEthDB{ - state: PrefixKVStore(vmPrefix, evmStore), + evmstore: evmstore, logContext: logContext, } } func (s *LoomEthDB) Put(key []byte, value []byte) error { - s.state.Set(key, value) + key = util.PrefixKey(vmPrefix, key) + s.evmstore.Set(key, value) return nil } func (s *LoomEthDB) Get(key []byte) ([]byte, error) { - return s.state.Get(key), nil + key = util.PrefixKey(vmPrefix, key) + return s.evmstore.Get(key), nil } func (s *LoomEthDB) Has(key []byte) (bool, error) { - return s.state.Has(key), nil + key = util.PrefixKey(vmPrefix, key) + return s.evmstore.Has(key), nil } func (s *LoomEthDB) Delete(key []byte) error { - s.state.Delete(key) + key = util.PrefixKey(vmPrefix, key) + s.evmstore.Delete(key) return nil } @@ -111,13 +116,15 @@ func (b *batch) Write() error { return bytes.Compare(b.cache[j].key, b.cache[k].key) < 0 }) + levelDBBatch := b.parentStore.evmstore.NewBatch() for _, kv := range b.cache { if kv.value == nil { - b.parentStore.Delete(kv.key) + levelDBBatch.Delete(util.PrefixKey(vmPrefix, kv.key)) } else { - b.parentStore.Put(kv.key, kv.value) + levelDBBatch.Set(util.PrefixKey(vmPrefix, kv.key), kv.value) } } + levelDBBatch.WriteSync() return nil } From 16cca338170427481e1be00d225effb8bc77f1ee Mon Sep 17 00:00:00 2001 From: Pathorn Date: Tue, 3 Dec 2019 14:36:50 +0700 Subject: [PATCH 57/80] use cache for logbatch --- store/loomethdb.go | 128 +++++++++++++++++++++++---------------------- 1 file changed, 65 insertions(+), 63 deletions(-) diff --git a/store/loomethdb.go b/store/loomethdb.go index c4c2afdadd..fd6fc57f32 100644 --- a/store/loomethdb.go +++ b/store/loomethdb.go @@ -7,10 +7,11 @@ import ( "sort" "sync" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" + "github.com/loomnetwork/go-ethereum/common" loom "github.com/loomnetwork/go-loom" "github.com/loomnetwork/go-loom/util" + dbm "github.com/tendermint/tendermint/libs/db" ) var ( @@ -36,7 +37,6 @@ func NewEthDBLogContext(height int64, contractAddr loom.Address, callerAddr loom // implements ethdb.Database type LoomEthDB struct { evmstore *EvmStore - lock sync.RWMutex logContext *EthDBLogContext } @@ -77,29 +77,24 @@ func (s *LoomEthDB) NewBatch() ethdb.Batch { return s.NewLogBatch(s.logContext) } else { newBatch := new(batch) - newBatch.parentStore = s - newBatch.Reset() + newBatch.levelDBbatch = s.evmstore.NewBatch() + newBatch.parentStore = s.evmstore + newBatch.size = 0 return newBatch } } // implements ethdb.Batch -type kvPair struct { - key []byte - value []byte -} type batch struct { - cache []kvPair - parentStore *LoomEthDB - size int + levelDBbatch dbm.Batch + parentStore *EvmStore + size int } func (b *batch) Put(key, value []byte) error { - b.cache = append(b.cache, kvPair{ - key: common.CopyBytes(key), - value: common.CopyBytes(value), - }) + key = util.PrefixKey(vmPrefix, key) + b.levelDBbatch.Set(key, value) b.size += len(value) return nil } @@ -109,47 +104,21 @@ func (b *batch) ValueSize() int { } func (b *batch) Write() error { - b.parentStore.lock.Lock() - defer b.parentStore.lock.Unlock() - - sort.Slice(b.cache, func(j, k int) bool { - return bytes.Compare(b.cache[j].key, b.cache[k].key) < 0 - }) - - levelDBBatch := b.parentStore.evmstore.NewBatch() - for _, kv := range b.cache { - if kv.value == nil { - levelDBBatch.Delete(util.PrefixKey(vmPrefix, kv.key)) - } else { - levelDBBatch.Set(util.PrefixKey(vmPrefix, kv.key), kv.value) - } - } - levelDBBatch.WriteSync() + b.levelDBbatch.WriteSync() return nil } func (b *batch) Reset() { - b.cache = make([]kvPair, 0) + b.levelDBbatch = b.parentStore.NewBatch() b.size = 0 } func (b *batch) Delete(key []byte) error { - b.cache = append(b.cache, kvPair{ - key: common.CopyBytes(key), - value: nil, - }) + key = util.PrefixKey(vmPrefix, key) + b.levelDBbatch.Delete(key) return nil } -func (b *batch) Dump(logger *log.Logger) { - b.parentStore.lock.Lock() - defer b.parentStore.lock.Unlock() - logger.Print("\n---- BATCH DUMP ----\n") - for i, kv := range b.cache { - logger.Printf("IDX %d, KEY %s\n", i, kv.key) - } -} - type EthDBLogParams struct { LogFilename string LogFlags int @@ -164,9 +133,16 @@ type EthDBLogParams struct { LogBeforeWriteDump bool } +type kvPair struct { + key []byte + value []byte +} type LogBatch struct { - batch batch - params EthDBLogParams + parentStore *LoomEthDB + size int + params EthDBLogParams + cache []kvPair + lock sync.RWMutex } const batchHeaderWithContext = ` @@ -190,9 +166,7 @@ const batchHeader = ` func (s *LoomEthDB) NewLogBatch(logContext *EthDBLogContext) ethdb.Batch { b := new(LogBatch) - b.batch = *new(batch) - b.batch.parentStore = s - b.batch.Reset() + b.parentStore = s b.params = EthDBLogParams{ LogFilename: "ethdb-batch.log", LogFlags: 0, @@ -210,7 +184,7 @@ func (s *LoomEthDB) NewLogBatch(logContext *EthDBLogContext) ethdb.Batch { if !loggerStarted { file, err := os.Create(b.params.LogFilename) if err != nil { - return &b.batch + return b } logger = *log.New(file, "", b.params.LogFlags) logger.Println("Created ethdb batch logger") @@ -228,7 +202,11 @@ func (b *LogBatch) Delete(key []byte) error { if b.params.LogDelete { logger.Println("Delete key: ", string(key)) } - return b.batch.Delete(key) + b.cache = append(b.cache, kvPair{ + key: common.CopyBytes(key), + value: nil, + }) + return nil } func (b *LogBatch) Put(key, value []byte) error { @@ -238,15 +216,19 @@ func (b *LogBatch) Put(key, value []byte) error { if b.params.LogPutValue { logger.Println("Put value: ", string(value)) } - err := b.batch.Put(key, value) + b.cache = append(b.cache, kvPair{ + key: common.CopyBytes(key), + value: common.CopyBytes(value), + }) + b.size += len(value) if b.params.LogPutDump { - b.batch.Dump(&logger) + b.Dump(&logger) } - return err + return nil } func (b *LogBatch) ValueSize() int { - size := b.batch.ValueSize() + size := b.size if b.params.LogValueSize { logger.Println("ValueSize : ", size) } @@ -259,21 +241,41 @@ func (b *LogBatch) Write() error { } if b.params.LogBeforeWriteDump { logger.Println("Write, before : ") - b.batch.Dump(&logger) + b.Dump(&logger) + } + b.lock.Lock() + sort.Slice(b.cache, func(j, k int) bool { + return bytes.Compare(b.cache[j].key, b.cache[k].key) < 0 + }) + levelDBbatch := b.parentStore.evmstore.NewBatch() + for _, kv := range b.cache { + if kv.value == nil { + levelDBbatch.Delete(util.PrefixKey(vmPrefix, kv.key)) + } else { + levelDBbatch.Set(util.PrefixKey(vmPrefix, kv.key), kv.value) + } } - err := b.batch.Write() + b.lock.Unlock() + levelDBbatch.WriteSync() if b.params.LogWriteDump { logger.Println("Write, after : ") - b.batch.Dump(&logger) + b.Dump(&logger) } - return err + return nil } func (b *LogBatch) Reset() { - if b.params.LogReset { - logger.Println("Reset batch") + b.cache = make([]kvPair, 0) + b.size = 0 +} + +func (b *LogBatch) Dump(logger *log.Logger) { + b.lock.Lock() + defer b.lock.Unlock() + logger.Print("\n---- BATCH DUMP ----\n") + for i, kv := range b.cache { + logger.Printf("IDX %d, KEY %s\n", i, kv.key) } - b.batch.Reset() } // sortKeys sorts prefixed keys, it will sort the postfix of the key in ascending lexographical order From ea2d726958b51184584947e8cb7e9654581fbfb3 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Tue, 3 Dec 2019 14:54:17 +0700 Subject: [PATCH 58/80] fix wrong import repo --- store/loomethdb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/store/loomethdb.go b/store/loomethdb.go index fd6fc57f32..d015a79fe2 100644 --- a/store/loomethdb.go +++ b/store/loomethdb.go @@ -7,8 +7,8 @@ import ( "sort" "sync" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" - "github.com/loomnetwork/go-ethereum/common" loom "github.com/loomnetwork/go-loom" "github.com/loomnetwork/go-loom/util" dbm "github.com/tendermint/tendermint/libs/db" From eeeab3551b5fd25d21c7c0ee0a145a49df05c45d Mon Sep 17 00:00:00 2001 From: Pathorn Date: Tue, 3 Dec 2019 16:03:28 +0700 Subject: [PATCH 59/80] address PR comments --- store/loomethdb.go | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/store/loomethdb.go b/store/loomethdb.go index d015a79fe2..5e202046c8 100644 --- a/store/loomethdb.go +++ b/store/loomethdb.go @@ -5,7 +5,6 @@ import ( "log" "os" "sort" - "sync" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" @@ -77,7 +76,7 @@ func (s *LoomEthDB) NewBatch() ethdb.Batch { return s.NewLogBatch(s.logContext) } else { newBatch := new(batch) - newBatch.levelDBbatch = s.evmstore.NewBatch() + newBatch.dbBatch = s.evmstore.NewBatch() newBatch.parentStore = s.evmstore newBatch.size = 0 return newBatch @@ -87,14 +86,14 @@ func (s *LoomEthDB) NewBatch() ethdb.Batch { // implements ethdb.Batch type batch struct { - levelDBbatch dbm.Batch - parentStore *EvmStore - size int + dbBatch dbm.Batch + parentStore *EvmStore + size int } func (b *batch) Put(key, value []byte) error { key = util.PrefixKey(vmPrefix, key) - b.levelDBbatch.Set(key, value) + b.dbBatch.Set(key, value) b.size += len(value) return nil } @@ -104,18 +103,18 @@ func (b *batch) ValueSize() int { } func (b *batch) Write() error { - b.levelDBbatch.WriteSync() + b.dbBatch.Write() return nil } func (b *batch) Reset() { - b.levelDBbatch = b.parentStore.NewBatch() + b.dbBatch = b.parentStore.NewBatch() b.size = 0 } func (b *batch) Delete(key []byte) error { key = util.PrefixKey(vmPrefix, key) - b.levelDBbatch.Delete(key) + b.dbBatch.Delete(key) return nil } @@ -142,7 +141,6 @@ type LogBatch struct { size int params EthDBLogParams cache []kvPair - lock sync.RWMutex } const batchHeaderWithContext = ` @@ -184,7 +182,7 @@ func (s *LoomEthDB) NewLogBatch(logContext *EthDBLogContext) ethdb.Batch { if !loggerStarted { file, err := os.Create(b.params.LogFilename) if err != nil { - return b + panic(err) } logger = *log.New(file, "", b.params.LogFlags) logger.Println("Created ethdb batch logger") @@ -243,20 +241,21 @@ func (b *LogBatch) Write() error { logger.Println("Write, before : ") b.Dump(&logger) } - b.lock.Lock() + sort.Slice(b.cache, func(j, k int) bool { return bytes.Compare(b.cache[j].key, b.cache[k].key) < 0 }) - levelDBbatch := b.parentStore.evmstore.NewBatch() + + dbBatch := b.parentStore.evmstore.NewBatch() for _, kv := range b.cache { if kv.value == nil { - levelDBbatch.Delete(util.PrefixKey(vmPrefix, kv.key)) + dbBatch.Delete(util.PrefixKey(vmPrefix, kv.key)) } else { - levelDBbatch.Set(util.PrefixKey(vmPrefix, kv.key), kv.value) + dbBatch.Set(util.PrefixKey(vmPrefix, kv.key), kv.value) } } - b.lock.Unlock() - levelDBbatch.WriteSync() + dbBatch.Write() + if b.params.LogWriteDump { logger.Println("Write, after : ") b.Dump(&logger) @@ -270,8 +269,6 @@ func (b *LogBatch) Reset() { } func (b *LogBatch) Dump(logger *log.Logger) { - b.lock.Lock() - defer b.lock.Unlock() logger.Print("\n---- BATCH DUMP ----\n") for i, kv := range b.cache { logger.Printf("IDX %d, KEY %s\n", i, kv.key) From ed6c72d526e495690c439d7b78c2f0e0e77325b2 Mon Sep 17 00:00:00 2001 From: Vadim Macagon Date: Tue, 3 Dec 2019 18:45:14 +0700 Subject: [PATCH 60/80] Remove LoomEthDB.logContext The log context is now always nil, and there's no obvious way to set it. --- cmd/loom/db/evm.go | 2 +- store/evmstore.go | 2 +- store/loomethdb.go | 38 +++++++++++++++----------------------- 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/cmd/loom/db/evm.go b/cmd/loom/db/evm.go index 4fb486649d..cc03d7f7f0 100644 --- a/cmd/loom/db/evm.go +++ b/cmd/loom/db/evm.go @@ -53,7 +53,7 @@ func newDumpEVMStateFromEvmDB() *cobra.Command { fmt.Printf("version: %d, root: %x\n", version, root) - srcStateDB := gstate.NewDatabase(store.NewLoomEthDB(evmStore, nil)) + srcStateDB := gstate.NewDatabase(store.NewLoomEthDB(evmStore)) srcStateDBTrie, err := srcStateDB.OpenTrie(evmRoot) if err != nil { fmt.Printf("cannot open trie, %s\n", evmRoot.Hex()) diff --git a/store/evmstore.go b/store/evmstore.go index adeb78f9ff..b67aeb3479 100644 --- a/store/evmstore.go +++ b/store/evmstore.go @@ -76,7 +76,7 @@ func NewEvmStore(evmDB db.DBWrapper, numCachedRoots int, flushInterval int64) *E rootCache: rootCache, flushInterval: flushInterval, } - ethDB := NewLoomEthDB(evmStore, nil) + ethDB := NewLoomEthDB(evmStore) evmStore.trieDB = trie.NewDatabase(ethDB) return evmStore } diff --git a/store/loomethdb.go b/store/loomethdb.go index 5e202046c8..45351e1c06 100644 --- a/store/loomethdb.go +++ b/store/loomethdb.go @@ -19,6 +19,7 @@ var ( loggerStarted = false ) +// EthDBLogContext provides additional context when type EthDBLogContext struct { blockHeight int64 contractAddr loom.Address @@ -33,38 +34,32 @@ func NewEthDBLogContext(height int64, contractAddr loom.Address, callerAddr loom } } -// implements ethdb.Database +// LoomEthDB implements ethdb.Database type LoomEthDB struct { - evmstore *EvmStore - logContext *EthDBLogContext + store *EvmStore } -func NewLoomEthDB(evmstore *EvmStore, logContext *EthDBLogContext) *LoomEthDB { +func NewLoomEthDB(evmStore *EvmStore) *LoomEthDB { return &LoomEthDB{ - evmstore: evmstore, - logContext: logContext, + store: evmStore, } } func (s *LoomEthDB) Put(key []byte, value []byte) error { - key = util.PrefixKey(vmPrefix, key) - s.evmstore.Set(key, value) + s.store.Set(util.PrefixKey(vmPrefix, key), value) return nil } func (s *LoomEthDB) Get(key []byte) ([]byte, error) { - key = util.PrefixKey(vmPrefix, key) - return s.evmstore.Get(key), nil + return s.store.Get(util.PrefixKey(vmPrefix, key)), nil } func (s *LoomEthDB) Has(key []byte) (bool, error) { - key = util.PrefixKey(vmPrefix, key) - return s.evmstore.Has(key), nil + return s.store.Has(util.PrefixKey(vmPrefix, key)), nil } func (s *LoomEthDB) Delete(key []byte) error { - key = util.PrefixKey(vmPrefix, key) - s.evmstore.Delete(key) + s.store.Delete(util.PrefixKey(vmPrefix, key)) return nil } @@ -73,18 +68,15 @@ func (s *LoomEthDB) Close() { func (s *LoomEthDB) NewBatch() ethdb.Batch { if LogEthDBBatch { - return s.NewLogBatch(s.logContext) - } else { - newBatch := new(batch) - newBatch.dbBatch = s.evmstore.NewBatch() - newBatch.parentStore = s.evmstore - newBatch.size = 0 - return newBatch + return s.NewLogBatch(nil) + } + return &batch{ + dbBatch: s.store.NewBatch(), + parentStore: s.store, } } // implements ethdb.Batch - type batch struct { dbBatch dbm.Batch parentStore *EvmStore @@ -246,7 +238,7 @@ func (b *LogBatch) Write() error { return bytes.Compare(b.cache[j].key, b.cache[k].key) < 0 }) - dbBatch := b.parentStore.evmstore.NewBatch() + dbBatch := b.parentStore.store.NewBatch() for _, kv := range b.cache { if kv.value == nil { dbBatch.Delete(util.PrefixKey(vmPrefix, kv.key)) From b259464b3340c2864d8b4e5a3ac2fabc384a6818 Mon Sep 17 00:00:00 2001 From: Vadim Macagon Date: Tue, 3 Dec 2019 18:53:46 +0700 Subject: [PATCH 61/80] Tweak the LoomEthDB batch a little bit --- store/loomethdb.go | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/store/loomethdb.go b/store/loomethdb.go index 45351e1c06..c95e22987e 100644 --- a/store/loomethdb.go +++ b/store/loomethdb.go @@ -70,10 +70,7 @@ func (s *LoomEthDB) NewBatch() ethdb.Batch { if LogEthDBBatch { return s.NewLogBatch(nil) } - return &batch{ - dbBatch: s.store.NewBatch(), - parentStore: s.store, - } + return newBatch(s.store) } // implements ethdb.Batch @@ -83,9 +80,15 @@ type batch struct { size int } +func newBatch(store *EvmStore) *batch { + return &batch{ + dbBatch: store.NewBatch(), + parentStore: store, + } +} + func (b *batch) Put(key, value []byte) error { - key = util.PrefixKey(vmPrefix, key) - b.dbBatch.Set(key, value) + b.dbBatch.Set(util.PrefixKey(vmPrefix, key), value) b.size += len(value) return nil } @@ -100,13 +103,13 @@ func (b *batch) Write() error { } func (b *batch) Reset() { + b.dbBatch.Close() b.dbBatch = b.parentStore.NewBatch() b.size = 0 } func (b *batch) Delete(key []byte) error { - key = util.PrefixKey(vmPrefix, key) - b.dbBatch.Delete(key) + b.dbBatch.Delete(util.PrefixKey(vmPrefix, key)) return nil } From 548e35cd31d80192b4a1e4cc1de6815645b72f39 Mon Sep 17 00:00:00 2001 From: Vadim Macagon Date: Wed, 4 Dec 2019 11:04:13 +0700 Subject: [PATCH 62/80] Increase delay before mapping accounts on Linux Both the OSX and Linux Jenkins machines can be sluggish sometimes. --- e2e/tests/receipts/run_truffle_tests.sh | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/e2e/tests/receipts/run_truffle_tests.sh b/e2e/tests/receipts/run_truffle_tests.sh index c176227a81..3d9008aeac 100755 --- a/e2e/tests/receipts/run_truffle_tests.sh +++ b/e2e/tests/receipts/run_truffle_tests.sh @@ -13,12 +13,7 @@ bash ../cluster.sh --init --dir $TEST_DIR --start cd ../truffle # Wait for all built-in contracts to be deployed to the test cluster. -if [[ "$OSTYPE" == "darwin"* ]] && [[ "$NODE_NAME" == "osx"* ]]; then - # Jenkins OSX machine is slugish so give it more time to spin up the test cluster. - sleep 5 -else - sleep 1 -fi +sleep 5 # Run Truffle tests using Truffle HDWallet provider & /eth endpoint CLUSTER_DIR=$TEST_DIR/cluster yarn run map-accounts From b53718b89929580457d40e84486152bd395e7681 Mon Sep 17 00:00:00 2001 From: Vadim Macagon Date: Wed, 4 Dec 2019 12:15:54 +0700 Subject: [PATCH 63/80] Revert irrelevant changes --- builtin/plugins/sample_go_contract/sample_go_contract_test.go | 1 + plugin/vm.go | 1 - rpc/query_server.go | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/builtin/plugins/sample_go_contract/sample_go_contract_test.go b/builtin/plugins/sample_go_contract/sample_go_contract_test.go index 18593c84d6..b125518992 100644 --- a/builtin/plugins/sample_go_contract/sample_go_contract_test.go +++ b/builtin/plugins/sample_go_contract/sample_go_contract_test.go @@ -58,6 +58,7 @@ func deployContractToEVM(ctx *plugin.FakeContextWithEVM, filename string, caller } byteCode := common.FromHex(string(hexByteCode)) byteCode, err = hex.DecodeString(string(hexByteCode)) + vm := evm.NewLoomVm(ctx.State, nil, nil, nil, false) _, contractAddr, err = vm.Create(caller, byteCode, loom.NewBigUIntFromInt(0)) if err != nil { diff --git a/plugin/vm.go b/plugin/vm.go index 33089ba884..4a12c17d47 100644 --- a/plugin/vm.go +++ b/plugin/vm.go @@ -209,7 +209,6 @@ func (vm *PluginVM) StaticCallEVM(caller, addr loom.Address, input []byte) ([]by return nil, err } } - evm := levm.NewLoomVm(vm.State, vm.EventHandler, vm.receiptWriter, createABM, false) return evm.StaticCall(caller, addr, input) } diff --git a/rpc/query_server.go b/rpc/query_server.go index da1dacdc7e..4bc726c0c7 100755 --- a/rpc/query_server.go +++ b/rpc/query_server.go @@ -278,7 +278,6 @@ func (s *QueryServer) queryEvm(state loomchain.State, caller, contract loom.Addr return nil, err } } - vm := levm.NewLoomVm(state, nil, nil, createABM, false) return vm.StaticCall(callerAddr, contract, query) } From 9fe103f4bc76328d4c7b93f61570c65feb0c9bcc Mon Sep 17 00:00:00 2001 From: Vadim Macagon Date: Wed, 4 Dec 2019 12:16:35 +0700 Subject: [PATCH 64/80] Cleanup LogBatch and remove unused sortKeys func --- evm/evm_test.go | 3 +- store/loomethdb.go | 61 +++++++++++------------------- store/loomethdb_test.go | 83 ----------------------------------------- 3 files changed, 24 insertions(+), 123 deletions(-) diff --git a/evm/evm_test.go b/evm/evm_test.go index 97a2875efb..f60778f472 100644 --- a/evm/evm_test.go +++ b/evm/evm_test.go @@ -41,7 +41,8 @@ func mockState() loomchain.State { header := abci.Header{} header.Height = BlockHeight header.Time = blockTime - return loomchain.NewStoreState(context.Background(), store.NewMemStore(), header, nil, nil).WithEVMState(mockEVMState()) + return loomchain.NewStoreState(context.Background(), store.NewMemStore(), header, nil, nil). + WithEVMState(mockEVMState()) } func mockEVMState() *loomchain.EVMState { diff --git a/store/loomethdb.go b/store/loomethdb.go index c95e22987e..edf7faaa25 100644 --- a/store/loomethdb.go +++ b/store/loomethdb.go @@ -132,7 +132,7 @@ type kvPair struct { value []byte } type LogBatch struct { - parentStore *LoomEthDB + parentStore *EvmStore size int params EthDBLogParams cache []kvPair @@ -158,20 +158,21 @@ const batchHeader = ` ` func (s *LoomEthDB) NewLogBatch(logContext *EthDBLogContext) ethdb.Batch { - b := new(LogBatch) - b.parentStore = s - b.params = EthDBLogParams{ - LogFilename: "ethdb-batch.log", - LogFlags: 0, - LogReset: true, - LogDelete: true, - LogWrite: true, - LogValueSize: false, - LogPutKey: true, - LogPutValue: false, - LogPutDump: false, - LogWriteDump: true, - LogBeforeWriteDump: false, + b := &LogBatch{ + parentStore: s.store, + params: EthDBLogParams{ + LogFilename: "ethdb-batch.log", + LogFlags: 0, + LogReset: true, + LogDelete: true, + LogWrite: true, + LogValueSize: false, + LogPutKey: true, + LogPutValue: false, + LogPutDump: false, + LogWriteDump: true, + LogBeforeWriteDump: false, + }, } if !loggerStarted { @@ -221,11 +222,10 @@ func (b *LogBatch) Put(key, value []byte) error { } func (b *LogBatch) ValueSize() int { - size := b.size if b.params.LogValueSize { - logger.Println("ValueSize : ", size) + logger.Println("ValueSize : ", b.size) } - return size + return b.size } func (b *LogBatch) Write() error { @@ -241,7 +241,7 @@ func (b *LogBatch) Write() error { return bytes.Compare(b.cache[j].key, b.cache[k].key) < 0 }) - dbBatch := b.parentStore.store.NewBatch() + dbBatch := b.parentStore.NewBatch() for _, kv := range b.cache { if kv.value == nil { dbBatch.Delete(util.PrefixKey(vmPrefix, kv.key)) @@ -259,6 +259,9 @@ func (b *LogBatch) Write() error { } func (b *LogBatch) Reset() { + if b.params.LogReset { + logger.Println("Reset batch") + } b.cache = make([]kvPair, 0) b.size = 0 } @@ -269,23 +272,3 @@ func (b *LogBatch) Dump(logger *log.Logger) { logger.Printf("IDX %d, KEY %s\n", i, kv.key) } } - -// sortKeys sorts prefixed keys, it will sort the postfix of the key in ascending lexographical order -func sortKeys(prefix []byte, kvs []kvPair) []kvPair { - var unsorted, sorted []int - var tmpKv []kvPair - for i, kv := range kvs { - if 0 == bytes.Compare(prefix, kv.key[:len(prefix)]) { - unsorted = append(unsorted, i) - sorted = append(sorted, i) - } - tmpKv = append(tmpKv, kv) - } - sort.Slice(sorted, func(j, k int) bool { - return bytes.Compare(kvs[sorted[j]].key, kvs[sorted[k]].key) < 0 - }) - for index := 0; index < len(sorted); index++ { - kvs[unsorted[index]] = tmpKv[sorted[index]] - } - return kvs -} diff --git a/store/loomethdb_test.go b/store/loomethdb_test.go index 04ceef5f8c..f0ff09cdeb 100644 --- a/store/loomethdb_test.go +++ b/store/loomethdb_test.go @@ -8,89 +8,6 @@ import ( "github.com/stretchr/testify/require" ) -// This test only verifies running a sort twice gives same result -func TestSortKeys(t *testing.T) { - test1 := []kvPair{ - {[]byte("prefixFred"), []byte("data1")}, - {[]byte("noPrefixMary"), []byte("data2")}, - {[]byte("noPrefixJohn"), []byte("data3")}, - {[]byte("prefixSally"), []byte("data4")}, - {[]byte("noPrefixBob"), []byte("data5")}, - {[]byte("prefixAnne"), []byte("data6")}, - } - test1 = sortKeys([]byte("prefix"), test1) - - test2 := []kvPair{ - {[]byte("prefixSally"), []byte("data4")}, - {[]byte("noPrefixMary"), []byte("data2")}, - {[]byte("noPrefixJohn"), []byte("data3")}, - {[]byte("prefixAnne"), []byte("data6")}, - {[]byte("noPrefixBob"), []byte("data5")}, - {[]byte("prefixFred"), []byte("data1")}, - } - - test2 = sortKeys([]byte("prefix"), test2) - for i := 0; i < len(test1); i++ { - require.Equal(t, 0, bytes.Compare(test1[i].key, test2[i].key)) - } -} - -// This test verifies that prefixed items are sorted by ascending order -func TestSortKeys2(t *testing.T) { - test1 := []kvPair{ - {[]byte("prefixSally"), []byte("data4")}, - {[]byte("prefixFred"), []byte("data1")}, - {[]byte("noPrefixMary"), []byte("data2")}, - {[]byte("noPrefixJohn"), []byte("data3")}, - {[]byte("noPrefixBob"), []byte("data5")}, - {[]byte("prefixAnne"), []byte("data6")}, - } - test1 = sortKeys([]byte("prefix"), test1) - - test2 := []kvPair{ - {[]byte("prefixAnne"), []byte("data6")}, - {[]byte("prefixFred"), []byte("data1")}, - {[]byte("noPrefixMary"), []byte("data2")}, - {[]byte("noPrefixJohn"), []byte("data3")}, - {[]byte("noPrefixBob"), []byte("data5")}, - {[]byte("prefixSally"), []byte("data4")}, - } - - for i := 0; i < len(test1); i++ { - require.Equal(t, string(test2[i].key), string(test1[i].key)) - } -} - -// Real life example -func TestSortSecureKeys(t *testing.T) { - test1 := []kvPair{ - {[]byte("secure-key-q�����;� ��Z���'=��ks֝B"), []byte("data1")}, - {[]byte("secure-key-؀&*>�Y��F8I听Qia���SQ�6��f@"), []byte("data2")}, - {[]byte("secure-key-)\n��T�b��E��8o�K���H@�6/���c"), []byte("data3")}, - {[]byte("h����Ntԇ�ב��E��K]}�ɐW��a7��"), []byte("data4")}, - {[]byte("�牔!��FQ���e�8���M˫����ܤ�S"), []byte("data5")}, - {[]byte("�Ka����ͯ>/�� �\tߕ|���}j���<<�"), []byte("data6")}, - {[]byte("-�F�bt����S �A������;BT�b�gF"), []byte("data7")}, - } - test1 = sortKeys([]byte("secure-key-"), test1) - - test2 := []kvPair{ - {[]byte("secure-key-)\n��T�b��E��8o�K���H@�6/���c"), []byte("data3")}, - {[]byte("secure-key-q�����;� ��Z���'=��ks֝B"), []byte("data1")}, - {[]byte("secure-key-؀&*>�Y��F8I听Qia���SQ�6��f@"), []byte("data2")}, - {[]byte("h����Ntԇ�ב��E��K]}�ɐW��a7��"), []byte("data4")}, - {[]byte("�牔!��FQ���e�8���M˫����ܤ�S"), []byte("data5")}, - {[]byte("�Ka����ͯ>/�� �\tߕ|���}j���<<�"), []byte("data6")}, - {[]byte("-�F�bt����S �A������;BT�b�gF"), []byte("data7")}, - } - - test2 = sortKeys([]byte("secure-key-"), test2) - - for i := 0; i < len(test1); i++ { - require.Equal(t, 0, bytes.Compare(test1[i].key, test2[i].key)) - } -} - func TestSortBarch(t *testing.T) { test1 := []kvPair{ {[]byte("secure-key-q�����;� ��Z���'=��ks֝B"), []byte("data1")}, From 4952caa458def2fb5b291f1ea430dff888ce7bdf Mon Sep 17 00:00:00 2001 From: Vadim Macagon Date: Wed, 4 Dec 2019 13:40:40 +0700 Subject: [PATCH 65/80] Add a few comments --- app.go | 2 +- evm_state.go | 2 +- store/evmstore.go | 15 +++++++++++---- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/app.go b/app.go index 38096d2848..e30d3c66cd 100644 --- a/app.go +++ b/app.go @@ -847,7 +847,7 @@ func (a *Application) Commit() abci.ResponseCommit { commitBlockLatency.With(lvs...).Observe(time.Since(begin).Seconds()) }(time.Now()) - // Commit EVM state + // Commit EVM state changes to the EvmStore if err := a.EVMState.Commit(); err != nil { panic(err) } diff --git a/evm_state.go b/evm_state.go index 999fc91a98..601d5617bb 100644 --- a/evm_state.go +++ b/evm_state.go @@ -30,7 +30,7 @@ func (s *EVMState) Commit() error { if err != nil { return err } - s.evmStore.SetVMRootKey(evmStateRoot[:]) + s.evmStore.SetCurrentRoot(evmStateRoot[:]) // Clear out old state data such as logs and cache to free up memory s.sdb.Reset(evmStateRoot) return nil diff --git a/store/evmstore.go b/store/evmstore.go index b67aeb3479..a2bc53f01a 100644 --- a/store/evmstore.go +++ b/store/evmstore.go @@ -6,9 +6,8 @@ import ( "sort" "time" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/trie" - - gcommon "github.com/ethereum/go-ethereum/common" "github.com/go-kit/kit/metrics" kitprometheus "github.com/go-kit/kit/metrics/prometheus" lru "github.com/hashicorp/golang-lru" @@ -134,6 +133,8 @@ func (s *EvmStore) Range(prefix []byte) plugin.RangeData { return ret } +// TODO: Range/Has/Get/Delete/Set are probably only called from the MultiWriterAppStore which +// doesn't need to do so anymore, remove these functions when MultiWriterAppStore is cleaned up. func (s *EvmStore) Has(key []byte) bool { return s.evmDB.Has(key) } @@ -180,8 +181,11 @@ func (s *EvmStore) Commit(version int64) []byte { // Only commit Patricia tree every N blocks if flushInterval == 0 || version%flushInterval == 0 { + // If the root hasn't changed since the last call to Commit that means no new state changes + // occurred in the trie DB since then, so we can skip committing. if !bytes.Equal(defaultRoot, currentRoot) && !bytes.Equal(currentRoot, s.lastSavedRoot) { - if err := s.trieDB.Commit(gcommon.BytesToHash(currentRoot), false); err != nil { + // trie.Database.Commit will call NewBatch (indirectly) to batch writes to evmDB + if err := s.trieDB.Commit(common.BytesToHash(currentRoot), false); err != nil { panic(err) } } @@ -226,7 +230,10 @@ func (s *EvmStore) TrieDB() *trie.Database { return s.trieDB } -func (s *EvmStore) SetVMRootKey(root []byte) { +// SetCurrentRoot sets the current EVM state root, this root must exist in the current trie DB. +// NOTE: This function must be called prior to each call to Commit. +// TODO: This is clunky, the root should just be passed into Commit! +func (s *EvmStore) SetCurrentRoot(root []byte) { s.rootHash = root } From 7b0a7640a7d18add74e41c6315f1451cd3dedec9 Mon Sep 17 00:00:00 2001 From: Vadim Macagon Date: Wed, 4 Dec 2019 16:28:22 +0700 Subject: [PATCH 66/80] Bump default number of cached EVM state roots to 500 The flush interval is currently set to 100, so the cache must be a bit larger than that, otherwise in-mem-only roots are likely to be evicted too quickly and EVMState.GetSnapshot() will end up returning older state than it should. --- evm/config.go | 2 +- evm_state.go | 10 ++++++++-- store/evmstore.go | 11 ++++++++++- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/evm/config.go b/evm/config.go index 4239a74b11..1a57b84620 100644 --- a/evm/config.go +++ b/evm/config.go @@ -20,7 +20,7 @@ func DefaultEvmStoreConfig() *EvmStoreConfig { DBBackend: "goleveldb", CacheSizeMegs: 256, WriteBufferMegs: 4, - NumCachedRoots: 100, + NumCachedRoots: 500, } } diff --git a/evm_state.go b/evm_state.go index 601d5617bb..d92efd0cb6 100644 --- a/evm_state.go +++ b/evm_state.go @@ -12,6 +12,7 @@ type EVMState struct { evmStore *store.EvmStore } +// NewEVMState returns the EVM state corresponding to the current version of the given store. func NewEVMState(evmStore *store.EvmStore) (*EVMState, error) { evmRoot, _ := evmStore.Version() sdb, err := state.New(common.BytesToHash(evmRoot), state.NewDatabaseWithTrieDB(evmStore.TrieDB())) @@ -43,12 +44,17 @@ func (s *EVMState) GetSnapshot(version int64) (*EVMState, error) { if err != nil { return nil, err } - return &EVMState{sdb: stateDB, evmStore: s.evmStore}, nil + return &EVMState{ + evmStore: nil, // this will ensure that Commit() will panic + sdb: stateDB, + }, nil } +// Clone returns a copy of the EVMState instance. +// NOTE: Do not call Commit on the returned instance. func (s *EVMState) Clone() *EVMState { return &EVMState{ - evmStore: s.evmStore, + evmStore: nil, // this will ensure that Commit() will panic sdb: s.sdb.Copy(), } } diff --git a/store/evmstore.go b/store/evmstore.go index a2bc53f01a..723494c90c 100644 --- a/store/evmstore.go +++ b/store/evmstore.go @@ -180,6 +180,8 @@ func (s *EvmStore) Commit(version int64) []byte { } // Only commit Patricia tree every N blocks + // TODO: What happens to all the roots that don't get committed? Are they just going to accumulate + // in the trie.Database.nodes cache forever? if flushInterval == 0 || version%flushInterval == 0 { // If the root hasn't changed since the last call to Commit that means no new state changes // occurred in the trie DB since then, so we can skip committing. @@ -254,9 +256,16 @@ func (s *EvmStore) getLastSavedRoot(targetVersion int64) ([]byte, int64) { return nil, 0 } +// GetRootAt returns the EVM state root corresponding to the given version. func (s *EvmStore) GetRootAt(version int64) []byte { var targetRoot []byte - // Expect cache to be almost 100% hit since cache miss yields extremely poor performance + // Expect cache to be almost 100% hit since cache miss yields extremely poor performance. + // There's an assumption here that the cache will almost always contain all the in-mem-only + // roots that haven't been flushed to disk yet, in the rare case where such a root is evicted + // from the cache the last root persisted to disk will be returned instead. This means it's + // possible (though highly unlikely) for queries to return stale state (since they rely on + // snapshots corresponding to specific versions). This could be fixed by storing the in-mem-only + // roots in another map instead of, or in addition to the cache. val, exist := s.rootCache.Get(version) if exist { targetRoot = val.([]byte) From d6702e78240754316e9a0aac559520e3bc4f3eaa Mon Sep 17 00:00:00 2001 From: Vadim Macagon Date: Wed, 4 Dec 2019 17:46:46 +0700 Subject: [PATCH 67/80] Don't reuse the cachingDB instance between EVMState instances --- cmd/loom/db/evm.go | 4 ++-- evm_state.go | 11 +++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/cmd/loom/db/evm.go b/cmd/loom/db/evm.go index cc03d7f7f0..b75d5d3a2e 100644 --- a/cmd/loom/db/evm.go +++ b/cmd/loom/db/evm.go @@ -44,7 +44,7 @@ func newDumpEVMStateFromEvmDB() *cobra.Command { return err } - evmStore := store.NewEvmStore(evmDB, 100, cfg.AppStore.IAVLFlushInterval) + evmStore := store.NewEvmStore(evmDB, 100, -1) if err := evmStore.LoadVersion(appHeight); err != nil { return err } @@ -132,7 +132,7 @@ func newGetEvmHeightCommand() *cobra.Command { } defer db.Close() - evmStore := store.NewEvmStore(db, 100, 0) + evmStore := store.NewEvmStore(db, 100, -1) if err := evmStore.LoadVersion(math.MaxInt64); err != nil { return err } diff --git a/evm_state.go b/evm_state.go index d92efd0cb6..23186c9206 100644 --- a/evm_state.go +++ b/evm_state.go @@ -40,13 +40,20 @@ func (s *EVMState) Commit() error { // GetSnapshot returns the EVMState instance containing the state as it was at the given version. // NOTE: Do not call Commit on the returned instance. func (s *EVMState) GetSnapshot(version int64) (*EVMState, error) { - stateDB, err := state.New(common.BytesToHash(s.evmStore.GetRootAt(version)), s.sdb.Database()) + // The cachingDB instance created by state.NewDatabaseWithTrieDB() contains a codeSizeCache which + // probably shouldn't be shared between the EVMState instance used by the tx handlers and the + // snapshots instances used by the query server. Which is why NewDatabaseWithTrieDB() is used + // here instead of s.sdb.Database(). + sdb, err := state.New( + common.BytesToHash(s.evmStore.GetRootAt(version)), + state.NewDatabaseWithTrieDB(s.evmStore.TrieDB()), + ) if err != nil { return nil, err } return &EVMState{ evmStore: nil, // this will ensure that Commit() will panic - sdb: stateDB, + sdb: sdb, }, nil } From d818dd91c60d77e89ed6c6ad4dccfd979b883c6b Mon Sep 17 00:00:00 2001 From: Vadim Macagon Date: Wed, 4 Dec 2019 18:30:35 +0700 Subject: [PATCH 68/80] Eliminate PruningIAVLStore We haven't used this store in ages and have no intention of doing so again because it has been replaced by the IAVL flushing interval. Finally getting rid of this store now because the snapshot code for the remaining stores needs to be updated and I don't want to waste time modifying code that doesn't get used. --- cmd/loom/loom.go | 22 +-- store/pruning_iavlstore.go | 271 ------------------------------------- store/store_test.go | 188 ------------------------- 3 files changed, 4 insertions(+), 477 deletions(-) delete mode 100644 store/pruning_iavlstore.go diff --git a/cmd/loom/loom.go b/cmd/loom/loom.go index f098036459..51355e9c3e 100644 --- a/cmd/loom/loom.go +++ b/cmd/loom/loom.go @@ -631,24 +631,10 @@ func loadAppStore( var appStore store.VersionedKVStore var evmStore *store.EvmStore if cfg.AppStore.Version == 1 { // TODO: cleanup these hardcoded numbers - if cfg.AppStore.PruneInterval > int64(0) { - logger.Info("Loading Pruning IAVL Store") - appStore, err = store.NewPruningIAVLStore(db, store.PruningIAVLStoreConfig{ - MaxVersions: cfg.AppStore.MaxVersions, - BatchSize: cfg.AppStore.PruneBatchSize, - Interval: time.Duration(cfg.AppStore.PruneInterval) * time.Second, - Logger: logger, - FlushInterval: cfg.AppStore.IAVLFlushInterval, - }) - if err != nil { - return nil, nil, err - } - } else { - logger.Info("Loading IAVL Store") - appStore, err = store.NewIAVLStore(db, cfg.AppStore.MaxVersions, targetVersion, cfg.AppStore.IAVLFlushInterval) - if err != nil { - return nil, nil, err - } + logger.Info("Loading IAVL Store") + appStore, err = store.NewIAVLStore(db, cfg.AppStore.MaxVersions, targetVersion, cfg.AppStore.IAVLFlushInterval) + if err != nil { + return nil, nil, err } } else if cfg.AppStore.Version == 3 { logger.Info("Loading Multi-Writer App Store") diff --git a/store/pruning_iavlstore.go b/store/pruning_iavlstore.go deleted file mode 100644 index 4e73cbad31..0000000000 --- a/store/pruning_iavlstore.go +++ /dev/null @@ -1,271 +0,0 @@ -package store - -import ( - "fmt" - "runtime" - "sync" - "time" - - "github.com/go-kit/kit/metrics" - kitprometheus "github.com/go-kit/kit/metrics/prometheus" - "github.com/loomnetwork/go-loom" - "github.com/loomnetwork/go-loom/plugin" - "github.com/loomnetwork/loomchain/log" - "github.com/pkg/errors" - stdprometheus "github.com/prometheus/client_golang/prometheus" - dbm "github.com/tendermint/tendermint/libs/db" -) - -var ( - pruneDuration metrics.Histogram - deleteVersionDuration metrics.Histogram -) - -func init() { - const namespace = "loomchain" - const subsystem = "pruning_iavl_store" - - pruneDuration = kitprometheus.NewSummaryFrom( - stdprometheus.SummaryOpts{ - Namespace: namespace, - Subsystem: subsystem, - Name: "prune_duration", - Help: "How long PruningIAVLStore.prune() took to execute (in seconds)", - Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, - }, []string{"error"}) - deleteVersionDuration = kitprometheus.NewSummaryFrom( - stdprometheus.SummaryOpts{ - Namespace: namespace, - Subsystem: subsystem, - Name: "delete_version_duration", - Help: "How long it took to delete a single version from the IAVL store (in seconds)", - Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, - }, []string{"error"}) -} - -type PruningIAVLStoreConfig struct { - MaxVersions int64 // maximum number of versions to keep when pruning - BatchSize int64 // maximum number of versions to delete in each cycle - FlushInterval int64 // number of versions before flushing to disk - Interval time.Duration - Logger *loom.Logger -} - -// PruningIAVLStore is a specialized IAVLStore that has a background thread that periodically prunes -// old versions. It should only be used to prune old clusters, on new clusters nodes will delete -// a version each time they save a new one, so the background thread, and all the extra locking -// is unnecessary. -type PruningIAVLStore struct { - store *IAVLStore - mutex *sync.RWMutex - oldestVer int64 - maxVersions int64 - batchSize int64 - batchCount uint64 - logger *loom.Logger -} - -// NewPruningIAVLStore creates a new PruningIAVLStore. -// maxVersions can be used to specify how many versions should be retained, if set to zero then -// old versions will never been deleted. -func NewPruningIAVLStore(db dbm.DB, cfg PruningIAVLStoreConfig) (*PruningIAVLStore, error) { - // always keep at least 2 of the latest versions - maxVersions := cfg.MaxVersions - if (maxVersions != 0) && (maxVersions < 2) { - maxVersions = 2 - } - - store, err := NewIAVLStore(db, maxVersions, 0, cfg.FlushInterval) - if err != nil { - return nil, err - } - - s := &PruningIAVLStore{ - store: store, - mutex: &sync.RWMutex{}, - maxVersions: maxVersions, - batchSize: cfg.BatchSize, - logger: cfg.Logger, - } - - if s.logger == nil { - s.logger = log.Default - } - - if maxVersions != 0 { - latestVer := store.Version() - - oldestVer := int64(0) - if cfg.BatchSize > 1 { - for i := int64(1); i <= latestVer; i++ { - if store.tree.VersionExists(i) { - oldestVer = i - break - } - } - } - s.oldestVer = oldestVer - - go s.loopWithInterval(s.prune, cfg.Interval) - } - - return s, nil -} - -func (s *PruningIAVLStore) Delete(key []byte) { - s.mutex.Lock() - defer s.mutex.Unlock() - - s.store.Delete(key) -} - -func (s *PruningIAVLStore) Set(key, val []byte) { - s.mutex.Lock() - defer s.mutex.Unlock() - - s.store.Set(key, val) -} - -func (s *PruningIAVLStore) Has(key []byte) bool { - s.mutex.RLock() - defer s.mutex.RUnlock() - - return s.store.Has(key) -} - -func (s *PruningIAVLStore) Get(key []byte) []byte { - s.mutex.RLock() - defer s.mutex.RUnlock() - - return s.store.Get(key) -} - -func (s *PruningIAVLStore) Range(prefix []byte) plugin.RangeData { - s.mutex.RLock() - defer s.mutex.RUnlock() - - return s.store.Range(prefix) -} - -func (s *PruningIAVLStore) Hash() []byte { - s.mutex.Lock() - defer s.mutex.Unlock() - - return s.store.Hash() -} - -func (s *PruningIAVLStore) Version() int64 { - s.mutex.RLock() - defer s.mutex.RUnlock() - - return s.store.Version() -} - -func (s *PruningIAVLStore) SaveVersion() ([]byte, int64, error) { - s.mutex.Lock() - defer s.mutex.Unlock() - - hash, ver, err := s.store.SaveVersion() - if err == nil && s.oldestVer == 0 { - s.oldestVer = ver - } - return hash, ver, err -} - -func (s *PruningIAVLStore) Prune() error { - // pruning is done in the goroutine, so do nothing here - return nil -} - -func (s *PruningIAVLStore) GetSnapshot() Snapshot { - // This isn't an actual snapshot obviously, and never will be, but lets pretend... - return &pruningIAVLStoreSnapshot{ - PruningIAVLStore: s, - } -} - -func (s *PruningIAVLStore) prune() error { - s.mutex.Lock() - defer s.mutex.Unlock() - - var err error - defer func(begin time.Time) { - lvs := []string{"error", fmt.Sprint(err != nil)} - pruneDuration.With(lvs...).Observe(time.Since(begin).Seconds()) - }(time.Now()) - - latestVer := s.store.Version() - endVer := latestVer - s.maxVersions - - if (s.oldestVer == 0) || (s.oldestVer > endVer) { - return nil // nothing to prune yet - } - - if (endVer - s.oldestVer) > s.batchSize { - endVer = s.oldestVer + s.batchSize - } - - if endVer > (latestVer - 2) { - endVer = latestVer - 2 - } - - for i := s.oldestVer; i <= endVer; i++ { - if s.store.tree.VersionExists(i) { - if err = s.deleteVersion(i); err != nil { - return errors.Wrapf(err, "failed to delete tree version %d", i) - } - } - s.oldestVer++ - } - - s.batchCount++ - return nil -} - -func (s *PruningIAVLStore) deleteVersion(ver int64) error { - var err error - defer func(begin time.Time) { - lvs := []string{"error", fmt.Sprint(err != nil)} - deleteVersionDuration.With(lvs...).Observe(time.Since(begin).Seconds()) - }(time.Now()) - - err = s.store.tree.DeleteVersion(ver) - return err -} - -// runWithRecovery should run in a goroutine, it will ensure the given function keeps on running in -// a goroutine as long as it doesn't panic due to a runtime error. -//[MGC] I believe this function shouldn't be used as we should just fail fast if this breaks -func (s *PruningIAVLStore) runWithRecovery(run func()) { - defer func() { - if r := recover(); r != nil { - s.logger.Error("Recovered from panic in PruningIAVLStore goroutine", "r", r) - // Unless it's a runtime error restart the goroutine - if _, ok := r.(runtime.Error); !ok { - time.Sleep(30 * time.Second) - s.logger.Info("Restarting PruningIAVLStore goroutine...\n") - go s.runWithRecovery(run) - } - } - }() - run() -} - -// loopWithInterval will execute the step function in an endless loop, sleeping for the specified -// interval at the end of each loop iteration. -func (s *PruningIAVLStore) loopWithInterval(step func() error, interval time.Duration) { - for { - if err := step(); err != nil { - s.logger.Error("PruneIAVLStore encountered an error", "err", err) - } - time.Sleep(interval) - } -} - -type pruningIAVLStoreSnapshot struct { - *PruningIAVLStore -} - -func (s *pruningIAVLStoreSnapshot) Release() { - // noop -} diff --git a/store/store_test.go b/store/store_test.go index a2c3841ef5..98f270bc2f 100644 --- a/store/store_test.go +++ b/store/store_test.go @@ -5,7 +5,6 @@ import ( "fmt" "sync" "testing" - "time" "github.com/loomnetwork/go-loom/plugin" "github.com/loomnetwork/go-loom/util" @@ -438,140 +437,6 @@ func (ts *MemStoreTestSuite) SetupSuite() { ts.supportsSnapshots = false } -// -// PruningIAVLStore -// - -func TestPruningIAVLStoreBatching(t *testing.T) { - db := dbm.NewMemDB() - cfg := PruningIAVLStoreConfig{ - MaxVersions: 5, - BatchSize: 5, - Interval: 1 * time.Second, - } - store, err := NewPruningIAVLStore(db, cfg) - require.NoError(t, err) - - require.Equal(t, int64(0), store.oldestVer) - - values := []struct { - key []byte - val []byte - }{ - {key: key1, val: val1}, - {key: key2, val: val2}, - {key: key3, val: val3}, - {key: key1, val: val3}, - {key: key2, val: val1}, - {key: key3, val: val2}, - {key: key1, val: val1}, - {key: key2, val: val2}, - {key: key3, val: val3}, - {key: key1, val: val3}, - {key: key2, val: val1}, - {key: key3, val: val2}, - } // 12 items - - curVer := int64(1) - for _, kv := range values { - store.Set(kv.key, kv.val) - _, ver, err := store.SaveVersion() - require.NoError(t, err) - require.Equal(t, curVer, ver) - curVer++ - } - - time.Sleep(5 * time.Second) - - require.True(t, store.Version() > cfg.MaxVersions) - require.Equal(t, store.Version(), store.oldestVer+cfg.MaxVersions-1, "correct number of versions has been kept") - require.Equal(t, uint64(2), store.batchCount, "correct number of batches has been pruned") - - prevOldestVer := store.oldestVer - - store, err = NewPruningIAVLStore(db, cfg) - require.NoError(t, err) - - // the oldest version shouldn't change when the IAVL store is reloaded - require.Equal(t, prevOldestVer, store.oldestVer) -} - -func TestPruningIAVLStoreKeepsAtLeastTwoVersions(t *testing.T) { - cfg := PruningIAVLStoreConfig{ - MaxVersions: 1, - BatchSize: 5, - Interval: 1 * time.Second, - } - store, err := NewPruningIAVLStore(dbm.NewMemDB(), cfg) - require.NoError(t, err) - require.Equal(t, int64(0), store.Version()) - - values := []struct { - key []byte - val []byte - }{ - {key: key1, val: val1}, - {key: key2, val: val2}, - } - - for i, kv := range values { - if i == 2 { - break - } - - store.Set(kv.key, kv.val) - _, _, err := store.SaveVersion() - require.NoError(t, err) - } - - time.Sleep(5 * time.Second) - - require.Equal(t, int64(2), store.Version()) - require.Equal(t, int64(1), store.oldestVer) - require.Equal(t, uint64(0), store.batchCount) -} - -func TestPruningIAVLStoreKeepsAllVersionsIfMaxVersionsIsZero(t *testing.T) { - cfg := PruningIAVLStoreConfig{ - MaxVersions: 0, - BatchSize: 5, - Interval: 1 * time.Second, - } - store, err := NewPruningIAVLStore(dbm.NewMemDB(), cfg) - require.NoError(t, err) - require.Equal(t, int64(0), store.Version()) - require.Equal(t, int64(0), store.maxVersions) - - values := []struct { - key []byte - val []byte - }{ - {key: key1, val: val1}, - {key: key2, val: val2}, - {key: key3, val: val3}, - {key: key1, val: val3}, - {key: key2, val: val1}, - {key: key3, val: val2}, - {key: key1, val: val1}, - {key: key2, val: val2}, - {key: key3, val: val3}, - {key: key1, val: val3}, - {key: key2, val: val1}, - {key: key3, val: val2}, - } // 12 items - - for _, kv := range values { - store.Set(kv.key, kv.val) - _, _, err := store.SaveVersion() - require.NoError(t, err) - } - - time.Sleep(4 * time.Second) - - require.Equal(t, int64(12), store.Version()) - require.Equal(t, uint64(0), store.batchCount) -} - func TestIAVLStoreKeepsAllVersionsIfMaxVersionsIsZero(t *testing.T) { store, err := NewIAVLStore(dbm.NewMemDB(), 0, 0, 0) require.NoError(t, err) @@ -604,56 +469,3 @@ func TestIAVLStoreKeepsAllVersionsIfMaxVersionsIsZero(t *testing.T) { require.Equal(t, int64(12), store.Version()) } - -func TestSwitchFromIAVLStoreToPruningIAVLStore(t *testing.T) { - memDB := dbm.NewMemDB() - store1, err := NewIAVLStore(memDB, 0, 0, 0) - require.NoError(t, err) - - values := []struct { - key []byte - val []byte - }{ - {key: key1, val: val1}, - {key: key2, val: val2}, - {key: key3, val: val3}, - {key: key1, val: val3}, - {key: key2, val: val1}, - {key: key3, val: val2}, - {key: key1, val: val1}, - {key: key2, val: val2}, - {key: key3, val: val3}, - {key: key1, val: val3}, - {key: key2, val: val1}, - {key: key3, val: val2}, - } // 12 items - - for _, kv := range values { - store1.Set(kv.key, kv.val) - _, _, err := store1.SaveVersion() - require.NoError(t, err) - } - - require.Equal(t, int64(12), store1.Version()) - - store2, err := NewIAVLStore(memDB, 11, 0, 0) - require.NoError(t, err) - // force the store to prune an old version - store2.Set(key1, val1) - _, _, err = store2.SaveVersion() - require.NoError(t, err) - - require.Equal(t, int64(13), store2.Version()) - - cfg := PruningIAVLStoreConfig{ - MaxVersions: 5, - BatchSize: 5, - Interval: 1 * time.Second, - } - store3, err := NewPruningIAVLStore(memDB, cfg) - require.NoError(t, err) - - time.Sleep(4 * time.Second) - - require.Equal(t, (store3.Version()-cfg.MaxVersions)+1, store3.oldestVer) -} From d4824ffa84dca30b3199c195ca4cd9a0a962b2d8 Mon Sep 17 00:00:00 2001 From: Vadim Macagon Date: Wed, 4 Dec 2019 18:47:44 +0700 Subject: [PATCH 69/80] Ensure that EVM & app state are in sync in Application.ReadOnlyState() --- app.go | 9 ++++++--- store/iavlstore.go | 7 +++++++ store/logstore.go | 4 ++++ store/memstore.go | 4 ++++ store/multi_writer_app_store.go | 28 +++++++++++++++++++++++++--- store/store.go | 1 + store/versioned_cachingstore.go | 24 ++++++++++++++++++++---- store/versioned_cachingstore_test.go | 10 +++++++++- 8 files changed, 76 insertions(+), 11 deletions(-) diff --git a/app.go b/app.go index e30d3c66cd..7bf8d72a69 100644 --- a/app.go +++ b/app.go @@ -931,11 +931,14 @@ func (a *Application) ReadOnlyState() State { if err != nil { panic(err) } - // TODO: the store snapshot should be created atomically, otherwise the block header might - // not match the state... need to figure out why this hasn't spectacularly failed already + appStateSnapshot, err := a.Store.GetSnapshotAt(lastBlockHeader.Height) + if err != nil { + panic(err) + } + return NewStoreStateSnapshot( nil, - a.Store.GetSnapshot(), + appStateSnapshot, *lastBlockHeader, nil, // TODO: last block hash! a.GetValidatorSet, diff --git a/store/iavlstore.go b/store/iavlstore.go index 253d6f5a00..87cf976e60 100755 --- a/store/iavlstore.go +++ b/store/iavlstore.go @@ -216,6 +216,13 @@ func (s *IAVLStore) GetSnapshot() Snapshot { } } +func (s *IAVLStore) GetSnapshotAt(version int64) (Snapshot, error) { + // This isn't an actual snapshot obviously, and never will be, but lets pretend... + return &iavlStoreSnapshot{ + IAVLStore: s, + }, nil +} + // NewIAVLStore creates a new IAVLStore. // maxVersions can be used to specify how many versions should be retained, if set to zero then // old versions will never been deleted. diff --git a/store/logstore.go b/store/logstore.go index 2e14a6b981..4de507b068 100644 --- a/store/logstore.go +++ b/store/logstore.go @@ -129,3 +129,7 @@ func (s *LogStore) Prune() error { func (s *LogStore) GetSnapshot() Snapshot { return s.store.GetSnapshot() } + +func (s *LogStore) GetSnapshotAt(version int64) (Snapshot, error) { + return s.store.GetSnapshotAt(version) +} diff --git a/store/memstore.go b/store/memstore.go index badf02b59f..dfe89e143c 100644 --- a/store/memstore.go +++ b/store/memstore.go @@ -79,3 +79,7 @@ func (m *MemStore) Prune() error { func (m *MemStore) GetSnapshot() Snapshot { panic("not implemented") } + +func (m *MemStore) GetSnapshotAt(version int64) (Snapshot, error) { + panic("not implemented") +} diff --git a/store/multi_writer_app_store.go b/store/multi_writer_app_store.go index 598da8a296..a456adfc4d 100644 --- a/store/multi_writer_app_store.go +++ b/store/multi_writer_app_store.go @@ -119,7 +119,9 @@ func NewMultiWriterAppStore( store.onlySaveEvmStateToEvmStore = bytes.Equal(store.appStore.Get(evmDBFeatureKey), []byte{1}) } - store.setLastSavedTreeToVersion(appStore.Version()) + if err := store.setLastSavedTreeToVersion(appStore.Version()); err != nil { + return nil, err + } return store, nil } @@ -262,12 +264,32 @@ func (s *MultiWriterAppStore) Prune() error { } func (s *MultiWriterAppStore) GetSnapshot() Snapshot { + snapshot, err := s.GetSnapshotAt(0) + if err != nil { + panic(err) + } + return snapshot +} + +func (s *MultiWriterAppStore) GetSnapshotAt(version int64) (Snapshot, error) { defer func(begin time.Time) { getSnapshotDuration.Observe(time.Since(begin).Seconds()) }(time.Now()) - appStoreTree := (*iavl.ImmutableTree)(atomic.LoadPointer(&s.lastSavedTree)) + + var err error + var appStoreTree *iavl.ImmutableTree + if version == 0 { + appStoreTree = (*iavl.ImmutableTree)(atomic.LoadPointer(&s.lastSavedTree)) + } else { + appStoreTree, err = s.appStore.tree.GetImmutable(version) + if err != nil { + return nil, errors.Wrapf(err, "failed to load immutable tree for version %v", version) + } + } + // TODO: It's no longer necessary to acquire a snapshot from the EvmStore since it's now provided + // by the EVMState. evmDbSnapshot := s.evmStore.GetSnapshot(appStoreTree.Version()) - return newMultiWriterStoreSnapshot(evmDbSnapshot, appStoreTree) + return newMultiWriterStoreSnapshot(evmDbSnapshot, appStoreTree), nil } type multiWriterStoreSnapshot struct { diff --git a/store/store.go b/store/store.go index f2aaae4320..85e0c7fe72 100644 --- a/store/store.go +++ b/store/store.go @@ -54,6 +54,7 @@ type VersionedKVStore interface { // Delete old version of the store Prune() error GetSnapshot() Snapshot + GetSnapshotAt(version int64) (Snapshot, error) } type cacheItem struct { diff --git a/store/versioned_cachingstore.go b/store/versioned_cachingstore.go index 8f9575efff..23cf6bb2d0 100644 --- a/store/versioned_cachingstore.go +++ b/store/versioned_cachingstore.go @@ -372,10 +372,26 @@ func (c *versionedCachingStore) SaveVersion() ([]byte, int64, error) { } func (c *versionedCachingStore) GetSnapshot() Snapshot { - return newVersionedCachingStoreSnapshot( - c.VersionedKVStore.GetSnapshot(), - c.cache, c.version-1, c.logger, - ) + snapshot, err := c.GetSnapshotAt(0) + if err != nil { + panic(err) + } + return snapshot +} + +func (c *versionedCachingStore) GetSnapshotAt(version int64) (Snapshot, error) { + if version == 0 { + return newVersionedCachingStoreSnapshot( + c.VersionedKVStore.GetSnapshot(), + c.cache, c.version-1, c.logger, + ), nil + } + + snapshot, err := c.VersionedKVStore.GetSnapshotAt(version) + if err != nil { + return nil, err + } + return newVersionedCachingStoreSnapshot(snapshot, c.cache, version, c.logger), nil } // CachingStoreSnapshot is a read-only CachingStore with specified version diff --git a/store/versioned_cachingstore_test.go b/store/versioned_cachingstore_test.go index 5345b77e5d..e00095e1a5 100644 --- a/store/versioned_cachingstore_test.go +++ b/store/versioned_cachingstore_test.go @@ -59,6 +59,14 @@ func (m *MockStore) Prune() error { } func (m *MockStore) GetSnapshot() Snapshot { + snapshot, err := m.GetSnapshotAt(0) + if err != nil { + panic(err) + } + return snapshot +} + +func (m *MockStore) GetSnapshotAt(version int64) (Snapshot, error) { snapshotStore := make(map[string][]byte) for k, v := range m.storage { snapshotStore[k] = v @@ -68,7 +76,7 @@ func (m *MockStore) GetSnapshot() Snapshot { } return &mockStoreSnapshot{ MockStore: mstore, - } + }, nil } type mockStoreSnapshot struct { From 7335869c7dbc6e7fd8734a210a3a1d7ce5af0429 Mon Sep 17 00:00:00 2001 From: Vadim Macagon Date: Fri, 6 Dec 2019 12:44:34 +0700 Subject: [PATCH 70/80] Remove bogus implementation of MockStore.GetSnapshotAt Since MockStore.GetSnapshotAt isn't used it's better not to have an implementation of it than to have one that is wrong. --- store/versioned_cachingstore_test.go | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/store/versioned_cachingstore_test.go b/store/versioned_cachingstore_test.go index e00095e1a5..0315046c25 100644 --- a/store/versioned_cachingstore_test.go +++ b/store/versioned_cachingstore_test.go @@ -59,24 +59,19 @@ func (m *MockStore) Prune() error { } func (m *MockStore) GetSnapshot() Snapshot { - snapshot, err := m.GetSnapshotAt(0) - if err != nil { - panic(err) - } - return snapshot -} - -func (m *MockStore) GetSnapshotAt(version int64) (Snapshot, error) { snapshotStore := make(map[string][]byte) for k, v := range m.storage { snapshotStore[k] = v } - mstore := &MockStore{ - storage: snapshotStore, - } return &mockStoreSnapshot{ - MockStore: mstore, - }, nil + MockStore: &MockStore{ + storage: snapshotStore, + }, + } +} + +func (m *MockStore) GetSnapshotAt(version int64) (Snapshot, error) { + panic("not implemented") } type mockStoreSnapshot struct { From c65d47ffa96c1d77c238cd0a2e82747f8310862e Mon Sep 17 00:00:00 2001 From: Vadim Macagon Date: Fri, 6 Dec 2019 17:28:04 +0700 Subject: [PATCH 71/80] Verify state roots in EVMState.GetSnapshot() Due to the flush interval in the EvmStore some EVM state roots may never be written to disk, so it's important to check that the root of the state returned by EVMState.GetSnapshot() at a particular height matches the EVM state root stored in the app store at that height, otherwise the EVM state and app state returned by Application.ReadOnlyState() may end up being out of sync. --- app.go | 15 +++++++++++---- evm_state.go | 16 ++++++++++++++-- store/evmstore.go | 17 ++++++++++------- store/multi_writer_app_store.go | 9 +++++++++ 4 files changed, 44 insertions(+), 13 deletions(-) diff --git a/app.go b/app.go index 7bf8d72a69..144612350e 100644 --- a/app.go +++ b/app.go @@ -927,15 +927,22 @@ func (a *Application) height() int64 { func (a *Application) ReadOnlyState() State { lastBlockHeader := (*abci.Header)(atomic.LoadPointer(&a.lastBlockHeader)) - evmStateSnapshot, err := a.EVMState.GetSnapshot(lastBlockHeader.Height) - if err != nil { - panic(err) - } appStateSnapshot, err := a.Store.GetSnapshotAt(lastBlockHeader.Height) if err != nil { panic(err) } + var evmStateSnapshot *EVMState + if a.EVMState != nil { + evmStateSnapshot, err = a.EVMState.GetSnapshot( + lastBlockHeader.Height, + store.GetEVMRootFromAppStore(appStateSnapshot), + ) + if err != nil { + panic(err) + } + } + return NewStoreStateSnapshot( nil, appStateSnapshot, diff --git a/evm_state.go b/evm_state.go index 23186c9206..d49f4174a1 100644 --- a/evm_state.go +++ b/evm_state.go @@ -1,6 +1,9 @@ package loomchain import ( + "bytes" + "fmt" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" "github.com/loomnetwork/loomchain/store" @@ -38,14 +41,23 @@ func (s *EVMState) Commit() error { } // GetSnapshot returns the EVMState instance containing the state as it was at the given version. +// The specified root is expected to match the root of the returned state, if the roots don't match +// an error will be returned. // NOTE: Do not call Commit on the returned instance. -func (s *EVMState) GetSnapshot(version int64) (*EVMState, error) { +func (s *EVMState) GetSnapshot(version int64, root []byte) (*EVMState, error) { + r, v := s.evmStore.GetRootAt(version) + if !bytes.Equal(r, root) { + return nil, fmt.Errorf( + "EVM roots mismatch, expected (%d): %X, actual (%d): %X", + version, root, v, r, + ) + } // The cachingDB instance created by state.NewDatabaseWithTrieDB() contains a codeSizeCache which // probably shouldn't be shared between the EVMState instance used by the tx handlers and the // snapshots instances used by the query server. Which is why NewDatabaseWithTrieDB() is used // here instead of s.sdb.Database(). sdb, err := state.New( - common.BytesToHash(s.evmStore.GetRootAt(version)), + common.BytesToHash(r), state.NewDatabaseWithTrieDB(s.evmStore.TrieDB()), ) if err != nil { diff --git a/store/evmstore.go b/store/evmstore.go index 723494c90c..5f0d5a5155 100644 --- a/store/evmstore.go +++ b/store/evmstore.go @@ -239,6 +239,9 @@ func (s *EvmStore) SetCurrentRoot(root []byte) { s.rootHash = root } +// getLastSavedRoot retrieves the EVM state root from disk that best matches the given version. +// The roots are not written to disk for every version, they only get written out when they change +// between versions, and even then depending on the flush interval some roots won't be written to disk. func (s *EvmStore) getLastSavedRoot(targetVersion int64) ([]byte, int64) { start := util.PrefixKey(vmPrefix, evmRootPrefix) end := prefixRangeEnd(evmRootKey(targetVersion)) @@ -257,8 +260,9 @@ func (s *EvmStore) getLastSavedRoot(targetVersion int64) ([]byte, int64) { } // GetRootAt returns the EVM state root corresponding to the given version. -func (s *EvmStore) GetRootAt(version int64) []byte { - var targetRoot []byte +// The second return value is version of the EVM state that corresponds to the returned root, +// it may be less than the version requested due to the reasons mentioned in getLastSavedRoot. +func (s *EvmStore) GetRootAt(version int64) ([]byte, int64) { // Expect cache to be almost 100% hit since cache miss yields extremely poor performance. // There's an assumption here that the cache will almost always contain all the in-mem-only // roots that haven't been flushed to disk yet, in the rare case where such a root is evicted @@ -268,16 +272,15 @@ func (s *EvmStore) GetRootAt(version int64) []byte { // roots in another map instead of, or in addition to the cache. val, exist := s.rootCache.Get(version) if exist { - targetRoot = val.([]byte) - } else { - targetRoot, _ = s.getLastSavedRoot(version) + return val.([]byte), version } - return targetRoot + return s.getLastSavedRoot(version) } // TODO: Get rid of this function. EvmStore does not provide snapshot anymore but EVMState does. func (s *EvmStore) GetSnapshot(version int64) *EvmStoreSnapshot { - return NewEvmStoreSnapshot(s.evmDB.GetSnapshot(), s.GetRootAt(version)) + root, _ := s.GetRootAt(version) + return NewEvmStoreSnapshot(s.evmDB.GetSnapshot(), root) } // TODO: Get rid of EvmStoreSnapshot. EvmStore does not provide snapshot anymore but EVMState does. diff --git a/store/multi_writer_app_store.go b/store/multi_writer_app_store.go index a456adfc4d..467f0770b2 100644 --- a/store/multi_writer_app_store.go +++ b/store/multi_writer_app_store.go @@ -81,6 +81,15 @@ func init() { ) } +// GetEVMRootFromAppStore retrieves the current EVM root from the given app store. +func GetEVMRootFromAppStore(s KVReader) []byte { + evmRoot := s.Get(rootKey) + if evmRoot == nil { + return defaultRoot + } + return evmRoot +} + // MultiWriterAppStore reads & writes keys that have the "vm" prefix via both the IAVLStore and the EvmStore, // or just the EvmStore, depending on the evmStoreEnabled flag. type MultiWriterAppStore struct { From 6ecf12fca4925347337243ec00ae8d35f116c82b Mon Sep 17 00:00:00 2001 From: Vadim Macagon Date: Fri, 6 Dec 2019 17:37:59 +0700 Subject: [PATCH 72/80] Remove unused Application.EvmStore field --- app.go | 1 - cmd/loom/loom.go | 1 - 2 files changed, 2 deletions(-) diff --git a/app.go b/app.go index 144612350e..8554aade56 100644 --- a/app.go +++ b/app.go @@ -372,7 +372,6 @@ type Application struct { EventHandler ReceiptHandlerProvider EvmAuxStore *evmaux.EvmAuxStore - EvmStore *store.EvmStore blockindex.BlockIndexStore CreateValidatorManager ValidatorsManagerFactoryFunc CreateChainConfigManager ChainConfigManagerFactoryFunc diff --git a/cmd/loom/loom.go b/cmd/loom/loom.go index 51355e9c3e..0cb4085f37 100644 --- a/cmd/loom/loom.go +++ b/cmd/loom/loom.go @@ -1128,7 +1128,6 @@ func loadApp( EventStore: eventStore, GetValidatorSet: getValidatorSet, EvmAuxStore: evmAuxStore, - EvmStore: evmStore, ReceiptsVersion: cfg.ReceiptsVersion, EVMState: evmState, }, nil From 23f109e3f885dbf95a4cb2adaa86ecd0743d0b52 Mon Sep 17 00:00:00 2001 From: Vadim Macagon Date: Fri, 6 Dec 2019 19:10:42 +0700 Subject: [PATCH 73/80] Revert some modifications to the MultiWriterAppStore tests The tests are supposed to test how the vm-prefixed keys are handled, replacing the prefix makes the tests fairly pointless, so the prefix has been restored. A couple of the snapshot tests have been removed because the EvmStore no longer caches writes until Commit, it now writes them out immediately. --- store/multi_writer_app_store.go | 2 +- store/multi_writer_app_store_test.go | 267 +++++++++++++-------------- 2 files changed, 125 insertions(+), 144 deletions(-) diff --git a/store/multi_writer_app_store.go b/store/multi_writer_app_store.go index 467f0770b2..a731a9bfee 100644 --- a/store/multi_writer_app_store.go +++ b/store/multi_writer_app_store.go @@ -117,7 +117,7 @@ func NewMultiWriterAppStore( appStoreEvmRoot = defaultRoot } } - evmStoreEvmRoot, version := store.evmStore.getLastSavedRoot(store.appStore.Version()) + evmStoreEvmRoot, version := store.evmStore.GetRootAt(store.appStore.Version()) if !bytes.Equal(appStoreEvmRoot, evmStoreEvmRoot) { return nil, fmt.Errorf("EVM roots mismatch, evm.db(%d): %X, app.db(%d): %X", version, evmStoreEvmRoot, appStore.Version(), appStoreEvmRoot) diff --git a/store/multi_writer_app_store_test.go b/store/multi_writer_app_store_test.go index 587e80e3e0..1e3e9e6a2d 100644 --- a/store/multi_writer_app_store_test.go +++ b/store/multi_writer_app_store_test.go @@ -1,19 +1,17 @@ package store import ( - "bytes" "testing" + "github.com/gogo/protobuf/proto" + "github.com/loomnetwork/go-loom/config" "github.com/loomnetwork/go-loom/util" "github.com/loomnetwork/loomchain/db" "github.com/loomnetwork/loomchain/log" + "github.com/pkg/errors" "github.com/stretchr/testify/suite" ) -var ( - nonePrefix = []byte("none") -) - type MultiWriterAppStoreTestSuite struct { suite.Suite } @@ -28,29 +26,29 @@ func TestMultiWriterAppStoreTestSuite(t *testing.T) { func (m *MultiWriterAppStoreTestSuite) TestEnableDisableMultiWriterAppStore() { require := m.Require() - store, err := mockMultiWriterStore(10) + store, err := mockMultiWriterStore(10, 10) require.NoError(err) // vm keys should be written to both the IAVL & EVM store store.Set(evmDBFeatureKey, []byte{}) - store.Set(nonePrefixKey("abcd"), []byte("hello")) - store.Set(nonePrefixKey("abcde"), []byte("world")) - store.Set(nonePrefixKey("evmStore"), []byte("yes")) - store.Set(nonePrefixKey("aaaa"), []byte("yes")) + store.Set(vmPrefixKey("abcd"), []byte("hello")) + store.Set(vmPrefixKey("abcde"), []byte("world")) + store.Set(vmPrefixKey("evmStore"), []byte("yes")) + store.Set(vmPrefixKey("aaaa"), []byte("yes")) store.Set([]byte("abcd"), []byte("NewData")) - rangeData := store.Range(nonePrefix) + rangeData := store.Range(vmPrefix) require.Equal(4, len(rangeData)) require.True(store.Has([]byte("abcd"))) // vm keys should now only be written to the EVM store store.Set(evmDBFeatureKey, []byte{1}) - store.Set(nonePrefixKey("gg"), []byte("world")) - store.Set(nonePrefixKey("dd"), []byte("yes")) - store.Set(nonePrefixKey("vv"), []byte("yes")) + store.Set(vmPrefixKey("gg"), []byte("world")) + store.Set(vmPrefixKey("dd"), []byte("yes")) + store.Set(vmPrefixKey("vv"), []byte("yes")) store.Set([]byte("dcba"), []byte("MoreData")) - rangeData = store.Range(nonePrefix) + rangeData = store.Range(vmPrefix) require.Equal(7, len(rangeData)) require.True(store.Has([]byte("abcd"))) require.True(store.Has([]byte("dcba"))) @@ -58,80 +56,47 @@ func (m *MultiWriterAppStoreTestSuite) TestEnableDisableMultiWriterAppStore() { func (m *MultiWriterAppStoreTestSuite) TestMultiWriterAppStoreDelete() { require := m.Require() - store, err := mockMultiWriterStore(10) + store, err := mockMultiWriterStore(10, 10) require.NoError(err) // vm keys should be written to both the IAVL & EVM store store.Set(evmDBFeatureKey, []byte{}) - store.Set(nonePrefixKey("abcd"), []byte("hello")) - store.Set(nonePrefixKey("abcde"), []byte("world")) - store.Set(nonePrefixKey("evmStore"), []byte("yes")) - store.Set(nonePrefixKey("aaaa"), []byte("yes")) + store.Set(vmPrefixKey("abcd"), []byte("hello")) + store.Set(vmPrefixKey("abcde"), []byte("world")) + store.Set(vmPrefixKey("evmStore"), []byte("yes")) + store.Set(vmPrefixKey("aaaa"), []byte("yes")) store.Set([]byte("vmroot"), []byte("SSSSSSSSSSSSS")) store.Set([]byte("abcd"), []byte("NewData")) - store.Delete(nonePrefixKey("abcd")) - require.False(store.Has(nonePrefixKey("abcd"))) + store.Delete(vmPrefixKey("abcd")) + require.False(store.Has(vmPrefixKey("abcd"))) - rangeData := store.Range(nonePrefix) + rangeData := store.Range(vmPrefix) require.Equal(3, len(rangeData)) require.True(store.Has([]byte("vmroot"))) require.True(store.Has([]byte("abcd"))) // vm keys should be written to the EVM store store.Set(evmDBFeatureKey, []byte{1}) - rangeData = store.Range(nonePrefix) + rangeData = store.Range(vmPrefix) require.Equal(3, len(rangeData)) require.Equal([]byte("SSSSSSSSSSSSS"), store.Get([]byte("vmroot"))) - store.Set(nonePrefixKey("gg"), []byte("world")) - store.Set(nonePrefixKey("dd"), []byte("yes")) - store.Set(nonePrefixKey("vv"), []byte("yes")) - store.Delete(nonePrefixKey("vv")) - require.False(store.Has(nonePrefixKey("vv"))) + store.Set(vmPrefixKey("gg"), []byte("world")) + store.Set(vmPrefixKey("dd"), []byte("yes")) + store.Set(vmPrefixKey("vv"), []byte("yes")) + store.Delete(vmPrefixKey("vv")) + require.False(store.Has(vmPrefixKey("vv"))) - rangeData = store.Range(nonePrefix) + rangeData = store.Range(vmPrefix) require.Equal(5, len(rangeData)) require.True(store.Has([]byte("abcd"))) } -func (m *MultiWriterAppStoreTestSuite) TestMultiWriterAppStoreSnapShot() { - require := m.Require() - store, err := mockMultiWriterStore(10) - require.NoError(err) - - store.Set(evmDBFeatureKey, []byte{1}) - store.Set(nonePrefixKey("abcd"), []byte("hello")) - store.Set(nonePrefixKey("abcde"), []byte("world")) - store.Set(nonePrefixKey("evmStore"), []byte("yes")) - store.Set(nonePrefixKey("aaaa"), []byte("yes")) - store.Set([]byte("ssssvvv"), []byte("SSSSSSSSSSSSS")) - store.Set([]byte("abcd"), []byte("NewData")) - _, _, err = store.SaveVersion() - require.NoError(err) - - store.Set(nonePrefixKey("abcd"), []byte("hellooooooo")) - store.Set(nonePrefixKey("abcde"), []byte("vvvvvvvvv")) - store.Set([]byte("abcd"), []byte("asdfasdf")) - - snapshot := store.GetSnapshot() - require.Equal([]byte("hello"), snapshot.Get(nonePrefixKey("abcd"))) - require.Equal([]byte("NewData"), snapshot.Get([]byte("abcd"))) - require.Equal([]byte("world"), snapshot.Get(nonePrefixKey("abcde"))) - - _, _, err = store.SaveVersion() - require.NoError(err) - - snapshot = store.GetSnapshot() - require.Equal([]byte("asdfasdf"), snapshot.Get([]byte("abcd"))) - require.Equal([]byte("hellooooooo"), snapshot.Get(nonePrefixKey("abcd"))) - require.Equal([]byte("vvvvvvvvv"), snapshot.Get(nonePrefixKey("abcde"))) -} - -func (m *MultiWriterAppStoreTestSuite) TestMultiWriterAppStoreSnapShotFlushInterval() { +func (m *MultiWriterAppStoreTestSuite) TestMultiWriterAppStoreSnapshotFlushInterval() { require := m.Require() // flush data to disk every 2 blocks - store, err := mockMultiWriterStore(2) + store, err := mockMultiWriterStore(2, 2) require.NoError(err) // the first version go to memory @@ -163,127 +128,143 @@ func (m *MultiWriterAppStoreTestSuite) TestMultiWriterAppStoreSnapShotFlushInter require.Equal([]byte("test2"), snapshotv1.Get([]byte("test2"))) } -func (m *MultiWriterAppStoreTestSuite) TestMultiWriterAppStoreSnapShotRange() { +func (m *MultiWriterAppStoreTestSuite) TestMultiWriterAppStoreSaveVersion() { require := m.Require() - store, err := mockMultiWriterStore(10) + store, err := mockMultiWriterStore(10, -1) require.NoError(err) + // vm keys should be written to the EVM store store.Set(evmDBFeatureKey, []byte{1}) - store.Set(nonePrefixKey("abcd"), []byte("hello")) - store.Set(nonePrefixKey("abcde"), []byte("world")) - store.Set(nonePrefixKey("evmStore"), []byte("yes")) - store.Set(nonePrefixKey("aaaa"), []byte("yes")) - store.Set([]byte("ssssvvv"), []byte("SSSSSSSSSSSSS")) + store.Set(vmPrefixKey("abcd"), []byte("hello")) + store.Set(vmPrefixKey("abcde"), []byte("world")) + store.Set(vmPrefixKey("evmStore"), []byte("yes")) + store.Set(vmPrefixKey("aaaa"), []byte("yes")) store.Set([]byte("abcd"), []byte("NewData")) - store.Set([]byte("uuuu"), []byte("SSSSSSSSSSSSS")) - store.Set([]byte("sssss"), []byte("NewData")) + store.Set([]byte("evmStore"), []byte("iavlStore")) + store.Set(vmPrefixKey("gg"), []byte("world")) + store.Set(vmPrefixKey("dd"), []byte("yes")) + store.Set(vmPrefixKey("vv"), []byte("yes")) - snapshot := store.GetSnapshot() - rangeData := snapshot.Range(nonePrefix) - require.Equal(0, len(rangeData)) - _, _, err = store.SaveVersion() + _, version, err := store.SaveVersion() + require.Equal(int64(1), version) require.NoError(err) - snapshot = store.GetSnapshot() - rangeData = snapshot.Range(nonePrefix) - require.Equal(4, len(rangeData)) - require.Equal(0, bytes.Compare(snapshot.Get(nonePrefixKey("abcd")), []byte("hello"))) - require.Equal(0, bytes.Compare(snapshot.Get(nonePrefixKey("abcde")), []byte("world"))) - require.Equal(0, bytes.Compare(snapshot.Get(nonePrefixKey("evmStore")), []byte("yes"))) - require.Equal(0, bytes.Compare(snapshot.Get(nonePrefixKey("aaaa")), []byte("yes"))) + require.Equal([]byte("hello"), store.Get(vmPrefixKey("abcd"))) + require.Equal([]byte("NewData"), store.Get([]byte("abcd"))) + require.True(store.Has(vmPrefixKey("gg"))) + store.Delete(vmPrefixKey("gg")) - // Modifications shouldn't be visible in the snapshot until the next SaveVersion() - store.Delete(nonePrefixKey("abcd")) - store.Delete([]byte("ssssvvv")) + dataRange := store.Range(vmPrefix) + require.Equal(6+1, len(dataRange)) // +1 is for the evm root that written by the EVM store itself - snapshot = store.GetSnapshot() - rangeData = snapshot.Range(nonePrefix) - require.Equal(4, len(rangeData)) - require.Equal(0, bytes.Compare(snapshot.Get(nonePrefixKey("abcd")), []byte("hello"))) - require.Equal(0, bytes.Compare(snapshot.Get(nonePrefixKey("abcde")), []byte("world"))) - require.Equal(0, bytes.Compare(snapshot.Get(nonePrefixKey("evmStore")), []byte("yes"))) - require.Equal(0, bytes.Compare(snapshot.Get(nonePrefixKey("aaaa")), []byte("yes"))) - - _, _, err = store.SaveVersion() + _, version, err = store.SaveVersion() + require.Equal(int64(2), version) require.NoError(err) - snapshot = store.GetSnapshot() - rangeData = snapshot.Range(nonePrefix) - require.Equal(3, len(rangeData)) - require.Equal(0, len(snapshot.Get(nonePrefixKey("abcd")))) // has been deleted - require.Equal(0, len(snapshot.Get([]byte("ssssvvv")))) // has been deleted - require.Equal(0, bytes.Compare(snapshot.Get(nonePrefixKey("abcde")), []byte("world"))) - require.Equal(0, bytes.Compare(snapshot.Get(nonePrefixKey("evmStore")), []byte("yes"))) - require.Equal(0, bytes.Compare(snapshot.Get(nonePrefixKey("aaaa")), []byte("yes"))) + require.Equal([]byte("hello"), store.Get(vmPrefixKey("abcd"))) + require.Equal([]byte("NewData"), store.Get([]byte("abcd"))) + require.False(store.Has(vmPrefixKey("gg"))) } -func (m *MultiWriterAppStoreTestSuite) TestMultiWriterAppStoreSaveVersion() { +func (m *MultiWriterAppStoreTestSuite) TestPruningEvmKeys() { require := m.Require() - store, err := mockMultiWriterStore(10) + store, err := mockMultiWriterStore(10, 10) require.NoError(err) - // vm keys should be written to the EVM store - store.Set(evmDBFeatureKey, []byte{1}) - store.Set(nonePrefixKey("abcd"), []byte("hello")) - store.Set(nonePrefixKey("abcde"), []byte("world")) - store.Set(nonePrefixKey("evmStore"), []byte("yes")) - store.Set(nonePrefixKey("aaaa"), []byte("yes")) - store.Set([]byte("abcd"), []byte("NewData")) - store.Set([]byte("evmStore"), []byte("iavlStore")) - store.Set(nonePrefixKey("gg"), []byte("world")) - store.Set(nonePrefixKey("dd"), []byte("yes")) - store.Set(nonePrefixKey("vv"), []byte("yes")) - + // write some vm keys to iavl store + iavlStore := store.appStore + iavlStore.Set(vmPrefixKey("abcde"), []byte("world")) + iavlStore.Set(vmPrefixKey("aaaa"), []byte("yes")) + iavlStore.Set(vmPrefixKey("abcd"), []byte("NewData")) + iavlStore.Set(vmPrefixKey("evmStore"), []byte("iavlStore")) + iavlStore.Set(vmPrefixKey("gg"), []byte("world")) + iavlStore.Set(vmPrefixKey("dd"), []byte("yes")) + iavlStore.Set(vmPrefixKey("vv"), []byte("yes")) _, version, err := store.SaveVersion() + require.NoError(err) require.Equal(int64(1), version) + require.Equal(version, iavlStore.Version()) + _, evmStoreVer := store.evmStore.Version() + require.Equal(version, evmStoreVer) + + newStore, err := NewMultiWriterAppStore(iavlStore, store.evmStore, false) require.NoError(err) - require.Equal([]byte("hello"), store.Get(nonePrefixKey("abcd"))) - require.Equal([]byte("NewData"), store.Get([]byte("abcd"))) - require.True(store.Has(nonePrefixKey("gg"))) - store.Delete(nonePrefixKey("gg")) + rangeData := iavlStore.Range([]byte("vm")) + require.Equal(7, len(rangeData)) - dataRange := store.Range(nonePrefix) - require.Equal(6, len(dataRange)) - _, version, err = store.SaveVersion() + // prune max 5 vm keys per block + cfg := config.DefaultConfig() + cfg.AppStore.NumEvmKeysToPrune = 5 + configBytes, err := proto.Marshal(cfg) + require.NoError(err) + newStore.Set([]byte(configKey), configBytes) + + // prune VM keys + // NOTE: only 3 vm keys will actually get pruned due to the quirkiness of RangeWithLimit + _, version, err = newStore.SaveVersion() require.Equal(int64(2), version) require.NoError(err) - require.Equal([]byte("hello"), store.Get(nonePrefixKey("abcd"))) - require.Equal([]byte("NewData"), store.Get([]byte("abcd"))) - require.False(store.Has(nonePrefixKey("gg"))) + // expect number of vm keys to be 7-3 = 4 + rangeData = iavlStore.Range([]byte("vm")) + require.Equal(4, len(rangeData)) + + // prune VM keys + // NOTE: once again only 3 vm keys will get pruned + _, version, err = newStore.SaveVersion() + require.Equal(int64(3), version) + require.NoError(err) + + // expect number of vm keys to be 4-3 = 1 + rangeData = iavlStore.Range([]byte("vm")) + require.Equal(1, len(rangeData)) + + // prune VM keys + _, version, err = newStore.SaveVersion() + require.Equal(int64(4), version) + require.NoError(err) + + // all the VM keys should be gone now + rangeData = iavlStore.Range([]byte("vm")) + require.Equal(0, len(rangeData)) } func (m *MultiWriterAppStoreTestSuite) TestIAVLRangeWithlimit() { require := m.Require() - store, err := mockMultiWriterStore(10) + store, err := mockMultiWriterStore(10, 10) require.NoError(err) // write some vm keys to iavl store iavlStore := store.appStore - iavlStore.Set(nonePrefixKey("abcde"), []byte("world")) - iavlStore.Set(nonePrefixKey("aaaa"), []byte("yes")) - iavlStore.Set(nonePrefixKey("abcd"), []byte("NewData")) - iavlStore.Set(nonePrefixKey("evmStore"), []byte("iavlStore")) - iavlStore.Set(nonePrefixKey("gg"), []byte("world")) - iavlStore.Set(nonePrefixKey("dd"), []byte("yes")) - iavlStore.Set(nonePrefixKey("vv"), []byte("yes")) + iavlStore.Set(vmPrefixKey("abcde"), []byte("world")) + iavlStore.Set(vmPrefixKey("aaaa"), []byte("yes")) + iavlStore.Set(vmPrefixKey("abcd"), []byte("NewData")) + iavlStore.Set(vmPrefixKey("evmStore"), []byte("iavlStore")) + iavlStore.Set(vmPrefixKey("gg"), []byte("world")) + iavlStore.Set(vmPrefixKey("dd"), []byte("yes")) + iavlStore.Set(vmPrefixKey("vv"), []byte("yes")) _, _, err = store.SaveVersion() require.NoError(err) // only 4 VM keys will be returned due to the quirkiness of RangeWithLimit - rangeData := iavlStore.RangeWithLimit(nonePrefix, 5) + rangeData := iavlStore.RangeWithLimit([]byte("vm"), 5) require.Equal(4, len(rangeData)) } -func mockMultiWriterStore(flushInterval int64) (*MultiWriterAppStore, error) { +func mockMultiWriterStore(appStoreFlushInterval, evmStoreFlushInterval int64) (*MultiWriterAppStore, error) { + // Using different flush intervals for the app & evm stores is not supported. + if appStoreFlushInterval > 0 && evmStoreFlushInterval > 0 && appStoreFlushInterval != evmStoreFlushInterval { + return nil, errors.New("positive flush intervals must be consistent") + } + memDb, _ := db.LoadMemDB() - iavlStore, err := NewIAVLStore(memDb, 0, 0, flushInterval) + iavlStore, err := NewIAVLStore(memDb, 0, 0, appStoreFlushInterval) if err != nil { return nil, err } memDb, _ = db.LoadMemDB() - evmStore := NewEvmStore(memDb, 100, 0) + evmStore := NewEvmStore(memDb, 100, evmStoreFlushInterval) multiWriterStore, err := NewMultiWriterAppStore(iavlStore, evmStore, false) if err != nil { return nil, err @@ -291,6 +272,6 @@ func mockMultiWriterStore(flushInterval int64) (*MultiWriterAppStore, error) { return multiWriterStore, nil } -func nonePrefixKey(key string) []byte { - return util.PrefixKey([]byte("none"), []byte(key)) +func vmPrefixKey(key string) []byte { + return util.PrefixKey([]byte("vm"), []byte(key)) } From 45224fcefd65f13ccb36c72d8edad123b7535b69 Mon Sep 17 00:00:00 2001 From: Vadim Macagon Date: Mon, 9 Dec 2019 17:16:56 +0700 Subject: [PATCH 74/80] Hack VersionedCachingStore to get the e2e tests passing The EVM root in the VersionedCachingStore cache wasn't being updated in VersionedCachingStore.SaveVersion() which meant that the snapshots obtained from the VersionedCachingStore contained a stale EVM root that didn't match the EVM state in the snapshot. This hack updates the cached EVM root in VersionedCachingStore.SaveVersion() but it's not a proper fix for the issue, the VersionedCachingStore shouldn't be aware of the EVM root at all. The most sensible thing to do is probably to eliminate the MultiWriterAppStore itself and update Application.Commit() to call EvmStore.Commit(), and then store the EVM root in the VersionedCachingStore. --- e2e/enable-receipts-v2-feature-genesis.json | 24 ++++++++++++++++ store/versioned_cachingstore.go | 32 +++++++++------------ 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/e2e/enable-receipts-v2-feature-genesis.json b/e2e/enable-receipts-v2-feature-genesis.json index 09349c4a73..04452b11d5 100644 --- a/e2e/enable-receipts-v2-feature-genesis.json +++ b/e2e/enable-receipts-v2-feature-genesis.json @@ -22,6 +22,30 @@ "numBlockConfirmations":"1" }, "features": [ + { + "name": "chaincfg:v1.1", + "status": "WAITING" + }, + { + "name": "chaincfg:v1.2", + "status": "WAITING" + }, + { + "name": "chaincfg:v1.3", + "status": "WAITING" + }, + { + "name": "receipts:v3.4", + "status": "WAITING" + }, + { + "name": "receipts:v3.1", + "status": "WAITING" + }, + { + "name": "appstore:v3.1", + "status": "WAITING" + } ] } }, diff --git a/store/versioned_cachingstore.go b/store/versioned_cachingstore.go index 23cf6bb2d0..732fcca5c2 100644 --- a/store/versioned_cachingstore.go +++ b/store/versioned_cachingstore.go @@ -367,6 +367,11 @@ func (c *versionedCachingStore) SaveVersion() ([]byte, int64, error) { // GetSnapshot() is called it won't return the current unpersisted state of the cache, // but rather the last persisted version. c.version = version + 1 + if err = c.cache.Set(rootKey, GetEVMRootFromAppStore(c.VersionedKVStore), version); err != nil { + // Only log error and dont error out + cacheErrors.With("cache_operation", "set").Add(1) + c.logger.Error("[VersionedCachingStore] error while caching EVM root", "err", err) + } } return hash, version, err } @@ -380,6 +385,8 @@ func (c *versionedCachingStore) GetSnapshot() Snapshot { } func (c *versionedCachingStore) GetSnapshotAt(version int64) (Snapshot, error) { + // TODO: c.version & c.VersionedKVStore.GetSnapshot() could end up corresponding to different + // versions, need to do this atomically. if version == 0 { return newVersionedCachingStoreSnapshot( c.VersionedKVStore.GetSnapshot(), @@ -394,7 +401,9 @@ func (c *versionedCachingStore) GetSnapshotAt(version int64) (Snapshot, error) { return newVersionedCachingStoreSnapshot(snapshot, c.cache, version, c.logger), nil } -// CachingStoreSnapshot is a read-only CachingStore with specified version +// versionedCachingStoreSnapshot is a read-only CachingStore with specified version. +// NOTE: versionedCachingStoreSnapshot.Range is not implemented, so the underlying snapshot's Range +// implementation will be used instead. type versionedCachingStoreSnapshot struct { Snapshot cache *versionedBigCache @@ -402,8 +411,9 @@ type versionedCachingStoreSnapshot struct { logger *loom.Logger } -func newVersionedCachingStoreSnapshot(snapshot Snapshot, cache *versionedBigCache, - version int64, logger *loom.Logger) *versionedCachingStoreSnapshot { +func newVersionedCachingStoreSnapshot( + snapshot Snapshot, cache *versionedBigCache, version int64, logger *loom.Logger, +) *versionedCachingStoreSnapshot { return &versionedCachingStoreSnapshot{ Snapshot: snapshot, cache: cache, @@ -412,14 +422,6 @@ func newVersionedCachingStoreSnapshot(snapshot Snapshot, cache *versionedBigCach } } -func (c *versionedCachingStoreSnapshot) Delete(key []byte) { - panic("[versionedCachingStoreSnapshot] Delete() not implemented") -} - -func (c *versionedCachingStoreSnapshot) Set(key, val []byte) { - panic("[versionedCachingStoreSnapshot] Set() not implemented") -} - func (c *versionedCachingStoreSnapshot) Has(key []byte) bool { var err error @@ -505,14 +507,6 @@ func (c *versionedCachingStoreSnapshot) Get(key []byte) []byte { return data } -func (c *versionedCachingStoreSnapshot) SaveVersion() ([]byte, int64, error) { - return nil, 0, errors.New("[VersionedCachingStoreSnapshot] SaveVersion() not implemented") -} - -func (c *versionedCachingStoreSnapshot) Prune() error { - return errors.New("[VersionedCachingStoreSnapshot] Prune() not implemented") -} - func (c *versionedCachingStoreSnapshot) Release() { c.Snapshot.Release() } From e996c1414103446ec61cb65bfaefb5619dca639b Mon Sep 17 00:00:00 2001 From: Vadim Macagon Date: Mon, 9 Dec 2019 17:50:27 +0700 Subject: [PATCH 75/80] Check that EVMState isn't nil in Application.Commit() --- app.go | 8 +++++--- store/versioned_cachingstore.go | 1 - 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app.go b/app.go index 8554aade56..28e5bb26d3 100644 --- a/app.go +++ b/app.go @@ -846,9 +846,11 @@ func (a *Application) Commit() abci.ResponseCommit { commitBlockLatency.With(lvs...).Observe(time.Since(begin).Seconds()) }(time.Now()) - // Commit EVM state changes to the EvmStore - if err := a.EVMState.Commit(); err != nil { - panic(err) + if a.EVMState != nil { + // Commit EVM state changes to the EvmStore + if err := a.EVMState.Commit(); err != nil { + panic(err) + } } appHash, _, err := a.Store.SaveVersion() diff --git a/store/versioned_cachingstore.go b/store/versioned_cachingstore.go index 732fcca5c2..95ec39d1b3 100644 --- a/store/versioned_cachingstore.go +++ b/store/versioned_cachingstore.go @@ -12,7 +12,6 @@ import ( "github.com/go-kit/kit/metrics" kitprometheus "github.com/go-kit/kit/metrics/prometheus" loom "github.com/loomnetwork/go-loom" - "github.com/pkg/errors" stdprometheus "github.com/prometheus/client_golang/prometheus" ) From 8b8a3cbda5d41c15217603f4dc1a073679f86f0b Mon Sep 17 00:00:00 2001 From: Vadim Macagon Date: Mon, 9 Dec 2019 18:35:54 +0700 Subject: [PATCH 76/80] Check that EVMState isn't nil in Application.CheckTx() --- app.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app.go b/app.go index 28e5bb26d3..eab21c0824 100644 --- a/app.go +++ b/app.go @@ -671,7 +671,11 @@ func (a *Application) CheckTx(txBytes []byte) abci.ResponseCheckTx { a.curBlockHeader, a.curBlockHash, a.GetValidatorSet, - ).WithOnChainConfig(a.config).WithEVMState(a.EVMState.Clone()) + ).WithOnChainConfig(a.config) + + if a.EVMState != nil { + state = state.WithEVMState(a.EVMState.Clone()) + } // Receipts & events generated in CheckTx must be discarded since the app state changes they // reflect aren't persisted. From b41cb2a0562553e5f0e8f3d6f7506fb05c8b94e4 Mon Sep 17 00:00:00 2001 From: Pathorn Date: Wed, 11 Dec 2019 13:21:33 +0700 Subject: [PATCH 77/80] address PR comments and make evm state snapshot panic if it calls Commit() --- e2e/tests/truffle/test/EvmSnapshot.js | 4 +++- evm_state.go | 3 +++ store/iavlstore.go | 5 +---- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/e2e/tests/truffle/test/EvmSnapshot.js b/e2e/tests/truffle/test/EvmSnapshot.js index ffd26d260b..62a997b7ff 100644 --- a/e2e/tests/truffle/test/EvmSnapshot.js +++ b/e2e/tests/truffle/test/EvmSnapshot.js @@ -74,12 +74,14 @@ contract('TestEvmSnapshot', async (accounts) => { } ); }) + + // SnapshotTest generates a lot of txs and queries and send them to a contract almost at the same time. + // This test ensures that the snapshot does not have a concurrent read/write problem. it('SnapshotTest', async () => { for (var i = 0; i < 50; i++) { contract.methods.set(7777).send().then() contract.methods.get().call().then() } - for (var i = 0; i < 50; i++) { contract.methods.set(8888).send().then() contract.methods.get().call().then() diff --git a/evm_state.go b/evm_state.go index d49f4174a1..a5f143a569 100644 --- a/evm_state.go +++ b/evm_state.go @@ -30,6 +30,9 @@ func NewEVMState(evmStore *store.EvmStore) (*EVMState, error) { // Commit writes the state changes that occurred since the previous commit to the underlying store. func (s *EVMState) Commit() error { + if s.evmStore == nil { + panic("EvmStore is nil") + } evmStateRoot, err := s.sdb.Commit(true) if err != nil { return err diff --git a/store/iavlstore.go b/store/iavlstore.go index 87cf976e60..01b79db71f 100755 --- a/store/iavlstore.go +++ b/store/iavlstore.go @@ -217,10 +217,7 @@ func (s *IAVLStore) GetSnapshot() Snapshot { } func (s *IAVLStore) GetSnapshotAt(version int64) (Snapshot, error) { - // This isn't an actual snapshot obviously, and never will be, but lets pretend... - return &iavlStoreSnapshot{ - IAVLStore: s, - }, nil + panic("not implemented") } // NewIAVLStore creates a new IAVLStore. From ad002bb8f97e1fedf42e5240340dd16ca3c773cb Mon Sep 17 00:00:00 2001 From: Vadim Macagon Date: Wed, 11 Dec 2019 19:34:38 +0700 Subject: [PATCH 78/80] Add EvmStore.FlushInterval setting to loom.yml This setting works similarly to AppStore.IAVLFlushInterval. --- cmd/loom/loom.go | 7 ++++++- config/config.go | 11 +++++++++++ evm/config.go | 7 +++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/cmd/loom/loom.go b/cmd/loom/loom.go index 0cb4085f37..d7bf7546ea 100644 --- a/cmd/loom/loom.go +++ b/cmd/loom/loom.go @@ -700,7 +700,12 @@ func loadEvmStore(cfg *config.Config, targetVersion int64) (*store.EvmStore, err if err != nil { return nil, err } - evmStore := store.NewEvmStore(db, evmStoreCfg.NumCachedRoots, cfg.AppStore.IAVLFlushInterval) + if cfg.AppStore.IAVLFlushInterval != evmStoreCfg.FlushInterval && + cfg.AppStore.IAVLFlushInterval > 0 && + evmStoreCfg.FlushInterval > 0 { + return nil, errors.New("invalid config, AppStore.IAVLFlushInterval doesn't match EvmStore.FlushInterval") + } + evmStore := store.NewEvmStore(db, evmStoreCfg.NumCachedRoots, evmStoreCfg.FlushInterval) if err := evmStore.LoadVersion(targetVersion); err != nil { return nil, err } diff --git a/config/config.go b/config/config.go index 3f39230d22..f95bdd2e24 100755 --- a/config/config.go +++ b/config/config.go @@ -750,6 +750,11 @@ AppStore: # If true the app store will write EVM state to both IAVLStore and EvmStore # This config works with AppStore Version 3 (MultiWriterAppStore) only SaveEVMStateToIAVL: {{ .AppStore.SaveEVMStateToIAVL }} + # Specifies the number of IAVL tree versions that should be kept in memory before writing a new + # version to disk. + # If set to zero every version will be written to disk unless overridden via the on-chain config. + # If set to -1 every version will always be written to disk, regardless of the on-chain config. + IAVLFlushInterval: {{ .AppStore.IAVLFlushInterval }} {{if .EventStore -}} # # EventStore @@ -773,6 +778,12 @@ EvmStore: CacheSizeMegs: {{.EvmStore.CacheSizeMegs}} # NumCachedRoots defines a number of in-memory cached EVM roots NumCachedRoots: {{.EvmStore.NumCachedRoots}} + # Specifies the number of Merkle tree versions that should be kept in memory before writing a + # new version to disk. + # If set to zero every version will be written to disk unless overridden via the on-chain config + # AppStore.IAVLFlushInterval setting. + # If set to -1 every version will always be written to disk, regardless of the on-chain config. + FlushInterval: {{.EvmStore.FlushInterval}} {{end}} {{if .Web3 -}} diff --git a/evm/config.go b/evm/config.go index 1a57b84620..1d8575d205 100644 --- a/evm/config.go +++ b/evm/config.go @@ -12,6 +12,12 @@ type EvmStoreConfig struct { WriteBufferMegs int // NumCachedRoots defines a number of in-memory cached EVM roots NumCachedRoots int + // Specifies the number of Merkle tree versions that should be kept in memory before writing a + // new version to disk. + // If set to zero every version will be written to disk unless overridden via the on-chain config + // AppStore.IAVLFlushInterval setting. + // If set to -1 every version will always be written to disk, regardless of the on-chain config. + FlushInterval int64 } func DefaultEvmStoreConfig() *EvmStoreConfig { @@ -21,6 +27,7 @@ func DefaultEvmStoreConfig() *EvmStoreConfig { CacheSizeMegs: 256, WriteBufferMegs: 4, NumCachedRoots: 500, + FlushInterval: 0, } } From b1d871b31486e82a889d60eb2505d30480f2219a Mon Sep 17 00:00:00 2001 From: Vadim Macagon Date: Tue, 17 Dec 2019 11:45:47 +0700 Subject: [PATCH 79/80] Switch back to go-ethereum/loomchain branch --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a7bd4e65ea..f9d473dbd2 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ GO_LOOM_GIT_REV = HEAD # Specifies the loomnetwork/transfer-gateway branch/revision to use. TG_GIT_REV = HEAD # loomnetwork/go-ethereum loomchain branch -ETHEREUM_GIT_REV = in-memory-statedb +ETHEREUM_GIT_REV = cce1b3f69354033160583e5576169f9b309ee62e # use go-plugin we get 'timeout waiting for connection info' error HASHICORP_GIT_REV = f4c3476bd38585f9ec669d10ed1686abd52b9961 LEVIGO_GIT_REV = c42d9e0ca023e2198120196f842701bb4c55d7b9 From 412d9878c6fa24712c4875e4bf539a58329365ab Mon Sep 17 00:00:00 2001 From: Vadim Macagon Date: Tue, 17 Dec 2019 11:48:56 +0700 Subject: [PATCH 80/80] Add back a lost comment --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index f9d473dbd2..42a84a8b7f 100644 --- a/Makefile +++ b/Makefile @@ -198,6 +198,7 @@ $(BINANCE_TGORACLE_DIR): cd $(BINANCE_TGORACLE_DIR) && git checkout master && git pull && git checkout $(BINANCE_TG_GIT_REV) $(PROMETHEUS_PROCFS_DIR): + # Temp workaround for https://github.com/prometheus/procfs/issues/221 git clone -q git@github.com:prometheus/procfs $(PROMETHEUS_PROCFS_DIR) cd $(PROMETHEUS_PROCFS_DIR) && git checkout master && git pull && git checkout d3b299e382e6acf1baa852560d862eca4ff643c8