Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Witness charging order with limits #502

Merged
merged 38 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
60ed072
state: access witness with partial cost charging
jsign Sep 12, 2024
ab90f4c
fix most problems
jsign Sep 12, 2024
f1c6f05
rebase fixes
jsign Sep 12, 2024
88cd5cf
fixes
jsign Sep 16, 2024
3138345
fixes
jsign Sep 16, 2024
711d8a1
.
jsign Sep 16, 2024
3ec64b5
remove 4762 gas call wrappers
jsign Sep 19, 2024
628842b
more progress
jsign Sep 19, 2024
89e6155
call gas
jsign Sep 19, 2024
33f8fe1
fix
jsign Sep 19, 2024
246105c
fix
jsign Sep 19, 2024
84f6792
fix
jsign Sep 19, 2024
65880c7
fix
jsign Sep 19, 2024
95a5d65
fix
jsign Sep 19, 2024
14b5b69
fix
jsign Sep 19, 2024
4dc1913
fix
jsign Sep 19, 2024
82f36b3
fix
jsign Sep 19, 2024
89662eb
fix
jsign Sep 20, 2024
e1a70f0
simplify warm costs
jsign Sep 23, 2024
313ef15
cleanup
jsign Sep 25, 2024
c99a94f
commit statedb
jsign Sep 27, 2024
36d4c27
pass upper bound for gas consumption
gballet Sep 24, 2024
cb26a96
fix missing gas sum
jsign Sep 25, 2024
1ee921b
fix: warm storage cost for basic data and code hash
gballet Sep 25, 2024
e4c5d26
fix: return wanted in TouchBasicData gas functions
gballet Sep 25, 2024
e6ce883
fix
jsign Sep 25, 2024
9886ea9
fix: add history contract to TestProcessorVerkle
gballet Sep 27, 2024
7ce760d
ci: new workflows for testing (#504)
jsign Oct 8, 2024
45e2f6c
import fixes from base branch until 51dca93bb04a4f93a2be9422a56ee8ea9…
gballet Oct 11, 2024
c0ba8e5
fix: create init order redux (#510)
gballet Oct 15, 2024
21c73d1
import most changes from jsign-witness-fix
gballet Oct 22, 2024
2ebc8e8
rebase nits
jsign Oct 22, 2024
da72c04
fix: move 1/64 before gas call
gballet Oct 22, 2024
f3abb68
fix: value transfer gas charge before 1/64th + warm costs for precomp…
gballet Oct 22, 2024
a9b0e68
Filling fixes (#516)
jsign Oct 23, 2024
87408a7
nit
jsign Oct 23, 2024
b5f8705
fix compilation error
jsign Oct 23, 2024
99b78be
Fill fixes continued (#519)
jsign Oct 24, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 6 additions & 14 deletions .github/workflows/spec-tests-branch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,13 @@ jobs:

- name: Clone execution-spec-tests and fill tests
run: |
git clone https://github.com/${{ env.EEST_USER }}/execution-spec-tests -b ${{ env.EEST_BRANCH }}
curl -LsSf https://astral.sh/uv/install.sh | sh
git clone https://github.com/${{ env.EEST_USER }}/execution-spec-tests -b ${{ env.EEST_BRANCH }} --depth 1
cd execution-spec-tests
python3 -m venv venv
. venv/bin/activate
pip install --upgrade pip
pip install -e ".[docs,lint,test]"
solc-select use 0.8.24 --always-install
if [ "${{ matrix.test-type }}" == "genesis" ]; then
fill --fork Verkle --output=../fixtures-${{ matrix.test-type }} -v -m blockchain_test -n auto
uv run fill --evm-bin="${{ github.workspace }}/bin/evm" --fork Verkle --output=../fixtures-${{ matrix.test-type }} -v -m blockchain_test -n auto
else
fill --from Shanghai --until EIP6800Transition --output=../fixtures-${{ matrix.test-type }} -v -m blockchain_test -n auto
uv run fill --evm-bin="${{ github.workspace }}/bin/evm" --from Shanghai --until EIP6800Transition --output=../fixtures-${{ matrix.test-type }} -v -m blockchain_test -n auto
fi
shell: bash

Expand Down Expand Up @@ -107,12 +103,8 @@ jobs:

- name: Clone execution-spec-tests and consume tests
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
git clone https://github.com/${{ env.EEST_USER }}/execution-spec-tests -b ${{ env.EEST_BRANCH }}
cd execution-spec-tests
python3 -m venv venv
. venv/bin/activate
pip install --upgrade pip
pip install -e ".[docs,lint,test]"
solc-select use 0.8.24 --always-install
consume direct --input=../fixtures-${{ matrix.test-type }} -n auto
uv run consume direct --evm-bin="${{ github.workspace }}/bin/evm" --input=../fixtures-${{ matrix.test-type }} -n auto
shell: bash
13 changes: 4 additions & 9 deletions .github/workflows/stable-spec-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
workflow_dispatch:

env:
FIXTURES_TAG: "[email protected].3"
FIXTURES_TAG: "[email protected].6"

jobs:
setup:
Expand Down Expand Up @@ -71,13 +71,8 @@ jobs:
extract: true
- name: Clone execution-spec-tests and consume tests
run: |
git clone https://github.com/ethereum/execution-spec-tests
curl -LsSf https://astral.sh/uv/install.sh | sh
git clone https://github.com/ethereum/execution-spec-tests -b ${{ env.FIXTURES_TAG }} --depth 1
cd execution-spec-tests
git checkout 250a064ba38cd92cad02691f4f7c2ecbefedd954
python3 -m venv venv
. venv/bin/activate
pip install --upgrade pip
pip install -e ".[docs,lint,test]"
solc-select use 0.8.24 --always-install
consume direct --input=../fixtures -n auto
uv run consume direct --evm-bin="${{ github.workspace }}/bin/evm" --input=../fixtures -n auto
shell: bash
3 changes: 2 additions & 1 deletion cmd/evm/internal/t8ntool/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
// Amount is in gwei, turn into wei
amount := new(big.Int).Mul(new(big.Int).SetUint64(w.Amount), big.NewInt(params.GWei))
statedb.AddBalance(w.Address, amount)
statedb.Witness().TouchFullAccount(w.Address[:], true)
statedb.Witness().TouchFullAccount(w.Address[:], true, math.MaxUint64)
}
if chainConfig.IsVerkle(big.NewInt(int64(pre.Env.Number)), pre.Env.Timestamp) {
if err := overlay.OverlayVerkleTransition(statedb, common.Hash{}, chainConfig.OverlayStride); err != nil {
Expand Down Expand Up @@ -451,6 +451,7 @@ func MakePreState(db ethdb.Database, chainConfig *params.ChainConfig, pre *Prest
codeHash := crypto.Keccak256Hash(acc.Code)
rawdb.WriteCode(codeWriter, codeHash, acc.Code)
}
statedb.Commit(0, false)

return statedb
}
Expand Down
3 changes: 2 additions & 1 deletion consensus/beacon/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
Expand Down Expand Up @@ -358,7 +359,7 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.
state.AddBalance(w.Address, amount)

// The returned gas is not charged
state.Witness().TouchFullAccount(w.Address[:], true)
state.Witness().TouchFullAccount(w.Address[:], true, math.MaxUint64)
}

if chain.Config().IsVerkle(header.Number, header.Time) {
Expand Down
6 changes: 6 additions & 0 deletions core/chain_makers.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,12 @@ func GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine
keyvals = append(keyvals, block.ExecutionWitness().StateDiff)
proots = append(proots, parent.Root())

// quick check that we are self-consistent
err = trie.DeserializeAndVerifyVerkleProof(block.ExecutionWitness().VerkleProof, block.ExecutionWitness().ParentStateRoot[:], block.Root().Bytes(), block.ExecutionWitness().StateDiff)
if err != nil {
panic(err)
}

return block, b.receipts
}
return nil, nil
Expand Down
190 changes: 111 additions & 79 deletions core/state/access_witness.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,132 +89,145 @@ func (aw *AccessWitness) Copy() *AccessWitness {
return naw
}

func (aw *AccessWitness) TouchFullAccount(addr []byte, isWrite bool) uint64 {
func (aw *AccessWitness) TouchFullAccount(addr []byte, isWrite bool, availableGas uint64) uint64 {
var gas uint64
for i := utils.BasicDataLeafKey; i <= utils.CodeHashLeafKey; i++ {
gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, byte(i), isWrite)
consumed, wanted := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, byte(i), isWrite, availableGas)
if consumed < wanted {
return wanted + gas
}
availableGas -= consumed
gas += consumed
}
return gas
}

func (aw *AccessWitness) TouchAndChargeMessageCall(addr []byte) uint64 {
var gas uint64
gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false)
return gas
func (aw *AccessWitness) TouchAndChargeMessageCall(addr []byte, availableGas uint64) uint64 {
_, wanted := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false, availableGas)
if wanted == 0 {
wanted = params.WarmStorageReadCostEIP2929
}
return wanted
}

func (aw *AccessWitness) TouchAndChargeValueTransfer(callerAddr, targetAddr []byte) uint64 {
var gas uint64
gas += aw.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BasicDataLeafKey, true)
gas += aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, true)
return gas
func (aw *AccessWitness) TouchAndChargeValueTransfer(callerAddr, targetAddr []byte, availableGas uint64) uint64 {
_, wanted1 := aw.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas)
if wanted1 > availableGas {
return wanted1
}
_, wanted2 := aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas-wanted1)
if wanted1+wanted2 == 0 {
return params.WarmStorageReadCostEIP2929
}
return wanted1 + wanted2
}

