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

Commit 80f023f

Browse files
Jan Xiekonradkonrad
authored andcommitted
add chain snapshot
1 parent 35bfb19 commit 80f023f

File tree

1 file changed

+190
-0
lines changed

1 file changed

+190
-0
lines changed

ethereum/snapshot.py

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import rlp
2+
from ethereum import blocks
3+
from ethereum.blocks import Account, BlockHeader, Block, CachedBlock
4+
from ethereum.utils import is_numeric, is_string, encode_hex, decode_hex, zpad, scan_bin, big_endian_to_int
5+
from ethereum.securetrie import SecureTrie
6+
from ethereum.trie import Trie, BLANK_NODE, BLANK_ROOT
7+
8+
9+
class FakeHeader(object):
10+
def __init__(self, number, hash, state_root, gas_limit, timestamp):
11+
self.number = number
12+
self.hash = hash
13+
self.state_root = state_root
14+
self.gas_limit = gas_limit
15+
self.timestamp = timestamp
16+
17+
18+
class FakeBlock(object):
19+
def __init__(self, env, header, chain_diff):
20+
self.env = env
21+
self.config = env.config
22+
self.header = header
23+
self.uncles = []
24+
self.number = header.number
25+
self.hash = header.hash
26+
self.gas_limit = header.gas_limit
27+
self.difficulty = header.difficulty
28+
self.timestamp = header.timestamp
29+
self._chain_diff = chain_diff
30+
31+
def chain_difficulty(self):
32+
return self._chain_diff
33+
34+
def has_parent(self):
35+
return False
36+
37+
def get_ancestor_list(self, n):
38+
if n == 0 or self.header.number == 0:
39+
return []
40+
p = FakeBlock(self.env, self.header, 0)
41+
return [p] + p.get_ancestor_list(n - 1)
42+
43+
44+
def create_snapshot(chain, recent=1024):
45+
env = chain.env
46+
head_block = chain.head
47+
base_block_hash = chain.index.get_block_by_number(max(head_block.number-recent, 0))
48+
base_block = chain.get(base_block_hash)
49+
50+
snapshot = create_env_snapshot(base_block)
51+
snapshot['base'] = create_base_snapshot(base_block)
52+
snapshot['blocks'] = create_blocks_snapshot(base_block, head_block)
53+
snapshot['alloc'] = create_state_snapshot(env, base_block.state)
54+
55+
return snapshot
56+
57+
58+
def create_env_snapshot(base):
59+
return {
60+
'chainDifficulty': snapshot_form(base.chain_difficulty())
61+
}
62+
63+
64+
def create_base_snapshot(base):
65+
return snapshot_form(rlp.encode(base.header))
66+
67+
68+
def create_state_snapshot(env, state_trie):
69+
alloc = dict()
70+
count = 0
71+
for addr, account_rlp in state_trie.to_dict().items():
72+
alloc[encode_hex(addr)] = create_account_snapshot(env, account_rlp)
73+
count += 1
74+
print "[%d] created account snapshot %s" % encode_hex(addr)
75+
return alloc
76+
77+
78+
def create_account_snapshot(env, rlpdata):
79+
account = get_account(env, rlpdata)
80+
storage_trie = SecureTrie(Trie(env.db, account.storage))
81+
storage = dict()
82+
for k, v in storage_trie.to_dict().items():
83+
storage[encode_hex(k.lstrip('\x00') or '\x00')] = encode_hex(v)
84+
return {
85+
'nonce': snapshot_form(account.nonce),
86+
'balance': snapshot_form(account.balance),
87+
'code': encode_hex(account.code),
88+
'storage': storage
89+
}
90+
91+
92+
def create_blocks_snapshot(base, head):
93+
recent_blocks = list()
94+
block = head
95+
while True:
96+
recent_blocks.append(snapshot_form(rlp.encode(block)))
97+
if block.prevhash != base.hash:
98+
block = block.get_parent()
99+
else:
100+
break
101+
recent_blocks.reverse()
102+
return recent_blocks
103+
104+
105+
def load_snapshot(chain, snapshot):
106+
base_header = rlp.decode(scan_bin(snapshot['base']), BlockHeader)
107+
108+
limit = len(snapshot['blocks'])
109+
# first block is child of base block
110+
first_block_rlp = scan_bin(snapshot['blocks'][0])
111+
first_header_data = rlp.decode(first_block_rlp)[0]
112+
head_block_rlp = scan_bin(snapshot['blocks'][limit-1])
113+
head_header_data = rlp.decode(head_block_rlp)[0]
114+
115+
state = load_state(chain.env, snapshot['alloc'])
116+
assert state.root_hash == base_header.state_root
117+
118+
_get_block_header = blocks.get_block_header
119+
def get_block_header(db, blockhash):
120+
if blockhash == first_header_data[0]: # first block's prevhash
121+
return base_header
122+
return _get_block_header(db, blockhash)
123+
blocks.get_block_header = get_block_header
124+
125+
_get_block = blocks.get_block
126+
def get_block(env, blockhash):
127+
if blockhash == first_header_data[0]:
128+
return FakeBlock(env, get_block_header(env.db, blockhash), int(snapshot['chainDifficulty']))
129+
return _get_block(env, blockhash)
130+
blocks.get_block = get_block
131+
132+
def validate_uncles():
133+
return True
134+
135+
first_block = rlp.decode(first_block_rlp, Block, env=chain.env)
136+
chain.index.add_block(first_block)
137+
chain._store_block(first_block)
138+
chain.blockchain.put('HEAD', first_block.hash)
139+
chain.blockchain.put(chain.index._block_by_number_key(first_block.number), first_block.hash)
140+
chain.blockchain.commit()
141+
chain._update_head_candidate()
142+
143+
count = 0
144+
for block_rlp in snapshot['blocks'][1:]:
145+
block_rlp = scan_bin(block_rlp)
146+
block = rlp.decode(block_rlp, Block, env=chain.env)
147+
if count < chain.env.config['MAX_UNCLE_DEPTH']+2:
148+
block.__setattr__('validate_uncles', validate_uncles)
149+
if not chain.add_block(block):
150+
print "Failed to load block #%d (%s), abort." % (block.number, encode_hex(block.hash)[:8])
151+
else:
152+
count += 1
153+
print "[%d] block #%d (%s) added" % (count, block.number, encode_hex(block.hash)[:8])
154+
print "Snapshot loaded."
155+
156+
157+
def load_state(env, alloc):
158+
db = env.db
159+
state = SecureTrie(Trie(db, BLANK_ROOT))
160+
for addr, account in alloc.items():
161+
acct = Account.blank_account(db, env.config['ACCOUNT_INITIAL_NONCE'])
162+
if len(account['storage']) > 0:
163+
t = SecureTrie(Trie(db, BLANK_ROOT))
164+
for k, v in account['storage'].items():
165+
enckey = zpad(decode_hex(k), 32)
166+
t.update(enckey, decode_hex(v))
167+
acct.storage = t.root_hash
168+
if account['nonce']:
169+
acct.nonce = int(account['nonce'])
170+
if account['balance']:
171+
acct.balance = int(account['balance'])
172+
if account['code']:
173+
acct.code = decode_hex(account['code'])
174+
state.update(decode_hex(addr), rlp.encode(acct))
175+
db.commit()
176+
return state
177+
178+
179+
def get_account(env, rlpdata):
180+
if rlpdata != BLANK_NODE:
181+
return rlp.decode(rlpdata, Account, db=env.db)
182+
else:
183+
return Account.blank_account(env.db, env.config['ACCOUNT_INITIAL_NONCE'])
184+
185+
186+
def snapshot_form(val):
187+
if is_numeric(val):
188+
return str(val)
189+
elif is_string(val):
190+
return b'0x' + encode_hex(val)

0 commit comments

Comments
 (0)