From 8c3b26366b70249e0d0d8c44c77c129ab9abf3c6 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Tue, 11 Jan 2022 06:07:45 +0000 Subject: [PATCH 1/4] core/vm: verkle extcodecopy naive way (do jumpdest analysis on target contract every EXTCODECOPY) --- core/vm/analysis.go | 4 ++-- core/vm/contract.go | 13 ++++++++++--- core/vm/gas_table.go | 5 +++-- core/vm/instructions.go | 29 +++++++++++++++-------------- core/vm/interpreter.go | 2 +- 5 files changed, 31 insertions(+), 22 deletions(-) diff --git a/core/vm/analysis.go b/core/vm/analysis.go index 449cded2a896..f1af062e956d 100644 --- a/core/vm/analysis.go +++ b/core/vm/analysis.go @@ -61,8 +61,8 @@ func (bits bitvec) set16(pos uint64) { bits[pos/8+2] = ^a } -// codeSegment checks if the position is in a code segment. -func (bits *bitvec) codeSegment(pos uint64) bool { +// IsCode checks if the position is in a code segment. +func (bits *bitvec) IsCode(pos uint64) bool { return ((*bits)[pos/8] & (0x80 >> (pos % 8))) == 0 } diff --git a/core/vm/contract.go b/core/vm/contract.go index d3f069db888f..5bcdd3a2da3b 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -28,6 +28,13 @@ type ContractRef interface { Address() common.Address } +// AnalyzedContract is an interface for a piece of contract +// code that has undegone jumpdest analysis, and whose bytecode +// can be queried to determine if it is "contract code" +type AnalyzedContract interface { + IsCode(dest uint64) bool +} + // AccountRef implements ContractRef. // // Account references are used during EVM initialisation and @@ -101,7 +108,7 @@ func (c *Contract) validJumpdest(dest *uint256.Int) bool { func (c *Contract) IsCode(udest uint64) bool { // Do we already have an analysis laying around? if c.analysis != nil { - return c.analysis.codeSegment(udest) + return c.analysis.IsCode(udest) } // Do we have a contract hash already? // If we do have a hash, that means it's a 'regular' contract. For regular @@ -117,7 +124,7 @@ func (c *Contract) IsCode(udest uint64) bool { } // Also stash it in current contract for faster access c.analysis = analysis - return analysis.codeSegment(udest) + return analysis.IsCode(udest) } // We don't have the code hash, most likely a piece of initcode not already // in state trie. In that case, we do an analysis, and save it locally, so @@ -126,7 +133,7 @@ func (c *Contract) IsCode(udest uint64) bool { if c.analysis == nil { c.analysis = codeBitmap(c.Code) } - return c.analysis.codeSegment(udest) + return c.analysis.IsCode(udest) } // AsDelegate sets the contract to be a delegate call and returns the current diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 7fe7b92e8bb8..72873c84a1dd 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -121,7 +121,7 @@ func gasCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory uint64Length = 0xffffffffffffffff } _, offset, nonPaddedSize := getDataAndAdjustedBounds(contract.Code, uint64CodeOffset, uint64Length) - statelessGas = touchEachChunksAndChargeGas(offset, nonPaddedSize, contract.Address().Bytes()[:], nil, evm.Accesses) + statelessGas = touchEachChunksAndChargeGas(offset, nonPaddedSize, contract.Address().Bytes()[:], nil, nil, evm.Accesses) } usedGas, err := gasCodeCopyStateful(evm, contract, stack, mem, memorySize) return usedGas + statelessGas, err @@ -133,6 +133,7 @@ func gasExtCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem var ( codeOffset = stack.Back(2) length = stack.Back(3) + targetAddr = stack.Back(0).Bytes20() ) uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() if overflow { @@ -148,7 +149,7 @@ func gasExtCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem // behavior from CODECOPY which only charges witness access costs for the part of the range // which overlaps in the account code. TODO: clarify this is desired behavior and amend the // spec. - statelessGas = touchEachChunksAndChargeGas(uint64CodeOffset, uint64Length, nil, nil, evm.Accesses) + statelessGas = touchEachChunksAndChargeGas(uint64CodeOffset, uint64Length, targetAddr[:], nil, nil, evm.Accesses) } usedGas, err := gasExtCodeCopyStateful(evm, contract, stack, mem, memorySize) return usedGas + statelessGas, err diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 4440aec4b44c..944624817f16 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -19,7 +19,6 @@ package vm import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" trieUtils "github.com/ethereum/go-ethereum/trie/utils" "github.com/holiman/uint256" @@ -373,21 +372,22 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(scope.Contract.Code, uint64CodeOffset, length.Uint64()) if interpreter.evm.Accesses != nil { - touchEachChunksAndChargeGas(copyOffset, nonPaddedCopyLength, scope.Contract.Address().Bytes()[:], scope.Contract, interpreter.evm.Accesses) + statelessGas := touchEachChunksAndChargeGas(copyOffset, nonPaddedCopyLength, scope.Contract.Address().Bytes()[:], scope.Contract.Code, scope.Contract, interpreter.evm.Accesses) + scope.Contract.UseGas(statelessGas) } scope.Memory.Set(memOffset.Uint64(), uint64(len(paddedCodeCopy)), paddedCodeCopy) return nil, nil } // touchEachChunksAndChargeGas is a helper function to touch every chunk in a code range and charge witness gas costs -func touchEachChunksAndChargeGas(offset, size uint64, address []byte, contract *Contract, accesses *types.AccessWitness) uint64 { +func touchEachChunksAndChargeGas(offset, size uint64, address []byte, code []byte, contract AnalyzedContract, accesses *types.AccessWitness) uint64 { // note that in the case where the copied code is outside the range of the // contract code but touches the last leaf with contract code in it, // we don't include the last leaf of code in the AccessWitness. The // reason that we do not need the last leaf is the account's code size // is already in the AccessWitness so a stateless verifier can see that // the code from the last leaf is not needed. - if contract != nil && (size == 0 || offset > uint64(len(contract.Code))) { + if contract != nil && (size == 0 || offset > uint64(len(code))) { return 0 } var ( @@ -397,17 +397,13 @@ func touchEachChunksAndChargeGas(offset, size uint64, address []byte, contract * startOffset uint64 endOffset uint64 numLeaves uint64 - code []byte index [32]byte ) - if contract != nil { - code = contract.Code[:] - } // startLeafOffset, endLeafOffset is the evm code offset of the first byte in the first leaf touched // and the evm code offset of the last byte in the last leaf touched startOffset = offset - (offset % 31) - if contract != nil && startOffset+size > uint64(len(contract.Code)) { - endOffset = uint64(len(contract.Code)) + if contract != nil && startOffset+size > uint64(len(code)) { + endOffset = uint64(len(code)) } else { endOffset = startOffset + size } @@ -431,11 +427,11 @@ func touchEachChunksAndChargeGas(offset, size uint64, address []byte, contract * index[31] = subIndex var value []byte - if contract != nil { + if code != nil { // the offset into the leaf that the first PUSH occurs var firstPushOffset uint64 = 0 // Look for the first code byte (i.e. no pushdata) - for ; firstPushOffset < 31 && firstPushOffset+uint64(i)*31 < uint64(len(contract.Code)) && !contract.IsCode(uint64(i)*31+firstPushOffset); firstPushOffset++ { + for ; firstPushOffset < 31 && firstPushOffset+uint64(i)*31 < uint64(len(code)) && !contract.IsCode(uint64(i)*31+firstPushOffset); firstPushOffset++ { } curEnd := (uint64(i) + 1) * 31 if curEnd > endOffset { @@ -472,7 +468,12 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) } addr := common.Address(a.Bytes20()) if interpreter.evm.Accesses != nil { - log.Warn("setting witness values for extcodecopy is not currently implemented") + code := interpreter.evm.StateDB.GetCode(addr) + paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64()) + cb := codeBitmap(code) + statelessGas := touchEachChunksAndChargeGas(copyOffset, nonPaddedCopyLength, addr[:], code, &cb, interpreter.evm.Accesses) + scope.Contract.UseGas(statelessGas) + scope.Memory.Set(memOffset.Uint64(), length.Uint64(), paddedCodeCopy) } else { codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64()) scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) @@ -977,7 +978,7 @@ func makePush(size uint64, pushByteSize int) executionFunc { } if interpreter.evm.Accesses != nil { - statelessGas := touchEachChunksAndChargeGas(uint64(startMin), uint64(pushByteSize), scope.Contract.Address().Bytes()[:], scope.Contract, interpreter.evm.Accesses) + statelessGas := touchEachChunksAndChargeGas(uint64(startMin), uint64(pushByteSize), scope.Contract.Address().Bytes()[:], scope.Contract.Code, scope.Contract, interpreter.evm.Accesses) scope.Contract.UseGas(statelessGas) } diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 9352d342fc1e..744b0d7e72b9 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -194,7 +194,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( if in.evm.TxContext.Accesses != nil { // if the PC ends up in a new "page" of verkleized code, charge the // associated witness costs. - contract.Gas -= touchEachChunksAndChargeGas(pc, 1, contract.Address().Bytes()[:], contract, in.evm.TxContext.Accesses) + contract.Gas -= touchEachChunksAndChargeGas(pc, 1, contract.Address().Bytes()[:], contract.Code, contract, in.evm.TxContext.Accesses) } // TODO how can we tell if we are in stateless mode here and need to get the op from the witness From 0210a871c44e84771f73f5eec89aa449d50debce Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Tue, 11 Jan 2022 08:08:28 +0000 Subject: [PATCH 2/4] no double-charge --- core/vm/instructions.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 944624817f16..f0f3842af4b9 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -372,8 +372,7 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(scope.Contract.Code, uint64CodeOffset, length.Uint64()) if interpreter.evm.Accesses != nil { - statelessGas := touchEachChunksAndChargeGas(copyOffset, nonPaddedCopyLength, scope.Contract.Address().Bytes()[:], scope.Contract.Code, scope.Contract, interpreter.evm.Accesses) - scope.Contract.UseGas(statelessGas) + touchEachChunksAndChargeGas(copyOffset, nonPaddedCopyLength, scope.Contract.Address().Bytes()[:], scope.Contract.Code, scope.Contract, interpreter.evm.Accesses) } scope.Memory.Set(memOffset.Uint64(), uint64(len(paddedCodeCopy)), paddedCodeCopy) return nil, nil @@ -471,8 +470,7 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) code := interpreter.evm.StateDB.GetCode(addr) paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64()) cb := codeBitmap(code) - statelessGas := touchEachChunksAndChargeGas(copyOffset, nonPaddedCopyLength, addr[:], code, &cb, interpreter.evm.Accesses) - scope.Contract.UseGas(statelessGas) + touchEachChunksAndChargeGas(copyOffset, nonPaddedCopyLength, addr[:], code, &cb, interpreter.evm.Accesses) scope.Memory.Set(memOffset.Uint64(), length.Uint64(), paddedCodeCopy) } else { codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64()) From c10eed3ca8b3abbc02d8afbe8bf9e69c42c2216b Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Wed, 12 Jan 2022 04:14:07 +0000 Subject: [PATCH 3/4] address edge-case in touchEachChunksAndChargeGas --- core/vm/instructions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index f0f3842af4b9..35a86d76539f 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -426,7 +426,7 @@ func touchEachChunksAndChargeGas(offset, size uint64, address []byte, code []byt index[31] = subIndex var value []byte - if code != nil { + if code != nil && len(code) > 0 { // the offset into the leaf that the first PUSH occurs var firstPushOffset uint64 = 0 // Look for the first code byte (i.e. no pushdata) From 36265f45a8b92e7103a12b0408bd222e09bf0826 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Thu, 13 Jan 2022 12:54:18 +0100 Subject: [PATCH 4/4] simplify line --- core/vm/instructions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 35a86d76539f..9767c25c2f27 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -426,7 +426,7 @@ func touchEachChunksAndChargeGas(offset, size uint64, address []byte, code []byt index[31] = subIndex var value []byte - if code != nil && len(code) > 0 { + if len(code) > 0 { // the offset into the leaf that the first PUSH occurs var firstPushOffset uint64 = 0 // Look for the first code byte (i.e. no pushdata)