astreum 0.1.18__tar.gz → 0.1.20__tar.gz

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.

Files changed (68) hide show
  1. {astreum-0.1.18/src/astreum.egg-info → astreum-0.1.20}/PKG-INFO +1 -1
  2. {astreum-0.1.18 → astreum-0.1.20}/pyproject.toml +1 -1
  3. astreum-0.1.20/src/astreum/node/__init__.py +447 -0
  4. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/node/storage/merkle.py +65 -65
  5. astreum-0.1.20/src/astreum/node/validation/__init__.py +0 -0
  6. astreum-0.1.20/src/astreum/node/validation/_block/__init__.py +0 -0
  7. astreum-0.1.20/src/astreum/node/validation/block.py +21 -0
  8. astreum-0.1.20/src/astreum/utils/__init__.py +0 -0
  9. {astreum-0.1.18 → astreum-0.1.20/src/astreum.egg-info}/PKG-INFO +1 -1
  10. {astreum-0.1.18 → astreum-0.1.20}/src/astreum.egg-info/SOURCES.txt +0 -1
  11. astreum-0.1.18/src/astreum/node/__init__.py +0 -475
  12. astreum-0.1.18/src/astreum/node/storage/__init__.py +0 -13
  13. astreum-0.1.18/src/astreum/node/validation/__init__.py +0 -84
  14. astreum-0.1.18/src/astreum/node/validation/_block/__init__.py +0 -12
  15. astreum-0.1.18/src/astreum/node/validation/block.py +0 -30
  16. astreum-0.1.18/src/astreum/node/validation/state.py +0 -230
  17. {astreum-0.1.18 → astreum-0.1.20}/LICENSE +0 -0
  18. {astreum-0.1.18 → astreum-0.1.20}/README.md +0 -0
  19. {astreum-0.1.18 → astreum-0.1.20}/setup.cfg +0 -0
  20. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/__init__.py +0 -0
  21. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/lispeum/__init__.py +0 -0
  22. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/lispeum/expression.py +0 -0
  23. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/lispeum/parser.py +0 -0
  24. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/lispeum/special/__init__.py +0 -0
  25. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/lispeum/special/definition.py +0 -0
  26. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/lispeum/special/list/__init__.py +0 -0
  27. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/lispeum/special/list/all.py +0 -0
  28. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/lispeum/special/list/any.py +0 -0
  29. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/lispeum/special/list/fold.py +0 -0
  30. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/lispeum/special/list/get.py +0 -0
  31. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/lispeum/special/list/insert.py +0 -0
  32. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/lispeum/special/list/map.py +0 -0
  33. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/lispeum/special/list/position.py +0 -0
  34. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/lispeum/special/list/remove.py +0 -0
  35. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/lispeum/special/number/__init__.py +0 -0
  36. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/lispeum/special/number/addition.py +0 -0
  37. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/lispeum/storage.py +0 -0
  38. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/lispeum/tokenizer.py +0 -0
  39. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/lispeum/utils.py +0 -0
  40. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/machine/__init__.py +0 -0
  41. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/machine/environment.py +0 -0
  42. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/machine/error.py +0 -0
  43. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/node/crypto/__init__.py +0 -0
  44. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/node/crypto/ed25519.py +0 -0
  45. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/node/crypto/x25519.py +0 -0
  46. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/node/relay/__init__.py +0 -0
  47. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/node/relay/bucket.py +0 -0
  48. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/node/relay/envelope.py +0 -0
  49. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/node/relay/message.py +0 -0
  50. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/node/relay/peer.py +0 -0
  51. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/node/relay/route.py +0 -0
  52. {astreum-0.1.18/src/astreum/utils → astreum-0.1.20/src/astreum/node/storage}/__init__.py +0 -0
  53. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/node/storage/patricia.py +0 -0
  54. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/node/storage/storage.py +0 -0
  55. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/node/storage/utils.py +0 -0
  56. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/node/utils.py +0 -0
  57. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/node/validation/_block/create.py +0 -0
  58. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/node/validation/_block/model.py +0 -0
  59. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/node/validation/_block/validate.py +0 -0
  60. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/node/validation/account.py +0 -0
  61. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/node/validation/constants.py +0 -0
  62. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/node/validation/stake.py +0 -0
  63. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/node/validation/transaction.py +0 -0
  64. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/node/validation/vdf.py +0 -0
  65. {astreum-0.1.18 → astreum-0.1.20}/src/astreum/utils/bytes_format.py +0 -0
  66. {astreum-0.1.18 → astreum-0.1.20}/src/astreum.egg-info/dependency_links.txt +0 -0
  67. {astreum-0.1.18 → astreum-0.1.20}/src/astreum.egg-info/requires.txt +0 -0
  68. {astreum-0.1.18 → astreum-0.1.20}/src/astreum.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: astreum
