trie, core/state: Nyota EIP-6800 & EIP-4762 spec updates (#30357)

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 <3272758+gballet@users.noreply.github.com>
Co-authored-by: Felix Lange <fjl@twurst.com>
pull/30373/head
Ignacio Hagopian 3 months ago committed by GitHub
parent e9467eec1c
commit ab3ee99ca9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      core/genesis_test.go
  2. 69
      core/state/access_events.go
  3. 36
      core/state/access_events_test.go
  4. 2
      core/state/database.go
  5. 2
      core/state/statedb.go
  6. 31
      core/state_processor_test.go
  7. 2
      core/state_transition.go
  8. 18
      core/vm/operations_verkle.go
  9. 2
      trie/secure_trie.go
  10. 80
      trie/utils/verkle.go
  11. 21
      trie/utils/verkle_test.go
  12. 87
      trie/verkle.go
  13. 16
      trie/verkle_test.go

@ -294,7 +294,7 @@ func TestVerkleGenesisCommit(t *testing.T) {
}, },
} }
expected := common.FromHex("14398d42be3394ff8d50681816a4b7bf8d8283306f577faba2d5bc57498de23b") expected := common.FromHex("4a83dc39eb688dbcfaf581d60e82de18f875e38786ebce5833342011d6fef37b")
got := genesis.ToBlock().Root().Bytes() got := genesis.ToBlock().Root().Bytes()
if !bytes.Equal(got, expected) { if !bytes.Equal(got, expected) {
t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got) t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got)

@ -94,11 +94,8 @@ func (ae *AccessEvents) Copy() *AccessEvents {
// member fields of an account. // member fields of an account.
func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool) uint64 { func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool) uint64 {
var gas uint64 var gas uint64
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, isWrite) gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, isWrite) gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, 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)
return gas return gas
} }
@ -107,8 +104,7 @@ func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool) uint64 {
// call to that account. // call to that account.
func (ae *AccessEvents) MessageCallGas(destination common.Address) uint64 { func (ae *AccessEvents) MessageCallGas(destination common.Address) uint64 {
var gas uint64 var gas uint64
gas += ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.VersionLeafKey, false) gas += ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.BasicDataLeafKey, false)
gas += ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.CodeSizeLeafKey, false)
return gas return gas
} }
@ -116,8 +112,8 @@ func (ae *AccessEvents) MessageCallGas(destination common.Address) uint64 {
// cold balance member fields of the caller and the callee accounts. // cold balance member fields of the caller and the callee accounts.
func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address) uint64 { func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address) uint64 {
var gas uint64 var gas uint64
gas += ae.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BalanceLeafKey, true) gas += ae.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BasicDataLeafKey, true)
gas += ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, true) gas += ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, true)
return gas return gas
} }
@ -125,32 +121,22 @@ func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address)
// a contract creation. // a contract creation.
func (ae *AccessEvents) ContractCreateInitGas(addr common.Address, createSendsValue bool) uint64 { func (ae *AccessEvents) ContractCreateInitGas(addr common.Address, createSendsValue bool) uint64 {
var gas uint64 var gas uint64
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, true) gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, true)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, true)
if createSendsValue {
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, true)
}
return gas return gas
} }
// AddTxOrigin adds the member fields of the sender account to the access event list, // 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. // so that cold accesses are not charged, since they are covered by the 21000 gas.
func (ae *AccessEvents) AddTxOrigin(originAddr common.Address) { func (ae *AccessEvents) AddTxOrigin(originAddr common.Address) {
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.VersionLeafKey, false) ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.BasicDataLeafKey, true)
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.BalanceLeafKey, true) ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeHashLeafKey, false)
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.NonceLeafKey, true)
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeKeccakLeafKey, false)
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeSizeLeafKey, false)
} }
// AddTxDestination adds the member fields of the sender account to the access event list, // 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. // so that cold accesses are not charged, since they are covered by the 21000 gas.
func (ae *AccessEvents) AddTxDestination(addr common.Address, sendsValue bool) { func (ae *AccessEvents) AddTxDestination(addr common.Address, sendsValue bool) {
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, false) ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, sendsValue)
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, sendsValue) ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false)
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, false)
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, false)
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, false)
} }
// SlotGas returns the amount of gas to be charged for a cold storage access. // SlotGas returns the amount of gas to be charged for a cold storage access.
@ -275,39 +261,12 @@ func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC,
return statelessGasCharged 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. // amount of gas that it costs.
// Note that an access in write mode implies an access in read mode, whereas an // 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. // access in read mode does not imply an access in write mode.
func (ae *AccessEvents) VersionGas(addr common.Address, isWrite bool) uint64 { func (ae *AccessEvents) BasicDataGas(addr common.Address, isWrite bool) uint64 {
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, isWrite) return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, 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)
} }
// CodeHashGas adds the account's code hash to the accessed data, and returns the // CodeHashGas adds the account's code hash to the accessed data, and returns the
@ -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 // 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. // read mode does not imply an access in write mode.
func (ae *AccessEvents) CodeHashGas(addr common.Address, isWrite bool) uint64 { 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)
} }

