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

@@ -0,0 +1,42 @@
1
+ from typing import Tuple
2
+
3
+
4
+ def address_str_to_host_and_port(address: str) -> Tuple[str, int]:
5
+ """Parse `host:port` (or `[ipv6]:port`) into a tuple."""
6
+ addr = address.strip()
7
+ if not addr:
8
+ raise ValueError("address cannot be empty")
9
+
10
+ host: str
11
+ port_str: str
12
+
13
+ if addr.startswith('['):
14
+ end = addr.find(']')
15
+ if end == -1:
16
+ raise ValueError("missing closing ']' in IPv6 address")
17
+ host = addr[1:end]
18
+ remainder = addr[end + 1 :]
19
+ if not remainder.startswith(':'):
20
+ raise ValueError("missing port separator after IPv6 address")
21
+ port_str = remainder[1:]
22
+ else:
23
+ if ':' not in addr:
24
+ raise ValueError("address must contain ':' separating host and port")
25
+ host, port_str = addr.rsplit(':', 1)
26
+
27
+ host = host.strip()
28
+ if not host:
29
+ raise ValueError("host cannot be empty")
30
+ port_str = port_str.strip()
31
+ if not port_str:
32
+ raise ValueError("port cannot be empty")
33
+
34
+ try:
35
+ port = int(port_str, 10)
36
+ except ValueError as exc:
37
+ raise ValueError(f"invalid port number: {port_str}") from exc
38
+
39
+ if not (0 < port < 65536):
40
+ raise ValueError(f"port out of range: {port}")
41
+
42
+ return host, port
@@ -1,6 +1,7 @@
1
1
  from .block import Block
2
2
  from .chain import Chain
3
3
  from .fork import Fork
4
+ from .receipt import Receipt
4
5
  from .transaction import Transaction
5
6
  from .setup import consensus_setup
6
7
 
@@ -9,6 +10,7 @@ __all__ = [
9
10
  "Block",
10
11
  "Chain",
11
12
  "Fork",
13
+ "Receipt",
12
14
  "Transaction",
13
15
  "consensus_setup",
14
16
  ]
@@ -1,7 +1,12 @@
1
-
2
- from typing import Callable, List, Optional, Tuple
1
+
2
+ from typing import Callable, List, Optional, Tuple, TYPE_CHECKING
3
3
 
4
4
  from .._storage.atom import Atom, ZERO32
5
+
6
+ if TYPE_CHECKING:
7
+ from .._storage.patricia import PatriciaTrie
8
+ from .transaction import Transaction
9
+ from .receipt import Receipt
5
10
  from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
6
11
  from cryptography.exceptions import InvalidSignature
7
12
 
@@ -22,16 +27,6 @@ def _be_bytes_to_int(b: Optional[bytes]) -> int:
22
27
  return int.from_bytes(b, "big")
23
28
 
24
29
 
25
- def _make_typed_bytes(data: bytes) -> Tuple[bytes, List[Atom]]:
26
- """Create a typed 'byte' atom for the given payload.
27
-
28
- Returns (object_id, atoms_in_dependency_order).
29
- """
30
- val = Atom.from_data(data=data)
31
- typ = Atom.from_data(data=b"byte", next_hash=val.object_id())
32
- return typ.object_id(), [val, typ]
33
-
34
-
35
30
  def _make_list(child_ids: List[bytes]) -> Tuple[bytes, List[Atom]]:
