astreum 0.2.41__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.
astreum/_node.py CHANGED
@@ -1,10 +1,23 @@
1
1
  from __future__ import annotations
2
- from typing import Dict, Optional
2
+ from pathlib import Path
3
+ from typing import Dict, List, Optional
3
4
  import uuid
4
5
  import threading
5
6
 
6
- from src.astreum._storage.atom import Atom
7
- from src.astreum._lispeum import Env, Expr, low_eval, parse, tokenize, ParseError
7
+ from astreum._storage.atom import AtomKind
8
+
9
+ from ._storage import Atom, storage_setup
10
+ from ._lispeum import Env, Expr, Meter, low_eval, parse, tokenize, ParseError
11
+ from .utils.logging import logging_setup
12
+
13
+ __all__ = [
14
+ "Node",
15
+ "Env",
16
+ "Expr",
17
+ "Meter",
18
+ "parse",
19
+ "tokenize",
20
+ ]
8
21
 
9
22
  def bytes_touched(*vals: bytes) -> int:
10
23
  """For metering: how many bytes were manipulated (max of operands)."""
@@ -12,9 +25,10 @@ def bytes_touched(*vals: bytes) -> int:
12
25
 
13
26
  class Node:
14
27
  def __init__(self, config: dict):
28
+ self.logger = logging_setup(config)
29
+ self.logger.info("Starting Astreum Node")
15
30
  # Storage Setup
16
- self.in_memory_storage: Dict[bytes, Atom] = {}
17
- self.in_memory_storage_lock = threading.RLock()
31
+ storage_setup(self, config=config)
18
32
  # Lispeum Setup
19
33
  self.environments: Dict[uuid.UUID, Env] = {}
20
34
  self.machine_environments_lock = threading.RLock()
@@ -25,11 +39,13 @@ class Node:
25
39
  communication_setup(node=self, config=config)
26
40
  except Exception:
27
41
  pass
28
- try:
29
- from astreum._consensus import consensus_setup # type: ignore
30
- consensus_setup(node=self)
42
+ try:
43
+ from astreum._consensus import consensus_setup # type: ignore
44
+ consensus_setup(node=self, config=config)
31
45
  except Exception:
32
46
  pass
47
+
48
+
33
49
 
34
50
  # ---- Env helpers ----
35
51
  def env_get(self, env_id: uuid.UUID, key: bytes) -> Optional[Expr]:
@@ -49,10 +65,134 @@ class Node:
49
65
  return True
50
66
 
51
67
  # Storage