3
- Version: 0.1.18
3
+ Version: 0.1.20
4
4
  Summary: Python library to interact with the Astreum blockchain and its Lispeum virtual machine.
5
5
  Author-email: "Roy R. O. Okello" <roy@stelar.xyz>
6
6
  Project-URL: Homepage, https://github.com/astreum/lib
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "astreum"
3
- version = "0.1.18"
3
+ version = "0.1.20"
4
4
  authors = [
5
5
  { name="Roy R. O. Okello", email="roy@stelar.xyz" },
6
6
  ]
@@ -0,0 +1,447 @@
1
+ import time
2
+ import threading
3
+ from typing import List
4
+ from cryptography.hazmat.primitives.asymmetric import ed25519
5
+ from cryptography.hazmat.primitives import serialization
6
+
7
+ from .relay import Relay, Topic
8
+ from ..machine import AstreumMachine
9
+ from .utils import hash_data
10
+ from .validation.block import Block
11
+ from .storage.storage import Storage
12
+
13
+ class Node:
14
+ def __init__(self, config: dict):
15
+ # Ensure config is a dictionary, but allow it to be None
16
+ self.config = config if config is not None else {}
17
+
18
+ # Handle validation key if provided
19
+ self.validation_private_key = None
20
+ self.validation_public_key = None
21
+ self.is_validator = False
22
+
23
+ # Extract validation private key from config
24
+ if 'validation_private_key' in self.config:
25
+ try:
26
+ key_bytes = bytes.fromhex(self.config['validation_private_key'])
27
+ self.validation_private_key = ed25519.Ed25519PrivateKey.from_private_bytes(key_bytes)
28
+ self.validation_public_key = self.validation_private_key.public_key()
29
+ self.is_validator = True
30
+
31
+ # Set validation_route to True in config so relay will join validation route
32
+ self.config['validation_route'] = True
33
+ print(f"Node is configured as a validator with validation key")
34
+ except Exception as e:
35
+ print(f"Error loading validation private key: {e}")
36
+
37
+ # Initialize relay with our config
38
+ self.relay = Relay(self.config)
39
+
40
+ # Get the node_id from relay
41
+ self.node_id = self.relay.node_id
42
+
43
+ # Initialize storage
44
+ self.storage = Storage(self.config)
45
+ self.storage.node = self # Set the storage node reference to self
46
+
47
+ # Initialize blockchain state
48
+ self.blockchain = create_account_state(self.config)
49
+
50
+ # Store our validator info if we're a validator
51
+ if self.is_validator and self.validation_public_key:
52
+ self.validator_address = self.validation_public_key.public_bytes(
53
+ encoding=serialization.Encoding.Raw,
54
+ format=serialization.PublicFormat.Raw
55
+ )
56
+ self.validator_private_bytes = self.validation_private_key.private_bytes(
57
+ encoding=serialization.Encoding.Raw,
58
+ format=serialization.PrivateFormat.Raw,
59
+ encryption_algorithm=serialization.NoEncryption()
60
+ )
61
+ print(f"Registered validator with address: {self.validator_address.hex()}")
62
+ else:
63
+ self.validator_address = None
64
+ self.validator_private_bytes = None
65
+
66
+ # Latest block of the chain this node is following
67
+ self.latest_block = None
68
+ self.followed_chain_id = self.config.get('followed_chain_id', None)
69
+
70
+ # Initialize machine
71
+ self.machine = AstreumMachine(node=self)
72
+
73
+ # Register message handlers
74
+ self.relay.message_handlers[Topic.PEER_ROUTE] = self._handle_peer_route
75
+ self.relay.message_handlers[Topic.PING] = self._handle_ping
76
+ self.relay.message_handlers[Topic.PONG] = self._handle_pong
77
+ self.relay.message_handlers[Topic.OBJECT_REQUEST] = self._handle_object_request
78
+ self.relay.message_handlers[Topic.OBJECT_RESPONSE] = self._handle_object_response
79
+ self.relay.message_handlers[Topic.ROUTE_REQUEST] = self._handle_route_request
80
+ self.relay.message_handlers[Topic.ROUTE] = self._handle_route
81
+ self.relay.message_handlers[Topic.LATEST_BLOCK_REQUEST] = self._handle_latest_block_request
82
+ self.relay.message_handlers[Topic.LATEST_BLOCK] = self._handle_latest_block
83
+ self.relay.message_handlers[Topic.TRANSACTION] = self._handle_transaction
84
+ self.relay.message_handlers[Topic.BLOCK_REQUEST] = self._handle_block_request
85
+ self.relay.message_handlers[Topic.BLOCK_RESPONSE] = self._handle_block_response
86
+
87
+ # Initialize latest block from storage if available
88
+ self._initialize_latest_block()
89
+
90
+ # Candidate chains that might be adopted
91
+ self.candidate_chains = {} # chain_id -> {'latest_block': block, 'timestamp': time.time()}
92
+ self.pending_blocks = {} # block_hash -> {'block': block, 'timestamp': time.time()}
93
+
94
+ # Threads for validation and chain monitoring
95
+ self.running = False
96
+ self.main_chain_validation_thread = None
97
+ self.candidate_chain_validation_thread = None
98
+
99
+ # Pending transactions for a block
100
+ self.pending_transactions = {} # tx_hash -> {'transaction': tx, 'timestamp': time.time()}
101
+
102
+ # Last block production attempt time
103
+ self.last_block_attempt_time = 0
104
+
105
+ def start(self):
106
+ """Start the node."""
107
+ self.running = True
108
+
109
+ # Start relay
110
+ self.relay.start()
111
+
112
+ # Start chain monitoring thread
113
+ self.main_chain_validation_thread = threading.Thread(
114
+ target=self._main_chain_validation_loop,
115
+ name="MainChainValidation"
116
+ )
117
+ self.main_chain_validation_thread.daemon = True
118
+ self.main_chain_validation_thread.start()
119
+
120
+ self.candidate_chain_validation_thread = threading.Thread(
121
+ target=self._candidate_chain_validation_loop,
122
+ name="CandidateChainValidation"
123
+ )
124
+ self.candidate_chain_validation_thread.daemon = True
125
+ self.candidate_chain_validation_thread.start()
126
+
127
+ # Set up recurring block query tasks
128
+ main_query_thread = threading.Thread(
129
+ target=self._block_query_loop,
130
+ args=('main',),
131
+ daemon=True
132
+ )
133
+ main_query_thread.start()
134
+
135
+ validation_query_thread = threading.Thread(
136
+ target=self._block_query_loop,
137
+ args=('validation',),
138
+ daemon=True
139
+ )
140
+ validation_query_thread.start()
141
+
142
+ print(f"Node started with ID {self.node_id.hex()}")
143
+
144
+ def stop(self):
145
+ """Stop the node and all its services."""
146
+ self.running = False
147
+
148
+ # Stop all threads
149
+ if self.main_chain_validation_thread and self.main_chain_validation_thread.is_alive():
150
+ self.main_chain_validation_thread.join(timeout=1.0)
151
+
152
+ if self.candidate_chain_validation_thread and self.candidate_chain_validation_thread.is_alive():
153
+ self.candidate_chain_validation_thread.join(timeout=1.0)
154
+
155
+ # Stop relay last
156
+ if self.relay:
157
+ self.relay.stop()
158
+
159
+ print("Node stopped")
160
+
161
+ def _main_chain_validation_loop(self):
162
+ """
163
+ Main validation loop for the primary blockchain.
164
+ This thread prioritizes validating blocks on the main chain we're following.
165
+ """
166
+ while self.running:
167
+ try:
168
+ # Update latest block if we don't have one yet
169
+ if not self.latest_block and hasattr(self.blockchain, 'get_latest_block'):
170
+ self.latest_block = self.blockchain.get_latest_block()
171
+
172
+ # Process any blocks that extend our main chain immediately
173
+ self._process_main_chain_blocks()
174
+
175
+ # Attempt block production if we are a validator
176
+ if self.is_validator and self.validator_address:
177
+ self._attempt_block_production()
178
+
179
+ # Cleanup old items
180
+ self._prune_pending_items()
181
+
182
+ # Sleep to prevent high CPU usage
183
+ time.sleep(0.1) # Short sleep for main chain validation
184
+ except Exception as e:
185
+ print(f"Error in main chain validation loop: {e}")
186
+ time.sleep(1) # Longer sleep on error
187
+
188
+ def _candidate_chain_validation_loop(self):
189
+ """
190
+ Validation loop for candidate chains (potential forks).
191
+ This thread handles validation of blocks from alternate chains
192
+ without slowing down the main chain processing.
193
+ """
194
+ while self.running:
195
+ try:
196
+ # Process candidate chains
197
+ self._evaluate_candidate_chains()
198
+
199
+ # Prune old candidate chains
200
+ self._prune_candidate_chains()
201
+
202
+ # Sleep longer for candidate chain validation (lower priority)
203
+ time.sleep(1) # Longer sleep for candidate chain validation
204
+ except Exception as e:
205
+ print(f"Error in candidate chain validation loop: {e}")
206
+ time.sleep(2) # Even longer sleep on error
207
+
208
+ def _prune_pending_items(self):
209
+ """Remove old pending blocks and transactions."""
210
+ current_time = time.time()
211
+
212
+ # Prune old pending blocks (older than 1 hour)
213
+ blocks_to_remove = [
214
+ block_hash for block_hash, data in self.pending_blocks.items()
215
+ if current_time - data['timestamp'] > 3600 # 1 hour
216
+ ]
217
+ for block_hash in blocks_to_remove:
218
+ del self.pending_blocks[block_hash]
219
+
220
+ # Prune old pending transactions (older than 30 minutes)
221
+ txs_to_remove = [
222
+ tx_hash for tx_hash, data in self.pending_transactions.items()
223
+ if current_time - data['timestamp'] > 1800 # 30 minutes
224
+ ]
225
+ for tx_hash in txs_to_remove:
226
+ del self.pending_transactions[tx_hash]
227
+
228
+ def _process_main_chain_blocks(self):
229
+ """
230
+ Process blocks that extend our current main chain.
231
+ Prioritizes blocks that build on our latest block.
232
+ """
233
+ # Skip if we don't have a latest block yet
234
+ if not self.latest_block:
235
+ return
236
+
237
+ # Get the hash of our latest block
238
+ latest_hash = self.latest_block.get_hash()
239
+
240
+ # Find any pending blocks that build on our latest block
241
+ main_chain_blocks = []
242
+ for block_hash, data in list(self.pending_blocks.items()):
243
+ block = data['block']
244
+
245
+ # Check if this block extends our latest block
246
+ if block.previous == latest_hash:
247
+ main_chain_blocks.append(block)
248
+
249
+ # Process found blocks
250
+ for block in main_chain_blocks:
251
+ self._validate_and_process_main_chain_block(block)
252
+
253
+ def _validate_and_process_main_chain_block(self, block: Block):
254
+ """
255
+ Validate and process a block that extends our main chain.
256
+
257
+ Args:
258
+ block: Block to validate and process
259
+ """
260
+ try:
261
+ # Validate block
262
+ is_valid = validate_block(block, self.blockchain.get_accounts_at_block(block.previous), self.blockchain.get_blocks())
263
+
264
+ if is_valid:
265
+ # Apply block to our state
266
+ success = validate_and_apply_block(self.blockchain, block)
267
+ if success:
268
+ print(f"Applied valid block {block.number} to blockchain state")
269
+ self._update_latest_block(block)
270
+ blocks_to_remove = [block.get_hash()]
271
+ for block_hash in blocks_to_remove:
272
+ if block_hash in self.pending_blocks:
273
+ del self.pending_blocks[block_hash]
274
+ print(f"Added block {block.number} to blockchain")
275
+ return True
276
+ except Exception as e:
277
+ print(f"Error validating main chain block {block.number}: {e}")
278
+
279
+ return False
280
+
281
+ def _evaluate_candidate_chains(self):
282
+ """
283
+ Evaluate candidate chains to determine if any should become our main chain.
284
+ This will validate pending blocks and look for chains with higher cumulative difficulty.
285
+ """
286
+ # Skip if no candidate chains
287
+ if not self.candidate_chains:
288
+ return
289
+
290
+ # For each candidate chain, validate blocks and calculate metrics
291
+ for chain_id, data in list(self.candidate_chains.items()):
292
+ latest_candidate_block = data['latest_block']
293
+
294
+ # Build the chain backwards
295
+ chain_blocks = self._build_chain_from_latest(latest_candidate_block)
296
+
297
+ # Skip if we couldn't build a complete chain
298
+ if not chain_blocks:
299
+ continue
300
+
301
+ # Validate the entire chain
302
+ valid_chain = self._validate_candidate_chain(chain_blocks)
303
+
304
+ # If valid and better than our current chain, switch to it
305
+ if valid_chain and self._is_better_chain(chain_blocks):
306
+ self._switch_to_new_chain(chain_blocks)
307
+
308
+ def _build_chain_from_latest(self, latest_block: Block) -> List[Block]:
309
+ """
310
+ Build a chain from the latest block back to a known point in our blockchain.
311
+
312
+ Args:
313
+ latest_block: Latest block in the candidate chain
314
+
315
+ Returns:
316
+ List of blocks in the chain, ordered from oldest to newest
317
+ """
318
+ chain_blocks = [latest_block]
319
+ current_block = latest_block
320
+
321
+ # Track visited blocks to avoid cycles
322
+ visited = {current_block.get_hash()}
323
+
324
+ # Build chain backwards until we either:
325
+ # 1. Find a block in our main chain
326
+ # 2. Run out of blocks
327
+ # 3. Detect a cycle
328
+ while current_block.number > 0:
329
+ previous_hash = current_block.previous
330
+
331
+ # Check if we have this block in our blockchain
332
+ if hasattr(self.blockchain, 'has_block') and self.blockchain.has_block(previous_hash):
333
+ # Found connection to our main chain
334
+ previous_block = self.blockchain.get_block(previous_hash)
335
+ chain_blocks.insert(0, previous_block)
336
+ break
337
+
338
+ # Check if block is in pending blocks
339
+ elif previous_hash in self.pending_blocks:
340
+ previous_block = self.pending_blocks[previous_hash]['block']
341
+
342
+ # Check for cycles
343
+ if previous_hash in visited:
344
+ print(f"Cycle detected in candidate chain at block {previous_block.number}")
345
+ return []
346
+
347
+ visited.add(previous_hash)
348
+ chain_blocks.insert(0, previous_block)
349
+ current_block = previous_block
350
+ else:
351
+ # Missing block, cannot validate the chain
352
+ print(f"Missing block {previous_hash.hex()} in candidate chain")
353
+ return []
354
+
355
+ return chain_blocks
356
+
357
+ def _validate_candidate_chain(self, chain_blocks: List[Block]) -> bool:
358
+ """
359
+ Validate a candidate chain of blocks.
360
+
361
+ Args:
362
+ chain_blocks: List of blocks in the chain (oldest to newest)
363
+
364
+ Returns:
365
+ True if the chain is valid, False otherwise
366
+ """
367
+ # Validate each block in the chain
368
+ for i, block in enumerate(chain_blocks):
369
+ # Skip first block, it's either genesis or a block we already have
370
+ if i == 0:
371
+ continue
372
+
373
+ # Validate block connections
374
+ if block.previous != chain_blocks[i-1].get_hash():
375
+ print(f"Invalid chain: block {block.number} does not reference previous block")
376
+ return False
377
+
378
+ # Validate block
379
+ is_valid = validate_block(block, self.blockchain.get_accounts_at_block(block.previous), self.blockchain.get_blocks())
380
+ if not is_valid:
381
+ print(f"Invalid chain: block {block.number} is invalid")
382
+ return False
383
+
384
+ return True
385
+
386
+ def _is_better_chain(self, chain_blocks: List[Block]) -> bool:
387
+ """
388
+ Determine if a candidate chain is better than our current chain.
389
+
390
+ Args:
391
+ chain_blocks: List of blocks in the candidate chain
392
+
393
+ Returns:
394
+ True if the candidate chain is better, False otherwise
395
+ """
396
+ # Get the latest block from the candidate chain
397
+ candidate_latest = chain_blocks[-1]
398
+
399
+ # If we don't have a latest block, any valid chain is better
400
+ if not self.latest_block:
401
+ return True
402
+
403
+ # Compare block numbers (longest chain rule)
404
+ if candidate_latest.number > self.latest_block.number:
405
+ print(f"Candidate chain is longer: {candidate_latest.number} vs {self.latest_block.number}")
406
+ return True
407
+
408
+ return False
409
+
410
+ def _switch_to_new_chain(self, chain_blocks: List[Block]):
411
+ """
412
+ Switch to a new chain by adding all blocks to our blockchain.
413
+
414
+ Args:
415
+ chain_blocks: List of blocks in the chain (oldest to newest)
416
+ """
417
+ # Find the point where the chains diverge
418
+ divergence_point = 0
419
+ for i, block in enumerate(chain_blocks):
420
+ # Check if we have this block in our blockchain
421
+ if hasattr(self.blockchain, 'has_block') and self.blockchain.has_block(block.get_hash()):
422
+ divergence_point = i + 1
423
+ else:
424
+ break
425
+
426
+ # Add all blocks after the divergence point
427
+ for i in range(divergence_point, len(chain_blocks)):
428
+ block = chain_blocks[i]
429
+
430
+ # Add block to blockchain
431
+ if hasattr(self.blockchain, 'add_block'):
432
+ try:
433
+ self.blockchain.add_block(block)
434
+
435
+ # Remove from pending blocks
436
+ block_hash = block.get_hash()
437
+ if block_hash in self.pending_blocks:
438
+ del self.pending_blocks[block_hash]
439
+
440
+ print(f"Added block {block.number} to blockchain")
441
+ except Exception as e:
442
+ print(f"Error adding block {block.number} to blockchain: {e}")
443
+ return
444
+
445
+ # Update latest block
446
+ self._update_latest_block(chain_blocks[-1])
447
+ print(f"Switched to new chain, latest block: {self.latest_block.number}")
@@ -1,7 +1,71 @@
1
1
  import blake3
