From 2796f55b023e4fa71e96a695d324dd8409d7ced3 Mon Sep 17 00:00:00 2001 From: noot <36753753+noot@users.noreply.github.com> Date: Mon, 16 Nov 2020 11:11:15 -0500 Subject: [PATCH] deps: update go-ethereum to v1.9.24 (#594) * update to go-ethereum v1.9.24 * go mod tidy * add YoloV2 to chain config * add accessList and implement csdb accessList funcs * cleanup * access list tests * changelog * add stateDB test * test Copy Co-authored-by: Federico Kunze --- CHANGELOG.md | 4 + go.mod | 2 +- go.sum | 12 +- x/evm/keeper/statedb_test.go | 2 +- x/evm/types/access_list.go | 134 +++++++++++++++++ x/evm/types/access_list_test.go | 242 +++++++++++++++++++++++++++++++ x/evm/types/chain_config.go | 10 +- x/evm/types/chain_config_test.go | 10 +- x/evm/types/journal.go | 33 +++++ x/evm/types/journal_test.go | 6 + x/evm/types/state_transition.go | 2 +- x/evm/types/statedb.go | 39 +++++ x/evm/types/statedb_test.go | 22 ++- 13 files changed, 501 insertions(+), 17 deletions(-) create mode 100644 x/evm/types/access_list.go create mode 100644 x/evm/types/access_list_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 2373a015..555f6150 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,10 @@ corresponding Ethereum API namespace: * (evm) [\#588](https://github.com/cosmos/ethermint/pull/588) The EVM transaction CLI has been removed in favor of the JSON-RPC. +### Improvements + +* (deps) [\#594](https://github.com/cosmos/ethermint/pull/594) Bump go-ethereum version to [v1.9.24](https://github.com/ethereum/go-ethereum/releases/tag/v1.9.24) + ### Bug Fixes * (ante) [\#597](https://github.com/cosmos/ethermint/pull/597) Fix incorrect fee check on `AnteHandler`. diff --git a/go.mod b/go.mod index 00e3baf8..e10abd2c 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/cespare/cp v1.1.1 // indirect github.com/cosmos/cosmos-sdk v0.39.1 github.com/deckarep/golang-set v1.7.1 // indirect - github.com/ethereum/go-ethereum v1.9.21 + github.com/ethereum/go-ethereum v1.9.24 github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 // indirect github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.4.2 diff --git a/go.sum b/go.sum index e5170444..dccf4418 100644 --- a/go.sum +++ b/go.sum @@ -164,6 +164,7 @@ github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498/go.mod h1:Mw6PkjjMXWbT github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dvsekhvalnov/jose2go v0.0.0-20180829124132-7f401d37b68a h1:mq+R6XEM6lJX5VlLyZIrUSP8tSuJp82xTK89hvBwJbU= github.com/dvsekhvalnov/jose2go v0.0.0-20180829124132-7f401d37b68a/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM= +github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= @@ -178,8 +179,8 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ethereum/go-ethereum v1.9.5/go.mod h1:PwpWDrCLZrV+tfrhqqF6kPknbISMHaJv9Ln3kPCZLwY= -github.com/ethereum/go-ethereum v1.9.21 h1:8qRlhzrItnmUGdVlBzZLI2Tb46S0RdSNjFwICo781ws= -github.com/ethereum/go-ethereum v1.9.21/go.mod h1:RXAVzbGrSGmDkDnHymruTAIEjUR3E4TX0EOpaj702sI= +github.com/ethereum/go-ethereum v1.9.24 h1:6AK+ORt3EMDO+FTjzXy/AQwHMbu52J2nYHIjyQX9azQ= +github.com/ethereum/go-ethereum v1.9.24/go.mod h1:JIfVb6esrqALTExdz9hRYvrP0xBDf6wCncIu1hNwHpM= github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ= github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= @@ -264,6 +265,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa h1:Q75Upo5UN4JbPFURXZ8nLKYUvF85dyFRop/vQ0Rv+64= +github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -703,6 +706,7 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= @@ -716,10 +720,12 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -834,7 +840,9 @@ golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd h1:hHkvGJK23seRCflePJnVa9IMv8fsuavSCWKd11kDQFs= golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/x/evm/keeper/statedb_test.go b/x/evm/keeper/statedb_test.go index 1096a063..ed2fbf5f 100644 --- a/x/evm/keeper/statedb_test.go +++ b/x/evm/keeper/statedb_test.go @@ -62,7 +62,7 @@ func (suite *KeeperTestSuite) TestBloomFilter() { } else { // get logs bloom from the log bloomInt := ethtypes.LogsBloom(logs) - bloomFilter := ethtypes.BytesToBloom(bloomInt.Bytes()) + bloomFilter := ethtypes.BytesToBloom(bloomInt) suite.Require().True(ethtypes.BloomLookup(bloomFilter, contractAddress), tc.name) suite.Require().False(ethtypes.BloomLookup(bloomFilter, ethcmn.BigToAddress(big.NewInt(2))), tc.name) } diff --git a/x/evm/types/access_list.go b/x/evm/types/access_list.go new file mode 100644 index 00000000..8d7cf283 --- /dev/null +++ b/x/evm/types/access_list.go @@ -0,0 +1,134 @@ +package types + +import ( + "github.com/ethereum/go-ethereum/common" +) + +// accessList is copied from go-ethereum +// https://github.com/ethereum/go-ethereum/blob/cf856ea1ad96ac39ea477087822479b63417036a/core/state/access_list.go#L23 +type accessList struct { + addresses map[common.Address]int + slots []map[common.Hash]struct{} +} + +// ContainsAddress returns true if the address is in the access list. +func (al *accessList) ContainsAddress(address common.Address) bool { + _, ok := al.addresses[address] + return ok +} + +// Contains checks if a slot within an account is present in the access list, returning +// separate flags for the presence of the account and the slot respectively. +func (al *accessList) Contains(address common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) { + idx, ok := al.addresses[address] + if !ok { + // no such address (and hence zero slots) + return false, false + } + if idx == -1 { + // address yes, but no slots + return true, false + } + + if idx >= len(al.slots) { + // return in case of out-of-range + return true, false + } + + _, slotPresent = al.slots[idx][slot] + return true, slotPresent +} + +// newAccessList creates a new accessList. +func newAccessList() *accessList { + return &accessList{ + addresses: make(map[common.Address]int), + } +} + +// Copy creates an independent copy of an accessList. +func (al *accessList) Copy() *accessList { + cp := newAccessList() + for k, v := range al.addresses { + cp.addresses[k] = v + } + cp.slots = make([]map[common.Hash]struct{}, len(al.slots)) + for i, slotMap := range al.slots { + newSlotmap := make(map[common.Hash]struct{}, len(slotMap)) + for k := range slotMap { + newSlotmap[k] = struct{}{} + } + cp.slots[i] = newSlotmap + } + return cp +} + +// AddAddress adds an address to the access list, and returns 'true' if the operation +// caused a change (addr was not previously in the list). +func (al *accessList) AddAddress(address common.Address) bool { + if _, present := al.addresses[address]; present { + return false + } + al.addresses[address] = -1 + return true +} + +// AddSlot adds the specified (addr, slot) combo to the access list. +// Return values are: +// - address added +// - slot added +// For any 'true' value returned, a corresponding journal entry must be made. +func (al *accessList) AddSlot(address common.Address, slot common.Hash) (addrChange bool, slotChange bool) { + idx, addrPresent := al.addresses[address] + if !addrPresent || idx == -1 { + // Address not present, or addr present but no slots there + al.addresses[address] = len(al.slots) + slotmap := map[common.Hash]struct{}{slot: {}} + al.slots = append(al.slots, slotmap) + return !addrPresent, true + } + + if idx >= len(al.slots) { + // return in case of out-of-range + return false, false + } + + // There is already an (address,slot) mapping + slotmap := al.slots[idx] + if _, ok := slotmap[slot]; !ok { + slotmap[slot] = struct{}{} + // Journal add slot change + return false, true + } + // No changes required + return false, false +} + +// DeleteSlot removes an (address, slot)-tuple from the access list. +// This operation needs to be performed in the same order as the addition happened. +// This method is meant to be used by the journal, which maintains ordering of +// operations. +func (al *accessList) DeleteSlot(address common.Address, slot common.Hash) { + idx, addrOk := al.addresses[address] + // There are two ways this can fail + if !addrOk { + panic("reverting slot change, address not present in list") + } + slotmap := al.slots[idx] + delete(slotmap, slot) + // If that was the last (first) slot, remove it + // Since additions and rollbacks are always performed in order, + // we can delete the item without worrying about screwing up later indices + if len(slotmap) == 0 { + al.slots = al.slots[:idx] + al.addresses[address] = -1 + } +} + +// DeleteAddress removes an address from the access list. This operation +// needs to be performed in the same order as the addition happened. +// This method is meant to be used by the journal, which maintains ordering of +// operations. +func (al *accessList) DeleteAddress(address common.Address) { + delete(al.addresses, address) +} diff --git a/x/evm/types/access_list_test.go b/x/evm/types/access_list_test.go new file mode 100644 index 00000000..700c94fc --- /dev/null +++ b/x/evm/types/access_list_test.go @@ -0,0 +1,242 @@ +package types + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/suite" + + ethcmn "github.com/ethereum/go-ethereum/common" + + "github.com/cosmos/ethermint/crypto/ethsecp256k1" +) + +type AccessListTestSuite struct { + suite.Suite + + address ethcmn.Address + accessList *accessList +} + +func (suite *AccessListTestSuite) SetupTest() { + privkey, err := ethsecp256k1.GenerateKey() + suite.Require().NoError(err) + + suite.address = ethcmn.BytesToAddress(privkey.PubKey().Address().Bytes()) + suite.accessList = newAccessList() + suite.accessList.addresses[suite.address] = 1 +} + +func TestAccessListTestSuite(t *testing.T) { + suite.Run(t, new(AccessListTestSuite)) +} + +func (suite *AccessListTestSuite) TestContainsAddress() { + found := suite.accessList.ContainsAddress(suite.address) + suite.Require().True(found) +} + +func (suite *AccessListTestSuite) TestContains() { + testCases := []struct { + name string + malleate func() + expAddrPresent bool + expSlotPresent bool + }{ + {"out of range", func() {}, true, false}, + { + "address, no slots", + func() { + suite.accessList.addresses[suite.address] = -1 + }, true, false, + }, + { + "no address, no slots", + func() { + delete(suite.accessList.addresses, suite.address) + }, false, false, + }, + { + "address, slot not present", + func() { + suite.accessList.addresses[suite.address] = 0 + suite.accessList.slots = make([]map[ethcmn.Hash]struct{}, 1) + }, true, false, + }, + { + "address, slots", + func() { + suite.accessList.addresses[suite.address] = 0 + suite.accessList.slots = make([]map[ethcmn.Hash]struct{}, 1) + suite.accessList.slots[0] = make(map[ethcmn.Hash]struct{}) + suite.accessList.slots[0][ethcmn.Hash{}] = struct{}{} + }, true, true, + }, + } + + for _, tc := range testCases { + tc.malleate() + + addrPresent, slotPresent := suite.accessList.Contains(suite.address, ethcmn.Hash{}) + + suite.Require().Equal(tc.expAddrPresent, addrPresent, tc.name) + suite.Require().Equal(tc.expSlotPresent, slotPresent, tc.name) + } +} + +func (suite *AccessListTestSuite) TestCopy() { + expAccessList := newAccessList() + + testCases := []struct { + name string + malleate func() + }{ + {"empty", func() { + expAccessList.slots = make([]map[ethcmn.Hash]struct{}, 0) + }}, + { + "single address", func() { + expAccessList = newAccessList() + expAccessList.slots = make([]map[ethcmn.Hash]struct{}, 0) + expAccessList.addresses[suite.address] = -1 + }, + }, + { + "single address, single slot", + func() { + expAccessList = newAccessList() + expAccessList.addresses[suite.address] = 0 + expAccessList.slots = make([]map[ethcmn.Hash]struct{}, 1) + expAccessList.slots[0] = make(map[ethcmn.Hash]struct{}) + expAccessList.slots[0][ethcmn.Hash{}] = struct{}{} + }, + }, + { + "multiple addresses, single slot each", + func() { + expAccessList = newAccessList() + expAccessList.slots = make([]map[ethcmn.Hash]struct{}, 10) + for i := 0; i < 10; i++ { + expAccessList.addresses[ethcmn.BytesToAddress([]byte(fmt.Sprintf("%d", i)))] = i + expAccessList.slots[i] = make(map[ethcmn.Hash]struct{}) + expAccessList.slots[i][ethcmn.BytesToHash([]byte(fmt.Sprintf("%d", i)))] = struct{}{} + } + }, + }, + { + "multiple addresses, multiple slots each", + func() { + expAccessList = newAccessList() + expAccessList.slots = make([]map[ethcmn.Hash]struct{}, 10) + for i := 0; i < 10; i++ { + expAccessList.addresses[ethcmn.BytesToAddress([]byte(fmt.Sprintf("%d", i)))] = i + expAccessList.slots[i] = make(map[ethcmn.Hash]struct{}) + for j := 0; j < 10; j++ { + expAccessList.slots[i][ethcmn.BytesToHash([]byte(fmt.Sprintf("%d-%d", i, j)))] = struct{}{} + } + } + }, + }, + } + + for _, tc := range testCases { + tc.malleate() + + accessList := expAccessList.Copy() + suite.Require().EqualValues(expAccessList, accessList, tc.name) + } +} + +func (suite *AccessListTestSuite) TestAddAddress() { + testCases := []struct { + name string + address ethcmn.Address + ok bool + }{ + {"already present", suite.address, false}, + {"ok", ethcmn.Address{}, true}, + } + + for _, tc := range testCases { + ok := suite.accessList.AddAddress(tc.address) + suite.Require().Equal(tc.ok, ok, tc.name) + } +} + +func (suite *AccessListTestSuite) TestAddSlot() { + testCases := []struct { + name string + malleate func() + expAddrChange bool + expSlotChange bool + }{ + {"out of range", func() {}, false, false}, + { + "address not present added, slot added", + func() { + delete(suite.accessList.addresses, suite.address) + }, true, true, + }, + { + "address present, slot not present added", + func() { + suite.accessList.addresses[suite.address] = 0 + suite.accessList.slots = make([]map[ethcmn.Hash]struct{}, 1) + suite.accessList.slots[0] = make(map[ethcmn.Hash]struct{}) + }, false, true, + }, + { + "address present, slot present", + func() { + suite.accessList.addresses[suite.address] = 0 + suite.accessList.slots = make([]map[ethcmn.Hash]struct{}, 1) + suite.accessList.slots[0] = make(map[ethcmn.Hash]struct{}) + suite.accessList.slots[0][ethcmn.Hash{}] = struct{}{} + }, false, false, + }, + } + + for _, tc := range testCases { + tc.malleate() + + addrChange, slotChange := suite.accessList.AddSlot(suite.address, ethcmn.Hash{}) + suite.Require().Equal(tc.expAddrChange, addrChange, tc.name) + suite.Require().Equal(tc.expSlotChange, slotChange, tc.name) + } +} + +func (suite *AccessListTestSuite) TestDeleteSlot() { + testCases := []struct { + name string + malleate func() + expPanic bool + }{ + {"panics, out of range", func() {}, true}, + {"panics, address not found", func() { + delete(suite.accessList.addresses, suite.address) + }, true}, + { + "single slot present", + func() { + suite.accessList.addresses[suite.address] = 0 + suite.accessList.slots = make([]map[ethcmn.Hash]struct{}, 1) + suite.accessList.slots[0] = make(map[ethcmn.Hash]struct{}) + suite.accessList.slots[0][ethcmn.Hash{}] = struct{}{} + }, false, + }, + } + + for _, tc := range testCases { + tc.malleate() + + if tc.expPanic { + suite.Require().Panics(func() { + suite.accessList.DeleteSlot(suite.address, ethcmn.Hash{}) + }, tc.name) + } else { + suite.Require().NotPanics(func() { + suite.accessList.DeleteSlot(suite.address, ethcmn.Hash{}) + }, tc.name) + } + } +} diff --git a/x/evm/types/chain_config.go b/x/evm/types/chain_config.go index a923bc97..e0f7e92b 100644 --- a/x/evm/types/chain_config.go +++ b/x/evm/types/chain_config.go @@ -40,7 +40,7 @@ type ChainConfig struct { IstanbulBlock sdk.Int `json:"istanbul_block" yaml:"istanbul_block"` // Istanbul switch block (< 0 no fork, 0 = already on istanbul) MuirGlacierBlock sdk.Int `json:"muir_glacier_block" yaml:"muir_glacier_block"` // Eip-2384 (bomb delay) switch block (< 0 no fork, 0 = already activated) - YoloV1Block sdk.Int `json:"yoloV1_block" yaml:"yoloV1_block"` // YOLO v1: https://github.com/ethereum/EIPs/pull/2657 (Ephemeral testnet) + YoloV2Block sdk.Int `json:"yoloV2_block" yaml:"yoloV2_block"` // YOLO v1: https://github.com/ethereum/EIPs/pull/2657 (Ephemeral testnet) EWASMBlock sdk.Int `json:"ewasm_block" yaml:"ewasm_block"` // EWASM switch block (< 0 no fork, 0 = already activated) } @@ -61,7 +61,7 @@ func (cc ChainConfig) EthereumConfig(chainID *big.Int) *params.ChainConfig { PetersburgBlock: getBlockValue(cc.PetersburgBlock), IstanbulBlock: getBlockValue(cc.IstanbulBlock), MuirGlacierBlock: getBlockValue(cc.MuirGlacierBlock), - YoloV1Block: getBlockValue(cc.YoloV1Block), + YoloV2Block: getBlockValue(cc.YoloV2Block), EWASMBlock: getBlockValue(cc.EWASMBlock), } } @@ -87,7 +87,7 @@ func DefaultChainConfig() ChainConfig { PetersburgBlock: sdk.ZeroInt(), IstanbulBlock: sdk.NewInt(-1), MuirGlacierBlock: sdk.NewInt(-1), - YoloV1Block: sdk.NewInt(-1), + YoloV2Block: sdk.NewInt(-1), EWASMBlock: sdk.NewInt(-1), } } @@ -136,8 +136,8 @@ func (cc ChainConfig) Validate() error { if err := validateBlock(cc.MuirGlacierBlock); err != nil { return sdkerrors.Wrap(err, "muirGlacierBlock") } - if err := validateBlock(cc.YoloV1Block); err != nil { - return sdkerrors.Wrap(err, "yoloV1Block") + if err := validateBlock(cc.YoloV2Block); err != nil { + return sdkerrors.Wrap(err, "yoloV2Block") } if err := validateBlock(cc.EWASMBlock); err != nil { return sdkerrors.Wrap(err, "eWASMBlock") diff --git a/x/evm/types/chain_config_test.go b/x/evm/types/chain_config_test.go index 00b6d948..1fdbcf87 100644 --- a/x/evm/types/chain_config_test.go +++ b/x/evm/types/chain_config_test.go @@ -33,7 +33,7 @@ func TestChainConfigValidate(t *testing.T) { PetersburgBlock: sdk.OneInt(), IstanbulBlock: sdk.OneInt(), MuirGlacierBlock: sdk.OneInt(), - YoloV1Block: sdk.OneInt(), + YoloV2Block: sdk.OneInt(), EWASMBlock: sdk.OneInt(), }, false, @@ -176,7 +176,7 @@ func TestChainConfigValidate(t *testing.T) { true, }, { - "invalid YoloV1Block", + "invalid YoloV2Block", ChainConfig{ HomesteadBlock: sdk.OneInt(), DAOForkBlock: sdk.OneInt(), @@ -189,7 +189,7 @@ func TestChainConfigValidate(t *testing.T) { PetersburgBlock: sdk.OneInt(), IstanbulBlock: sdk.OneInt(), MuirGlacierBlock: sdk.OneInt(), - YoloV1Block: sdk.Int{}, + YoloV2Block: sdk.Int{}, }, true, }, @@ -207,7 +207,7 @@ func TestChainConfigValidate(t *testing.T) { PetersburgBlock: sdk.OneInt(), IstanbulBlock: sdk.OneInt(), MuirGlacierBlock: sdk.OneInt(), - YoloV1Block: sdk.OneInt(), + YoloV2Block: sdk.OneInt(), EWASMBlock: sdk.Int{}, }, true, @@ -238,7 +238,7 @@ constantinople_block: "0" petersburg_block: "0" istanbul_block: "-1" muir_glacier_block: "-1" -yoloV1_block: "-1" +yoloV2_block: "-1" ewasm_block: "-1" ` require.Equal(t, configStr, DefaultChainConfig().String()) diff --git a/x/evm/types/journal.go b/x/evm/types/journal.go index b2190759..9a4a8d60 100644 --- a/x/evm/types/journal.go +++ b/x/evm/types/journal.go @@ -186,10 +186,18 @@ type ( // prev bool // prevDirty bool } + accessListAddAccountChange struct { + address *ethcmn.Address + } + accessListAddSlotChange struct { + address *ethcmn.Address + slot *ethcmn.Hash + } ) func (ch createObjectChange) revert(s *CommitStateDB) { delete(s.stateObjectsDirty, *ch.account) + idx, exists := s.addressToObjectIndex[*ch.account] if !exists { // perform no-op @@ -342,3 +350,28 @@ func (ch addPreimageChange) revert(s *CommitStateDB) { func (ch addPreimageChange) dirtied() *ethcmn.Address { return nil } + +func (ch accessListAddAccountChange) revert(s *CommitStateDB) { + /* + One important invariant here, is that whenever a (addr, slot) is added, if the + addr is not already present, the add causes two journal entries: + - one for the address, + - one for the (address,slot) + Therefore, when unrolling the change, we can always blindly delete the + (addr) at this point, since no storage adds can remain when come upon + a single (addr) change. + */ + s.accessList.DeleteAddress(*ch.address) +} + +func (ch accessListAddAccountChange) dirtied() *ethcmn.Address { + return nil +} + +func (ch accessListAddSlotChange) revert(s *CommitStateDB) { + s.accessList.DeleteSlot(*ch.address, *ch.slot) +} + +func (ch accessListAddSlotChange) dirtied() *ethcmn.Address { + return nil +} diff --git a/x/evm/types/journal_test.go b/x/evm/types/journal_test.go index 11c4f89a..a07f6a9b 100644 --- a/x/evm/types/journal_test.go +++ b/x/evm/types/journal_test.go @@ -220,6 +220,12 @@ func (suite *JournalTestSuite) TestJournal_append_revert() { txhash: ethcmn.BytesToHash([]byte("txhash")), }, }, + { + "accessListAddAccountChange", + accessListAddAccountChange{ + address: &suite.address, + }, + }, } var dirtyCount int for i, tc := range testCases { diff --git a/x/evm/types/state_transition.go b/x/evm/types/state_transition.go index 980bf53f..9c203717 100644 --- a/x/evm/types/state_transition.go +++ b/x/evm/types/state_transition.go @@ -159,7 +159,7 @@ func (st StateTransition) TransitionDb(ctx sdk.Context, config ChainConfig) (*Ex return nil, err } - bloomInt = ethtypes.LogsBloom(logs) + bloomInt = big.NewInt(0).SetBytes(ethtypes.LogsBloom(logs)) bloomFilter = ethtypes.BytesToBloom(bloomInt.Bytes()) } diff --git a/x/evm/types/statedb.go b/x/evm/types/statedb.go index 94fa5b8f..688bb7c5 100644 --- a/x/evm/types/statedb.go +++ b/x/evm/types/statedb.go @@ -77,6 +77,9 @@ type CommitStateDB struct { validRevisions []revision nextRevisionID int + // Per-transaction access list + accessList *accessList + // mutex for state deep copying lock sync.Mutex } @@ -100,6 +103,7 @@ func NewCommitStateDB( preimages: []preimageEntry{}, hashToPreimageIndex: make(map[ethcmn.Hash]int), journal: newJournal(), + accessList: newAccessList(), } } @@ -243,6 +247,41 @@ func (csdb *CommitStateDB) SubRefund(gas uint64) { csdb.refund -= gas } +// AddAddressToAccessList adds the given address to the access list +func (csdb *CommitStateDB) AddAddressToAccessList(addr ethcmn.Address) { + if csdb.accessList.AddAddress(addr) { + csdb.journal.append(accessListAddAccountChange{&addr}) + } +} + +// AddSlotToAccessList adds the given (address, slot)-tuple to the access list +func (csdb *CommitStateDB) AddSlotToAccessList(addr ethcmn.Address, slot ethcmn.Hash) { + addrMod, slotMod := csdb.accessList.AddSlot(addr, slot) + if addrMod { + // In practice, this should not happen, since there is no way to enter the + // scope of 'address' without having the 'address' become already added + // to the access list (via call-variant, create, etc). + // Better safe than sorry, though + csdb.journal.append(accessListAddAccountChange{&addr}) + } + if slotMod { + csdb.journal.append(accessListAddSlotChange{ + address: &addr, + slot: &slot, + }) + } +} + +// AddressInAccessList returns true if the given address is in the access list. +func (csdb *CommitStateDB) AddressInAccessList(addr ethcmn.Address) bool { + return csdb.accessList.ContainsAddress(addr) +} + +// SlotInAccessList returns true if the given (address, slot)-tuple is in the access list. +func (csdb *CommitStateDB) SlotInAccessList(addr ethcmn.Address, slot ethcmn.Hash) (bool, bool) { + return csdb.accessList.Contains(addr, slot) +} + // ---------------------------------------------------------------------------- // Getters // ---------------------------------------------------------------------------- diff --git a/x/evm/types/statedb_test.go b/x/evm/types/statedb_test.go index f4418afe..b0ccdeaa 100644 --- a/x/evm/types/statedb_test.go +++ b/x/evm/types/statedb_test.go @@ -113,8 +113,8 @@ func (suite *StateDBTestSuite) TestBloomFilter() { } } else { // get logs bloom from the log - bloomInt := ethtypes.LogsBloom(logs) - bloomFilter := ethtypes.BytesToBloom(bloomInt.Bytes()) + bloomBytes := ethtypes.LogsBloom(logs) + bloomFilter := ethtypes.BytesToBloom(bloomBytes) suite.Require().True(ethtypes.BloomLookup(bloomFilter, contractAddress), tc.name) suite.Require().False(ethtypes.BloomLookup(bloomFilter, ethcmn.BigToAddress(big.NewInt(2))), tc.name) } @@ -695,3 +695,21 @@ func (suite *StateDBTestSuite) TestCommitStateDB_ForEachStorage() { storage = types.Storage{} } } + +func (suite *StateDBTestSuite) TestCommitStateDB_AccessList() { + addr := ethcmn.Address([20]byte{77}) + hash := ethcmn.Hash([32]byte{99}) + + suite.Require().False(suite.stateDB.AddressInAccessList(addr)) + + suite.stateDB.AddAddressToAccessList(addr) + suite.Require().True(suite.stateDB.AddressInAccessList(addr)) + addrIn, slotIn := suite.stateDB.SlotInAccessList(addr, hash) + suite.Require().True(addrIn) + suite.Require().False(slotIn) + + suite.stateDB.AddSlotToAccessList(addr, hash) + addrIn, slotIn = suite.stateDB.SlotInAccessList(addr, hash) + suite.Require().True(addrIn) + suite.Require().True(slotIn) +}