52
- def _local_get(self, key: bytes) -> Optional[Atom]:
53
- with self.in_memory_storage_lock:
54
- return self.in_memory_storage.get(key)
68
+ def _hot_storage_get(self, key: bytes) -> Optional[Atom]:
69
+ atom = self.hot_storage.get(key)
70
+ if atom is not None:
71
+ self.hot_storage_hits[key] = self.hot_storage_hits.get(key, 0) + 1
72
+ return atom
73
+
74
+ def _hot_storage_set(self, key: bytes, value: Atom) -> bool:
75
+ """Store atom in hot storage without exceeding the configured limit."""
76
+ projected = self.hot_storage_size + value.size
77
+ if projected > self.hot_storage_limit:
78
+ return False
79
+
80
+ self.hot_storage[key] = value
81
+ self.hot_storage_size = projected
82
+ return True
83
+
84
+ def _network_get(self, key: bytes) -> Optional[Atom]:
85
+ # locate storage provider
86
+ # query storage provider
87
+ return None
88
+
89
+ def storage_get(self, key: bytes) -> Optional[Atom]:
90
+ """Retrieve an Atom by checking local storage first, then the network."""
91
+ atom = self._hot_storage_get(key)
92
+ if atom is not None:
93
+ return atom
94
+ atom = self._cold_storage_get(key)
95
+ if atom is not None:
96
+ return atom
97
+ return self._network_get(key)
98
+
99
+ def _cold_storage_get(self, key: bytes) -> Optional[Atom]:
100
+ """Read an atom from the cold storage directory if configured."""
101
+ if not self.cold_storage_path:
102
+ return None
103
+ filename = f"{key.hex().upper()}.bin"
104
+ file_path = Path(self.cold_storage_path) / filename
105
+ try:
106
+ data = file_path.read_bytes()
107
+ except FileNotFoundError:
108
+ return None
109
+ except OSError:
110
+ return None
111
+ try:
112
+ return Atom.from_bytes(data)
113
+ except ValueError:
114
+ return None
115
+
116
+ def _cold_storage_set(self, atom: Atom) -> None:
117
+ """Persist an atom into the cold storage directory if it already exists."""
118
+ if not self.cold_storage_path:
119
+ return
120
+ atom_bytes = atom.to_bytes()
121
+ projected = self.cold_storage_size + len(atom_bytes)
122
+ if self.cold_storage_limit and projected > self.cold_storage_limit:
123
+ return
124
+ directory = Path(self.cold_storage_path)
125
+ if not directory.exists():
126
+ return
127
+ atom_id = atom.object_id()
128
+ filename = f"{atom_id.hex().upper()}.bin"
129
+ file_path = directory / filename
130
+ try:
131
+ file_path.write_bytes(atom_bytes)
132
+ self.cold_storage_size = projected
133
+ except OSError:
134
+ return
135
+
136
+ def _network_set(self, atom: Atom) -> None:
137
+ """Advertise an atom to the closest known peer so they can fetch it from us."""
138
+ try:
139
+ from ._communication.message import Message, MessageTopic
140
+ except Exception:
141
+ return
142
+
143
+ atom_id = atom.object_id()
144
+ try:
145
+ closest_peer = self.peer_route.closest_peer_for_hash(atom_id)
146
+ except Exception:
147
+ return
148
+ if closest_peer is None or closest_peer.address is None:
149
+ return
150
+ target_addr = closest_peer.address
151
+
152
+ try:
153
+ provider_ip, provider_port = self.incoming_socket.getsockname()[:2]
154
+ except Exception:
155
+ return
156
+
157
+ provider_str = f"{provider_ip}:{int(provider_port)}"
158
+ try:
159
+ provider_bytes = provider_str.encode("utf-8")
160
+ except Exception:
161
+ return
162
+
163
+ payload = atom_id + provider_bytes
164
+ message = Message(topic=MessageTopic.STORAGE_REQUEST, content=payload)
165
+ self.outgoing_queue.put((message.to_bytes(), target_addr))
166
+
167
+ def get_expr_list_from_storage(self, key: bytes) -> Optional["ListExpr"]:
168
+ atoms = self.get_atom_list_from_storage(root_hash=key)
169
+ if atoms is None:
170
+ return None
171
+
172
+ expr_list = []
173
+ for atom in atoms:
174
+ match atom.kind:
175
+ case AtomKind.SYMBOL:
176
+ expr_list.append(Expr.Symbol(atom.data))
177
+ case AtomKind.BYTES:
178
+ expr_list.append(Expr.Bytes(atom.data))
179
+ case AtomKind.LIST:
180
+ expr_list.append(Expr.ListExpr([
181
+ Expr.Bytes(atom.data),
182
+ Expr.Symbol("ref")
183
+ ]))
55
184
 
56
- def _local_set(self, key: bytes, value: Atom) -> None:
57
- with self.in_memory_storage_lock:
58
- self.in_memory_storage[key] = value
185
+ expr_list.reverse()
186
+ return Expr.ListExpr(expr_list)
187
+
188
+ def get_atom_list_from_storage(self, root_hash: bytes) -> Optional[List["Atom"]]:
189
+ next_id: Optional[bytes] = root_hash
190
+ atom_list: List["Atom"] = []
191
+ while next_id:
192
+ elem = self.storage_get(key=next_id)
193
+ if elem:
194
+ atom_list.append(elem)
195
+ next_id = elem.next
196
+ else:
197
+ return None
198
+ return atom_list
@@ -1,5 +1,7 @@
1
- from .atom import Atom
2
-
3
- __all__ = [
4
- "Atom",
5
- ]
1
+ from .atom import Atom
2
+ from .setup import storage_setup
3
+
4
+ __all__ = [
5
+ "Atom",
6
+ "storage_setup",
7
+ ]
astreum/_storage/atom.py CHANGED
@@ -1,9 +1,9 @@
1
1
 