@ -40,55 +40,43 @@ func TestAccountHeaderGas(t *testing.T) {
ae := NewAccessEvents(utils.NewPointCache(1024)) ae := NewAccessEvents(utils.NewPointCache(1024))
// Check cold read cost // Check cold read cost
gas := ae.VersionGas(testAddr, false) gas := ae.BasicDataGas(testAddr, false)
if want := params.WitnessBranchReadCost + params.WitnessChunkReadCost; gas != want { if want := params.WitnessBranchReadCost + params.WitnessChunkReadCost; gas != want {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
} }
// Check warm read cost // Check warm read cost
gas = ae.VersionGas(testAddr, false) gas = ae.BasicDataGas(testAddr, false)
if gas != 0 { if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", 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 // 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) gas = ae.CodeHashGas(testAddr, false)
if gas != params.WitnessChunkReadCost { if gas != params.WitnessChunkReadCost {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost) t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
} }
// Check cold write cost // Check cold write cost
gas = ae.VersionGas(testAddr, true) gas = ae.BasicDataGas(testAddr, true)
if want := params.WitnessBranchWriteCost + params.WitnessChunkWriteCost; gas != want { if want := params.WitnessBranchWriteCost + params.WitnessChunkWriteCost; gas != want {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
} }
// Check warm write cost // Check warm write cost
gas = ae.VersionGas(testAddr, true) gas = ae.BasicDataGas(testAddr, true)
if gas != 0 { if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", 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 // 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 { if want := params.WitnessBranchReadCost + params.WitnessBranchWriteCost + params.WitnessChunkWriteCost + params.WitnessChunkReadCost; gas != want {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
} }
// Check that a write followed by a read charges nothing // Check that a write followed by a read charges nothing
gas = ae.BalanceGas(testAddr2, false) gas = ae.BasicDataGas(testAddr2, false)
if gas != 0 { if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
} }
@ -113,7 +101,7 @@ func TestContractCreateInitGas(t *testing.T) {
// Check cold read cost, without a value // Check cold read cost, without a value
gas := ae.ContractCreateInitGas(testAddr, false) 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) t.Fatalf("incorrect gas computed, got %d, want %d", gas, want)
} }
@ -131,17 +119,17 @@ func TestMessageCallGas(t *testing.T) {
// Check cold read cost, without a value // Check cold read cost, without a value
gas := ae.MessageCallGas(testAddr) 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) 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 // Check that reading the basic data and code hash of the same account does not incur the branch read cost
gas = ae.VersionGas(testAddr, false) gas = ae.BasicDataGas(testAddr, false)
if gas != 0 { if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
} }
gas = ae.CodeSizeGas(testAddr, false) gas = ae.CodeHashGas(testAddr, false)
if gas != 0 { if gas != params.WitnessChunkReadCost {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
} }

