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 +214 -66
- astreum/node/relay/__init__.py +73 -49
- {astreum-0.1.12.dist-info → astreum-0.1.14.dist-info}/METADATA +6 -6
- {astreum-0.1.12.dist-info → astreum-0.1.14.dist-info}/RECORD +7 -7
- {astreum-0.1.12.dist-info → astreum-0.1.14.dist-info}/LICENSE +0 -0
- {astreum-0.1.12.dist-info → astreum-0.1.14.dist-info}/WHEEL +0 -0
- {astreum-0.1.12.dist-info → astreum-0.1.14.dist-info}/top_level.txt +0 -0
astreum/node/__init__.py
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import hashlib
|
|
3
3
|
import time
|
|
4
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
#
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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
|
|
401
|
-
|
|
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
|
-
#
|
|
405
|
-
|
|
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
|
-
#
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
-
|
|
432
|
-
|
|
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
|
|
590
|
+
Add a block to the candidate chains.
|
|
469
591
|
|
|
470
592
|
Args:
|
|
471
|
-
block:
|
|
593
|
+
block: Block to add
|
|
472
594
|
"""
|
|
473
595
|
chain_id = block.chain_id
|
|
474
596
|
|
|
475
|
-
#
|
|
597
|
+
# Check if we already have this chain as a candidate
|
|
476
598
|
if chain_id in self.candidate_chains:
|
|
477
|
-
|
|
478
|
-
|
|
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
|
-
|
|
491
|
-
|
|
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()
|
astreum/node/relay/__init__.py
CHANGED
|
@@ -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 '
|
|
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['
|
|
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
|
|
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
|
-
#
|
|
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
|
|
105
|
-
"""
|
|
106
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
for
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
219
|
-
if envelope.message.topic
|
|
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
|
-
|
|
224
|
-
|
|
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
|
-
|
|
228
|
-
|
|
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.
|
|
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
|
-
| `
|
|
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
|
-
"
|
|
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=
|
|
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=
|
|
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.
|
|
34
|
-
astreum-0.1.
|
|
35
|
-
astreum-0.1.
|
|
36
|
-
astreum-0.1.
|
|
37
|
-
astreum-0.1.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|