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 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
- self.config = config
17
- self.relay = Relay(config)
18
- # Get the node_id from relay instead of generating our own
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
- self.storage = Storage(config)
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 receipt of a transaction.
400
- Accept if validation route is present and counter is valid relative to the latest block in our chain.
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
- # Check if we're in the validation route
404
- # This is now already checked by the relay's _handle_message method
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
- # Deserialize the transaction
409
- transaction = Transaction.from_bytes(body)
410
- if not transaction:
411
- return
412
-
413
- # Check if we're following this chain
414
- if not self.machine.is_following_chain(transaction.chain_id):
415
- return
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
- # Process the valid transaction
431
- self.machine.process_transaction(transaction)
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
- self.storage_path = Path(config.get('storage_path', 'storage'))
19
- self.storage_path.mkdir(parents=True, exist_ok=True)
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,
@@ -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 'private_key' in config:
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['private_key'])
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.10
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
- | `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=wvmMMACicwhJ-ipSLpR1rsEUXWuHaZ8fnsBCkUA_RA8,22387
24
- astreum/node/models.py,sha256=MScDCNEpR-SfaFsRSU567mVuZRhH1Lqc7gmlqb3j-nI,10980
25
- astreum/node/relay/__init__.py,sha256=gQNYDxllkjZqE6bvgPOYkKu3QHkjPFn11RvtP-BxtKI,13994
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.10.dist-info/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
34
- astreum-0.1.10.dist-info/METADATA,sha256=50hGAAYObIlHpnh2ikWSAALUtOIwmw9O_U6c1qLCre8,2942
35
- astreum-0.1.10.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
36
- astreum-0.1.10.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
37
- astreum-0.1.10.dist-info/RECORD,,
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,,