astreum 0.1.12__py3-none-any.whl → 0.1.14__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/__init__.py CHANGED
@@ -1,8 +1,11 @@
1
1
  import os
2
2
  import hashlib
3
3
  import time
4
- from typing import Tuple, Optional
4
+ import threading
5
+ import random
6
+ from typing import Tuple, Optional, List
5
7
  import json
8
+ from cryptography.hazmat.primitives.asymmetric import ed25519
6
9
 
7
10
  from .relay import Relay, Topic
8
11
  from .relay.peer import Peer
@@ -15,9 +18,33 @@ class Node:
15
18
  def __init__(self, config: dict):
16
19
  # Ensure config is a dictionary, but allow it to be None
17
20
  self.config = config if config is not None else {}
21
+
22
+ # Handle validation key if provided
23
+ self.validation_private_key = None
24
+ self.validation_public_key = None
25
+ self.is_validator = False
26
+
27
+ # Extract validation private key from config
28
+ if 'validation_private_key' in self.config:
29
+ try:
30
+ key_bytes = bytes.fromhex(self.config['validation_private_key'])
31
+ self.validation_private_key = ed25519.Ed25519PrivateKey.from_private_bytes(key_bytes)
32
+ self.validation_public_key = self.validation_private_key.public_key()
33
+ self.is_validator = True
34
+
35
+ # Set validation_route to True in config so relay will join validation route
36
+ self.config['validation_route'] = True
37
+ print(f"Node is configured as a validator with validation key")
38
+ except Exception as e:
39
+ print(f"Error loading validation private key: {e}")
40
+
41
+ # Initialize relay with our config
18
42
  self.relay = Relay(self.config)
19
- # Get the node_id from relay instead of generating our own
43
+
44
+ # Get the node_id from relay
20
45
  self.node_id = self.relay.node_id
46
+
47
+ # Initialize storage
21
48
  self.storage = Storage(self.config)
22
49
  self.storage.node = self # Set the storage node reference to self
23
50
 
@@ -46,6 +73,84 @@ class Node:
46
73
  # Candidate chains that might be adopted
47
74
  self.candidate_chains = {} # chain_id -> {'latest_block': block, 'timestamp': time.time()}
48
75
 
76
+ # Block query timers for different routes
77
+ self.running = False
78
+ self.block_query_threads = []
79
+
80
+ def start(self):
81
+ """Start the node and all its services."""
82
+ self.running = True
83
+
84
+ # Start periodic block query thread for validation route
85
+ validation_query_thread = threading.Thread(
86
+ target=self._periodic_validation_route_query,
87
+ daemon=True
88
+ )
89
+ validation_query_thread.start()
90
+ self.block_query_threads.append(validation_query_thread)
91
+
92
+ print(f"Node started with ID {self.node_id.hex()}")
93
+ print(f"Listening on port {self.relay.incoming_port}")
94
+
95
+ def stop(self):
96
+ """Stop the node and all its services."""
97
+ self.running = False
98
+
99
+ # Stop all threads
100
+ for thread in self.block_query_threads:
101
+ if thread.is_alive():
102
+ thread.join(timeout=1.0) # Give threads 1 second to shut down
103
+
104
+ print("Node stopped")
105
+
106
+ def _periodic_validation_route_query(self):
107
+ """Periodically query random peers in the validation route for latest blocks."""
108
+ while self.running:
109
+ try:
110
+ # Query 3 random peers from validation route for latest block
111
+ self._query_random_peers_for_latest_block(route_type=1, count=3)
112
+
113
+ # Prune old candidate chains periodically
114
+ self._prune_candidate_chains()
115
+
116
+ # Sleep according to validator status
117
+ if self.is_validator:
118
+ # Validators check more frequently (every second)
119
+ time.sleep(1)
120
+ else:
121
+ # Regular nodes check less frequently (every 3 seconds)
122
+ time.sleep(3)
123
+ except Exception as e:
124
+ print(f"Error in validation route query: {e}")
125
+ time.sleep(1) # Sleep briefly before retrying
126
+
127
+ def _query_random_peers_for_latest_block(self, route_type: int, count: int = 3):
128
+ """
129
+ Query random peers from specified route for latest block.
130
+
131
+ Args:
132
+ route_type (int): Route type (0 for peer, 1 for validation)
133
+ count (int): Number of random peers to query
134
+ """
135
+ # Only continue if we're tracking this route
136
+ if not self.relay.is_tracking_route(route_type):
137
+ return
138
+
139
+ # Get random peers from the route
140
+ random_peers = self.relay.get_random_peers_from_route(route_type, count)
141
+
142
+ # Query each peer for latest block
143
+ for peer in random_peers:
144
+ try:
145
+ # Create empty request message
146
+ request_data = b''
147
+
148
+ # Send request to peer
149
+ addr = (peer.ip, peer.port)
150
+ self.relay.send_message(request_data, Topic.LATEST_BLOCK_REQUEST, addr)
151
+ except Exception as e:
152
+ print(f"Error querying peer {peer.node_id.hex()}: {e}")
153
+
49
154
  def _handle_ping(self, body: bytes, addr: Tuple[str, int], envelope):
