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>
This commit is contained in:
parent
e9467eec1c
commit
ab3ee99ca9
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
@ -107,8 +104,7 @@ 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
|
||||
}
|
||||
|
||||
@ -116,8 +112,8 @@ func (ae *AccessEvents) MessageCallGas(destination common.Address) uint64 {
|
||||
// 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
|
||||
}
|
||||
|
||||
@ -125,32 +121,22 @@ func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address)
|
||||
// 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.
|
||||
@ -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
|
||||
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -28,13 +28,13 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// The spec of verkle key encoding can be found here.
|
||||
// https://notes.ethereum.org/@vbuterin/verkle_tree_eip#Tree-embedding
|
||||
VersionLeafKey = 0
|
||||
BalanceLeafKey = 1
|
||||
NonceLeafKey = 2
|
||||
CodeKeccakLeafKey = 3
|
||||
CodeSizeLeafKey = 4
|
||||
BasicDataLeafKey = 0
|
||||
CodeHashLeafKey = 1
|
||||
|
||||
BasicDataVersionOffset = 0
|
||||
BasicDataCodeSizeOffset = 5
|
||||
BasicDataNonceOffset = 8
|
||||
BasicDataBalanceOffset = 16
|
||||
)
|
||||
|
||||
var (
|
||||
@ -177,31 +177,16 @@ func GetTreeKeyWithEvaluatedAddress(evaluated *verkle.Point, treeIndex *uint256.
|
||||
return pointToHash(ret, subIndex)
|
||||
}
|
||||
|
||||
// VersionKey returns the verkle tree key of the version field for the specified account.
|
||||
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
|
||||
// BasicDataKey returns the verkle tree key of the basic data field for
|
||||
// the specified account.
|
||||
func CodeKeccakKey(address []byte) []byte {
|
||||
return GetTreeKey(address, zero, CodeKeccakLeafKey)
|
||||
func BasicDataKey(address []byte) []byte {
|
||||
return GetTreeKey(address, zero, BasicDataLeafKey)
|
||||
}
|
||||
|
||||
// CodeSizeKey returns the verkle tree key of the code size field for the
|
||||
// specified account.
|
||||
func CodeSizeKey(address []byte) []byte {
|
||||
return GetTreeKey(address, zero, CodeSizeLeafKey)
|
||||
// CodeHashKey returns the verkle tree key of the code hash field for
|
||||
// the specified account.
|
||||
func CodeHashKey(address []byte) []byte {
|
||||
return GetTreeKey(address, zero, CodeHashLeafKey)
|
||||
}
|
||||
|
||||
func codeChunkIndex(chunk *uint256.Int) (*uint256.Int, byte) {
|
||||
@ -249,39 +234,18 @@ func StorageSlotKey(address []byte, storageKey []byte) []byte {
|
||||
return GetTreeKey(address, treeIndex, subIndex)
|
||||
}
|
||||
|
||||
// VersionKeyWithEvaluatedAddress returns the verkle tree key of the version
|
||||
// field for the specified account. The difference between VersionKey is the
|
||||
// BasicDataKeyWithEvaluatedAddress returns the verkle tree key of the basic data
|
||||
// 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)
|
||||
func BasicDataKeyWithEvaluatedAddress(evaluated *verkle.Point) []byte {
|
||||
return GetTreeKeyWithEvaluatedAddress(evaluated, zero, BasicDataLeafKey)
|
||||
}
|
||||
|
||||
// BalanceKeyWithEvaluatedAddress returns the verkle tree key of the balance
|
||||
// field for the specified account. The difference between BalanceKey is the
|
||||
// CodeHashKeyWithEvaluatedAddress returns the verkle tree key of the code
|
||||
// hash for the specified account. The difference between CodeHashKey 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.
|
||||
func CodeKeccakKeyWithEvaluatedAddress(evaluated *verkle.Point) []byte {
|
||||
return GetTreeKeyWithEvaluatedAddress(evaluated, zero, CodeKeccakLeafKey)
|
||||
}
|
||||
|
||||
// CodeSizeKeyWithEvaluatedAddress returns the verkle tree key of the code
|
||||
// size for the specified account. The difference between CodeSizeKey is the
|
||||
// address evaluation is already computed to minimize the computational overhead.
|
||||
func CodeSizeKeyWithEvaluatedAddress(evaluated *verkle.Point) []byte {
|
||||
return GetTreeKeyWithEvaluatedAddress(evaluated, zero, CodeSizeLeafKey)
|
||||
func CodeHashKeyWithEvaluatedAddress(evaluated *verkle.Point) []byte {
|
||||
return GetTreeKeyWithEvaluatedAddress(evaluated, zero, CodeHashLeafKey)
|
||||
}
|
||||
|
||||
// CodeChunkKeyWithEvaluatedAddress returns the verkle tree key of the code
|
||||
|
@ -33,20 +33,11 @@ func TestTreeKey(t *testing.T) {
|
||||
smallStorage = []byte{0x1}
|
||||
largeStorage = bytes.Repeat([]byte{0xff}, 16)
|
||||
)
|
||||
if !bytes.Equal(VersionKey(address), VersionKeyWithEvaluatedAddress(addressEval)) {
|
||||
t.Fatal("Unmatched version key")
|
||||
if !bytes.Equal(BasicDataKey(address), BasicDataKeyWithEvaluatedAddress(addressEval)) {
|
||||
t.Fatal("Unmatched basic data key")
|
||||
}
|
||||
if !bytes.Equal(BalanceKey(address), BalanceKeyWithEvaluatedAddress(addressEval)) {
|
||||
t.Fatal("Unmatched balance 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(CodeHashKey(address), CodeHashKeyWithEvaluatedAddress(addressEval)) {
|
||||
t.Fatal("Unmatched code hash key")
|
||||
}
|
||||
if !bytes.Equal(CodeChunkKey(address, smallIndex), CodeChunkKeyWithEvaluatedAddress(addressEval, smallIndex)) {
|
||||
t.Fatal("Unmatched code chunk key")
|
||||
@ -76,7 +67,7 @@ func BenchmarkTreeKey(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
|
||||
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.ResetTimer()
|
||||
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 {
|
||||
return nil, nil
|
||||
}
|
||||
// Decode nonce in little-endian
|
||||
if len(values[utils.NonceLeafKey]) > 0 {
|
||||
acc.Nonce = binary.LittleEndian.Uint64(values[utils.NonceLeafKey])
|
||||
}
|
||||
// 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]
|
||||
basicData := values[utils.BasicDataLeafKey]
|
||||
acc.Nonce = binary.BigEndian.Uint64(basicData[utils.BasicDataNonceOffset:])
|
||||
acc.Balance = new(uint256.Int).SetBytes(basicData[utils.BasicDataBalanceOffset : utils.BasicDataBalanceOffset+16])
|
||||
acc.CodeHash = values[utils.CodeHashLeafKey]
|
||||
|
||||
// TODO account.Root is leave as empty. How should we handle the legacy account?
|
||||
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.
|
||||
// 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 (
|
||||
err error
|
||||
nonce, balance [32]byte
|
||||
basicData [32]byte
|
||||
values = make([][]byte, verkle.NodeWidth)
|
||||
stem = t.cache.GetStem(addr[:])
|
||||
)
|
||||
values[utils.VersionLeafKey] = zero[:]
|
||||
values[utils.CodeKeccakLeafKey] = acc.CodeHash[:]
|
||||
|
||||
// Encode nonce in little-endian
|
||||
binary.LittleEndian.PutUint64(nonce[:], acc.Nonce)
|
||||
values[utils.NonceLeafKey] = nonce[:]
|
||||
|
||||
// Encode balance in little-endian
|
||||
bytes := acc.Balance.Bytes()
|
||||
for i, b := range bytes {
|
||||
balance[len(bytes)-i-1] = b
|
||||
// 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. PutUint32(...) requires
|
||||
// 4 bytes, so we need to shift the offset 1 byte to the left.
|
||||
binary.BigEndian.PutUint32(basicData[utils.BasicDataCodeSizeOffset-1:], uint32(codeLen))
|
||||
binary.BigEndian.PutUint64(basicData[utils.BasicDataNonceOffset:], acc.Nonce)
|
||||
if acc.Balance.ByteLen() > 16 {
|
||||
panic("balance too large")
|
||||
}
|
||||
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:
|
||||
err = n.InsertValuesAtStem(t.cache.GetStem(addr[:]), values, t.nodeResolver)
|
||||
if err != nil {
|
||||
return fmt.Errorf("UpdateAccount (%x) error: %v", addr, err)
|
||||
}
|
||||
err = root.InsertValuesAtStem(stem, values, t.nodeResolver)
|
||||
default:
|
||||
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
|
||||
}
|
||||
|
||||
@ -208,31 +198,33 @@ func (t *VerkleTrie) DeleteAccount(addr common.Address) error {
|
||||
func (t *VerkleTrie) RollBackAccount(addr common.Address) error {
|
||||
var (
|
||||
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 {
|
||||
return fmt.Errorf("rollback: error finding code size: %w", err)
|
||||
}
|
||||
if len(codeSizeBytes) == 0 {
|
||||
return errors.New("rollback: code size is not existent")
|
||||
if len(basicDataBytes) == 0 {
|
||||
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
|
||||
_, err = t.root.(*verkle.InternalNode).DeleteAtStem(codeSizeKey[:31], t.nodeResolver)
|
||||
_, err = t.root.(*verkle.InternalNode).DeleteAtStem(basicDataKey[:31], t.nodeResolver)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error rolling back account header: %w", err)
|
||||
}
|
||||
|
||||
// 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
|
||||
offset := uint256.NewInt(chunknr)
|
||||
key := utils.CodeChunkKeyWithEvaluatedAddress(evaluatedAddr, offset)
|
||||
|
||||
_, err = t.root.(*verkle.InternalNode).DeleteAtStem(key[:], t.nodeResolver)
|
||||
if err != nil {
|
||||
if _, err = t.root.(*verkle.InternalNode).DeleteAtStem(key[:], t.nodeResolver); err != nil {
|
||||
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
|
||||
// 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 {
|
||||
var (
|
||||
chunks = ChunkifyCode(code)
|
||||
@ -400,12 +393,6 @@ func (t *VerkleTrie) UpdateContractCode(addr common.Address, codeHash common.Has
|
||||
}
|
||||
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 {
|
||||
switch root := t.root.(type) {
|
||||
case *verkle.InternalNode:
|
||||
|
@ -61,7 +61,7 @@ func TestVerkleTreeReadWrite(t *testing.T) {
|
||||
tr, _ := NewVerkleTrie(types.EmptyVerkleHash, db, utils.NewPointCache(100))
|
||||
|
||||
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)
|
||||
}
|
||||
for key, val := range storages[addr] {
|
||||
@ -96,7 +96,13 @@ func TestVerkleRollBack(t *testing.T) {
|
||||
tr, _ := NewVerkleTrie(types.EmptyVerkleHash, db, utils.NewPointCache(100))
|
||||
|
||||
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)
|
||||
}
|
||||
for key, val := range storages[addr] {
|
||||
@ -104,12 +110,6 @@ func TestVerkleRollBack(t *testing.T) {
|
||||
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)
|
||||
if err := tr.UpdateContractCode(addr, hash, code); err != nil {
|
||||
t.Fatalf("Failed to update contract, %v", err)
|
||||
|
Loading…
Reference in New Issue
Block a user