Skip to content

Commit

Permalink
trie, core/state: Nyota EIP-6800 & EIP-4762 spec updates (ethereum#30357
Browse files Browse the repository at this point in the history
)

This PR implements changes related to
[EIP-6800](https://eips.ethereum.org/EIPS/eip-6800) and
[EIP-4762](https://eips.ethereum.org/EIPS/eip-4762) spec updates.

A TL;DR of the changes is that `Version`, `Balance`, `Nonce` and
`CodeSize` are encoded in a single leaf named `BasicData`. For more
details, see the [_Header Values_ table in
EIP-6800](https://eips.ethereum.org/EIPS/eip-6800#header-values).

The motivation for this was simplifying access event patterns, reducing
code complexity, and, as a side effect, saving gas since fewer leaf
nodes must be accessed.

---------

Co-authored-by: Guillaume Ballet <[email protected]>
Co-authored-by: Felix Lange <[email protected]>
  • Loading branch information
3 people authored and zfy0701 committed Dec 3, 2024
1 parent ff0103a commit c111fcd
Show file tree
Hide file tree
Showing 13 changed files with 140 additions and 228 deletions.
2 changes: 1 addition & 1 deletion core/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ func TestVerkleGenesisCommit(t *testing.T) {
},
}

expected := common.FromHex("14398d42be3394ff8d50681816a4b7bf8d8283306f577faba2d5bc57498de23b")
expected := common.FromHex("4a83dc39eb688dbcfaf581d60e82de18f875e38786ebce5833342011d6fef37b")
got := genesis.ToBlock().Root().Bytes()
if !bytes.Equal(got, expected) {
t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got)
Expand Down
69 changes: 14 additions & 55 deletions core/state/access_events.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,8 @@ func (ae *AccessEvents) Copy() *AccessEvents {
// member fields of an account.
func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool) uint64 {
var gas uint64
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, isWrite)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, isWrite)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, isWrite)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, isWrite)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, isWrite)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite)
return gas
}

Expand All @@ -107,50 +104,39 @@ func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool) uint64 {
// call to that account.
func (ae *AccessEvents) MessageCallGas(destination common.Address) uint64 {
var gas uint64
gas += ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.VersionLeafKey, false)
gas += ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.CodeSizeLeafKey, false)
gas += ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.BasicDataLeafKey, false)
return gas
}

// ValueTransferGas returns the gas to be charged for each of the currently
// cold balance member fields of the caller and the callee accounts.
func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address) uint64 {
var gas uint64
gas += ae.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BalanceLeafKey, true)
gas += ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, true)
gas += ae.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BasicDataLeafKey, true)
gas += ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, true)
return gas
}

// ContractCreateInitGas returns the access gas costs for the initialization of
// a contract creation.
func (ae *AccessEvents) ContractCreateInitGas(addr common.Address, createSendsValue bool) uint64 {
var gas uint64
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, true)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, true)
if createSendsValue {
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, true)
}
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, true)
return gas
}

// AddTxOrigin adds the member fields of the sender account to the access event list,
// so that cold accesses are not charged, since they are covered by the 21000 gas.
func (ae *AccessEvents) AddTxOrigin(originAddr common.Address) {
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.VersionLeafKey, false)
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.BalanceLeafKey, true)
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.NonceLeafKey, true)
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeKeccakLeafKey, false)
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeSizeLeafKey, false)
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.BasicDataLeafKey, true)
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeHashLeafKey, false)
}

// AddTxDestination adds the member fields of the sender account to the access event list,
// so that cold accesses are not charged, since they are covered by the 21000 gas.
func (ae *AccessEvents) AddTxDestination(addr common.Address, sendsValue bool) {
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, false)
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, sendsValue)
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, false)
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, false)
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, false)
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, sendsValue)
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false)
}

// SlotGas returns the amount of gas to be charged for a cold storage access.
Expand Down Expand Up @@ -275,39 +261,12 @@ func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC,
return statelessGasCharged
}

// VersionGas adds the account's version to the accessed data, and returns the
// BasicDataGas adds the account's basic data to the accessed data, and returns the
// amount of gas that it costs.
// Note that an access in write mode implies an access in read mode, whereas an
// access in read mode does not imply an access in write mode.
func (ae *AccessEvents) VersionGas(addr common.Address, isWrite bool) uint64 {
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, isWrite)
}

// BalanceGas adds the account's balance to the accessed data, and returns the
// amount of gas that it costs.
// in write mode. If false, the charged gas corresponds to an access in read mode.
// Note that an access in write mode implies an access in read mode, whereas an access in
// read mode does not imply an access in write mode.
func (ae *AccessEvents) BalanceGas(addr common.Address, isWrite bool) uint64 {
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, isWrite)
}

// NonceGas adds the account's nonce to the accessed data, and returns the
// amount of gas that it costs.
// in write mode. If false, the charged gas corresponds to an access in read mode.
// Note that an access in write mode implies an access in read mode, whereas an access in
// read mode does not imply an access in write mode.
func (ae *AccessEvents) NonceGas(addr common.Address, isWrite bool) uint64 {
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, isWrite)
}

