astreum 0.2.29__py3-none-any.whl → 0.2.61__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.
Files changed (58) hide show
  1. astreum/__init__.py +9 -1
  2. astreum/_communication/__init__.py +11 -0
  3. astreum/{models → _communication}/message.py +101 -64
  4. astreum/_communication/peer.py +23 -0
  5. astreum/_communication/ping.py +33 -0
  6. astreum/_communication/route.py +95 -0
  7. astreum/_communication/setup.py +322 -0
  8. astreum/_communication/util.py +42 -0
  9. astreum/_consensus/__init__.py +20 -0
  10. astreum/_consensus/account.py +95 -0
  11. astreum/_consensus/accounts.py +38 -0
  12. astreum/_consensus/block.py +311 -0
  13. astreum/_consensus/chain.py +66 -0
  14. astreum/_consensus/fork.py +100 -0
  15. astreum/_consensus/genesis.py +72 -0
  16. astreum/_consensus/receipt.py +136 -0
  17. astreum/_consensus/setup.py +115 -0
  18. astreum/_consensus/transaction.py +215 -0
  19. astreum/_consensus/workers/__init__.py +9 -0
  20. astreum/_consensus/workers/discovery.py +48 -0
  21. astreum/_consensus/workers/validation.py +125 -0
  22. astreum/_consensus/workers/verify.py +63 -0
  23. astreum/_lispeum/__init__.py +16 -0
  24. astreum/_lispeum/environment.py +13 -0
  25. astreum/_lispeum/expression.py +190 -0
  26. astreum/_lispeum/high_evaluation.py +236 -0
  27. astreum/_lispeum/low_evaluation.py +123 -0
  28. astreum/_lispeum/meter.py +18 -0
  29. astreum/_lispeum/parser.py +51 -0
  30. astreum/_lispeum/tokenizer.py +22 -0
  31. astreum/_node.py +198 -0
  32. astreum/_storage/__init__.py +7 -0
  33. astreum/_storage/atom.py +109 -0
  34. astreum/_storage/patricia.py +478 -0
  35. astreum/_storage/setup.py +35 -0
  36. astreum/models/block.py +48 -39
  37. astreum/node.py +755 -563
  38. astreum/utils/bytes.py +24 -0
  39. astreum/utils/integer.py +25 -0
  40. astreum/utils/logging.py +219 -0
  41. {astreum-0.2.29.dist-info → astreum-0.2.61.dist-info}/METADATA +50 -14
  42. astreum-0.2.61.dist-info/RECORD +57 -0
  43. astreum/lispeum/__init__.py +0 -2
  44. astreum/lispeum/environment.py +0 -40
  45. astreum/lispeum/expression.py +0 -86
  46. astreum/lispeum/parser.py +0 -41
  47. astreum/lispeum/tokenizer.py +0 -52
  48. astreum/models/account.py +0 -91
  49. astreum/models/accounts.py +0 -34
  50. astreum/models/transaction.py +0 -106
  51. astreum/relay/__init__.py +0 -0
  52. astreum/relay/peer.py +0 -9
  53. astreum/relay/route.py +0 -25
  54. astreum/relay/setup.py +0 -58
  55. astreum-0.2.29.dist-info/RECORD +0 -33
  56. {astreum-0.2.29.dist-info → astreum-0.2.61.dist-info}/WHEEL +0 -0
  57. {astreum-0.2.29.dist-info → astreum-0.2.61.dist-info}/licenses/LICENSE +0 -0
  58. {astreum-0.2.29.dist-info → astreum-0.2.61.dist-info}/top_level.txt +0 -0