// TouchAndChargeContractCreateCheck charges access costs before
// a contract creation is initiated. It is just reads, because the
// address collision is done before the transfer, and so no write
// are guaranteed to happen at this point.
func (aw *AccessWitness) TouchAndChargeContractCreateCheck(addr []byte) uint64 {
var gas uint64
gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false)
gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false)
return gas
func (aw *AccessWitness) TouchAndChargeContractCreateCheck(addr []byte, availableGas uint64) uint64 {
gas1, wanted1 := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false, availableGas)
_, wanted2 := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false, availableGas-gas1)
return wanted1 + wanted2
}

// TouchAndChargeContractCreateInit charges access costs to initiate
// a contract creation.
func (aw *AccessWitness) TouchAndChargeContractCreateInit(addr []byte) uint64 {
var gas uint64
gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, true)
gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, true)
return gas
func (aw *AccessWitness) TouchAndChargeContractCreateInit(addr []byte, availableGas uint64) (uint64, uint64) {
gas1, wanted1 := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas)
gas2, wanted2 := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, true, availableGas-gas1)
return gas1 + gas2, wanted1 + wanted2
}

func (aw *AccessWitness) TouchTxOriginAndComputeGas(originAddr []byte) uint64 {
func (aw *AccessWitness) TouchTxOriginAndComputeGas(originAddr []byte) {
for i := utils.BasicDataLeafKey; i <= utils.CodeHashLeafKey; i++ {
aw.touchAddressAndChargeGas(originAddr, zeroTreeIndex, byte(i), i == utils.BasicDataLeafKey)
aw.touchAddressAndChargeGas(originAddr, zeroTreeIndex, byte(i), i == utils.BasicDataLeafKey, math.MaxUint64)
}

// Kaustinen note: we're currently experimenting with stop chargin gas for the origin address
// so simple transfer still take 21000 gas. This is to potentially avoid breaking existing tooling.
// This is the reason why we return 0 instead of `gas`.
// Note that we still have to touch the addresses to make sure the witness is correct.
return 0
}

func (aw *AccessWitness) TouchTxExistingAndComputeGas(targetAddr []byte, sendsValue bool) uint64 {
aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, sendsValue)
aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeHashLeafKey, false)

