forked from status-im/nimbus-eth1
-
Notifications
You must be signed in to change notification settings - Fork 0
/
vm_state_transactions.nim
193 lines (159 loc) · 7.34 KB
/
vm_state_transactions.nim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http:https://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http:https://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
ranges/typedranges, sequtils, strformat, tables, options,
eth/common, chronicles, ./db/[db_chain, state_db],
constants, errors, transaction, vm_types, vm_state, utils,
./vm/[computation, interpreter], ./vm/interpreter/gas_costs
proc validateTransaction*(vmState: BaseVMState, transaction: Transaction, sender: EthAddress): bool =
# XXX: https://github.com/status-im/nimbus/issues/35#issuecomment-391726518
# XXX: lots of avoidable u256 construction
var readOnlyDB = vmState.readOnlyStateDB
let limitAndValue = transaction.gasLimit.u256 + transaction.value
let gasCost = transaction.gasLimit.u256 * transaction.gasPrice.u256
transaction.gasLimit >= transaction.intrinsicGas and
transaction.gasPrice <= (1 shl 34) and
limitAndValue <= readOnlyDB.getBalance(sender) and
transaction.accountNonce == readOnlyDB.getNonce(sender) and
readOnlyDB.getBalance(sender) >= gasCost
proc setupComputation*(vmState: BaseVMState, tx: Transaction, sender: EthAddress, forkOverride=none(Fork)) : BaseComputation =
let fork =
if forkOverride.isSome:
forkOverride.get
else:
vmState.blockNumber.toFork
var recipient: EthAddress
var gas = tx.gasLimit - tx.intrinsicGas
# TODO: refactor message to use byterange
# instead of seq[byte]
var data, code: seq[byte]
if tx.isContractCreation:
recipient = generateAddress(sender, tx.accountNonce)
gas = gas - gasFees[fork][GasTXCreate]
data = @[]
code = tx.payload
else:
recipient = tx.to
data = tx.payload
code = vmState.readOnlyStateDB.getCode(tx.to).toSeq
let msg = newMessage(
gas = gas,
gasPrice = tx.gasPrice,
to = tx.to,
sender = sender,
value = tx.value,
data = data,
code = code,
options = newMessageOptions(origin = sender,
createAddress = recipient))
result = newBaseComputation(vmState, vmState.blockNumber, msg, forkOverride)
doAssert result.isOriginComputation
proc execComputation*(computation: var BaseComputation): bool =
var snapshot = computation.snapshot()
defer: snapshot.dispose()
computation.vmState.mutateStateDB:
db.subBalance(computation.msg.origin, computation.msg.value)
db.addBalance(computation.msg.storageAddress, computation.msg.value)
try:
computation.executeOpcodes()
computation.vmState.mutateStateDB:
for deletedAccount in computation.getAccountsForDeletion:
db.deleteAccount deletedAccount
result = not computation.isError
except ValueError:
result = false
debug "execComputation() error", msg = getCurrentExceptionMsg()
if result:
snapshot.commit()
else:
snapshot.revert()
proc applyCreateTransaction*(tx: Transaction, vmState: BaseVMState, sender: EthAddress, forkOverride=none(Fork)): GasInt =
doAssert tx.isContractCreation
# TODO: clean up params
trace "Contract creation"
var c = setupComputation(vmState, tx, sender, forkOverride)
if execComputation(c):
var db = vmState.accountDb
# XXX: copy/pasted from GST fixture
# TODO: more merging/refactoring/etc
# also a couple lines can collapse because variable used once
# once verified in GST fixture
let
gasRemaining = c.gasMeter.gasRemaining
gasRefunded = c.getGasRefund()
gasUsed = tx.gasLimit - gasRemaining
gasRefund = min(gasRefunded, gasUsed div 2)
var codeCost = 200 * c.output.len
# This apparently is not supposed to actually consume the gas, just be able to,
# for purposes of accounting. Py-EVM apparently does consume the gas, but it is
# not matching observed blockchain balances if consumeGas is called.
let contractAddress = c.msg.storageAddress
if not c.isSuicided(contractAddress):
# make changes only if it not selfdestructed
if gasRemaining >= codeCost:
db.setCode(contractAddress, c.output.toRange)
else:
# XXX: Homestead behaves differently; reverts state on gas failure
# https://github.com/ethereum/py-evm/blob/master/eth/vm/forks/homestead/computation.py
codeCost = 0
db.setCode(contractAddress, ByteRange())
db.addBalance(sender, (tx.gasLimit - gasUsed - codeCost + gasRefund).u256 * tx.gasPrice.u256)
return (gasUsed + codeCost - gasRefund)
else:
if c.tracingEnabled: c.traceError()
vmState.clearLogs()
return tx.gasLimit
#[
method executeTransaction(vmState: BaseVMState, transaction: Transaction): (BaseComputation, BlockHeader) {.base.}=
# Execute the transaction in the vm
# TODO: introduced here: https://github.com/ethereum/py-evm/commit/21c57f2d56ab91bb62723c3f9ebe291d0b132dde
# Refactored/Removed here: https://github.com/ethereum/py-evm/commit/cc991bf
# Deleted here: https://github.com/ethereum/py-evm/commit/746defb6f8e83cee2c352a0ab8690e1281c4227c
raise newException(ValueError, "Must be implemented by subclasses")
method addTransaction*(vmState: BaseVMState, transaction: Transaction, computation: BaseComputation, b: Block): (Block, Table[string, string]) =
# Add a transaction to the given block and
# return `trieData` to store the transaction data in chaindb in VM layer
# Update the bloomFilter, transaction trie and receipt trie roots, bloom_filter,
# bloom, and usedGas of the block
# transaction: the executed transaction
# computation: the Computation object with executed result
# block: the Block which the transaction is added in
# var receipt = vmState.makeReceipt(transaction, computation)
# vmState.add_receipt(receipt)
# block.transactions.append(transaction)
# # Get trie roots and changed key-values.
# tx_root_hash, tx_kv_nodes = make_trie_root_and_nodes(block.transactions)
# receipt_root_hash, receipt_kv_nodes = make_trie_root_and_nodes(self.receipts)
# trie_data = merge(tx_kv_nodes, receipt_kv_nodes)
# block.bloom_filter |= receipt.bloom
# block.header.transaction_root = tx_root_hash
# block.header.receipt_root = receipt_root_hash
# block.header.bloom = int(block.bloom_filter)
# block.header.gas_used = receipt.gas_used
# return block, trie_data
result = (b, initTable[string, string]())
method applyTransaction*(
vmState: BaseVMState,
transaction: Transaction,
b: Block,
isStateless: bool): (BaseComputation, Block, Table[string, string]) =
# Apply transaction to the given block
# transaction: the transaction need to be applied
# b: the block which the transaction applies on
# isStateless: if isStateless, call vmState.addTransaction to set block
if isStateless:
var ourBlock = b # deepcopy
vmState.blockHeader = b.header
var (computation, blockHeader) = vmState.executeTransaction(transaction)
ourBlock.header = blockHeader
var trieData: Table[string, string]
(ourBlock, trieData) = vmState.addTransaction(transaction, computation, ourBlock)
result = (computation, ourBlock, trieData)
else:
var (computation, blockHeader) = vmState.executeTransaction(transaction)
return (computation, nil, initTable[string, string]())
]#