Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion consensus/beacon/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,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, true /* noop */)
}

if chain.Config().IsPrague(header.Number, header.Time) {
Expand Down
1 change: 0 additions & 1 deletion core/chain_makers.go
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,6 @@ func GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine
if err != nil {
panic(fmt.Sprintf("could not find state for block %d: err=%v, parent root=%x", i, err, parent.Root()))
}
statedb.NewAccessWitness()
block, receipt := genblock(i, parent, statedb)
blocks[i] = block
receipts[i] = receipt
Expand Down
69 changes: 40 additions & 29 deletions core/state/access_witness.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,16 @@ var zeroTreeIndex uint256.Int
type AccessWitness struct {
branches map[branchAccessKey]mode
chunks map[chunkAccessKey]mode
fills map[chunkAccessKey]struct{}

pointCache *utils.PointCache
}

func NewAccessWitness(pointCache *utils.PointCache) *AccessWitness {
func NewAccessWitness(pointCache *utils.PointCache, fills map[chunkAccessKey]struct{}) *AccessWitness {
return &AccessWitness{
branches: make(map[branchAccessKey]mode),
chunks: make(map[chunkAccessKey]mode),
fills: fills,
pointCache: pointCache,
}
}
Expand All @@ -64,6 +66,9 @@ func (aw *AccessWitness) Merge(other *AccessWitness) {
for k, chunk := range other.chunks {
aw.chunks[k] |= chunk
}
for k := range other.fills {
aw.fills[k] = struct{}{}
}
}

// Key returns, predictably, the list of keys that were touched during the
Expand All @@ -83,49 +88,50 @@ func (aw *AccessWitness) Copy() *AccessWitness {
naw := &AccessWitness{
branches: make(map[branchAccessKey]mode),
chunks: make(map[chunkAccessKey]mode),
fills: make(map[chunkAccessKey]struct{}),
pointCache: aw.pointCache,
}
naw.Merge(aw)
return naw
}

func (aw *AccessWitness) TouchFullAccount(addr []byte, isWrite bool) uint64 {
func (aw *AccessWitness) TouchFullAccount(addr []byte, isWrite, isFill bool) uint64 {
var gas uint64
for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ {
gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, byte(i), isWrite)
gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, byte(i), isWrite, isFill)
}
return gas
}

func (aw *AccessWitness) TouchAndChargeMessageCall(addr []byte) uint64 {
var gas uint64
gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, false)
gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, false)
gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, false, false)
gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, false, false)
return gas
}

func (aw *AccessWitness) TouchAndChargeValueTransfer(callerAddr, targetAddr []byte) uint64 {
var gas uint64
gas += aw.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BalanceLeafKey, true)
gas += aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, true)
gas += aw.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BalanceLeafKey, true, false)
gas += aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, true, false)
return gas
}

// TouchAndChargeContractCreateInit charges access costs to initiate
// a contract creation
func (aw *AccessWitness) TouchAndChargeContractCreateInit(addr []byte, createSendsValue bool) uint64 {
var gas uint64
gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, true)
gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, true)
gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, true, false)
gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, true, false)
if createSendsValue {
gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, true)
gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, true, false)
}
return gas
}

func (aw *AccessWitness) TouchTxOriginAndComputeGas(originAddr []byte) uint64 {
for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ {
aw.touchAddressAndChargeGas(originAddr, zeroTreeIndex, byte(i), i == utils.BalanceLeafKey || i == utils.NonceLeafKey)
aw.touchAddressAndChargeGas(originAddr, zeroTreeIndex, byte(i), i == utils.BalanceLeafKey || i == utils.NonceLeafKey, false)
}

// Kaustinen note: we're currently experimenting with stop chargin gas for the origin address
Expand All @@ -136,14 +142,14 @@ func (aw *AccessWitness) TouchTxOriginAndComputeGas(originAddr []byte) uint64 {
}

func (aw *AccessWitness) TouchTxExistingAndComputeGas(targetAddr []byte, sendsValue bool) uint64 {
aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.VersionLeafKey, false)
aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeSizeLeafKey, false)
aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeHashLeafKey, false)
aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.NonceLeafKey, false)
aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.VersionLeafKey, false, false)
aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeSizeLeafKey, false, false)
aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeHashLeafKey, false, false)
aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.NonceLeafKey, false, false)
if sendsValue {
aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, true)
aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, true, false)
} else {
aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, false)
aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, false, false)
}

// Kaustinen note: we're currently experimenting with stop chargin gas for the origin address
Expand All @@ -153,13 +159,13 @@ func (aw *AccessWitness) TouchTxExistingAndComputeGas(targetAddr []byte, sendsVa
return 0
}

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

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)
func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite, isFill bool) uint64 {
stemRead, selectorRead, stemWrite, selectorWrite, selectorFill := aw.touchAddress(addr, treeIndex, subIndex, isWrite, isFill)

var gas uint64
if stemRead {
Expand All @@ -182,7 +188,7 @@ func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, treeIndex uint256
}

// 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) touchAddress(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite, isFill bool) (bool, bool, bool, bool, bool) {
branchKey := newBranchAccessKey(addr, treeIndex)
chunkKey := newChunkAccessKey(branchKey, subIndex)

Expand Down Expand Up @@ -211,7 +217,11 @@ func (aw *AccessWitness) touchAddress(addr []byte, treeIndex uint256.Int, subInd
aw.chunks[chunkKey] |= AccessWitnessWriteFlag
}

// TODO: charge chunk filling costs if the leaf was previously empty in the state
_, ok := aw.fills[chunkKey]
if isFill && !ok {
chunkFill = true
aw.fills[chunkKey] = struct{}{}
}
}