2
2
 
3
- from typing import List, Optional, Tuple
3
+ from enum import IntEnum
4
+ from typing import List, Optional, Tuple
4
5
 
5
- from .._lispeum.expression import Expr
6
- from blake3 import blake3
6
+ from blake3 import blake3
7
7
 
8
8
  ZERO32 = b"\x00"*32
9
9
 
@@ -13,105 +13,97 @@ def u64_le(n: int) -> bytes:
13
13
  def hash_bytes(b: bytes) -> bytes:
14
14
  return blake3(b).digest()
15
15
 
16
- class Atom:
16
+ class AtomKind(IntEnum):
17
+ SYMBOL = 0
18
+ BYTES = 1
19
+ LIST = 2
20
+
21
+
22
+ class Atom:
17
23
  data: bytes
18
24
  next: bytes
19
25
  size: int
20
26
 
21
- def __init__(self, data: bytes, next: bytes = ZERO32, size: Optional[int] = None):
22
- self.data = data
23
- self.next = next
24
- self.size = len(data) if size is None else size
25
-
26
- @staticmethod
27
- def from_data(data: bytes, next_hash: bytes = ZERO32) -> "Atom":
28
- return Atom(data=data, next=next_hash, size=len(data))
29
-
30
- @staticmethod
31
- def object_id_from_parts(data_hash: bytes, next_hash: bytes, size: int) -> bytes:
32
- return blake3(data_hash + next_hash + u64_le(size)).digest()
33
-
34
- def data_hash(self) -> bytes:
35
- return hash_bytes(self.data)
36
-
37
- def object_id(self) -> bytes:
38
- return self.object_id_from_parts(self.data_hash(), self.next, self.size)
39
-
40
- @staticmethod
41
- def verify_metadata(object_id: bytes, size: int, next_hash: bytes, data_hash: bytes) -> bool:
42
- return object_id == blake3(data_hash + next_hash + u64_le(size)).digest()
43
-
44
- def to_bytes(self) -> bytes:
45
- return self.next + self.data
46
-
47
- @staticmethod
48
- def from_bytes(buf: bytes) -> "Atom":
49
- if len(buf) < len(ZERO32):
50
- raise ValueError("buffer too short for Atom header")
51
- next_hash = buf[:len(ZERO32)]
52
- data = buf[len(ZERO32):]
53
- return Atom(data=data, next=next_hash, size=len(data))
54
-
55
- def expr_to_atoms(e: Expr) -> Tuple[bytes, List[Atom]]:
56
- def symbol(value: str) -> Tuple[bytes, List[Atom]]:
57
- val = value.encode("utf-8")
58
- val_atom = Atom.from_data(data=val)
59
- typ_atom = Atom.from_data(b"symbol", val_atom.object_id())
60
- return typ_atom.object_id(), [val_atom, typ_atom]
61
-
62
- def bytes(data: bytes) -> Tuple[bytes, List[Atom]]:
63
- val_atom = Atom.from_data(data=data)
64
- typ_atom = Atom.from_data(b"byte", val_atom.object_id())
65
- return typ_atom.object_id(), [val_atom, typ_atom]
66
-
67
- def err(topic: str, origin: Optional[Expr]) -> Tuple[bytes, List[Atom]]:
68
- topic_bytes = topic.encode("utf-8")
69
- topic_atom = Atom.from_data(data=topic_bytes)
70
- typ_atom = Atom.from_data(data=b"error", next_hash=topic_atom.object_id())
71
- return typ_atom.object_id(), [topic_atom, typ_atom]
72
-
73
- def lst(items: List[Expr]) -> Tuple[bytes, List[Atom]]:
74
- acc: List[Atom] = []
75
- child_hashes: List[bytes] = []
76
- for it in items:
77
- h, atoms = expr_to_atoms(it)
78
- acc.extend(atoms)
79
- child_hashes.append(h)
80
- next_hash = ZERO32
81
- elem_atoms: List[Atom] = []
82
- for h in reversed(child_hashes):
83
- a = Atom.from_data(h, next_hash)
84
- next_hash = a.object_id()
85
- elem_atoms.append(a)
86
- elem_atoms.reverse()
87
- head = next_hash
88
- val_atom = Atom.from_data(data=u64_le(len(items)), next_hash=head)
89
- typ_atom = Atom.from_data(data=b"list", next_hash=val_atom.object_id())
90
- return typ_atom.object_id(), acc + elem_atoms + [val_atom, typ_atom]
91
-
92
- if isinstance(e, Expr.Symbol):
93
- return symbol(e.value)
94
- if isinstance(e, Expr.Bytes):
95
- return bytes(e.value)
96
- if isinstance(e, Expr.Error):
97
- return err(e.topic, e.origin)
98
- if isinstance(e, Expr.ListExpr):
99
- return lst(e.elements)
100
- raise TypeError("unknown Expr variant")
27
+ def __init__(
28
+ self,
29
+ data: bytes,
30
+ next: bytes = ZERO32,
31
+ size: Optional[int] = None,
32
+ kind: AtomKind = AtomKind.BYTES,
33
+ ):
34
+ self.data = data
35
+ self.next = next
36
+ self.size = len(data) if size is None else size
37
+ self.kind = kind
101
38
 
