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

core: store the chunked code directly in the database #121

Draft
wants to merge 4 commits into
base: verkle-trie-post-merge
Choose a base branch
from
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
27 changes: 26 additions & 1 deletion core/state/state_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
trieUtils "github.com/ethereum/go-ethereum/trie/utils"
"github.com/gballet/go-verkle"
"github.com/holiman/uint256"
Expand Down Expand Up @@ -580,11 +581,35 @@ func (s *stateObject) SetCode(codeHash common.Hash, code []byte) {
}

func (s *stateObject) setCode(codeHash common.Hash, code []byte) {
s.code = code
if s.db.trie.IsVerkle() {
chunks := trie.ChunkifyCode(code)
s.code = []byte(chunks)
} else {
s.code = code
}
s.data.CodeHash = codeHash[:]
s.dirtyCode = true
}

func (s *stateObject) AddCodeChunksToWitness() uint64 {
var (
chunks = trie.ChunkedCode(s.code)
it = chunks.Iter(0, 0)
i, gas uint64
)
for {
chunk, err := it()
if err != nil {
break
}
addr := trieUtils.GetTreeKeyCodeChunkWithEvaluatedAddress(s.pointEval, uint256.NewInt(i))
gas += s.db.Witness().TouchAddressOnWriteAndComputeGas(addr)
s.db.Witness().SetLeafValue(addr, chunk)
i++
}
return gas
}

