astreum 0.2.4__py3-none-any.whl → 0.2.5__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.

Files changed (44) hide show
  1. {astreum-0.2.4.dist-info → astreum-0.2.5.dist-info}/METADATA +1 -1
  2. astreum-0.2.5.dist-info/RECORD +18 -0
  3. astreum/_node/relay/__init__.py +0 -371
  4. astreum/_node/relay/bucket.py +0 -90
  5. astreum/_node/relay/envelope.py +0 -280
  6. astreum/_node/relay/message.py +0 -110
  7. astreum/_node/relay/peer.py +0 -174
  8. astreum/_node/relay/route.py +0 -161
  9. astreum/_node/storage/storage.py +0 -253
  10. astreum/_node/storage/utils.py +0 -137
  11. astreum/_node/utils.py +0 -34
  12. astreum/_node/validation/__init__.py +0 -0
  13. astreum/_node/validation/_block/__init__.py +0 -0
  14. astreum/_node/validation/_block/create.py +0 -98
  15. astreum/_node/validation/_block/model.py +0 -81
  16. astreum/_node/validation/_block/validate.py +0 -196
  17. astreum/_node/validation/account.py +0 -99
  18. astreum/_node/validation/block.py +0 -21
  19. astreum/_node/validation/constants.py +0 -15
  20. astreum/_node/validation/stake.py +0 -229
  21. astreum/_node/validation/transaction.py +0 -146
  22. astreum/_node/validation/vdf.py +0 -80
  23. astreum/lispeum/expression.py +0 -95
  24. astreum/lispeum/special/__init__.py +0 -0
  25. astreum/lispeum/special/definition.py +0 -27
  26. astreum/lispeum/special/list/__init__.py +0 -0
  27. astreum/lispeum/special/list/all.py +0 -32
  28. astreum/lispeum/special/list/any.py +0 -32
  29. astreum/lispeum/special/list/fold.py +0 -29
  30. astreum/lispeum/special/list/get.py +0 -20
  31. astreum/lispeum/special/list/insert.py +0 -23
  32. astreum/lispeum/special/list/map.py +0 -30
  33. astreum/lispeum/special/list/position.py +0 -33
  34. astreum/lispeum/special/list/remove.py +0 -22
  35. astreum/lispeum/special/number/__init__.py +0 -0
  36. astreum/lispeum/special/number/addition.py +0 -0
  37. astreum/lispeum/storage.py +0 -410
  38. astreum/machine/__init__.py +0 -352
  39. astreum/machine/environment.py +0 -4
  40. astreum/machine/error.py +0 -0
  41. astreum-0.2.4.dist-info/RECORD +0 -56
  42. {astreum-0.2.4.dist-info → astreum-0.2.5.dist-info}/WHEEL +0 -0
  43. {astreum-0.2.4.dist-info → astreum-0.2.5.dist-info}/licenses/LICENSE +0 -0
  44. {astreum-0.2.4.dist-info → astreum-0.2.5.dist-info}/top_level.txt +0 -0