39
+ @staticmethod
40
+ def from_data(
41
+ data: bytes,
42
+ next_hash: bytes = ZERO32,
43
+ kind: AtomKind = AtomKind.BYTES,
44
+ ) -> "Atom":
45
+ return Atom(data=data, next=next_hash, size=len(data), kind=kind)
46
+
47
+ def generate_id(self) -> bytes:
48
+ """Compute the object id using this atom's metadata."""
49
+ kind_bytes = int(self.kind).to_bytes(1, "little", signed=False)
50
+ return blake3(
51
+ kind_bytes + self.data_hash() + self.next + u64_le(self.size)
52
+ ).digest()
102
53
 
103
- def bytes_list_to_atoms(values: List[bytes]) -> Tuple[bytes, List[Atom]]:
104
- """Build a forward-ordered linked list of atoms from byte payloads.
54
+ def data_hash(self) -> bytes:
55
+ return hash_bytes(self.data)
105
56
 
106
- Returns the head object's hash (ZERO32 if no values) and the atoms created.
107
- """
108
- next_hash = ZERO32
109
- atoms: List[Atom] = []
57
+ def object_id(self) -> bytes:
58
+ return self.generate_id()
110
59
 
111
- for value in reversed(values):
112
- atom = Atom.from_data(data=bytes(value), next_hash=next_hash)
113
- atoms.append(atom)
114
- next_hash = atom.object_id()
60
+ @staticmethod
61
+ def verify_metadata(
62
+ object_id: bytes,
63
+ size: int,
64
+ next_hash: bytes,
65
+ data_hash: bytes,
66
+ kind: AtomKind,
67
+ ) -> bool:
68
+ kind_bytes = int(kind).to_bytes(1, "little", signed=False)
69
+ expected = blake3(kind_bytes + data_hash + next_hash + u64_le(size)).digest()
70
+ return object_id == expected
71
+
72
+ def to_bytes(self) -> bytes:
73
+ """Serialize as next-hash + kind byte + payload."""
74
+ kind_byte = int(self.kind).to_bytes(1, "little", signed=False)
75
+ return self.next + kind_byte + self.data
115
76
 