// Kaustinen note: we're currently experimenting with stop chargin gas for the origin address
// so simple transfer still take 21000 gas. This is to potentially avoid breaking existing tooling.
// This is the reason why we return 0 instead of `gas`.
// Note that we still have to touch the addresses to make sure the witness is correct.
return 0
func (aw *AccessWitness) TouchTxTarget(targetAddr []byte, sendsValue, doesntExist bool) {
aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, sendsValue, math.MaxUint64)
// Note that we do a write-event in CodeHash without distinguishing if the tx target account
// exists or not. Pre-7702, there's no situation in which an existing codeHash can be mutated, thus
// doing a write-event shouldn't cause an observable difference in gas usage.
// TODO(7702): re-check this in the spec and implementation to be sure is a correct solution after
// EIP-7702 is implemented.
aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeHashLeafKey, doesntExist, math.MaxUint64)
}

func (aw *AccessWitness) TouchSlotAndChargeGas(addr []byte, slot common.Hash, isWrite bool) uint64 {
func (aw *AccessWitness) TouchSlotAndChargeGas(addr []byte, slot common.Hash, isWrite bool, availableGas uint64, warmCostCharging bool) uint64 {
treeIndex, subIndex := utils.GetTreeKeyStorageSlotTreeIndexes(slot.Bytes())
return aw.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite)
}

func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool) uint64 {
stemRead, selectorRead, stemWrite, selectorWrite, selectorFill := aw.touchAddress(addr, treeIndex, subIndex, isWrite)

var gas uint64
if stemRead {
gas += params.WitnessBranchReadCost
}
if selectorRead {
gas += params.WitnessChunkReadCost
_, wanted := aw.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite, availableGas)
if wanted == 0 && warmCostCharging {
wanted = params.WarmStorageReadCostEIP2929
}
if stemWrite {
gas += params.WitnessBranchWriteCost
}
if selectorWrite {
gas += params.WitnessChunkWriteCost
}
if selectorFill {
gas += params.WitnessChunkFillCost
}

return gas
return wanted
}

