astreum 0.2.28__py3-none-any.whl → 0.2.29__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.py CHANGED
@@ -9,6 +9,9 @@ import uuid
9
9
 
10
10
  from astreum.lispeum.environment import Env
11
11
  from astreum.lispeum.expression import Expr
12
+ from astreum.relay.peer import Peer
13
+ from astreum.relay.route import Route
14
+ from astreum.relay.setup import load_ed25519, load_x25519, make_routes, setup_outgoing, setup_udp
12
15
  from astreum.storage.object import ObjectRequest, ObjectRequestType, ObjectResponse, ObjectResponseType
13
16
  from astreum.storage.setup import storage_setup
14
17
 
@@ -21,39 +24,6 @@ import blake3
21
24
  import struct
22
25
  from .models.message import Message, MessageTopic
23
26
 
24
-
25
-
26
- class Peer:
27
- shared_key: bytes
28
- timestamp: datetime
29
- def __init__(self, my_sec_key: X25519PrivateKey, peer_pub_key: X25519PublicKey):
30
- self.shared_key = my_sec_key.exchange(peer_pub_key)
31
- self.timestamp = datetime.now(timezone.utc)
32
-
33
- class Route:
34
- def __init__(self, relay_public_key: X25519PublicKey, bucket_size: int = 16):
35
- self.relay_public_key_bytes = relay_public_key.public_bytes(encoding=Encoding.Raw, format=PublicFormat.Raw)
36
- self.bucket_size = bucket_size
37
- self.buckets: Dict[int, List[X25519PublicKey]] = {
38
- i: [] for i in range(len(self.relay_public_key_bytes) * 8)
39
- }
40
- self.peers = {}
41
-
42
- @staticmethod
43
- def _matching_leading_bits(a: bytes, b: bytes) -> int:
44
- for byte_index, (ba, bb) in enumerate(zip(a, b)):
45
- diff = ba ^ bb
46
- if diff:
47
- return byte_index * 8 + (8 - diff.bit_length())
48
- return len(a) * 8
49
-
50
- def add_peer(self, peer_public_key: X25519PublicKey):
51
- peer_public_key_bytes = peer_public_key.public_bytes(encoding=Encoding.Raw, format=PublicFormat.Raw)
52
- bucket_idx = self._matching_leading_bits(self.relay_public_key_bytes, peer_public_key_bytes)
53
- if len(self.buckets[bucket_idx]) < self.bucket_size:
54
- self.buckets[bucket_idx].append(peer_public_key)
55
-
56
-
57
27
  def encode_ip_address(host: str, port: int) -> bytes:
58
28
  ip_bytes = socket.inet_pton(socket.AF_INET6 if ':' in host else socket.AF_INET, host)
59
29
  port_bytes = struct.pack("!H", port)
@@ -95,71 +65,45 @@ class Node:
95
65
  pass
96
66
 
97
67
  def _relay_setup(self, config: dict):
