astreum 0.1.10__py3-none-any.whl → 0.1.13__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.
- astreum/node/__init__.py +82 -40
- astreum/node/models.py +30 -5
- astreum/node/relay/__init__.py +4 -22
- {astreum-0.1.10.dist-info → astreum-0.1.13.dist-info}/METADATA +6 -6
- {astreum-0.1.10.dist-info → astreum-0.1.13.dist-info}/RECORD +8 -8
- {astreum-0.1.10.dist-info → astreum-0.1.13.dist-info}/LICENSE +0 -0
- {astreum-0.1.10.dist-info → astreum-0.1.13.dist-info}/WHEEL +0 -0
- {astreum-0.1.10.dist-info → astreum-0.1.13.dist-info}/top_level.txt +0 -0
astreum/node/__init__.py
CHANGED
|
@@ -3,6 +3,7 @@ import hashlib
|
|
|
3
3
|
import time
|
|
4
4
|
from typing import Tuple, Optional
|
|
5
5
|
import json
|
|
6
|
+
from cryptography.hazmat.primitives.asymmetric import ed25519
|
|
6
7
|
|
|
7
8
|
from .relay import Relay, Topic
|
|
8
9
|
from .relay.peer import Peer
|
|
@@ -13,16 +14,41 @@ from astreum.lispeum.storage import store_expr, get_expr_from_storage
|
|
|
13
14
|
|
|
14
15
|
class Node:
|
|
15
16
|
def __init__(self, config: dict):
|
|
16
|
-
|
|
17
|
-
self.
|
|
18
|
-
|
|
17
|
+
# Ensure config is a dictionary, but allow it to be None
|
|
18
|
+
self.config = config if config is not None else {}
|
|
19
|
+
|
|
20
|
+
# Handle validation key if provided
|
|
21
|
+
self.validation_private_key = None
|
|
22
|
+
self.validation_public_key = None
|
|
23
|
+
self.is_validator = False
|
|
24
|
+
|
|
25
|
+
# Extract validation private key from config
|
|
26
|
+
if 'validation_private_key' in self.config:
|
|
27
|
+
try:
|
|
28
|
+
key_bytes = bytes.fromhex(self.config['validation_private_key'])
|
|
29
|
+
self.validation_private_key = ed25519.Ed25519PrivateKey.from_private_bytes(key_bytes)
|
|
30
|
+
self.validation_public_key = self.validation_private_key.public_key()
|
|
31
|
+
self.is_validator = True
|
|
32
|
+
|
|
33
|
+
# Set validation_route to True in config so relay will join validation route
|
|
34
|
+
self.config['validation_route'] = True
|
|
35
|
+
print(f"Node is configured as a validator with validation key")
|
|
36
|
+
except Exception as e:
|
|
37
|
+
print(f"Error loading validation private key: {e}")
|
|
38
|
+
|
|
39
|
+
# Initialize relay with our config
|
|
40
|
+
self.relay = Relay(self.config)
|
|
41
|
+
|
|
42
|
+
# Get the node_id from relay
|
|
19
43
|
self.node_id = self.relay.node_id
|
|
20
|
-
|
|
44
|
+
|
|
45
|
+
# Initialize storage
|
|
46
|
+
self.storage = Storage(self.config)
|
|
21
47
|
self.storage.node = self # Set the storage node reference to self
|
|
22
48
|
|
|
23
49
|
# Latest block of the chain this node is following
|
|
24
50
|
self.latest_block = None
|
|
25
|
-
self.followed_chain_id = config.get('followed_chain_id', None)
|
|
51
|
+
self.followed_chain_id = self.config.get('followed_chain_id', None)
|
|
26
52
|
|
|
27
53
|
# Initialize machine
|
|
28
54
|
self.machine = AstreumMachine(node=self)
|
|
@@ -396,48 +422,64 @@ class Node:
|
|
|
396
422
|
|
|
397
423
|
def _handle_transaction(self, body: bytes, addr: Tuple[str, int], envelope):
|
|
398
424
|
"""
|
|
399
|
-
Handle
|
|
400
|
-
|
|
425
|
+
Handle incoming transaction messages.
|
|
426
|
+
|
|
427
|
+
This method is called when we receive a transaction from the network.
|
|
428
|
+
Transactions should only be processed by validator nodes.
|
|
429
|
+
|
|
430
|
+
Args:
|
|
431
|
+
body: Transaction data
|
|
432
|
+
addr: Source address
|
|
433
|
+
envelope: Full message envelope
|
|
401
434
|
"""
|
|
435
|
+
# Ignore if we're not a validator (don't have a validation key)
|
|
436
|
+
if not self.is_validator or not self.relay.is_in_validation_route():
|
|
437
|
+
print("Ignoring transaction as we're not a validator")
|
|
438
|
+
return
|
|
439
|
+
|
|
402
440
|
try:
|
|
403
|
-
#
|
|
404
|
-
|
|
405
|
-
if not self.relay.is_in_validation_route():
|
|
406
|
-
return
|
|
441
|
+
# Parse transaction data
|
|
442
|
+
tx_data = json.loads(body.decode('utf-8'))
|
|
407
443
|
|
|
408
|
-
#
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
# Verify transaction has a valid validation route
|
|
418
|
-
if not transaction.has_valid_route():
|
|
419
|
-
return
|
|
420
|
-
|
|
421
|
-
# Get latest block from this chain
|
|
422
|
-
latest_block = self.machine.get_latest_block(transaction.chain_id)
|
|
423
|
-
if not latest_block:
|
|
424
|
-
return
|
|
425
|
-
|
|
426
|
-
# Verify transaction counter is valid relative to the latest block
|
|
427
|
-
if not transaction.is_counter_valid(latest_block):
|
|
428
|
-
return
|
|
444
|
+
# Store the transaction in our local storage
|
|
445
|
+
tx_hash = bytes.fromhex(tx_data.get('hash'))
|
|
446
|
+
tx_raw = bytes.fromhex(tx_data.get('data'))
|
|
447
|
+
|
|
448
|
+
# Create transaction entry in storage
|
|
449
|
+
if not self.storage.contains(tx_hash):
|
|
450
|
+
self.storage.put(tx_hash, tx_raw)
|
|
451
|
+
print(f"Stored transaction {tx_hash.hex()}")
|
|
429
452
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
# Relay to other peers in the validation route
|
|
434
|
-
validation_peers = self.relay.get_route_peers(1) # 1 = validation route
|
|
435
|
-
for peer in validation_peers:
|
|
436
|
-
if peer.address != addr: # Don't send back to originator
|
|
437
|
-
self.relay.send_message(body, Topic.TRANSACTION, peer.address)
|
|
453
|
+
# Process the transaction as a validator
|
|
454
|
+
self._process_transaction_as_validator(tx_hash, tx_raw)
|
|
455
|
+
|
|
438
456
|
except Exception as e:
|
|
439
457
|
print(f"Error handling transaction: {e}")
|
|
440
458
|
|
|
459
|
+
def _process_transaction_as_validator(self, tx_hash: bytes, tx_raw: bytes):
|
|
460
|
+
"""
|
|
461
|
+
Process a transaction as a validator node.
|
|
462
|
+
|
|
463
|
+
This method is called when we receive a transaction and we're a validator.
|
|
464
|
+
It verifies the transaction and may include it in a future block.
|
|
465
|
+
|
|
466
|
+
Args:
|
|
467
|
+
tx_hash: Transaction hash
|
|
468
|
+
tx_raw: Raw transaction data
|
|
469
|
+
"""
|
|
470
|
+
try:
|
|
471
|
+
print(f"Processing transaction {tx_hash.hex()} as validator")
|
|
472
|
+
# Here we would verify the transaction and potentially queue it
|
|
473
|
+
# for inclusion in the next block we create
|
|
474
|
+
|
|
475
|
+
# For now, just log that we processed it
|
|
476
|
+
print(f"Verified transaction {tx_hash.hex()}")
|
|
477
|
+
|
|
478
|
+
# TODO: Implement transaction validation and queueing for block creation
|
|
479
|
+
|
|
480
|
+
except Exception as e:
|
|
481
|
+
print(f"Error processing transaction as validator: {e}")
|
|
482
|
+
|
|
441
483
|
def _initialize_latest_block(self):
|
|
442
484
|
"""Initialize latest block from storage if available."""
|
|
443
485
|
# Implementation would load the latest block from storage
|
astreum/node/models.py
CHANGED
|
@@ -15,8 +15,21 @@ class Storage:
|
|
|
15
15
|
def __init__(self, config: dict):
|
|
16
16
|
self.max_space = config.get('max_storage_space', 1024 * 1024 * 1024) # Default 1GB
|
|
17
17
|
self.current_space = 0
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
|
|
19
|
+
# Check if storage_path is provided in config
|
|
20
|
+
storage_path = config.get('storage_path')
|
|
21
|
+
self.use_memory_storage = storage_path is None
|
|
22
|
+
|
|
23
|
+
# Initialize in-memory storage if no path provided
|
|
24
|
+
self.memory_storage = {} if self.use_memory_storage else None
|
|
25
|
+
|
|
26
|
+
# Only create storage path if not using memory storage
|
|
27
|
+
if not self.use_memory_storage:
|
|
28
|
+
self.storage_path = Path(storage_path)
|
|
29
|
+
self.storage_path.mkdir(parents=True, exist_ok=True)
|
|
30
|
+
# Calculate current space usage
|
|
31
|
+
self.current_space = sum(f.stat().st_size for f in self.storage_path.glob('*') if f.is_file())
|
|
32
|
+
|
|
20
33
|
self.max_object_recursion = config.get('max_object_recursion', 50)
|
|
21
34
|
self.network_request_timeout = config.get('network_request_timeout', 5.0) # Default 5 second timeout
|
|
22
35
|
self.node = None # Will be set by the Node after initialization
|
|
@@ -24,9 +37,6 @@ class Storage:
|
|
|
24
37
|
# In-progress requests tracking
|
|
25
38
|
self.pending_requests = {} # hash -> (start_time, event)
|
|
26
39
|
self.request_lock = threading.Lock()
|
|
27
|
-
|
|
28
|
-
# Calculate current space usage
|
|
29
|
-
self.current_space = sum(f.stat().st_size for f in self.storage_path.glob('*') if f.is_file())
|
|
30
40
|
|
|
31
41
|
def put(self, data_hash: bytes, data: bytes) -> bool:
|
|
32
42
|
"""Store data with its hash. Returns True if successful, False if space limit exceeded."""
|
|
@@ -34,6 +44,14 @@ class Storage:
|
|
|
34
44
|
if self.current_space + data_size > self.max_space:
|
|
35
45
|
return False
|
|
36
46
|
|
|
47
|
+
# If using memory storage, store in dictionary
|
|
48
|
+
if self.use_memory_storage:
|
|
49
|
+
if data_hash not in self.memory_storage:
|
|
50
|
+
self.memory_storage[data_hash] = data
|
|
51
|
+
self.current_space += data_size
|
|
52
|
+
return True
|
|
53
|
+
|
|
54
|
+
# Otherwise use file storage
|
|
37
55
|
file_path = self.storage_path / data_hash.hex()
|
|
38
56
|
|
|
39
57
|
# Don't store if already exists
|
|
@@ -54,6 +72,11 @@ class Storage:
|
|
|
54
72
|
|
|
55
73
|
def _local_get(self, data_hash: bytes) -> Optional[bytes]:
|
|
56
74
|
"""Get data from local storage only, no network requests."""
|
|
75
|
+
# If using memory storage, get from dictionary
|
|
76
|
+
if self.use_memory_storage:
|
|
77
|
+
return self.memory_storage.get(data_hash)
|
|
78
|
+
|
|
79
|
+
# Otherwise use file storage
|
|
57
80
|
file_path = self.storage_path / data_hash.hex()
|
|
58
81
|
if file_path.exists():
|
|
59
82
|
return file_path.read_bytes()
|
|
@@ -144,6 +167,8 @@ class Storage:
|
|
|
144
167
|
|
|
145
168
|
def contains(self, data_hash: bytes) -> bool:
|
|
146
169
|
"""Check if data exists in storage."""
|
|
170
|
+
if self.use_memory_storage:
|
|
171
|
+
return data_hash in self.memory_storage
|
|
147
172
|
return (self.storage_path / data_hash.hex()).exists()
|
|
148
173
|
|
|
149
174
|
def get_recursive(self, root_hash: bytes, max_depth: Optional[int] = None,
|
astreum/node/relay/__init__.py
CHANGED
|
@@ -24,10 +24,10 @@ class Relay:
|
|
|
24
24
|
self.num_workers = config.get('num_workers', 4)
|
|
25
25
|
|
|
26
26
|
# Generate Ed25519 keypair for this node
|
|
27
|
-
if '
|
|
27
|
+
if 'relay_private_key' in config:
|
|
28
28
|
# Load existing private key if provided
|
|
29
29
|
try:
|
|
30
|
-
private_key_bytes = bytes.fromhex(config['
|
|
30
|
+
private_key_bytes = bytes.fromhex(config['relay_private_key'])
|
|
31
31
|
self.private_key = ed25519.Ed25519PrivateKey.from_private_bytes(private_key_bytes)
|
|
32
32
|
except Exception as e:
|
|
33
33
|
print(f"Error loading private key: {e}, generating new one")
|
|
@@ -49,6 +49,8 @@ class Relay:
|
|
|
49
49
|
# Peer route is always enabled
|
|
50
50
|
self.routes.append(0) # Peer route
|
|
51
51
|
|
|
52
|
+
# Check if the node should join validation route
|
|
53
|
+
# This is now controlled by the parent Node class based on validation_private_key
|
|
52
54
|
if config.get('validation_route', False):
|
|
53
55
|
self.routes.append(1) # Validation route
|
|
54
56
|
|
|
@@ -101,26 +103,6 @@ class Relay:
|
|
|
101
103
|
"""Check if this node is part of the validation route."""
|
|
102
104
|
return 1 in self.routes
|
|
103
105
|
|
|
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)
|
|
108
|
-
|
|
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)
|
|
113
|
-
|
|
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)
|
|
118
|
-
|
|
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)
|
|
123
|
-
|
|
124
106
|
def add_peer_to_route(self, peer: Peer, route_types: List[int]):
|
|
125
107
|
"""
|
|
126
108
|
Add a peer to specified routes.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: astreum
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.13
|
|
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=
|
|
24
|
-
astreum/node/models.py,sha256=
|
|
25
|
-
astreum/node/relay/__init__.py,sha256=
|
|
23
|
+
astreum/node/__init__.py,sha256=VlyhTLOjqy5VJlcLiTfTqgcsWuN3mudv_WtM5ixrojc,24121
|
|
24
|
+
astreum/node/models.py,sha256=9Uf2_u55uxWG0ujjySvFJUO5Ub-EzlMnnMJWcgJHjHk,11980
|
|
25
|
+
astreum/node/relay/__init__.py,sha256=k-_lEenUvLnBriSqjsqpnbwua1j6ZUghqkGD_Jc9ZYM,13493
|
|
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.13.dist-info/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
|
|
34
|
+
astreum-0.1.13.dist-info/METADATA,sha256=0wxUc4qtd3ipFd3-IwFjDcsWTBqAK8I-u3hrgBmgVX8,3261
|
|
35
|
+
astreum-0.1.13.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
|
|
36
|
+
astreum-0.1.13.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
|
|
37
|
+
astreum-0.1.13.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|