return branchRead, chunkRead, branchWrite, chunkWrite, chunkFill
Expand Down Expand Up @@ -265,7 +275,8 @@ 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)
// if the contract is written, then it's also a fill
gas := aw.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite, isWrite)
var overflow bool
statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, gas)
if overflow {
Expand All @@ -277,21 +288,21 @@ func (aw *AccessWitness) TouchCodeChunksRangeAndChargeGas(contractAddr []byte, s
}

func (aw *AccessWitness) TouchVersion(addr []byte, isWrite bool) uint64 {
return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, isWrite)
return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, isWrite, false)
}

func (aw *AccessWitness) TouchBalance(addr []byte, isWrite bool) uint64 {
return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, isWrite)
return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, isWrite, false)
}

func (aw *AccessWitness) TouchNonce(addr []byte, isWrite bool) uint64 {
return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, isWrite)
return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, isWrite, false)
}

func (aw *AccessWitness) TouchCodeSize(addr []byte, isWrite bool) uint64 {
return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, isWrite)
return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, isWrite, false)
}

func (aw *AccessWitness) TouchCodeHash(addr []byte, isWrite bool) uint64 {
return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite)
return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite, false)
}
23 changes: 21 additions & 2 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,21 @@ import (
"sort"
"time"

"github.com/cockroachdb/pebble"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb/memorydb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/trie/triestate"
"github.com/ethereum/go-ethereum/trie/utils"
)

type revision struct {
Expand Down Expand Up @@ -187,12 +190,18 @@ func (s *StateDB) Snaps() *snapshot.Tree {
}

func (s *StateDB) NewAccessWitness() *AccessWitness {
return NewAccessWitness(s.db.(*cachingDB).addrToPoint)
return NewAccessWitness(s.db.(*cachingDB).addrToPoint, make(map[chunkAccessKey]struct{}))
}

// NewAccessWitnessWithFills does the same thing as NewAccessWitness,
// but reusing the state fills map so that the fill costs are not
// charged multiple times in the same block.
func (s *StateDB) NewAccessWitnessWithFills() *AccessWitness {
return NewAccessWitness(s.db.(*cachingDB).addrToPoint, s.witness.fills)
}
func (s *StateDB) Witness() *AccessWitness {
if s.witness == nil {
s.witness = s.NewAccessWitness()
panic("witness wasn't initialized")
}
return s.witness
}
Expand Down Expand Up @@ -1467,3 +1476,13 @@ func copy2DSet[k comparable](set map[k]map[common.Hash][]byte) map[k]map[common.
}
return copied
}