98
- self.use_ipv6 = config.get('use_ipv6', False)
99
- incoming_port = config.get('incoming_port', 7373)
100
-
101
- if 'relay_secret_key' in config:
102
- try:
103
- private_key_bytes = bytes.fromhex(config['relay_secret_key'])
104
- self.relay_secret_key = ed25519.Ed25519PrivateKey.from_private_bytes(private_key_bytes)
105
- except Exception as e:
106
- raise Exception(f"Error loading relay secret key provided: {e}")
107
- else:
108
- self.relay_secret_key = ed25519.Ed25519PrivateKey.generate()
109
-
110
- self.relay_public_key = self.relay_secret_key.public_key()
111
-
112
- if 'validation_secret_key' in config:
113
- try:
114
- private_key_bytes = bytes.fromhex(config['validation_secret_key'])
115
- self.validation_secret_key = x25519.X25519PrivateKey.from_private_bytes(private_key_bytes)
116
- except Exception as e:
117
- raise Exception(f"Error loading validation secret key provided: {e}")
118
-
119
- # setup peer route and validation route
120
- self.peer_route = Route(self.relay_public_key)
121
- if self.validation_secret_key:
122
- self.validation_route = Route(self.relay_public_key)
123
-
124
- # Choose address family based on IPv4 or IPv6
125
- family = socket.AF_INET6 if self.use_ipv6 else socket.AF_INET
126
-
127
- self.incoming_socket = socket.socket(family, socket.SOCK_DGRAM)
128
- if self.use_ipv6:
129
- self.incoming_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
130
- bind_address = "::" if self.use_ipv6 else "0.0.0.0"
131
- self.incoming_socket.bind((bind_address, incoming_port or 0))
132
- self.incoming_port = self.incoming_socket.getsockname()[1]
133
- self.incoming_queue = Queue()
134
-
135
- self.incoming_populate_thread = threading.Thread(target=self._relay_incoming_queue_populating)
136
- self.incoming_populate_thread.daemon = True
137
- self.incoming_populate_thread.start()
138
-
139
- self.incoming_process_thread = threading.Thread(target=self._relay_incoming_queue_processing)
140
- self.incoming_process_thread.daemon = True
141
- self.incoming_process_thread.start()
142
-
143
- # outgoing thread
144
- self.outgoing_socket = socket.socket(family, socket.SOCK_DGRAM)
145
- self.outgoing_queue = Queue()
146
- self.outgoing_thread = threading.Thread(target=self._relay_outgoing_queue_processor)
147
- self.outgoing_thread.daemon = True
148
- self.outgoing_thread.start()
149
-
68
+ self.use_ipv6 = config.get('use_ipv6', False)
69
+
70
+ # key loading
71
+ self.relay_secret_key = load_x25519(config.get('relay_secret_key'))
72
+ self.validation_secret_key = load_ed25519(config.get('validation_secret_key'))
73
+
74
+ # derive pubs + routes
75
+ self.relay_public_key = self.relay_secret_key.public_key()
76
+ self.peer_route, self.validation_route = make_routes(
77
+ self.relay_public_key,
78
+ self.validation_secret_key
79
+ )
80
+
81
+ # sockets + queues + threads
82
+ (self.incoming_socket,
83
+ self.incoming_port,
84
+ self.incoming_queue,
85
+ self.incoming_populate_thread,
86
+ self.incoming_process_thread
87
+ ) = setup_udp(config.get('incoming_port', 7373), self.use_ipv6)
88
+
89
+ (self.outgoing_socket,
90
+ self.outgoing_queue,
91
+ self.outgoing_thread
92
+ ) = setup_outgoing(self.use_ipv6)
93
+
94
+ # other workers & maps
150
95
  self.object_request_queue = Queue()
151
-
152
- self.peer_manager_thread = threading.Thread(target=self._relay_peer_manager)
153
- self.peer_manager_thread.daemon = True
96
+ self.peer_manager_thread = threading.Thread(
97
+ target=self._relay_peer_manager,
98
+ daemon=True
99
+ )
154
100
  self.peer_manager_thread.start()
155
101
 
156
- self.peers = Dict[X25519PublicKey, Peer]
157
- self.addresses = Dict[Tuple[str, int], X25519PublicKey]
158
-
159
- if 'bootstrap' in config:
160
- for addr in config['bootstrap']:
161
- self._send_ping(addr)
102
+ self.peers, self.addresses = {}, {} # peers: Dict[X25519PublicKey,Peer], addresses: Dict[(str,int),X25519PublicKey]
162
103
 
104
+ # bootstrap pings
105
+ for addr in config.get('bootstrap', []):
106
+ self._send_ping(addr)
163
107
 
164
108
  def _local_object_get(self, data_hash: bytes) -> Optional[bytes]:
165
109
  if self.memory_storage is not None:
@@ -530,137 +474,10 @@ class Node:
530
474
  return result
531
475
 
532
476
  # # List