// CodeSizeGas adds the account's code size to the accessed data, and returns the
// amount of gas that it costs.
// in write mode. If false, the charged gas corresponds to an access in read mode.
// Note that an access in write mode implies an access in read mode, whereas an access in
// read mode does not imply an access in write mode.
func (ae *AccessEvents) CodeSizeGas(addr common.Address, isWrite bool) uint64 {
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, isWrite)
func (ae *AccessEvents) BasicDataGas(addr common.Address, isWrite bool) uint64 {
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite)
}

// CodeHashGas adds the account's code hash to the accessed data, and returns the
Expand All @@ -316,5 +275,5 @@ func (ae *AccessEvents) CodeSizeGas(addr common.Address, isWrite bool) uint64 {
// Note that an access in write mode implies an access in read mode, whereas an access in
// read mode does not imply an access in write mode.
func (ae *AccessEvents) CodeHashGas(addr common.Address, isWrite bool) uint64 {
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, isWrite)
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite)
}
36 changes: 12 additions & 24 deletions core/state/access_events_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,55 +40,43 @@ func TestAccountHeaderGas(t *testing.T) {
ae := NewAccessEvents(utils.NewPointCache(1024))

// Check cold read cost
gas := ae.VersionGas(testAddr, false)
gas := ae.BasicDataGas(testAddr, false)
if want := params.WitnessBranchReadCost + params.WitnessChunkReadCost; gas != want {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
}

// Check warm read cost
gas = ae.VersionGas(testAddr, false)
gas = ae.BasicDataGas(testAddr, false)
if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
}

// Check cold read costs in the same group no longer incur the branch read cost
gas = ae.BalanceGas(testAddr, false)
if gas != params.WitnessChunkReadCost {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
}
gas = ae.NonceGas(testAddr, false)
if gas != params.WitnessChunkReadCost {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
}
gas = ae.CodeSizeGas(testAddr, false)
if gas != params.WitnessChunkReadCost {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
}
gas = ae.CodeHashGas(testAddr, false)
if gas != params.WitnessChunkReadCost {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
}

// Check cold write cost
gas = ae.VersionGas(testAddr, true)
gas = ae.BasicDataGas(testAddr, true)
if want := params.WitnessBranchWriteCost + params.WitnessChunkWriteCost; gas != want {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
}

// Check warm write cost
gas = ae.VersionGas(testAddr, true)
gas = ae.BasicDataGas(testAddr, true)
if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
}

// Check a write without a read charges both read and write costs
gas = ae.BalanceGas(testAddr2, true)
gas = ae.BasicDataGas(testAddr2, true)
if want := params.WitnessBranchReadCost + params.WitnessBranchWriteCost + params.WitnessChunkWriteCost + params.WitnessChunkReadCost; gas != want {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
}

