Skip to content

Commit

Permalink
partial-witness-add-touches (#843)
Browse files Browse the repository at this point in the history
* partial-witness-add-touches

* extend opcode-list
  • Loading branch information
kstoykov authored Jul 25, 2024
1 parent 668603d commit b80986a
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 16 deletions.
6 changes: 3 additions & 3 deletions core/state/intra_block_state_zkevm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion core/vm/evmtypes/evmtypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
9 changes: 1 addition & 8 deletions core/vm/instructions_zkevm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
4 changes: 2 additions & 2 deletions core/vm/instructions_zkevm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
72 changes: 70 additions & 2 deletions core/vm/interpreter_zkevm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit b80986a

Please sign in to comment.