From b80986abe055c6fbc4fea01564d0fad2a9db93e3 Mon Sep 17 00:00:00 2001 From: Kamen Stoykov <24619432+kstoykov@users.noreply.github.com> Date: Thu, 25 Jul 2024 16:55:54 +0300 Subject: [PATCH] partial-witness-add-touches (#843) * partial-witness-add-touches * extend opcode-list --- core/state/intra_block_state_zkevm.go | 6 +-- core/vm/evmtypes/evmtypes.go | 2 +- core/vm/instructions_zkevm.go | 9 +--- core/vm/instructions_zkevm_test.go | 4 +- core/vm/interpreter_zkevm.go | 72 ++++++++++++++++++++++++++- 5 files changed, 77 insertions(+), 16 deletions(-) diff --git a/core/state/intra_block_state_zkevm.go b/core/state/intra_block_state_zkevm.go index 9dc30ed58dd..0528b05e536 100644 --- a/core/state/intra_block_state_zkevm.go +++ b/core/state/intra_block_state_zkevm.go @@ -152,14 +152,14 @@ func (sdb *IntraBlockState) scalableSetBlockHash(blockNum uint64, blockHash *lib sdb.SetState(ADDRESS_SCALABLE_L2, &mkh, *hashAsBigU) } -func (sdb *IntraBlockState) GetBlockStateRoot(blockNum uint64) libcommon.Hash { - d1 := common.LeftPadBytes(uint256.NewInt(blockNum).Bytes(), 32) +func (sdb *IntraBlockState) GetBlockStateRoot(blockNum *uint256.Int) *uint256.Int { + d1 := common.LeftPadBytes(blockNum.Bytes(), 32) d2 := common.LeftPadBytes(STATE_ROOT_STORAGE_POS.Bytes(), 32) mapKey := keccak256.Hash(d1, d2) mkh := libcommon.BytesToHash(mapKey) hash := uint256.NewInt(0) sdb.GetState(ADDRESS_SCALABLE_L2, &mkh, hash) - return libcommon.BytesToHash(hash.Bytes()) + return hash } func (sdb *IntraBlockState) ScalableSetSmtRootHash(roHermezDb ReadOnlyHermezDb) error { diff --git a/core/vm/evmtypes/evmtypes.go b/core/vm/evmtypes/evmtypes.go index 9815f4a3bd0..9a3e110350f 100644 --- a/core/vm/evmtypes/evmtypes.go +++ b/core/vm/evmtypes/evmtypes.go @@ -108,6 +108,6 @@ type IntraBlockState interface { AddLog(*types.Log) AddLog_zkEvm(*types.Log) GetLogs(hash libcommon.Hash) []*types.Log - GetBlockStateRoot(blockNum uint64) libcommon.Hash + GetBlockStateRoot(blockNum *uint256.Int) *uint256.Int GetBlockNumber() *uint256.Int } diff --git a/core/vm/instructions_zkevm.go b/core/vm/instructions_zkevm.go index f1bc09a85e3..0b261e39d58 100644 --- a/core/vm/instructions_zkevm.go +++ b/core/vm/instructions_zkevm.go @@ -61,16 +61,9 @@ func opExtCodeHash_zkevm(pc *uint64, interpreter *EVMInterpreter, scope *ScopeCo func opBlockhash_zkevm(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { num := scope.Stack.Peek() - num64, overflow := num.Uint64WithOverflow() - if overflow { - num.Clear() - return nil, nil - } ibs := interpreter.evm.IntraBlockState() - hash := ibs.GetBlockStateRoot(num64) - - num.SetFromBig(hash.Big()) + num.Set(ibs.GetBlockStateRoot(num)) return nil, nil } diff --git a/core/vm/instructions_zkevm_test.go b/core/vm/instructions_zkevm_test.go index 5e01c6a69e1..6b4396ab07d 100644 --- a/core/vm/instructions_zkevm_test.go +++ b/core/vm/instructions_zkevm_test.go @@ -143,8 +143,8 @@ func (ibs TestIntraBlockState) GetLogs(hash libcommon.Hash) []*types.Log { panic("implement me") } -func (ibs TestIntraBlockState) GetBlockStateRoot(blockNum uint64) libcommon.Hash { - return libcommon.BigToHash(new(big.Int).SetUint64(blockNum)) +func (ibs TestIntraBlockState) GetBlockStateRoot(blockNum *uint256.Int) *uint256.Int { + return uint256.NewInt(0).Set(blockNum) } func (ibs TestIntraBlockState) GetBlockNumber() *uint256.Int { diff --git a/core/vm/interpreter_zkevm.go b/core/vm/interpreter_zkevm.go index c9a9eadadee..e369222ced3 100644 --- a/core/vm/interpreter_zkevm.go +++ b/core/vm/interpreter_zkevm.go @@ -44,6 +44,39 @@ func getJumpTable(cr *chain.Rules) *JumpTable { return jt } +func shouldExecuteLastOpCode(op OpCode) bool { + switch op { + case BLOCKHASH: + fallthrough + case CODESIZE: + fallthrough + case EXTCODESIZE: + fallthrough + case EXTCODECOPY: + fallthrough + case EXTCODEHASH: + fallthrough + case SELFBALANCE: + fallthrough + case BALANCE: + fallthrough + case CREATE: + fallthrough + case RETURN: + fallthrough + case CREATE2: + fallthrough + case SENDALL: + fallthrough + case SLOAD: + fallthrough + case SSTORE: + return true + } + + return false +} + // NewZKEVMInterpreter returns a new instance of the Interpreter. func NewZKEVMInterpreter(evm VMInterpreter, cfg ZkConfig) *EVMInterpreter { jt := getJumpTable(evm.ChainRules()) @@ -138,10 +171,45 @@ func (in *EVMInterpreter) RunZk(contract *Contract, input []byte, readOnly bool) return } - // execute the operation in case of SLOAD | SSTORE + /* + The code below this line is executed in case of an error => it is reverted. + The single side-effect of this execution (which is reverted anyway) is accounts that are "touched", because "touches" are not reverted and they are needed during witness generation. + */ + + /* + Zkevm detects errors during execution of an opcode. + Cdk-erigon detects some errors (listed below) before execution of an opcode. + => zkevm may execute (partially) 1 additinal opcode compared to cdk-erigon because cdk-erigon detects the errors before trying to execute an opcode. + + In terms of execution - everything is fine because there is an error and everything will be reverted. + In terms of "touched" accounts - there could be some accounts that are not "touched" because the 1 additional opcode is not execute (even partially). + => The additional opcode execution (even partially) could touch more accounts than cdk-erigon + + That's why we must execute the last opcode in order to mimic the zkevm logic. + During this execution (that will be reverted anyway) we may detect panic but instead of stopping the node just ignore the panic. + By ignoring the panic we ensure that we've execute as much as possible of the additional 1 opcode. + */ + + // execute the operation in case of a list of opcodes + executeBecauseOfSpecificOpCodes := shouldExecuteLastOpCode(op) + + // execute the operation in case of early error detection + // _, errorIsUnderflow := err.(*ErrStackUnderflow) + // _, errorIsOverflow := err.(*ErrStackOverflow) + // executeBecauseOfEarlyErrorDetection := errors.Is(err, ErrOutOfGas) || errors.Is(err, ErrGasUintOverflow) || errorIsUnderflow || errorIsOverflow + + // uncommend the live above in order to enable execution based on error types in addition to opcode list + executeBecauseOfEarlyErrorDetection := false + // the actual result of this operation does not matter because it will be reverted anyway, because err != nil // we implement it this way in order execution to be identical to tracing - if op == SLOAD || op == SSTORE { + if executeBecauseOfSpecificOpCodes || executeBecauseOfEarlyErrorDetection { + defer func() { + // the goal if this recover is to catch a panic that could have happen during the execution of "in.jt[op].execute" below + // by ignoring the panic we are effectively executing as much as possible instructions of the last opcode before the error + recover() + }() + // we can safely use pc here instead of pcCopy, // because pc and pcCopy can be different only if the main loop finishes normally without error // but is it finishes normally without error then "ret" != nil and the .execute below will never be invoked at all