@ -94,7 +94,7 @@ type Trie interface {
// UpdateAccount abstracts an account write to the trie. It encodes the // UpdateAccount abstracts an account write to the trie. It encodes the
// provided account object with associated algorithm and then updates it // provided account object with associated algorithm and then updates it
// in the trie with provided address. // 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, // 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 // any existing value is deleted from the trie. The value bytes must not be modified

@ -557,7 +557,7 @@ func (s *StateDB) GetTransientState(addr common.Address, key common.Hash) common
func (s *StateDB) updateStateObject(obj *stateObject) { func (s *StateDB) updateStateObject(obj *stateObject) {
// Encode the account and update the account trie // Encode the account and update the account trie
addr := obj.Address() 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)) s.setError(fmt.Errorf("updateStateObject (%x) error: %v", addr[:], err))
} }
if obj.dirtyCode { if obj.dirtyCode {

@ -486,8 +486,35 @@ func TestProcessVerkle(t *testing.T) {
txCost1 := params.TxGas txCost1 := params.TxGas
txCost2 := params.TxGas txCost2 := params.TxGas
contractCreationCost := intrinsicContractCreationGas + uint64(2039 /* execution costs */) contractCreationCost := intrinsicContractCreationGas +
codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + uint64(57444 /* execution costs */) 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{ blockGasUsagesExpected := []uint64{
txCost1*2 + txCost2, txCost1*2 + txCost2,
txCost1*2 + txCost2 + contractCreationCost + codeWithExtCodeCopyGas, txCost1*2 + txCost2 + contractCreationCost + codeWithExtCodeCopyGas,

@ -470,7 +470,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
// add the coinbase to the witness iff the fee is greater than 0 // add the coinbase to the witness iff the fee is greater than 0
if rules.IsEIP4762 && fee.Sign() != 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)
} }
} }

@ -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) { func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
address := stack.peek().Bytes20() address := stack.peek().Bytes20()
gas := evm.AccessEvents.BalanceGas(address, false) gas := evm.AccessEvents.BasicDataGas(address, false)
if gas == 0 { if gas == 0 {
gas = params.WarmStorageReadCostEIP2929 gas = params.WarmStorageReadCostEIP2929
} }
@ -52,8 +52,7 @@ func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
if _, isPrecompile := evm.precompile(address); isPrecompile { if _, isPrecompile := evm.precompile(address); isPrecompile {
return 0, nil return 0, nil
} }
gas := evm.AccessEvents.VersionGas(address, false) gas := evm.AccessEvents.BasicDataGas(address, false)
gas += evm.AccessEvents.CodeSizeGas(address, false)
if gas == 0 { if gas == 0 {
gas = params.WarmStorageReadCostEIP2929 gas = params.WarmStorageReadCostEIP2929
} }
@ -102,17 +101,15 @@ func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Mem
return 0, nil return 0, nil
} }
contractAddr := contract.Address() contractAddr := contract.Address()
statelessGas := evm.AccessEvents.VersionGas(contractAddr, false) statelessGas := evm.AccessEvents.BasicDataGas(contractAddr, false)
statelessGas += evm.AccessEvents.CodeSizeGas(contractAddr, false)
statelessGas += evm.AccessEvents.BalanceGas(contractAddr, false)
if contractAddr != beneficiaryAddr { if contractAddr != beneficiaryAddr {
statelessGas += evm.AccessEvents.BalanceGas(beneficiaryAddr, false) statelessGas += evm.AccessEvents.BasicDataGas(beneficiaryAddr, false)
} }
// Charge write costs if it transfers value // Charge write costs if it transfers value
if evm.StateDB.GetBalance(contractAddr).Sign() != 0 { if evm.StateDB.GetBalance(contractAddr).Sign() != 0 {
statelessGas += evm.AccessEvents.BalanceGas(contractAddr, true) statelessGas += evm.AccessEvents.BasicDataGas(contractAddr, true)
if contractAddr != beneficiaryAddr { if contractAddr != beneficiaryAddr {
statelessGas += evm.AccessEvents.BalanceGas(beneficiaryAddr, true) statelessGas += evm.AccessEvents.BasicDataGas(beneficiaryAddr, true)
} }
} }
return statelessGas, nil return statelessGas, nil
@ -145,8 +142,7 @@ func gasExtCodeCopyEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memo
return 0, err return 0, err
} }
addr := common.Address(stack.peek().Bytes20()) addr := common.Address(stack.peek().Bytes20())
wgas := evm.AccessEvents.VersionGas(addr, false) wgas := evm.AccessEvents.BasicDataGas(addr, false)
wgas += evm.AccessEvents.CodeSizeGas(addr, false)
if wgas == 0 { if wgas == 0 {
wgas = params.WarmStorageReadCostEIP2929 wgas = params.WarmStorageReadCostEIP2929
} }