116
- atoms.reverse()
117
- return (next_hash if values else ZERO32), atoms
77
+ @staticmethod
78
+ def from_bytes(buf: bytes) -> "Atom":
79
+ header_len = len(ZERO32)
80
+ if len(buf) < header_len + 1:
81
+ raise ValueError("buffer too short for Atom header")
82
+ next_hash = buf[:header_len]
83
+ kind_value = buf[header_len]
84
+ data = buf[header_len + 1 :]
85
+ try:
86
+ kind = AtomKind(kind_value)
87
+ except ValueError as exc:
88
+ raise ValueError(f"unknown atom kind: {kind_value}") from exc
89
+ return Atom(data=data, next=next_hash, size=len(data), kind=kind)
90
+
91
+ def bytes_list_to_atoms(values: List[bytes]) -> Tuple[bytes, List[Atom]]:
92
+ """Build a forward-ordered linked list of atoms from byte payloads.
93
+
94
+ Returns the head object's hash (ZERO32 if no values) and the atoms created.
95
+ """
96
+ next_hash = ZERO32
97
+ atoms: List[Atom] = []
98
+
99
+ for value in reversed(values):
100
+ atom = Atom.from_data(
101
+ data=bytes(value),
102
+ next_hash=next_hash,
103
+ kind=AtomKind.BYTES,
104
+ )
105
+ atoms.append(atom)
106
+ next_hash = atom.object_id()
107
+
108
+ atoms.reverse()
109
+ return (next_hash if values else ZERO32), atoms
@@ -1,7 +1,7 @@
1
1
  import blake3
2
2
  from typing import Dict, List, Optional, Tuple, TYPE_CHECKING
3
3
 
4
- from .atom import Atom, ZERO32
4
+ from .atom import Atom, AtomKind, ZERO32
5
5
 
6
6
  if TYPE_CHECKING:
7
7
  from .._node import Node
@@ -43,10 +43,11 @@ class PatriciaNode:
43
43
 
44
44
  def to_atoms(self) -> Tuple[bytes, List[Atom]]:
45
45
  """
46
- Materialise this node as a flat atom chain containing the fields in a
47
- traversal-friendly order: key length prefix (single byte) + key bytes,
48
- child_0 hash, child_1 hash, and value bytes. Returns the head atom hash
49
- and the atom list.
46
+ Materialise this node with the canonical atom layout used by the
47
+ storage layer: a leading SYMBOL atom with payload ``b"radix"`` whose
48
+ ``next`` pointer links to four BYTES atoms containing, in order:
49
+ key (len byte + key payload), child_0 hash, child_1 hash, value bytes.
50
+ Returns the top atom hash and the emitted atoms.
50
51
  """
51
52
  if self.key_len > 255:
52
53
  raise ValueError("Patricia key length > 255 bits cannot be encoded in a single atom field")
@@ -58,16 +59,23 @@ class PatriciaNode:
58
59
  self.value or b"",
59
60
  ]
60
61
 
61
- atoms: List[Atom] = []
62
+ data_atoms: List[Atom] = []
62
63
  next_hash = ZERO32
63
64
  for payload in reversed(entries):
64
- atom = Atom.from_data(data=payload, next_hash=next_hash)
65
- atoms.append(atom)
65
+ atom = Atom.from_data(data=payload, next_hash=next_hash, kind=AtomKind.BYTES)
66
+ data_atoms.append(atom)
66
67
  next_hash = atom.object_id()
67
68
 
68
- head = next_hash
69
- atoms.reverse()
70
- return head, atoms
69
+ data_atoms.reverse()
70
+
71
+ type_atom = Atom.from_data(
72
+ data=b"radix",
73
+ next_hash=next_hash,
74
+ kind=AtomKind.SYMBOL,
75
+ )
76
+
77
+ atoms = data_atoms + [type_atom]
78
+ return type_atom.object_id(), atoms
71
79
 
72
80
  @classmethod
