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.
- {astreum-0.2.4.dist-info → astreum-0.2.5.dist-info}/METADATA +1 -1
- astreum-0.2.5.dist-info/RECORD +18 -0
- astreum/_node/relay/__init__.py +0 -371
- astreum/_node/relay/bucket.py +0 -90
- astreum/_node/relay/envelope.py +0 -280
- astreum/_node/relay/message.py +0 -110
- astreum/_node/relay/peer.py +0 -174
- astreum/_node/relay/route.py +0 -161
- astreum/_node/storage/storage.py +0 -253
- astreum/_node/storage/utils.py +0 -137
- astreum/_node/utils.py +0 -34
- astreum/_node/validation/__init__.py +0 -0
- astreum/_node/validation/_block/__init__.py +0 -0
- astreum/_node/validation/_block/create.py +0 -98
- astreum/_node/validation/_block/model.py +0 -81
- astreum/_node/validation/_block/validate.py +0 -196
- astreum/_node/validation/account.py +0 -99
- astreum/_node/validation/block.py +0 -21
- astreum/_node/validation/constants.py +0 -15
- astreum/_node/validation/stake.py +0 -229
- astreum/_node/validation/transaction.py +0 -146
- astreum/_node/validation/vdf.py +0 -80
- astreum/lispeum/expression.py +0 -95
- astreum/lispeum/special/__init__.py +0 -0
- astreum/lispeum/special/definition.py +0 -27
- astreum/lispeum/special/list/__init__.py +0 -0
- astreum/lispeum/special/list/all.py +0 -32
- astreum/lispeum/special/list/any.py +0 -32
- astreum/lispeum/special/list/fold.py +0 -29
- astreum/lispeum/special/list/get.py +0 -20
- astreum/lispeum/special/list/insert.py +0 -23
- astreum/lispeum/special/list/map.py +0 -30
- astreum/lispeum/special/list/position.py +0 -33
- astreum/lispeum/special/list/remove.py +0 -22
- astreum/lispeum/special/number/__init__.py +0 -0
- astreum/lispeum/special/number/addition.py +0 -0
- astreum/lispeum/storage.py +0 -410
- astreum/machine/__init__.py +0 -352
- astreum/machine/environment.py +0 -4
- astreum/machine/error.py +0 -0
- astreum-0.2.4.dist-info/RECORD +0 -56
- {astreum-0.2.4.dist-info → astreum-0.2.5.dist-info}/WHEEL +0 -0
- {astreum-0.2.4.dist-info → astreum-0.2.5.dist-info}/licenses/LICENSE +0 -0
- {astreum-0.2.4.dist-info → astreum-0.2.5.dist-info}/top_level.txt +0 -0
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Block model for the Astreum blockchain.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import time
|
|
6
|
-
import threading
|
|
7
|
-
from typing import Dict, List, Optional, Tuple, Set
|
|
8
|
-
from dataclasses import dataclass, field
|
|
9
|
-
|
|
10
|
-
from ...node.storage.merkle import MerkleTree
|
|
11
|
-
from ...utils import hash_data
|
|
12
|
-
|
|
13
|
-
class Block:
|
|
14
|
-
def __init__(self, number, validator, previous, transactions, timestamp, vdf, signature, proof, receipts, data, delay, difficulty, accounts=None, chain=None):
|
|
15
|
-
self.accounts = accounts
|
|
16
|
-
self.chain = chain
|
|
17
|
-
self.data = data
|
|
18
|
-
self.delay = delay
|
|
19
|
-
self.difficulty = difficulty
|
|
20
|
-
self.number = number
|
|
21
|
-
self.validator = validator
|
|
22
|
-
self.vdf = vdf
|
|
23
|
-
self.previous = previous
|
|
24
|
-
self.transactions = transactions
|
|
25
|
-
self.timestamp = timestamp
|
|
26
|
-
self.signature = signature
|
|
27
|
-
self.proof = proof
|
|
28
|
-
self.receipts = receipts
|
|
29
|
-
|
|
30
|
-
def get_details_hash(self):
|
|
31
|
-
detail_fields = [
|
|
32
|
-
'accounts',
|
|
33
|
-
'chain',
|
|
34
|
-
'data',
|
|
35
|
-
'delay',
|
|
36
|
-
'difficulty',
|
|
37
|
-
'number',
|
|
38
|
-
'previous',
|
|
39
|
-
'proof',
|
|
40
|
-
'receipts',
|
|
41
|
-
'timestamp',
|
|
42
|
-
'transactions',
|
|
43
|
-
'validator',
|
|
44
|
-
'vdf',
|
|
45
|
-
]
|
|
46
|
-
|
|
47
|
-
# Prepare serialized entries in the exact order of detail_fields
|
|
48
|
-
serialized_entries = []
|
|
49
|
-
for field in detail_fields:
|
|
50
|
-
value = getattr(self, field, None) # Use None if attribute doesn't exist
|
|
51
|
-
|
|
52
|
-
# Convert value to bytes directly based on type
|
|
53
|
-
if isinstance(value, (bytes, bytearray)):
|
|
54
|
-
# Already bytes, use as is
|
|
55
|
-
entry = value
|
|
56
|
-
elif isinstance(value, (int, float)):
|
|
57
|
-
# Convert numbers to bytes
|
|
58
|
-
entry = str(value).encode('utf-8')
|
|
59
|
-
elif isinstance(value, str):
|
|
60
|
-
# Convert strings to bytes
|
|
61
|
-
entry = value.encode('utf-8')
|
|
62
|
-
elif value is None:
|
|
63
|
-
# Handle None values
|
|
64
|
-
entry = b''
|
|
65
|
-
else:
|
|
66
|
-
# For complex types, use their string representation
|
|
67
|
-
entry = str(value).encode('utf-8')
|
|
68
|
-
|
|
69
|
-
serialized_entries.append(entry)
|
|
70
|
-
|
|
71
|
-
# Use MerkleTree from storage module
|
|
72
|
-
merkle_tree = MerkleTree()
|
|
73
|
-
merkle_tree.add(serialized_entries)
|
|
74
|
-
|
|
75
|
-
return merkle_tree.get_root_hash()
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def get_hash(self):
|
|
79
|
-
merkle_tree = MerkleTree()
|
|
80
|
-
merkle_tree.add([self.get_details_hash(), self.signature])
|
|
81
|
-
return merkle_tree.get_root_hash()
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
# Cached verified blocks
|
|
2
|
-
verified_blocks = set()
|
|
3
|
-
|
|
4
|
-
def validate_block(block: Block, accounts: Dict[bytes, Account],
|
|
5
|
-
known_blocks: Dict[bytes, Block]) -> bool:
|
|
6
|
-
"""
|
|
7
|
-
Validate a block.
|
|
8
|
-
|
|
9
|
-
Args:
|
|
10
|
-
block: Block to validate
|
|
11
|
-
accounts: Dictionary of accounts
|
|
12
|
-
known_blocks: Dictionary of known blocks
|
|
13
|
-
|
|
14
|
-
Returns:
|
|
15
|
-
True if block is valid, False otherwise
|
|
16
|
-
"""
|
|
17
|
-
# Skip validation if already verified
|
|
18
|
-
block_hash = block.get_hash()
|
|
19
|
-
if block_hash in verified_blocks:
|
|
20
|
-
return True
|
|
21
|
-
|
|
22
|
-
# Check block structure
|
|
23
|
-
if not _validate_block_structure(block):
|
|
24
|
-
return False
|
|
25
|
-
|
|
26
|
-
# Check if previous block exists, except for genesis block
|
|
27
|
-
if block.number > 0 and block.previous not in known_blocks:
|
|
28
|
-
return False
|
|
29
|
-
|
|
30
|
-
# Check if validator is a validator
|
|
31
|
-
if not _is_valid_producer(block, accounts):
|
|
32
|
-
return False
|
|
33
|
-
|
|
34
|
-
# Validate VDF proof
|
|
35
|
-
if not _validate_vdf_proof(block):
|
|
36
|
-
return False
|
|
37
|
-
|
|
38
|
-
# Validate transactions
|
|
39
|
-
if not _validate_transactions(block, accounts):
|
|
40
|
-
return False
|
|
41
|
-
|
|
42
|
-
# All checks passed, mark as verified
|
|
43
|
-
verified_blocks.add(block_hash)
|
|
44
|
-
return True
|
|
45
|
-
|
|
46
|
-
def _validate_block_structure(block: Block) -> bool:
|
|
47
|
-
"""
|
|
48
|
-
Validate basic block structure.
|
|
49
|
-
|
|
50
|
-
Args:
|
|
51
|
-
block: Block to validate
|
|
52
|
-
|
|
53
|
-
Returns:
|
|
54
|
-
True if structure is valid, False otherwise
|
|
55
|
-
"""
|
|
56
|
-
# Check for required fields
|
|
57
|
-
if (block.number is None or block.time is None or
|
|
58
|
-
not hasattr(block, 'validator') or not block.validator):
|
|
59
|
-
return False
|
|
60
|
-
|
|
61
|
-
# Check that genesis block has no previous
|
|
62
|
-
if block.number == 0 and block.previous is not None:
|
|
63
|
-
return False
|
|
64
|
-
|
|
65
|
-
# Check that non-genesis block has previous
|
|
66
|
-
if block.number > 0 and block.previous is None:
|
|
67
|
-
return False
|
|
68
|
-
|
|
69
|
-
# Check timestamp is reasonable
|
|
70
|
-
current_time = int(time.time())
|
|
71
|
-
if block.time > current_time + 60: # Allow 1 minute clock drift
|
|
72
|
-
return False
|
|
73
|
-
|
|
74
|
-
return True
|
|
75
|
-
|
|
76
|
-
def _is_valid_producer(block: Block, accounts: Dict[bytes, Account]) -> bool:
|
|
77
|
-
"""
|
|
78
|
-
Check if block validator is a valid validator.
|
|
79
|
-
|
|
80
|
-
Args:
|
|
81
|
-
block: Block to validate
|
|
82
|
-
accounts: Dictionary of accounts
|
|
83
|
-
|
|
84
|
-
Returns:
|
|
85
|
-
True if validator is valid, False otherwise
|
|
86
|
-
"""
|
|
87
|
-
# Anyone can produce the genesis block
|
|
88
|
-
if block.number == 0:
|
|
89
|
-
return True
|
|
90
|
-
|
|
91
|
-
# Check if validator is a validator with stake
|
|
92
|
-
if not block.validator or not hasattr(block.validator, 'public_key'):
|
|
93
|
-
return False
|
|
94
|
-
|
|
95
|
-
validator_address = block.validator.public_key
|
|
96
|
-
stake = get_validator_stake(accounts, validator_address)
|
|
97
|
-
return stake >= MIN_STAKE_AMOUNT
|
|
98
|
-
|
|
99
|
-
def _validate_vdf_proof(block: Block) -> bool:
|
|
100
|
-
"""
|
|
101
|
-
Validate the VDF proof in the block.
|
|
102
|
-
|
|
103
|
-
Args:
|
|
104
|
-
block: Block to validate
|
|
105
|
-
|
|
106
|
-
Returns:
|
|
107
|
-
True if VDF proof is valid, False otherwise
|
|
108
|
-
"""
|
|
109
|
-
# Skip VDF validation for genesis block
|
|
110
|
-
if block.number == 0:
|
|
111
|
-
return True
|
|
112
|
-
|
|
113
|
-
# In a real implementation, this would verify the VDF proof
|
|
114
|
-
# For our purposes, we'll assume all blocks have valid VDF proofs
|
|
115
|
-
if not block.vdf_proof:
|
|
116
|
-
return False
|
|
117
|
-
|
|
118
|
-
return validate_block_vdf(block.number, block.previous, block.vdf_proof)
|
|
119
|
-
|
|
120
|
-
def _validate_transactions(block: Block, accounts: Dict[bytes, Account]) -> bool:
|
|
121
|
-
"""
|
|
122
|
-
Validate transactions in a block.
|
|
123
|
-
|
|
124
|
-
Args:
|
|
125
|
-
block: Block to validate
|
|
126
|
-
accounts: Dictionary of accounts
|
|
127
|
-
|
|
128
|
-
Returns:
|
|
129
|
-
True if all transactions are valid, False otherwise
|
|
130
|
-
"""
|
|
131
|
-
# In a real implementation, this would verify each transaction
|
|
132
|
-
# For our purposes, we'll assume all transactions in a block are valid
|
|
133
|
-
return True
|
|
134
|
-
|
|
135
|
-
def select_validator(accounts: Dict[bytes, Account], random_seed: bytes) -> Optional[bytes]:
|
|
136
|
-
"""
|
|
137
|
-
Select a validator based on stake using a random seed.
|
|
138
|
-
|
|
139
|
-
Args:
|
|
140
|
-
accounts: Dictionary of accounts
|
|
141
|
-
random_seed: Random seed for selection
|
|
142
|
-
|
|
143
|
-
Returns:
|
|
144
|
-
Selected validator address or None if no validators
|
|
145
|
-
"""
|
|
146
|
-
# Get validators (accounts with stake)
|
|
147
|
-
validators = {addr: acc for addr, acc in accounts.items()
|
|
148
|
-
if get_validator_stake(accounts, addr) >= MIN_STAKE_AMOUNT}
|
|
149
|
-
|
|
150
|
-
# Calculate total stake
|
|
151
|
-
total_stake = sum(get_validator_stake(accounts, addr) for addr in validators)
|
|
152
|
-
|
|
153
|
-
if total_stake <= 0:
|
|
154
|
-
return None
|
|
155
|
-
|
|
156
|
-
# Convert random seed to a number between 0 and total_stake
|
|
157
|
-
seed_hash = hash_data(random_seed)
|
|
158
|
-
random_value = int.from_bytes(seed_hash, byteorder='big') % total_stake
|
|
159
|
-
|
|
160
|
-
# Build cumulative stake mapping
|
|
161
|
-
cumulative_stake = 0
|
|
162
|
-
stake_map = {}
|
|
163
|
-
|
|
164
|
-
# Sort validators by address for determinism
|
|
165
|
-
for addr in sorted(validators.keys()):
|
|
166
|
-
cumulative_stake += get_validator_stake(accounts, addr)
|
|
167
|
-
stake_map[addr] = cumulative_stake
|
|
168
|
-
|
|
169
|
-
# Find the validator whose cumulative stake covers the random value
|
|
170
|
-
for addr, cum_stake in stake_map.items():
|
|
171
|
-
if random_value < cum_stake:
|
|
172
|
-
return addr
|
|
173
|
-
|
|
174
|
-
# If no validator found (should not happen), return the last one
|
|
175
|
-
if stake_map:
|
|
176
|
-
return list(stake_map.keys())[-1]
|
|
177
|
-
return None
|
|
178
|
-
|
|
179
|
-
def select_validator_for_slot(accounts: Dict[bytes, Account], slot: int,
|
|
180
|
-
previous_vdf: bytes) -> Optional[bytes]:
|
|
181
|
-
"""
|
|
182
|
-
Select validator for a specific slot.
|
|
183
|
-
|
|
184
|
-
Args:
|
|
185
|
-
accounts: Dictionary of accounts
|
|
186
|
-
slot: Slot number
|
|
187
|
-
previous_vdf: VDF output from previous block
|
|
188
|
-
|
|
189
|
-
Returns:
|
|
190
|
-
Selected validator address or None if no validators
|
|
191
|
-
"""
|
|
192
|
-
# Generate random seed based on slot and previous VDF
|
|
193
|
-
seed = hash_data(previous_vdf + slot.to_bytes(8, byteorder='big'))
|
|
194
|
-
|
|
195
|
-
# Select validator based on stake
|
|
196
|
-
return select_validator(accounts, seed)
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
from ..storage.patricia import PatriciaTrie
|
|
3
|
-
|
|
4
|
-
import astreum.format as format
|
|
5
|
-
class Account:
|
|
6
|
-
def __init__(self, public_key: bytes, balance: int, code: bytes, counter: int, data: bytes, secret_key: Optional[bytes] = None):
|
|
7
|
-
"""
|
|
8
|
-
Initialize an Account.
|
|
9
|
-
|
|
10
|
-
:param public_key: The public key used as the account identifier (used as trie key).
|
|
11
|
-
:param balance: The account balance.
|
|
12
|
-
:param code: The associated code (for example, smart contract code).
|
|
13
|
-
:param counter: A transaction counter (nonce).
|
|
14
|
-
:param data: Additional account data.
|
|
15
|
-
:param secret_key: (Optional) The account’s secret key.
|
|
16
|
-
"""
|
|
17
|
-
self.public_key = public_key
|
|
18
|
-
self.secret_key = secret_key # Optional private key.
|
|
19
|
-
self.balance = balance
|
|
20
|
-
self.code = code
|
|
21
|
-
self.counter = counter
|
|
22
|
-
self.data = data
|
|
23
|
-
|
|
24
|
-
@classmethod
|
|
25
|
-
def from_bytes(cls, public_key: bytes, data: bytes, secret_key: Optional[bytes] = None) -> 'Account':
|
|
26
|
-
"""
|
|
27
|
-
Deserialize an Account from its byte representation.
|
|
28
|
-
|
|
29
|
-
Expected format: [balance, code, counter, data]
|
|
30
|
-
|
|
31
|
-
The public_key (and optional secret_key) must be provided separately.
|
|
32
|
-
"""
|
|
33
|
-
decoded = format.decode(data)
|
|
34
|
-
balance, code, counter, account_data = decoded
|
|
35
|
-
return cls(public_key, balance, code, counter, account_data, secret_key=secret_key)
|
|
36
|
-
|
|
37
|
-
def to_bytes(self) -> bytes:
|
|
38
|
-
"""
|
|
39
|
-
Serialize the Account into bytes.
|
|
40
|
-
|
|
41
|
-
Format: [balance, code, counter, data]
|
|
42
|
-
"""
|
|
43
|
-
return format.encode([
|
|
44
|
-
self.balance,
|
|
45
|
-
self.code,
|
|
46
|
-
self.counter,
|
|
47
|
-
self.data
|
|
48
|
-
])
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
class Accounts(PatriciaTrie):
|
|
52
|
-
"""
|
|
53
|
-
Accounts is a PatriciaTrie storing Account objects.
|
|
54
|
-
|
|
55
|
-
The trie key is the account’s public key (bytes), while the value is the account data
|
|
56
|
-
(serialized with balance, code, counter, and data only).
|
|
57
|
-
|
|
58
|
-
You can instantiate Accounts with a storage instance and, if available, a root hash
|
|
59
|
-
representing an existing trie.
|
|
60
|
-
"""
|
|
61
|
-
def get_account(self, public_key: bytes) -> Optional[Account]:
|
|
62
|
-
"""
|
|
63
|
-
Retrieve an account using its public key.
|
|
64
|
-
|
|
65
|
-
:param public_key: The public key (bytes) of the account.
|
|
66
|
-
:return: The Account instance if found, else None.
|
|
67
|
-
"""
|
|
68
|
-
raw = self.get(public_key)
|
|
69
|
-
if raw is None:
|
|
70
|
-
return None
|
|
71
|
-
return Account.from_bytes(public_key, raw)
|
|
72
|
-
|
|
73
|
-
def put_account(self, account: Account) -> None:
|
|
74
|
-
"""
|
|
75
|
-
Insert or update an account in the trie.
|
|
76
|
-
|
|
77
|
-
:param account: The Account instance to store.
|
|
78
|
-
"""
|
|
79
|
-
self.put(account.public_key, account.to_bytes())
|
|
80
|
-
|
|
81
|
-
def get_account_from_storage(public_key: bytes, accounts: Accounts, storage: Storage) -> Optional[Account]:
|
|
82
|
-
# 1. get account details hash
|
|
83
|
-
# 2. resolve storage objects to get account details
|
|
84
|
-
# 3. return account
|
|
85
|
-
return None
|
|
86
|
-
|
|
87
|
-
# Example of instantiation:
|
|
88
|
-
#
|
|
89
|
-
# Assuming you have an instance of Storage (e.g., from your storage module), you can create
|
|
90
|
-
# an Accounts instance either as an empty trie or using an existing root hash:
|
|
91
|
-
#
|
|
92
|
-
# storage = Storage()
|
|
93
|
-
#
|
|
94
|
-
# # To instantiate an empty Accounts trie:
|
|
95
|
-
# accounts = Accounts(storage)
|
|
96
|
-
#
|
|
97
|
-
# # To instantiate with an existing trie root hash:
|
|
98
|
-
# existing_root_hash = b'...' # This would be loaded from persistent storage.
|
|
99
|
-
# accounts = Accounts(storage, root_hash=existing_root_hash)
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
class Block:
|
|
2
|
-
|
|
3
|
-
def __init__(self):
|
|
4
|
-
pass
|
|
5
|
-
|
|
6
|
-
@classmethod
|
|
7
|
-
def from_bytes(cls) -> 'Block':
|
|
8
|
-
"""
|
|
9
|
-
Deserialize a block from its byte representation.
|
|
10
|
-
"""
|
|
11
|
-
return cls()
|
|
12
|
-
|
|
13
|
-
def to_bytes(self) -> bytes:
|
|
14
|
-
"""
|
|
15
|
-
Serialize the block into bytes.
|
|
16
|
-
"""
|
|
17
|
-
return b""
|
|
18
|
-
|
|
19
|
-
class Chain:
|
|
20
|
-
def __init__(self, latest_block: Block):
|
|
21
|
-
self.latest_block = latest_block
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Constants for the validation module.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import time
|
|
6
|
-
from ..utils import hash_data
|
|
7
|
-
|
|
8
|
-
# Special addresses
|
|
9
|
-
VALIDATION_ADDRESS = b'\xFF' * 32 # Address for staking (all F's)
|
|
10
|
-
BURN_ADDRESS = b'\x00' * 32 # Address for burning tokens (all 0's)
|
|
11
|
-
|
|
12
|
-
# Validation parameters
|
|
13
|
-
MIN_STAKE_AMOUNT = 1 # Minimum stake amount in Aster
|
|
14
|
-
SLOT_DURATION = 1 # Duration of each slot in seconds
|
|
15
|
-
VDF_DIFFICULTY = 1 # Default VDF difficulty
|
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Stake management for validators.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import time
|
|
6
|
-
import random
|
|
7
|
-
import json
|
|
8
|
-
from typing import Dict, List, Optional, Tuple
|
|
9
|
-
|
|
10
|
-
from ...crypto import hash_data
|
|
11
|
-
from ..models import Transaction, TransactionType
|
|
12
|
-
from ..storage import Storage, MerkleNode
|
|
13
|
-
from .constants import SLOT_DURATION, MIN_STAKE_AMOUNT, VALIDATION_ADDRESS
|
|
14
|
-
from .account import Account
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def get_validator_stake(storage: Storage, stake_root: bytes, validator_address: bytes) -> int:
|
|
18
|
-
"""
|
|
19
|
-
Get the stake amount for a specific validator.
|
|
20
|
-
|
|
21
|
-
Args:
|
|
22
|
-
storage: Storage instance
|
|
23
|
-
stake_root: Root hash of the stake Merkle tree
|
|
24
|
-
validator_address: Address of the validator to check
|
|
25
|
-
|
|
26
|
-
Returns:
|
|
27
|
-
Stake amount for the validator, or 0 if not a validator
|
|
28
|
-
"""
|
|
29
|
-
from ..storage.merkle import find_first
|
|
30
|
-
|
|
31
|
-
# Find the validator node containing the stake information
|
|
32
|
-
def match_validator(node_data: bytes) -> bool:
|
|
33
|
-
try:
|
|
34
|
-
stake_data = json.loads(node_data.decode('utf-8'))
|
|
35
|
-
return stake_data.get('address') == validator_address.hex()
|
|
36
|
-
except:
|
|
37
|
-
return False
|
|
38
|
-
|
|
39
|
-
validator_node = find_first(storage, stake_root, match_validator)
|
|
40
|
-
|
|
41
|
-
if validator_node:
|
|
42
|
-
try:
|
|
43
|
-
stake_data = json.loads(validator_node.decode('utf-8'))
|
|
44
|
-
return stake_data.get('stake', 0)
|
|
45
|
-
except:
|
|
46
|
-
return 0
|
|
47
|
-
|
|
48
|
-
return 0
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def is_validator(storage: Storage, stake_root: bytes, address: bytes) -> bool:
|
|
52
|
-
"""
|
|
53
|
-
Check if an address is registered as a validator.
|
|
54
|
-
|
|
55
|
-
Args:
|
|
56
|
-
storage: Storage instance
|
|
57
|
-
stake_root: Root hash of the stake Merkle tree
|
|
58
|
-
address: Address to check
|
|
59
|
-
|
|
60
|
-
Returns:
|
|
61
|
-
True if the address is a validator, False otherwise
|
|
62
|
-
"""
|
|
63
|
-
stake = get_validator_stake(storage, stake_root, address)
|
|
64
|
-
return stake >= MIN_STAKE_AMOUNT
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def get_all_validators(storage: Storage, stake_root: bytes) -> Dict[bytes, int]:
|
|
68
|
-
"""
|
|
69
|
-
Get all validators and their stakes.
|
|
70
|
-
|
|
71
|
-
Args:
|
|
72
|
-
storage: Storage instance
|
|
73
|
-
stake_root: Root hash of the stake Merkle tree
|
|
74
|
-
|
|
75
|
-
Returns:
|
|
76
|
-
Dictionary mapping validator addresses to their stakes
|
|
77
|
-
"""
|
|
78
|
-
from ..storage.merkle import find_all
|
|
79
|
-
|
|
80
|
-
validators = {}
|
|
81
|
-
|
|
82
|
-
# Find all validator nodes containing stake information
|
|
83
|
-
def match_all_validators(node_data: bytes) -> bool:
|
|
84
|
-
return True # Match all nodes
|
|
85
|
-
|
|
86
|
-
validator_nodes = find_all(storage, stake_root, match_all_validators)
|
|
87
|
-
|
|
88
|
-
for node_data in validator_nodes:
|
|
89
|
-
try:
|
|
90
|
-
stake_data = json.loads(node_data.decode('utf-8'))
|
|
91
|
-
address = bytes.fromhex(stake_data.get('address', ''))
|
|
92
|
-
stake = stake_data.get('stake', 0)
|
|
93
|
-
|
|
94
|
-
if stake >= MIN_STAKE_AMOUNT:
|
|
95
|
-
validators[address] = stake
|
|
96
|
-
except:
|
|
97
|
-
continue
|
|
98
|
-
|
|
99
|
-
return validators
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def process_stake_transaction(
|
|
103
|
-
storage: Storage,
|
|
104
|
-
stake_root: bytes,
|
|
105
|
-
validator_address: bytes,
|
|
106
|
-
amount: int
|
|
107
|
-
) -> bytes:
|
|
108
|
-
"""
|
|
109
|
-
Process a staking transaction and update the stake tree.
|
|
110
|
-
|
|
111
|
-
Args:
|
|
112
|
-
storage: Storage instance
|
|
113
|
-
stake_root: Current root hash of the stake Merkle tree
|
|
114
|
-
validator_address: Address of the validator staking tokens
|
|
115
|
-
amount: Amount being staked
|
|
116
|
-
|
|
117
|
-
Returns:
|
|
118
|
-
New root hash of the stake Merkle tree after the update
|
|
119
|
-
"""
|
|
120
|
-
from ..storage.merkle import find_first, MerkleTree
|
|
121
|
-
|
|
122
|
-
# Find the validator node if it exists
|
|
123
|
-
def match_validator(node_data: bytes) -> bool:
|
|
124
|
-
try:
|
|
125
|
-
stake_data = json.loads(node_data.decode('utf-8'))
|
|
126
|
-
return stake_data.get('address') == validator_address.hex()
|
|
127
|
-
except:
|
|
128
|
-
return False
|
|
129
|
-
|
|
130
|
-
validator_node = find_first(storage, stake_root, match_validator)
|
|
131
|
-
|
|
132
|
-
# Create or update the validator stake
|
|
133
|
-
if validator_node:
|
|
134
|
-
# Update existing validator
|
|
135
|
-
try:
|
|
136
|
-
stake_data = json.loads(validator_node.decode('utf-8'))
|
|
137
|
-
current_stake = stake_data.get('stake', 0)
|
|
138
|
-
stake_data['stake'] = current_stake + amount
|
|
139
|
-
updated_node = json.dumps(stake_data).encode('utf-8')
|
|
140
|
-
|
|
141
|
-
# Replace the node in the tree
|
|
142
|
-
merkle_tree = MerkleTree(storage)
|
|
143
|
-
merkle_tree.root_hash = stake_root
|
|
144
|
-
new_root = merkle_tree.add(updated_node)
|
|
145
|
-
except Exception as e:
|
|
146
|
-
print(f"Error updating validator stake: {e}")
|
|
147
|
-
return stake_root
|
|
148
|
-
else:
|
|
149
|
-
# Create new validator
|
|
150
|
-
stake_data = {
|
|
151
|
-
'address': validator_address.hex(),
|
|
152
|
-
'stake': amount,
|
|
153
|
-
'last_update': int(time.time())
|
|
154
|
-
}
|
|
155
|
-
new_node = json.dumps(stake_data).encode('utf-8')
|
|
156
|
-
|
|
157
|
-
# Add the node to the tree
|
|
158
|
-
merkle_tree = MerkleTree(storage)
|
|
159
|
-
if stake_root:
|
|
160
|
-
merkle_tree.root_hash = stake_root
|
|
161
|
-
new_root = merkle_tree.add(new_node)
|
|
162
|
-
else:
|
|
163
|
-
new_root = merkle_tree.add([new_node])
|
|
164
|
-
|
|
165
|
-
return new_root
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
class SlotManager:
|
|
169
|
-
"""
|
|
170
|
-
Manager for tracking slots and selecting validators.
|
|
171
|
-
"""
|
|
172
|
-
|
|
173
|
-
def __init__(self, storage: Storage, stake_root: bytes = None):
|
|
174
|
-
"""
|
|
175
|
-
Initialize slot manager.
|
|
176
|
-
|
|
177
|
-
Args:
|
|
178
|
-
storage: Storage instance
|
|
179
|
-
stake_root: Root hash of the stake Merkle tree
|
|
180
|
-
"""
|
|
181
|
-
self.storage = storage
|
|
182
|
-
self.stake_root = stake_root
|
|
183
|
-
|
|
184
|
-
def get_current_slot(self) -> int:
|
|
185
|
-
"""
|
|
186
|
-
Get the current slot based on current time.
|
|
187
|
-
|
|
188
|
-
Returns:
|
|
189
|
-
Current slot number
|
|
190
|
-
"""
|
|
191
|
-
return int(time.time() // SLOT_DURATION)
|
|
192
|
-
|
|
193
|
-
def select_validator_for_slot(self, slot: int, previous_vdf: bytes) -> Optional[bytes]:
|
|
194
|
-
"""
|
|
195
|
-
Select a validator for the given slot.
|
|
196
|
-
|
|
197
|
-
Args:
|
|
198
|
-
slot: Slot number
|
|
199
|
-
previous_vdf: VDF output from previous block
|
|
200
|
-
|
|
201
|
-
Returns:
|
|
202
|
-
Selected validator address, or None if no validators available
|
|
203
|
-
"""
|
|
204
|
-
# Get all validators
|
|
205
|
-
validators = get_all_validators(self.storage, self.stake_root)
|
|
206
|
-
|
|
207
|
-
if not validators:
|
|
208
|
-
return None
|
|
209
|
-
|
|
210
|
-
# Generate random seed based on slot and previous VDF
|
|
211
|
-
seed = hash_data(previous_vdf + slot.to_bytes(8, byteorder='big'))
|
|
212
|
-
|
|
213
|
-
# Select validator based on stake and random seed
|
|
214
|
-
total_stake = sum(validators.values())
|
|
215
|
-
if total_stake == 0:
|
|
216
|
-
return None
|
|
217
|
-
|
|
218
|
-
# Convert seed to a random number between 0 and 1
|
|
219
|
-
random_value = int.from_bytes(seed, byteorder='big') / (2**(len(seed) * 8) - 1)
|
|
220
|
-
|
|
221
|
-
# Select validator based on stake weight
|
|
222
|
-
cumulative_prob = 0
|
|
223
|
-
for validator, stake in validators.items():
|
|
224
|
-
cumulative_prob += stake / total_stake
|
|
225
|
-
if random_value <= cumulative_prob:
|
|
226
|
-
return validator
|
|
227
|
-
|
|
228
|
-
# Should never reach here unless there's a rounding error
|
|
229
|
-
return list(validators.keys())[0]
|