50
155
  """
51
156
  Handle ping messages by storing peer info and responding with a pong.
@@ -358,10 +463,7 @@ class Node:
358
463
  in chain is in the previous field.
359
464
  """
360
465
  try:
361
- # Check if we're in the validation route
362
- # This is now already checked by the relay's _handle_message method
363
- if not self.relay.is_in_validation_route():
364
- return
466
+ # All nodes can process latest blocks now, regardless of route membership
365
467
 
366
468
  # Deserialize the block
367
469
  block = Block.from_bytes(body)
@@ -374,71 +476,91 @@ class Node:
374
476
  if not self.followed_chain_id or block.chain_id != self.followed_chain_id:
375
477
  self._add_candidate_chain(block)
376
478
  return
377
-
378
- # Get our current latest block
379
- our_latest = self.latest_block
380
-
381
- # Verify block hash links to our latest block
382
- if our_latest and block.previous_hash == our_latest.hash:
383
- # Process the valid block
384
- self.machine.process_block(block)
385
479
 
386
- # Update our latest block
387
- self.latest_block = block
388
- # Check if this block is ahead of our current chain
389
- elif our_latest and block.height > our_latest.height:
390
- # Block is ahead but doesn't link directly to our latest
391
- # Add to candidate chains for potential future adoption
392
- self._add_candidate_chain(block)
393
-
394
- # No automatic broadcasting - nodes will request latest blocks when needed
480
+ # Only proceed if block chain_id matches what we're following
481
+ if self.followed_chain_id and block.chain_id != self.followed_chain_id:
482
+ return
483
+
484
+ if not self.latest_block:
485
+ # We don't have a latest block, so this might be the first one we've seen
486
+ # Store it as our latest
487
+ self._update_latest_block(block)
488
+ return
489
+
490
+ # If this block is newer than our latest, update our latest block
491
+ if block.height > self.latest_block.height:
492
+ # Verify chain continuity
493
+ if self.latest_block.hash_bytes in block.previous_blocks:
494
+ # This is a valid continuation of our chain
495
+ self._update_latest_block(block)
496
+ else:
497
+ # This block doesn't build on our latest, check for forking
498
+ # but continue tracking it as a candidate
499
+ self._add_candidate_chain(block)
500
+
395
501
  except Exception as e:
396
502
  print(f"Error handling latest block: {e}")
397
503
 
398
504
  def _handle_transaction(self, body: bytes, addr: Tuple[str, int], envelope):
399
505
  """
400
- Handle receipt of a transaction.
401
- Accept if validation route is present and counter is valid relative to the latest block in our chain.
506
+ Handle incoming transaction messages.
507
+
508
+ This method is called when we receive a transaction from the network.
509
+ Transactions should only be processed by validator nodes.
510
+
511
+ Args:
512
+ body: Transaction data
513
+ addr: Source address
514
+ envelope: Full message envelope
402
515
  """
