astreum 0.2.15__py3-none-any.whl → 0.2.17__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 CHANGED
@@ -1,10 +1,51 @@
1
- from ..format import encode, decode
1
+ from __future__ import annotations
2
+
3
+ from typing import List, Dict, Any, Optional, Union
2
4
  from ..crypto import ed25519
3
- import blake3
5
+ from .merkle import MerkleTree
6
+
7
+ # Constants for integer field names
8
+ _INT_FIELDS = {
9
+ "delay_difficulty",
10
+ "number",
11
+ "timestamp",
12
+ "transaction_limit",
13
+ "transactions_total_fees",
14
+ }
4
15
 
5
16
  class Block:
6
17
  def __init__(
7
18
  self,
19
+ block_hash: bytes,
20
+ body_tree: Optional[MerkleTree] = None,
21
+ signature: Optional[bytes] = None,
22
+ ) -> None:
23
+ self._block_hash = block_hash
24
+ self._body_tree = body_tree
25
+ self._signature = signature
26
+ # store field names in alphabetical order for consistent indexing
27
+ self._field_names = [
28
+ "accounts_hash",
29
+ "delay_difficulty",
30
+ "delay_output",
31
+ "delay_proof",
32
+ "number",
33
+ "prev_block_hash",
34
+ "timestamp",
35
+ "transaction_limit",
36
+ "transactions_root_hash",
37
+ "transactions_total_fees",
38
+ "validator_pk",
39
+ ]
40
+
41
+ @property
42
+ def hash(self) -> bytes:
43
+ """Return the block hash (Merkle root of body_root || signature)."""
44
+ return self._block_hash
45
+
46
+ @classmethod
47
+ def create(
48
+ cls,
8
49
  number: int,
9
50
  prev_block_hash: bytes,
10
51
  timestamp: int,
@@ -12,87 +53,78 @@ class Block:
12
53
  transactions_total_fees: int,
13
54
  transaction_limit: int,
14
55
  transactions_root_hash: bytes,
15
- vdf_difficulty: int,
16
- vdf_output: bytes,
17
- vdf_proof: bytes,
56
+ delay_difficulty: int,
57
+ delay_output: bytes,
58
+ delay_proof: bytes,
18
59
  validator_pk: bytes,
19
60
  signature: bytes,
20
- ) -> None:
21
- self.accounts_hash = accounts_hash
22
- self.number = int(number)
23
- self.prev_block_hash = prev_block_hash
24
- self.timestamp = int(timestamp)
25
- self.transactions_total_fees = int(transactions_total_fees)
26
- self.transaction_limit = int(transaction_limit)
27
- self.transactions_root_hash = transactions_root_hash
28
- self.validator_pk = validator_pk
29
- self.vdf_difficulty = int(vdf_difficulty)
30
- self.vdf_output = vdf_output
31
- self.vdf_proof = vdf_proof
32
- self.signature = signature
33
- self.body_hash = self._compute_body_hash()
61
+ ) -> Block:
62
+ """Build a new block by hashing the provided fields into Merkle trees."""
63
+ # map fields by name
64
+ field_map: Dict[str, Any] = {
65
+ "accounts_hash": accounts_hash,
66
+ "delay_difficulty": delay_difficulty,
67
+ "delay_output": delay_output,
68
+ "delay_proof": delay_proof,
69
+ "number": number,
70
+ "prev_block_hash": prev_block_hash,
71
+ "timestamp": timestamp,
72
+ "transaction_limit": transaction_limit,
73
+ "transactions_root_hash": transactions_root_hash,
74
+ "transactions_total_fees": transactions_total_fees,
75
+ "validator_pk": validator_pk,
76
+ }
77
+
78
+ leaves: List[bytes] = []
79
+ for name in sorted(field_map):
80
+ v = field_map[name]
81
+ if isinstance(v, bytes):
82
+ leaf_bytes = v
83
+ elif isinstance(v, int):
84
+ length = (v.bit_length() + 7) // 8 or 1
85
+ leaf_bytes = v.to_bytes(length, "big")
86
+ else:
87
+ raise TypeError(f"Unsupported field type for '{name}': {type(v)}")
88
+ leaves.append(leaf_bytes)
89
+
90
+ body_tree = MerkleTree.from_leaves(leaves)
91
+ body_root = body_tree.root_hash
92
+ top_tree = MerkleTree.from_leaves([body_root, signature])
93
+ block_hash = top_tree.root_hash
34
94
 