533
- # elif first.value == "list.new":
534
- # return Expr.ListExpr([self.evaluate_expression(arg, env) for arg in expr.elements[1:]])
535
-
536
- # elif first.value == "list.get":
537
- # args = expr.elements[1:]
538
- # if len(args) != 2:
539
- # return Expr.Error(
540
- # category="SyntaxError",
541
- # message="list.get expects exactly two arguments: a list and an index"
542
- # )
543
- # list_obj = self.evaluate_expression(args[0], env)
544
- # index = self.evaluate_expression(args[1], env)
545
- # return handle_list_get(self, list_obj, index, env)
546
-
547
- # elif first.value == "list.insert":
548
- # args = expr.elements[1:]
549
- # if len(args) != 3:
550
- # return Expr.ListExpr([
551
- # Expr.ListExpr([]),
552
- # Expr.String("list.insert expects exactly three arguments: a list, an index, and a value")
553
- # ])
554
-
555
- # return handle_list_insert(
556
- # list=self.evaluate_expression(args[0], env),
557
- # index=self.evaluate_expression(args[1], env),
558
- # value=self.evaluate_expression(args[2], env),
559
- # )
560
-
561
- # elif first.value == "list.remove":
562
- # args = expr.elements[1:]
563
- # if len(args) != 2:
564
- # return Expr.ListExpr([
565
- # Expr.ListExpr([]),
566
- # Expr.String("list.remove expects exactly two arguments: a list and an index")
567
- # ])
568
-
569
- # return handle_list_remove(
570
- # list=self.evaluate_expression(args[0], env),
571
- # index=self.evaluate_expression(args[1], env),
572
- # )
573
-
574
- # elif first.value == "list.length":
575
- # args = expr.elements[1:]
576
- # if len(args) != 1:
577
- # return Expr.ListExpr([
578
- # Expr.ListExpr([]),
579
- # Expr.String("list.length expects exactly one argument: a list")
580
- # ])
581
-
582
- # list_obj = self.evaluate_expression(args[0], env)
583
- # if not isinstance(list_obj, Expr.ListExpr):
584
- # return Expr.ListExpr([
585
- # Expr.ListExpr([]),
586
- # Expr.String("Argument must be a list")
587
- # ])
588
-
589
- # return Expr.ListExpr([
590
- # Expr.Integer(len(list_obj.elements)),
591
- # Expr.ListExpr([])
592
- # ])
593
-
594
- # elif first.value == "list.fold":
595
- # if len(args) != 3:
596
- # return Expr.ListExpr([
597
- # Expr.ListExpr([]),
598
- # Expr.String("list.fold expects exactly three arguments: a list, an initial value, and a function")
599
- # ])
600
-
601
- # return handle_list_fold(
602
- # machine=self,
603
- # list=self.evaluate_expression(args[0], env),
604
- # initial=self.evaluate_expression(args[1], env),
605
- # func=self.evaluate_expression(args[2], env),
606
- # env=env,
607
- # )
608
-
609
- # elif first.value == "list.map":
610
- # if len(args) != 2:
611
- # return Expr.ListExpr([
612
- # Expr.ListExpr([]),
613
- # Expr.String("list.map expects exactly two arguments: a list and a function")
614
- # ])
615
-
616
- # return handle_list_map(
617
- # machine=self,
618
- # list=self.evaluate_expression(args[0], env),
619
- # func=self.evaluate_expression(args[1], env),
620
- # env=env,
621
- # )
622
-
623
- # elif first.value == "list.position":
624
- # if len(args) != 2:
625
- # return Expr.ListExpr([
626
- # Expr.ListExpr([]),
627
- # Expr.String("list.position expects exactly two arguments: a list and a function")
628
- # ])
629
-
630
- # return handle_list_position(
631
- # machine=self,
632
- # list=self.evaluate_expression(args[0], env),
633
- # predicate=self.evaluate_expression(args[1], env),
634
- # env=env,
635
- # )
636
-
637
- # elif first.value == "list.any":
638
- # if len(args) != 2:
639
- # return Expr.ListExpr([
640
- # Expr.ListExpr([]),
641
- # Expr.String("list.any expects exactly two arguments: a list and a function")
642
- # ])
643
-
644
- # return handle_list_any(
645
- # machine=self,
646
- # list=self.evaluate_expression(args[0], env),
647
- # predicate=self.evaluate_expression(args[1], env),
648
- # env=env,
649
- # )
650
-
651
- # elif first.value == "list.all":
652
- # if len(args) != 2:
653
- # return Expr.ListExpr([
654
- # Expr.ListExpr([]),
655
- # Expr.String("list.all expects exactly two arguments: a list and a function")
656
- # ])
657
-
658
- # return handle_list_all(
659
- # machine=self,
660
- # list=self.evaluate_expression(args[0], env),
661
- # predicate=self.evaluate_expression(args[1], env),
662
- # env=env,
663
- # )
477
+ elif first.value == "list.each":
478
+ internal_function = expr.elements[1]
479
+
480
+
664
481
 
665
482
  # Integer arithmetic primitives
666
483
  elif first.value == "+":
@@ -747,6 +564,19 @@ class Node:
747
564
 
748
565
  return Expr.Boolean(res)
749
566
 
567
+ if isinstance(first, Expr.Function):
568
+ arg_exprs = expr.elements[1:]
569
+ if len(arg_exprs) != len(first.params):
570
+ return Expr.Error(f"arity mismatch: expected {len(first.params)}, got {len(arg_exprs)}", origin=expr)
571
+
572
+ call_env = self.machine_create_environment(parent_id=env_id)
573
+ for name, aexpr in zip(first.params, arg_exprs):
574
+ val = self.machine_expr_eval(env_id, aexpr)
575
+ if isinstance(val, Expr.Error): return val
576
+ self.machine_expr_put(call_env, name, val)
577
+
578
+ return self.machine_expr_eval(env_id=call_env, expr=first.body)
579
+
750
580
  else:
