refactor/relocate CALL/CREATE to reduce stack usage

This commit is contained in:
andri lim 2019-03-14 14:58:26 +07:00
parent 4ed98e03d1
commit 039ab1ce71
No known key found for this signature in database
GPG Key ID: 31702AE10541E6B9
2 changed files with 62 additions and 62 deletions

View File

@ -183,7 +183,7 @@ proc writeContract(fork: Fork, computation: var BaseComputation, opCode: static[
if fork < FkHomestead: computation.output = @[] if fork < FkHomestead: computation.output = @[]
result = false result = false
proc generateChildComputation*(fork: Fork, computation: var BaseComputation, childMsg: Message, opCode: static[Op]): BaseComputation = proc generateChildComputation*(fork: Fork, computation: var BaseComputation, childMsg: Message): BaseComputation =
var childComp = newBaseComputation( var childComp = newBaseComputation(
computation.vmState, computation.vmState,
computation.vmState.blockNumber, computation.vmState.blockNumber,
@ -193,20 +193,6 @@ proc generateChildComputation*(fork: Fork, computation: var BaseComputation, chi
# Copy the fork op code executor proc (assumes child computation is in the same fork) # Copy the fork op code executor proc (assumes child computation is in the same fork)
childComp.opCodeExec = computation.opCodeExec childComp.opCodeExec = computation.opCodeExec
var snapshot = computation.snapshot()
defer: snapshot.dispose()
var contractOK = true
if applyMessage(childComp, opCode):
if childMsg.isCreate:
contractOK = fork.writeContract(childComp, opCode)
if not contractOK and fork == FkHomestead:
# consume all gas
snapshot.revert(true)
else:
snapshot.commit()
return childComp return childComp
proc addChildComputation(fork: Fork, computation: var BaseComputation, child: BaseComputation) = proc addChildComputation(fork: Fork, computation: var BaseComputation, child: BaseComputation) =
@ -234,11 +220,25 @@ proc getFork*(computation: BaseComputation): Fork =
else: else:
computation.vmState.blockNumber.toFork computation.vmState.blockNumber.toFork
proc applyChildComputation*(computation: var BaseComputation, childMsg: Message, opCode: static[Op]): BaseComputation = proc applyChildComputation*(parentComp, childComp: var BaseComputation, opCode: static[Op]) =
## Apply the vm message childMsg as a child computation. ## Apply the vm message childMsg as a child computation.
let fork = computation.getFork let fork = parentComp.getFork
result = fork.generateChildComputation(computation, childMsg, opCode)
fork.addChildComputation(computation, result) var snapshot = parentComp.snapshot()
defer: snapshot.dispose()
var contractOK = true
if applyMessage(childComp, opCode):
if childComp.msg.isCreate:
contractOK = fork.writeContract(childComp, opCode)
if not contractOK and fork == FkHomestead:
# consume all gas
snapshot.revert(true)
else:
snapshot.commit()
fork.addChildComputation(parentComp, childComp)
proc registerAccountForDeletion*(c: var BaseComputation, beneficiary: EthAddress) = proc registerAccountForDeletion*(c: var BaseComputation, beneficiary: EthAddress) =
if c.msg.storageAddress in c.accountsToDelete: if c.msg.storageAddress in c.accountsToDelete:

View File

@ -506,11 +506,7 @@ genLog()
# ########################################## # ##########################################
# f0s: System operations. # f0s: System operations.
op create, inline = false, value, startPosition, size: proc transferBalance(computation: BaseComputation, memPos, len: int, value: Uint256): bool =
## 0xf0, Create a new account with associated code.
# TODO: Forked create for Homestead
let (memPos, len) = (startPosition.cleanMemRef, size.cleanMemRef)
# tricky gasCost: 1,0,0 -> createCost. 0,0,x -> depositCost # tricky gasCost: 1,0,0 -> createCost. 0,0,x -> depositCost
let gasCost = computation.gasCosts[Create].m_handler(1, 0, 0) let gasCost = computation.gasCosts[Create].m_handler(1, 0, 0)
let reason = &"CREATE: GasCreate + {len} * memory expansion" let reason = &"CREATE: GasCreate + {len} * memory expansion"
@ -527,30 +523,15 @@ op create, inline = false, value, startPosition, size:
if senderBalance < value: if senderBalance < value:
debug "Computation Failure", reason = "Insufficient funds available to transfer", required = computation.msg.value, balance = senderBalance debug "Computation Failure", reason = "Insufficient funds available to transfer", required = computation.msg.value, balance = senderBalance
push: 0 return false
return
if computation.msg.depth >= MaxCallDepth: if computation.msg.depth >= MaxCallDepth:
debug "Computation Failure", reason = "Stack too deep", maximumDepth = MaxCallDepth, depth = computation.msg.depth debug "Computation Failure", reason = "Stack too deep", maximumDepth = MaxCallDepth, depth = computation.msg.depth
push: 0 return false
return
##### getBalance type error: expression 'db' is of type: proc (vmState: untyped, readOnly: untyped, handler: untyped): untyped{.noSideEffect, gcsafe, locks: <unknown>.} result = true
# computation.vmState.db(readOnly=true):
# when ForkName >= FkHomestead: # TODO this is done in Geth but not Parity and Py-EVM
# let insufficientFunds = db.getBalance(computation.msg.storageAddress) < value # TODO check gas balance rollover
# let stackTooDeep = computation.msg.depth >= MaxCallDepth
# # TODO: error message
# if insufficientFunds or stackTooDeep:
# push: 0
# return
# else:
# let stackTooDeep = computation.msg.depth >= MaxCallDepth
# if stackTooDeep:
# push: 0
# return
proc setupCreate(computation: var BaseComputation, memPos, len: int, value: Uint256): (BaseComputation, EthAddress) =
let let
callData = computation.memory.read(memPos, len) callData = computation.memory.read(memPos, len)
createMsgGas = computation.getGasRemaining() createMsgGas = computation.getGasRemaining()
@ -588,15 +569,28 @@ op create, inline = false, value, startPosition, size:
) )
childMsg.sender = computation.msg.storageAddress childMsg.sender = computation.msg.storageAddress
let childComputation = computation.applyChildComputation(childMsg, Create) var childComp = generateChildComputation(computation.getFork, computation, childMsg)
result = (childComp, contractAddress)
if childComputation.isError: op create, inline = false, value, startPosition, size:
## 0xf0, Create a new account with associated code.
# TODO: Forked create for Homestead
let (memPos, len) = (startPosition.cleanMemRef, size.cleanMemRef)
if not computation.transferBalance(memPos, len, value):
push: 0
return
var (childComp, contractAddress) = setupCreate(computation, memPos, len, value)
computation.applyChildComputation(childComp, Create)
if childComp.isError:
push: 0 push: 0
else: else:
push: contractAddress push: contractAddress
if not childComputation.shouldBurnGas: if not childComp.shouldBurnGas:
computation.gasMeter.returnGas(childComputation.gasMeter.gasRemaining) computation.gasMeter.returnGas(childComp.gasMeter.gasRemaining)
proc callParams(computation: var BaseComputation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, MsgFlags) = proc callParams(computation: var BaseComputation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, MsgFlags) =
let gas = computation.stack.popInt() let gas = computation.stack.popInt()
@ -680,11 +674,7 @@ proc staticCallParams(computation: var BaseComputation): (UInt256, UInt256, EthA
emvcStatic) # is_static emvcStatic) # is_static
template genCall(callName: untyped, opCode: Op): untyped = template genCall(callName: untyped, opCode: Op): untyped =
op callName, inline = false: proc `callName Setup`(computation: var BaseComputation, callNameStr: string): (BaseComputation, int, int) =
## CALL, 0xf1, Message-Call into an account
## CALLCODE, 0xf2, Message-call into this account with an alternative account's code.
## DELEGATECALL, 0xf4, Message-call into this account with an alternative account's code, but persisting the current values for sender and value.
## STATICCALL, 0xfa, Static message-call into an account.
let (gas, value, to, sender, let (gas, value, to, sender,
codeAddress, codeAddress,
memoryInputStartPosition, memoryInputSize, memoryInputStartPosition, memoryInputSize,
@ -704,7 +694,7 @@ template genCall(callName: untyped, opCode: Op): untyped =
(memOutPos, memOutLen) (memOutPos, memOutLen)
if gas > high(GasInt).u256: if gas > high(GasInt).u256:
raise newException(TypeError, "GasInt Overflow (" & callName.astToStr & ")") raise newException(TypeError, "GasInt Overflow (" & callNameStr & ")")
let (childGasFee, childGasLimit) = computation.gasCosts[opCode].c_handler( let (childGasFee, childGasLimit) = computation.gasCosts[opCode].c_handler(
value, value,
@ -718,12 +708,12 @@ template genCall(callName: untyped, opCode: Op): untyped =
c_opCode: opCode c_opCode: opCode
)) ))
trace "Call (" & callName.astToStr & ")", childGasLimit, childGasFee #trace "Call (" & callNameStr & ")", childGasLimit, childGasFee
if childGasFee >= 0: if childGasFee >= 0:
computation.gasMeter.consumeGas(childGasFee, reason = $opCode) computation.gasMeter.consumeGas(childGasFee, reason = $opCode)
if childGasFee < 0 and childGasLimit <= 0: if childGasFee < 0 and childGasLimit <= 0:
raise newException(OutOfGas, "Gas not enough to perform calculation (" & callName.astToStr & ")") raise newException(OutOfGas, "Gas not enough to perform calculation (" & callNameStr & ")")
computation.memory.extend(memInPos, memInLen) computation.memory.extend(memInPos, memInLen)
computation.memory.extend(memOutPos, memOutLen) computation.memory.extend(memOutPos, memOutLen)
@ -754,20 +744,30 @@ template genCall(callName: untyped, opCode: Op): untyped =
when opCode == CallCode: when opCode == CallCode:
childMsg.storageAddress = computation.msg.storageAddress childMsg.storageAddress = computation.msg.storageAddress
var childComputation = applyChildComputation(computation, childMsg, opCode) var childComp = generateChildComputation(computation.getFork, computation, childMsg)
result = (childComp, memOutPos, memOutLen)
if childComputation.isError: op callName, inline = false:
## CALL, 0xf1, Message-Call into an account
## CALLCODE, 0xf2, Message-call into this account with an alternative account's code.
## DELEGATECALL, 0xf4, Message-call into this account with an alternative account's code, but persisting the current values for sender and value.
## STATICCALL, 0xfa, Static message-call into an account.
var (childComp, memOutPos, memOutLen) = `callName Setup`(computation, callName.astToStr)
applyChildComputation(computation, childComp, opCode)
if childComp.isError:
push: 0 push: 0
else: else:
push: 1 push: 1
if not childComputation.shouldEraseReturnData: if not childComp.shouldEraseReturnData:
let actualOutputSize = min(memOutLen, childComputation.output.len) let actualOutputSize = min(memOutLen, childComp.output.len)
computation.memory.write( computation.memory.write(
memOutPos, memOutPos,
childComputation.output.toOpenArray(0, actualOutputSize - 1)) childComp.output.toOpenArray(0, actualOutputSize - 1))
if not childComputation.shouldBurnGas: if not childComp.shouldBurnGas:
computation.gasMeter.returnGas(childComputation.gasMeter.gasRemaining) computation.gasMeter.returnGas(childComp.gasMeter.gasRemaining)
if computation.gasMeter.gasRemaining <= 0: if computation.gasMeter.gasRemaining <= 0:
raise newException(OutOfGas, "computation out of gas after contract call (" & callName.astToStr & ")") raise newException(OutOfGas, "computation out of gas after contract call (" & callName.astToStr & ")")