35
- def _body_fields_without_sig(self) -> list:
36
- return [
37
- self.accounts_hash,
38
- self.number,
39
- self.prev_block_hash,
40
- self.timestamp,
41
- self.transactions_total_fees,
42
- self.transaction_limit,
43
- self.transactions_root_hash,
44
- self.validator_pk,
45
- self.vdf_difficulty,
46
- self.vdf_output,
47
- self.vdf_proof,
48
- ]
95
+ return cls(block_hash, body_tree, signature)
49
96
 
50
- def _compute_body_hash(self) -> bytes:
51
- return blake3.blake3(encode(self._body_fields_without_sig())).digest()
52
-
53
- def to_bytes(self) -> bytes:
54
- return encode(self._body_fields_without_sig() + [self.signature])
97
+ def get_body_hash(self) -> bytes:
98
+ """Return the Merkle root of the body fields."""
99
+ if not self._body_tree:
100
+ raise ValueError("Body tree not available for this block instance.")
101
+ return self._body_tree.root_hash
55
102
 
56
- @classmethod
57
- def from_bytes(cls, blob: bytes) -> "Block":
58
- (
59
- accounts_hash,
60
- number,
61
- prev_block_hash,
62
- timestamp,
63
- transactions_total_fees,
64
- transaction_limit,
65
- transactions_root_hash,
66
- validator_pk,
67
- vdf_difficulty,
68
- vdf_output,
69
- vdf_proof,
70
- signature
71
- ) = decode(blob)
72
- return cls(
73
- number=int(number),
74
- prev_block_hash=prev_block_hash,
75
- timestamp=int(timestamp),
76
- accounts_hash=accounts_hash,
77
- transactions_total_fees=int(transactions_total_fees),
78
- transaction_limit=int(transaction_limit),
79
- transactions_root_hash=transactions_root_hash,
80
- vdf_difficulty=int(vdf_difficulty),
81
- vdf_output=vdf_output,
82
- vdf_proof=vdf_proof,
83
- validator_pk=validator_pk,
84
- signature=signature,
85
- )
103
+ def get_signature(self) -> bytes:
104
+ """Return the block's signature leaf."""
105
+ if self._signature is None:
106
+ raise ValueError("Signature not available for this block instance.")
107
+ return self._signature
86
108
 
87
- @property
88
- def hash(self) -> bytes:
89
- return blake3.blake3(self.body_hash + self.signature).digest()
109
+ def get_field(self, name: str) -> Union[int, bytes]:
110
+ """Query a single body field by name, returning an int or bytes."""
111
+ if name not in self._field_names:
112
+ raise KeyError(f"Unknown field: {name}")
113
+ if not self._body_tree:
114
+ raise ValueError("Body tree not available for field queries.")
115
+ idx = self._field_names.index(name)
116
+ leaf_bytes = self._body_tree.leaves[idx]
117
+ if name in _INT_FIELDS:
118
+ return int.from_bytes(leaf_bytes, "big")
119
+ return leaf_bytes
90
120
 
91
121
  def verify_block_signature(self) -> bool:
122
+ """Verify the block's Ed25519 signature against its body root."""
123
+ pub = ed25519.Ed25519PublicKey.from_public_bytes(
124
+ self.get_field("validator_pk")
125
+ )
92
126
  try:
93
- pub = ed25519.Ed25519PublicKey.from_public_bytes(self.validator_pk)
94
- pub.verify(self.signature, self.body_hash)
127
+ pub.verify(self.get_signature(), self.get_body_hash())
95
128
  return True
96
129
  except Exception:
97
130
  return False
98
-
astreum/models/merkle.py CHANGED
@@ -217,6 +217,7 @@ class MerkleTree:
217
217
  leaf.value = value