516
+ # Ignore if we're not a validator (don't have a validation key)
517
+ if not self.is_validator or not self.relay.is_in_validation_route():
518
+ print("Ignoring transaction as we're not a validator")
519
+ return
520
+
403
521
  try:
404
- # Check if we're in the validation route
405
- # This is now already checked by the relay's _handle_message method
406
- if not self.relay.is_in_validation_route():
407
- return
522
+ # Parse transaction data
523
+ tx_data = json.loads(body.decode('utf-8'))
408
524
 
409
- # Deserialize the transaction
410
- transaction = Transaction.from_bytes(body)
411
- if not transaction:
412
- return
413
-
414
- # Check if we're following this chain
415
- if not self.machine.is_following_chain(transaction.chain_id):
416
- return
417
-
418
- # Verify transaction has a valid validation route
419
- if not transaction.has_valid_route():
420
- return
421
-
422
- # Get latest block from this chain
423
- latest_block = self.machine.get_latest_block(transaction.chain_id)
424
- if not latest_block:
425
- return
426
-
427
- # Verify transaction counter is valid relative to the latest block
428
- if not transaction.is_counter_valid(latest_block):
429
- return
525
+ # Store the transaction in our local storage
526
+ tx_hash = bytes.fromhex(tx_data.get('hash'))
527
+ tx_raw = bytes.fromhex(tx_data.get('data'))
528
+
529
+ # Create transaction entry in storage
530
+ if not self.storage.contains(tx_hash):
531
+ self.storage.put(tx_hash, tx_raw)
532
+ print(f"Stored transaction {tx_hash.hex()}")
430
533
 
431
- # Process the valid transaction
432
- self.machine.process_transaction(transaction)
433
-
434
- # Relay to other peers in the validation route
435
- validation_peers = self.relay.get_route_peers(1) # 1 = validation route
436
- for peer in validation_peers:
437
- if peer.address != addr: # Don't send back to originator
438
- self.relay.send_message(body, Topic.TRANSACTION, peer.address)
534
+ # Process the transaction as a validator
535
+ self._process_transaction_as_validator(tx_hash, tx_raw)
536
+
439
537
  except Exception as e:
440
538
  print(f"Error handling transaction: {e}")
441
539
 
540
+ def _process_transaction_as_validator(self, tx_hash: bytes, tx_raw: bytes):
541
+ """
542
+ Process a transaction as a validator node.
543
+
544
+ This method is called when we receive a transaction and we're a validator.
545
+ It verifies the transaction and may include it in a future block.
546
+
547
+ Args:
548
+ tx_hash: Transaction hash
549
+ tx_raw: Raw transaction data
550
+ """
551
+ try:
552
+ print(f"Processing transaction {tx_hash.hex()} as validator")
553
+ # Here we would verify the transaction and potentially queue it
554
+ # for inclusion in the next block we create
555
+
556
+ # For now, just log that we processed it
557
+ print(f"Verified transaction {tx_hash.hex()}")
558
+
559
+ # TODO: Implement transaction validation and queueing for block creation
560
+
561
+ except Exception as e:
562
+ print(f"Error processing transaction as validator: {e}")
563
+
442
564
  def _initialize_latest_block(self):
443
565
  """Initialize latest block from storage if available."""
444
566
  # Implementation would load the latest block from storage
@@ -465,17 +587,19 @@ class Node:
465
587
 
466
588
  def _add_candidate_chain(self, block):
467
589
  """
468
- Add a block to candidate chains for potential future adoption.
590
+ Add a block to the candidate chains.
469
591
 
470
592
  Args:
471
- block: The block to add as a candidate
593
+ block: Block to add
472
594
  """
473
595
  chain_id = block.chain_id
474
596
 
475
- # If we already have this chain as a candidate, only update if this block is newer
597
+ # Check if we already have this chain as a candidate
476
598
  if chain_id in self.candidate_chains:
477
- current_candidate = self.candidate_chains[chain_id]['latest_block']
478
- if block.height > current_candidate.height:
599
+ existing_block = self.candidate_chains[chain_id]['latest_block']
600
+
601
+ # Only update if this block is newer
602
+ if block.height > existing_block.height:
479
603
  self.candidate_chains[chain_id] = {
480
604
  'latest_block': block,
481
605
  'timestamp': time.time()
@@ -486,10 +610,34 @@ class Node:
486
610
  'latest_block': block,
487
611
  'timestamp': time.time()
488
612
  }
613
+
614
+ print(f"Added candidate chain {chain_id.hex()} with height {block.height}")
615
+
616
+ def _update_latest_block(self, block):
617
+ """
618
+ Update our latest block and process it.
489
619
 
490
- # Prune old candidates (older than 1 hour)
491
- self._prune_candidate_chains()
492
-
620
+ Args:
621
+ block: New latest block to set
622
+ """
623
+ # Process the block if it's new
624
+ if not self.latest_block or block.hash_bytes != self.latest_block.hash_bytes:
625
+ # Process block logic in the machine
626
+ self.machine.process_block(block)
627
+
628
+ # Update our latest block reference
629
+ self.latest_block = block
630
+
631
+ # Update followed chain ID if needed
632
+ if not self.followed_chain_id:
633
+ self.followed_chain_id = block.chain_id
634
+
635
+ print(f"Updated latest block to height {block.height}, hash {block.hash}")
636
+
637
+ # Save latest block to storage for persistence
638
+ if self.storage:
639
+ self.storage.put_latest_block(block)
640
+
493
641
  def _prune_candidate_chains(self):
494
642
  """Remove candidate chains that are older than 1 hour."""
495
643
  current_time = time.time()
@@ -4,6 +4,8 @@ Relay module for handling network communication in the Astreum node.
4
4
 
5
5
  import socket
6
6
  import threading
7
+ import random
8
+ import time
7
9
  from queue import Queue
8
10
  from typing import Tuple, Callable, Dict, Set, Optional, List
9
11
  from .message import Message, Topic
@@ -24,10 +26,10 @@ class Relay:
24
26
  self.num_workers = config.get('num_workers', 4)
25
27
 
26
28
  # Generate Ed25519 keypair for this node
27
- if 'private_key' in config:
29
+ if 'relay_private_key' in config:
28
30
  # Load existing private key if provided
29
31
  try:
30
- private_key_bytes = bytes.fromhex(config['private_key'])
32
+ private_key_bytes = bytes.fromhex(config['relay_private_key'])
31
33
  self.private_key = ed25519.Ed25519PrivateKey.from_private_bytes(private_key_bytes)
32
34
  except Exception as e:
33
35
  print(f"Error loading private key: {e}, generating new one")
@@ -43,12 +45,16 @@ class Relay:
43
45
  # Save private key bytes for config persistence
44
46
  self.private_key_bytes = self.private_key.private_bytes_raw()
45
47
 
46
- # Routes that this node participates in (0 = peer route, 1 = validation route)
48
+ # Routes that this node participates in
49
+ # 0 = peer route, 1 = validation route
50
+ # All routes are tracked by default, but we only join some
47
51
  self.routes: List[int] = []
52
+ self.tracked_routes: List[int] = [0, 1] # Track all routes
48
53
 
49
- # Peer route is always enabled
54
+ # Always join peer route
50
55
  self.routes.append(0) # Peer route
51
56
 
57
+ # Check if this node should join validation route
52
58
  if config.get('validation_route', False):
53
59
  self.routes.append(1) # Validation route
54
60
 
@@ -100,44 +106,53 @@ class Relay:
100
106
  def is_in_validation_route(self) -> bool:
101
107
  """Check if this node is part of the validation route."""
102
108
  return 1 in self.routes
109
+
110
+ def is_tracking_route(self, route_type: int) -> bool:
111
+ """Check if this node is tracking a specific route."""
112
+ return route_type in self.tracked_routes
103
113
 