@ -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. // 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()) hk := t.hashKey(address.Bytes())
data, err := rlp.EncodeToBytes(acc) data, err := rlp.EncodeToBytes(acc)
if err != nil { if err != nil {

@ -28,13 +28,13 @@ import (
) )
const ( const (
// The spec of verkle key encoding can be found here. BasicDataLeafKey = 0
// https://notes.ethereum.org/@vbuterin/verkle_tree_eip#Tree-embedding CodeHashLeafKey = 1
VersionLeafKey = 0
BalanceLeafKey = 1 BasicDataVersionOffset = 0
NonceLeafKey = 2 BasicDataCodeSizeOffset = 5
CodeKeccakLeafKey = 3 BasicDataNonceOffset = 8
CodeSizeLeafKey = 4 BasicDataBalanceOffset = 16
) )
var ( var (
@ -177,31 +177,16 @@ func GetTreeKeyWithEvaluatedAddress(evaluated *verkle.Point, treeIndex *uint256.
return pointToHash(ret, subIndex) return pointToHash(ret, subIndex)
} }
// VersionKey returns the verkle tree key of the version field for the specified account. // BasicDataKey returns the verkle tree key of the basic data field for
func VersionKey(address []byte) []byte {
return GetTreeKey(address, zero, VersionLeafKey)
}
// BalanceKey returns the verkle tree key of the balance field for the specified account.
func BalanceKey(address []byte) []byte {
return GetTreeKey(address, zero, BalanceLeafKey)
}
// NonceKey returns the verkle tree key of the nonce field for the specified account.
func NonceKey(address []byte) []byte {
return GetTreeKey(address, zero, NonceLeafKey)
}
// CodeKeccakKey returns the verkle tree key of the code keccak field for
// the specified account. // the specified account.
func CodeKeccakKey(address []byte) []byte { func BasicDataKey(address []byte) []byte {
return GetTreeKey(address, zero, CodeKeccakLeafKey) return GetTreeKey(address, zero, BasicDataLeafKey)
} }
// CodeSizeKey returns the verkle tree key of the code size field for the // CodeHashKey returns the verkle tree key of the code hash field for
// specified account. // the specified account.
func CodeSizeKey(address []byte) []byte { func CodeHashKey(address []byte) []byte {
return GetTreeKey(address, zero, CodeSizeLeafKey) return GetTreeKey(address, zero, CodeHashLeafKey)
} }
func codeChunkIndex(chunk *uint256.Int) (*uint256.Int, byte) { func codeChunkIndex(chunk *uint256.Int) (*uint256.Int, byte) {
@ -249,39 +234,18 @@ func StorageSlotKey(address []byte, storageKey []byte) []byte {
return GetTreeKey(address, treeIndex, subIndex) return GetTreeKey(address, treeIndex, subIndex)
} }
// VersionKeyWithEvaluatedAddress returns the verkle tree key of the version // BasicDataKeyWithEvaluatedAddress returns the verkle tree key of the basic data
// field for the specified account. The difference between VersionKey is the // field for the specified account. The difference between BasicDataKey is the
// address evaluation is already computed to minimize the computational overhead.
func VersionKeyWithEvaluatedAddress(evaluated *verkle.Point) []byte {
return GetTreeKeyWithEvaluatedAddress(evaluated, zero, VersionLeafKey)
}
// BalanceKeyWithEvaluatedAddress returns the verkle tree key of the balance
// field for the specified account. The difference between BalanceKey is the
// address evaluation is already computed to minimize the computational overhead.
func BalanceKeyWithEvaluatedAddress(evaluated *verkle.Point) []byte {
return GetTreeKeyWithEvaluatedAddress(evaluated, zero, BalanceLeafKey)
}
// NonceKeyWithEvaluatedAddress returns the verkle tree key of the nonce
// field for the specified account. The difference between NonceKey is the
// address evaluation is already computed to minimize the computational overhead.
func NonceKeyWithEvaluatedAddress(evaluated *verkle.Point) []byte {
return GetTreeKeyWithEvaluatedAddress(evaluated, zero, NonceLeafKey)
}
// CodeKeccakKeyWithEvaluatedAddress returns the verkle tree key of the code
// keccak for the specified account. The difference between CodeKeccakKey is the
// address evaluation is already computed to minimize the computational overhead. // address evaluation is already computed to minimize the computational overhead.
func CodeKeccakKeyWithEvaluatedAddress(evaluated *verkle.Point) []byte { func BasicDataKeyWithEvaluatedAddress(evaluated *verkle.Point) []byte {
return GetTreeKeyWithEvaluatedAddress(evaluated, zero, CodeKeccakLeafKey) return GetTreeKeyWithEvaluatedAddress(evaluated, zero, BasicDataLeafKey)
} }
// CodeSizeKeyWithEvaluatedAddress returns the verkle tree key of the code // CodeHashKeyWithEvaluatedAddress returns the verkle tree key of the code
// size for the specified account. The difference between CodeSizeKey is the // hash for the specified account. The difference between CodeHashKey is the
// address evaluation is already computed to minimize the computational overhead. // address evaluation is already computed to minimize the computational overhead.
func CodeSizeKeyWithEvaluatedAddress(evaluated *verkle.Point) []byte { func CodeHashKeyWithEvaluatedAddress(evaluated *verkle.Point) []byte {
return GetTreeKeyWithEvaluatedAddress(evaluated, zero, CodeSizeLeafKey) return GetTreeKeyWithEvaluatedAddress(evaluated, zero, CodeHashLeafKey)
} }
// CodeChunkKeyWithEvaluatedAddress returns the verkle tree key of the code // CodeChunkKeyWithEvaluatedAddress returns the verkle tree key of the code

@ -33,20 +33,11 @@ func TestTreeKey(t *testing.T) {
smallStorage = []byte{0x1} smallStorage = []byte{0x1}
largeStorage = bytes.Repeat([]byte{0xff}, 16) largeStorage = bytes.Repeat([]byte{0xff}, 16)
) )
if !bytes.Equal(VersionKey(address), VersionKeyWithEvaluatedAddress(addressEval)) { if !bytes.Equal(BasicDataKey(address), BasicDataKeyWithEvaluatedAddress(addressEval)) {
t.Fatal("Unmatched version key") t.Fatal("Unmatched basic data key")
} }
if !bytes.Equal(BalanceKey(address), BalanceKeyWithEvaluatedAddress(addressEval)) { if !bytes.Equal(CodeHashKey(address), CodeHashKeyWithEvaluatedAddress(addressEval)) {
t.Fatal("Unmatched balance key") t.Fatal("Unmatched code hash key")
}
if !bytes.Equal(NonceKey(address), NonceKeyWithEvaluatedAddress(addressEval)) {
t.Fatal("Unmatched nonce key")
}
if !bytes.Equal(CodeKeccakKey(address), CodeKeccakKeyWithEvaluatedAddress(addressEval)) {
t.Fatal("Unmatched code keccak key")
}
if !bytes.Equal(CodeSizeKey(address), CodeSizeKeyWithEvaluatedAddress(addressEval)) {
t.Fatal("Unmatched code size key")
} }
if !bytes.Equal(CodeChunkKey(address, smallIndex), CodeChunkKeyWithEvaluatedAddress(addressEval, smallIndex)) { if !bytes.Equal(CodeChunkKey(address, smallIndex), CodeChunkKeyWithEvaluatedAddress(addressEval, smallIndex)) {
t.Fatal("Unmatched code chunk key") t.Fatal("Unmatched code chunk key")
@ -76,7 +67,7 @@ func BenchmarkTreeKey(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
BalanceKey([]byte{0x01}) BasicDataKey([]byte{0x01})
} }
} }
@ -96,7 +87,7 @@ func BenchmarkTreeKeyWithEvaluation(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
BalanceKeyWithEvaluatedAddress(eval) BasicDataKeyWithEvaluatedAddress(eval)
} }
} }

