Skip to content
This repository has been archived by the owner on Nov 30, 2021. It is now read-only.

Commit

Permalink
handle 3 cases
Browse files Browse the repository at this point in the history
  • Loading branch information
fedekunze committed Nov 26, 2020
1 parent df3e15c commit 5041f8d
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 27 deletions.
1 change: 0 additions & 1 deletion app/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ func NewDefaultGenesisState() simapp.GenesisState {
func (app *EthermintApp) ExportAppStateAndValidators(
forZeroHeight bool, jailWhiteList []string,
) (appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) {

// Creates context with current height and checks txs for ctx to be usable by start of next block
ctx := app.NewContext(true, abci.Header{Height: app.LastBlockHeight()})

Expand Down
2 changes: 1 addition & 1 deletion x/evm/types/journal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func (suite *JournalTestSuite) setup() {
evmSubspace := paramsKeeper.Subspace(types.DefaultParamspace).WithKeyTable(ParamKeyTable())

ak := auth.NewAccountKeeper(cdc, authKey, authSubspace, ethermint.ProtoAccount)
suite.ctx = sdk.NewContext(cms, abci.Header{ChainID: "8"}, false, tmlog.NewNopLogger())
suite.ctx = sdk.NewContext(cms, abci.Header{ChainID: "ethermint-8"}, false, tmlog.NewNopLogger())
suite.stateDB = NewCommitStateDB(suite.ctx, storeKey, evmSubspace, ak).WithContext(suite.ctx)
suite.stateDB.SetParams(DefaultParams())
}
Expand Down
13 changes: 13 additions & 0 deletions x/evm/types/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,21 @@ var (
KeyPrefixCode = []byte{0x04}
KeyPrefixStorage = []byte{0x05}
KeyPrefixChainConfig = []byte{0x06}
KeyPrefixHeightHash = []byte{0x07}
)

// HeightHashKey returns the key for the given chain epoch and height.
// The key will be composed in the following order:
// key = prefix + bytes(height) + bytes(epoch)
// This ordering facilitates the iteration by height for the EVM GetHashFn
// queries. The epoch (i.e chain version) needs to be stored in case a software
// upgrade resets the height to 0.
func HeightHashKey(epoch, height uint64) []byte {
epochBz := sdk.Uint64ToBigEndian(epoch)
heightBz := sdk.Uint64ToBigEndian(height)
return append(heightBz, epochBz...)
}

// BloomKey defines the store key for a block Bloom
func BloomKey(height int64) []byte {
return sdk.Uint64ToBigEndian(uint64(height))
Expand Down
65 changes: 48 additions & 17 deletions x/evm/types/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,37 +47,50 @@ type ExecutionResult struct {
GasInfo GasInfo
}

// GetHashFn implements vm.GetHashFunc for Ethermint. It casts the current
// tendermint header hash to an ethereum Hash format. If the context block height
// doesn't match the requested height, it will return an empty hash.
func GetHashFn(ctx sdk.Context) vm.GetHashFunc {
// GetHashFn implements vm.GetHashFunc for Ethermint. It handles 3 cases:
// 1. The requested height matches the current height from context (and thus same epoch number)
// 2. The requested height is from an previous height from the same chain epoch
// 3. The requested height is from a previous chain epoch number (i.e previous chain version)
func GetHashFn(ctx sdk.Context, csdb *CommitStateDB, chainEpoch uint64) vm.GetHashFunc {
return func(height uint64) common.Hash {
if ctx.BlockHeight() != int64(height) {
return common.Hash{}
if ctx.BlockHeight() == int64(height) {
// Case 1: The requested height matches the one from the context so we can retrieve the header
// hash directly from the context.
return hashFromContext(ctx)
}

// cast the ABCI header to tendermint Header type
tmHeader := AbciHeaderToTendermint(ctx.BlockHeader())

// get the Tendermint block hash from the current header
tmBlockHash := tmHeader.Hash()
// Case 2: if the chain is not the current height we need to retrieve the hash from the store for the
// current chain epoch.
hash, found := csdb.GetHeightHash(chainEpoch, height)
if found {
return hash
}

// NOTE: if the validator set hash is missing the hash will be returned as nil,
// so we need to check for this case to prevent a panic when calling Bytes()
if tmBlockHash == nil {
// Case 3: iterate over the past chain epochs and check if there was a previous epoch with the requested
// height. This case applies when a chain upgrades to a non-zero height.
// Eg: chainID ethermint-1, epoch number: 1, final height: 100 --> chainID ethermint-2, epoch number: 2, initial height: 101
hash, found = csdb.FindHeightHash(height)
if !found {
// return an empty hash if the hash wasn't found
return common.Hash{}
}

return common.BytesToHash(tmBlockHash.Bytes())
return hash
}
}

func (st StateTransition) newEVM(ctx sdk.Context, csdb *CommitStateDB, gasLimit uint64, gasPrice *big.Int, config ChainConfig) *vm.EVM {
func (st StateTransition) newEVM(
ctx sdk.Context,
csdb *CommitStateDB,
gasLimit uint64,
gasPrice *big.Int,
config ChainConfig,
) *vm.EVM {
// Create context for evm
context := vm.Context{
CanTransfer: core.CanTransfer,
Transfer: core.Transfer,
GetHash: GetHashFn(ctx),
GetHash: GetHashFn(ctx, csdb, st.ChainID.Uint64()),
Origin: st.Sender,
Coinbase: common.Address{}, // there's no benefitiary since we're not mining
BlockNumber: big.NewInt(ctx.BlockHeight()),
Expand Down Expand Up @@ -249,3 +262,21 @@ func (st StateTransition) TransitionDb(ctx sdk.Context, config ChainConfig) (*Ex

return executionResult, nil
}

// hashFromContext returns the Ethereum Header hash from the context's Tendermint
// block header.
func hashFromContext(ctx sdk.Context) common.Hash {
// cast the ABCI header to tendermint Header type
tmHeader := AbciHeaderToTendermint(ctx.BlockHeader())

// get the Tendermint block hash from the current header
tmBlockHash := tmHeader.Hash()

// NOTE: if the validator set hash is missing the hash will be returned as nil,
// so we need to check for this case to prevent a panic when calling Bytes()
if tmBlockHash == nil {
return common.Hash{}
}

return common.BytesToHash(tmBlockHash.Bytes())
}
2 changes: 1 addition & 1 deletion x/evm/types/state_transition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func (suite *StateDBTestSuite) TestGetHashFn() {
for _, tc := range testCase {
tc.malleate()

hash := types.GetHashFn(suite.ctx)(tc.height)
hash := types.GetHashFn(suite.ctx, suite.stateDB, suite.chainEpoch)(tc.height)
if tc.expEmptyHash {
suite.Require().Equal(common.Hash{}, hash)
} else {
Expand Down
51 changes: 45 additions & 6 deletions x/evm/types/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/params"

emint "github.com/cosmos/ethermint/types"
ethermint "github.com/cosmos/ethermint/types"

ethcmn "github.com/ethereum/go-ethereum/common"
ethstate "github.com/ethereum/go-ethereum/core/state"
Expand Down Expand Up @@ -107,7 +107,7 @@ func NewCommitStateDB(
}
}

// WithContext returns a Database with an updated sdk context
// WithContext returns a Database with an updated SDK context
func (csdb *CommitStateDB) WithContext(ctx sdk.Context) *CommitStateDB {
csdb.ctx = ctx
return csdb
Expand All @@ -117,6 +117,13 @@ func (csdb *CommitStateDB) WithContext(ctx sdk.Context) *CommitStateDB {
// Setters
// ----------------------------------------------------------------------------

// SetHeightHash sets the block header hash associated with a given height.
func (csdb *CommitStateDB) SetHeightHash(epoch, height uint64, hash ethcmn.Hash) {
store := prefix.NewStore(csdb.ctx.KVStore(csdb.storeKey), KeyPrefixHeightHash)
key := HeightHashKey(epoch, height)
store.Set(key, hash.Bytes())
}

// SetParams sets the evm parameters to the param space.
func (csdb *CommitStateDB) SetParams(params Params) {
csdb.paramSpace.SetParamSet(csdb.ctx, &params)
Expand Down Expand Up @@ -286,6 +293,18 @@ func (csdb *CommitStateDB) SlotInAccessList(addr ethcmn.Address, slot ethcmn.Has
// Getters
// ----------------------------------------------------------------------------

// GetHeightHash returns the block header hash associated with a given block height and chain epoch number.
func (csdb *CommitStateDB) GetHeightHash(epoch, height uint64) (ethcmn.Hash, bool) {
store := prefix.NewStore(csdb.ctx.KVStore(csdb.storeKey), KeyPrefixHeightHash)
key := HeightHashKey(epoch, height)
bz := store.Get(key)
if len(bz) == 0 {
return ethcmn.Hash{}, false
}

return ethcmn.BytesToHash(bz), true
}

// GetParams returns the total set of evm parameters.
func (csdb *CommitStateDB) GetParams() (params Params) {
csdb.paramSpace.GetParamSet(csdb.ctx, &params)
Expand Down Expand Up @@ -680,20 +699,20 @@ func (csdb *CommitStateDB) Reset(_ ethcmn.Hash) error {
func (csdb *CommitStateDB) UpdateAccounts() {
for _, stateEntry := range csdb.stateObjects {
currAcc := csdb.accountKeeper.GetAccount(csdb.ctx, sdk.AccAddress(stateEntry.address.Bytes()))
emintAcc, ok := currAcc.(*emint.EthAccount)
ethermintAcc, ok := currAcc.(*ethermint.EthAccount)
if !ok {
continue
}

evmDenom := csdb.GetParams().EvmDenom
balance := sdk.Coin{
Denom: evmDenom,
Amount: emintAcc.GetCoins().AmountOf(evmDenom),
Amount: ethermintAcc.GetCoins().AmountOf(evmDenom),
}

if stateEntry.stateObject.Balance() != balance.Amount.BigInt() && balance.IsValid() ||
stateEntry.stateObject.Nonce() != emintAcc.GetSequence() {
stateEntry.stateObject.account = emintAcc
stateEntry.stateObject.Nonce() != ethermintAcc.GetSequence() {
stateEntry.stateObject.account = ethermintAcc
}
}
}
Expand Down Expand Up @@ -921,6 +940,26 @@ func (csdb *CommitStateDB) RawDump() ethstate.Dump {
return ethstate.Dump{}
}

// FindHeightHash iterates
func (csdb *CommitStateDB) FindHeightHash(height uint64) (ethcmn.Hash, bool) {
store := csdb.ctx.KVStore(csdb.storeKey)
// use the height as the prefix iterator to iterate on all the epochs
prefix := append(KeyPrefixHeightHash, sdk.Uint64ToBigEndian(height)...)
// use the reverse iterator to iterate in descending order (i.e from latest epoch to earliest).
iterator := sdk.KVStoreReversePrefixIterator(store, prefix)
defer iterator.Close()

for ; iterator.Valid(); iterator.Next() {
// NOTE: if the store has a hash for the requested height, then we know that the first
// element will be the one from the latest epoch (due to the reverse/descending iteration).
// Thus it's safe to return the hash directly here.
return ethcmn.BytesToHash(iterator.Value()), true
}

// not found
return ethcmn.Hash{}, false
}

type preimageEntry struct {
// hash key of the preimage entry
hash ethcmn.Hash
Expand Down
4 changes: 3 additions & 1 deletion x/evm/types/statedb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type StateDBTestSuite struct {

ctx sdk.Context
app *app.EthermintApp
chainEpoch uint64
stateDB *types.CommitStateDB
address ethcmn.Address
stateObject types.StateObject
Expand All @@ -40,7 +41,8 @@ func (suite *StateDBTestSuite) SetupTest() {
checkTx := false

suite.app = app.Setup(checkTx)
suite.ctx = suite.app.BaseApp.NewContext(checkTx, abci.Header{Height: 1})
suite.ctx = suite.app.BaseApp.NewContext(checkTx, abci.Header{Height: 1, ChainID: "ethermint-1"})
suite.chainEpoch = 1
suite.stateDB = suite.app.EvmKeeper.CommitStateDB.WithContext(suite.ctx)

privkey, err := ethsecp256k1.GenerateKey()
Expand Down

0 comments on commit 5041f8d

Please sign in to comment.