73
81
  def from_atoms(
@@ -82,19 +90,46 @@ class PatriciaNode:
82
90
  if head_hash == ZERO32:
83
91
  raise ValueError("empty atom chain for Patricia node")
84
92
 
93
+ def _atom_kind(atom: Optional[Atom]) -> Optional[AtomKind]:
94
+ kind_value = getattr(atom, "kind", None)
95
+ if isinstance(kind_value, AtomKind):
96
+ return kind_value
97
+ if isinstance(kind_value, int):
98
+ try:
99
+ return AtomKind(kind_value)
100
+ except ValueError:
101
+ return None
102
+ return None
103
+
104
+ def _require_atom(atom_hash: Optional[bytes], context: str) -> Atom:
105
+ if not atom_hash or atom_hash == ZERO32:
106
+ raise ValueError(f"missing {context}")
107
+ atom = node.storage_get(atom_hash)
108
+ if atom is None:
109
+ raise ValueError(f"missing {context}")
110
+ return atom
111
+
112
+ type_atom = _require_atom(head_hash, "Patricia type atom")
113
+ if _atom_kind(type_atom) is not AtomKind.SYMBOL:
114
+ raise ValueError("malformed Patricia node (type atom kind)")
115
+ if type_atom.data != b"radix":
116
+ raise ValueError("not a Patricia node (type mismatch)")
117
+
85
118
  entries: List[bytes] = []
86
- current = head_hash
119
+ current = type_atom.next
87
120
  hops = 0
88
121
 
89
- while current != ZERO32 and hops < 4:
90
- atom = node._local_get(current)
122
+ while current and current != ZERO32 and hops < 4:
123
+ atom = node.storage_get(current)
91
124
  if atom is None:
92
125
  raise ValueError("missing atom while decoding Patricia node")
126
+ if _atom_kind(atom) is not AtomKind.BYTES:
127
+ raise ValueError("Patricia node detail atoms must be bytes")
93
128
  entries.append(atom.data)
94
129
  current = atom.next
95
130
  hops += 1
96
131
 
97
- if current != ZERO32:
132
+ if current and current != ZERO32:
98
133
  raise ValueError("too many fields while decoding Patricia node")
99
134
 
100
135
  if len(entries) != 4:
@@ -163,7 +198,7 @@ class PatriciaTrie:
163
198
  if cached is not None:
164
199
  return cached
165
200
 
166
- if storage_node._local_get(h) is None:
201
+ if storage_node.storage_get(h) is None:
167
202
  return None
168
203
 
169
204
  pat_node = PatriciaNode.from_atoms(storage_node, h)
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+
7
+ def storage_setup(node: Any, config: dict) -> None:
8
+ """Initialize hot/cold storage helpers on the node."""
9
+
10
+ node.hot_storage = {}
11
+ node.hot_storage_hits = {}
12
+ node.storage_index = {}
13
+ node.hot_storage_size = 0
14
+ hot_storage_default_limit = 1 << 30 # 1 GiB
15
+ hot_storage_limit_value = config.get("hot_storage_limit", hot_storage_default_limit)
16
+ try:
17
+ node.hot_storage_limit = int(hot_storage_limit_value)
18
+ except (TypeError, ValueError):
19
+ node.hot_storage_limit = hot_storage_default_limit
20
+
21
+ node.cold_storage_size = 0
22
+ cold_storage_default_limit = 10 << 30 # 10 GiB
23
+ cold_storage_limit_value = config.get("cold_storage_limit", cold_storage_default_limit)
24
+ try:
25
+ node.cold_storage_limit = int(cold_storage_limit_value)
26
+ except (TypeError, ValueError):
27
+ node.cold_storage_limit = cold_storage_default_limit
28
+
29
+ cold_storage_path = config.get("cold_storage_path")
30
+ if cold_storage_path:
31
+ try:
32
+ Path(cold_storage_path).mkdir(parents=True, exist_ok=True)
33
+ except OSError:
34
+ cold_storage_path = None
35
+ node.cold_storage_path = cold_storage_path
astreum/models/block.py CHANGED
@@ -103,9 +103,9 @@ class Block:
103
103
  stake_root = stake_trie.root_hash
104
104
 
105
105
  # 2. three Account bodies
106
- validator_acct = Account.create(balance=0, data=b"", nonce=0)
107
- treasury_acct = Account.create(balance=1, data=stake_root, nonce=0)
108
- burn_acct = Account.create(balance=0, data=b"", nonce=0)
106
+ validator_acct = Account.create(balance=0, data=b"", counter=0)
107
+ treasury_acct = Account.create(balance=1, data=stake_root, counter=0)
108
+ burn_acct = Account.create(balance=0, data=b"", counter=0)
109
109
 
110
110
  # 3. global Accounts structure
111
111
  accts = Accounts()
@@ -190,7 +190,7 @@ class Block:
190
190
 
191
191
  def _credit(addr: bytes, amt: int):
192
192
  acc = blk.accounts.get_account(addr) or Account.create(0, b"", 0)
193
- blk.accounts.set_account(addr, Account.create(acc.balance() + amt, acc.data(), acc.nonce()))
193
+ blk.accounts.set_account(addr, Account.create(acc.balance + amt, acc.data, acc.counter))
194
194
 
195
195
  if burn_amt:
196
196
  _credit(BURN, burn_amt)
@@ -280,17 +280,17 @@ class Block:
280
280
 
281
281
  sender_acct = self.accounts.get_account(sender_pk)
282
282
  if (sender_acct is None
283
- or sender_acct.nonce() != nonce
284
- or sender_acct.balance() < amount + fee):
283
+ or sender_acct.counter != nonce
284
+ or sender_acct.balance < amount + fee):
285
285
  raise ValueError("invalid or unaffordable transaction")
