astreum 0.2.28__py3-none-any.whl → 0.2.30__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.py +237 -229
- astreum/relay/__init__.py +0 -0
- astreum/relay/peer.py +9 -0
- astreum/relay/route.py +25 -0
- astreum/relay/setup.py +58 -0
- astreum/storage/setup.py +1 -2
- {astreum-0.2.28.dist-info → astreum-0.2.30.dist-info}/METADATA +1 -1
- {astreum-0.2.28.dist-info → astreum-0.2.30.dist-info}/RECORD +11 -7
- {astreum-0.2.28.dist-info → astreum-0.2.30.dist-info}/WHEEL +0 -0
- {astreum-0.2.28.dist-info → astreum-0.2.30.dist-info}/licenses/LICENSE +0 -0
- {astreum-0.2.28.dist-info → astreum-0.2.30.dist-info}/top_level.txt +0 -0
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
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
self.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
#
|
|
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
|
-
|
|
153
|
-
|
|
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,
|
|
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:
|
|
@@ -519,7 +463,7 @@ class Node:
|
|
|
519
463
|
elif first.value == "def":
|
|
520
464
|
args = expr.elements[1:]
|
|
521
465
|
if len(args) != 2:
|
|
522
|
-
return Expr.Error(
|
|
466
|
+
return Expr.Error("def expects key value", origin=expr)
|
|
523
467
|
if not isinstance(args[0], Expr.Symbol):
|
|
524
468
|
return Expr.Error(message="first argument to 'def' must be a symbol", origin=args[0])
|
|
525
469
|
result = self.machine_expr_eval(env_id=env_id, expr=args[1])
|
|
@@ -529,140 +473,191 @@ class Node:
|
|
|
529
473
|
self.machine_expr_put(env_id=env_id, name=args[0].value, expr=result)
|
|
530
474
|
return result
|
|
531
475
|
|
|
532
|
-
|
|
533
|
-
#
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
#
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
#
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
#
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
476
|
+
## List: ints -> (1 2)
|
|
477
|
+
# push: (list.push 3 ints) -> (1 2 3) / (list.push 0 0 ints) -> (0 1 2)
|
|
478
|
+
elif first.value == "list.push":
|
|
479
|
+
args = expr.elements[1:]
|
|
480
|
+
if len(args) == 2:
|
|
481
|
+
val_expr, list_expr = args
|
|
482
|
+
idx = None
|
|
483
|
+
elif len(args) == 3:
|
|
484
|
+
idx_expr, val_expr, list_expr = args
|
|
485
|
+
idx = self.machine_expr_eval(env_id, idx_expr)
|
|
486
|
+
if isinstance(idx, Expr.Error): return idx
|
|
487
|
+
if not isinstance(idx, Expr.IntExpr):
|
|
488
|
+
return Expr.Error("index must be int", origin=idx_expr)
|
|
489
|
+
idx = idx.value
|
|
490
|
+
else:
|
|
491
|
+
return Expr.Error("list.push expects (value list) or (index value list)", origin=expr)
|
|
492
|
+
|
|
493
|
+
lst = self.machine_expr_eval(env_id, list_expr)
|
|
494
|
+
if isinstance(lst, Expr.Error): return lst
|
|
495
|
+
if not isinstance(lst, Expr.ListExpr):
|
|
496
|
+
return Expr.Error("last arg to list.push must be a list", origin=list_expr)
|
|
497
|
+
|
|
498
|
+
val = self.machine_expr_eval(env_id, val_expr)
|
|
499
|
+
if isinstance(val, Expr.Error): return val
|
|
500
|
+
|
|
501
|
+
elems = list(lst.elements)
|
|
502
|
+
if idx is None:
|
|
503
|
+
elems.append(val)
|
|
504
|
+
else:
|
|
505
|
+
if idx < 0 or idx > len(elems):
|
|
506
|
+
return Expr.Error("index out of range", origin=idx_expr)
|
|
507
|
+
elems.insert(idx, val)
|
|
508
|
+
return Expr.ListExpr(elems)
|
|
509
|
+
|
|
510
|
+
# pop: (list.pop 1 ints) -> 2
|
|
511
|
+
elif first.value == "list.pop":
|
|
512
|
+
if len(expr.elements) < 3:
|
|
513
|
+
return Expr.Error("list.pop expects index list", origin=expr)
|
|
514
|
+
|
|
515
|
+
idx_expr, list_expr = expr.elements[1], expr.elements[2]
|
|
516
|
+
idx = self.machine_expr_eval(env_id, idx_expr)
|
|
517
|
+
if isinstance(idx, Expr.Error): return idx
|
|
518
|
+
if not isinstance(idx, Expr.IntExpr):
|
|
519
|
+
return Expr.Error("index must be int", origin=idx_expr)
|
|
520
|
+
idx = idx.value
|
|
521
|
+
|
|
522
|
+
lst = self.machine_expr_eval(env_id, list_expr)
|
|
523
|
+
if isinstance(lst, Expr.Error): return lst
|
|
524
|
+
if not isinstance(lst, Expr.ListExpr):
|
|
525
|
+
return Expr.Error("second arg to list.pop must be a list", origin=list_expr)
|
|
526
|
+
|
|
527
|
+
elems = list(lst.elements)
|
|
528
|
+
if idx < 0 or idx >= len(elems):
|
|
529
|
+
return Expr.Error("index out of range", origin=idx_expr)
|
|
530
|
+
del elems[idx]
|
|
531
|
+
return Expr.ListExpr(elems)
|
|
532
|
+
|
|
533
|
+
# get: (list.get 1 ints) -> 2
|
|
534
|
+
elif first.value == "list.get":
|
|
535
|
+
if len(expr.elements) < 3:
|
|
536
|
+
return Expr.Error("list.get expects index list", origin=expr)
|
|
537
|
+
|
|
538
|
+
idx_expr, list_expr = expr.elements[1], expr.elements[2]
|
|
539
|
+
idx = self.machine_expr_eval(env_id, idx_expr)
|
|
540
|
+
if isinstance(idx, Expr.Error): return idx
|
|
541
|
+
if not isinstance(idx, Expr.IntExpr):
|
|
542
|
+
return Expr.Error("index must be int", origin=idx_expr)
|
|
543
|
+
idx = idx.value
|
|
544
|
+
|
|
545
|
+
lst = self.machine_expr_eval(env_id, list_expr)
|
|
546
|
+
if isinstance(lst, Expr.Error): return lst
|
|
547
|
+
if not isinstance(lst, Expr.ListExpr):
|
|
548
|
+
return Expr.Error("second arg to list.get must be a list", origin=list_expr)
|
|
549
|
+
|
|
550
|
+
if idx < 0 or idx >= len(lst.elements):
|
|
551
|
+
return Expr.Error("index out of range", origin=idx_expr)
|
|
552
|
+
return lst.elements[idx]
|
|
553
|
+
|
|
554
|
+
# set: (list.set 1 3 ints) -> (1 3)
|
|
555
|
+
elif first.value == "list.set":
|
|
556
|
+
if len(expr.elements) < 4:
|
|
557
|
+
return Expr.Error("list.set expects index value list", origin=expr)
|
|
558
|
+
idx_expr, val_expr, list_expr = expr.elements[1], expr.elements[2], expr.elements[3]
|
|
559
|
+
idx = self.machine_expr_eval(env_id, idx_expr)
|
|
560
|
+
if isinstance(idx, Expr.Error): return idx
|
|
561
|
+
if not isinstance(idx, Expr.IntExpr):
|
|
562
|
+
return Expr.Error("index must be int", origin=idx_expr)
|
|
563
|
+
idx = idx.value
|
|
564
|
+
|
|
565
|
+
val = self.machine_expr_eval(env_id, val_expr)
|
|
566
|
+
if isinstance(val, Expr.Error): return val
|
|
567
|
+
|
|
568
|
+
lst = self.machine_expr_eval(env_id, list_expr)
|
|
569
|
+
if isinstance(lst, Expr.Error): return lst
|
|
570
|
+
if not isinstance(lst, Expr.ListExpr):
|
|
571
|
+
return Expr.Error("third arg to list.set must be a list", origin=list_expr)
|
|
572
|
+
|
|
573
|
+
elems = list(lst.elements)
|
|
574
|
+
if idx < 0 or idx >= len(elems):
|
|
575
|
+
return Expr.Error("index out of range", origin=idx_expr)
|
|
576
|
+
elems[idx] = val
|
|
577
|
+
return Expr.ListExpr(elems)
|
|
578
|
+
|
|
579
|
+
### each: (list.each fn list) -> ()
|
|
580
|
+
elif first.value == "list.each":
|
|
581
|
+
if len(expr.elements) < 3:
|
|
582
|
+
return Expr.Error("list.each expects fn list", origin=expr)
|
|
583
|
+
fn_expr, list_expr = expr.elements[1], expr.elements[2]
|
|
584
|
+
lst = self.machine_expr_eval(env_id, list_expr)
|
|
585
|
+
if isinstance(lst, Expr.Error):
|
|
586
|
+
return lst
|
|
587
|
+
if not isinstance(lst, Expr.ListExpr):
|
|
588
|
+
return Expr.Error("second arg to list.each must be a list", origin=list_expr)
|
|
589
|
+
|
|
590
|
+
for el in lst.elements:
|
|
591
|
+
res = self.machine_expr_eval(env_id, Expr.ListExpr([fn_expr, el]))
|
|
592
|
+
if isinstance(res, Expr.Error):
|
|
593
|
+
return res
|
|
594
|
+
return Expr.ListExpr([])
|
|
595
|
+
|
|
596
|
+
### fold: (list.fold fn init list) / (list.fold + 0 ints) -> 3
|
|
597
|
+
elif first.value == "list.fold":
|
|
598
|
+
fn_expr, init_expr, list_expr = expr.elements[1], expr.elements[2], expr.elements[3]
|
|
599
|
+
acc = self.machine_expr_eval(env_id, init_expr)
|
|
600
|
+
if isinstance(acc, Expr.Error):
|
|
601
|
+
return acc
|
|
602
|
+
|
|
603
|
+
lst = self.machine_expr_eval(env_id, list_expr)
|
|
604
|
+
if isinstance(lst, Expr.Error):
|
|
605
|
+
return lst
|
|
606
|
+
if not isinstance(lst, Expr.ListExpr):
|
|
607
|
+
return Expr.Error("third arg to list.fold must be a list", origin=list_expr)
|
|
608
|
+
|
|
609
|
+
for el in lst.elements:
|
|
610
|
+
call = Expr.ListExpr([fn_expr, acc, el])
|
|
611
|
+
res = self.machine_expr_eval(env_id, call)
|
|
612
|
+
if isinstance(res, Expr.Error):
|
|
613
|
+
return res
|
|
614
|
+
acc = res
|
|
615
|
+
|
|
616
|
+
return acc
|
|
617
|
+
|
|
618
|
+
### sort: (list.sort fn list) / (list.sort (fn (a b) (a < b)) ints) -> (2 1)
|
|
619
|
+
elif first.value == "list.sort":
|
|
620
|
+
if len(expr.elements) < 3:
|
|
621
|
+
return Expr.Error("list.sort fn list", origin=expr)
|
|
622
|
+
fn_e, lst_e = expr.elements[1], expr.elements[2]
|
|
623
|
+
|
|
624
|
+
lst = self.machine_expr_eval(env_id, lst_e)
|
|
625
|
+
if isinstance(lst, Expr.Error): return lst
|
|
626
|
+
if not isinstance(lst, Expr.ListExpr):
|
|
627
|
+
return Expr.Error("second arg must be list", origin=lst_e)
|
|
628
|
+
|
|
629
|
+
elems = list(lst.elements)
|
|
630
|
+
for i in range(1, len(elems)):
|
|
631
|
+
j = i
|
|
632
|
+
while j > 0:
|
|
633
|
+
cmp_res = self.machine_expr_eval(
|
|
634
|
+
env_id,
|
|
635
|
+
Expr.ListExpr([fn_e, elems[j-1], elems[j]])
|
|
636
|
+
)
|
|
637
|
+
if isinstance(cmp_res, Expr.Error): return cmp_res
|
|
638
|
+
if not isinstance(cmp_res, Expr.BoolExpr):
|
|
639
|
+
return Expr.Error("comparator must return bool", origin=fn_e)
|
|
640
|
+
|
|
641
|
+
if cmp_res.value:
|
|
642
|
+
elems[j-1], elems[j] = elems[j], elems[j-1]
|
|
643
|
+
j -= 1
|
|
644
|
+
else:
|
|
645
|
+
break
|
|
646
|
+
return Expr.ListExpr(elems)
|
|
647
|
+
|
|
648
|
+
### len: (list.len list) -> Int / (list.len ints) -> Integer(2)
|
|
649
|
+
elif first.value == "list.len":
|
|
650
|
+
if len(expr.elements) < 2:
|
|
651
|
+
return Expr.Error("list.len list", origin=expr)
|
|
652
|
+
lst_e = expr.elements[1]
|
|
653
|
+
lst = self.machine_expr_eval(env_id, lst_e)
|
|
654
|
+
if isinstance(lst, Expr.Error): return lst
|
|
655
|
+
if not isinstance(lst, Expr.ListExpr):
|
|
656
|
+
return Expr.Error("arg must be list", origin=lst_e)
|
|
657
|
+
return Expr.Integer(len(lst.elements))
|
|
658
|
+
|
|
659
|
+
## Integer
|
|
660
|
+
### add
|
|
666
661
|
elif first.value == "+":
|
|
667
662
|
args = expr.elements[1:]
|
|
668
663
|
if not args:
|
|
@@ -747,6 +742,19 @@ class Node:
|
|
|
747
742
|
|
|
748
743
|
return Expr.Boolean(res)
|
|
749
744
|
|
|
745
|
+
if isinstance(first, Expr.Function):
|
|
746
|
+
arg_exprs = expr.elements[1:]
|
|
747
|
+
if len(arg_exprs) != len(first.params):
|
|
748
|
+
return Expr.Error(f"arity mismatch: expected {len(first.params)}, got {len(arg_exprs)}", origin=expr)
|
|
749
|
+
|
|
750
|
+
call_env = self.machine_create_environment(parent_id=env_id)
|
|
751
|
+
for name, aexpr in zip(first.params, arg_exprs):
|
|
752
|
+
val = self.machine_expr_eval(env_id, aexpr)
|
|
753
|
+
if isinstance(val, Expr.Error): return val
|
|
754
|
+
self.machine_expr_put(call_env, name, val)
|
|
755
|
+
|
|
756
|
+
return self.machine_expr_eval(env_id=call_env, expr=first.body)
|
|
757
|
+
|
|
750
758
|
else:
|
|
751
759
|
evaluated_elements = [self.machine_expr_eval(env_id=env_id, expr=e) for e in expr.elements]
|
|
752
760
|
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.
|
|
3
|
+
Version: 0.2.30
|
|
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=
|
|
3
|
+
astreum/node.py,sha256=hBux-hMj2vLtXsWaEiMd9y8PgKCh3Sp1-tmaKs0B5ZU,38163
|
|
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=
|
|
25
|
-
astreum-0.2.
|
|
26
|
-
astreum-0.2.
|
|
27
|
-
astreum-0.2.
|
|
28
|
-
astreum-0.2.
|
|
29
|
-
astreum-0.2.
|
|
28
|
+
astreum/storage/setup.py,sha256=1-9ztEFI_BvRDvAA0lAn4mFya8iq65THTArlj--M3Hg,626
|
|
29
|
+
astreum-0.2.30.dist-info/licenses/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
|
|
30
|
+
astreum-0.2.30.dist-info/METADATA,sha256=LqByNjd5zu9TMcCuIw6j-YuRAEYqCQHMP1prBdjFZaM,5478
|
|
31
|
+
astreum-0.2.30.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
32
|
+
astreum-0.2.30.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
|
|
33
|
+
astreum-0.2.30.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|