@@ -1,280 +0,0 @@
1
- """
2
- Envelope related classes and utilities for Astreum node network.
3
-
4
- Message Structure:
5
- + - - - - - - - +
6
- | Envelope |
7
- + - - - - - - - +
8
- ^
9
- . - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - .
10
- ^ ^ ^ ^
11
- + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - +
12
- | Time | | Encrypted | | Nonce | | Message |
13
- + - - - - - - - + + - - - - - - - + + - - - - - - - + + - - - - - - - +
14
- ^
15
- . - - - - - - - - - - - .
16
- ^ ^
17
- + - - - - - - - + + - - - - - - - +
18
- | Topic | | Body |
19
- + - - - - - - - + + - - - - - - - +
20
-
21
- The Envelope uses a Merkle tree structure with the following leaves:
22
- - Timestamp
23
- - Encrypted flag
24
- - Nonce
25
- - Message bytes
26
-
27
- The root hash of this Merkle tree must have a specified number of leading zero bits,
28
- determined by the difficulty parameter. The nonce is adjusted until this requirement is met.
29
- """
30
-
31
- import struct
32
- import time
33
- import os
34
- from dataclasses import dataclass
35
- from typing import Optional, Tuple, List
36
- from .message import Message, Topic
37
- from astreum.format import encode, decode
38
- from ..utils import hash_data
39
-
40
- @dataclass
41
- class Envelope:
42
- """
43
- Represents an envelope that wraps a message with additional metadata.
44
-
45
- Attributes:
46
- encrypted (bool): True if the message is encrypted, False otherwise
47
- message (Message): The message being sent
48
- nonce (bytes): Nonce for encryption and proof of work
49
- timestamp (int): Time when the envelope was created
50
- """
51
- encrypted: bool
52
- message: Message
53
- nonce: bytes
54
- timestamp: int
55
-
56
- @classmethod
57
- def create(cls, body: bytes, topic: Topic, encrypted: bool = False, difficulty: int = 1) -> 'Envelope':
58
- """
59
- Create a new envelope with the current timestamp and a nonce that satisfies
60
- the given difficulty level using a Merkle tree structure.
61
-
62
- Args:
63
- body (bytes): The message body
64
- topic (Topic): The message topic
65
- encrypted (bool): Whether the message is encrypted
66
- difficulty (int): Number of leading zero bits required in the Merkle root hash
67
-
68
- Returns:
69
- Envelope: A new envelope with a valid nonce
70
- """
71
- timestamp = int(time.time())
72
- message = Message(body=body, topic=topic)
73
-
74
- # Generate a valid nonce for the Merkle tree
75
- nonce = cls._generate_nonce(message, timestamp, encrypted, difficulty)
76
-
77
- return cls(
78
- encrypted=encrypted,
79
- message=message,
80
- nonce=nonce,
81
- timestamp=timestamp
82
- )
83
-
84
- @staticmethod
85
- def _generate_nonce(message: Message, timestamp: int, encrypted: bool, difficulty: int) -> bytes:
86
- """
87
- Generate a nonce that results in a Merkle tree root hash with the specified
88
- number of leading zero bits.
89
-
90
- Args:
91
- message (Message): The message to include in the Merkle tree
92
- timestamp (int): The timestamp to include in the Merkle tree
93
- encrypted (bool): Whether the message is encrypted
94
- difficulty (int): Number of leading zero bits required
95
-
96
- Returns:
97
- bytes: A valid nonce
98
- """
99
- # Prepare the message data
100
- message_data = message.to_bytes()
101
- timestamp_data = struct.pack('!Q', timestamp)
102
- encrypted_flag = b'\x01' if encrypted else b'\x00'
103
-
104
- # Calculate how many bytes need to be zero
105
- zero_bytes = difficulty // 8
106
- # Calculate how many bits in the last byte need to be zero
107
- remaining_bits = difficulty % 8
108
-
109
- # Create a mask for the remaining bits
110
- mask = 0
111
- if remaining_bits > 0:
112
- mask = 0xFF >> remaining_bits
113
-
114
- while True:
115
- # Generate a random nonce
116
- nonce = os.urandom(32)
117
-
118
- # Calculate the Merkle root using the leaves
119
- merkle_root = Envelope._calculate_merkle_root([
120
- timestamp_data,
121
- encrypted_flag,
122
- nonce,
123
- message_data
124
- ])
125
-
126
- # Check if it meets the difficulty requirement
127
- valid = True
128
-
129
- # Check full zero bytes
130
- for i in range(zero_bytes):
131
- if merkle_root[i] != 0:
132
- valid = False
133
- break
134
-
135
- # If we need to check partial bits in a byte
136
- if valid and remaining_bits > 0:
137
- # The next byte should have required number of leading zeros
138
- if (merkle_root[zero_bytes] & (0xFF ^ mask)) != 0:
139
- valid = False
140
-
141
- if valid:
142
- return nonce
143
-
144
- @staticmethod
145
- def _calculate_merkle_root(leaves: List[bytes]) -> bytes:
146
- """
147
- Calculate the Merkle root hash from a list of leaf node data.
148
-
149
- Args:
150
- leaves (List[bytes]): List of leaf node data
151
-
152
- Returns:
153
- bytes: The Merkle root hash
154
- """
155
- if not leaves:
156
- return hash_data(b'')
157
-
158
- if len(leaves) == 1:
159
- return hash_data(leaves[0])
160
-
161
- # Hash all leaf nodes
162
- hashed_leaves = [hash_data(leaf) for leaf in leaves]
163
-
164
- # Build the Merkle tree
165
- while len(hashed_leaves) > 1:
166
- if len(hashed_leaves) % 2 != 0:
167
- # Duplicate the last element if there's an odd number
168
- hashed_leaves.append(hashed_leaves[-1])
169
-
170
- # Combine adjacent pairs and hash them
171
- next_level = []
172
- for i in range(0, len(hashed_leaves), 2):
173
- combined = hashed_leaves[i] + hashed_leaves[i+1]
174
- next_level.append(hash_data(combined))
175
-
176
- hashed_leaves = next_level
177
-
178
- # Return the root hash
179
- return hashed_leaves[0]
180
-
181
- def verify_nonce(self, difficulty: int = 1) -> bool:
182
- """
183
- Verify that the nonce produces a valid Merkle tree root hash
184
- with the specified number of leading zero bits.
185
-
186
- Args:
187
- difficulty (int): Number of leading zero bits required in the root hash
188
-
189
- Returns:
190
- bool: True if the nonce is valid, False otherwise
191
- """
192
- # Prepare the message data
193
- message_data = self.message.to_bytes()
194
- timestamp_data = struct.pack('!Q', self.timestamp)
195
- encrypted_flag = b'\x01' if self.encrypted else b'\x00'
196
-
197
- # Calculate the Merkle root
198
- merkle_root = self._calculate_merkle_root([
199
- timestamp_data,
200
- encrypted_flag,
201
- self.nonce,
202
- message_data
203
- ])
204
-
205
- # Calculate how many bytes need to be zero
206
- zero_bytes = difficulty // 8
207
- # Calculate how many bits in the last byte need to be zero
208
- remaining_bits = difficulty % 8
209
-
210
- # Create a mask for the remaining bits
211
- mask = 0
212
- if remaining_bits > 0:
213
- mask = 0xFF >> remaining_bits
214
-
215
- # Check if it meets the difficulty requirement
216
- valid = True
217
-
218
- # Check full zero bytes
219
- for i in range(zero_bytes):
220
- if merkle_root[i] != 0:
221
- valid = False
222
- break
223
-
224
- # If we need to check partial bits in a byte
225
- if valid and remaining_bits > 0:
226
- # The next byte should have required number of leading zeros
227
- if (merkle_root[zero_bytes] & (0xFF ^ mask)) != 0:
228
- valid = False
229
-
230
- return valid
231
-
232
- def to_bytes(self) -> bytes:
233
- """
234
- Convert this Envelope to bytes.
235
-
236
- Returns:
237
- bytes: Serialized envelope
238
- """
239
- return encode([
240
- struct.pack('!Q', self.timestamp),
241
- b'\x01' if self.encrypted else b'\x00',
242
- self.nonce,
243
- self.message.to_bytes()
244
- ])
245
-
246
- @classmethod
247
- def from_bytes(cls, data: bytes) -> Optional['Envelope']:
248
- """
249
- Create an Envelope from its serialized form.
250
-
251
- Args:
252
- data (bytes): Serialized envelope
253
-
254
- Returns:
255
- Optional[Envelope]: The deserialized envelope, or None if the data is invalid
256
- """
257
- try:
258
- parts = decode(data)
259
- if len(parts) != 4:
260
- return None
261
-
262
- timestamp_data, encrypted_flag, nonce, message_data = parts
263
-
264
- timestamp = struct.unpack('!Q', timestamp_data)[0]
265
- encrypted = encrypted_flag == b'\x01'
266
- nonce = nonce
267
- message = Message.from_bytes(message_data)
268
-
269
- if not message:
270
- return None
271
-
272
- return cls(
273
- encrypted=encrypted,
274
- message=message,
275
- nonce=nonce,
276
- timestamp=timestamp
277
- )
278
- except (ValueError, struct.error) as e:
279
- print(f"Error deserializing envelope: {e}")
280
- return None
@@ -1,110 +0,0 @@
1
- """
2
- Message related classes and utilities for Astreum node network.
3
- """
4
-
5
- import struct
6
- from enum import IntEnum, auto
7
- from dataclasses import dataclass
8
- from typing import Optional
9
- from astreum.format import encode, decode
10
-
11
- class Topic(IntEnum):
12
- """
13
- Enum for different message topics in the Astreum network.
14
- """
15
- PEER_ROUTE = 1
16
- LATEST_BLOCK_REQUEST = 2
17
- LATEST_BLOCK_RESPONSE = 3
18
- GET_BLOCKS = 4
19
- BLOCKS = 5
20
- TRANSACTION = 6
21
- BLOCK_COMMIT = 7
22
- OBJECT_REQUEST = 8
23
- OBJECT_RESPONSE = 9
24
- PING = 10
25
- PONG = 11
26
- ROUTE = 12
27
- ROUTE_REQUEST = 13
28
- LATEST_BLOCK = 14
29
- BLOCK = 15
30
-
31
- def to_bytes(self) -> bytes:
32
- """
33
- Convert this Topic enum value to bytes.
34
-
35
- Returns:
36
- bytes: Single byte representing the topic
37
- """
38
- return struct.pack('!B', self.value)
39
-
40
- @classmethod
41
- def from_bytes(cls, data: bytes) -> Optional['Topic']:
42
- """
43
- Create a Topic from its serialized form.
44
-
45
- Args:
46
- data (bytes): Serialized topic (single byte)
47
-
48
- Returns:
49
- Optional[Topic]: The deserialized topic, or None if the data is invalid
50
- """
51
- if not data or len(data) != 1:
52
- return None
53
-
54
- try:
55
- topic_value = struct.unpack('!B', data)[0]
56
- return cls(topic_value)
57
- except (struct.error, ValueError) as e:
58
- print(f"Error deserializing topic: {e}")
59
- return None
60
-
61
- @dataclass
62
- class Message:
63
- """
64
- Represents a message in the Astreum network.
65
-
66
- Attributes:
67
- body (bytes): The actual content of the message
68
- topic (Topic): The topic/type of the message
69
- """
70
- body: bytes
71
- topic: Topic
72
-
73
- def to_bytes(self) -> bytes:
74
- """
75
- Convert this Message to bytes using bytes_format.
76
-
77
- Returns:
78
- bytes: Serialized message
79
- """
80
- return encode([
81
- self.topic.to_bytes(),
82
- self.body
83
- ])
84
-
85
- @classmethod
86
- def from_bytes(cls, data: bytes) -> Optional['Message']:
87
- """
88
- Create a Message from its serialized form using bytes_format.
89
-
90
- Args:
91
- data (bytes): Serialized message
92
-
93
- Returns:
94
- Optional[Message]: The deserialized message, or None if the data is invalid
95
- """
96
- try:
97
- parts = decode(data)
98
- if len(parts) != 2:
99
- return None
100
-
101
- topic_data, body = parts
102
- topic = Topic.from_bytes(topic_data)
103
-
104
- if not topic:
105
- return None
106
-
107
- return cls(body=body, topic=topic)
108
- except (ValueError, struct.error) as e:
109
- print(f"Error deserializing message: {e}")
110
- return None
@@ -1,174 +0,0 @@
1
- """
2
- Peer management for Astreum node's Kademlia-style network.
3
- """
4
-
5
- from dataclasses import dataclass
6
- from typing import Tuple, Optional
7
- import time
8
-
9
- @dataclass
10
- class Peer:
11
- """
12
- Represents a peer in the Astreum network.
13
-
14
- Attributes:
15
- address (Tuple[str, int]): The network address (host, port)
16
- public_key (bytes): The public key of the peer
17
- difficulty (int): The proof-of-work difficulty required for this peer
18
- last_seen (int): Timestamp when the peer was last seen
19
- failed_attempts (int): Number of consecutive failed communication attempts
20
- """
21
- address: Tuple[str, int]
22
- public_key: bytes
23
- difficulty: int = 1
24
- last_seen: int = 0
25
- failed_attempts: int = 0
26
-
27
- def __post_init__(self):
28
- if self.last_seen == 0:
29
- self.last_seen = int(time.time())
30
-
31
- def update_last_seen(self):
32
- """Update the last seen timestamp to current time."""
33
- self.last_seen = int(time.time())
34
- self.failed_attempts = 0
35
-
36
- def register_failed_attempt(self):
37
- """Register a failed communication attempt with this peer."""
38
- self.failed_attempts += 1
39
-
40
- def is_active(self, max_age: int = 3600, max_failures: int = 3) -> bool:
41
- """
42
- Check if the peer is considered active.
43
-
44
- Args:
45
- max_age (int): Maximum age in seconds before peer is considered inactive
46
- max_failures (int): Maximum consecutive failures before peer is considered inactive
47
-
48
- Returns:
49
- bool: True if the peer is active, False otherwise
50
- """
51
- current_time = int(time.time())
52
- return (
53
- (current_time - self.last_seen) <= max_age and
54
- self.failed_attempts < max_failures
55
- )
56
-
57
- class PeerManager:
58
- """
59
- Manages a collection of peers and provides utilities for peer operations.
60
- """
61
-
62
- def __init__(self, our_node_id: bytes):
63
- """
64
- Initialize a peer manager.
65
-
66
- Args:
67
- our_node_id (bytes): Our node's unique identifier
68
- """
69
- self.our_node_id = our_node_id
70
- self.peers_by_id = {} # node_id: Peer
71
- self.peers_by_address = {} # address: Peer
72
-
73
- def add_or_update_peer(self, address: Tuple[str, int], public_key: bytes) -> Peer:
74
- """
75
- Add a new peer or update an existing one.
76
-
77
- Args:
78
- address (Tuple[str, int]): Network address (host, port)
79
- public_key (bytes): The public key of the peer
80
-
81
- Returns:
82
- Peer: The added or updated peer
83
- """
84
- # Check if we already know this peer by ID
85
- if public_key in self.peers_by_id:
86
- peer = self.peers_by_id[public_key]
87
- # Update address if changed
88
- if peer.address != address:
89
- if peer.address in self.peers_by_address:
90
- del self.peers_by_address[peer.address]
91
- peer.address = address
92
- self.peers_by_address[address] = peer
93
- peer.update_last_seen()
94
- return peer
95
-
96
- # Check if address exists with different ID
97
- if address in self.peers_by_address:
98
- old_peer = self.peers_by_address[address]
99
- if old_peer.public_key in self.peers_by_id:
100
- del self.peers_by_id[old_peer.public_key]
101
-
102
- # Create and add new peer
103
- peer = Peer(address=address, public_key=public_key)
104
- self.peers_by_id[public_key] = peer
105
- self.peers_by_address[address] = peer
106
- return peer
107
-
108
- def get_peer_by_id(self, public_key: bytes) -> Optional[Peer]:
109
- """
110
- Get a peer by its public key.
111
-
112
- Args:
113
- public_key (bytes): The peer's public key
114
-
115
- Returns:
116
- Optional[Peer]: The peer if found, None otherwise
117
- """
118
- return self.peers_by_id.get(public_key)
119
-
120
- def get_peer_by_address(self, address: Tuple[str, int]) -> Optional[Peer]:
121
- """
122
- Get a peer by its network address.
123
-
124
- Args:
125
- address (Tuple[str, int]): The peer's network address
126
-
127
- Returns:
128
- Optional[Peer]: The peer if found, None otherwise
129
- """
130
- return self.peers_by_address.get(address)
131
-
132
- def remove_peer(self, public_key: bytes) -> bool:
133
- """
134
- Remove a peer by its public key.
135
-
136
- Args:
137
- public_key (bytes): The peer's public key
138
-
139
- Returns:
140
- bool: True if the peer was removed, False otherwise
141
- """
142
- if public_key in self.peers_by_id:
143
- peer = self.peers_by_id[public_key]
144
- del self.peers_by_id[public_key]
145
- if peer.address in self.peers_by_address:
146
- del self.peers_by_address[peer.address]
147
- return True
148
- return False
149
-
150
- def calculate_distance(self, public_key: bytes) -> int:
151
- """
152
- Calculate the XOR distance between our node ID and the given public key.
153
-
154
- In Kademlia, peers are organized into buckets based on the XOR distance.
155
- The bucket index (0-255) represents the position of the first bit that differs.
156
-
157
- Args:
158
- public_key (bytes): The remote node's public key
159
-
160
- Returns:
161
- int: XOR distance (0-255)
162
- """
163
- # Assuming IDs are 256-bit (32 bytes)
164
- for i in range(min(len(self.our_node_id), len(public_key))):
165
- xor_byte = self.our_node_id[i] ^ public_key[i]
166
- if xor_byte == 0:
167
- continue
168
-
169
- # Find the most significant bit
170
- for bit in range(7, -1, -1):
171
- if (xor_byte >> bit) & 1:
172
- return (i * 8) + (7 - bit)
173
-
174
- return 255 # Default maximum distance