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:
Ignacio Hagopian 2024-08-30 09:13:02 -03:00 committed by GitHub
parent e9467eec1c
commit ab3ee99ca9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 140 additions and 228 deletions

View File

@ -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)

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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

View File

@ -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 {

View File

@ -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,

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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

View File

@ -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)
}
}

View File

@ -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
values = make([][]byte, verkle.NodeWidth)
err error
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:

View File

@ -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)