astreum 0.1.16__py3-none-any.whl → 0.1.17__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of astreum might be problematic. Click here for more details.
- astreum/node/relay/__init__.py +3 -0
- astreum/node/storage/merkle.py +224 -734
- astreum/node/storage/patricia.py +289 -0
- astreum/node/validation/account.py +99 -874
- astreum/node/validation/block.py +30 -0
- astreum/node/validation/transaction.py +57 -1
- {astreum-0.1.16.dist-info → astreum-0.1.17.dist-info}/METADATA +1 -1
- {astreum-0.1.16.dist-info → astreum-0.1.17.dist-info}/RECORD +15 -14
- {astreum-0.1.16.dist-info → astreum-0.1.17.dist-info}/WHEEL +1 -1
- astreum/node/storage/trie.py +0 -146
- /astreum/node/validation/{block → _block}/__init__.py +0 -0
- /astreum/node/validation/{block → _block}/create.py +0 -0
- /astreum/node/validation/{block → _block}/model.py +0 -0
- /astreum/node/validation/{block → _block}/validate.py +0 -0
- {astreum-0.1.16.dist-info → astreum-0.1.17.dist-info}/licenses/LICENSE +0 -0
- {astreum-0.1.16.dist-info → astreum-0.1.17.dist-info}/top_level.txt +0 -0
|
@@ -1,874 +1,99 @@
|
|
|
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
|
-
data: bytes
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def
|
|
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
|
-
return None
|
|
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
|
-
Args:
|
|
101
|
-
accounts: Dictionary mapping addresses to Account objects
|
|
102
|
-
address: Address to check
|
|
103
|
-
|
|
104
|
-
Returns:
|
|
105
|
-
True if the address is a validator, False otherwise
|
|
106
|
-
"""
|
|
107
|
-
# This function would need to be updated to use a separate stake lookup
|
|
108
|
-
# For now, returning False as a placeholder
|
|
109
|
-
return False
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
def create_genesis_account(address: bytes, balance: int = 1000) -> Account:
|
|
113
|
-
"""
|
|
114
|
-
Create a genesis account with initial balance.
|
|
115
|
-
|
|
116
|
-
Args:
|
|
117
|
-
address: Address of the genesis account
|
|
118
|
-
balance: Initial balance (default: 1000)
|
|
119
|
-
|
|
120
|
-
Returns:
|
|
121
|
-
New Account object
|
|
122
|
-
"""
|
|
123
|
-
return Account(
|
|
124
|
-
address=address,
|
|
125
|
-
balance=balance,
|
|
126
|
-
counter=0,
|
|
127
|
-
data=b''
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def apply_transaction_to_account(
|
|
132
|
-
accounts: Dict[bytes, Account],
|
|
133
|
-
sender: bytes,
|
|
134
|
-
recipient: bytes,
|
|
135
|
-
amount: int,
|
|
136
|
-
data: bytes = b'',
|
|
137
|
-
storage: Optional[Storage] = None,
|
|
138
|
-
stake_root: Optional[bytes] = None
|
|
139
|
-
) -> Tuple[bool, Optional[bytes]]:
|
|
140
|
-
"""
|
|
141
|
-
Apply a transaction to update account balances.
|
|
142
|
-
|
|
143
|
-
Args:
|
|
144
|
-
accounts: Dictionary mapping addresses to Account objects
|
|
145
|
-
sender: Sender address
|
|
146
|
-
recipient: Recipient address
|
|
147
|
-
amount: Amount to transfer
|
|
148
|
-
data: Transaction data
|
|
149
|
-
storage: Storage instance (required for staking transactions)
|
|
150
|
-
stake_root: Root hash of the stake Merkle tree (required for staking transactions)
|
|
151
|
-
|
|
152
|
-
Returns:
|
|
153
|
-
Tuple containing:
|
|
154
|
-
- Boolean indicating if transaction was successfully applied
|
|
155
|
-
- New stake root hash if transaction was a staking transaction, otherwise None
|
|
156
|
-
"""
|
|
157
|
-
# Ensure accounts exist
|
|
158
|
-
if sender not in accounts:
|
|
159
|
-
return False, None
|
|
160
|
-
|
|
161
|
-
# Get sender account
|
|
162
|
-
sender_account = accounts[sender]
|
|
163
|
-
|
|
164
|
-
# Check if sender has sufficient balance
|
|
165
|
-
if sender_account.balance < amount:
|
|
166
|
-
return False, None
|
|
167
|
-
|
|
168
|
-
# Handle special case for staking (sending to validation address)
|
|
169
|
-
if recipient == VALIDATION_ADDRESS:
|
|
170
|
-
# This is a staking transaction
|
|
171
|
-
if not storage or stake_root is None:
|
|
172
|
-
# Cannot process staking without storage and stake root
|
|
173
|
-
return False, None
|
|
174
|
-
|
|
175
|
-
# Update account balance
|
|
176
|
-
sender_account.balance -= amount
|
|
177
|
-
sender_account.counter += 1
|
|
178
|
-
|
|
179
|
-
# Update stake in the Merkle tree
|
|
180
|
-
new_stake_root = process_stake_transaction(
|
|
181
|
-
storage,
|
|
182
|
-
stake_root,
|
|
183
|
-
sender,
|
|
184
|
-
amount
|
|
185
|
-
)
|
|
186
|
-
|
|
187
|
-
return True, new_stake_root
|
|
188
|
-
|
|
189
|
-
# Create recipient account if it doesn't exist
|
|
190
|
-
if recipient not in accounts:
|
|
191
|
-
accounts[recipient] = Account(address=recipient)
|
|
192
|
-
|
|
193
|
-
# Get recipient account
|
|
194
|
-
recipient_account = accounts[recipient]
|
|
195
|
-
|
|
196
|
-
# Handle regular transaction
|
|
197
|
-
sender_account.balance -= amount
|
|
198
|
-
recipient_account.balance += amount
|
|
199
|
-
sender_account.counter += 1
|
|
200
|
-
|
|
201
|
-
# Update data if provided
|
|
202
|
-
if data:
|
|
203
|
-
recipient_account.data = data
|
|
204
|
-
|
|
205
|
-
return True, None
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
def store_account(storage: Storage, account: Account) -> bytes:
|
|
209
|
-
"""
|
|
210
|
-
Stores an account in the hierarchical Merkle tree structure and returns the account hash.
|
|
211
|
-
|
|
212
|
-
Structure:
|
|
213
|
-
- Account tree
|
|
214
|
-
- Address tree (addresses as leaves)
|
|
215
|
-
- Details tree
|
|
216
|
-
- Balance
|
|
217
|
-
- Code
|
|
218
|
-
- Counter
|
|
219
|
-
- Storage
|
|
220
|
-
|
|
221
|
-
Args:
|
|
222
|
-
storage: Storage instance
|
|
223
|
-
account: Account to store
|
|
224
|
-
|
|
225
|
-
Returns:
|
|
226
|
-
Root hash of the account tree
|
|
227
|
-
"""
|
|
228
|
-
from ..storage.merkle import MerkleTree
|
|
229
|
-
import json
|
|
230
|
-
|
|
231
|
-
# First, build the detail leaves
|
|
232
|
-
detail_leaves = []
|
|
233
|
-
|
|
234
|
-
# Balance leaf
|
|
235
|
-
balance_data = {'type': 'balance', 'value': account.balance}
|
|
236
|
-
balance_leaf = json.dumps(balance_data).encode('utf-8')
|
|
237
|
-
detail_leaves.append(balance_leaf)
|
|
238
|
-
|
|
239
|
-
# Code/data leaf (account data)
|
|
240
|
-
code_data = {'type': 'code', 'value': account.data.hex() if account.data else ''}
|
|
241
|
-
code_leaf = json.dumps(code_data).encode('utf-8')
|
|
242
|
-
detail_leaves.append(code_leaf)
|
|
243
|
-
|
|
244
|
-
# Counter leaf
|
|
245
|
-
counter_data = {'type': 'counter', 'value': account.counter}
|
|
246
|
-
counter_leaf = json.dumps(counter_data).encode('utf-8')
|
|
247
|
-
detail_leaves.append(counter_leaf)
|
|
248
|
-
|
|
249
|
-
# Storage leaf (placeholder for now)
|
|
250
|
-
storage_data = {'type': 'storage', 'value': ''}
|
|
251
|
-
storage_leaf = json.dumps(storage_data).encode('utf-8')
|
|
252
|
-
detail_leaves.append(storage_leaf)
|
|
253
|
-
|
|
254
|
-
# Build the details Merkle tree
|
|
255
|
-
details_tree = MerkleTree(storage)
|
|
256
|
-
details_root = details_tree.add(detail_leaves)
|
|
257
|
-
|
|
258
|
-
# Create address and details nodes
|
|
259
|
-
address_data = {'type': 'address', 'value': account.address.hex()}
|
|
260
|
-
address_leaf = json.dumps(address_data).encode('utf-8')
|
|
261
|
-
|
|
262
|
-
details_data = {'type': 'details', 'root': details_root.hex()}
|
|
263
|
-
details_leaf = json.dumps(details_data).encode('utf-8')
|
|
264
|
-
|
|
265
|
-
# Build the account Merkle tree
|
|
266
|
-
account_tree = MerkleTree(storage)
|
|
267
|
-
account_root = account_tree.add([address_leaf, details_leaf])
|
|
268
|
-
|
|
269
|
-
return account_root
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
def update_account(storage: Storage, account_root: bytes, account: Account) -> bytes:
|
|
273
|
-
"""
|
|
274
|
-
Updates an existing account in the Merkle tree structure.
|
|
275
|
-
|
|
276
|
-
Args:
|
|
277
|
-
storage: Storage instance
|
|
278
|
-
account_root: Existing account root hash
|
|
279
|
-
account: Updated account data
|
|
280
|
-
|
|
281
|
-
Returns:
|
|
282
|
-
New root hash of the account tree
|
|
283
|
-
"""
|
|
284
|
-
from ..storage.merkle import MerkleTree, find_first
|
|
285
|
-
import json
|
|
286
|
-
|
|
287
|
-
# First, find the details node
|
|
288
|
-
def is_details_node(node_data: bytes) -> bool:
|
|
289
|
-
try:
|
|
290
|
-
data = json.loads(node_data.decode('utf-8'))
|
|
291
|
-
return data.get('type') == 'details'
|
|
292
|
-
except:
|
|
293
|
-
return False
|
|
294
|
-
|
|
295
|
-
details_node = find_first(storage, account_root, is_details_node)
|
|
296
|
-
if not details_node:
|
|
297
|
-
# If no details node found, create a new account
|
|
298
|
-
return store_account(storage, account)
|
|
299
|
-
|
|
300
|
-
# Get the details root
|
|
301
|
-
details_data = json.loads(details_node.decode('utf-8'))
|
|
302
|
-
details_root = bytes.fromhex(details_data.get('root', ''))
|
|
303
|
-
|
|
304
|
-
# Update each leaf in the details tree
|
|
305
|
-
detail_types = ['balance', 'code', 'counter', 'storage']
|
|
306
|
-
new_leaves = []
|
|
307
|
-
|
|
308
|
-
for detail_type in detail_types:
|
|
309
|
-
# Find the existing leaf
|
|
310
|
-
def is_matching_leaf(node_data: bytes) -> bool:
|
|
311
|
-
try:
|
|
312
|
-
data = json.loads(node_data.decode('utf-8'))
|
|
313
|
-
return data.get('type') == detail_type
|
|
314
|
-
except:
|
|
315
|
-
return False
|
|
316
|
-
|
|
317
|
-
leaf_node = find_first(storage, details_root, is_matching_leaf)
|
|
318
|
-
|
|
319
|
-
# Create updated leaf data
|
|
320
|
-
if detail_type == 'balance':
|
|
321
|
-
leaf_data = {'type': 'balance', 'value': account.balance}
|
|
322
|
-
elif detail_type == 'code':
|
|
323
|
-
leaf_data = {'type': 'code', 'value': account.data.hex() if account.data else ''}
|
|
324
|
-
elif detail_type == 'counter':
|
|
325
|
-
leaf_data = {'type': 'counter', 'value': account.counter}
|
|
326
|
-
elif detail_type == 'storage':
|
|
327
|
-
# Get existing storage value or use empty if not found
|
|
328
|
-
if leaf_node:
|
|
329
|
-
try:
|
|
330
|
-
existing_data = json.loads(leaf_node.decode('utf-8'))
|
|
331
|
-
leaf_data = {'type': 'storage', 'value': existing_data.get('value', '')}
|
|
332
|
-
except:
|
|
333
|
-
leaf_data = {'type': 'storage', 'value': ''}
|
|
334
|
-
else:
|
|
335
|
-
leaf_data = {'type': 'storage', 'value': ''}
|
|
336
|
-
|
|
337
|
-
new_leaves.append(json.dumps(leaf_data).encode('utf-8'))
|
|
338
|
-
|
|
339
|
-
# Build new details tree
|
|
340
|
-
details_tree = MerkleTree(storage)
|
|
341
|
-
new_details_root = details_tree.add(new_leaves)
|
|
342
|
-
|
|
343
|
-
# Update details node
|
|
344
|
-
new_details_data = {'type': 'details', 'root': new_details_root.hex()}
|
|
345
|
-
new_details_leaf = json.dumps(new_details_data).encode('utf-8')
|
|
346
|
-
|
|
347
|
-
# Find address node
|
|
348
|
-
def is_address_node(node_data: bytes) -> bool:
|
|
349
|
-
try:
|
|
350
|
-
data = json.loads(node_data.decode('utf-8'))
|
|
351
|
-
return data.get('type') == 'address'
|
|
352
|
-
except:
|
|
353
|
-
return False
|
|
354
|
-
|
|
355
|
-
address_node = find_first(storage, account_root, is_address_node)
|
|
356
|
-
|
|
357
|
-
# Build new account tree
|
|
358
|
-
account_tree = MerkleTree(storage)
|
|
359
|
-
new_account_root = account_tree.add([address_node, new_details_leaf])
|
|
360
|
-
|
|
361
|
-
return new_account_root
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
def get_account_from_tree(storage: Storage, account_root: bytes, address: bytes) -> Optional[Account]:
|
|
365
|
-
"""
|
|
366
|
-
Retrieve an account from the account Merkle tree based on the address.
|
|
367
|
-
|
|
368
|
-
Args:
|
|
369
|
-
storage: Storage instance
|
|
370
|
-
account_root: Root hash of the account tree
|
|
371
|
-
address: Address to find
|
|
372
|
-
|
|
373
|
-
Returns:
|
|
374
|
-
Account if found, None otherwise
|
|
375
|
-
"""
|
|
376
|
-
from ..storage.merkle import find_first
|
|
377
|
-
|
|
378
|
-
# First find the address node to verify it matches
|
|
379
|
-
def is_matching_address(node_data: bytes) -> bool:
|
|
380
|
-
return node_data == address # Direct binary comparison
|
|
381
|
-
|
|
382
|
-
address_node = find_first(storage, account_root, is_matching_address)
|
|
383
|
-
if not address_node:
|
|
384
|
-
return None
|
|
385
|
-
|
|
386
|
-
# Extract balance, code, and counter from the tree
|
|
387
|
-
balance = 0
|
|
388
|
-
code = b''
|
|
389
|
-
counter = 0
|
|
390
|
-
|
|
391
|
-
# Instead of using inefficient search, get the account directly
|
|
392
|
-
account = get_account_from_tree_direct(storage, account_root)
|
|
393
|
-
if not account or account.address != address:
|
|
394
|
-
return None
|
|
395
|
-
|
|
396
|
-
return account
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
def build_accounts_state_tree(storage: Storage, accounts: Dict[bytes, Account]) -> bytes:
|
|
400
|
-
"""
|
|
401
|
-
Build a complete state tree containing multiple accounts.
|
|
402
|
-
|
|
403
|
-
Args:
|
|
404
|
-
storage: Storage instance
|
|
405
|
-
accounts: Dictionary mapping addresses to Account objects
|
|
406
|
-
|
|
407
|
-
Returns:
|
|
408
|
-
Root hash of the state tree
|
|
409
|
-
"""
|
|
410
|
-
from ..storage.merkle import MerkleTree
|
|
411
|
-
import json
|
|
412
|
-
|
|
413
|
-
# Store each account and collect their root hashes
|
|
414
|
-
account_nodes = []
|
|
415
|
-
|
|
416
|
-
for address, account in accounts.items():
|
|
417
|
-
# Store the account
|
|
418
|
-
account_root = store_account(storage, account)
|
|
419
|
-
|
|
420
|
-
# Create a state node reference to this account
|
|
421
|
-
state_node_data = {
|
|
422
|
-
'address': address.hex(),
|
|
423
|
-
'account_root': account_root.hex()
|
|
424
|
-
}
|
|
425
|
-
state_node = json.dumps(state_node_data).encode('utf-8')
|
|
426
|
-
account_nodes.append(state_node)
|
|
427
|
-
|
|
428
|
-
# Build the state tree
|
|
429
|
-
state_tree = MerkleTree(storage)
|
|
430
|
-
state_root = state_tree.add(account_nodes)
|
|
431
|
-
|
|
432
|
-
return state_root
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
def get_account_from_state(storage: Storage, state_root: bytes, address: bytes) -> Optional[Account]:
|
|
436
|
-
"""
|
|
437
|
-
Retrieve an account from the state tree by address.
|
|
438
|
-
|
|
439
|
-
Args:
|
|
440
|
-
storage: Storage instance
|
|
441
|
-
state_root: Root hash of the state tree
|
|
442
|
-
address: Address to find
|
|
443
|
-
|
|
444
|
-
Returns:
|
|
445
|
-
Account if found, None otherwise
|
|
446
|
-
"""
|
|
447
|
-
from ..storage.merkle import find_first
|
|
448
|
-
import json
|
|
449
|
-
|
|
450
|
-
# Find the state node for this address
|
|
451
|
-
def is_matching_state_node(node_data: bytes) -> bool:
|
|
452
|
-
try:
|
|
453
|
-
data = json.loads(node_data.decode('utf-8'))
|
|
454
|
-
return data.get('address') == address.hex()
|
|
455
|
-
except:
|
|
456
|
-
return False
|
|
457
|
-
|
|
458
|
-
state_node = find_first(storage, state_root, is_matching_state_node)
|
|
459
|
-
if not state_node:
|
|
460
|
-
return None
|
|
461
|
-
|
|
462
|
-
# Get the account root
|
|
463
|
-
try:
|
|
464
|
-
state_data = json.loads(state_node.decode('utf-8'))
|
|
465
|
-
account_root = bytes.fromhex(state_data.get('account_root', ''))
|
|
466
|
-
except:
|
|
467
|
-
return None
|
|
468
|
-
|
|
469
|
-
# Get the account from its tree
|
|
470
|
-
return get_account_from_tree(storage, account_root, address)
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
def update_account_in_state(storage: Storage, state_root: bytes, account: Account) -> bytes:
|
|
474
|
-
"""
|
|
475
|
-
Update an account in the state tree or add it if it doesn't exist.
|
|
476
|
-
|
|
477
|
-
Args:
|
|
478
|
-
storage: Storage instance
|
|
479
|
-
state_root: Root hash of the state tree
|
|
480
|
-
account: Account to update
|
|
481
|
-
|
|
482
|
-
Returns:
|
|
483
|
-
New root hash of the state tree
|
|
484
|
-
"""
|
|
485
|
-
from ..storage.merkle import MerkleTree, find_first, find_all
|
|
486
|
-
import json
|
|
487
|
-
|
|
488
|
-
# Find all existing state nodes
|
|
489
|
-
def match_all_nodes(node_data: bytes) -> bool:
|
|
490
|
-
return True
|
|
491
|
-
|
|
492
|
-
all_state_nodes = find_all(storage, state_root, match_all_nodes)
|
|
493
|
-
|
|
494
|
-
# Check if this account already exists in state
|
|
495
|
-
account_root = None
|
|
496
|
-
existing_node = None
|
|
497
|
-
|
|
498
|
-
for node in all_state_nodes:
|
|
499
|
-
try:
|
|
500
|
-
data = json.loads(node.decode('utf-8'))
|
|
501
|
-
if data.get('address') == account.address.hex():
|
|
502
|
-
account_root = bytes.fromhex(data.get('account_root', ''))
|
|
503
|
-
existing_node = node
|
|
504
|
-
break
|
|
505
|
-
except:
|
|
506
|
-
continue
|
|
507
|
-
|
|
508
|
-
# Store or update the account
|
|
509
|
-
if account_root:
|
|
510
|
-
# Update existing account
|
|
511
|
-
new_account_root = update_account(storage, account_root, account)
|
|
512
|
-
else:
|
|
513
|
-
# Store new account
|
|
514
|
-
new_account_root = store_account(storage, account)
|
|
515
|
-
|
|
516
|
-
# Create or update state node
|
|
517
|
-
state_node_data = {
|
|
518
|
-
'address': account.address.hex(),
|
|
519
|
-
'account_root': new_account_root.hex()
|
|
520
|
-
}
|
|
521
|
-
new_state_node = json.dumps(state_node_data).encode('utf-8')
|
|
522
|
-
|
|
523
|
-
# If we're updating an existing node
|
|
524
|
-
if existing_node:
|
|
525
|
-
# Replace the node in the list
|
|
526
|
-
new_nodes = []
|
|
527
|
-
for node in all_state_nodes:
|
|
528
|
-
if node == existing_node:
|
|
529
|
-
new_nodes.append(new_state_node)
|
|
530
|
-
else:
|
|
531
|
-
new_nodes.append(node)
|
|
532
|
-
else:
|
|
533
|
-
# Add the new node to the list
|
|
534
|
-
new_nodes = list(all_state_nodes)
|
|
535
|
-
new_nodes.append(new_state_node)
|
|
536
|
-
|
|
537
|
-
# Build new state tree
|
|
538
|
-
state_tree = MerkleTree(storage)
|
|
539
|
-
new_state_root = state_tree.add(new_nodes)
|
|
540
|
-
|
|
541
|
-
return new_state_root
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
def create_account_details_objects(account: Account) -> Dict[bytes, MerkleNode]:
|
|
545
|
-
"""
|
|
546
|
-
Creates MerkleNode objects for account details that can be directly stored
|
|
547
|
-
in the storage system.
|
|
548
|
-
|
|
549
|
-
Args:
|
|
550
|
-
account: The account to create objects for
|
|
551
|
-
|
|
552
|
-
Returns:
|
|
553
|
-
Dictionary mapping node hashes to MerkleNode objects
|
|
554
|
-
"""
|
|
555
|
-
from ..storage.merkle import MerkleNodeType
|
|
556
|
-
|
|
557
|
-
# Dictionary to store all nodes
|
|
558
|
-
nodes = {}
|
|
559
|
-
|
|
560
|
-
# Create leaf nodes for each detail
|
|
561
|
-
# Balance leaf - directly use 8-byte representation of balance
|
|
562
|
-
balance_bytes = account.balance.to_bytes(8, 'big')
|
|
563
|
-
balance_node = MerkleNode(
|
|
564
|
-
node_type=MerkleNodeType.LEAF,
|
|
565
|
-
hash=hash_data(b'\x00' + balance_bytes),
|
|
566
|
-
data=balance_bytes
|
|
567
|
-
)
|
|
568
|
-
nodes[balance_node.hash] = balance_node
|
|
569
|
-
|
|
570
|
-
# Code/data leaf - directly use raw bytes
|
|
571
|
-
code_bytes = account.data if account.data else b''
|
|
572
|
-
code_node = MerkleNode(
|
|
573
|
-
node_type=MerkleNodeType.LEAF,
|
|
574
|
-
hash=hash_data(b'\x00' + code_bytes),
|
|
575
|
-
data=code_bytes
|
|
576
|
-
)
|
|
577
|
-
nodes[code_node.hash] = code_node
|
|
578
|
-
|
|
579
|
-
# Counter leaf - directly use 8-byte representation of counter
|
|
580
|
-
counter_bytes = account.counter.to_bytes(8, 'big')
|
|
581
|
-
counter_node = MerkleNode(
|
|
582
|
-
node_type=MerkleNodeType.LEAF,
|
|
583
|
-
hash=hash_data(b'\x00' + counter_bytes),
|
|
584
|
-
data=counter_bytes
|
|
585
|
-
)
|
|
586
|
-
nodes[counter_node.hash] = counter_node
|
|
587
|
-
|
|
588
|
-
# Storage leaf - placeholder for account storage data
|
|
589
|
-
storage_bytes = b''
|
|
590
|
-
storage_node = MerkleNode(
|
|
591
|
-
node_type=MerkleNodeType.LEAF,
|
|
592
|
-
hash=hash_data(b'\x00' + storage_bytes),
|
|
593
|
-
data=storage_bytes
|
|
594
|
-
)
|
|
595
|
-
nodes[storage_node.hash] = storage_node
|
|
596
|
-
|
|
597
|
-
# Create branch nodes for the details tree
|
|
598
|
-
# First level: balance+code branch
|
|
599
|
-
balance_code_branch = MerkleNode(
|
|
600
|
-
node_type=MerkleNodeType.BRANCH,
|
|
601
|
-
hash=hash_data(b'\x01' + balance_node.hash + code_node.hash),
|
|
602
|
-
left_child=balance_node.hash,
|
|
603
|
-
right_child=code_node.hash
|
|
604
|
-
)
|
|
605
|
-
nodes[balance_code_branch.hash] = balance_code_branch
|
|
606
|
-
|
|
607
|
-
# First level: counter+storage branch
|
|
608
|
-
counter_storage_branch = MerkleNode(
|
|
609
|
-
node_type=MerkleNodeType.BRANCH,
|
|
610
|
-
hash=hash_data(b'\x01' + counter_node.hash + storage_node.hash),
|
|
611
|
-
left_child=counter_node.hash,
|
|
612
|
-
right_child=storage_node.hash
|
|
613
|
-
)
|
|
614
|
-
nodes[counter_storage_branch.hash] = counter_storage_branch
|
|
615
|
-
|
|
616
|
-
# Second level: details root
|
|
617
|
-
details_root = MerkleNode(
|
|
618
|
-
node_type=MerkleNodeType.BRANCH,
|
|
619
|
-
hash=hash_data(b'\x01' + balance_code_branch.hash + counter_storage_branch.hash),
|
|
620
|
-
left_child=balance_code_branch.hash,
|
|
621
|
-
right_child=counter_storage_branch.hash
|
|
622
|
-
)
|
|
623
|
-
nodes[details_root.hash] = details_root
|
|
624
|
-
|
|
625
|
-
# Address leaf node - directly use raw address bytes
|
|
626
|
-
address_bytes = account.address
|
|
627
|
-
address_node = MerkleNode(
|
|
628
|
-
node_type=MerkleNodeType.LEAF,
|
|
629
|
-
hash=hash_data(b'\x00' + address_bytes),
|
|
630
|
-
data=address_bytes
|
|
631
|
-
)
|
|
632
|
-
nodes[address_node.hash] = address_node
|
|
633
|
-
|
|
634
|
-
# Create the account root node
|
|
635
|
-
account_root = MerkleNode(
|
|
636
|
-
node_type=MerkleNodeType.BRANCH,
|
|
637
|
-
hash=hash_data(b'\x01' + address_node.hash + details_root.hash),
|
|
638
|
-
left_child=address_node.hash,
|
|
639
|
-
right_child=details_root.hash
|
|
640
|
-
)
|
|
641
|
-
nodes[account_root.hash] = account_root
|
|
642
|
-
|
|
643
|
-
return nodes
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
def store_account_direct(storage: Storage, account: Account) -> bytes:
|
|
647
|
-
"""
|
|
648
|
-
Stores an account directly in storage as MerkleNode objects.
|
|
649
|
-
|
|
650
|
-
Args:
|
|
651
|
-
storage: Storage instance
|
|
652
|
-
account: Account to store
|
|
653
|
-
|
|
654
|
-
Returns:
|
|
655
|
-
Root hash of the account tree
|
|
656
|
-
"""
|
|
657
|
-
# Create all the nodes
|
|
658
|
-
nodes = create_account_details_objects(account)
|
|
659
|
-
|
|
660
|
-
# Store all nodes in storage
|
|
661
|
-
for node_hash, node in nodes.items():
|
|
662
|
-
storage.put(node_hash, node.serialize())
|
|
663
|
-
|
|
664
|
-
# Return the root hash (the last node is the root)
|
|
665
|
-
account_root_hash = None
|
|
666
|
-
for node_hash, node in nodes.items():
|
|
667
|
-
account_root_hash = node_hash # The last one will be the root
|
|
668
|
-
|
|
669
|
-
return account_root_hash
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
def update_account_direct(storage: Storage, account_root_hash: bytes, account: Account) -> bytes:
|
|
673
|
-
"""
|
|
674
|
-
Updates an account directly in storage using MerkleNode objects.
|
|
675
|
-
|
|
676
|
-
Args:
|
|
677
|
-
storage: Storage instance
|
|
678
|
-
account_root_hash: Current root hash of the account
|
|
679
|
-
account: Updated account data
|
|
680
|
-
|
|
681
|
-
Returns:
|
|
682
|
-
New root hash of the account tree
|
|
683
|
-
"""
|
|
684
|
-
from ..storage.merkle import MerkleNodeType
|
|
685
|
-
|
|
686
|
-
# Get the account root node
|
|
687
|
-
root_data = storage.get(account_root_hash)
|
|
688
|
-
if not root_data:
|
|
689
|
-
# Account doesn't exist, create it
|
|
690
|
-
return store_account_direct(storage, account)
|
|
691
|
-
|
|
692
|
-
root_node = MerkleNode.deserialize(root_data)
|
|
693
|
-
if root_node.node_type != MerkleNodeType.BRANCH:
|
|
694
|
-
# Invalid root node, create new account
|
|
695
|
-
return store_account_direct(storage, account)
|
|
696
|
-
|
|
697
|
-
# Create new nodes
|
|
698
|
-
new_nodes = create_account_details_objects(account)
|
|
699
|
-
|
|
700
|
-
# Store all nodes
|
|
701
|
-
for node_hash, node in new_nodes.items():
|
|
702
|
-
storage.put(node_hash, node.serialize())
|
|
703
|
-
|
|
704
|
-
# Return the new root hash
|
|
705
|
-
account_root_hash = None
|
|
706
|
-
for node_hash, node in new_nodes.items():
|
|
707
|
-
account_root_hash = node_hash # The last one will be the root
|
|
708
|
-
|
|
709
|
-
return account_root_hash
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
def build_state_tree_direct(storage: Storage, accounts: Dict[bytes, Account]) -> bytes:
|
|
713
|
-
"""
|
|
714
|
-
Builds a state tree directly from account objects without using MerkleTree.
|
|
715
|
-
|
|
716
|
-
Args:
|
|
717
|
-
storage: Storage instance
|
|
718
|
-
accounts: Dictionary mapping addresses to accounts
|
|
719
|
-
|
|
720
|
-
Returns:
|
|
721
|
-
Root hash of the state tree
|
|
722
|
-
"""
|
|
723
|
-
from ..storage.merkle import MerkleNodeType
|
|
724
|
-
import json
|
|
725
|
-
|
|
726
|
-
# Create state leaf nodes for each account
|
|
727
|
-
state_leaves = []
|
|
728
|
-
|
|
729
|
-
for address, account in accounts.items():
|
|
730
|
-
# Store the account
|
|
731
|
-
account_root_hash = store_account_direct(storage, account)
|
|
732
|
-
|
|
733
|
-
# Create state leaf
|
|
734
|
-
state_data = {
|
|
735
|
-
'address': address.hex(),
|
|
736
|
-
'account_root': account_root_hash.hex()
|
|
737
|
-
}
|
|
738
|
-
state_bytes = json.dumps(state_data).encode('utf-8')
|
|
739
|
-
state_leaf = MerkleNode(
|
|
740
|
-
node_type=MerkleNodeType.LEAF,
|
|
741
|
-
hash=hash_data(b'\x00' + state_bytes),
|
|
742
|
-
data=state_bytes
|
|
743
|
-
)
|
|
744
|
-
storage.put(state_leaf.hash, state_leaf.serialize())
|
|
745
|
-
state_leaves.append(state_leaf)
|
|
746
|
-
|
|
747
|
-
# If no accounts, return None
|
|
748
|
-
if not state_leaves:
|
|
749
|
-
return None
|
|
750
|
-
|
|
751
|
-
# Build a balanced tree from state leaves
|
|
752
|
-
current_level = state_leaves
|
|
753
|
-
|
|
754
|
-
while len(current_level) > 1:
|
|
755
|
-
next_level = []
|
|
756
|
-
|
|
757
|
-
# Process pairs of nodes
|
|
758
|
-
for i in range(0, len(current_level), 2):
|
|
759
|
-
# If we have a pair, create a branch node
|
|
760
|
-
if i + 1 < len(current_level):
|
|
761
|
-
branch = MerkleNode(
|
|
762
|
-
node_type=MerkleNodeType.BRANCH,
|
|
763
|
-
hash=hash_data(b'\x01' + current_level[i].hash + current_level[i+1].hash),
|
|
764
|
-
left_child=current_level[i].hash,
|
|
765
|
-
right_child=current_level[i+1].hash
|
|
766
|
-
)
|
|
767
|
-
# If we have an odd node left, duplicate it
|
|
768
|
-
else:
|
|
769
|
-
branch = MerkleNode(
|
|
770
|
-
node_type=MerkleNodeType.BRANCH,
|
|
771
|
-
hash=hash_data(b'\x01' + current_level[i].hash + current_level[i].hash),
|
|
772
|
-
left_child=current_level[i].hash,
|
|
773
|
-
right_child=current_level[i].hash
|
|
774
|
-
)
|
|
775
|
-
|
|
776
|
-
storage.put(branch.hash, branch.serialize())
|
|
777
|
-
next_level.append(branch)
|
|
778
|
-
|
|
779
|
-
current_level = next_level
|
|
780
|
-
|
|
781
|
-
# The last remaining node is the root
|
|
782
|
-
return current_level[0].hash if current_level else None
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
def get_account_from_tree_direct(storage: Storage, account_root_hash: bytes) -> Optional[Account]:
|
|
786
|
-
"""
|
|
787
|
-
Retrieves an account from storage using its root hash, working directly with MerkleNodes.
|
|
788
|
-
|
|
789
|
-
Args:
|
|
790
|
-
storage: Storage instance
|
|
791
|
-
account_root_hash: Root hash of the account tree
|
|
792
|
-
|
|
793
|
-
Returns:
|
|
794
|
-
The account if found, None otherwise
|
|
795
|
-
"""
|
|
796
|
-
from ..storage.merkle import MerkleNodeType
|
|
797
|
-
|
|
798
|
-
# Get the account root node
|
|
799
|
-
root_data = storage.get(account_root_hash)
|
|
800
|
-
if not root_data:
|
|
801
|
-
return None
|
|
802
|
-
|
|
803
|
-
root_node = MerkleNode.deserialize(root_data)
|
|
804
|
-
if root_node.node_type != MerkleNodeType.BRANCH:
|
|
805
|
-
return None
|
|
806
|
-
|
|
807
|
-
# Get the address node
|
|
808
|
-
address_hash = root_node.left_child
|
|
809
|
-
address_data = storage.get(address_hash)
|
|
810
|
-
if not address_data:
|
|
811
|
-
return None
|
|
812
|
-
|
|
813
|
-
address_node = MerkleNode.deserialize(address_data)
|
|
814
|
-
if address_node.node_type != MerkleNodeType.LEAF:
|
|
815
|
-
return None
|
|
816
|
-
|
|
817
|
-
# Address is directly stored as bytes
|
|
818
|
-
address = address_node.data
|
|
819
|
-
|
|
820
|
-
# Get the details node
|
|
821
|
-
details_hash = root_node.right_child
|
|
822
|
-
details_data = storage.get(details_hash)
|
|
823
|
-
if not details_data:
|
|
824
|
-
return None
|
|
825
|
-
|
|
826
|
-
details_node = MerkleNode.deserialize(details_data)
|
|
827
|
-
|
|
828
|
-
# Get values from the details subtree
|
|
829
|
-
balance = 0
|
|
830
|
-
code = b''
|
|
831
|
-
counter = 0
|
|
832
|
-
|
|
833
|
-
# We need to traverse the details tree to get all values
|
|
834
|
-
if details_node.node_type == MerkleNodeType.BRANCH:
|
|
835
|
-
# Get balance+code branch
|
|
836
|
-
balance_code_hash = details_node.left_child
|
|
837
|
-
balance_code_data = storage.get(balance_code_hash)
|
|
838
|
-
if balance_code_data:
|
|
839
|
-
balance_code_node = MerkleNode.deserialize(balance_code_data)
|
|
840
|
-
|
|
841
|
-
# Get balance node
|
|
842
|
-
balance_hash = balance_code_node.left_child
|
|
843
|
-
balance_data = storage.get(balance_hash)
|
|
844
|
-
if balance_data:
|
|
845
|
-
balance_node = MerkleNode.deserialize(balance_data)
|
|
846
|
-
balance = int.from_bytes(balance_node.data, 'big')
|
|
847
|
-
|
|
848
|
-
# Get code node
|
|
849
|
-
code_hash = balance_code_node.right_child
|
|
850
|
-
code_data = storage.get(code_hash)
|
|
851
|
-
if code_data:
|
|
852
|
-
code_node = MerkleNode.deserialize(code_data)
|
|
853
|
-
code = code_node.data
|
|
854
|
-
|
|
855
|
-
# Get counter+storage branch
|
|
856
|
-
counter_storage_hash = details_node.right_child
|
|
857
|
-
counter_storage_data = storage.get(counter_storage_hash)
|
|
858
|
-
if counter_storage_data:
|
|
859
|
-
counter_storage_node = MerkleNode.deserialize(counter_storage_data)
|
|
860
|
-
|
|
861
|
-
# Get counter node
|
|
862
|
-
counter_hash = counter_storage_node.left_child
|
|
863
|
-
counter_data = storage.get(counter_hash)
|
|
864
|
-
if counter_data:
|
|
865
|
-
counter_node = MerkleNode.deserialize(counter_data)
|
|
866
|
-
counter = int.from_bytes(counter_node.data, 'big')
|
|
867
|
-
|
|
868
|
-
# Create the account with the retrieved values
|
|
869
|
-
return Account(
|
|
870
|
-
address=address,
|
|
871
|
-
balance=balance,
|
|
872
|
-
counter=counter,
|
|
873
|
-
data=code
|
|
874
|
-
)
|
|
1
|
+
from typing import Optional
|
|
2
|
+
from ..storage.patricia import PatriciaTrie
|
|
3
|
+
|
|
4
|
+
import astreum.utils.bytes_format as bytes_format
|
|
5
|
+
class Account:
|
|
6
|
+
def __init__(self, public_key: bytes, balance: int, code: bytes, counter: int, data: bytes, secret_key: Optional[bytes] = None):
|
|
7
|
+
"""
|
|
8
|
+
Initialize an Account.
|
|
9
|
+
|
|
10
|
+
:param public_key: The public key used as the account identifier (used as trie key).
|
|
11
|
+
:param balance: The account balance.
|
|
12
|
+
:param code: The associated code (for example, smart contract code).
|
|
13
|
+
:param counter: A transaction counter (nonce).
|
|
14
|
+
:param data: Additional account data.
|
|
15
|
+
:param secret_key: (Optional) The account’s secret key.
|
|
16
|
+
"""
|
|
17
|
+
self.public_key = public_key
|
|
18
|
+
self.secret_key = secret_key # Optional private key.
|
|
19
|
+
self.balance = balance
|
|
20
|
+
self.code = code
|
|
21
|
+
self.counter = counter
|
|
22
|
+
self.data = data
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def from_bytes(cls, public_key: bytes, data: bytes, secret_key: Optional[bytes] = None) -> 'Account':
|
|
26
|
+
"""
|
|
27
|
+
Deserialize an Account from its byte representation.
|
|
28
|
+
|
|
29
|
+
Expected format: [balance, code, counter, data]
|
|
30
|
+
|
|
31
|
+
The public_key (and optional secret_key) must be provided separately.
|
|
32
|
+
"""
|
|
33
|
+
decoded = bytes_format.decode(data)
|
|
34
|
+
balance, code, counter, account_data = decoded
|
|
35
|
+
return cls(public_key, balance, code, counter, account_data, secret_key=secret_key)
|
|
36
|
+
|
|
37
|
+
def to_bytes(self) -> bytes:
|
|
38
|
+
"""
|
|
39
|
+
Serialize the Account into bytes.
|
|
40
|
+
|
|
41
|
+
Format: [balance, code, counter, data]
|
|
42
|
+
"""
|
|
43
|
+
return bytes_format.encode([
|
|
44
|
+
self.balance,
|
|
45
|
+
self.code,
|
|
46
|
+
self.counter,
|
|
47
|
+
self.data
|
|
48
|
+
])
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class Accounts(PatriciaTrie):
|
|
52
|
+
"""
|
|
53
|
+
Accounts is a PatriciaTrie storing Account objects.
|
|
54
|
+
|
|
55
|
+
The trie key is the account’s public key (bytes), while the value is the account data
|
|
56
|
+
(serialized with balance, code, counter, and data only).
|
|
57
|
+
|
|
58
|
+
You can instantiate Accounts with a storage instance and, if available, a root hash
|
|
59
|
+
representing an existing trie.
|
|
60
|
+
"""
|
|
61
|
+
def get_account(self, public_key: bytes) -> Optional[Account]:
|
|
62
|
+
"""
|
|
63
|
+
Retrieve an account using its public key.
|
|
64
|
+
|
|
65
|
+
:param public_key: The public key (bytes) of the account.
|
|
66
|
+
:return: The Account instance if found, else None.
|
|
67
|
+
"""
|
|
68
|
+
raw = self.get(public_key)
|
|
69
|
+
if raw is None:
|
|
70
|
+
return None
|
|
71
|
+
return Account.from_bytes(public_key, raw)
|
|
72
|
+
|
|
73
|
+
def put_account(self, account: Account) -> None:
|
|
74
|
+
"""
|
|
75
|
+
Insert or update an account in the trie.
|
|
76
|
+
|
|
77
|
+
:param account: The Account instance to store.
|
|
78
|
+
"""
|
|
79
|
+
self.put(account.public_key, account.to_bytes())
|
|
80
|
+
|
|
81
|
+
def get_account_from_storage(public_key: bytes, accounts: Accounts, storage: Storage) -> Optional[Account]:
|
|
82
|
+
# 1. get account details hash
|
|
83
|
+
# 2. resolve storage objects to get account details
|
|
84
|
+
# 3. return account
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
# Example of instantiation:
|
|
88
|
+
#
|
|
89
|
+
# Assuming you have an instance of Storage (e.g., from your storage module), you can create
|
|
90
|
+
# an Accounts instance either as an empty trie or using an existing root hash:
|
|
91
|
+
#
|
|
92
|
+
# storage = Storage()
|
|
93
|
+
#
|
|
94
|
+
# # To instantiate an empty Accounts trie:
|
|
95
|
+
# accounts = Accounts(storage)
|
|
96
|
+
#
|
|
97
|
+
# # To instantiate with an existing trie root hash:
|
|
98
|
+
# existing_root_hash = b'...' # This would be loaded from persistent storage.
|
|
99
|
+
# accounts = Accounts(storage, root_hash=existing_root_hash)
|