218
218
  self._invalidate(leaf)
219
219
  new_hash = leaf.hash()
220
+ self.nodes[new_hash] = leaf
220
221
 
221
222
  # bubble updated hashes
222
223
  for parent, old_hash, went_right in reversed(stack):
@@ -1,8 +1,19 @@
1
1
  import blake3
2
2
  from typing import Callable, Dict, List, Optional, Tuple
3
- from astreum import format
3
+ from ..format import encode, decode
4
4
 
5
5
  class PatriciaNode:
6
+ """
7
+ A node in a compressed-key Patricia trie.
8
+
9
+ Attributes:
10
+ key_len (int): Number of bits in the `key` prefix that are meaningful.
11
+ key (bytes): The MSB-aligned bit prefix (zero-padded in last byte).
12
+ value (Optional[bytes]): Stored payload (None for internal nodes).
13
+ child_0 (Optional[bytes]): Hash pointer for next-bit == 0.
14
+ child_1 (Optional[bytes]): Hash pointer for next-bit == 1.
15
+ """
16
+
6
17
  def __init__(
7
18
  self,
8
19
  key_len: int,
@@ -16,33 +27,64 @@ class PatriciaNode:
16
27
  self.value = value
17
28
  self.child_0 = child_0
18
29
  self.child_1 = child_1
19
- self._hash: bytes | None = None
30
+ self._hash: Optional[bytes] = None
20
31
 
21
32
  def to_bytes(self) -> bytes:
22
- return format.encode([self.key_len, self.key, self.value, self.child_0, self.child_1])
33
+ """
34
+ Serialize node fields to bytes using the shared encode format.
35
+ - key_len in a single byte.
36
+ - None pointers/values as empty bytes.
37
+ """
38
+ key_len_b = self.key_len.to_bytes(1, "big")
39
+ val_b = self.value if self.value is not None else b""
40
+ c0_b = self.child_0 if self.child_0 is not None else b""
41
+ c1_b = self.child_1 if self.child_1 is not None else b""
42
+ return encode([key_len_b, self.key, val_b, c0_b, c1_b])
23
43
 
24
44
  @classmethod
25
45
  def from_bytes(cls, blob: bytes) -> "PatriciaNode":
26
- key_len, key, value, child_0, child_1 = format.decode(blob)
46
+ """
47
+ Deserialize a blob produced by to_bytes() back into a PatriciaNode.
48
+ Empty bytes are converted back to None for value/children.
49
+ """
50
+ key_len_b, key, val_b, c0_b, c1_b = decode(blob)
51
+ key_len = key_len_b[0]
52
+ value = val_b if val_b else None
53
+ child_0 = c0_b if c0_b else None
54
+ child_1 = c1_b if c1_b else None
27
55
  return cls(key_len, key, value, child_0, child_1)
28
-
56
+
29
57
  def hash(self) -> bytes:
58
+ """
59
+ Compute and cache the BLAKE3 hash of this node's serialized form.
60
+ """
30
61
  if self._hash is None:
31
62
  self._hash = blake3.blake3(self.to_bytes()).digest()
32
63
  return self._hash
33
64
 
34
65
  class PatriciaTrie:
66
+ """
67
+ A compressed-key Patricia trie supporting get and put.
68
+ """
69
+
35
70
  def __init__(
36
71
  self,
37
72
  node_get: Callable[[bytes], Optional[bytes]],
38
73
  root_hash: Optional[bytes] = None,
39
74
  ) -> None:
75
+ """
76
+ :param node_get: function mapping node-hash -> serialized node bytes (or None)
77
+ :param root_hash: optional hash of existing root node
78
+ """
40
79
  self._node_get = node_get
41
80
  self.nodes: Dict[bytes, PatriciaNode] = {}
42
- self.root_hash: Optional[bytes] = root_hash
81
+ self.root_hash = root_hash
43
82
 
44
83
  @staticmethod
45
84
  def _bit(buf: bytes, idx: int) -> bool:
85
+ """
86
+ Return the bit at position `idx` (MSB-first) from `buf`.
87
+ """
46
88
  byte_i, offset = divmod(idx, 8)
47
89
  return ((buf[byte_i] >> (7 - offset)) & 1) == 1
48
90
 
@@ -54,15 +96,22 @@ class PatriciaTrie:
54
96
  key: bytes,
55
97
  key_bit_offset: int,
56
98
  ) -> bool:
57
- if key_bit_offset + prefix_len > len(key) * 8:
99
+ """
100
+ Check whether the `prefix_len` bits of `prefix` match
101
+ bits in `key` starting at `key_bit_offset`.
102
+ """
103
+ total_bits = len(key) * 8
104
+ if key_bit_offset + prefix_len > total_bits:
58
105
  return False
59
-
60
106
  for i in range(prefix_len):
61
107
  if cls._bit(prefix, i) != cls._bit(key, key_bit_offset + i):
62
108
  return False
63
109
  return True
64
110
 
65
111
  def _fetch(self, h: bytes) -> Optional[PatriciaNode]:
112
+ """
113
+ Fetch a node by hash, using in-memory cache then external node_get.
114
+ """
66
115
  node = self.nodes.get(h)
67
116
  if node is None:
68
117
  raw = self._node_get(h)
@@ -72,8 +121,11 @@ class PatriciaTrie:
72
121
  self.nodes[h] = node
73
122
  return node
74
123
 
75
- def get(self, key: bytes) -> Optional["PatriciaNode"]:
76
- """Return the node that stores *key*, or ``None`` if absent."""
124
+ def get(self, key: bytes) -> Optional[bytes]:
125
+ """
126
+ Return the stored value for `key`, or None if absent.
127
+ """
128
+ # Empty trie?
77
129
  if self.root_hash is None:
78
130
  return None
79
131
 
@@ -81,40 +133,42 @@ class PatriciaTrie:
81
133
  if node is None:
82
134
  return None
83
135
 
84
- key_pos = 0
136
+ key_pos = 0 # bit offset into key
85
137
 
86
138
  while node is not None:
87
- # 1️⃣ Verify that this node's (possibly sub‑byte) prefix matches.
139
+ # 1) Check that this node's prefix matches the key here
88
140
  if not self._match_prefix(node.key, node.key_len, key, key_pos):
89
141
  return None
90
142
  key_pos += node.key_len
91
143
 
92
- # 2️⃣ If every bit of *key* has been matched, success only if the
93
- # node actually stores a value.
144
+ # 2) If we've consumed all bits of the search key:
94
145
  if key_pos == len(key) * 8:
95
- return node if node.value is not None else None
146
+ # Return value only if this node actually stores one
147
+ return node.value
96
148
 
97
- # 3️⃣ Decide which branch to follow using the next bit of *key*.
149
+ # 3) Decide which branch to follow via next bit
98
150
  try:
99
151
  next_bit = self._bit(key, key_pos)
100
- except IndexError: # key ended prematurely
152
+ except IndexError:
101
153
  return None
102
154
 
103
155
  child_hash = node.child_1 if next_bit else node.child_0
104
- if child_hash is None: # dead end – key not present
105
- return None
156
+ if child_hash is None:
157
+ return None # dead end
106
158
 
107
- # 4️⃣ Fetch the child node via unified helper.
159
+ # 4) Fetch child and continue descent
108
160
  node = self._fetch(child_hash)
109
- if node is None: # dangling pointer
110
- return None
161
+ if node is None:
162
+ return None # dangling pointer
111
163
 
112
- key_pos += 1 # we just consumed one routing bit
164
+ key_pos += 1 # consumed routing bit
113
165
 
114
166
  return None
115
-
167
+
116
168
  def put(self, key: bytes, value: bytes) -> None:
117
- """Insert or update ``key`` with ``value`` in‑place."""
169
+ """
170
+ Insert or update `key` with `value` in-place.
171
+ """
118
172
  total_bits = len(key) * 8
119
173
 
120
174
  # S1 – Empty trie → create root leaf