36
31
  """Create a typed 'list' atom for child object ids.
37
32
 
@@ -65,15 +60,16 @@ class Block:
65
60
  signature_atom = Atom(data=<signature-bytes>)
66
61
 
67
62
  Details order in body_list:
68
- 0: previous_block (bytes)
63
+ 0: previous_block_hash (bytes)
69
64
  1: number (int → big-endian bytes)
70
65
  2: timestamp (int → big-endian bytes)
71
66
  3: accounts_hash (bytes)
72
67
  4: transactions_total_fees (int → big-endian bytes)
73
- 5: transactions_root_hash (bytes)
74
- 6: delay_difficulty (int → big-endian bytes)
75
- 7: delay_output (bytes)
76
- 8: validator_public_key (bytes)
68
+ 5: transactions_hash (bytes)
69
+ 6: receipts_hash (bytes)
70
+ 7: delay_difficulty (int → big-endian bytes)
71
+ 8: delay_output (bytes)
72
+ 9: validator_public_key (bytes)
77
73
 
78
74
  Notes:
79
75
  - "body tree" is represented here by the body_list id (self.body_hash), not
@@ -85,14 +81,16 @@ class Block:
85
81
 
86
82
  # essential identifiers
87
83
  hash: bytes
88
- previous_block: bytes
84
+ previous_block_hash: bytes
85
+ previous_block: Optional["Block"]
89
86
 
90
87
  # block details
91
88
  number: Optional[int]
92
89
  timestamp: Optional[int]
93
90
  accounts_hash: Optional[bytes]
94
91
  transactions_total_fees: Optional[int]
95
- transactions_root_hash: Optional[bytes]
92
+ transactions_hash: Optional[bytes]
93
+ receipts_hash: Optional[bytes]
96
94
  delay_difficulty: Optional[int]
97
95
  delay_output: Optional[bytes]
98
96
  validator_public_key: Optional[bytes]
@@ -101,33 +99,48 @@ class Block:
101
99
  body_hash: Optional[bytes]
102
100
  signature: Optional[bytes]
103
101
 
102
+ # structures
103
+ accounts: Optional["PatriciaTrie"]
104
+ transactions: Optional[List["Transaction"]]
105
+ receipts: Optional[List["Receipt"]]
106
+
107
+
108
+
104
109
  def __init__(self) -> None:
105
110
  # defaults for safety
106
111
  self.hash = b""
107
- self.previous_block = ZERO32
112
+ self.previous_block_hash = ZERO32
113
+ self.previous_block = None
108
114
  self.number = None
109
115
  self.timestamp = None
110
116
  self.accounts_hash = None
111
117
  self.transactions_total_fees = None
112
- self.transactions_root_hash = None
118
+ self.transactions_hash = None
119
+ self.receipts_hash = None
113
120
  self.delay_difficulty = None
114
121
  self.delay_output = None
115
122
  self.validator_public_key = None
116
123
  self.body_hash = None
117
124
  self.signature = None
125
+ self.accounts = None
126
+ self.transactions = None
127
+ self.receipts = None
118
128
 
119
129
  def to_atom(self) -> Tuple[bytes, List[Atom]]:
120
- # Build body details as typed bytes, in defined order
130
+ # Build body details as direct byte atoms, in defined order
121
131
  details_ids: List[bytes] = []
122
132
  atoms_acc: List[Atom] = []
123
133
 
124
134
  def _emit(detail_bytes: bytes) -> None:
125
- oid, ats = _make_typed_bytes(detail_bytes)
126
- details_ids.append(oid)
127
- atoms_acc.extend(ats)
128
-
129
- # 0: previous_block
130
- _emit(self.previous_block or ZERO32)
135
+ atom = Atom.from_data(data=detail_bytes)
136
+ details_ids.append(atom.object_id())
137
+ atoms_acc.append(atom)
138
+
139
+ # 0: previous_block_hash
140
+ prev_hash = self.previous_block_hash or (self.previous_block.hash if self.previous_block else b"")
141
+ prev_hash = prev_hash or ZERO32
142
+ self.previous_block_hash = prev_hash
143
+ _emit(prev_hash)
131
144
  # 1: number
132
145
  _emit(_int_to_be_bytes(self.number))
133
146
  # 2: timestamp
@@ -136,13 +149,15 @@ class Block:
136
149
  _emit(self.accounts_hash or b"")
137
150
  # 4: transactions_total_fees
138
151
  _emit(_int_to_be_bytes(self.transactions_total_fees))
139
- # 5: transactions_root_hash
140
- _emit(self.transactions_root_hash or b"")
141
- # 6: delay_difficulty
152
+ # 5: transactions_hash
153
+ _emit(self.transactions_hash or b"")
154
+ # 6: receipts_hash
155
+ _emit(self.receipts_hash or b"")
156
+ # 7: delay_difficulty
142
157
  _emit(_int_to_be_bytes(self.delay_difficulty))
143
- # 7: delay_output
158
+ # 8: delay_output
144
159
  _emit(self.delay_output or b"")
145
- # 8: validator_public_key
160
+ # 9: validator_public_key
146
161
  _emit(self.validator_public_key or b"")
147
162
 
148
163
  # Build body list
@@ -206,23 +221,20 @@ class Block:
206
221
  raise ValueError("malformed body (value)")
207
222
  cur_elem_id = body_val.next
208
223
 
209
- def _read_typed_bytes(elem_id: bytes) -> bytes:
224
+ def _read_detail_bytes(elem_id: bytes) -> bytes:
210
225
  elem = storage_get(elem_id)
211
226
  if elem is None:
212
227
  return b""
213
228
  child_id = elem.data
214
- typ = storage_get(child_id)
215
- if typ is None or typ.data != b"byte":
216
- return b""
217
- val = storage_get(typ.next)
218
- return val.data if val is not None else b""
229
+ detail = storage_get(child_id)
230
+ return detail.data if detail is not None else b""
219
231
 
220
232
  details: List[bytes] = []
221
- # We read up to 9 fields if present
222
- for _ in range(9):
233
+ # We read up to 10 fields if present
234
+ for _ in range(10):
223
235
  if not cur_elem_id:
224
236
  break
225
- b = _read_typed_bytes(cur_elem_id)
237
+ b = _read_detail_bytes(cur_elem_id)
226
238
  details.append(b)
227
239
  nxt = storage_get(cur_elem_id)
228
240
  cur_elem_id = nxt.next if nxt is not None else b""
@@ -233,21 +245,23 @@ class Block:
233
245
 
234
246
  # Map details back per the defined order
235
247
  get = lambda i: details[i] if i < len(details) else b""
236
- b.previous_block = get(0) or ZERO32
248
+ b.previous_block_hash = get(0) or ZERO32
249
+ b.previous_block = None
237
250
  b.number = _be_bytes_to_int(get(1))
238
251
  b.timestamp = _be_bytes_to_int(get(2))
239
252
  b.accounts_hash = get(3) or None
240
253
  b.transactions_total_fees = _be_bytes_to_int(get(4))
241
- b.transactions_root_hash = get(5) or None
242
- b.delay_difficulty = _be_bytes_to_int(get(6))
243
- b.delay_output = get(7) or None
244
- b.validator_public_key = get(8) or None
254
+ b.transactions_hash = get(5) or None
255
+ b.receipts_hash = get(6) or None
256
+ b.delay_difficulty = _be_bytes_to_int(get(7))
257
+ b.delay_output = get(8) or None
258
+ b.validator_public_key = get(9) or None
245
259
 
246
- # 4) Parse signature if present (supports raw or typed 'byte' atom)
260
+ # 4) Parse signature if present (supports raw or typed 'bytes' atom)
247
261
  if sig_atom_id is not None:
248
262
  sa = storage_get(sig_atom_id)
249
263
  if sa is not None:
250
- if sa.data == b"byte":
264
+ if sa.data == b"bytes":
251
265
  sval = storage_get(sa.next)
252
266
  b.signature = sval.data if sval is not None else b""
253
267
  else:
@@ -282,13 +296,24 @@ class Block:
282
296
  raise ValueError("invalid signature") from e
283
297
 
284
298
  # 2) Timestamp monotonicity against previous block
285
- if self.previous_block and self.previous_block != ZERO32:
299
+ prev_ts: Optional[int] = None
300
+ prev_hash = self.previous_block_hash or ZERO32
301
+
302
+ if self.previous_block is not None:
303
+ prev_ts = int(self.previous_block.timestamp or 0)
304
+ prev_hash = self.previous_block.hash or prev_hash or ZERO32
305
+
306
+ if prev_hash and prev_hash != ZERO32 and prev_ts is None:
286
307
  # If previous block cannot be loaded, treat as unverifiable, not malicious
287
308
  try:
288
- prev = Block.from_atom(storage_get, self.previous_block)
309
+ prev = Block.from_atom(storage_get, prev_hash)
289
310
  except Exception:
290
311
  return False
291
312
  prev_ts = int(prev.timestamp or 0)
313
+
314
+ if prev_hash and prev_hash != ZERO32:
315
+ if prev_ts is None:
316
+ return False
292
317
  cur_ts = int(self.timestamp or 0)
293
318
  if cur_ts < prev_ts + 1:
294
319
  raise ValueError("timestamp must be at least prev+1")
@@ -54,10 +54,13 @@ class Chain:
54
54
  self.malicious_block_hash = getattr(blk, "hash", None)
55
55
  raise
56
56
 
57
- if blk.previous_block == ZERO32:
57
+ prev_hash = blk.previous_block_hash if hasattr(blk, "previous_block_hash") else ZERO32
58
+ if prev_hash == ZERO32:
58
59
  break
59
60
  # Move to previous block using cache-aware loader
60
- blk = load_block(blk.previous_block)
61
+ prev_blk = load_block(prev_hash)
62
+ blk.previous_block = prev_blk # cache the object for any downstream use
63
+ blk = prev_blk
61
64
 
62
65
  self.validated_upto_block = blk
63
66
  return blk
@@ -92,7 +92,9 @@ class Fork:
92
92
  self.validated_upto = blk.hash
93
93
  return True
94
94
 
95
- nxt = load_block(blk.previous_block)
95
+ prev_hash = blk.previous_block_hash if hasattr(blk, "previous_block_hash") else ZERO32
96
+ nxt = load_block(prev_hash)
96
97
  if nxt is None:
97
98
  return False
99
+ blk.previous_block = nxt # cache for future use
98
100
  blk = nxt
@@ -0,0 +1,167 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Callable, List, Optional, Tuple
5
+
6
+ from .._storage.atom import Atom, ZERO32
7
+
8
+ STATUS_SUCCESS = 0
9
+ STATUS_FAILED = 1
10
+
11
+
12
+ def _int_to_be_bytes(value: Optional[int]) -> bytes:
13
+ if value is None:
14
+ return b""
15
+ value = int(value)
16
+ if value == 0:
17
+ return b"\x00"
18
+ size = (value.bit_length() + 7) // 8
19
+ return value.to_bytes(size, "big")
20
+
21
+
22
+ def _be_bytes_to_int(data: Optional[bytes]) -> int:
23
+ if not data:
24
+ return 0
25
+ return int.from_bytes(data, "big")
26
+
27
+
28
+ def _make_list(child_ids: List[bytes]) -> Tuple[bytes, List[Atom]]:
29
+ atoms: List[Atom] = []
30
+ next_hash = ZERO32
31
+ elements: List[Atom] = []
32
+ for child_id in reversed(child_ids):
33
+ elem = Atom.from_data(data=child_id, next_hash=next_hash)
34
+ next_hash = elem.object_id()
35
+ elements.append(elem)
36
+ elements.reverse()
37
+ list_value = Atom.from_data(data=len(child_ids).to_bytes(8, "little"), next_hash=next_hash)
38
+ list_type = Atom.from_data(data=b"list", next_hash=list_value.object_id())
39
+ atoms.extend(elements)
40
+ atoms.append(list_value)
41
+ atoms.append(list_type)
42
+ return list_type.object_id(), atoms
43
+
44
+
45
+ def _read_list_entries(
46
+ storage_get: Callable[[bytes], Optional[Atom]], start: bytes
47
+ ) -> List[bytes]:
48
+ entries: List[bytes] = []
49
+ current = start if start and start != ZERO32 else b""
50
+ while current:
51
+ elem = storage_get(current)
52
+ if elem is None:
53
+ break
54
+ entries.append(elem.data)
55
+ nxt = elem.next
56
+ current = nxt if nxt and nxt != ZERO32 else b""
57
+ return entries
58
+
59
+
60
+ def _read_payload_bytes(
61
+ storage_get: Callable[[bytes], Optional[Atom]], object_id: bytes
62
+ ) -> bytes:
63
+ if not object_id or object_id == ZERO32:
64
+ return b""
65
+ atom = storage_get(object_id)
66
+ if atom is None:
67
+ return b""
68
+ if atom.data == b"bytes":
69
+ value_atom = storage_get(atom.next)
70
+ return value_atom.data if value_atom is not None else b""
71
+ return atom.data
72
+
73
+
74
+ @dataclass
75
+ class Receipt:
76
+ transaction_hash: bytes = ZERO32
77
+ cost: int = 0
78
+ logs: bytes = b""
79
+ status: int = 0
80
+
81
+ def to_atom(self) -> Tuple[bytes, List[Atom]]:
82
+ """Serialise the receipt into Atom storage."""
83
+ if self.status not in (STATUS_SUCCESS, STATUS_FAILED):
84
+ raise ValueError("unsupported receipt status")
85
+
86
+ atoms: List[Atom] = []
87
+
88
+ tx_atom = Atom.from_data(data=bytes(self.transaction_hash))
89
+ status_atom = Atom.from_data(data=_int_to_be_bytes(self.status))
90
+ cost_atom = Atom.from_data(data=_int_to_be_bytes(self.cost))
91
+ logs_atom = Atom.from_data(data=bytes(self.logs))
92
+
93
+ atoms.extend([tx_atom, status_atom, cost_atom, logs_atom])
94
+
95
+ body_child_ids = [
96
+ tx_atom.object_id(),
97
+ status_atom.object_id(),
98
+ cost_atom.object_id(),
99
+ logs_atom.object_id(),
100
+ ]
101
+ body_id, body_atoms = _make_list(body_child_ids)
102
+ atoms.extend(body_atoms)
103
+
104
+ type_atom = Atom.from_data(data=b"receipt", next_hash=body_id)
105
+ atoms.append(type_atom)
106
+
107
+ top_list_id, top_atoms = _make_list([type_atom.object_id(), body_id])
108
+ atoms.extend(top_atoms)
109
+
110
+ return top_list_id, atoms
111
+
112
+ @classmethod
113
+ def from_atom(
114
+ cls,
115
+ storage_get: Callable[[bytes], Optional[Atom]],
116
+ receipt_id: bytes,
117
+ ) -> Receipt:
118
+ """Materialise a Receipt from Atom storage."""
119
+ top_type_atom = storage_get(receipt_id)
120
+ if top_type_atom is None or top_type_atom.data != b"list":
121
+ raise ValueError("not a receipt (outer list missing)")
122
+
123
+ top_value_atom = storage_get(top_type_atom.next)
124
+ if top_value_atom is None:
125
+ raise ValueError("malformed receipt (outer value missing)")
126
+
127
+ head = top_value_atom.next
128
+ first_elem = storage_get(head) if head else None
129
+ if first_elem is None:
130
+ raise ValueError("malformed receipt (type element missing)")
131
+
132
+ type_atom_id = first_elem.data
133
+ type_atom = storage_get(type_atom_id)
134
+ if type_atom is None or type_atom.data != b"receipt":
135
+ raise ValueError("not a receipt (type mismatch)")
136
+
137
+ remainder_entries = _read_list_entries(storage_get, first_elem.next)
138
+ if not remainder_entries:
139
+ raise ValueError("malformed receipt (body missing)")
140
+ body_id = remainder_entries[0]
141
+
142
+ body_type_atom = storage_get(body_id)
143
+ if body_type_atom is None or body_type_atom.data != b"list":
144
+ raise ValueError("malformed receipt body (type)")
145
+
146
+ body_value_atom = storage_get(body_type_atom.next)
147
+ if body_value_atom is None:
148
+ raise ValueError("malformed receipt body (value)")
149
+
150
+ body_entries = _read_list_entries(storage_get, body_value_atom.next)
151
+ if len(body_entries) < 4:
152
+ body_entries.extend([ZERO32] * (4 - len(body_entries)))
153
+
154
+ transaction_hash_bytes = _read_payload_bytes(storage_get, body_entries[0])
155
+ status_bytes = _read_payload_bytes(storage_get, body_entries[1])
156
+ cost_bytes = _read_payload_bytes(storage_get, body_entries[2])
157
+ logs_bytes = _read_payload_bytes(storage_get, body_entries[3])
158
+ status_value = _be_bytes_to_int(status_bytes)
159
+ if status_value not in (STATUS_SUCCESS, STATUS_FAILED):
160
+ raise ValueError("unsupported receipt status")
161
+
162
+ return cls(
163
+ transaction_hash=transaction_hash_bytes or ZERO32,
164
+ cost=_be_bytes_to_int(cost_bytes),
165
+ logs=logs_bytes,
166
+ status=status_value,
167
+ )
@@ -8,7 +8,7 @@ from typing import Any, Dict, Optional, Tuple
8
8
  from .block import Block
9
9
  from .chain import Chain
10
10
  from .fork import Fork
11
- from .transaction import Transaction
11
+ from .transaction import Transaction, apply_transaction
12
12
  from .._storage.atom import ZERO32, Atom
13
13
 
14
14
 
@@ -17,11 +17,6 @@ def current_validator(node: Any) -> bytes:
17
17
  raise NotImplementedError("current_validator must be implemented by the host node")
18
18
 
19
19
 
20
- def apply_transaction(node: Any, block: object, transaction_hash: bytes) -> None:
21
- """Apply transaction to the candidate block. Override downstream."""
22
- pass
23
-
24
-
25
20
  def consensus_setup(node: Any) -> None:
26
21
  # Shared state
27
22
  node.validation_lock = getattr(node, "validation_lock", threading.RLock())
@@ -194,16 +189,17 @@ def consensus_setup(node: Any) -> None:
194
189
  break
195
190
 
196
191
  # Start workers as daemons
197
- node.validation_discovery_thread = threading.Thread(
198
- target=_discovery_worker, daemon=True, name="validation-discovery"
192
+ node.consensus_discovery_thread = threading.Thread(
193
+ target=_discovery_worker, daemon=True, name="consensus-discovery"
199
194
  )
200
- node.validation_verify_thread = threading.Thread(
201
- target=_verify_worker, daemon=True, name="validation-verify"
195
+ node.consensus_verify_thread = threading.Thread(
196
+ target=_verify_worker, daemon=True, name="consensus-verify"
202
197
  )
203
- node.validation_worker_thread = threading.Thread(
204
- target=_validation_worker, daemon=True, name="validation-worker"
198
+ node.consensus_validation_thread = threading.Thread(
199
+ target=_validation_worker, daemon=True, name="consensus-validation"
205
200
  )
206
- node.validation_discovery_thread.start()
207
- node.validation_verify_thread.start()
201
+ node.consensus_discovery_thread.start()
202
+ node.consensus_verify_thread.start()
208
203
  if getattr(node, "validation_secret_key", None):
209
- node.validation_worker_thread.start()
204
+ node.consensus_validation_thread.start()
205
+
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
- from typing import Callable, List, Optional, Tuple
4
+ from typing import Any, Callable, List, Optional, Tuple
5
5
 
6
6
  from .._storage.atom import Atom, ZERO32
7
7
 
@@ -22,12 +22,6 @@ def _be_bytes_to_int(data: Optional[bytes]) -> int:
22
22
  return int.from_bytes(data, "big")
23
23
 
24
24
 
25
- def _make_typed_bytes(payload: bytes) -> Tuple[bytes, List[Atom]]:
26
- value_atom = Atom.from_data(data=payload)
27
- type_atom = Atom.from_data(data=b"byte", next_hash=value_atom.object_id())
28
- return type_atom.object_id(), [value_atom, type_atom]
29
-
30
-
31
25
  def _make_list(child_ids: List[bytes]) -> Tuple[bytes, List[Atom]]:
32
26
  atoms: List[Atom] = []
33
27
  next_hash = ZERO32
@@ -61,9 +55,9 @@ class Transaction:
61
55
  acc: List[Atom] = []
62
56
 
63
57
  def emit(payload: bytes) -> None:
64
- oid, atoms = _make_typed_bytes(payload)
65
- body_child_ids.append(oid)
66
- acc.extend(atoms)
58
+ atom = Atom.from_data(data=payload)
59
+ body_child_ids.append(atom.object_id())
60
+ acc.append(atom)
67
61
 
68
62
  emit(_int_to_be_bytes(self.amount))
69
63
  emit(_int_to_be_bytes(self.counter))
@@ -141,23 +135,20 @@ class Transaction:
141
135
  if len(body_entries) < 5:
142
136
  body_entries.extend([ZERO32] * (5 - len(body_entries)))
143
137
 
144
- def read_typed_bytes(entry_id: bytes) -> bytes:
145
- if not entry_id or entry_id == ZERO32:
138
+ def read_detail_bytes(entry_id: bytes) -> bytes:
139
+ if entry_id == ZERO32:
146
140
  return b""
147
141
  elem = storage_get(entry_id)
148
142
  if elem is None:
149
143
  return b""
150
- type_atom = storage_get(elem.data)
151
- if type_atom is None or type_atom.data != b"byte":
152
- return b""
153
- value_atom = storage_get(type_atom.next)
154
- return value_atom.data if value_atom is not None else b""
144
+ detail_atom = storage_get(elem.data)
145
+ return detail_atom.data if detail_atom is not None else b""
155
146
 
156
- amount_bytes = read_typed_bytes(body_entries[0])
157
- counter_bytes = read_typed_bytes(body_entries[1])
158
- data_bytes = read_typed_bytes(body_entries[2])
159
- recipient_bytes = read_typed_bytes(body_entries[3])
160
- sender_bytes = read_typed_bytes(body_entries[4])
147
+ amount_bytes = read_detail_bytes(body_entries[0])
148
+ counter_bytes = read_detail_bytes(body_entries[1])
149
+ data_bytes = read_detail_bytes(body_entries[2])
150
+ recipient_bytes = read_detail_bytes(body_entries[3])
151
+ sender_bytes = read_detail_bytes(body_entries[4])
161
152
 
162
153
  signature_atom = storage_get(signature_atom_id)
163
154
  signature_bytes = signature_atom.data if signature_atom is not None else b""
@@ -170,3 +161,8 @@ class Transaction:
170
161
  sender=sender_bytes,
171
162
  signature=signature_bytes,
172
163
  )
164
+
165
+
166
+ def apply_transaction(node: Any, block: object, transaction_hash: bytes) -> None:
167
+ """Apply transaction to the candidate block. Override downstream."""
168
+ pass