bugfix: implement computation snapshot, revert, commit

This commit is contained in:
andri lim 2019-02-17 07:30:02 +07:00 committed by zah
parent 927abdc142
commit 8461211f9a
4 changed files with 89 additions and 92 deletions

View File

@ -90,12 +90,25 @@ proc prepareChildMessage*(
code,
childOptions)
proc applyMessage(computation: var BaseComputation, opCode: static[Op]) =
var transaction = computation.vmState.beginTransaction()
defer: transaction.dispose()
type
ComputationSnapshot* = object
snapshot: Snapshot
computation: BaseComputation
proc snapshot*(computation: BaseComputation): ComputationSnapshot =
result.snapshot = computation.vmState.snapshot()
result.computation = computation
proc revert*(snapshot: var ComputationSnapshot, burnsGas: bool = false) =
snapshot.snapshot.revert()
snapshot.computation.error = Error(info: getCurrentExceptionMsg(), burnsGas: burnsGas)
proc commit*(snapshot: var ComputationSnapshot) =
snapshot.snapshot.commit()
proc applyMessageAux(computation: var BaseComputation, opCode: static[Op]) =
if computation.msg.depth > STACK_DEPTH_LIMIT:
raise newException(StackDepthError, "Stack depth limit reached")
raise newException(StackDepthError, "Stack depth limit reached")
if computation.msg.value != 0:
let senderBalance =
@ -156,65 +169,62 @@ proc applyMessage(computation: var BaseComputation, opCode: static[Op]) =
computation.vmState.mutateStateDb:
db.addBalance(computation.msg.storageAddress, computation.msg.value)
# Run code
# We cannot use the normal dispatching function `executeOpcodes`
# within `interpreter_dispatch.nim` due to a cyclic dependency.
if not computation.execPrecompiles:
computation.opcodeExec(computation)
proc applyMessage(computation: var BaseComputation, opCode: static[Op]) =
var snapshot = computation.snapshot()
try:
computation.applyMessageAux(opCode)
except VMError:
snapshot.revert()
debug "applyMessageAux failed", msg = computation.error.info
return
if not computation.isError:
trace "Computation committed"
transaction.commit()
else:
debug "Computation rolled back due to error"
try:
# Run code
# We cannot use the normal dispatching function `executeOpcodes`
# within `interpreter_dispatch.nim` due to a cyclic dependency.
if not computation.execPrecompiles:
computation.opcodeExec(computation)
snapshot.commit()
except VMError:
snapshot.revert(true)
debug "applyMessage failed", msg = computation.error.info
proc applyCreateMessage(fork: Fork, computation: var BaseComputation, opCode: static[Op]) =
computation.applyMessage(opCode)
if computation.isError: return
var transaction: DbTransaction
defer: transaction.safeDispose()
let contractCode = computation.output
if contractCode.len == 0: return
if fork >= FkFrontier:
transaction = computation.vmState.beginTransaction()
var snapshot = computation.snapshot()
if computation.isError:
return
else:
let contractCode = computation.output
if contractCode.len > 0:
if fork >= FkSpurious and contractCode.len >= EIP170_CODE_SIZE_LIMIT:
raise newException(OutOfGas, &"Contract code size exceeds EIP170 limit of {EIP170_CODE_SIZE_LIMIT}. Got code of size: {contractCode.len}")
if fork >= FkSpurious and contractCode.len >= EIP170_CODE_SIZE_LIMIT:
raise newException(OutOfGas, &"Contract code size exceeds EIP170 limit of {EIP170_CODE_SIZE_LIMIT}. Got code of size: {contractCode.len}")
try:
computation.gasMeter.consumeGas(
computation.gasCosts[Create].m_handler(0, 0, contractCode.len),
reason = "Write contract code for CREATE")
try:
computation.gasMeter.consumeGas(
computation.gasCosts[Create].m_handler(0, 0, contractCode.len),
reason = "Write contract code for CREATE")
let storageAddr = computation.msg.storageAddress
trace "SETTING CODE",
address = storageAddr.toHex,
length = len(contract_code),
hash = contractCode.rlpHash
let storageAddr = computation.msg.storageAddress
trace "SETTING CODE",
address = storageAddr.toHex,
length = len(contract_code),
hash = contractCode.rlpHash
computation.vmState.mutateStateDb:
db.setCode(storageAddr, contractCode.toRange)
computation.vmState.mutateStateDb:
db.setCode(storageAddr, contractCode.toRange)
if transaction != nil:
transaction.commit()
except OutOfGas:
if fork == FkFrontier:
computation.output = @[]
else:
# Different from Frontier:
# Reverts state on gas failure while writing contract code.
# Transaction are reverted automatically by safeDispose.
discard
snapshot.commit()
except OutOfGas:
if fork == FkFrontier:
computation.output = @[]
else:
if transaction != nil:
transaction.commit()
# Different from Frontier:
# Reverts state on gas failure while writing contract code.
snapshot.revert()
proc generateChildComputation*(fork: Fork, computation: BaseComputation, childMsg: Message, opCode: static[Op]): BaseComputation =
proc generateChildComputation*(fork: Fork, computation: var BaseComputation, childMsg: Message, opCode: static[Op]): BaseComputation =
var childComp = newBaseComputation(
computation.vmState,
computation.vmState.blockNumber,
@ -224,37 +234,14 @@ proc generateChildComputation*(fork: Fork, computation: BaseComputation, childMs
# Copy the fork op code executor proc (assumes child computation is in the same fork)
childComp.opCodeExec = computation.opCodeExec
# TODO: use AccountStateDB revert/commit after JournalDB implemented
let stateDb = computation.vmState.accountDb
let intermediateRoot = stateDb.rootHash
computation.vmState.blockHeader.stateRoot = stateDb.rootHash
try:
if childMsg.isCreate:
fork.applyCreateMessage(childComp, opCode)
else:
applyMessage(childComp, opCode)
except VMError:
# weird Nim bug
# cannot use `Error` directly when constructing error object
# it seems the compiler confused in the presence of static[Op]
# param
type ErrorT = vm_types.Error
childComp.error = ErrorT(info: getCurrentExceptionMsg())
if childMsg.isCreate:
debug "childComputation: createMessage() failed", error = getCurrentExceptionMsg()
else:
debug "childComputation: applyMessage() failed", error = getCurrentExceptionMsg()
if childComp.isError:
stateDb.rootHash = intermediateRoot
computation.vmState.blockHeader.stateRoot = intermediateRoot
if childMsg.isCreate:
fork.applyCreateMessage(childComp, opCode)
else:
stateDb.rootHash = computation.vmState.blockHeader.stateRoot
applyMessage(childComp, opCode)
return childComp
proc addChildComputation(fork: Fork, computation: BaseComputation, child: BaseComputation) =
proc addChildComputation(fork: Fork, computation: var BaseComputation, child: BaseComputation) =
if child.isError:
if child.msg.isCreate:
computation.returnData = child.output
@ -276,7 +263,7 @@ proc getFork*(computation: BaseComputation): Fork =
else:
computation.vmState.blockNumber.toFork
proc applyChildComputation*(computation: BaseComputation, childMsg: Message, opCode: static[Op]): BaseComputation =
proc applyChildComputation*(computation: var BaseComputation, childMsg: Message, opCode: static[Op]): BaseComputation =
## Apply the vm message childMsg as a child computation.
let fork = computation.getFork
result = fork.generateChildComputation(computation, childMsg, opCode)

View File

@ -679,9 +679,6 @@ template genCall(callName: untyped, opCode: Op): untyped =
## 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 transaction = computation.vmState.beginTransaction()
defer: transaction.dispose()
let (gas, value, to, sender,
codeAddress,
memoryInputStartPosition, memoryInputSize,
@ -753,8 +750,6 @@ template genCall(callName: untyped, opCode: Op): untyped =
childComputation.output.toOpenArray(0, actualOutputSize - 1))
if not childComputation.shouldBurnGas:
computation.gasMeter.returnGas(childComputation.gasMeter.gasRemaining)
if not isError(computation):
transaction.commit()
genCall(call, Call)
genCall(callCode, CallCode)

View File

@ -130,8 +130,27 @@ template mutateStateDB*(vmState: BaseVMState, body: untyped) =
export DbTransaction, commit, rollback, dispose, safeDispose
proc beginTransaction*(vmState: BaseVMState): DbTransaction =
vmState.chaindb.db.beginTransaction()
type
Snapshot* = object
transaction: DbTransaction
intermediateRoot: Hash256
vmState: BaseVMState
proc snapshot*(vmState: BaseVMState): Snapshot =
# TODO: use AccountStateDB revert/commit after JournalDB implemented
result.transaction = vmState.chaindb.db.beginTransaction()
result.intermediateRoot = vmState.accountDb.rootHash
vmState.blockHeader.stateRoot = vmState.accountDb.rootHash
result.vmState = vmState
proc revert*(s: var Snapshot) =
s.transaction.dispose()
s.vmState.accountDb.rootHash = s.intermediateRoot
s.vmState.blockHeader.stateRoot = s.intermediateRoot
proc commit*(s: var Snapshot) =
s.transaction.commit()
s.vmState.accountDb.rootHash = s.vmState.blockHeader.stateRoot
proc getTracingResult*(vmState: BaseVMState): JsonNode =
assert(vmState.tracingEnabled)

View File

@ -41,25 +41,21 @@ proc setupComputation*(vmState: BaseVMState, transaction: Transaction, sender: E
doAssert result.isOriginComputation
proc execComputation*(computation: var BaseComputation): bool =
# TODO: use AccountStateDB revert/commit after JournalDB implemented
let stateDb = computation.vmState.accountDb
let intermediateRoot = stateDb.rootHash
computation.vmState.blockHeader.stateRoot = stateDb.rootHash
var snapshot = computation.snapshot()
try:
computation.executeOpcodes()
computation.vmState.mutateStateDB:
for deletedAccount in computation.getAccountsForDeletion:
db.deleteAccount deletedAccount
result = not computation.isError
except ValueError:
result = false
if result:
stateDb.rootHash = computation.vmState.blockHeader.stateRoot
snapshot.commit()
else:
stateDb.rootHash = intermediateRoot
snapshot.revert()
proc applyCreateTransaction*(t: Transaction, vmState: BaseVMState, sender: EthAddress, forkOverride=none(Fork)): UInt256 =
doAssert t.isContractCreation