286
286
 
287
287
  # --- debit sender --------------------------------------------------
288
288
  self.accounts.set_account(
289
289
  sender_pk,
290
290
  Account.create(
291
- balance=sender_acct.balance() - amount - fee,
292
- data=sender_acct.data(),
293
- nonce=sender_acct.nonce() + 1,
291
+ balance=sender_acct.balance - amount - fee,
292
+ data=sender_acct.data,
293
+ counter=sender_acct.counter + 1,
294
294
  )
295
295
  )
296
296
 
@@ -298,14 +298,14 @@ class Block:
298
298
  if recip_pk == TREASURY:
299
299
  treasury = self.accounts.get_account(TREASURY)
300
300
 
301
- trie = PatriciaTrie(node_get=None, root_hash=treasury.data())
301
+ trie = PatriciaTrie(node_get=None, root_hash=treasury.data)
302
302
  stake_bytes = trie.get(sender_pk) or b""
303
303
  current_stake = int.from_bytes(stake_bytes, "big") if stake_bytes else 0
304
304
 
305
305
  if amount > 0:
306
306
  # stake **deposit**
307
307
  trie.put(sender_pk, (current_stake + amount).to_bytes(32, "big"))
308
- new_treas_bal = treasury.balance() + amount
308
+ new_treas_bal = treasury.balance + amount
309
309
  else:
310
310
  # stake **withdrawal**
311
311
  if current_stake == 0:
@@ -315,13 +315,13 @@ class Block:
315
315
  self.accounts.set_account(
316
316
  sender_pk,
317
317
  Account.create(
318
- balance=sender_after.balance() + current_stake,
319
- data=sender_after.data(),
320
- nonce=sender_after.nonce(),
318
+ balance=sender_after.balance + current_stake,
319
+ data=sender_after.data,
320
+ counter=sender_after.counter,
321
321
  )
322
322
  )
323
323
  trie.delete(sender_pk)
324
- new_treas_bal = treasury.balance() # treasury balance unchanged
324
+ new_treas_bal = treasury.balance # treasury balance unchanged
325
325
 
326
326
  # write back treasury with new trie root
327
327
  self.accounts.set_account(
@@ -329,7 +329,7 @@ class Block:
329
329
  Account.create(
330
330
  balance=new_treas_bal,
331
331
  data=trie.root_hash,
332
- nonce=treasury.nonce(),
332
+ counter=treasury.counter,
333
333
  )
334
334
  )
335
335
 
@@ -338,9 +338,9 @@ class Block:
338
338
  self.accounts.set_account(
339
339
  recip_pk,
340
340
  Account.create(
341
- balance=recip_acct.balance() + amount,
342
- data=recip_acct.data(),
343
- nonce=recip_acct.nonce(),
341
+ balance=recip_acct.balance + amount,
342
+ data=recip_acct.data,
343
+ counter=recip_acct.counter,
344
344
  )
345
345
  )
346
346
 
@@ -417,7 +417,7 @@ class Block:
417
417
  v_acct = dummy.accounts.get_account(self.validator_pk) or Account.create(0,b"",0)
418
418
  dummy.accounts.set_account(
419
419
  self.validator_pk,
420
- Account.create(v_acct.balance()+rew, v_acct.data(), v_acct.nonce())
420
+ Account.create(v_acct.balance+rew, v_acct.data, v_acct.counter)
421
421
  )
422
422
 
423
423
  if dummy.accounts.root_hash != self.accounts_hash: