forked from ethereum/pyethereum
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathprocessblock.py
353 lines (299 loc) · 13.3 KB
/
processblock.py
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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
import sys
import rlp
from rlp.sedes import CountableList, binary
from rlp.utils import decode_hex, ascii_chr
from ethereum.utils import encode_hex
from ethereum import opcodes
from ethereum import utils
from ethereum import specials
from ethereum import bloom
from ethereum import vm as vm
from ethereum.exceptions import InvalidNonce, InsufficientStartGas, UnsignedTransaction, \
BlockGasLimitReached, InsufficientBalance, VerificationFailed
from ethereum.utils import safe_ord, normalize_address, mk_contract_address, \
mk_metropolis_contract_address, big_endian_to_int
from ethereum import transactions
import ethereum.config as config
sys.setrecursionlimit(100000)
from ethereum.slogging import get_logger
log_tx = get_logger('eth.pb.tx')
log_msg = get_logger('eth.pb.msg')
log_state = get_logger('eth.pb.msg.state')
TT255 = 2 ** 255
TT256 = 2 ** 256
TT256M1 = 2 ** 256 - 1
OUT_OF_GAS = -1
# contract creating transactions send to an empty address
CREATE_CONTRACT_ADDRESS = b''
def verify(block, parent):
from ethereum import blocks
try:
block2 = rlp.decode(rlp.encode(block), blocks.Block,
env=parent.env, parent=parent)
assert block == block2
return True
except VerificationFailed:
return False
class Log(rlp.Serializable):
# TODO: original version used zpad (here replaced by int32.serialize); had
# comment "why zpad"?
fields = [
('address', utils.address),
('topics', CountableList(utils.int32)),
('data', binary)
]
def __init__(self, address, topics, data):
if len(address) == 40:
address = decode_hex(address)
assert len(address) == 20
super(Log, self).__init__(address, topics, data)
def bloomables(self):
return [self.address] + [utils.int32.serialize(x) for x in self.topics]
def to_dict(self):
return {
"bloom": encode_hex(bloom.b64(bloom.bloom_from_list(self.bloomables()))),
"address": encode_hex(self.address),
"data": b'0x' + encode_hex(self.data),
"topics": [encode_hex(utils.int32.serialize(t))
for t in self.topics]
}
def __repr__(self):
return '<Log(address=%r, topics=%r, data=%r)>' % \
(encode_hex(self.address), self.topics, self.data)
def validate_transaction(block, tx):
def rp(what, actual, target):
return '%r: %r actual:%r target:%r' % (tx, what, actual, target)
# (1) The transaction signature is valid;
if not tx.sender: # sender is set and validated on Transaction initialization
if block.number >= config.default_config["METROPOLIS_FORK_BLKNUM"]:
tx._sender = normalize_address(config.default_config["METROPOLIS_ENTRY_POINT"])
else:
raise UnsignedTransaction(tx)
if block.number >= config.default_config["HOMESTEAD_FORK_BLKNUM"]:
tx.check_low_s()
# (2) the transaction nonce is valid (equivalent to the
# sender account's current nonce);
acctnonce = block.get_nonce(tx.sender)
if acctnonce != tx.nonce:
raise InvalidNonce(rp('nonce', tx.nonce, acctnonce))
# (3) the gas limit is no smaller than the intrinsic gas,
# g0, used by the transaction;
if tx.startgas < tx.intrinsic_gas_used:
raise InsufficientStartGas(rp('startgas', tx.startgas, tx.intrinsic_gas_used))
# (4) the sender account balance contains at least the
# cost, v0, required in up-front payment.
total_cost = tx.value + tx.gasprice * tx.startgas
if block.get_balance(tx.sender) < total_cost:
raise InsufficientBalance(rp('balance', block.get_balance(tx.sender), total_cost))
# check block gas limit
if block.gas_used + tx.startgas > block.gas_limit:
raise BlockGasLimitReached(rp('gaslimit', block.gas_used + tx.startgas, block.gas_limit))
return True
class lazy_safe_encode(object):
"""Creates a lazy and logging safe representation of transaction data.
Use this in logging of transactions; instead of
>>> log.debug(data=data)
do this:
>>> log.debug(data=lazy_safe_encode(data))
"""
def __init__(self, data):
self.data = data
def __str__(self):
if not isinstance(self.data, (str, unicode)):
return repr(self.data)
else:
return encode_hex(self.data)
def __repr__(self):
return str(self)
def apply_transaction(block, tx):
validate_transaction(block, tx)
# print(block.get_nonce(tx.sender), '@@@')
def rp(what, actual, target):
return '%r: %r actual:%r target:%r' % (tx, what, actual, target)
intrinsic_gas = tx.intrinsic_gas_used
if block.number >= block.config['HOMESTEAD_FORK_BLKNUM']:
assert tx.s * 2 < transactions.secpk1n
if not tx.to or tx.to == CREATE_CONTRACT_ADDRESS:
intrinsic_gas += opcodes.CREATE[3]
if tx.startgas < intrinsic_gas:
raise InsufficientStartGas(rp('startgas', tx.startgas, intrinsic_gas))
log_tx.debug('TX NEW', tx_dict=tx.log_dict())
# start transacting #################
block.increment_nonce(tx.sender)
# buy startgas
assert block.get_balance(tx.sender) >= tx.startgas * tx.gasprice
block.delta_balance(tx.sender, -tx.startgas * tx.gasprice)
message_gas = tx.startgas - intrinsic_gas
message_data = vm.CallData([safe_ord(x) for x in tx.data], 0, len(tx.data))
message = vm.Message(tx.sender, tx.to, tx.value, message_gas, message_data, code_address=tx.to)
# MESSAGE
ext = VMExt(block, tx)
if tx.to and tx.to != CREATE_CONTRACT_ADDRESS:
result, gas_remained, data = apply_msg(ext, message)
log_tx.debug('_res_', result=result, gas_remained=gas_remained, data=lazy_safe_encode(data))
else: # CREATE
result, gas_remained, data = create_contract(ext, message)
assert utils.is_numeric(gas_remained)
log_tx.debug('_create_', result=result, gas_remained=gas_remained, data=lazy_safe_encode(data))
assert gas_remained >= 0
log_tx.debug("TX APPLIED", result=result, gas_remained=gas_remained,
data=lazy_safe_encode(data))
if not result: # 0 = OOG failure in both cases
log_tx.debug('TX FAILED', reason='out of gas',
startgas=tx.startgas, gas_remained=gas_remained)
block.gas_used += tx.startgas
block.delta_balance(block.coinbase, tx.gasprice * tx.startgas)
output = b''
success = 0
else:
log_tx.debug('TX SUCCESS', data=lazy_safe_encode(data))
gas_used = tx.startgas - gas_remained
block.refunds += len(set(block.suicides)) * opcodes.GSUICIDEREFUND
if block.refunds > 0:
log_tx.debug('Refunding', gas_refunded=min(block.refunds, gas_used // 2))
gas_remained += min(block.refunds, gas_used // 2)
gas_used -= min(block.refunds, gas_used // 2)
block.refunds = 0
# sell remaining gas
block.delta_balance(tx.sender, tx.gasprice * gas_remained)
block.delta_balance(block.coinbase, tx.gasprice * gas_used)
block.gas_used += gas_used
if tx.to:
output = b''.join(map(ascii_chr, data))
else:
output = data
success = 1
block.commit_state()
suicides = block.suicides
block.suicides = []
for s in suicides:
block.ether_delta -= block.get_balance(s)
block.set_balance(s, 0)
block.del_account(s)
block.add_transaction_to_list(tx)
block.logs = []
return success, output
# External calls that can be made from inside the VM. To use the EVM with a
# different blockchain system, database, set parameters for testing, just
# swap out the functions here
class VMExt():
def __init__(self, block, tx):
self._block = block
self.get_code = block.get_code
self.get_balance = block.get_balance
self.set_balance = block.set_balance
self.get_nonce = block.get_nonce
self.set_nonce = block.set_nonce
self.set_storage_data = block.set_storage_data
self.get_storage_data = block.get_storage_data
self.log_storage = lambda x: block.account_to_dict(x)['storage']
self.add_suicide = lambda x: block.suicides.append(x)
self.add_refund = lambda x: \
setattr(block, 'refunds', block.refunds + x)
self.block_hash = lambda x: block.get_ancestor_hash(block.number - x) \
if (1 <= block.number - x <= 256 and x <= block.number) else b''
self.block_coinbase = block.coinbase
self.block_timestamp = block.timestamp
self.block_number = block.number
self.block_difficulty = block.difficulty
self.block_gas_limit = block.gas_limit
self.log = lambda addr, topics, data: \
block.add_log(Log(addr, topics, data))
self.tx_origin = tx.sender
self.tx_gasprice = tx.gasprice
self.create = lambda msg: create_contract(self, msg)
self.msg = lambda msg: _apply_msg(self, msg, self.get_code(msg.code_address))
self.account_exists = block.account_exists
self.post_homestead_hardfork = block.number >= block.config['HOMESTEAD_FORK_BLKNUM']
self.post_anti_dos_hardfork = block.number >= block.config['ANTI_DOS_FORK_BLKNUM']
def apply_msg(ext, msg):
return _apply_msg(ext, msg, ext.get_code(msg.code_address))
def _apply_msg(ext, msg, code):
trace_msg = log_msg.is_active('trace')
if trace_msg:
log_msg.debug("MSG APPLY", sender=encode_hex(msg.sender), to=encode_hex(msg.to),
gas=msg.gas, value=msg.value,
data=encode_hex(msg.data.extract_all()))
if log_state.is_active('trace'):
log_state.trace('MSG PRE STATE SENDER', account=encode_hex(msg.sender),
bal=ext.get_balance(msg.sender),
state=ext.log_storage(msg.sender))
log_state.trace('MSG PRE STATE RECIPIENT', account=encode_hex(msg.to),
bal=ext.get_balance(msg.to),
state=ext.log_storage(msg.to))
# log_state.trace('CODE', code=code)
# Transfer value, instaquit if not enough
snapshot = ext._block.snapshot()
if msg.transfers_value:
if not ext._block.transfer_value(msg.sender, msg.to, msg.value):
log_msg.debug('MSG TRANSFER FAILED', have=ext.get_balance(msg.to),
want=msg.value)
return 1, msg.gas, []
# Main loop
if msg.code_address in specials.specials:
res, gas, dat = specials.specials[msg.code_address](ext, msg)
else:
res, gas, dat = vm.vm_execute(ext, msg, code)
# gas = int(gas)
# assert utils.is_numeric(gas)
if trace_msg:
log_msg.debug('MSG APPLIED', gas_remained=gas,
sender=encode_hex(msg.sender), to=encode_hex(msg.to), data=dat)
if log_state.is_active('trace'):
log_state.trace('MSG POST STATE SENDER', account=encode_hex(msg.sender),
bal=ext.get_balance(msg.sender),
state=ext.log_storage(msg.sender))
log_state.trace('MSG POST STATE RECIPIENT', account=encode_hex(msg.to),
bal=ext.get_balance(msg.to),
state=ext.log_storage(msg.to))
if res == 0:
log_msg.debug('REVERTING')
ext._block.revert(snapshot)
return res, gas, dat
def create_contract(ext, msg):
log_msg.debug('CONTRACT CREATION')
#print('CREATING WITH GAS', msg.gas)
sender = decode_hex(msg.sender) if len(msg.sender) == 40 else msg.sender
code = msg.data.extract_all()
if ext._block.number >= ext._block.config['METROPOLIS_FORK_BLKNUM']:
msg.to = mk_metropolis_contract_address(msg.sender, code)
if ext.get_code(msg.to):
if ext.get_nonce(msg.to) >= 2 ** 40:
ext.set_nonce(msg.to, (ext.get_nonce(msg.to) + 1) % 2 ** 160)
msg.to = normalize_address((ext.get_nonce(msg.to) - 1) % 2 ** 160)
else:
ext.set_nonce(msg.to, (big_endian_to_int(msg.to) + 2) % 2 ** 160)
msg.to = normalize_address((ext.get_nonce(msg.to) - 1) % 2 ** 160)
else:
if ext.tx_origin != msg.sender:
ext._block.increment_nonce(msg.sender)
nonce = utils.encode_int(ext._block.get_nonce(msg.sender) - 1)
msg.to = mk_contract_address(sender, nonce)
b = ext.get_balance(msg.to)
if b > 0:
ext.set_balance(msg.to, b)
ext._block.set_nonce(msg.to, 0)
ext._block.set_code(msg.to, b'')
ext._block.reset_storage(msg.to)
msg.is_create = True
# assert not ext.get_code(msg.to)
msg.data = vm.CallData([], 0, 0)
snapshot = ext._block.snapshot()
res, gas, dat = _apply_msg(ext, msg, code)
assert utils.is_numeric(gas)
if res:
if not len(dat):
return 1, gas, msg.to
gcost = len(dat) * opcodes.GCONTRACTBYTE
if gas >= gcost:
gas -= gcost
else:
dat = []
log_msg.debug('CONTRACT CREATION OOG', have=gas, want=gcost, block_number=ext._block.number)
if ext._block.number >= ext._block.config['HOMESTEAD_FORK_BLKNUM']:
ext._block.revert(snapshot)
return 0, 0, b''
ext._block.set_code(msg.to, b''.join(map(ascii_chr, dat)))
return 1, gas, msg.to
else:
return 0, gas, b''