751
581
  evaluated_elements = [self.machine_expr_eval(env_id=env_id, expr=e) for e in expr.elements]
752
582
  return Expr.ListExpr(evaluated_elements)
File without changes
astreum/relay/peer.py ADDED
@@ -0,0 +1,9 @@
1
+ from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
2
+ from datetime import datetime, timezone
3
+
4
+ class Peer:
5
+ shared_key: bytes
6
+ timestamp: datetime
7
+ def __init__(self, my_sec_key: X25519PrivateKey, peer_pub_key: X25519PublicKey):
8
+ self.shared_key = my_sec_key.exchange(peer_pub_key)
9
+ self.timestamp = datetime.now(timezone.utc)
astreum/relay/route.py ADDED
@@ -0,0 +1,25 @@
1
+ from typing import Dict, List
2
+ from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
3
+
4
+ class Route:
5
+ def __init__(self, relay_public_key: X25519PublicKey, bucket_size: int = 16):
6
+ self.relay_public_key_bytes = relay_public_key.public_bytes(encoding=Encoding.Raw, format=PublicFormat.Raw)
7
+ self.bucket_size = bucket_size
8
+ self.buckets: Dict[int, List[X25519PublicKey]] = {
9
+ i: [] for i in range(len(self.relay_public_key_bytes) * 8)
10
+ }
11
+ self.peers = {}
12
+
13
+ @staticmethod
14
+ def _matching_leading_bits(a: bytes, b: bytes) -> int:
15
+ for byte_index, (ba, bb) in enumerate(zip(a, b)):
16
+ diff = ba ^ bb
17
+ if diff:
18
+ return byte_index * 8 + (8 - diff.bit_length())
19
+ return len(a) * 8
20
+
21
+ def add_peer(self, peer_public_key: X25519PublicKey):
22
+ peer_public_key_bytes = peer_public_key.public_bytes(encoding=Encoding.Raw, format=PublicFormat.Raw)
23
+ bucket_idx = self._matching_leading_bits(self.relay_public_key_bytes, peer_public_key_bytes)
24
+ if len(self.buckets[bucket_idx]) < self.bucket_size:
25
+ self.buckets[bucket_idx].append(peer_public_key)
astreum/relay/setup.py ADDED
@@ -0,0 +1,58 @@
1
+ import socket, threading
2
+ from queue import Queue
3
+ from typing import Tuple, Optional
4
+ from cryptography.hazmat.primitives.asymmetric import ed25519
5
+ from cryptography.hazmat.primitives.asymmetric.x25519 import (
6
+ X25519PrivateKey,
7
+ X25519PublicKey,
8
+ )
9
+ from yourproject.routes import Route
10
+
11
+ def load_x25519(hex_key: Optional[str]) -> X25519PrivateKey:
12
+ """DH key for relaying (always X25519)."""
13
+ return
14
+
15
+ def load_ed25519(hex_key: Optional[str]) -> Optional[ed25519.Ed25519PrivateKey]:
16
+ """Signing key for validation (Ed25519), or None if absent."""
17
+ return ed25519.Ed25519PrivateKey.from_private_bytes(bytes.fromhex(hex_key)) \
18
+ if hex_key else None
19
+
20
+ def make_routes(
21
+ relay_pk: X25519PublicKey,
22
+ val_sk: Optional[ed25519.Ed25519PrivateKey]
23
+ ) -> Tuple[Route, Optional[Route]]:
24
+ """Peer route (DH pubkey) + optional validation route (ed pubkey)."""
25
+ peer_rt = Route(relay_pk)
26
+ val_rt = Route(val_sk.public_key()) if val_sk else None
27
+ return peer_rt, val_rt
28
+
29
+ def setup_udp(
30
+ bind_port: int,
31
+ use_ipv6: bool
32
+ ) -> Tuple[socket.socket, int, Queue, threading.Thread, threading.Thread]:
33
+ fam = socket.AF_INET6 if use_ipv6 else socket.AF_INET
34
+ sock = socket.socket(fam, socket.SOCK_DGRAM)
35
+ if use_ipv6:
36
+ sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
37
+ sock.bind(("::" if use_ipv6 else "0.0.0.0", bind_port or 0))
38
+ port = sock.getsockname()[1]
39
+
40
+ q = Queue()
41
+ pop = threading.Thread(target=lambda: None, daemon=True)
42
+ proc = threading.Thread(target=lambda: None, daemon=True)
43
+ pop.start(); proc.start()
44
+ return sock, port, q, pop, proc
45
+
46
+ def setup_outgoing(
47
+ use_ipv6: bool
48
+ ) -> Tuple[socket.socket, Queue, threading.Thread]:
49
+ fam = socket.AF_INET6 if use_ipv6 else socket.AF_INET
50
+ sock = socket.socket(fam, socket.SOCK_DGRAM)
51
+ q = Queue()
52
+ thr = threading.Thread(target=lambda: None, daemon=True)
53
+ thr.start()
54
+ return sock, q, thr
55
+
56
+ def make_maps():
57
+ """Empty lookup maps: peers and addresses."""
58
+ return
astreum/storage/setup.py CHANGED
@@ -1,8 +1,7 @@
1
1
  from pathlib import Path