@ -100,20 +100,10 @@ func (t *VerkleTrie) GetAccount(addr common.Address) (*types.StateAccount, error
if values == nil { if values == nil {
return nil, nil return nil, nil
} }
// Decode nonce in little-endian basicData := values[utils.BasicDataLeafKey]
if len(values[utils.NonceLeafKey]) > 0 { acc.Nonce = binary.BigEndian.Uint64(basicData[utils.BasicDataNonceOffset:])
acc.Nonce = binary.LittleEndian.Uint64(values[utils.NonceLeafKey]) acc.Balance = new(uint256.Int).SetBytes(basicData[utils.BasicDataBalanceOffset : utils.BasicDataBalanceOffset+16])
} acc.CodeHash = values[utils.CodeHashLeafKey]
// Decode balance in little-endian
var balance [32]byte
copy(balance[:], values[utils.BalanceLeafKey])
for i := 0; i < len(balance)/2; i++ {
balance[len(balance)-i-1], balance[i] = balance[i], balance[len(balance)-i-1]
}
acc.Balance = new(uint256.Int).SetBytes32(balance[:])
// Decode codehash
acc.CodeHash = values[utils.CodeKeccakLeafKey]
// TODO account.Root is leave as empty. How should we handle the legacy account? // TODO account.Root is leave as empty. How should we handle the legacy account?
return acc, nil return acc, nil
@ -133,36 +123,36 @@ func (t *VerkleTrie) GetStorage(addr common.Address, key []byte) ([]byte, error)
// UpdateAccount implements state.Trie, writing the provided account into the tree. // UpdateAccount implements state.Trie, writing the provided account into the tree.
// If the tree is corrupted, an error will be returned. // If the tree is corrupted, an error will be returned.
func (t *VerkleTrie) UpdateAccount(addr common.Address, acc *types.StateAccount) error { func (t *VerkleTrie) UpdateAccount(addr common.Address, acc *types.StateAccount, codeLen int) error {
var ( var (
err error err error
nonce, balance [32]byte basicData [32]byte
values = make([][]byte, verkle.NodeWidth) values = make([][]byte, verkle.NodeWidth)
stem = t.cache.GetStem(addr[:])
) )
values[utils.VersionLeafKey] = zero[:]
values[utils.CodeKeccakLeafKey] = acc.CodeHash[:]
// Encode nonce in little-endian // Code size is encoded in BasicData as a 3-byte big-endian integer. Spare bytes are present
binary.LittleEndian.PutUint64(nonce[:], acc.Nonce) // before the code size to support bigger integers in the future. PutUint32(...) requires
values[utils.NonceLeafKey] = nonce[:] // 4 bytes, so we need to shift the offset 1 byte to the left.
binary.BigEndian.PutUint32(basicData[utils.BasicDataCodeSizeOffset-1:], uint32(codeLen))
// Encode balance in little-endian binary.BigEndian.PutUint64(basicData[utils.BasicDataNonceOffset:], acc.Nonce)
bytes := acc.Balance.Bytes() if acc.Balance.ByteLen() > 16 {
for i, b := range bytes { panic("balance too large")
balance[len(bytes)-i-1] = b
} }
values[utils.BalanceLeafKey] = balance[:] acc.Balance.WriteToSlice(basicData[utils.BasicDataBalanceOffset : utils.BasicDataBalanceOffset+16])
values[utils.BasicDataLeafKey] = basicData[:]
values[utils.CodeHashLeafKey] = acc.CodeHash[:]
switch n := t.root.(type) { switch root := t.root.(type) {
case *verkle.InternalNode: case *verkle.InternalNode:
err = n.InsertValuesAtStem(t.cache.GetStem(addr[:]), values, t.nodeResolver) err = root.InsertValuesAtStem(stem, values, t.nodeResolver)
if err != nil {
return fmt.Errorf("UpdateAccount (%x) error: %v", addr, err)
}
default: default:
return errInvalidRootType return errInvalidRootType
} }
// TODO figure out if the code size needs to be updated, too if err != nil {
return fmt.Errorf("UpdateAccount (%x) error: %v", addr, err)
}
return nil return nil
} }
@ -208,31 +198,33 @@ func (t *VerkleTrie) DeleteAccount(addr common.Address) error {
func (t *VerkleTrie) RollBackAccount(addr common.Address) error { func (t *VerkleTrie) RollBackAccount(addr common.Address) error {
var ( var (
evaluatedAddr = t.cache.Get(addr.Bytes()) evaluatedAddr = t.cache.Get(addr.Bytes())
codeSizeKey = utils.CodeSizeKeyWithEvaluatedAddress(evaluatedAddr) basicDataKey = utils.BasicDataKeyWithEvaluatedAddress(evaluatedAddr)
) )
codeSizeBytes, err := t.root.Get(codeSizeKey, t.nodeResolver) basicDataBytes, err := t.root.Get(basicDataKey, t.nodeResolver)
if err != nil { if err != nil {
return fmt.Errorf("rollback: error finding code size: %w", err) return fmt.Errorf("rollback: error finding code size: %w", err)
} }
if len(codeSizeBytes) == 0 { if len(basicDataBytes) == 0 {
return errors.New("rollback: code size is not existent") return errors.New("rollback: basic data is not existent")
} }
codeSize := binary.LittleEndian.Uint64(codeSizeBytes) // The code size is encoded in BasicData as a 3-byte big-endian integer. Spare bytes are present
// before the code size to support bigger integers in the future.
// LittleEndian.Uint32(...) expects 4-bytes, so we need to shift the offset 1-byte to the left.
codeSize := binary.BigEndian.Uint32(basicDataBytes[utils.BasicDataCodeSizeOffset-1:])
// Delete the account header + first 64 slots + first 128 code chunks // Delete the account header + first 64 slots + first 128 code chunks
_, err = t.root.(*verkle.InternalNode).DeleteAtStem(codeSizeKey[:31], t.nodeResolver) _, err = t.root.(*verkle.InternalNode).DeleteAtStem(basicDataKey[:31], t.nodeResolver)
if err != nil { if err != nil {
return fmt.Errorf("error rolling back account header: %w", err) return fmt.Errorf("error rolling back account header: %w", err)
} }
// Delete all further code // Delete all further code
for i, chunknr := uint64(31*128), uint64(128); i < codeSize; i, chunknr = i+31*256, chunknr+256 { for i, chunknr := uint64(31*128), uint64(128); i < uint64(codeSize); i, chunknr = i+31*256, chunknr+256 {
// evaluate group key at the start of a new group // evaluate group key at the start of a new group
offset := uint256.NewInt(chunknr) offset := uint256.NewInt(chunknr)
key := utils.CodeChunkKeyWithEvaluatedAddress(evaluatedAddr, offset) key := utils.CodeChunkKeyWithEvaluatedAddress(evaluatedAddr, offset)
_, err = t.root.(*verkle.InternalNode).DeleteAtStem(key[:], t.nodeResolver) if _, err = t.root.(*verkle.InternalNode).DeleteAtStem(key[:], t.nodeResolver); err != nil {
if err != nil {
return fmt.Errorf("error deleting code chunk stem (addr=%x, offset=%d) error: %w", addr[:], offset, err) return fmt.Errorf("error deleting code chunk stem (addr=%x, offset=%d) error: %w", addr[:], offset, err)
} }
} }
@ -385,6 +377,7 @@ func ChunkifyCode(code []byte) ChunkedCode {
// UpdateContractCode implements state.Trie, writing the provided contract code // UpdateContractCode implements state.Trie, writing the provided contract code
// into the trie. // into the trie.
// Note that the code-size *must* be already saved by a previous UpdateAccount call.
func (t *VerkleTrie) UpdateContractCode(addr common.Address, codeHash common.Hash, code []byte) error { func (t *VerkleTrie) UpdateContractCode(addr common.Address, codeHash common.Hash, code []byte) error {
var ( var (
chunks = ChunkifyCode(code) chunks = ChunkifyCode(code)
@ -400,12 +393,6 @@ func (t *VerkleTrie) UpdateContractCode(addr common.Address, codeHash common.Has
} }
values[groupOffset] = chunks[i : i+32] values[groupOffset] = chunks[i : i+32]
// Reuse the calculated key to also update the code size.
if i == 0 {
cs := make([]byte, 32)
binary.LittleEndian.PutUint64(cs, uint64(len(code)))
values[utils.CodeSizeLeafKey] = cs
}
if groupOffset == 255 || len(chunks)-i <= 32 { if groupOffset == 255 || len(chunks)-i <= 32 {
switch root := t.root.(type) { switch root := t.root.(type) {
case *verkle.InternalNode: case *verkle.InternalNode:

@ -61,7 +61,7 @@ func TestVerkleTreeReadWrite(t *testing.T) {
tr, _ := NewVerkleTrie(types.EmptyVerkleHash, db, utils.NewPointCache(100)) tr, _ := NewVerkleTrie(types.EmptyVerkleHash, db, utils.NewPointCache(100))
for addr, acct := range accounts { for addr, acct := range accounts {
if err := tr.UpdateAccount(addr, acct); err != nil { if err := tr.UpdateAccount(addr, acct, 0); err != nil {
t.Fatalf("Failed to update account, %v", err) t.Fatalf("Failed to update account, %v", err)
} }
for key, val := range storages[addr] { for key, val := range storages[addr] {
@ -96,7 +96,13 @@ func TestVerkleRollBack(t *testing.T) {
tr, _ := NewVerkleTrie(types.EmptyVerkleHash, db, utils.NewPointCache(100)) tr, _ := NewVerkleTrie(types.EmptyVerkleHash, db, utils.NewPointCache(100))
for addr, acct := range accounts { for addr, acct := range accounts {
if err := tr.UpdateAccount(addr, acct); err != nil { // create more than 128 chunks of code
code := make([]byte, 129*32)
for i := 0; i < len(code); i += 2 {
code[i] = 0x60
code[i+1] = byte(i % 256)
}
if err := tr.UpdateAccount(addr, acct, len(code)); err != nil {
t.Fatalf("Failed to update account, %v", err) t.Fatalf("Failed to update account, %v", err)
} }
for key, val := range storages[addr] { for key, val := range storages[addr] {
@ -104,12 +110,6 @@ func TestVerkleRollBack(t *testing.T) {
t.Fatalf("Failed to update account, %v", err) t.Fatalf("Failed to update account, %v", err)
} }
} }
// create more than 128 chunks of code
code := make([]byte, 129*32)
for i := 0; i < len(code); i += 2 {
code[i] = 0x60
code[i+1] = byte(i % 256)
}
hash := crypto.Keccak256Hash(code) hash := crypto.Keccak256Hash(code)
if err := tr.UpdateContractCode(addr, hash, code); err != nil { if err := tr.UpdateContractCode(addr, hash, code); err != nil {
t.Fatalf("Failed to update contract, %v", err) t.Fatalf("Failed to update contract, %v", err)

Loading…
Cancel
Save