104
- def add_to_peer_route(self):
105
- """Add this node to the peer route."""
106
- if 0 not in self.routes:
107
- self.routes.append(0)
114
+ def get_random_peers_from_route(self, route_type: int, count: int = 3) -> List[Peer]:
115
+ """
116
+ Get a list of random peers from different buckets in the specified route.
108
117
 
109
- def add_to_validation_route(self):
110
- """Add this node to the validation route."""
111
- if 1 not in self.routes:
112
- self.routes.append(1)
118
+ Args:
119
+ route_type (int): Route type (0 for peer, 1 for validation)
120
+ count (int): Number of random peers to select (one from each bucket)
121
+
122
+ Returns:
123
+ List[Peer]: List of randomly selected peers from different buckets
124
+ """
125
+ result = []
126
+ route_id = self._get_route_id(route_type)
113
127
 
114
- def remove_from_peer_route(self):
115
- """Remove this node from the peer route."""
116
- if 0 in self.routes:
117
- self.routes.remove(0)
128
+ # Get all buckets that have peers for this route
129
+ buckets_with_peers = []
130
+ for i, bucket in enumerate(self.routing_table):
131
+ # For each bucket, collect peers that are in this route
132
+ route_peers_in_bucket = [peer for peer in bucket.values()
133
+ if peer.routes and route_id in peer.routes]
134
+ if route_peers_in_bucket:
135
+ buckets_with_peers.append((i, route_peers_in_bucket))
118
136
 
119
- def remove_from_validation_route(self):
120
- """Remove this node from the validation route."""
121
- if 1 in self.routes:
122
- self.routes.remove(1)
137
+ # If we don't have any buckets with peers, return empty list
138
+ if not buckets_with_peers:
139
+ return []
123
140
 
124
- def add_peer_to_route(self, peer: Peer, route_types: List[int]):
125
- """
126
- Add a peer to specified routes.
141
+ # If we have fewer buckets than requested count, adjust count
142
+ sample_count = min(count, len(buckets_with_peers))
127
143
 
128
- Args:
129
- peer (Peer): The peer to add
130
- route_types (List[int]): List of route types to add the peer to (0 = peer, 1 = validation)
131
- """
132
- for route_type in route_types:
133
- if route_type == 0: # Peer route
134
- # Add to top of bucket, eject last if at capacity
135
- self.peer_route_bucket.add(peer, to_front=True)
136
- elif route_type == 1: # Validation route
137
- # Add to top of bucket, eject last if at capacity
138
- self.validation_route_bucket.add(peer, to_front=True)
144
+ # Sample random buckets
145
+ selected_buckets = random.sample(buckets_with_peers, sample_count)
146
+
147
+ # For each selected bucket, pick one random peer
148
+ for bucket_idx, peers in selected_buckets:
149
+ # Select one random peer from this bucket
150
+ selected_peer = random.choice(peers)
151
+ result.append(selected_peer)
139
152
 
