From 2feb5bbb5bbed0e6b624dc9382676fdfb68a9c7e Mon Sep 17 00:00:00 2001
From: Aleksandr Bezobchuk <alex@tendermint.com>
Date: Thu, 4 Oct 2018 12:06:19 -0400
Subject: [PATCH] Finish initial StateDB implementation

---
 state/state_object.go |  36 ++++++++++-
 state/statedb.go      | 144 ++++++++++++++++++++++++++++++++----------
 types/account.go      |   6 +-
 3 files changed, 147 insertions(+), 39 deletions(-)

diff --git a/state/state_object.go b/state/state_object.go
index 95304de5..b4e1a95c 100644
--- a/state/state_object.go
+++ b/state/state_object.go
@@ -8,16 +8,17 @@ import (
 	sdk "github.com/cosmos/cosmos-sdk/types"
 	auth "github.com/cosmos/cosmos-sdk/x/auth"
 
+	tmcrypto "github.com/tendermint/tendermint/crypto"
+
 	"github.com/cosmos/ethermint/types"
 	ethcmn "github.com/ethereum/go-ethereum/common"
 	ethstate "github.com/ethereum/go-ethereum/core/state"
-	"github.com/ethereum/go-ethereum/crypto"
 )
 
 var (
 	_ ethstate.StateObject = (*stateObject)(nil)
 
-	emptyCodeHash = crypto.Keccak256(nil)
+	emptyCodeHash = tmcrypto.Sha256(nil)
 )
 
 type (
@@ -194,6 +195,33 @@ func (so *stateObject) markSuicided() {
 	so.suicided = true
 }
 
+// commitState commits all dirty storage to a KVStore.
+func (so *stateObject) commitState() {
+	ctx := so.stateDB.ctx
+	store := ctx.KVStore(so.stateDB.storageKey)
+
+	for key, value := range so.dirtyStorage {
+		delete(so.dirtyStorage, key)
+
+		// skip no-op changes, persist actual changes
+		if value == so.originStorage[key] {
+			continue
+		}
+
+		so.originStorage[key] = value
+
+		// delete empty values
+		if (value == ethcmn.Hash{}) {
+			store.Delete(key.Bytes())
+			continue
+		}
+
+		store.Set(key.Bytes(), value.Bytes())
+	}
+
+	// TODO: Set the account (storage) root (but we probably don't need this)
+}
+
 // ----------------------------------------------------------------------------
 // Getters
 // ----------------------------------------------------------------------------
@@ -271,6 +299,8 @@ func (so *stateObject) GetCommittedState(_ ethstate.Database, key ethcmn.Hash) e
 	store := ctx.KVStore(so.stateDB.storageKey)
 	rawValue := store.Get(prefixKey.Bytes())
 
+	// TODO: Do we need to RLP split/decode?
+
 	if len(rawValue) > 0 {
 		value.SetBytes(rawValue)
 	}
@@ -328,5 +358,5 @@ func (so stateObject) GetStorageByAddressKey(key []byte) ethcmn.Hash {
 	copy(compositeKey, prefix)
 	copy(compositeKey[len(prefix):], key)
 
-	return crypto.Keccak256Hash(compositeKey)
+	return ethcmn.BytesToHash(tmcrypto.Sha256(compositeKey))
 }
diff --git a/state/statedb.go b/state/statedb.go
index 7f145b9c..7cbdc641 100644
--- a/state/statedb.go
+++ b/state/statedb.go
@@ -9,10 +9,11 @@ import (
 	sdk "github.com/cosmos/cosmos-sdk/types"
 	"github.com/cosmos/cosmos-sdk/x/auth"
 
+	tmcrypto "github.com/tendermint/tendermint/crypto"
+
 	ethcmn "github.com/ethereum/go-ethereum/common"
 	ethstate "github.com/ethereum/go-ethereum/core/state"
 	ethtypes "github.com/ethereum/go-ethereum/core/types"
-	"github.com/ethereum/go-ethereum/crypto"
 )
 
 var (
@@ -126,7 +127,8 @@ func (csdb *CommitStateDB) SetState(addr ethcmn.Address, key, value ethcmn.Hash)
 func (csdb *CommitStateDB) SetCode(addr ethcmn.Address, code []byte) {
 	so := csdb.GetOrNewStateObject(addr)
 	if so != nil {
-		so.SetCode(crypto.Keccak256Hash(code), code)
+		key := ethcmn.BytesToHash(tmcrypto.Sha256(code))
+		so.SetCode(key, code)
 	}
 }
 
@@ -299,22 +301,94 @@ func (csdb *CommitStateDB) StorageTrie(addr ethcmn.Address) ethstate.Trie {
 
 // TODO: Commit writes the state ...
 func (csdb *CommitStateDB) Commit(deleteEmptyObjects bool) (root ethcmn.Hash, err error) {
-	// TODO: ...
+	defer csdb.clearJournalAndRefund()
+
+	// remove dirty state object entries based on the journal
+	for addr := range csdb.journal.dirties {
+		csdb.stateObjectsDirty[addr] = struct{}{}
+	}
+
+	// set the state objects
+	for addr, so := range csdb.stateObjects {
+		_, isDirty := csdb.stateObjectsDirty[addr]
+		switch {
+		case so.suicided || (isDirty && deleteEmptyObjects && so.empty()):
+			// If the state object has been removed, don't bother syncing it and just
+			// remove it from the store.
+			csdb.deleteStateObject(so)
+
+		case isDirty:
+			// write any contract code associated with the state object
+			if so.code != nil && so.dirtyCode {
+				csdb.SetCode(so.Address(), so.code)
+				so.dirtyCode = false
+			}
+
+			// update the object in the KVStore
+			csdb.updateStateObject(so)
+		}
+
+		delete(csdb.stateObjectsDirty, addr)
+	}
+
+	// TODO: Get and return the commit/root from the context
 	return
 }
 
-// TODO: ...
+// Finalize finalizes the state objects (accounts) state by setting their state,
+// removing the csdb destructed objects and clearing the journal as well as the
+// refunds.
 func (csdb *CommitStateDB) Finalize(deleteEmptyObjects bool) {
-	// TODO: ...
+	for addr := range csdb.journal.dirties {
+		so, exist := csdb.stateObjects[addr]
+		if !exist {
+			// ripeMD is 'touched' at block 1714175, in tx:
+			// 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2
+			//
+			// That tx goes out of gas, and although the notion of 'touched' does not
+			// exist there, the touch-event will still be recorded in the journal.
+			// Since ripeMD is a special snowflake, it will persist in the journal even
+			// though the journal is reverted. In this special circumstance, it may
+			// exist in journal.dirties but not in stateObjects. Thus, we can safely
+			// ignore it here.
+			continue
+		}
+
+		if so.suicided || (deleteEmptyObjects && so.empty()) {
+			csdb.deleteStateObject(so)
+		} else {
+			// Set all the dirty state storage items for the state object in the
+			// KVStore and finally set the account in the account mapper.
+			so.commitState()
+			csdb.updateStateObject(so)
+		}
+
+		csdb.stateObjectsDirty[addr] = struct{}{}
+	}
+
+	// invalidate journal because reverting across transactions is not allowed
+	csdb.clearJournalAndRefund()
 }
 
-// IntermediateRoot computes the current root hash of the state trie.
-// It is called in between transactions to get the root hash that
-// goes into transaction receipts.
+// IntermediateRoot returns the current root hash of the state. It is called in
+// between transactions to get the root hash that goes into transaction
+// receipts.
 func (csdb *CommitStateDB) IntermediateRoot(deleteEmptyObjects bool) ethcmn.Hash {
-	// TODO: ...
-	// csdb.Finalize(deleteEmptyObjects)
-	// return csdb.trie.Hash()
+	csdb.Finalize(deleteEmptyObjects)
+
+	// TODO: Get and return the commit/root from the context
+	return ethcmn.Hash{}
+}
+
+// updateStateObject writes the given state object to the store.
+func (csdb *CommitStateDB) updateStateObject(so *stateObject) {
+	csdb.am.SetAccount(csdb.ctx, so.account)
+}
+
+// deleteStateObject removes the given state object from the state store.
+func (csdb *CommitStateDB) deleteStateObject(so *stateObject) {
+	so.deleted = true
+	csdb.am.RemoveAccount(csdb.ctx, so.account)
 }
 
 // ----------------------------------------------------------------------------
@@ -351,6 +425,30 @@ func (csdb *CommitStateDB) RevertToSnapshot(revID int) {
 // Auxiliary
 // ----------------------------------------------------------------------------
 
+// Database retrieves the low level database supporting the lower level trie
+// ops. It is not used in Ethermint, so it returns nil.
+func (csdb *CommitStateDB) Database() ethstate.Database {
+	return nil
+}
+
+// Empty returns whether the state object is either non-existent or empty
+// according to the EIP161 specification (balance = nonce = code = 0).
+func (csdb *CommitStateDB) Empty(addr ethcmn.Address) bool {
+	so := csdb.getStateObject(addr)
+	return so == nil || so.empty()
+}
+
+// Exist reports whether the given account address exists in the state. Notably,
+// this also returns true for suicided accounts.
+func (csdb *CommitStateDB) Exist(addr ethcmn.Address) bool {
+	return csdb.getStateObject(addr) != nil
+}
+
+// Error returns the first non-nil error the StateDB encountered.
+func (csdb *CommitStateDB) Error() error {
+	return csdb.dbErr
+}
+
 // Suicide marks the given account as suicided and clears the account balance.
 //
 // The account's state object is still available until the state is committed,
@@ -540,24 +638,6 @@ func (csdb *CommitStateDB) createObject(addr ethcmn.Address) (newObj, prevObj *s
 	return newObj, prevObj
 }
 
-// Empty returns whether the state object is either non-existent or empty
-// according to the EIP161 specification (balance = nonce = code = 0).
-func (csdb *CommitStateDB) Empty(addr ethcmn.Address) bool {
-	so := csdb.getStateObject(addr)
-	return so == nil || so.empty()
-}
-
-// Exist reports whether the given account address exists in the state. Notably,
-// this also returns true for suicided accounts.
-func (csdb *CommitStateDB) Exist(addr ethcmn.Address) bool {
-	return csdb.getStateObject(addr) != nil
-}
-
-// Error returns the first non-nil error the StateDB encountered.
-func (csdb *CommitStateDB) Error() error {
-	return csdb.dbErr
-}
-
 // setError remembers the first non-nil error it is called with.
 func (csdb *CommitStateDB) setError(err error) {
 	if csdb.dbErr == nil {
@@ -593,9 +673,3 @@ func (csdb *CommitStateDB) getStateObject(addr ethcmn.Address) (stateObject *sta
 func (csdb *CommitStateDB) setStateObject(object *stateObject) {
 	csdb.stateObjects[object.Address()] = object
 }
-
-// Database retrieves the low level database supporting the lower level trie
-// ops. It is not used in Ethermint, so it returns nil.
-func (csdb *CommitStateDB) Database() ethstate.Database {
-	return nil
-}
diff --git a/types/account.go b/types/account.go
index 67fae188..4d58e431 100644
--- a/types/account.go
+++ b/types/account.go
@@ -21,7 +21,11 @@ var _ auth.Account = (*Account)(nil)
 type Account struct {
 	*auth.BaseAccount
 
-	Root     ethcmn.Hash // merkle root of the storage trie
+	// merkle root of the storage trie
+	//
+	// TODO: good chance we may not need this
+	Root ethcmn.Hash
+
 	CodeHash []byte
 }