astreum/models/account.py DELETED
@@ -1,91 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Optional, Callable
4
-
5
- from .merkle import MerkleTree
6
-
7
- _FIELD_ORDER = ["balance", "data", "nonce"]
8
- _INT_FIELDS = {"balance", "nonce"}
9
-
10
- def _int_to_min_bytes(i: int) -> bytes:
11
- length = (i.bit_length() + 7) // 8 or 1
12
- return i.to_bytes(length, "big")
13
-
14
- class Account:
15
- def __init__(
16
- self,
17
- body_hash: bytes,
18
- *,
19
- body_tree: Optional[MerkleTree] = None,
20
- get_node_fn: Optional[Callable[[bytes], Optional[bytes]]] = None,
21
- ) -> None:
22
- self._body_hash = body_hash
23
- self._body_tree = body_tree
24
- self._balance: Optional[int] = None
25
- self._data: Optional[bytes] = None
26
- self._nonce: Optional[int] = None
27
-
28
- if self._body_tree and get_node_fn:
29
- self._body_tree._node_get = get_node_fn
30
-
31
- @classmethod
32
- def create(
33
- cls,
34
- balance: int,
35
- data: bytes,
36
- nonce: int,
37
- ) -> Account:
38
- """Build an Account body from explicit fields in alphabetical order."""
39
- # prepare values dict
40
- values = {"balance": balance, "data": data, "nonce": nonce}
41
-
42
- # build leaves in alphabetical order
43
- leaves: list[bytes] = []
44
- for name in _FIELD_ORDER:
45
- v = values[name]
46
- if name in _INT_FIELDS:
47
- leaves.append(_int_to_min_bytes(v)) # type: ignore[arg-type]
48
- else:
49
- leaves.append(v)
50
-
51
- tree = MerkleTree.from_leaves(leaves)
52
- return cls(tree.root_hash, body_tree=tree)
53
-
54
- def body_hash(self) -> bytes:
55
- """Return the Merkle root of the account body."""
56
- return self._body_hash
57
-
58
- def _require_tree(self) -> MerkleTree:
59
- if not self._body_tree:
60
- raise ValueError("Body tree unavailable for this Account")
61
- return self._body_tree
62
-
63
- def balance(self) -> int:
64
- """Fetch & cache the `balance` field (leaf 0)."""
65
- if self._balance is not None:
66
- return self._balance
67
- raw = self._require_tree().get(0)
68
- if raw is None:
69
- raise ValueError("Merkle leaf 0 (balance) missing")
70
- self._balance = int.from_bytes(raw, "big")
71
- return self._balance
72
-
73
- def data(self) -> bytes:
74
- """Fetch & cache the `data` field (leaf 1)."""
75
- if self._data is not None:
76
- return self._data
77
- raw = self._require_tree().get(1)
78
- if raw is None:
79
- raise ValueError("Merkle leaf 1 (data) missing")
80
- self._data = raw
81
- return self._data
82
-
83
- def nonce(self) -> int:
84
- """Fetch & cache the `nonce` field (leaf 2)."""
85
- if self._nonce is not None:
86
- return self._nonce
87
- raw = self._require_tree().get(2)
88
- if raw is None:
89
- raise ValueError("Merkle leaf 2 (nonce) missing")
90
- self._nonce = int.from_bytes(raw, "big")
91
- return self._nonce
@@ -1,34 +0,0 @@
1
- from __future__ import annotations
2
- from typing import Dict, Optional, Callable
3
- from .patricia import PatriciaTrie
4
- from .account import Account
5
-
6
- class Accounts:
7
- def __init__(
8
- self,
9
- root_hash: Optional[bytes] = None,
10
- global_get_fn: Optional[Callable[[bytes], Optional[bytes]]] = None,
11
- ) -> None:
12
- self._global_get_fn = global_get_fn
13
- self._trie = PatriciaTrie(node_get=global_get_fn, root_hash=root_hash)
14
- self._cache: Dict[bytes, Account] = {}
15
-
16
- @property
17
- def root_hash(self) -> Optional[bytes]:
18
- return self._trie.root_hash
19
-
20
- def get_account(self, address: bytes) -> Optional[Account]:
21
- if address in self._cache:
22
- return self._cache[address]
23
-
24
- body_hash: Optional[bytes] = self._trie.get(address)
25
- if body_hash is None:
26
- return None
27
-
28
- acc = Account(body_hash, get_node_fn=self._global_get_fn)
29
- self._cache[address] = acc
30
- return acc
31
-
32
- def set_account(self, address: bytes, account: Account) -> None:
33
- self._cache[address] = account
34
- self._trie.put(address, account.body_hash())
@@ -1,106 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Dict, List, Optional, Union, Any, Callable
4
-
5
- from .merkle import MerkleTree
6
- from ..crypto import ed25519
7
-
8
- _FIELD_ORDER: List[str] = [
9
- "amount",
10
- "balance",
11
- "fee",
12
- "nonce",
13
- "recipient_pk",
14
- "sender_pk",
15
- ]
16
-
17
- _INT_FIELDS = {"amount", "balance", "fee", "nonce"}
18
-
19
- def _int_to_min_bytes(i: int) -> bytes:
20
- length = (i.bit_length() + 7) // 8 or 1
21
- return i.to_bytes(length, "big")
22
-
23
- class Transaction:
24
- # init
25
- def __init__(
26
- self,
27
- tx_hash: bytes,
28
- *,
29
- tree: Optional[MerkleTree] = None,
30
- global_get_fn: Optional[Callable[[bytes], Optional[bytes]]] = None,
31
- ) -> None:
32
- self._hash = tx_hash
33
- self._tree = tree
34
- self._field_cache: Dict[str, Union[int, bytes]] = {}
35
-
36
- if self._tree and global_get_fn:
37
- self._tree.global_get_fn = global_get_fn
38
-
39
- @classmethod
40
- def create(
41
- cls,
42
- *,
43
- amount: int,
44
- balance: int,
45
- fee: int,
46
- nonce: int,
47
- recipient_pk: bytes,
48
- sender_pk: bytes,
49
- ) -> "Transaction":
50
- vals: Dict[str, Any] = locals().copy()
51
- leaves = [
52
- vals[name] if isinstance(vals[name], bytes) else _int_to_min_bytes(vals[name])
53
- for name in _FIELD_ORDER
54
- ]
55
-
56
- tree = MerkleTree.from_leaves(leaves)
57
- return cls(tx_hash=tree.root_hash, tree=tree)
58
-
59
- @property
60
- def hash(self) -> bytes:
61
- return self._hash
62
-
63
- def _require_tree(self) -> MerkleTree:
64
- if not self._tree:
65
- raise ValueError("Merkle tree unavailable for this Transaction")
66
- return self._tree
67
-
68
- def _field(self, idx: int, name: str) -> Union[int, bytes]:
69
- if name in self._field_cache:
70
- return self._field_cache[name]
71
-
72
- raw = self._require_tree().get(idx)
73
- if raw is None:
74
- raise ValueError(f"Leaf {idx} (‘{name}’) missing from Merkle tree")
75
-
76
- value = int.from_bytes(raw, "big") if name in _INT_FIELDS else raw
77
- self._field_cache[name] = value
78
- return value
79
-
80
- def get_amount(self) -> int:
81
- return self._field(0, "amount")
82
-
83
- def get_balance(self) -> int:
84
- return self._field(1, "balance")
85
-
86
- def get_fee(self) -> int:
87
- return self._field(2, "fee")
88
-
89
- def get_nonce(self) -> int:
90
- return self._field(3, "nonce")
91
-
92
- def get_recipient_pk(self) -> bytes:
93
- return self._field(4, "recipient_pk")
94
-
95
- def get_sender_pk(self) -> bytes:
96
- return self._field(5, "sender_pk")
97
-
98
- def sign(self, priv: ed25519.Ed25519PrivateKey) -> bytes:
99
- return priv.sign(self.hash)
100
-
101
- def verify_signature(self, sig: bytes, sender_pk: bytes) -> bool:
102
- try:
103
- ed25519.Ed25519PublicKey.from_public_bytes(sender_pk).verify(sig, self.hash)
104
- return True
105
- except Exception:
106
- return False
astreum/relay/__init__.py DELETED
File without changes
astreum/relay/peer.py DELETED
@@ -1,9 +0,0 @@
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 DELETED
@@ -1,25 +0,0 @@
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 DELETED
@@ -1,58 +0,0 @@
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
@@ -1,33 +0,0 @@
1
- astreum/__init__.py,sha256=y2Ok3EY_FstcmlVASr80lGR_0w-dH-SXDCCQFmL6uwA,28
2
- astreum/format.py,sha256=X4tG5GGPweNCE54bHYkLFiuLTbmpy5upO_s1Cef-MGA,2711
3
- astreum/node.py,sha256=OhiApRqQcPc6-CWbk2NeKdOiXQapy0mbl3uLK_xjmYU,28971
4
- astreum/crypto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- astreum/crypto/ed25519.py,sha256=FRnvlN0kZlxn4j-sJKl-C9tqiz_0z4LZyXLj3KIj1TQ,1760
6
- astreum/crypto/quadratic_form.py,sha256=pJgbORey2NTWbQNhdyvrjy_6yjORudQ67jBz2ScHptg,4037
7
- astreum/crypto/wesolowski.py,sha256=SUgGXW3Id07dJtWzDcs4dluIhjqbRWQ8YWjn_mK78AQ,4092
8
- astreum/crypto/x25519.py,sha256=i29v4BmwKRcbz9E7NKqFDQyxzFtJUqN0St9jd7GS1uA,1137
9
- astreum/lispeum/__init__.py,sha256=K-NDzIjtIsXzC9X7lnYvlvIaVxjFcY7WNsgLIE3DH3U,58
10
- astreum/lispeum/environment.py,sha256=wolwt9psDl62scgjaVG0G59xlBs1AM4NPgryUbxzG_4,1220
11
- astreum/lispeum/expression.py,sha256=K8gFifDaHu394bs9qnpvP8tjeiymFGQpnDC_iW9nU4E,2379
12
- astreum/lispeum/parser.py,sha256=jQRzZYvBuSg8t_bxsbt1-WcHaR_LPveHNX7Qlxhaw-M,1165
13
- astreum/lispeum/tokenizer.py,sha256=J-I7MEd0r2ZoVqxvRPlu-Afe2ZdM0tKXXhf1R4SxYTo,1429
14
- astreum/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- astreum/models/account.py,sha256=sHujGSwtV13rvOGJ5LZXuMrJ4F9XUdvyuWKz-zJ9lkE,2986
16
- astreum/models/accounts.py,sha256=aFSEWlq6zRf65-KGAdNGqEJyNVY3fpKhx8y1vU6sgSc,1164
17
- astreum/models/block.py,sha256=-5j7uO0woVtNi0h52__e7AxpDQSVhzKUhr6Qc-2xZsE,17870
18
- astreum/models/merkle.py,sha256=lvWJa9nmrBL0n_2h_uNqpB_9a5s5Hn1FceRLx0IZIVQ,6778
19
- astreum/models/message.py,sha256=vv8yx-ndVYjCmPM4gXRVMToCTlKY_mflPu0uKsb9iiE,2117
20
- astreum/models/patricia.py,sha256=ohmXrcaz7Ae561tyC4u4iPOkQPkKr8N0IWJek4upFIg,13392
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
26
- astreum/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
- astreum/storage/object.py,sha256=knFlvw_tpcC4twSu1DGNpHX31wlANN8E5dgEqIfU--Q,2041
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,,