Skip to content
This repository was archived by the owner on May 23, 2023. It is now read-only.

Commit 16e2a51

Browse files
committed
Added basic state tree pruning to chain class
1 parent 3323d11 commit 16e2a51

File tree

7 files changed

+114
-81
lines changed

7 files changed

+114
-81
lines changed

ethereum/common.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
from ethereum.messages import apply_transaction
1111
log = get_logger('eth.block')
1212

13+
SKIP_RECEIPT_ROOT_VALIDATION = False
14+
1315
# Gas limit adjustment algo
1416
def calc_gaslimit(parent, config=default_config):
1517
decay = parent.gas_limit // config['GASLIMIT_EMA_FACTOR']
@@ -143,7 +145,7 @@ def verify_execution_results(state, block):
143145
if block.header.state_root != state.trie.root_hash:
144146
raise ValueError("State root mismatch: header %s computed %s" %
145147
(encode_hex(block.header.state_root), encode_hex(state.trie.root_hash)))
146-
if block.header.receipts_root != mk_receipt_sha(state.receipts):
148+
if block.header.receipts_root != mk_receipt_sha(state.receipts) and not SKIP_RECEIPT_ROOT_VALIDATION:
147149
raise ValueError("Receipt root mismatch: header %s computed %s, gas used header %d computed %d, %d receipts" %
148150
(encode_hex(block.header.receipts_root), encode_hex(mk_receipt_sha(state.receipts)),
149151
block.header.gas_used, state.gas_used, len(state.receipts)))

ethereum/messages.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
CREATE_CONTRACT_ADDRESS = b''
4141

4242
# DEV OPTIONS
43-
SKIP_RECEIPT_ROOT_VALIDATION = False
4443
SKIP_MEDSTATES = False
4544

4645
def rp(tx, what, actual, target):

ethereum/pow/chain.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
class Chain(object):
3030