2
2
  from .storage import Storage
3
- import astreum.utils.bytes_format as bytes_format bytes_format.decode, bytes_format.encode
3
+ from astreum.utils import bytes_format
4
4
 
5
+ class MerkleNode:
6
+ def __init__(self, leaf: bool, data: bytes):
7
+ """
8
+ Initialize a Merkle node.
9
+
10
+ For a leaf node, `data` is the actual content to be stored.
11
+ For an internal node, `data` should be the concatenation of the two child hashes.
12
+
13
+ :param leaf: A boolean flag indicating whether this node is a leaf node (True) or an internal node (False).
14
+ :param data: The node's data. For leaves, the stored data; for internal nodes, concatenated child hashes.
15
+ """
16
+ self.leaf = leaf
17
+ self.data = data
18
+ self._hash = None # Cached hash value to avoid recomputation.
19
+
20
+ @classmethod
21
+ def from_bytes(cls, data: bytes) -> 'MerkleNode':
22
+ """
23
+ Deserialize a MerkleNode from its byte representation.
24
+
25
+ The input bytes are expected to be in the Astreum format, containing a leaf flag and node data.
26
+
27
+ :param data: The serialized node data.
28
+ :return: A new MerkleNode instance.
29
+ """
30
+ leaf_flag, node_data = bytes_format.decode(data)
31
+ return cls(True if leaf_flag == 1 else False, node_data)
32
+
33
+ @classmethod
34
+ def from_storage(cls, storage: Storage, hash_value: bytes) -> 'MerkleNode' or None:
35
+ """
36
+ Retrieve and deserialize a MerkleNode from storage using its hash.
37
+
38
+ :param storage: The Storage instance used to retrieve the node.
39
+ :param hash_value: The hash key under which the node is stored.
40
+ :return: A MerkleNode instance if found, otherwise None.
41
+ """
42
+ node_bytes = storage.get(hash_value)
43
+ if node_bytes is None:
44
+ return None
45
+ return cls.from_bytes(node_bytes)
46
+
47
+ def to_bytes(self) -> bytes:
48
+ """
49
+ Serialize the MerkleNode into bytes using the Astreum format.
50
+
51
+ The format encodes a list containing the leaf flag and the node data.
52
+
53
+ :return: The serialized bytes representing the node.
54
+ """
55
+ return bytes_format.encode([1 if self.leaf else 0, self.data])
56
+
57
+ def hash(self) -> bytes:
58
+ """
59
+ Compute (or retrieve a cached) hash of the node using the Blake3 algorithm.
60
+
61
+ For leaf nodes, the hash is computed over the actual data.
62
+ For internal nodes, the hash is computed over the concatenated child hashes.
63
+
64
+ :return: The Blake3 digest of the node's data.
65
+ """
66
+ if self._hash is None:
67
+ self._hash = blake3.blake3(self.data).digest()
68
+ return self._hash
5
69
 