@@ -126,7 +180,7 @@ class PatriciaTrie:
126
180
  # S2 – traversal bookkeeping
127
181
  stack: List[Tuple[PatriciaNode, bytes, int]] = [] # (parent, parent_hash, dir_bit)
128
182
  node = self._fetch(self.root_hash)
129
- assert node is not None # root must exist now
183
+ assert node is not None
130
184
  key_pos = 0
131
185
 
132
186
  # S4 – main descent loop
@@ -144,6 +198,7 @@ class PatriciaTrie:
144
198
  self._invalidate_hash(node)
145
199
  node.value = value
146
200
  new_hash = node.hash()
201
+ self.nodes[new_hash] = node
147
202
  self._bubble(stack, new_hash)
148
203
  return
149
204
 
@@ -162,9 +217,11 @@ class PatriciaTrie:
162
217
  # 4.8 – fetch child and continue
163
218
  node = self._fetch(child_hash)
164
219
  if node is None:
165
- # Dangling pointer treat as append missing leaf
166
- self._append_leaf(stack[-1][0], next_bit, key, key_pos, value, stack[:-1])
220
+ # Dangling pointer: treat as missing child
221
+ parent, _, _ = stack[-1]
222
+ self._append_leaf(parent, next_bit, key, key_pos, value, stack[:-1])
167
223
  return
224
+
168
225
  key_pos += 1 # consumed routing bit
169
226
 
170
227
  def _append_leaf(
@@ -176,18 +233,20 @@ class PatriciaTrie:
176
233
  value: bytes,
177
234
  stack: List[Tuple[PatriciaNode, bytes, int]],
178
235
  ) -> None:
179
- # key_pos points to routing bit; leaf stores the *rest* after that bit
236
+ # key_pos points to routing bit; leaf stores the rest after that bit
180
237
  tail_len = len(key) * 8 - (key_pos + 1)
181
238
  tail_bits, tail_len = self._bit_slice(key, key_pos + 1, tail_len)
182
239
  leaf = self._make_node(tail_bits, tail_len, value, None, None)
183
240
 
184
- # attach
241
+ # attach to parent
185
242
  if dir_bit:
186
243
  parent.child_1 = leaf.hash()
187
244
  else:
188
245
  parent.child_0 = leaf.hash()
246
+
189
247
  self._invalidate_hash(parent)
190
248
  new_parent_hash = parent.hash()
249
+ self.nodes[new_parent_hash] = parent
191
250
  self._bubble(stack, new_parent_hash)
192
251
 
193
252
  def _split_and_insert(
@@ -198,52 +257,121 @@ class PatriciaTrie:
198
257
  key_pos: int,
199
258
  value: bytes,
200
259
  ) -> None:
201
- """Split ``node`` at first divergent bit and insert new leaf for *key*."""
202
- # Compute LCP between node.key and remaining key bits
260
+ # ➊—find longest-common-prefix (lcp) as before
203
261
  max_lcp = min(node.key_len, len(key) * 8 - key_pos)
204
262
  lcp = 0
205
263
  while lcp < max_lcp and self._bit(node.key, lcp) == self._bit(key, key_pos + lcp):
206
264
  lcp += 1
207
265
 
208
- # Common prefix bits new internal node
266
+ # divergence bit values (taken **before** we mutate node.key)
267
+ old_div_bit = self._bit(node.key, lcp)
268
+ new_div_bit = self._bit(key, key_pos + lcp)
269
+
270
+ # ➋—internal node that holds the common prefix
209
271
  common_bits, common_len = self._bit_slice(node.key, 0, lcp)
210
272
  internal = self._make_node(common_bits, common_len, None, None, None)
211
273
 
212
- # Trim old node prefix
213
- old_suffix_bits, old_suffix_len = self._bit_slice(node.key, lcp, node.key_len - lcp)
274
+ # ➌—trim the *existing* node’s prefix **after** the divergence bit
275
+ old_suffix_bits, old_suffix_len = self._bit_slice(
276
+ node.key,
277
+ lcp + 1, # start *after* divergence bit
278
+ node.key_len - lcp - 1 # may be zero
279
+ )
214
280
  node.key = old_suffix_bits