// touchAddress adds any missing access event to the witness.
func (aw *AccessWitness) touchAddress(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool) (bool, bool, bool, bool, bool) {
func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool, availableGas uint64) (uint64, uint64) {
branchKey := newBranchAccessKey(addr, treeIndex)
chunkKey := newChunkAccessKey(branchKey, subIndex)

// Read access.
var branchRead, chunkRead bool
if _, hasStem := aw.branches[branchKey]; !hasStem {
branchRead = true
aw.branches[branchKey] = AccessWitnessReadFlag
}
if _, hasSelector := aw.chunks[chunkKey]; !hasSelector {
chunkRead = true
aw.chunks[chunkKey] = AccessWitnessReadFlag
}

// Write access.
var branchWrite, chunkWrite, chunkFill bool
if isWrite {
if (aw.branches[branchKey] & AccessWitnessWriteFlag) == 0 {
branchWrite = true
aw.branches[branchKey] |= AccessWitnessWriteFlag
}

chunkValue := aw.chunks[chunkKey]
if (chunkValue & AccessWitnessWriteFlag) == 0 {
chunkWrite = true
aw.chunks[chunkKey] |= AccessWitnessWriteFlag
}
}

var gas uint64
if branchRead {
gas += params.WitnessBranchReadCost
}
if chunkRead {
gas += params.WitnessChunkReadCost
}
if branchWrite {
gas += params.WitnessBranchWriteCost
}
if chunkWrite {
gas += params.WitnessChunkWriteCost
}
if chunkFill {
gas += params.WitnessChunkFillCost
}

if availableGas < gas {
// consumed != wanted
return availableGas, gas
}

// TODO: charge chunk filling costs if the leaf was previously empty in the state
if branchRead {
aw.branches[branchKey] = AccessWitnessReadFlag
}
if branchWrite {
aw.branches[branchKey] |= AccessWitnessWriteFlag
}
if chunkRead {
aw.chunks[chunkKey] = AccessWitnessReadFlag
}
if chunkWrite {
aw.chunks[chunkKey] |= AccessWitnessWriteFlag
}

return branchRead, chunkRead, branchWrite, chunkWrite, chunkFill
// consumed == wanted
return gas, gas
}

type branchAccessKey struct {
Expand Down Expand Up @@ -242,15 +255,15 @@ func newChunkAccessKey(branchKey branchAccessKey, leafKey byte) chunkAccessKey {
}

// touchCodeChunksRangeOnReadAndChargeGas is a helper function to touch every chunk in a code range and charge witness gas costs
func (aw *AccessWitness) TouchCodeChunksRangeAndChargeGas(contractAddr []byte, startPC, size uint64, codeLen uint64, isWrite bool) uint64 {
func (aw *AccessWitness) TouchCodeChunksRangeAndChargeGas(contractAddr []byte, startPC, size uint64, codeLen uint64, isWrite bool, availableGas uint64) (uint64, uint64) {
// note that in the case where the copied code is outside the range of the
// contract code but touches the last leaf with contract code in it,
// we don't include the last leaf of code in the AccessWitness. The
// reason that we do not need the last leaf is the account's code size
// is already in the AccessWitness so a stateless verifier can see that
// the code from the last leaf is not needed.
if size == 0 || startPC >= codeLen {
return 0
return 0, 0
}

endPC := startPC + size
Expand All @@ -265,21 +278,40 @@ func (aw *AccessWitness) TouchCodeChunksRangeAndChargeGas(contractAddr []byte, s
for chunkNumber := startPC / 31; chunkNumber <= endPC/31; chunkNumber++ {
treeIndex := *uint256.NewInt((chunkNumber + 128) / 256)
subIndex := byte((chunkNumber + 128) % 256)
gas := aw.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite)
consumed, wanted := aw.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite, availableGas)
// did we OOG ?
if wanted > consumed {
return statelessGasCharged + consumed, statelessGasCharged + wanted
}
var overflow bool
statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, gas)
statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, consumed)
if overflow {
panic("overflow when adding gas")
}
availableGas -= consumed
}

return statelessGasCharged
return statelessGasCharged, statelessGasCharged
}

func (aw *AccessWitness) TouchBasicData(addr []byte, isWrite bool) uint64 {
return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite)
func (aw *AccessWitness) TouchBasicData(addr []byte, isWrite bool, availableGas uint64, warmCostCharging bool) uint64 {
_, wanted := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite, availableGas)
if wanted == 0 && warmCostCharging {
if availableGas < params.WarmStorageReadCostEIP2929 {
return availableGas
}
wanted = params.WarmStorageReadCostEIP2929
}
return wanted
}

func (aw *AccessWitness) TouchCodeHash(addr []byte, isWrite bool) uint64 {
return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite)
func (aw *AccessWitness) TouchCodeHash(addr []byte, isWrite bool, availableGas uint64, chargeWarmCosts bool) uint64 {
_, wanted := aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite, availableGas)
if wanted == 0 && chargeWarmCosts {
if availableGas < params.WarmStorageReadCostEIP2929 {
return availableGas
}
wanted = params.WarmStorageReadCostEIP2929
}
return wanted
}
Loading
Loading