6
70
 
7
71
  class MerkleTree:
@@ -158,67 +222,3 @@ class MerkleTree:
158
222
  return new_node_hash
159
223
 
160
224
 
161
- class MerkleNode:
162
- def __init__(self, leaf: bool, data: bytes):
163
- """
164
- Initialize a Merkle node.
165
-
166
- For a leaf node, `data` is the actual content to be stored.
167
- For an internal node, `data` should be the concatenation of the two child hashes.
168
-
169
- :param leaf: A boolean flag indicating whether this node is a leaf node (True) or an internal node (False).
170
- :param data: The node's data. For leaves, the stored data; for internal nodes, concatenated child hashes.
171
- """
172
- self.leaf = leaf
173
- self.data = data
174
- self._hash = None # Cached hash value to avoid recomputation.
175
-
176
- @classmethod
177
- def from_bytes(cls, data: bytes) -> 'MerkleNode':
178
- """
179
- Deserialize a MerkleNode from its byte representation.
180
-
181
- The input bytes are expected to be in the Astreum format, containing a leaf flag and node data.
182
-
183
- :param data: The serialized node data.
184
- :return: A new MerkleNode instance.
185
- """
186
- leaf_flag, node_data = bytes_format.decode(data)
187
- return cls(True if leaf_flag == 1 else False, node_data)
188
-
189
- @classmethod
190
- def from_storage(cls, storage: Storage, hash_value: bytes) -> 'MerkleNode' or None:
191
- """
192
- Retrieve and deserialize a MerkleNode from storage using its hash.
193
-
194
- :param storage: The Storage instance used to retrieve the node.
195
- :param hash_value: The hash key under which the node is stored.
196
- :return: A MerkleNode instance if found, otherwise None.
197
- """
198
- node_bytes = storage.get(hash_value)
199
- if node_bytes is None:
200
- return None
201
- return cls.from_bytes(node_bytes)
202
-
203
- def to_bytes(self) -> bytes:
204
- """
205
- Serialize the MerkleNode into bytes using the Astreum format.
206
-
207
- The format encodes a list containing the leaf flag and the node data.
208
-
209
- :return: The serialized bytes representing the node.
210
- """
211
- return bytes_format.encode([1 if self.leaf else 0, self.data])
212
-
213
- def hash(self) -> bytes:
214
- """
215
- Compute (or retrieve a cached) hash of the node using the Blake3 algorithm.
216
-
217
- For leaf nodes, the hash is computed over the actual data.
218
- For internal nodes, the hash is computed over the concatenated child hashes.
219
-
220
- :return: The Blake3 digest of the node's data.
221
- """
222
- if self._hash is None:
223
- self._hash = blake3.blake3(self.data).digest()
224
- return self._hash
File without changes
@@ -0,0 +1,21 @@
1
+ class Block:
2
+
3
+ def __init__(self):
4
+ pass
5
+
6
+ @classmethod
7
+ def from_bytes(cls) -> 'Block':
8
+ """
9
+ Deserialize a block from its byte representation.
10
+ """
11
+ return cls()
12
+
13
+ def to_bytes(self) -> bytes:
14
+ """
15
+ Serialize the block into bytes.
16
+ """
17
+ return b""
18
+
19
+ class Chain:
20
+ def __init__(self, latest_block: Block):
21
+ self.latest_block = latest_block
File without changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: astreum
3
- Version: 0.1.18
3
+ Version: 0.1.20
4
4
  Summary: Python library to interact with the Astreum blockchain and its Lispeum virtual machine.
5
5
  Author-email: "Roy R. O. Okello" <roy@stelar.xyz>
6
6
  Project-URL: Homepage, https://github.com/astreum/lib
@@ -50,7 +50,6 @@ src/astreum/node/validation/account.py
50
50
  src/astreum/node/validation/block.py
51
51
  src/astreum/node/validation/constants.py
52
52
  src/astreum/node/validation/stake.py
53
- src/astreum/node/validation/state.py
54
53
  src/astreum/node/validation/transaction.py
55
54
  src/astreum/node/validation/vdf.py
56
55
  src/astreum/node/validation/_block/__init__.py