Skip to content

Commit f164197

Browse files
yihuangAlex | Interchain Labsvladjdk
authored
Problem: eip-2935 is not implemented (#407)
* Problem: eip-2935 is not implemented Solution: - adapt the implementation from geth * change BLOCKHASH opcode to query contract storage * commit statedb * fix panic * temp * just emulate the contract behavior with native code * fix test * fix lint * fix lint * revert unneeded changes * fix test * revert * only set contract storage if it's deployed * fix build * add history serve window parameter * cleanup * fix lint --------- Co-authored-by: Alex | Interchain Labs <[email protected]> Co-authored-by: Vlad J <[email protected]>
1 parent aa9a834 commit f164197

File tree

13 files changed

+554
-452
lines changed

13 files changed

+554
-452
lines changed

api/cosmos/evm/vm/v1/evm.pulsar.go

Lines changed: 325 additions & 266 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

proto/cosmos/evm/vm/v1/evm.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ message Params {
3535
// active_static_precompiles defines the slice of hex addresses of the
3636
// precompiled contracts that are active
3737
repeated string active_static_precompiles = 9;
38+
uint64 history_serve_window = 10;
3839
}
3940

4041
// AccessControl defines the permission policy of the EVM

tests/integration/x/vm/test_genesis.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ func (s *GenesisTestSuite) TestExportGenesis() {
222222

223223
genState := vm.ExportGenesis(s.network.GetContext(), s.network.App.GetEVMKeeper())
224224
// Exported accounts 4 default preinstalls
225-
s.Require().Len(genState.Accounts, 7)
225+
s.Require().Len(genState.Accounts, 8)
226226

227227
addrs := make([]string, len(genState.Accounts))
228228
for i, acct := range genState.Accounts {

tests/integration/x/vm/test_iterate_contracts.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func TestIterateContracts(t *testing.T, create network.CreateEvmApp, options ...
6767
return false
6868
})
6969

70-
require.Len(t, foundAddrs, 6, "expected 6 contracts to be found when iterating (4 preinstalled + 2 deployed)")
70+
require.Len(t, foundAddrs, 7, "expected 7 contracts to be found when iterating (5 preinstalled + 2 deployed)")
7171
require.Contains(t, foundAddrs, contractAddr, "expected contract 1 to be found when iterating")
7272
require.Contains(t, foundAddrs, contractAddr2, "expected contract 2 to be found when iterating")
7373

tests/integration/x/vm/test_keeper.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"math/big"
66

77
"github.com/ethereum/go-ethereum/common"
8+
ethparams "github.com/ethereum/go-ethereum/params"
89
"github.com/holiman/uint256"
910

1011
"github.com/cosmos/evm/utils"
@@ -89,7 +90,7 @@ func (s *KeeperTestSuite) TestGetAccountStorage() {
8990

9091
storage := s.Network.App.GetEVMKeeper().GetAccountStorage(ctx, address)
9192

92-
if address == contractAddr {
93+
if address == contractAddr || address == ethparams.HistoryStorageAddress {
9394
s.Require().NotEqual(0, len(storage),
9495
"expected account %d to have non-zero amount of storage slots, got %d",
9596
i, len(storage),

tests/integration/x/vm/test_state_transition.go

Lines changed: 12 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212

1313
"github.com/cometbft/cometbft/crypto/tmhash"
1414
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
15-
cmttypes "github.com/cometbft/cometbft/types"
1615

1716
"github.com/cosmos/evm/testutil/config"
1817
"github.com/cosmos/evm/testutil/integration/evm/factory"
@@ -33,7 +32,6 @@ import (
3332
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
3433
consensustypes "github.com/cosmos/cosmos-sdk/x/consensus/types"
3534
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
36-
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
3735
)
3836

3937
func (s *KeeperTestSuite) TestContextSetConsensusParams() {
@@ -81,9 +79,11 @@ func (s *KeeperTestSuite) TestContextSetConsensusParams() {
8179

8280
func (s *KeeperTestSuite) TestGetHashFn() {
8381
s.SetupTest()
84-
header := s.Network.GetContext().BlockHeader()
85-
h, _ := cmttypes.HeaderFromProto(&header)
86-
hash := h.Hash()
82+
s.Require().NoError(s.Network.NextBlock())
83+
ctx := s.Network.GetContext()
84+
height := uint64(ctx.BlockHeight()) //nolint:gosec // G115
85+
headerHash := common.BytesToHash(ctx.HeaderHash())
86+
fmt.Println("get headerHash", height, headerHash)
8787

8888
testCases := []struct {
8989
msg string
@@ -93,7 +93,7 @@ func (s *KeeperTestSuite) TestGetHashFn() {
9393
}{
9494
{
9595
"case 1.1: context hash cached",
96-
uint64(s.Network.GetContext().BlockHeight()), //nolint:gosec // G115
96+
height,
9797
func() sdk.Context {
9898
return s.Network.GetContext().WithHeaderHash(
9999
tmhash.Sum([]byte("header")),
@@ -102,51 +102,22 @@ func (s *KeeperTestSuite) TestGetHashFn() {
102102
common.BytesToHash(tmhash.Sum([]byte("header"))),
103103
},
104104
{
105-
"case 1.2: failed to cast CometBFT header",
106-
uint64(s.Network.GetContext().BlockHeight()), //nolint:gosec // G115
105+
"case 1.2: works for invalid CometBFT header",
106+
height,
107107
func() sdk.Context {
108108
header := tmproto.Header{}
109109
header.Height = s.Network.GetContext().BlockHeight()
110110
return s.Network.GetContext().WithBlockHeader(header)
111111
},
112-
common.Hash{},
113-
},
114-
{
115-
"case 1.3: hash calculated from CometBFT header",
116-
uint64(s.Network.GetContext().BlockHeight()), //nolint:gosec // G115
117-
func() sdk.Context {
118-
return s.Network.GetContext().WithBlockHeader(header)
119-
},
120-
common.BytesToHash(hash),
112+
headerHash,
121113
},
122114
{
123-
"case 2.1: height lower than current one, hist info not found",
124-
1,
115+
"case 2.1: height lower than current one works",
116+
height,
125117
func() sdk.Context {
126118
return s.Network.GetContext().WithBlockHeight(10)
127119
},
128-
common.Hash{},
129-
},
130-
{
131-
"case 2.2: height lower than current one, invalid hist info header",
132-
1,
133-
func() sdk.Context {
134-
s.Require().NoError(s.Network.App.GetStakingKeeper().SetHistoricalInfo(s.Network.GetContext(), 1, &stakingtypes.HistoricalInfo{}))
135-
return s.Network.GetContext().WithBlockHeight(10)
136-
},
137-
common.Hash{},
138-
},
139-
{
140-
"case 2.3: height lower than current one, calculated from hist info header",
141-
1,
142-
func() sdk.Context {
143-
histInfo := &stakingtypes.HistoricalInfo{
144-
Header: header,
145-
}
146-
s.Require().NoError(s.Network.App.GetStakingKeeper().SetHistoricalInfo(s.Network.GetContext(), 1, histInfo))
147-
return s.Network.GetContext().WithBlockHeight(10)
148-
},
149-
common.BytesToHash(hash),
120+
headerHash,
150121
},
151122
{
152123
"case 3: height greater than current one",

testutil/integration/evm/network/abci.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ func (n *IntegrationNetwork) finalizeBlockAndCommit(duration time.Duration, txBy
6060
// This might have to be changed with time if we want to test gas limits
6161
newCtx = newCtx.WithBlockGasMeter(storetypes.NewInfiniteGasMeter())
6262
newCtx = newCtx.WithVoteInfos(req.DecidedLastCommit.GetVotes())
63+
newCtx = newCtx.WithHeaderHash(header.AppHash)
6364
n.ctx = newCtx
6465

6566
// commit changes

x/vm/keeper/abci.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ func (k *Keeper) BeginBlock(ctx sdk.Context) error {
3131
),
3232
})
3333
}
34+
35+
k.SetHeaderHash(ctx)
3436
return nil
3537
}
3638

x/vm/keeper/keeper.go

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
package keeper
22

33
import (
4+
"encoding/binary"
45
"math/big"
56

67
"github.com/ethereum/go-ethereum/common"
78
"github.com/ethereum/go-ethereum/core"
89
"github.com/ethereum/go-ethereum/core/tracing"
910
ethtypes "github.com/ethereum/go-ethereum/core/types"
1011
"github.com/ethereum/go-ethereum/core/vm"
11-
"github.com/ethereum/go-ethereum/params"
12+
ethparams "github.com/ethereum/go-ethereum/params"
1213
"github.com/holiman/uint256"
1314

1415
evmmempool "github.com/cosmos/evm/mempool"
@@ -254,7 +255,7 @@ func (k Keeper) GetAccountStorage(ctx sdk.Context, address common.Address) types
254255
// ----------------------------------------------------------------------------
255256

256257
// Tracer return a default vm.Tracer based on current keeper state
257-
func (k Keeper) Tracer(ctx sdk.Context, msg core.Message, ethCfg *params.ChainConfig) *tracing.Hooks {
258+
func (k Keeper) Tracer(ctx sdk.Context, msg core.Message, ethCfg *ethparams.ChainConfig) *tracing.Hooks {
258259
return types.NewTracer(k.tracer, msg, ethCfg, ctx.BlockHeight(), uint64(ctx.BlockTime().Unix())) //#nosec G115 -- int overflow is not a concern here
259260
}
260261

@@ -401,3 +402,35 @@ func (k *Keeper) SetEvmMempool(evmMempool *evmmempool.ExperimentalEVMMempool) {
401402
func (k Keeper) GetEvmMempool() *evmmempool.ExperimentalEVMMempool {
402403
return k.evmMempool
403404
}
405+
406+
// SetHeaderHash sets current block hash into EIP-2935 compatible storage contract.
407+
func (k Keeper) SetHeaderHash(ctx sdk.Context) {
408+
window := uint64(types.DefaultHistoryServeWindow)
409+
params := k.GetParams(ctx)
410+
if params.HistoryServeWindow > 0 {
411+
window = params.HistoryServeWindow
412+
}
413+
414+
acct := k.GetAccount(ctx, ethparams.HistoryStorageAddress)
415+
if acct != nil && acct.IsContract() {
416+
// set current block hash in the contract storage, compatible with EIP-2935
417+
ringIndex := uint64(ctx.BlockHeight()) % window //nolint:gosec // G115 // won't exceed uint64
418+
var key common.Hash
419+
binary.BigEndian.PutUint64(key[24:], ringIndex)
420+
k.SetState(ctx, ethparams.HistoryStorageAddress, key, ctx.HeaderHash())
421+
}
422+
}
423+
424+
// GetHeaderHash sets block hash into EIP-2935 compatible storage contract.
425+
func (k Keeper) GetHeaderHash(ctx sdk.Context, height uint64) common.Hash {
426+
window := uint64(types.DefaultHistoryServeWindow)
427+
params := k.GetParams(ctx)
428+
if params.HistoryServeWindow > 0 {
429+
window = params.HistoryServeWindow
430+
}
431+
432+
ringIndex := height % window
433+
var key common.Hash
434+
binary.BigEndian.PutUint64(key[24:], ringIndex)
435+
return k.GetState(ctx, ethparams.HistoryStorageAddress, key)
436+
}

x/vm/keeper/state_transition.go

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -112,23 +112,11 @@ func (k Keeper) GetHashFn(ctx sdk.Context) vm.GetHashFunc {
112112
return common.BytesToHash(headerHash)
113113

114114
case ctx.BlockHeight() > h:
115-
// Case 2: if the chain is not the current height we need to retrieve the hash from the store for the
116-
// current chain epoch. This only applies if the current height is greater than the requested height.
117-
histInfo, err := k.stakingKeeper.GetHistoricalInfo(ctx, h)
118-
if err != nil {
119-
k.Logger(ctx).Debug("error while getting historical info", "height", h, "error", err.Error())
120-
return common.Hash{}
121-
}
122-
123-
header, err := cmttypes.HeaderFromProto(&histInfo.Header)
124-
if err != nil {
125-
k.Logger(ctx).Error("failed to cast CometBFT header from proto", "error", err)
126-
return common.Hash{}
127-
}
128-
129-
return common.BytesToHash(header.Hash())
115+
// Case 2: The requested height is historical, query EIP-2935 contract storage for that
116+
// see: https://github.com/cosmos/evm/issues/406
117+
return k.GetHeaderHash(ctx, height)
130118
default:
131-
// Case 3: heights greater than the current one returns an empty hash.
119+
// Case 3: The requested height is greater than the latest one, return empty hash
132120
return common.Hash{}
133121
}
134122
}

0 commit comments

Comments
 (0)