// Check that a write followed by a read charges nothing
gas = ae.BalanceGas(testAddr2, false)
gas = ae.BasicDataGas(testAddr2, false)
if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
}
Expand All @@ -113,7 +101,7 @@ func TestContractCreateInitGas(t *testing.T) {

// Check cold read cost, without a value
gas := ae.ContractCreateInitGas(testAddr, false)
if want := params.WitnessBranchWriteCost + params.WitnessBranchReadCost + params.WitnessChunkWriteCost*2 + params.WitnessChunkReadCost*2; gas != want {
if want := params.WitnessBranchWriteCost + params.WitnessBranchReadCost + params.WitnessChunkWriteCost + params.WitnessChunkReadCost; gas != want {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
}

Expand All @@ -131,17 +119,17 @@ func TestMessageCallGas(t *testing.T) {

// Check cold read cost, without a value
gas := ae.MessageCallGas(testAddr)
if want := params.WitnessBranchReadCost + params.WitnessChunkReadCost*2; gas != want {
if want := params.WitnessBranchReadCost + params.WitnessChunkReadCost; gas != want {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
}

// Check that reading the version and code size of the same account does not incur the branch read cost
gas = ae.VersionGas(testAddr, false)
// Check that reading the basic data and code hash of the same account does not incur the branch read cost
gas = ae.BasicDataGas(testAddr, false)
if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
}
gas = ae.CodeSizeGas(testAddr, false)
if gas != 0 {
gas = ae.CodeHashGas(testAddr, false)
if gas != params.WitnessChunkReadCost {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
}

Expand Down
2 changes: 1 addition & 1 deletion core/state/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ type Trie interface {
// UpdateAccount abstracts an account write to the trie. It encodes the
// provided account object with associated algorithm and then updates it
// in the trie with provided address.
UpdateAccount(address common.Address, account *types.StateAccount) error
UpdateAccount(address common.Address, account *types.StateAccount, codeLen int) error

// UpdateStorage associates key with value in the trie. If value has length zero,
// any existing value is deleted from the trie. The value bytes must not be modified
Expand Down
2 changes: 1 addition & 1 deletion core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ func (s *StateDB) GetTransientState(addr common.Address, key common.Hash) common
func (s *StateDB) updateStateObject(obj *stateObject) {
// Encode the account and update the account trie
addr := obj.Address()
if err := s.trie.UpdateAccount(addr, &obj.data); err != nil {
if err := s.trie.UpdateAccount(addr, &obj.data, len(obj.code)); err != nil {
s.setError(fmt.Errorf("updateStateObject (%x) error: %v", addr[:], err))
}
if obj.dirtyCode {
Expand Down
31 changes: 29 additions & 2 deletions core/state_processor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -486,8 +486,35 @@ func TestProcessVerkle(t *testing.T) {

txCost1 := params.TxGas
txCost2 := params.TxGas
contractCreationCost := intrinsicContractCreationGas + uint64(2039 /* execution costs */)
codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + uint64(57444 /* execution costs */)
contractCreationCost := intrinsicContractCreationGas +
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* creation with value */
739 /* execution costs */
codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas +
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (tx) */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (CREATE at pc=0x20) */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* write code hash */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #0 */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #1 */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #2 */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #3 */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #4 */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #5 */
params.WitnessChunkReadCost + /* SLOAD in constructor */
params.WitnessChunkWriteCost + /* SSTORE in constructor */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (CREATE at PC=0x121) */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* write code hash */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #0 */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #1 */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #2 */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #3 */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #4 */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #5 */
params.WitnessChunkReadCost + /* SLOAD in constructor */
params.WitnessChunkWriteCost + /* SSTORE in constructor */
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* write code hash for tx creation */
15*(params.WitnessChunkReadCost+params.WitnessChunkWriteCost) + /* code chunks #0..#14 */
4844 /* execution costs */
blockGasUsagesExpected := []uint64{
txCost1*2 + txCost2,
txCost1*2 + txCost2 + contractCreationCost + codeWithExtCodeCopyGas,
Expand Down
2 changes: 1 addition & 1 deletion core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {

// add the coinbase to the witness iff the fee is greater than 0
if rules.IsEIP4762 && fee.Sign() != 0 {
st.evm.AccessEvents.BalanceGas(st.evm.Context.Coinbase, true)
st.evm.AccessEvents.AddAccount(st.evm.Context.Coinbase, true)
}
}

Expand Down
18 changes: 7 additions & 11 deletions core/vm/operations_verkle.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor

func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
address := stack.peek().Bytes20()
gas := evm.AccessEvents.BalanceGas(address, false)
gas := evm.AccessEvents.BasicDataGas(address, false)
if gas == 0 {
gas = params.WarmStorageReadCostEIP2929
}
Expand All @@ -52,8 +52,7 @@ func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
if _, isPrecompile := evm.precompile(address); isPrecompile {
return 0, nil
}
gas := evm.AccessEvents.VersionGas(address, false)
gas += evm.AccessEvents.CodeSizeGas(address, false)
gas := evm.AccessEvents.BasicDataGas(address, false)
if gas == 0 {
gas = params.WarmStorageReadCostEIP2929
}
Expand Down Expand Up @@ -102,17 +101,15 @@ func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Mem
return 0, nil
}
contractAddr := contract.Address()
statelessGas := evm.AccessEvents.VersionGas(contractAddr, false)
statelessGas += evm.AccessEvents.CodeSizeGas(contractAddr, false)
statelessGas += evm.AccessEvents.BalanceGas(contractAddr, false)
statelessGas := evm.AccessEvents.BasicDataGas(contractAddr, false)
if contractAddr != beneficiaryAddr {
statelessGas += evm.AccessEvents.BalanceGas(beneficiaryAddr, false)
statelessGas += evm.AccessEvents.BasicDataGas(beneficiaryAddr, false)
}
// Charge write costs if it transfers value
if evm.StateDB.GetBalance(contractAddr).Sign() != 0 {
statelessGas += evm.AccessEvents.BalanceGas(contractAddr, true)
statelessGas += evm.AccessEvents.BasicDataGas(contractAddr, true)
if contractAddr != beneficiaryAddr {
statelessGas += evm.AccessEvents.BalanceGas(beneficiaryAddr, true)
statelessGas += evm.AccessEvents.BasicDataGas(beneficiaryAddr, true)
}
}
return statelessGas, nil
Expand Down Expand Up @@ -145,8 +142,7 @@ func gasExtCodeCopyEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memo
return 0, err
}
addr := common.Address(stack.peek().Bytes20())
wgas := evm.AccessEvents.VersionGas(addr, false)
wgas += evm.AccessEvents.CodeSizeGas(addr, false)
wgas := evm.AccessEvents.BasicDataGas(addr, false)
if wgas == 0 {
wgas = params.WarmStorageReadCostEIP2929
}
Expand Down
2 changes: 1 addition & 1 deletion trie/secure_trie.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ func (t *StateTrie) UpdateStorage(_ common.Address, key, value []byte) error {
}

// UpdateAccount will abstract the write of an account to the secure trie.
func (t *StateTrie) UpdateAccount(address common.Address, acc *types.StateAccount) error {
func (t *StateTrie) UpdateAccount(address common.Address, acc *types.StateAccount, _ int) error {
hk := t.hashKey(address.Bytes())
data, err := rlp.EncodeToBytes(acc)
if err != nil {
Expand Down
Loading

0 comments on commit c111fcd

Please sign in to comment.