140
- def get_route_peers(self, route_type: int) -> List[Peer]:
153
+ return result
154
+
155
+ def get_peers_in_route(self, route_type: int) -> List[Peer]:
141
156
  """
142
157
  Get all peers in a specific route.
143
158
 
@@ -153,6 +168,22 @@ class Relay:
153
168
  return self.validation_route_bucket.get_peers()
154
169
  return []
155
170
 
171
+ def add_peer_to_route(self, peer: Peer, route_types: List[int]):
172
+ """
173
+ Add a peer to specified routes.
174
+
175
+ Args:
176
+ peer (Peer): The peer to add
177
+ route_types (List[int]): List of route types to add the peer to (0 = peer, 1 = validation)
178
+ """
179
+ for route_type in route_types:
180
+ if route_type == 0: # Peer route
181
+ # Add to top of bucket, eject last if at capacity
182
+ self.peer_route_bucket.add(peer, to_front=True)
183
+ elif route_type == 1: # Validation route
184
+ # Add to top of bucket, eject last if at capacity
185
+ self.validation_route_bucket.add(peer, to_front=True)
186
+
156
187
  def register_message_handler(self, topic: Topic, handler_func):
157
188
  """Register a handler function for a specific message topic."""
158
189
  self.message_handlers[topic] = handler_func
@@ -215,26 +246,19 @@ class Relay:
215
246
  """Handle an incoming message."""
216
247
  envelope = Envelope.from_bytes(data)
217
248
  if envelope and envelope.message.topic in self.message_handlers:
218
- # Check if this is a transaction or block message that requires validation route
219
- if envelope.message.topic in (Topic.TRANSACTION, Topic.BLOCK):
220
- # Only process if we're part of the validation route
249
+ # For transaction messages, only process if we're in validation route
250
+ if envelope.message.topic == Topic.TRANSACTION:
221
251
  if self.is_in_validation_route():
222
252
  self.message_handlers[envelope.message.topic](envelope.message.body, addr, envelope)
223
- elif envelope.message.topic == Topic.LATEST_BLOCK:
224
- # For latest_block, we only process if we're in the validation route
253
+ # For block messages, only process if we're in validation route
254
+ elif envelope.message.topic == Topic.BLOCK:
225
255
  if self.is_in_validation_route():
226
256
  self.message_handlers[envelope.message.topic](envelope.message.body, addr, envelope)
227
- elif envelope.message.topic in (Topic.LATEST_BLOCK_REQUEST, Topic.GET_BLOCKS):
228
- # Allow all nodes to request blocks for syncing
229
- self.message_handlers[envelope.message.topic](envelope.message.body, addr, envelope)
230
- elif envelope.message.topic == Topic.OBJECT_REQUEST:
231
- # Handle object request
232
- self.message_handlers[envelope.message.topic](envelope.message.body, addr, envelope)
233
- elif envelope.message.topic == Topic.OBJECT_RESPONSE:
234
- # Handle object response
257
+ # Latest block and latest block requests can be handled by any node tracking the routes
258
+ elif envelope.message.topic in (Topic.LATEST_BLOCK, Topic.LATEST_BLOCK_REQUEST):
235
259
  self.message_handlers[envelope.message.topic](envelope.message.body, addr, envelope)
260
+ # For other message types, always process
236
261
  else:
237
- # For other message types, always process
238
262
  self.message_handlers[envelope.message.topic](envelope.message.body, addr, envelope)
239
263
 
240
264
  def send(self, data: bytes, addr: Tuple[str, int]):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: astreum
3
- Version: 0.1.12
3
+ Version: 0.1.14
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
@@ -28,7 +28,8 @@ When initializing an Astreum Node, you need to provide a configuration dictionar
28
28
 
29
29
  | Parameter | Type | Default | Description |
30
30
  |-----------|------|---------|-------------|
31
- | `private_key` | string | Auto-generated | Hex string of Ed25519 private key. If not provided, a new keypair will be generated automatically |
31
+ | `relay_private_key` | string | Auto-generated | Hex string of Ed25519 private key for network identity. If not provided, a new keypair will be generated automatically |
32
+ | `validation_private_key` | string | None | Hex string of Ed25519 private key for block validation. If provided, the node will join the validation route automatically |
32
33
  | `storage_path` | string | "storage" | Path to store data |
33
34
  | `max_storage_space` | int | 1073741824 (1GB) | Maximum storage space in bytes |
34
35
  | `max_object_recursion` | int | 50 | Maximum recursion depth for resolving nested objects |
@@ -47,7 +48,7 @@ When initializing an Astreum Node, you need to provide a configuration dictionar
47
48
 
48
49
  | Parameter | Type | Default | Description |
49
50
  |-----------|------|---------|-------------|
50
- | `validation_route` | bool | False | Whether to participate in the block validation route |
51
+ | `validation_route` | bool | False | Whether to participate in the block validation route (automatically set to True if validation_private_key is provided) |
51
52
  | `bootstrap_peers` | list | [] | List of bootstrap peers in the format `[("hostname", port), ...]` |
52
53
 
53
54
  > **Note:** The peer route is always enabled as it's necessary for object discovery and retrieval.
@@ -59,12 +60,11 @@ from astreum.node import Node
59
60
 
60
61
  # Configuration dictionary
61
62
  config = {
62
- "private_key": "my-private-key-goes-here",
63
+ "relay_private_key": "relay-private-key-hex-string",
64
+ "validation_private_key": "validation-private-key-hex-string", # Optional, for validator nodes
63
65
  "storage_path": "./data/node1",
64
66
  "incoming_port": 7373,
65
67
  "use_ipv6": False,
66
- "peer_route": True,
67
- "validation_route": True,
68
68
  "bootstrap_peers": [
69
69
  ("bootstrap.astreum.org", 7373),
70
70
  ("127.0.0.1", 7374)
@@ -20,9 +20,9 @@ astreum/lispeum/special/number/addition.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5
20
20
  astreum/machine/__init__.py,sha256=GOdZl1tS9uIJHbq5WVcplifMDPDLQroX7CVew-K2YbA,15262
21
21
  astreum/machine/environment.py,sha256=K0084U6B7wwjrDZ9b2_7cEcbBzsB7UOy_Zpbrr7B3GY,834
22
22
  astreum/machine/error.py,sha256=MvqBaZZt33rNELNhUJ2lER3TE3aS8WVqsWF2hz2AwoA,38
23
- astreum/node/__init__.py,sha256=EdGMa9pargAdB7KzjKBkhuFJ3G3wZF2VeIGkYFOCLws,22497
23
+ astreum/node/__init__.py,sha256=Zvu7_oadjB8V3TQpDR9pTdURXZpcHKI5Qhxz4Ykep7U,28271
24
24
  astreum/node/models.py,sha256=9Uf2_u55uxWG0ujjySvFJUO5Ub-EzlMnnMJWcgJHjHk,11980
25
- astreum/node/relay/__init__.py,sha256=gQNYDxllkjZqE6bvgPOYkKu3QHkjPFn11RvtP-BxtKI,13994
25
+ astreum/node/relay/__init__.py,sha256=0zvbchIbLUPqGA7QXJbXokKupcIq6Iu3X3VUpxejIQc,14981
26
26
  astreum/node/relay/bucket.py,sha256=pcmollbbM-xeHlmDxLZnzvf0Ut-9v9RoN6SijYiQuu8,2893
27
27
  astreum/node/relay/envelope.py,sha256=TfkynttoPX7smvMV7xEAdtIlfz-Z-EZjuhZ826csZxA,10078
28
28
  astreum/node/relay/message.py,sha256=uezmGjNaQK4fZmYQLCHd2YpiosaaFb8DOa3H58HS1jA,2887
@@ -30,8 +30,8 @@ astreum/node/relay/peer.py,sha256=DlvTR9j0BZQ1dW-p_9UGgfLvQqwNdpNLMSCYEW4FhyI,58
30
30
  astreum/node/relay/route.py,sha256=fyOSsAe1mfsCVeN6LtQ_OEUEb1FiC5dobZBEJKNGU9U,5814
31
31
  astreum/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
32
  astreum/utils/bytes_format.py,sha256=X4tG5GGPweNCE54bHYkLFiuLTbmpy5upO_s1Cef-MGA,2711
33
- astreum-0.1.12.dist-info/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
34
- astreum-0.1.12.dist-info/METADATA,sha256=LnGyI9YKLRNRWLolnyuHDgg0MKPSyLSwJho4FCTg1to,2942
35
- astreum-0.1.12.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
36
- astreum-0.1.12.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
37
- astreum-0.1.12.dist-info/RECORD,,
33
+ astreum-0.1.14.dist-info/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
34
+ astreum-0.1.14.dist-info/METADATA,sha256=W3SxJCJeN-OxSs_rGNuG1FrqQunyOFYBOlaCZkjlEyk,3261
35
+ astreum-0.1.14.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
36
+ astreum-0.1.14.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
37
+ astreum-0.1.14.dist-info/RECORD,,