astreum 0.2.25__py3-none-any.whl → 0.2.27__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/models/block.py +107 -22
- astreum/models/merkle.py +6 -6
- astreum/models/message.py +64 -0
- astreum/models/transaction.py +3 -3
- astreum/node.py +40 -151
- {astreum-0.2.25.dist-info → astreum-0.2.27.dist-info}/METADATA +1 -1
- {astreum-0.2.25.dist-info → astreum-0.2.27.dist-info}/RECORD +10 -9
- {astreum-0.2.25.dist-info → astreum-0.2.27.dist-info}/WHEEL +0 -0
- {astreum-0.2.25.dist-info → astreum-0.2.27.dist-info}/licenses/LICENSE +0 -0
- {astreum-0.2.25.dist-info → astreum-0.2.27.dist-info}/top_level.txt +0 -0
astreum/models/block.py
CHANGED
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from threading import Thread
|
|
4
4
|
from typing import List, Dict, Any, Optional, Union
|
|
5
5
|
|
|
6
|
-
from astreum.crypto.wesolowski import vdf_generate
|
|
6
|
+
from astreum.crypto.wesolowski import vdf_generate, vdf_verify
|
|
7
7
|
from astreum.models.account import Account
|
|
8
8
|
from astreum.models.accounts import Accounts
|
|
9
9
|
from astreum.models.patricia import PatriciaTrie
|
|
@@ -86,51 +86,46 @@ class Block:
|
|
|
86
86
|
return int.from_bytes(leaf_bytes, "big")
|
|
87
87
|
return leaf_bytes
|
|
88
88
|
|
|
89
|
-
def verify_block_signature(self) -> bool:
|
|
90
|
-
"""Verify the block's Ed25519 signature against its body root."""
|
|
91
|
-
pub = ed25519.Ed25519PublicKey.from_public_bytes(
|
|
92
|
-
self.get_field("validator_pk")
|
|
93
|
-
)
|
|
94
|
-
try:
|
|
95
|
-
pub.verify(self.get_signature(), self.get_body_hash())
|
|
96
|
-
return True
|
|
97
|
-
except Exception:
|
|
98
|
-
return False
|
|
99
|
-
|
|
100
89
|
@classmethod
|
|
101
90
|
def genesis(cls, validator_addr: bytes) -> "Block":
|
|
102
|
-
# 1
|
|
91
|
+
# 1. validator-stakes sub-trie
|
|
103
92
|
stake_trie = PatriciaTrie()
|
|
104
93
|
stake_trie.put(validator_addr, (1).to_bytes(32, "big"))
|
|
105
94
|
stake_root = stake_trie.root_hash
|
|
106
95
|
|
|
107
|
-
# 2
|
|
108
|
-
validator_acct = Account.create(balance=0, data=b"",
|
|
96
|
+
# 2. three Account bodies
|
|
97
|
+
validator_acct = Account.create(balance=0, data=b"", nonce=0)
|
|
109
98
|
treasury_acct = Account.create(balance=1, data=stake_root, nonce=0)
|
|
99
|
+
burn_acct = Account.create(balance=0, data=b"", nonce=0)
|
|
110
100
|
|
|
111
|
-
# 3
|
|
101
|
+
# 3. global Accounts structure
|
|
112
102
|
accts = Accounts()
|
|
113
103
|
accts.set_account(validator_addr, validator_acct)
|
|
114
104
|
accts.set_account(b"\x11" * 32, treasury_acct)
|
|
105
|
+
accts.set_account(b"\x00" * 32, burn_acct)
|
|
115
106
|
accounts_hash = accts.root_hash
|
|
116
107
|
|
|
117
|
-
# 4
|
|
108
|
+
# 4. constant body fields for genesis
|
|
118
109
|
body_kwargs = dict(
|
|
110
|
+
block_hash = b"",
|
|
119
111
|
number = 0,
|
|
120
112
|
prev_block_hash = b"\x00" * 32,
|
|
121
113
|
timestamp = 0,
|
|
114
|
+
block_time = 0,
|
|
122
115
|
accounts_hash = accounts_hash,
|
|
116
|
+
accounts = accts,
|
|
123
117
|
transactions_total_fees = 0,
|
|
124
|
-
transaction_limit =
|
|
118
|
+
transaction_limit = 1,
|
|
125
119
|
transactions_root_hash = b"\x00" * 32,
|
|
126
|
-
|
|
120
|
+
transactions_count = 0,
|
|
121
|
+
delay_difficulty = 1,
|
|
127
122
|
delay_output = b"",
|
|
128
123
|
delay_proof = b"",
|
|
129
124
|
validator_pk = validator_addr,
|
|
130
125
|
signature = b"",
|
|
131
126
|
)
|
|
132
127
|
|
|
133
|
-
# 5
|
|
128
|
+
# 5. build and return the block
|
|
134
129
|
return cls.create(**body_kwargs)
|
|
135
130
|
|
|
136
131
|
@classmethod
|
|
@@ -139,10 +134,9 @@ class Block:
|
|
|
139
134
|
previous_block: "Block",
|
|
140
135
|
transactions: List[Transaction],
|
|
141
136
|
*,
|
|
142
|
-
validator_sk,
|
|
137
|
+
validator_sk,
|
|
143
138
|
natural_rate: float = 0.618,
|
|
144
139
|
) -> "Block":
|
|
145
|
-
TREASURY = b"\x11" * 32
|
|
146
140
|
BURN = b"\x00" * 32
|
|
147
141
|
|
|
148
142
|
blk = cls(
|
|
@@ -345,3 +339,94 @@ class Block:
|
|
|
345
339
|
self.total_fees += fee
|
|
346
340
|
self.tx_hashes.append(tx.hash)
|
|
347
341
|
self.transactions_count += 1
|
|
342
|
+
|
|
343
|
+
def validate_block(self, remote_get_fn) -> bool:
|
|
344
|
+
NAT = 0.618
|
|
345
|
+
_i2b = lambda i: i.to_bytes((i.bit_length() + 7) // 8 or 1, "big")
|
|
346
|
+
|
|
347
|
+
# ---------- 1. block-hash & signature -----------------------------
|
|
348
|
+
blk_mt = MerkleTree(node_get=remote_get_fn, root_hash=self.hash)
|
|
349
|
+
body_root = blk_mt.get(0); sig = blk_mt.get(1)
|
|
350
|
+
ed25519.verify_signature(public_key=self.validator_pk, message=body_root, signature=sig)
|
|
351
|
+
|
|
352
|
+
# ---------- 2. rebuild body_root from fields ----------------------
|
|
353
|
+
f_names = (
|
|
354
|
+
"accounts_hash","block_time","delay_difficulty","delay_output","delay_proof",
|
|
355
|
+
"number","prev_block_hash","timestamp","transaction_limit",
|
|
356
|
+
"transactions_count","transactions_root_hash","transactions_total_fees",
|
|
357
|
+
"validator_pk",
|
|
358
|
+
)
|
|
359
|
+
leaves = [
|
|
360
|
+
v if isinstance(v := self.get_field(n), bytes) else _i2b(v)
|
|
361
|
+
for n in sorted(f_names)
|
|
362
|
+
]
|
|
363
|
+
if MerkleTree.from_leaves(leaves).root_hash != body_root:
|
|
364
|
+
raise ValueError("body root mismatch")
|
|
365
|
+
|
|
366
|
+
# ---------- 3. previous block header & VDF ------------------------
|
|
367
|
+
prev_mt = MerkleTree(node_get=remote_get_fn, root_hash=self.prev_block_hash)
|
|
368
|
+
prev_body_root, prev_sig = prev_mt.get(0), prev_mt.get(1)
|
|
369
|
+
prev_body_mt = MerkleTree(node_get=remote_get_fn, root_hash=prev_body_root)
|
|
370
|
+
prev_blk = Block(block_hash=self.prev_block_hash,
|
|
371
|
+
body_tree=prev_body_mt, signature=prev_sig)
|
|
372
|
+
prev_out = prev_blk.get_field("delay_output")
|
|
373
|
+
prev_diff = prev_blk.get_field("delay_difficulty")
|
|
374
|
+
prev_bt = prev_blk.get_field("block_time")
|
|
375
|
+
prev_limit = prev_blk.get_field("transaction_limit")
|
|
376
|
+
prev_cnt = prev_blk.get_field("transactions_count")
|
|
377
|
+
|
|
378
|
+
if not vdf_verify(prev_out, self.delay_output, self.delay_proof,
|
|
379
|
+
T=self.delay_difficulty, D=-4):
|
|
380
|
+
raise ValueError("bad VDF proof")
|
|
381
|
+
|
|
382
|
+
# ---------- 4. replay all txs -------------------------------------
|
|
383
|
+
accs = Accounts(root_hash=prev_blk.get_field("accounts_hash"),
|
|
384
|
+
node_get=remote_get_fn)
|
|
385
|
+
tx_mt = MerkleTree(node_get=remote_get_fn,
|
|
386
|
+
root_hash=self.transactions_root_hash)
|
|
387
|
+
if tx_mt.leaf_count() != self.transactions_count:
|
|
388
|
+
raise ValueError("transactions_count mismatch")
|
|
389
|
+
|
|
390
|
+
dummy = Block(block_hash=b"", accounts=accs,
|
|
391
|
+
accounts_hash=accs.root_hash,
|
|
392
|
+
transaction_limit=prev_limit)
|
|
393
|
+
for i in range(self.transactions_count):
|
|
394
|
+
h = tx_mt.get(i)
|
|
395
|
+
tm = MerkleTree(node_get=remote_get_fn, root_hash=h)
|
|
396
|
+
tx = Transaction(h, tree=tm, node_get=remote_get_fn)
|
|
397
|
+
dummy.apply_tx(tx)
|
|
398
|
+
|
|
399
|
+
# fee split identical to build()
|
|
400
|
+
burn = dummy.total_fees // 2
|
|
401
|
+
rew = dummy.total_fees - burn
|
|
402
|
+
if burn:
|
|
403
|
+
dummy.accounts.set_account(
|
|
404
|
+
b"\x00"*32,
|
|
405
|
+
Account.create(burn, b"", 0)
|
|
406
|
+
)
|
|
407
|
+
if rew:
|
|
408
|
+
v_acct = dummy.accounts.get_account(self.validator_pk) or Account.create(0,b"",0)
|
|
409
|
+
dummy.accounts.set_account(
|
|
410
|
+
self.validator_pk,
|
|
411
|
+
Account.create(v_acct.balance()+rew, v_acct.data(), v_acct.nonce())
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
if dummy.accounts.root_hash != self.accounts_hash:
|
|
415
|
+
raise ValueError("accounts_hash mismatch")
|
|
416
|
+
|
|
417
|
+
# ---------- 5. natural-rate rules --------------------------------
|
|
418
|
+
grow_thr = prev_limit * NAT
|
|
419
|
+
shrink_thr = prev_cnt * NAT
|
|
420
|
+
expect_lim = prev_cnt if prev_cnt > grow_thr \
|
|
421
|
+
else max(1, int(prev_limit * NAT)) if prev_cnt < shrink_thr \
|
|
422
|
+
else prev_limit
|
|
423
|
+
if self.transaction_limit != expect_lim:
|
|
424
|
+
raise ValueError("tx-limit rule")
|
|
425
|
+
|
|
426
|
+
expect_diff = max(1, int(prev_diff / NAT)) if prev_bt <= 1 \
|
|
427
|
+
else max(1, int(prev_diff * NAT))
|
|
428
|
+
if self.delay_difficulty != expect_diff:
|
|
429
|
+
raise ValueError("difficulty rule")
|
|
430
|
+
|
|
431
|
+
return True
|
|
432
|
+
|
astreum/models/merkle.py
CHANGED
|
@@ -41,11 +41,11 @@ class MerkleNode:
|
|
|
41
41
|
class MerkleTree:
|
|
42
42
|
def __init__(
|
|
43
43
|
self,
|
|
44
|
-
|
|
44
|
+
global_get_fn: Callable[[bytes], Optional[bytes]],
|
|
45
45
|
root_hash: Optional[bytes] = None,
|
|
46
46
|
height: Optional[int] = None,
|
|
47
47
|
) -> None:
|
|
48
|
-
self.
|
|
48
|
+
self._global_get_fn = global_get_fn
|
|
49
49
|
self.nodes: Dict[bytes, MerkleNode] = {}
|
|
50
50
|
self.root_hash = root_hash
|
|
51
51
|
self._height: Optional[int] = height
|
|
@@ -54,13 +54,13 @@ class MerkleTree:
|
|
|
54
54
|
def from_leaves(
|
|
55
55
|
cls,
|
|
56
56
|
leaves: List[bytes],
|
|
57
|
-
|
|
57
|
+
global_get_fn: Callable[[bytes], Optional[bytes]] | None = None,
|
|
58
58
|
) -> "MerkleTree":
|
|
59
59
|
if not leaves:
|
|
60
60
|
raise ValueError("must supply at least one leaf")
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
tree = cls(
|
|
62
|
+
global_get_fn = global_get_fn or (lambda _h: None)
|
|
63
|
+
tree = cls(global_get_fn=global_get_fn)
|
|
64
64
|
|
|
65
65
|
# Step 1 – create leaf nodes list[bytes]
|
|
66
66
|
level_hashes: List[bytes] = []
|
|
@@ -97,7 +97,7 @@ class MerkleTree:
|
|
|
97
97
|
return None
|
|
98
98
|
node = self.nodes.get(h)
|
|
99
99
|
if node is None:
|
|
100
|
-
raw = self.
|
|
100
|
+
raw = self._global_get_fn(h)
|
|
101
101
|
if raw is None:
|
|
102
102
|
return None
|
|
103
103
|
node = MerkleNode.from_bytes(raw)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from enum import IntEnum
|
|
2
|
+
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PublicKey
|
|
3
|
+
|
|
4
|
+
class MessageTopic(IntEnum):
|
|
5
|
+
PING = 0
|
|
6
|
+
OBJECT_REQUEST = 1
|
|
7
|
+
OBJECT_RESPONSE = 2
|
|
8
|
+
ROUTE_REQUEST = 3
|
|
9
|
+
ROUTE_RESPONSE = 4
|
|
10
|
+
|
|
11
|
+
class Message:
|
|
12
|
+
handshake: bool
|
|
13
|
+
sender: X25519PublicKey
|
|
14
|
+
|
|
15
|
+
topic: MessageTopic
|
|
16
|
+
content: bytes
|
|
17
|
+
|
|
18
|
+
def to_bytes(self):
|
|
19
|
+
if self.handshake:
|
|
20
|
+
# handshake byte (1) + raw public key bytes
|
|
21
|
+
return bytes([1]) + self.sender.public_bytes(
|
|
22
|
+
encoding=serialization.Encoding.Raw,
|
|
23
|
+
format=serialization.PublicFormat.Raw
|
|
24
|
+
)
|
|
25
|
+
else:
|
|
26
|
+
# normal message: 0 + topic + content
|
|
27
|
+
return bytes([0, self.topic.value]) + self.content
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def from_bytes(cls, data: bytes) -> "Message":
|
|
31
|
+
if len(data) < 1:
|
|
32
|
+
raise ValueError("Cannot parse Message: no data")
|
|
33
|
+
flag = data[0]
|
|
34
|
+
# create empty instance
|
|
35
|
+
msg = cls.__new__(cls)
|
|
36
|
+
|
|
37
|
+
if flag == 1:
|
|
38
|
+
# handshake message: the rest is the peer’s public key
|
|
39
|
+
key_bytes = data[1:]
|
|
40
|
+
try:
|
|
41
|
+
sender = X25519PublicKey.from_public_bytes(key_bytes)
|
|
42
|
+
except ValueError:
|
|
43
|
+
raise ValueError("Invalid public key bytes")
|
|
44
|
+
msg.handshake = True
|
|
45
|
+
msg.sender = sender
|
|
46
|
+
msg.topic = None
|
|
47
|
+
msg.content = b''
|
|
48
|
+
elif flag == 0:
|
|
49
|
+
# normal message: next byte is topic, rest is content
|
|
50
|
+
if len(data) < 2:
|
|
51
|
+
raise ValueError("Cannot parse Message: missing topic byte")
|
|
52
|
+
topic_val = data[1]
|
|
53
|
+
try:
|
|
54
|
+
topic = MessageTopic(topic_val)
|
|
55
|
+
except ValueError:
|
|
56
|
+
raise ValueError(f"Unknown MessageTopic: {topic_val}")
|
|
57
|
+
msg.handshake = False
|
|
58
|
+
msg.sender = None
|
|
59
|
+
msg.topic = topic
|
|
60
|
+
msg.content = data[2:]
|
|
61
|
+
else:
|
|
62
|
+
raise ValueError(f"Invalid handshake flag: {flag}")
|
|
63
|
+
|
|
64
|
+
return msg
|
astreum/models/transaction.py
CHANGED
|
@@ -27,14 +27,14 @@ class Transaction:
|
|
|
27
27
|
tx_hash: bytes,
|
|
28
28
|
*,
|
|
29
29
|
tree: Optional[MerkleTree] = None,
|
|
30
|
-
|
|
30
|
+
global_get_fn: Optional[Callable[[bytes], Optional[bytes]]] = None,
|
|
31
31
|
) -> None:
|
|
32
32
|
self._hash = tx_hash
|
|
33
33
|
self._tree = tree
|
|
34
34
|
self._field_cache: Dict[str, Union[int, bytes]] = {}
|
|
35
35
|
|
|
36
|
-
if self._tree and
|
|
37
|
-
self._tree.
|
|
36
|
+
if self._tree and global_get_fn:
|
|
37
|
+
self._tree.global_get_fn = global_get_fn
|
|
38
38
|
|
|
39
39
|
@classmethod
|
|
40
40
|
def create(
|
astreum/node.py
CHANGED
|
@@ -15,6 +15,7 @@ from .crypto import ed25519, x25519
|
|
|
15
15
|
from enum import IntEnum
|
|
16
16
|
import blake3
|
|
17
17
|
import struct
|
|
18
|
+
from .models.message import Message, MessageTopic
|
|
18
19
|
|
|
19
20
|
class ObjectRequestType(IntEnum):
|
|
20
21
|
OBJECT_GET = 0
|
|
@@ -61,106 +62,11 @@ class ObjectResponse:
|
|
|
61
62
|
type_val, data_val, hash_val = decode(data)
|
|
62
63
|
return cls(type=ObjectResponseType(type_val[0]), data=data_val, hash=hash_val)
|
|
63
64
|
|
|
64
|
-
class MessageTopic(IntEnum):
|
|
65
|
-
PING = 0
|
|
66
|
-
OBJECT_REQUEST = 1
|
|
67
|
-
OBJECT_RESPONSE = 2
|
|
68
|
-
ROUTE_REQUEST = 3
|
|
69
|
-
ROUTE_RESPONSE = 4
|
|
70
|
-
|
|
71
|
-
class Message:
|
|
72
|
-
body: bytes
|
|
73
|
-
topic: MessageTopic
|
|
74
|
-
|
|
75
|
-
def to_bytes(self):
|
|
76
|
-
return encode([self.body, [self.topic.value]])
|
|
77
|
-
|
|
78
|
-
@classmethod
|
|
79
|
-
def from_bytes(cls, data: bytes):
|
|
80
|
-
body, topic = decode(data)
|
|
81
|
-
return cls(body=body, topic=MessageTopic(topic[0]))
|
|
82
|
-
|
|
83
|
-
class Envelope:
|
|
84
|
-
encrypted: bool
|
|
85
|
-
message: Message
|
|
86
|
-
nonce: int
|
|
87
|
-
sender: X25519PublicKey
|
|
88
|
-
timestamp: datetime
|
|
89
|
-
|
|
90
|
-
def __init__(self, message: Message, sender: X25519PublicKey, encrypted: bool = False, nonce: int = 0, timestamp: Union[int, datetime, None] = None, difficulty: int = 1):
|
|
91
|
-
self.encrypted = encrypted
|
|
92
|
-
encrypted_bytes = b'\x01' if self.encrypted else b'\x00'
|
|
93
|
-
|
|
94
|
-
self.message = message
|
|
95
|
-
message_bytes = message.to_bytes()
|
|
96
|
-
|
|
97
|
-
self.sender = sender
|
|
98
|
-
self.sender_bytes = sender.public_bytes()
|
|
99
|
-
|
|
100
|
-
self.nonce = nonce
|
|
101
|
-
|
|
102
|
-
if timestamp is None:
|
|
103
|
-
self.timestamp = datetime.now(timezone.utc)
|
|
104
|
-
timestamp_int = int(self.timestamp.timestamp())
|
|
105
|
-
elif isinstance(timestamp, int):
|
|
106
|
-
self.timestamp = datetime.fromtimestamp(timestamp, timezone.utc)
|
|
107
|
-
timestamp_int = timestamp
|
|
108
|
-
elif isinstance(timestamp, datetime):
|
|
109
|
-
self.timestamp = timestamp
|
|
110
|
-
timestamp_int = int(timestamp.timestamp())
|
|
111
|
-
else:
|
|
112
|
-
raise TypeError("Timestamp must be an int (Unix timestamp), datetime object, or None")
|
|
113
|
-
|
|
114
|
-
def count_leading_zero_bits(data: bytes) -> int:
|
|
115
|
-
count = 0
|
|
116
|
-
for b in data:
|
|
117
|
-
if b == 0:
|
|
118
|
-
count += 8
|
|
119
|
-
else:
|
|
120
|
-
count += 8 - b.bit_length()
|
|
121
|
-
break
|
|
122
|
-
return count
|
|
123
|
-
|
|
124
|
-
while True:
|
|
125
|
-
envelope_bytes = encode([
|
|
126
|
-
encrypted_bytes,
|
|
127
|
-
message_bytes,
|
|
128
|
-
self.nonce,
|
|
129
|
-
self.sender_bytes,
|
|
130
|
-
timestamp_int
|
|
131
|
-
])
|
|
132
|
-
envelope_hash = blake3.blake3(envelope_bytes).digest()
|
|
133
|
-
if count_leading_zero_bits(envelope_hash) >= difficulty:
|
|
134
|
-
self.hash = envelope_hash
|
|
135
|
-
break
|
|
136
|
-
self.nonce += 1
|
|
137
|
-
|
|
138
|
-
def to_bytes(self):
|
|
139
|
-
encrypted_bytes = b'\x01' if self.encrypted else b'\x00'
|
|
140
|
-
|
|
141
|
-
return encode([
|
|
142
|
-
encrypted_bytes,
|
|
143
|
-
self.message.to_bytes(),
|
|
144
|
-
self.nonce,
|
|
145
|
-
self.sender.public_bytes(),
|
|
146
|
-
int(self.timestamp.timestamp())
|
|
147
|
-
])
|
|
148
|
-
|
|
149
|
-
@classmethod
|
|
150
|
-
def from_bytes(cls, data: bytes):
|
|
151
|
-
encrypted_bytes, message_bytes, nonce, sender_bytes, timestamp_int = decode(data)
|
|
152
|
-
return cls(
|
|
153
|
-
encrypted=(encrypted_bytes == b'\x01'),
|
|
154
|
-
message=Message.from_bytes(message_bytes),
|
|
155
|
-
nonce=nonce,
|
|
156
|
-
sender=X25519PublicKey.from_public_bytes(sender_bytes),
|
|
157
|
-
timestamp=datetime.fromtimestamp(timestamp_int, timezone.utc)
|
|
158
|
-
)
|
|
159
|
-
|
|
160
65
|
class Peer:
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
66
|
+
shared_key: bytes
|
|
67
|
+
timestamp: datetime
|
|
68
|
+
def __init__(self, my_sec_key: X25519PrivateKey, peer_pub_key: X25519PublicKey):
|
|
69
|
+
self.shared_key = my_sec_key.exchange(peer_pub_key)
|
|
164
70
|
self.timestamp = datetime.now(timezone.utc)
|
|
165
71
|
|
|
166
72
|
class Route:
|
|
@@ -419,6 +325,7 @@ class Node:
|
|
|
419
325
|
self.peer_manager_thread.start()
|
|
420
326
|
|
|
421
327
|
self.peers = Dict[X25519PublicKey, Peer]
|
|
328
|
+
self.addresses = Dict[Tuple[str, int], X25519PublicKey]
|
|
422
329
|
|
|
423
330
|
if 'bootstrap' in config:
|
|
424
331
|
for addr in config['bootstrap']:
|
|
@@ -451,9 +358,8 @@ class Node:
|
|
|
451
358
|
# find the nearest peer route node to the hash and send an object request
|
|
452
359
|
closest_peer = self._get_closest_local_peer(hash)
|
|
453
360
|
if closest_peer:
|
|
454
|
-
object_request_message = Message(topic=MessageTopic.OBJECT_REQUEST,
|
|
455
|
-
|
|
456
|
-
self.outgoing_queue.put((object_request_envelope.to_bytes(), self.peers[closest_peer].address))
|
|
361
|
+
object_request_message = Message(topic=MessageTopic.OBJECT_REQUEST, content=hash)
|
|
362
|
+
self.outgoing_queue.put((object_request_message.to_bytes(), self.peers[closest_peer].address))
|
|
457
363
|
|
|
458
364
|
# wait for upto self.storage_get_relay_timeout seconds for the object to be stored/until local_object_get returns something
|
|
459
365
|
start_time = time.time()
|
|
@@ -481,30 +387,31 @@ class Node:
|
|
|
481
387
|
while True:
|
|
482
388
|
try:
|
|
483
389
|
data, addr = self.incoming_queue.get()
|
|
484
|
-
|
|
485
|
-
match
|
|
390
|
+
message = Message.from_bytes(data)
|
|
391
|
+
match message.topic:
|
|
486
392
|
case MessageTopic.PING:
|
|
487
|
-
|
|
488
|
-
|
|
393
|
+
peer_pub_key = self.addresses.get(addr)
|
|
394
|
+
if peer_pub_key in self.peers:
|
|
395
|
+
self.peers[peer_pub_key].timestamp = datetime.now(timezone.utc)
|
|
489
396
|
continue
|
|
490
397
|
|
|
491
|
-
is_validator_flag = decode(
|
|
398
|
+
is_validator_flag = decode(message.body)
|
|
492
399
|
|
|
493
|
-
if
|
|
400
|
+
if peer_pub_key not in self.peers:
|
|
494
401
|
self._send_ping(addr)
|
|
495
402
|
|
|
496
|
-
peer = Peer(self.relay_secret_key,
|
|
403
|
+
peer = Peer(my_sec_key=self.relay_secret_key, peer_pub_key=peer_pub_key)
|
|
497
404
|
self.peers[peer.sender] = peer
|
|
498
|
-
self.peer_route.add_peer(
|
|
405
|
+
self.peer_route.add_peer(peer_pub_key)
|
|
499
406
|
if is_validator_flag == [1]:
|
|
500
|
-
self.validation_route.add_peer(
|
|
407
|
+
self.validation_route.add_peer(peer_pub_key)
|
|
501
408
|
|
|
502
409
|
if peer.timestamp < datetime.now(timezone.utc) - timedelta(minutes=5.0):
|
|
503
410
|
self._send_ping(addr)
|
|
504
411
|
|
|
505
412
|
case MessageTopic.OBJECT_REQUEST:
|
|
506
413
|
try:
|
|
507
|
-
object_request = ObjectRequest.from_bytes(
|
|
414
|
+
object_request = ObjectRequest.from_bytes(message.body)
|
|
508
415
|
|
|
509
416
|
match object_request.type:
|
|
510
417
|
# -------------- OBJECT_GET --------------
|
|
@@ -519,9 +426,8 @@ class Node:
|
|
|
519
426
|
data=local_data,
|
|
520
427
|
hash=object_hash
|
|
521
428
|
)
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
self.outgoing_queue.put((env.to_bytes(), addr))
|
|
429
|
+
obj_res_msg = Message(topic=MessageTopic.OBJECT_RESPONSE, body=resp.to_bytes())
|
|
430
|
+
self.outgoing_queue.put((obj_res_msg.to_bytes(), addr))
|
|
525
431
|
return # done
|
|
526
432
|
|
|
527
433
|
# 2. If we know a provider, tell the requester.
|
|
@@ -534,9 +440,8 @@ class Node:
|
|
|
534
440
|
data=provider_bytes,
|
|
535
441
|
hash=object_hash
|
|
536
442
|
)
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
self.outgoing_queue.put((env.to_bytes(), addr))
|
|
443
|
+
obj_res_msg = Message(topic=MessageTopic.OBJECT_RESPONSE, body=resp.to_bytes())
|
|
444
|
+
self.outgoing_queue.put((obj_res_msg.to_bytes(), addr))
|
|
540
445
|
return # done
|
|
541
446
|
|
|
542
447
|
# 3. Otherwise, direct the requester to a peer nearer to the hash.
|
|
@@ -555,14 +460,13 @@ class Node:
|
|
|
555
460
|
data=peer_info,
|
|
556
461
|
hash=object_hash
|
|
557
462
|
)
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
self.outgoing_queue.put((env.to_bytes(), addr))
|
|
463
|
+
obj_res_msg = Message(topic=MessageTopic.OBJECT_RESPONSE, body=resp.to_bytes())
|
|
464
|
+
self.outgoing_queue.put((obj_res_msg.to_bytes(), addr))
|
|
561
465
|
|
|
562
466
|
# -------------- OBJECT_PUT --------------
|
|
563
467
|
case ObjectRequestType.OBJECT_PUT:
|
|
564
468
|
# Ensure the hash is present / correct.
|
|
565
|
-
obj_hash = object_request.hash or
|
|
469
|
+
obj_hash = object_request.hash or blake3.blake3(object_request.data).digest()
|
|
566
470
|
|
|
567
471
|
nearest = self._get_closest_local_peer(obj_hash)
|
|
568
472
|
# If a strictly nearer peer exists, forward the PUT.
|
|
@@ -572,13 +476,13 @@ class Node:
|
|
|
572
476
|
data=object_request.data,
|
|
573
477
|
hash=obj_hash
|
|
574
478
|
)
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
self.outgoing_queue.put((fwd_env.to_bytes(), nearest[1].address))
|
|
479
|
+
obj_req_msg = Message(topic=MessageTopic.OBJECT_REQUEST, body=fwd_req.to_bytes())
|
|
480
|
+
self.outgoing_queue.put((obj_req_msg.to_bytes(), nearest[1].address))
|
|
578
481
|
else:
|
|
579
482
|
# We are closest → remember who can provide the object.
|
|
483
|
+
peer_pub_key = self.addresses.get(addr)
|
|
580
484
|
provider_record = encode([
|
|
581
|
-
|
|
485
|
+
peer_pub_key.public_bytes(),
|
|
582
486
|
encode_ip_address(*addr)
|
|
583
487
|
])
|
|
584
488
|
if not hasattr(self, "storage_index") or not isinstance(self.storage_index, dict):
|
|
@@ -590,13 +494,13 @@ class Node:
|
|
|
590
494
|
|
|
591
495
|
case MessageTopic.OBJECT_RESPONSE:
|
|
592
496
|
try:
|
|
593
|
-
object_response = ObjectResponse.from_bytes(
|
|
497
|
+
object_response = ObjectResponse.from_bytes(message.body)
|
|
594
498
|
if object_response.hash not in self.object_request_queue:
|
|
595
499
|
continue
|
|
596
500
|
|
|
597
501
|
match object_response.type:
|
|
598
502
|
case ObjectResponseType.OBJECT_FOUND:
|
|
599
|
-
if object_response.hash !=
|
|
503
|
+
if object_response.hash != blake3.blake3(object_response.data).digest():
|
|
600
504
|
continue
|
|
601
505
|
self.object_request_queue.remove(object_response.hash)
|
|
602
506
|
self._local_object_put(object_response.hash, object_response.data)
|
|
@@ -604,9 +508,8 @@ class Node:
|
|
|
604
508
|
case ObjectResponseType.OBJECT_PROVIDER:
|
|
605
509
|
_provider_public_key, provider_address = decode(object_response.data)
|
|
606
510
|
provider_ip, provider_port = decode_ip_address(provider_address)
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
self.outgoing_queue.put((object_request_envelope.to_bytes(), (provider_ip, provider_port)))
|
|
511
|
+
obj_req_msg = Message(topic=MessageTopic.OBJECT_REQUEST, body=object_hash)
|
|
512
|
+
self.outgoing_queue.put((obj_req_msg.to_bytes(), (provider_ip, provider_port)))
|
|
610
513
|
|
|
611
514
|
case ObjectResponseType.OBJECT_NEAREST_PEER:
|
|
612
515
|
# -- decode the peer info sent back
|
|
@@ -630,23 +533,10 @@ class Node:
|
|
|
630
533
|
if self._is_closer_than_local_peers(
|
|
631
534
|
object_response.hash, nearest_peer_public_key
|
|
632
535
|
):
|
|
633
|
-
nearest_peer_ip, nearest_peer_port = decode_ip_address(
|
|
634
|
-
|
|
635
|
-
)
|
|
636
|
-
|
|
637
|
-
topic=MessageTopic.OBJECT_REQUEST,
|
|
638
|
-
body=object_response.hash,
|
|
639
|
-
)
|
|
640
|
-
object_request_envelope = Envelope(
|
|
641
|
-
message=object_request_message,
|
|
642
|
-
sender=self.relay_public_key,
|
|
643
|
-
)
|
|
644
|
-
self.outgoing_queue.put(
|
|
645
|
-
(
|
|
646
|
-
object_request_envelope.to_bytes(),
|
|
647
|
-
(nearest_peer_ip, nearest_peer_port),
|
|
648
|
-
)
|
|
649
|
-
)
|
|
536
|
+
nearest_peer_ip, nearest_peer_port = decode_ip_address(nearest_peer_address)
|
|
537
|
+
obj_req_msg = Message(topic=MessageTopic.OBJECT_REQUEST, content=object_response.hash)
|
|
538
|
+
self.outgoing_queue.put((obj_req_msg.to_bytes(), (nearest_peer_ip, nearest_peer_port),)
|
|
539
|
+
)
|
|
650
540
|
|
|
651
541
|
|
|
652
542
|
except Exception as e:
|
|
@@ -678,9 +568,8 @@ class Node:
|
|
|
678
568
|
|
|
679
569
|
def _send_ping(self, addr: Tuple[str, int]):
|
|
680
570
|
is_validator_flag = encode([1] if self.validation_secret_key else [0])
|
|
681
|
-
ping_message = Message(topic=MessageTopic.PING,
|
|
682
|
-
|
|
683
|
-
self.outgoing_queue.put((ping_envelope.to_bytes(), addr))
|
|
571
|
+
ping_message = Message(topic=MessageTopic.PING, content=is_validator_flag)
|
|
572
|
+
self.outgoing_queue.put((ping_message.to_bytes(), addr))
|
|
684
573
|
|
|
685
574
|
def _get_closest_local_peer(self, hash: bytes) -> Optional[Tuple[X25519PublicKey, Peer]]:
|
|
686
575
|
# Find the globally closest peer using XOR distance
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: astreum
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.27
|
|
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=DZyFQLQcdcNw-Vl3JrJzNbDKAiEqSbNQhyOLeGWGXz4,41967
|
|
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
|
|
@@ -12,12 +12,13 @@ astreum/lispeum/tokenizer.py,sha256=J-I7MEd0r2ZoVqxvRPlu-Afe2ZdM0tKXXhf1R4SxYTo,
|
|
|
12
12
|
astreum/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
13
|
astreum/models/account.py,sha256=sHujGSwtV13rvOGJ5LZXuMrJ4F9XUdvyuWKz-zJ9lkE,2986
|
|
14
14
|
astreum/models/accounts.py,sha256=aFSEWlq6zRf65-KGAdNGqEJyNVY3fpKhx8y1vU6sgSc,1164
|
|
15
|
-
astreum/models/block.py,sha256
|
|
16
|
-
astreum/models/merkle.py,sha256=
|
|
15
|
+
astreum/models/block.py,sha256=-5j7uO0woVtNi0h52__e7AxpDQSVhzKUhr6Qc-2xZsE,17870
|
|
16
|
+
astreum/models/merkle.py,sha256=lvWJa9nmrBL0n_2h_uNqpB_9a5s5Hn1FceRLx0IZIVQ,6778
|
|
17
|
+
astreum/models/message.py,sha256=vv8yx-ndVYjCmPM4gXRVMToCTlKY_mflPu0uKsb9iiE,2117
|
|
17
18
|
astreum/models/patricia.py,sha256=ohmXrcaz7Ae561tyC4u4iPOkQPkKr8N0IWJek4upFIg,13392
|
|
18
|
-
astreum/models/transaction.py,sha256=
|
|
19
|
-
astreum-0.2.
|
|
20
|
-
astreum-0.2.
|
|
21
|
-
astreum-0.2.
|
|
22
|
-
astreum-0.2.
|
|
23
|
-
astreum-0.2.
|
|
19
|
+
astreum/models/transaction.py,sha256=MkLL5YX18kIf9-O4LBaZ4eWjkXDAaYIrDcDehbDZoqg,3038
|
|
20
|
+
astreum-0.2.27.dist-info/licenses/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
|
|
21
|
+
astreum-0.2.27.dist-info/METADATA,sha256=nMaxqfSmJtxic-_mIXivDbg2WszmnGkDnniiX1hqDH4,5478
|
|
22
|
+
astreum-0.2.27.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
23
|
+
astreum-0.2.27.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
|
|
24
|
+
astreum-0.2.27.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|