func (s *StateDB) IsSlotFilled(addr common.Address, slot common.Hash) bool {
// The snapshot can not be used, because it uses the old encoding where
// no difference is made between 0 and no data.
_, err := s.db.DiskDB().Get(utils.GetTreeKeyStorageSlotWithEvaluatedAddress(s.witness.pointCache.GetTreeKeyHeader(addr[:]), slot[:]))
// The error needs to be checked because we want to be future-proof
// and not rely on the length of the encoding, in case 0-values are
// somehow compressed later.
return errors.Is(pebble.ErrNotFound, err) || errors.Is(memorydb.ErrMemorydbNotFound, err)
}
6 changes: 3 additions & 3 deletions core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error) {
// Create a new context to be used in the EVM environment.
txContext := NewEVMTxContext(msg)
txContext.Accesses = statedb.NewAccessWitness()
txContext.Accesses = statedb.NewAccessWitnessWithFills()
evm.Reset(txContext, statedb)

// Apply the transaction to the current state (included in the env).
Expand Down Expand Up @@ -182,7 +182,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo

func InsertBlockHashHistoryAtEip2935Fork(statedb *state.StateDB, prevNumber uint64, prevHash common.Hash, chain consensus.ChainHeaderReader) {
// Make sure that the historical contract is added to the witness
statedb.Witness().TouchFullAccount(params.HistoryStorageAddress[:], true)
statedb.Witness().TouchFullAccount(params.HistoryStorageAddress[:], true, true /* noop */)

ancestor := chain.GetHeader(prevHash, prevNumber)
for i := prevNumber; i > 0 && i >= prevNumber-params.Eip2935BlockHashHistorySize; i-- {
Expand All @@ -196,5 +196,5 @@ func ProcessParentBlockHash(statedb *state.StateDB, prevNumber uint64, prevHash
var key common.Hash
binary.BigEndian.PutUint64(key[24:], ringIndex)
statedb.SetState(params.HistoryStorageAddress, key, prevHash)
statedb.Witness().TouchSlotAndChargeGas(params.HistoryStorageAddress[:], key, true)
statedb.Witness().TouchSlotAndChargeGas(params.HistoryStorageAddress[:], key, true, true /* noop */)
}
97 changes: 97 additions & 0 deletions core/state_processor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,103 @@ func TestProcessVerkle(t *testing.T) {
}
}

// TestProcessVerkleFillThenNoFill tests the case where a slot is filled in a first transaction, for which the
// CHUNK_EDIT costs are filled, and then the same slot is written to in a second transaction, for which the CHUNK_EDIT
// costs are not charged, provided the two transactions occur in the same block.
func TestProcessVerkleFillThenNoFill(t *testing.T) {
var (
config = &params.ChainConfig{
ChainID: big.NewInt(1),
HomesteadBlock: big.NewInt(0),
EIP150Block: big.NewInt(0),
EIP155Block: big.NewInt(0),
EIP158Block: big.NewInt(0),
ByzantiumBlock: big.NewInt(0),
ConstantinopleBlock: big.NewInt(0),
PetersburgBlock: big.NewInt(0),
IstanbulBlock: big.NewInt(0),
MuirGlacierBlock: big.NewInt(0),
BerlinBlock: big.NewInt(0),
LondonBlock: big.NewInt(0),
Ethash: new(params.EthashConfig),
ShanghaiTime: u64(0),
PragueTime: u64(0),
TerminalTotalDifficulty: common.Big0,
TerminalTotalDifficultyPassed: true,
ProofInBlocks: true,
}
signer = types.LatestSigner(config)
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain
gendb = rawdb.NewMemoryDatabase() // Database for the block-generation code, they must be separate as they are path-based.
coinbase = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7")
gspec = &Genesis{
Config: config,
Alloc: GenesisAlloc{
coinbase: GenesisAccount{
Balance: big.NewInt(1000000000000000000), // 1 ether
},
common.Address{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}: GenesisAccount{
// PUSH1 0, CALLDATALOAD, PUSH1 2, SSTORE
Code: []byte{0x60, 0, 0x35, 0x60, 2, 0x55},
Balance: big.NewInt(1000000000000000000),
},
},
}
loggerCfg = &logger.Config{}
)

os.MkdirAll("output", 0755)
traceFile, err := os.Create("./output/traces.jsonl")
if err != nil {
t.Fatal(err)
}

// Verkle trees use the snapshot, which must be enabled before the
// data is saved into the tree+database.
genesis := gspec.MustCommit(bcdb)
blockchain, _ := NewBlockChain(bcdb, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{Tracer: logger.NewJSONLogger(loggerCfg, traceFile)}, nil, nil)
defer blockchain.Stop()
// Commit the genesis block to the block-generation database as it
// is now independent of the blockchain database.
gspec.MustCommit(gendb)

blockGasUsagesExpected := params.TxGas + 216 /* intrinsic gas */ + 3*vm.GasFastestStep + params.WitnessChunkReadCost + params.WitnessChunkWriteCost
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: get intrinsic gas from the function

chain, _, _, _ := GenerateVerkleChain(gspec.Config, genesis, beacon.New(ethash.NewFaker()), gendb, 1, func(i int, gen *BlockGen) {
gen.SetPoS()

// fill slot
tx, _ := types.SignTx(types.NewTransaction(0, common.Address{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, big.NewInt(999), blockGasUsagesExpected+params.WitnessChunkFillCost, big.NewInt(875000000), []byte{1}), signer, testKey)
gen.AddTx(tx)
// write but no fill
tx, _ = types.SignTx(types.NewTransaction(1, common.Address{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, big.NewInt(999), blockGasUsagesExpected, big.NewInt(875000000), []byte{2}), signer, testKey)
gen.AddTx(tx)
})

// check the proof for the last block
// err = trie.DeserializeAndVerifyVerkleProof(proofs[0], genesis.Root().Bytes(), chain[0].Root().Bytes(), keyvals[0])
// if err != nil {
// t.Fatal(err)
// }
// t.Log("verfied verkle proof")
Comment on lines +662 to +667
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: figure out why this is broken and fix it


endnum, err := blockchain.InsertChain(chain)
if err != nil {
t.Fatalf("block %d imported with error: %v", endnum, err)
}

b := blockchain.GetBlockByNumber(1)
if b == nil {
t.Fatalf("expected block %d to be present in chain", 1)
}
if b.Hash() != chain[0].Hash() {
t.Fatalf("block #%d not found at expected height", b.NumberU64())
}
if b.GasUsed() != 2*blockGasUsagesExpected+params.WitnessChunkFillCost {
t.Fatalf("expected block #%d txs to use %d, got %d\n", b.NumberU64(), 2*blockGasUsagesExpected, b.GasUsed())
}
}

func TestProcessVerkleInvalidContractCreation(t *testing.T) {
var (
config = &params.ChainConfig{
Expand Down
2 changes: 1 addition & 1 deletion core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,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.Accesses.TouchFullAccount(st.evm.Context.Coinbase[:], true)
st.evm.Accesses.TouchFullAccount(st.evm.Context.Coinbase[:], true, true /* noop */)
}
}

Expand Down
Loading