215
281
  node.key_len = old_suffix_len
216
- self._invalidate_hash(node) # will be re‑hashed when attached
217
- old_div_bit = self._bit(node.key, 0) if old_suffix_len > 0 else False
282
+ self._invalidate_hash(node)
283
+ new_node_hash = node.hash()
284
+ self.nodes[new_node_hash] = node
218
285
 
219
- # New key leaf
220
- new_key_tail_len = len(key) * 8 - (key_pos + lcp + 1)
221
- new_tail_bits, new_tail_len = self._bit_slice(key, key_pos + lcp + 1, new_key_tail_len)
286
+ # ➍—new leaf for the key being inserted (unchanged)
287
+ new_tail_len = len(key) * 8 - (key_pos + lcp + 1)
288
+ new_tail_bits, _ = self._bit_slice(key, key_pos + lcp + 1, new_tail_len)
222
289
  leaf = self._make_node(new_tail_bits, new_tail_len, value, None, None)
223
- new_div_bit = self._bit(key, key_pos + lcp)
224
290
 
225
- # Attach children to internal
291
+ # ➎—hang the two children off the internal node
226
292
  if old_div_bit:
227
- internal.child_1 = node.hash()
228
- internal.child_0 = leaf.hash() if not new_div_bit else internal.child_0
293
+ internal.child_1 = new_node_hash
294
+ internal.child_0 = leaf.hash()
229
295
  else:
230
- internal.child_0 = node.hash()
231
- internal.child_1 = leaf.hash() if new_div_bit else internal.child_1
296
+ internal.child_0 = new_node_hash
297
+ internal.child_1 = leaf.hash()
298
+
299
+ # ➏—rehash up to the root (unchanged)
232
300
  self._invalidate_hash(internal)
233
301
  internal_hash = internal.hash()
302
+ self.nodes[internal_hash] = internal
234
303
 
235
- # Rewire parent link or set as root
236
304
  if not stack:
237
305
  self.root_hash = internal_hash
238
306
  return
239
307
 
240
- parent, parent_old_hash, dir_bit = stack.pop()
308
+ parent, _, dir_bit = stack.pop()
241
309
  if dir_bit == 0:
242
310
  parent.child_0 = internal_hash
243
311
  else:
244
312
  parent.child_1 = internal_hash
245
313
  self._invalidate_hash(parent)
246
- parent_new_hash = parent.hash()
247
- self._bubble(stack, parent_new_hash)
314
+ self._bubble(stack, parent.hash())
315
+
316
+
317
+ def _make_node(
318
+ self,
319
+ prefix_bits: bytes,
320
+ prefix_len: int,
321
+ value: Optional[bytes],
322
+ child0: Optional[bytes],
323
+ child1: Optional[bytes],
324
+ ) -> PatriciaNode:
325
+ node = PatriciaNode(prefix_len, prefix_bits, value, child0, child1)
326
+ self.nodes[node.hash()] = node
327
+ return node
248
328
 
329
+ def _invalidate_hash(self, node: PatriciaNode) -> None:
330
+ """Clear cached hash so next .hash() recomputes."""
331
+ node._hash = None # type: ignore
249
332
 
