astreum 0.1.14__py3-none-any.whl → 0.1.15__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/lispeum/storage.py +10 -10
- astreum/lispeum/utils.py +17 -0
- astreum/node/__init__.py +333 -552
- astreum/node/relay/envelope.py +5 -5
- astreum/node/storage/__init__.py +13 -0
- astreum/node/storage/merkle.py +734 -0
- astreum/node/{models.py → storage/storage.py} +41 -99
- astreum/node/storage/trie.py +146 -0
- astreum/node/storage/utils.py +137 -0
- astreum/node/utils.py +34 -0
- astreum/node/validation/__init__.py +84 -0
- astreum/node/validation/account.py +874 -0
- astreum/node/validation/block/__init__.py +12 -0
- astreum/node/validation/block/create.py +98 -0
- astreum/node/validation/block/model.py +81 -0
- astreum/node/validation/block/validate.py +196 -0
- astreum/node/validation/constants.py +15 -0
- astreum/node/validation/stake.py +229 -0
- astreum/node/validation/state.py +230 -0
- astreum/node/validation/vdf.py +80 -0
- {astreum-0.1.14.dist-info → astreum-0.1.15.dist-info}/METADATA +2 -1
- {astreum-0.1.14.dist-info → astreum-0.1.15.dist-info}/RECORD +25 -9
- {astreum-0.1.14.dist-info → astreum-0.1.15.dist-info}/LICENSE +0 -0
- {astreum-0.1.14.dist-info → astreum-0.1.15.dist-info}/WHEEL +0 -0
- {astreum-0.1.14.dist-info → astreum-0.1.15.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,874 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Account validation and stake management for the Astreum blockchain.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
import json
|
|
7
|
+
from typing import Dict, Optional, Tuple, List, Set, Any
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from ..utils import hash_data
|
|
10
|
+
from ..storage import Storage
|
|
11
|
+
from ..storage.merkle import MerkleNode, MerkleTree
|
|
12
|
+
from .stake import process_stake_transaction
|
|
13
|
+
|
|
14
|
+
from .constants import VALIDATION_ADDRESS, MIN_STAKE_AMOUNT
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class Account:
|
|
19
|
+
"""
|
|
20
|
+
Account class for tracking balance and account state.
|
|
21
|
+
"""
|
|
22
|
+
address: bytes
|
|
23
|
+
balance: int = 0
|
|
24
|
+
counter: int = 0
|
|
25
|
+
data: bytes = field(default_factory=lambda: b'')
|
|
26
|
+
|
|
27
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
28
|
+
"""Convert account to dictionary representation"""
|
|
29
|
+
return {
|
|
30
|
+
'address': self.address.hex(),
|
|
31
|
+
'balance': self.balance,
|
|
32
|
+
'counter': self.counter,
|
|
33
|
+
'data': self.data.hex() if self.data else ''
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def from_dict(cls, data: Dict[str, Any]) -> 'Account':
|
|
38
|
+
"""Create account from dictionary representation"""
|
|
39
|
+
return cls(
|
|
40
|
+
address=bytes.fromhex(data['address']),
|
|
41
|
+
balance=data['balance'],
|
|
42
|
+
counter=data['counter'],
|
|
43
|
+
data=bytes.fromhex(data['data']) if data['data'] else b''
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def get_account_details(storage: Storage, details_hash: bytes) -> Optional[Account]:
|
|
48
|
+
"""
|
|
49
|
+
Retrieve account details from storage using a details hash.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
storage: Storage instance to retrieve data from
|
|
53
|
+
details_hash: Hash of the account details to retrieve
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Account object if found, None otherwise
|
|
57
|
+
"""
|
|
58
|
+
# Retrieve the account node from storage
|
|
59
|
+
node_data = storage.get(details_hash)
|
|
60
|
+
if not node_data:
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
# Deserialize the node
|
|
64
|
+
node = MerkleNode.deserialize(node_data)
|
|
65
|
+
if not node or not node.data:
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
# Try to parse the account data
|
|
69
|
+
try:
|
|
70
|
+
# Assuming the node.data contains a serialized JSON or similar format
|
|
71
|
+
# that can be converted to a dictionary and then to an Account
|
|
72
|
+
import json
|
|
73
|
+
account_dict = json.loads(node.data.decode('utf-8'))
|
|
74
|
+
return Account.from_dict(account_dict)
|
|
75
|
+
except Exception as e:
|
|
76
|
+
print(f"Error parsing account data: {e}")
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def get_validator_stake(accounts: Dict[bytes, Account], validator_address: bytes) -> int:
|
|
81
|
+
"""
|
|
82
|
+
Get the stake amount for a specific validator.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
accounts: Dictionary mapping addresses to Account objects
|
|
86
|
+
validator_address: Address of the validator to check
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Stake amount for the validator, or 0 if not a validator
|
|
90
|
+
"""
|
|
91
|
+
# This function would need to be updated to use a separate stake lookup
|
|
92
|
+
# For now, returning 0 as a placeholder
|
|
93
|
+
return 0
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def is_validator(accounts: Dict[bytes, Account], address: bytes) -> bool:
|
|
97
|
+
"""
|
|
98
|
+
Check if an address is registered as a validator.
|
|
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
|
+
)
|