3131
def __init__(self, genesis=None, env=None, \
32-
new_head_cb=None, reset_genesis=False, localtime=None, **kwargs):
32+
new_head_cb=None, reset_genesis=False, localtime=None, max_history=1000, **kwargs):
3333
self.env = env or Env()
3434
# Initialize the state
3535
if 'head_hash' in self.db: # new head tag
@@ -82,6 +82,7 @@ def __init__(self, genesis=None, env=None, \
8282
self.time_queue = []
8383
self.parent_queue = {}
8484
self.localtime = time.time() if localtime is None else localtime
85+
self.max_history = max_history
8586

8687
# Head (tip) of the chain
8788
@property
@@ -246,6 +247,7 @@ def add_block(self, block):
246247
(now, block.header.timestamp, block.header.timestamp - now))
247248
return False
248249
# Is the block being added to the head?
250+
self.state.deletes = []
249251
if block.header.prevhash == self.head_hash:
250252
log.info('Adding to head', head=encode_hex(block.header.prevhash))
251253
try:
@@ -260,6 +262,7 @@ def add_block(self, block):
260262
for i, tx in enumerate(block.transactions):
261263
self.db.put(b'txindex:' + tx.hash, rlp.encode([block.number, i]))
262264
assert self.get_blockhash_by_number(block.header.number) == block.header.hash
265+
deletes = self.state.deletes
263266
# Or is the block being added to a chain that is not currently the head?
264267
elif block.header.prevhash in self.env.db:
265268
log.info('Receiving block not on head, adding to secondary post state',
@@ -271,6 +274,7 @@ def add_block(self, block):
271274
log.info('Block %s with parent %s invalid, reason: %s' %
272275
(encode_hex(block.header.hash), encode_hex(block.header.prevhash), e))
273276
return False
277+
deletes = temp_state.deletes
274278
block_score = self.get_score(block)
275279
# If the block should be the new head, replace the head
276280
if block_score > self.get_score(self.head):
@@ -313,12 +317,28 @@ def add_block(self, block):
313317
if block.header.prevhash not in self.parent_queue:
314318
self.parent_queue[block.header.prevhash] = []
315319
self.parent_queue[block.header.prevhash].append(block)
316-
log.info('No parent found. Delaying for now')
320+
log.info('Got block %d (%s) with prevhash %s, parent not found. Delaying for now' %
321+
(block.number, encode_hex(block.hash), encode_hex(block.prevhash)))
317322
return False
318323
self.add_child(block)
319324
self.db.put('head_hash', self.head_hash)
320-
self.db.put(block.header.hash, rlp.encode(block))
325+
self.db.put(block.hash, rlp.encode(block))
326+
self.db.put(b'deletes:'+block.hash, b''.join(deletes))
327+
print('Saved %d trie node deletes for block %d (%s)' % (len(deletes), block.number, utils.encode_hex(block.hash)))
328+
# Delete old junk data
329+
old_block_hash = self.get_blockhash_by_number(block.number - self.max_history)
330+
if old_block_hash:
331+
try:
332+
deletes = self.db.get(b'deletes:'+old_block_hash)
333+
print('Deleting %d trie nodes' % (len(deletes) // 32))
334+
for i in range(0, len(deletes), 32):
335+
self.db.delete(deletes[i: i+32])
336+
self.db.delete(b'deletes:'+old_block_hash)
337+
except KeyError as e:
338+
print(e)
339+
pass
321340
self.db.commit()
341+
assert (b'deletes:'+block.hash) in self.db
322342
log.info('Added block %d (%s) with %d txs and %d gas' % \
323343
(block.header.number, encode_hex(block.header.hash)[:8],
324344
len(block.transactions), block.header.gas_used))

ethereum/securetrie.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ def root_hash(self):
4141
def root_hash(self, value):
4242
self.trie.root_hash = value
4343

44+
@property
45+
def deletes(self):
46+
return self.trie.deletes
47+
48+
@deletes.setter
49+
def deletes(self, value):
50+
self.trie.deletes = value
51+
4452
def process_epoch(self, epoch):
4553
self.trie.process_epoch(epoch)
4654

ethereum/state.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,13 @@ class Account(rlp.Serializable):
5858
('code_hash', hash32)
5959
]
6060

61-
def __init__(self, nonce, balance, storage, code_hash, env):
61+
def __init__(self, nonce, balance, storage, code_hash, env, address):
6262
assert isinstance(env.db, BaseDB)
6363
self.env = env
64+
self.address = address
6465
super(Account, self).__init__(nonce, balance, storage, code_hash)
6566
self.storage_cache = {}
66-
self.storage_trie = SecureTrie(Trie(self.env.db))
67+
self.storage_trie = SecureTrie(Trie(self.env.db, prefix=address))
6768
self.storage_trie.root_hash = self.storage
6869
self.touched = False
6970
self.existent_at_start = True
@@ -102,9 +103,9 @@ def set_storage_data(self, key, value):
102103
self.storage_cache[key] = value
103104

104105
@classmethod
105-
def blank_account(cls, env, initial_nonce=0):
106+
def blank_account(cls, env, address, initial_nonce=0):
106107
env.db.put(BLANK_HASH, b'')
107-
o = cls(initial_nonce, 0, trie.BLANK_ROOT, BLANK_HASH, env)
108+
o = cls(initial_nonce, 0, trie.BLANK_ROOT, BLANK_HASH, env, address)
108109
o.existent_at_start = False
109110
return o
110111

@@ -136,6 +137,7 @@ def __init__(self, root=b'', env=Env(), **kwargs):
136137
self.journal = []
137138
self.cache = {}
138139
self.log_listeners = []
140+
self.deletes = []
139141

140142
@property
141143
def db(self):
@@ -160,9 +162,9 @@ def get_and_cache_account(self, address):
160162
return self.cache[address]
161163
rlpdata = self.trie.get(address)
162164
if rlpdata != trie.BLANK_NODE:
163-
o = rlp.decode(rlpdata, Account, env=self.env)
165+
o = rlp.decode(rlpdata, Account, env=self.env, address=address)
164166
else:
165-
o = Account.blank_account(self.env, self.config['ACCOUNT_INITIAL_NONCE'])
167+
o = Account.blank_account(self.env, address, self.config['ACCOUNT_INITIAL_NONCE'])
166168
self.cache[address] = o
167169
o._mutable = True
168170
o._cached_rlp = None
@@ -315,12 +317,15 @@ def commit(self, allow_empties=False):
315317
for addr, acct in self.cache.items():
316318
if acct.touched:
317319
acct.commit()
320+
self.deletes.extend(acct.storage_trie.deletes)
318321
if acct.exists or allow_empties or (not self.is_SPURIOUS_DRAGON() and not acct.deleted):
319322
# print('upd', encode_hex(addr))
320323
self.trie.update(addr, rlp.encode(acct))
321324
else:
322325
# print('del', encode_hex(addr))
323326
self.trie.delete(addr)
327+
self.deletes.extend(self.trie.deletes)
328+
self.trie.deletes = []
324329
self.cache = {}
325330
self.journal = []
326331

ethereum/todo_tests/tst_frontier.py

Lines changed: 62 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
1-
from ethereum import parse_genesis_declaration, db
1+
from ethereum import genesis_helpers, db
22
from ethereum.block import Block, BlockHeader
33
from ethereum.config import Env
4-
import ethereum.state_transition as state_transition
5-
from ethereum import chain
4+
import ethereum.messages as messages
5+
import ethereum.common as common
6+
from pyethapp.leveldb_service import LevelDB
7+
from ethereum.pow import chain
68
import rlp
79
import json
810
import os
911
import sys
1012
import time
13+
import random
1114

1215
# from ethereum.slogging import LogRecorder, configure_logging, set_level
1316
# config_string = ':info,eth.vm.log:trace,eth.vm.op:trace,eth.vm.stack:trace,eth.vm.exit:trace,eth.pb.msg:trace,eth.pb.tx:debug'
1417
# configure_logging(config_string=config_string)
1518

16-
state_transition.SKIP_MEDSTATES = True
17-
state_transition.SKIP_RECEIPT_ROOT_VALIDATION = True
19+
messages.SKIP_MEDSTATES = True
20+
common.SKIP_RECEIPT_ROOT_VALIDATION = True
1821
# assert not state_transition.SKIP_MEDSTATES or state_transition.SKIP_RECEIPT_ROOT_VALIDATION
1922

2023
STATE_LOAD_FN = 'saved_state.json'
@@ -33,55 +36,50 @@
3336
if '--benchmark' in sys.argv:
3437
BENCHMARK = int(sys.argv[sys.argv.index('--benchmark') + 1])
3538

39+
DB_DIR = '/tmp/%d' % random.randrange(int(time.time() * 1000000))
40+
if '--db' in sys.argv:
41+
DB_DIR = int(sys.argv[sys.argv.index('--db') + 1])
42+
3643
_path, _file = os.path.split(STATE_LOAD_FN)
3744
if _file in os.listdir(os.path.join(os.getcwd(), _path)):
38-
print 'loading state from %s ...' % STATE_LOAD_FN
45+
print('loading state from %s ...' % STATE_LOAD_FN)
3946
c = chain.Chain(json.load(open(STATE_LOAD_FN)), Env())
40-
print 'loaded.'
47+
print('loaded.')
4148
elif 'genesis_frontier.json' not in os.listdir(os.getcwd()):
42-
print 'Please download genesis_frontier.json from ' + \
43-
'http://vitalik.ca/files/genesis_frontier.json'
49+
print('Please download genesis_frontier.json from ' + \
50+
'http://vitalik.ca/files/genesis_frontier.json')
4451
sys.exit()
4552
else:
46-
c = chain.Chain(json.load(open('genesis_frontier.json')), Env())
53+
c = chain.Chain(json.load(open('genesis_frontier.json')), Env(LevelDB(DB_DIR)))
4754
assert c.state.trie.root_hash.encode('hex') == \
4855
'd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544'
4956
assert c.state.prev_headers[0].hash.encode('hex') == \
5057
'd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3'
51-
print 'state generated from genesis'
52-
print 'Attempting to open %s' % RLP_BLOCKS_FILE
58+
print('state generated from genesis')
59+
print('Attempting to open %s' % RLP_BLOCKS_FILE)
5360
_path, _file = os.path.split(RLP_BLOCKS_FILE)
5461
if not _path or _file not in os.listdir(_path):
55-
print 'Please download 200kblocks.rlp from http://vitalik.ca/files/200kblocks.rlp ' + \
56-
'and put it in this directory to continue the test'
62+
print('Please download 200kblocks.rlp from http://vitalik.ca/files/200kblocks.rlp ' + \
63+
'and put it in this directory to continue the test')
5764
sys.exit()
5865

59-
batch_size = 1024 * 10240 # approximately 10000 blocks
6066
f = open(RLP_BLOCKS_FILE)
6167

62-
# skip already processed blocks
63-
skip = c.state.block_number + 1
64-
print 'Skipping %d' % skip
65-
count = 0
66-
block_rlps = f.readlines(batch_size)
67-
while len(block_rlps) > 0:
68-
if len(block_rlps) + count <= skip:
69-
count += len(block_rlps)
70-
block_rlps = f.readlines(batch_size)
71-
else:
72-
block_rlps = block_rlps[skip - count:]
73-
count = skip
74-
break
75-
print "skipped %d processed blocks" % skip
76-
68+
pos = 0
69+
block_source_data = f.read()
70+
block_rlps = []
71+
while pos < len(block_source_data):
72+
_, l1, l2 = rlp.codec.consume_length_prefix(block_source_data, pos)
73+
block_rlps.append(block_source_data[pos: l1+l2])
74+
pos = l1 + l2
7775

7876
def report(st, num_blks, num_txs, gas_used):
7977
now = time.time()
8078
elapsed = now - st
8179
tps = num_txs / elapsed
8280
bps = num_blks / elapsed
8381
gps = gas_used / elapsed
84-
print '%.2f >>> elapsed:%d blocks:%d txs:%d gas:%d bps:%d tps:%d gps:%d' % (now, elapsed, num_blks, num_txs, gas_used, bps, tps, gps)
82+
print('%.2f >>> elapsed:%d blocks:%d txs:%d gas:%d bps:%d tps:%d gps:%d' % (now, elapsed, num_blks, num_txs, gas_used, bps, tps, gps))
8583

8684

8785
def check_snapshot_consistency(snapshot, env=None):
@@ -98,7 +96,7 @@ def check_snapshot_consistency(snapshot, env=None):
9896

9997

10098
def snapshot(c, num_blocks):
101-
print 'creating snapshot'
99+
print('creating snapshot')
102100
snapshot = c.state.to_snapshot()
103101
if (num_blocks / SAVE_INTERVAL) % 2 == 1:
104102
check_snapshot_consistency(snapshot, env=None)
@@ -114,10 +112,11 @@ def snapshot(c, num_blocks):
114112
open(fn, 'w').write(json.dumps(snapshot, indent=4))
115113

116114
REPORT_INTERVAL = 1000
117-
SAVE_INTERVAL = 10 * 1000
118-
SNAPSHOT_INTERVAL = 100 * 1000
115+
SAVE_INTERVAL = 999999
116+
SNAPSHOT_INTERVAL = 999999
119117

120-
MANUAL_SNAPSHOTS = [68000, 68382, 68666, 69000, 909330]
118+
# MANUAL_SNAPSHOTS = [68000, 68382, 68666, 69000, 909330]
119+
MANUAL_SNAPSHOTS = []
121120

122121
# don't check pow
123122
BlockHeader.check_pow = lambda *args: True
@@ -127,33 +126,31 @@ def snapshot(c, num_blocks):
127126
num_blks = 0
128127
num_txs = 0
129128
gas_used = 0
130-
while len(block_rlps) > 0:
131-
for block in block_rlps:
132-
# print 'prevh:', s.prev_headers
133-
block = rlp.decode(block.strip().decode('hex'), Block)
134-
assert c.add_block(block)
135-
num_blks += 1
136-
num_txs += len(block.transactions)
137-
gas_used += block.gas_used
138-
if BENCHMARK > 0:
139-
report(st, num_blks, num_txs, gas_used)
140-
if num_blks == BENCHMARK:
141-
print "Benchmark completed (%d blocks)." % num_blks
142-
sys.exit()
143-
else:
144-
num_blocks = block.header.number + 1
145-
if num_blocks % REPORT_INTERVAL == 0 or num_blocks in MANUAL_SNAPSHOTS:
146-
report(st, REPORT_INTERVAL, num_txs, gas_used)
147-
st = time.time()
148-
num_blks = 0
149-
num_txs = 0
150-
gas_used = 0
151-
if num_blocks % SAVE_INTERVAL == 0 or num_blocks in MANUAL_SNAPSHOTS:
152-
snapshot(c, num_blocks)
153-
st = time.time()
154-
num_blks = 0
155-
num_txs = 0
156-
gas_used = 0
157-
block_rlps = f.readlines(batch_size)
158-
159-
print 'Test successful'
129+
for block in block_rlps[1:50000]:
130+
# print 'prevh:', s.prev_headers
131+
block = rlp.decode(block, Block)
132+
assert c.add_block(block)
133+
num_blks += 1
134+
num_txs += len(block.transactions)
135+
gas_used += block.gas_used
136+
if BENCHMARK > 0:
137+
report(st, num_blks, num_txs, gas_used)
138+
if num_blks == BENCHMARK:
139+
print("Benchmark completed (%d blocks)." % num_blks)
140+
sys.exit()
141+
else:
142+
num_blocks = block.header.number + 1
143+
if num_blocks % REPORT_INTERVAL == 0 or num_blocks in MANUAL_SNAPSHOTS:
144+
report(st, REPORT_INTERVAL, num_txs, gas_used)
145+
st = time.time()
146+
num_blks = 0
147+
num_txs = 0
148+
gas_used = 0
149+
if num_blocks % SAVE_INTERVAL == 0 or num_blocks in MANUAL_SNAPSHOTS:
150+
snapshot(c, num_blocks)
151+
st = time.time()
152+
num_blks = 0
153+
num_txs = 0
154+
gas_used = 0
155+
156+
print('Test successful')

0 commit comments

Comments
 (0)