333
+ def _bubble(
334
+ self,
335
+ stack: List[Tuple[PatriciaNode, bytes, int]],
336
+ new_hash: bytes
337
+ ) -> None:
338
+ """
339
+ Propagate updated child-hash `new_hash` up the ancestor stack,
340
+ rebasing each parent's pointer, invalidating and re-hashing.
341
+ """
342
+ while stack:
343
+ parent, old_hash, dir_bit = stack.pop()
344
+ if dir_bit == 0:
345
+ parent.child_0 = new_hash
346
+ else:
347
+ parent.child_1 = new_hash
348
+ self._invalidate_hash(parent)
349
+ new_hash = parent.hash()
350
+ self.nodes[new_hash] = parent
351
+ self.root_hash = new_hash
352
+
353
+ def _bit_slice(
354
+ self,
355
+ buf: bytes,
356
+ start_bit: int,
357
+ length: int
358
+ ) -> tuple[bytes, int]:
359
+ """
360
+ Extract `length` bits from `buf` starting at `start_bit` (MSB-first),
361
+ returning (bytes, bit_len) with zero-padding.
362
+ """
363
+ if length == 0:
364
+ return b"", 0
365
+
366
+ total = int.from_bytes(buf, "big")
367
+ bits_in_buf = len(buf) * 8
368
+
369
+ # shift so slice ends at LSB
370
+ shift = bits_in_buf - (start_bit + length)
371
+ slice_int = (total >> shift) & ((1 << length) - 1)
372
+
373
+ # left-align to MSB of first byte
374
+ pad = (8 - (length % 8)) % 8
375
+ slice_int <<= pad
376
+ byte_len = (length + 7) // 8
377
+ return slice_int.to_bytes(byte_len, "big"), length
@@ -74,10 +74,5 @@ class Transaction:
74
74
  self.nonce,
75
75
  ])
76
76
 
77
- def __eq__(self, other: "Transaction") -> bool:
78
- if not isinstance(other, Transaction):
79
- return NotImplemented
80
- return self.tx_hash == other.tx_hash
81
-
82
77
  def __hash__(self) -> int:
83
78
  return int.from_bytes(self.tx_hash, 'big')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: astreum
3
- Version: 0.2.15
3
+ Version: 0.2.17
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
@@ -10,12 +10,12 @@ astreum/lispeum/__init__.py,sha256=K-NDzIjtIsXzC9X7lnYvlvIaVxjFcY7WNsgLIE3DH3U,5
10
10
  astreum/lispeum/parser.py,sha256=jQRzZYvBuSg8t_bxsbt1-WcHaR_LPveHNX7Qlxhaw-M,1165
11
11
  astreum/lispeum/tokenizer.py,sha256=J-I7MEd0r2ZoVqxvRPlu-Afe2ZdM0tKXXhf1R4SxYTo,1429
12
12
  astreum/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- astreum/models/block.py,sha256=NKYyxL6_BrtXRgcgIrnkYsobX0Z_bGqQT-fQ_09zEOo,3226
14
- astreum/models/merkle.py,sha256=ceH4yJlt82XDTXe46hyoU88QIaGVOsVDsBZeDnOJYv8,8590
15
- astreum/models/patricia.py,sha256=D7UVU4b6Yvn2_McI35VoMEbpqwR8OmZon5LGoUSRADo,8913
16
- astreum/models/transaction.py,sha256=Vu0cfmh80S31nEbxyJfv1dk9_zqtgGNyMdhlM0uQF4E,2611
17
- astreum-0.2.15.dist-info/licenses/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
18
- astreum-0.2.15.dist-info/METADATA,sha256=7Nv0BCooioBjPEUhtTB1SdaNceBCgGf2iPZY2BLSx1E,5478
19
- astreum-0.2.15.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
- astreum-0.2.15.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
21
- astreum-0.2.15.dist-info/RECORD,,
13
+ astreum/models/block.py,sha256=YIY3qLneFM7OooY29khLDEs3NIEA7y8VhlXitflkxTo,4564
14
+ astreum/models/merkle.py,sha256=E850dITZmuQS2LmQt2yA_luy0a3QjHgSbL2ibQAmYJc,8627
15
+ astreum/models/patricia.py,sha256=WtfwVufZazFTOjVDnSDI0I3ghetm3GBzAwqNQevOlJ8,12974
16
+ astreum/models/transaction.py,sha256=ReCmLf9j1WLm2VUTtZh4cAVEyJodk17bVPp3t-UyNnI,2427
17
+ astreum-0.2.17.dist-info/licenses/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
18
+ astreum-0.2.17.dist-info/METADATA,sha256=7BooYG3o3YQyZ2Y0hOsAtD-5ykT5nvfWnT3koADQ4gM,5478
19
+ astreum-0.2.17.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
+ astreum-0.2.17.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
21
+ astreum-0.2.17.dist-info/RECORD,,