diff --git a/core/state/state_object.go b/core/state/state_object.go index e92633bbc8fa..a7ce412facec 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -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" @@ -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, diff --git a/core/state/statedb.go b/core/state/statedb.go index c10e5502b63c..8b2bbf9a1cd4 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -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 { @@ -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 { @@ -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) diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 0b4201a903a0..6b9427c85abb 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -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) } }) @@ -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 @@ -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 { @@ -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 { @@ -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") + } } diff --git a/core/state_transition.go b/core/state_transition.go index 240729ffa13c..1f9a57300a3f 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -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) @@ -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) diff --git a/core/types/access_witness.go b/core/types/access_witness.go index b4f748bb119a..9bdb6737d8f2 100644 --- a/core/types/access_witness.go +++ b/core/types/access_witness.go @@ -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 } diff --git a/core/vm/analysis.go b/core/vm/analysis.go index 4aa8cfe70f11..1223fa550e98 100644 --- a/core/vm/analysis.go +++ b/core/vm/analysis.go @@ -16,6 +16,8 @@ package vm +import "github.com/ethereum/go-ethereum/trie" + const ( set2BitsMask = uint16(0b11) set3BitsMask = uint16(0b111) @@ -62,10 +64,12 @@ 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) } @@ -73,8 +77,9 @@ func codeBitmap(code []byte) bitvec { // 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 diff --git a/core/vm/contract.go b/core/vm/contract.go index b296dbb784d6..1dbc6b925385 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -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" @@ -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) diff --git a/core/vm/evm.go b/core/vm/evm.go index 1909b0bf661a..0da27e8b3939 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -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 + } } } diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 3ad63d582e9e..1535927ea287 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -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 diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 7b4cf4fc4337..9b25495c8fac 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -370,17 +370,47 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ memOffset = scope.Stack.pop() codeOffset = scope.Stack.pop() length = scope.Stack.pop() + endOffset = new(uint256.Int).Add(&codeOffset, &length) + copied = uint64(0) // Amount of bytes already copied + chunks = trie.ChunkedCode(scope.Contract.Code) ) uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() if overflow { uint64CodeOffset = 0xffffffffffffffff } + uint64EndOffset, overflow := endOffset.Uint64WithOverflow() + if overflow { + uint64EndOffset = 0xffffffffffffffff + } + uint64Length := uint64EndOffset - uint64CodeOffset - paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(scope.Contract.Code, uint64CodeOffset, length.Uint64()) if interpreter.evm.chainConfig.IsCancun(interpreter.evm.Context.BlockNumber) { - touchEachChunksOnReadAndChargeGas(copyOffset, nonPaddedCopyLength, scope.Contract.AddressPoint(), scope.Contract.Code, interpreter.evm.Accesses, scope.Contract.IsDeployment) + chunkedOffset := uint64CodeOffset + 1 + uint64CodeOffset/31 // codeOffset translated into the chunked space + chunkedEnd := uint64EndOffset + 1 + uint64EndOffset/31 // endOffset translated into the chunked space + touchEachChunksOnReadAndChargeGas(chunkedOffset, chunkedEnd-chunkedOffset, scope.Contract.AddressPoint(), scope.Contract.Code, interpreter.evm.Accesses, scope.Contract.IsDeployment) + + // Copy all full chunks in the middle + it := chunks.Iter(uint64CodeOffset/31, uint64EndOffset/31) + for { + chunk, err := it() + if err != nil { + break // the only possible error is when the iterator has reached the end + } + start := (uint64CodeOffset + copied) % 31 + scope.Memory.Set(memOffset.Uint64()+copied, 31, chunk[1+start:]) + copied += uint64(len(chunk)) - start - 1 + } + + // Being chunked, the code will be padded to the next 31-byte boundary. + if copied < uint64Length { + chunk := chunks.GetChunk(uint64EndOffset / 31) + scope.Memory.Set(memOffset.Uint64()+copied, uint64EndOffset%31, chunk[1:1+uint64EndOffset%31]) + } + } else { + codeCopy := getData(scope.Contract.Code, uint64CodeOffset, length.Uint64()) + scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) } - scope.Memory.Set(memOffset.Uint64(), uint64(len(paddedCodeCopy)), paddedCodeCopy) + return nil, nil } @@ -447,10 +477,6 @@ func touchEachChunksOnReadAndChargeGas(offset, size uint64, addrPoint *verkle.Po } else { endOffset = offset + size } - chunks, err := trie.ChunkifyCode(code) - if err != nil { - panic(err) - } // endOffset - 1 since if the end offset is aligned on a chunk boundary, // the last chunk should not be included. @@ -467,7 +493,7 @@ func touchEachChunksOnReadAndChargeGas(offset, size uint64, addrPoint *verkle.Po if deployment { accesses.SetLeafValue(index[:], nil) } else { - accesses.SetLeafValue(index[:], chunks[32*i:(i+1)*32]) + accesses.SetLeafValue(index[:], code[32*i:(i+1)*32]) } } } @@ -482,17 +508,43 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) memOffset = stack.pop() codeOffset = stack.pop() length = stack.pop() + copied = uint64(0) ) uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() if overflow { uint64CodeOffset = 0xffffffffffffffff } + uint64EndOffset, overflow := length.Uint64WithOverflow() + if overflow { + uint64EndOffset = 0xffffffffffffffff + } + uint64Length := uint64EndOffset - uint64CodeOffset + addr := common.Address(a.Bytes20()) if interpreter.evm.chainConfig.IsCancun(interpreter.evm.Context.BlockNumber) { - code := interpreter.evm.StateDB.GetCode(addr) - paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64()) - touchEachChunksOnReadAndChargeGasWithAddress(copyOffset, nonPaddedCopyLength, addr[:], code, interpreter.evm.Accesses, false) - scope.Memory.Set(memOffset.Uint64(), length.Uint64(), paddedCodeCopy) + chunks := trie.ChunkedCode(interpreter.evm.StateDB.GetCode(addr)) + + chunkedOffset := uint64CodeOffset + 1 + uint64CodeOffset/31 // codeOffset translated into the chunked space + chunkedEnd := uint64EndOffset + 1 + uint64EndOffset/31 // endOffset translated into the chunked space + touchEachChunksOnReadAndChargeGas(chunkedOffset, chunkedEnd-chunkedOffset, scope.Contract.AddressPoint(), scope.Contract.Code, interpreter.evm.Accesses, scope.Contract.IsDeployment) + + // Copy all full chunks in the middle + it := chunks.Iter(uint64CodeOffset/31, uint64EndOffset/31) + for { + chunk, err := it() + if err != nil { + break // the only possible error is when the iterator has reached the end + } + start := (uint64CodeOffset + copied) % 31 + scope.Memory.Set(memOffset.Uint64()+copied, 31, chunk[1+start:]) + copied += uint64(len(chunk)) - start - 1 + } + + // Being chunked, the code will be padded to the next 31-byte boundary. + if copied < uint64Length { + chunk := chunks.GetChunk(uint64EndOffset / 31) + scope.Memory.Set(memOffset.Uint64()+copied, uint64EndOffset%31, chunk[1:1+uint64EndOffset%31]) + } } else { codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64()) scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) @@ -650,7 +702,7 @@ func opJump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt return nil, errStopToken } pos := scope.Stack.pop() - if !scope.Contract.validJumpdest(&pos) { + if !scope.Contract.validJumpdest(&pos, interpreter.evm.ChainConfig().IsCancun(interpreter.evm.Context.BlockNumber)) { return nil, ErrInvalidJump } *pc = pos.Uint64() - 1 // pc will be increased by the interpreter loop @@ -663,7 +715,7 @@ func opJumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by } pos, cond := scope.Stack.pop(), scope.Stack.pop() if !cond.IsZero() { - if !scope.Contract.validJumpdest(&pos) { + if !scope.Contract.validJumpdest(&pos, interpreter.evm.ChainConfig().IsCancun(interpreter.evm.Context.BlockNumber)) { return nil, ErrInvalidJump } *pc = pos.Uint64() - 1 // pc will be increased by the interpreter loop @@ -1004,13 +1056,18 @@ func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by ) *pc += 1 if *pc < codeLen { - scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc]))) - if interpreter.evm.chainConfig.IsCancun(interpreter.evm.Context.BlockNumber) && *pc%31 == 0 { - // touch next chunk if PUSH1 is at the boundary. if so, *pc has - // advanced past this boundary. - statelessGas := touchEachChunksOnReadAndChargeGas(*pc+1, uint64(1), scope.Contract.AddressPoint(), scope.Contract.Code, interpreter.evm.Accesses, scope.Contract.IsDeployment) - scope.Contract.UseGas(statelessGas) + if interpreter.evm.chainConfig.IsCancun(interpreter.evm.Context.BlockNumber) { + chunks := trie.ChunkedCode(scope.Contract.Code) + scope.Stack.push(integer.SetUint64(uint64(chunks.AtPC(*pc)))) + if *pc%31 == 0 && !scope.Contract.IsDeployment { + // touch next chunk if PUSH1 is at the boundary. if so, *pc has + // advanced past this boundary. + statelessGas := touchEachChunksOnReadAndChargeGas(*pc, uint64(1), scope.Contract.AddressPoint(), scope.Contract.Code, interpreter.evm.Accesses, scope.Contract.IsDeployment) + scope.Contract.UseGas(statelessGas) + } + } else { + scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc]))) } } else { scope.Stack.push(integer.Clear()) @@ -1028,12 +1085,16 @@ func makePush(size uint64, pushByteSize int) executionFunc { startMin = int(*pc + 1) } + if interpreter.evm.ChainConfig().IsCancun(interpreter.evm.Context.BlockNumber) { + startMin += 1 + int(*pc/31) + } + endMin := codeLen if startMin+pushByteSize < endMin { endMin = startMin + pushByteSize } - if interpreter.evm.chainConfig.IsCancun(interpreter.evm.Context.BlockNumber) { + if interpreter.evm.chainConfig.IsCancun(interpreter.evm.Context.BlockNumber) && !scope.Contract.IsDeployment { statelessGas := touchEachChunksOnReadAndChargeGas(uint64(startMin), uint64(pushByteSize), scope.Contract.AddressPoint(), scope.Contract.Code, interpreter.evm.Accesses, scope.Contract.IsDeployment) scope.Contract.UseGas(statelessGas) } diff --git a/core/vm/interface.go b/core/vm/interface.go index 37a7eab63912..35b36e023f88 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -38,6 +38,7 @@ type StateDB interface { GetCode(common.Address) []byte SetCode(common.Address, []byte) GetCodeSize(common.Address) int + AddCodeChunksToWitness(common.Address) uint64 AddRefund(uint64) SubRefund(uint64) diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index f6831c6374b8..eefe9c46ef43 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -181,9 +181,9 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( }() } - // Evaluate one address per group of 256, 31-byte chunks - if in.evm.ChainConfig().IsCancun(in.evm.Context.BlockNumber) && !contract.IsDeployment { - chunks, err = trie.ChunkifyCode(contract.Code) + // Evaluate one address per group of 256 code pages, 31-byte chunks + if in.evm.ChainConfig().IsCancun(in.evm.Context.BlockNumber) { + chunks = contract.Code totalEvals := len(contract.Code) / 31 / 256 if len(contract.Code)%(256*31) != 0 { @@ -206,7 +206,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( logged, pcCopy, gasCopy = false, pc, contract.Gas } - if chunks != nil { + if chunks != nil && !contract.IsDeployment { // if the PC ends up in a new "chunk" of verkleized code, charge the // associated costs. contract.Gas -= touchChunkOnReadAndChargeGas(chunks, pc, chunkEvals, contract.Code, in.evm.TxContext.Accesses, contract.IsDeployment) @@ -214,7 +214,12 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( // Get the operation from the jump table and validate the stack to ensure there are // enough stack items available to perform the operation. - op = contract.GetOp(pc) + if chunks != nil { + nextbyte := pc + (pc / 31) /* other chunks */ + 1 /* first chunk */ + op = contract.GetOp(nextbyte) + } else { + op = contract.GetOp(pc) + } operation := in.cfg.JumpTable[op] cost = operation.constantGas // For tracing // Validate stack diff --git a/trie/verkle.go b/trie/verkle.go index 54b701d95f37..2795d63be087 100644 --- a/trie/verkle.go +++ b/trie/verkle.go @@ -272,6 +272,46 @@ func deserializeVerkleProof(serialized []byte, rootC *verkle.Point, keyvals []ve // are actual code, and 1 byte is the pushdata offset). type ChunkedCode []byte +// Iter provides an iterator that returns all the chunks starting at chunk +// number `start` up to (and excluding) chunk number `end`. A value of `0` +// for `end`, then the iterator will stop at the last chunk. +func (cc ChunkedCode) Iter(start, end uint64) func() ([]byte, error) { + current := start + if end <= start { + end = (uint64(len(cc)) + 31) / 32 + } + return func() ([]byte, error) { + if current*32 > uint64(len(cc)) || current == end { + return nil, errIteratorEnd + } + defer func() { current++ }() + + return cc.GetChunk(current), nil + } +} + +// GetChunk gets the chunk number `number`. +func (cc ChunkedCode) GetChunk(number uint64) []byte { + if number*32 > uint64(len(cc)) { + var padding [32]byte + return padding[:] + } + + return cc[32*number : 32*(number+1)] +} + +// Len returns the length of the unchunked bytecode +func (cc ChunkedCode) Len() int { + return (len(cc) * 31) / 32 +} + +// AtPC return the byte at offset `pc` in the un-chunked bytecode. +func (cc ChunkedCode) AtPC(pc uint64) byte { + chunknr := pc / 31 + chunkOffset := pc % 31 + return cc.GetChunk(chunknr)[chunkOffset+1] +} + // Copy the values here so as to avoid an import cycle const ( PUSH1 = byte(0x60) @@ -284,7 +324,7 @@ const ( ) // ChunkifyCode generates the chunked version of an array representing EVM bytecode -func ChunkifyCode(code []byte) (ChunkedCode, error) { +func ChunkifyCode(code []byte) ChunkedCode { var ( chunkOffset = 0 // offset in the chunk chunkCount = len(code) / 31 @@ -331,5 +371,5 @@ func ChunkifyCode(code []byte) (ChunkedCode, error) { } } - return chunks, nil + return chunks } diff --git a/trie/verkle_test.go b/trie/verkle_test.go index 8481290a565c..3c02d49f3cfd 100644 --- a/trie/verkle_test.go +++ b/trie/verkle_test.go @@ -93,10 +93,7 @@ func TestReproduceTree(t *testing.T) { func TestChunkifyCodeTestnet(t *testing.T) { code, _ := hex.DecodeString("6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea264697066735822122000382db0489577c1646ea2147a05f92f13f32336a32f1f82c6fb10b63e19f04064736f6c63430008070033") - chunks, err := ChunkifyCode(code) - if err != nil { - t.Fatal(err) - } + chunks := ChunkifyCode(code) if len(chunks) != 32*(len(code)/31+1) { t.Fatalf("invalid length %d != %d", len(chunks), 32*(len(code)/31+1)) } @@ -116,10 +113,7 @@ func TestChunkifyCodeTestnet(t *testing.T) { t.Logf("code=%x, chunks=%x\n", code, chunks) code, _ = hex.DecodeString("608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220d8add45a339f741a94b4fe7f22e101b560dc8a5874cbd957a884d8c9239df86264736f6c63430008070033") - chunks, err = ChunkifyCode(code) - if err != nil { - t.Fatal(err) - } + chunks = ChunkifyCode(code) if len(chunks) != 32*((len(code)+30)/31) { t.Fatalf("invalid length %d", len(chunks)) } @@ -138,10 +132,7 @@ func TestChunkifyCodeTestnet(t *testing.T) { t.Logf("code=%x, chunks=%x\n", code, chunks) code, _ = hex.DecodeString("6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea2646970667358221220163c79eab5630c3dbe22f7cc7692da08575198dda76698ae8ee2e3bfe62af3de64736f6c63430008070033") - chunks, err = ChunkifyCode(code) - if err != nil { - t.Fatal(err) - } + chunks = ChunkifyCode(code) if len(chunks) != 32*((len(code)+30)/31) { t.Fatalf("invalid length %d", len(chunks)) } @@ -170,10 +161,7 @@ func TestChunkifyCodeSimple(t *testing.T) { 23, 24, 25, 26, 27, 28, 29, 30, } t.Logf("code=%x", code) - chunks, err := ChunkifyCode(code) - if err != nil { - t.Fatal(err) - } + chunks := ChunkifyCode(code) if len(chunks) != 96 { t.Fatalf("invalid length %d", len(chunks)) } @@ -194,10 +182,7 @@ func TestChunkifyCodeFuzz(t *testing.T) { 3, PUSH32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, } - chunks, err := ChunkifyCode(code) - if err != nil { - t.Fatal(err) - } + chunks := ChunkifyCode(code) if len(chunks) != 32 { t.Fatalf("invalid length %d", len(chunks)) } @@ -210,10 +195,7 @@ func TestChunkifyCodeFuzz(t *testing.T) { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, PUSH32, } - chunks, err = ChunkifyCode(code) - if err != nil { - t.Fatal(err) - } + chunks = ChunkifyCode(code) if len(chunks) != 32 { t.Fatalf("invalid length %d", len(chunks)) } @@ -226,10 +208,7 @@ func TestChunkifyCodeFuzz(t *testing.T) { PUSH4, PUSH32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, } - chunks, err = ChunkifyCode(code) - if err != nil { - t.Fatal(err) - } + chunks = ChunkifyCode(code) if len(chunks) != 64 { t.Fatalf("invalid length %d", len(chunks)) } @@ -245,10 +224,7 @@ func TestChunkifyCodeFuzz(t *testing.T) { PUSH4, PUSH32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, } - chunks, err = ChunkifyCode(code) - if err != nil { - t.Fatal(err) - } + chunks = ChunkifyCode(code) if len(chunks) != 64 { t.Fatalf("invalid length %d", len(chunks)) }