func (s *stateObject) SetNonce(nonce uint64) {
s.db.journal.append(nonceChange{
account: &s.address,
Expand Down
24 changes: 12 additions & 12 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,14 @@ func (s *StateDB) SetCode(addr common.Address, code []byte) {
}
}

func (s *StateDB) AddCodeChunksToWitness(addr common.Address) uint64 {
stateObject := s.GetOrNewStateObject(addr)
if stateObject != nil {
return stateObject.AddCodeChunksToWitness()
}
return 0
}

func (s *StateDB) SetState(addr common.Address, key, value common.Hash) {
stateObject := s.GetOrNewStateObject(addr)
if stateObject != nil {
Expand Down Expand Up @@ -523,12 +531,8 @@ func (s *StateDB) updateStateObject(obj *stateObject) {
}

if obj.dirtyCode {
if chunks, err := trie.ChunkifyCode(obj.code); err == nil {
for i := 0; i < len(chunks); i += 32 {
s.trie.TryUpdate(trieUtils.GetTreeKeyCodeChunkWithEvaluatedAddress(obj.pointEval, uint256.NewInt(uint64(i)/32)), chunks[i:i+32])
}
} else {
s.setError(err)
for i := 0; i < len(obj.code); i += 32 {
s.trie.TryUpdate(trieUtils.GetTreeKeyCodeChunkWithEvaluatedAddress(obj.pointEval, uint256.NewInt(uint64(i)/32)), obj.code[i:i+32])
}
}
} else {
Expand Down Expand Up @@ -1019,12 +1023,8 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
// Write any contract code associated with the state object
if obj.code != nil && obj.dirtyCode {
if s.trie.IsVerkle() {
if chunks, err := trie.ChunkifyCode(obj.code); err == nil {
for i := 0; i < len(chunks); i += 32 {
s.trie.TryUpdate(trieUtils.GetTreeKeyCodeChunkWithEvaluatedAddress(obj.pointEval, uint256.NewInt(uint64(i)/32)), chunks[i:32+i])
}
} else {
s.setError(err)
for i := 0; i < len(obj.code); i += 32 {
s.trie.TryUpdate(trieUtils.GetTreeKeyCodeChunkWithEvaluatedAddress(obj.pointEval, uint256.NewInt(uint64(i)/32)), obj.code[i:32+i])
}
}
rawdb.WriteCode(codeWriter, common.BytesToHash(obj.CodeHash()), obj.code)
Expand Down
19 changes: 15 additions & 4 deletions core/state_processor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ func TestProcessVerkleCodeDeployExec(t *testing.T) {
gen.AddTx(tx)
} else {
// Call the contract's `store` function in block #2
tx, _ := types.SignTx(types.NewTransaction(1, contractAddr, big.NewInt(0), 3000000, big.NewInt(875000000), callStoreInput), signer, testKey)
tx, _ := types.SignTx(types.NewTransaction(1, contractAddr, big.NewInt(0), 3000000, big.NewInt(910000000), callStoreInput), signer, testKey)
gen.AddTx(tx)
}
})
Expand All @@ -510,8 +510,8 @@ func TestProcessVerkleCodeDeployExec(t *testing.T) {
t.Fatalf("expected block %d to be present in chain", 1)
}
var (
hascode bool
contractStem [31]byte
hascode, hasData bool
contractStem [31]byte
)

// Look for the stem that the contract will be deployed to
Expand All @@ -537,7 +537,7 @@ func TestProcessVerkleCodeDeployExec(t *testing.T) {
t.Fatalf("expected block %d to be present in chain", 2)
}

hascode = false
hascode, hasData = false, false
codeCount := 0
for _, kv := range b2.Header().VerkleKeyVals {
if bytes.Equal(contractStem[:], kv.Key[:31]) && kv.Key[31] >= 128 {
Expand All @@ -557,6 +557,13 @@ func TestProcessVerkleCodeDeployExec(t *testing.T) {
t.Fatalf("0-filled code chunk %x", kv.Key)
}
}

if bytes.Equal(contractStem[:], kv.Key[:31]) && kv.Key[31] == 64 {
hasData = true
if len(kv.Value) != 0 {
t.Fatalf("invalid value in witness, expected nil or [], got %x", kv.Value)
}
}
}

if !hascode {
Expand All @@ -566,4 +573,8 @@ func TestProcessVerkleCodeDeployExec(t *testing.T) {
if codeCount != 10 {
t.Fatalf("got %d code chunks, expected 10", codeCount)
}

if !hasData {
t.Fatal("could not find the storage write in the witness of the calling block")
}
}
7 changes: 6 additions & 1 deletion core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie"
)

var emptyCodeHash = crypto.Keccak256Hash(nil)
Expand Down Expand Up @@ -373,7 +374,11 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
vmerr error // vm errors do not effect consensus and are therefore not assigned to err
)
if contractCreation {
ret, _, st.gas, vmerr = st.evm.Create(sender, st.data, st.gas, st.value)
code := st.data
if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber) {
code = trie.ChunkifyCode(st.data)
}
ret, _, st.gas, vmerr = st.evm.Create(sender, code, st.gas, st.value)
} else {
// Increment the nonce for the next transaction
st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)
Expand Down
4 changes: 1 addition & 3 deletions core/types/access_witness.go
Original file line number Diff line number Diff line change
Expand Up @@ -336,9 +336,7 @@ func (aw *AccessWitness) TouchAndChargeContractCreateInit(addr []byte, createSen

gas += aw.TouchAddressOnWriteAndComputeGas(versionkey)
gas += aw.TouchAddressOnWriteAndComputeGas(noncekey[:])
if createSendsValue {
gas += aw.TouchAddressOnWriteAndComputeGas(balancekey[:])
}
gas += aw.TouchAddressOnWriteAndComputeGas(balancekey[:])
gas += aw.TouchAddressOnWriteAndComputeGas(ckkey[:])
return gas
}
Expand Down
11 changes: 8 additions & 3 deletions core/vm/analysis.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package vm

import "github.com/ethereum/go-ethereum/trie"

const (
set2BitsMask = uint16(0b11)
set3BitsMask = uint16(0b111)
Expand Down Expand Up @@ -62,19 +64,22 @@ func (bits *bitvec) codeSegment(pos uint64) bool {

// codeBitmap collects data locations in code.
func codeBitmap(code []byte) bitvec {
chunks := trie.ChunkedCode(code)

// The bitmap is 4 bytes longer than necessary, in case the code
// ends with a PUSH32, the algorithm will push zeroes onto the
// bitvector outside the bounds of the actual code.
bits := make(bitvec, len(code)/8+1+4)
bits := make(bitvec, chunks.Len()/8+1+4)
return codeBitmapInternal(code, bits)
}

// codeBitmapInternal is the internal implementation of codeBitmap.
// It exists for the purpose of being able to run benchmark tests
// without dynamic allocations affecting the results.
func codeBitmapInternal(code, bits bitvec) bitvec {
for pc := uint64(0); pc < uint64(len(code)); {
op := OpCode(code[pc])
chunks := trie.ChunkedCode(code)
for pc := uint64(0); pc < uint64(chunks.Len()); {
op := OpCode(chunks.AtPC(pc))
pc++
if int8(op) < int8(PUSH1) { // If not PUSH (the int8(op) > int(PUSH32) is always false).
continue
Expand Down
8 changes: 5 additions & 3 deletions core/vm/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/utils"
"github.com/gballet/go-verkle"
"github.com/holiman/uint256"
Expand Down Expand Up @@ -88,15 +89,16 @@ func NewContract(caller ContractRef, object ContractRef, value *big.Int, gas uin
return c
}

func (c *Contract) validJumpdest(dest *uint256.Int) bool {
func (c *Contract) validJumpdest(dest *uint256.Int, chunked bool) bool {
udest, overflow := dest.Uint64WithOverflow()
chunks := trie.ChunkedCode(c.Code)
// PC cannot go beyond len(code) and certainly can't be bigger than 63bits.
// Don't bother checking for JUMPDEST in that case.
if overflow || udest >= uint64(len(c.Code)) {
if overflow || udest >= uint64(chunks.Len()) {
return false
}
// Only JUMPDESTs allowed for destinations
if OpCode(c.Code[udest]) != JUMPDEST {
if OpCode(chunks.AtPC(udest)) != JUMPDEST {
return false
}
return c.IsCode(udest)
Expand Down
5 changes: 5 additions & 0 deletions core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
err = ErrOutOfGas
} else {
evm.Accesses.SetLeafValuesContractCreateCompleted(address.Bytes()[:], zeroVerkleLeaf[:], zeroVerkleLeaf[:])

if !contract.UseGas(evm.StateDB.AddCodeChunksToWitness(address)) {
evm.StateDB.RevertToSnapshot(snapshot)
err = ErrOutOfGas
}
}
}

Expand Down
6 changes: 4 additions & 2 deletions core/vm/gas_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,10 @@ func gasCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory
if overflow {
uint64Length = 0xffffffffffffffff
}
_, offset, nonPaddedSize := getDataAndAdjustedBounds(contract.Code, uint64CodeOffset, uint64Length)
statelessGas = touchEachChunksOnReadAndChargeGas(offset, nonPaddedSize, contract.AddressPoint(), nil, evm.Accesses, contract.IsDeployment)
if !contract.IsDeployment {
_, offset, nonPaddedSize := getDataAndAdjustedBounds(contract.Code, uint64CodeOffset, uint64Length)
statelessGas = touchEachChunksOnReadAndChargeGas(offset, nonPaddedSize, contract.AddressPoint(), nil, evm.Accesses, contract.IsDeployment)
}
}
usedGas, err := gasCodeCopyStateful(evm, contract, stack, mem, memorySize)
return usedGas + statelessGas, err
Expand Down
Loading