2
2
  from typing import Optional, Dict, Tuple, Any
3
3
 
4
- def storage_setup(config: dict
5
- ) -> Tuple[Optional[Path], Dict[bytes, Any], int, Dict[bytes, bytes]]:
4
+ def storage_setup(config: dict) -> Tuple[Optional[Path], Dict[bytes, Any], int, Dict[bytes, bytes]]:
6
5
  storage_path_str = config.get('storage_path')
7
6
  if storage_path_str is None:
8
7
  storage_path, memory_storage = None, {}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: astreum
3
- Version: 0.2.28
3
+ Version: 0.2.29
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
  astreum/__init__.py,sha256=y2Ok3EY_FstcmlVASr80lGR_0w-dH-SXDCCQFmL6uwA,28
2
2
  astreum/format.py,sha256=X4tG5GGPweNCE54bHYkLFiuLTbmpy5upO_s1Cef-MGA,2711
3
- astreum/node.py,sha256=L2YpIxyhf0f-y85_vBGBaGiItgFGzLgXYhyabDetaKM,37116
3
+ astreum/node.py,sha256=OhiApRqQcPc6-CWbk2NeKdOiXQapy0mbl3uLK_xjmYU,28971
4
4
  astreum/crypto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  astreum/crypto/ed25519.py,sha256=FRnvlN0kZlxn4j-sJKl-C9tqiz_0z4LZyXLj3KIj1TQ,1760
6
6
  astreum/crypto/quadratic_form.py,sha256=pJgbORey2NTWbQNhdyvrjy_6yjORudQ67jBz2ScHptg,4037
@@ -19,11 +19,15 @@ astreum/models/merkle.py,sha256=lvWJa9nmrBL0n_2h_uNqpB_9a5s5Hn1FceRLx0IZIVQ,6778
19
19
  astreum/models/message.py,sha256=vv8yx-ndVYjCmPM4gXRVMToCTlKY_mflPu0uKsb9iiE,2117
20
20
  astreum/models/patricia.py,sha256=ohmXrcaz7Ae561tyC4u4iPOkQPkKr8N0IWJek4upFIg,13392
21
21
  astreum/models/transaction.py,sha256=MkLL5YX18kIf9-O4LBaZ4eWjkXDAaYIrDcDehbDZoqg,3038
22
+ astreum/relay/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
+ astreum/relay/peer.py,sha256=94rNkHfsvYfq-ijLDR9QQkEJZ1meMr17HsaNYDBB8kg,398
24
+ astreum/relay/route.py,sha256=enWT_1260LJq-L-zK-jtacQ8LbZGquNO9yj-9IglSXE,1232
25
+ astreum/relay/setup.py,sha256=ynvGaJdlDtw_f5LLiow2Wo7IRzUjvgk8eSr1Sv4_zTg,2090
22
26
  astreum/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
27
  astreum/storage/object.py,sha256=knFlvw_tpcC4twSu1DGNpHX31wlANN8E5dgEqIfU--Q,2041
24
- astreum/storage/setup.py,sha256=EhMKr4wnBaESbzF8AlE2R-8YPIzxWx3afSv5BL8X6Lo,632
25
- astreum-0.2.28.dist-info/licenses/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
26
- astreum-0.2.28.dist-info/METADATA,sha256=dB1O5if24FmM7erOlVfX-WAa923EO8KgzCl4GpaHS7o,5478
27
- astreum-0.2.28.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
28
- astreum-0.2.28.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
29
- astreum-0.2.28.dist-info/RECORD,,
28
+ astreum/storage/setup.py,sha256=1-9ztEFI_BvRDvAA0lAn4mFya8iq65THTArlj--M3Hg,626
29
+ astreum-0.2.29.dist-info/licenses/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
30
+ astreum-0.2.29.dist-info/METADATA,sha256=JSIc8N98KW4Wmm89uMMFxyJKiEFxIU-zGRjIXf9JL24,5478
31
+ astreum-0.2.29.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
32
+ astreum-0.2.29.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
33
+ astreum-0.2.29.dist-info/RECORD,,