astreum 0.1.13__py3-none-any.whl → 0.1.15__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/lispeum/storage.py +10 -10
- astreum/lispeum/utils.py +17 -0
- astreum/node/__init__.py +358 -470
- astreum/node/relay/__init__.py +72 -30
- astreum/node/relay/envelope.py +5 -5
- astreum/node/storage/__init__.py +13 -0
- astreum/node/storage/merkle.py +734 -0
- astreum/node/{models.py → storage/storage.py} +41 -99
- astreum/node/storage/trie.py +146 -0
- astreum/node/storage/utils.py +137 -0
- astreum/node/utils.py +34 -0
- astreum/node/validation/__init__.py +84 -0
- astreum/node/validation/account.py +874 -0
- astreum/node/validation/block/__init__.py +12 -0
- astreum/node/validation/block/create.py +98 -0
- astreum/node/validation/block/model.py +81 -0
- astreum/node/validation/block/validate.py +196 -0
- astreum/node/validation/constants.py +15 -0
- astreum/node/validation/stake.py +229 -0
- astreum/node/validation/state.py +230 -0
- astreum/node/validation/vdf.py +80 -0
- {astreum-0.1.13.dist-info → astreum-0.1.15.dist-info}/METADATA +2 -1
- {astreum-0.1.13.dist-info → astreum-0.1.15.dist-info}/RECORD +26 -10
- {astreum-0.1.13.dist-info → astreum-0.1.15.dist-info}/LICENSE +0 -0
- {astreum-0.1.13.dist-info → astreum-0.1.15.dist-info}/WHEEL +0 -0
- {astreum-0.1.13.dist-info → astreum-0.1.15.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Block creation functionality for the Astreum blockchain.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from typing import Dict, List, Optional, Tuple, Set
|
|
7
|
+
|
|
8
|
+
from .model import Block
|
|
9
|
+
from ..account import Account
|
|
10
|
+
from ...models import Transaction
|
|
11
|
+
from ...utils import hash_data
|
|
12
|
+
|
|
13
|
+
def create_block(
|
|
14
|
+
number: int,
|
|
15
|
+
validator: bytes,
|
|
16
|
+
previous: bytes,
|
|
17
|
+
transactions: List[Transaction],
|
|
18
|
+
timestamp: Optional[int] = None,
|
|
19
|
+
vdf: Optional[bytes] = None,
|
|
20
|
+
signature: Optional[bytes] = None,
|
|
21
|
+
proof: Optional[bytes] = None,
|
|
22
|
+
receipts: Optional[List] = None,
|
|
23
|
+
data: Optional[bytes] = None,
|
|
24
|
+
delay: Optional[int] = None,
|
|
25
|
+
difficulty: Optional[int] = None,
|
|
26
|
+
accounts: Optional[Dict] = None,
|
|
27
|
+
chain: Optional[bytes] = None
|
|
28
|
+
) -> Block:
|
|
29
|
+
"""
|
|
30
|
+
Create a new block.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
number: Block number
|
|
34
|
+
validator: Address of block validator
|
|
35
|
+
previous: Hash of previous block
|
|
36
|
+
transactions: List of transactions to include
|
|
37
|
+
timestamp: Block timestamp (defaults to current time)
|
|
38
|
+
vdf: VDF proof
|
|
39
|
+
signature: Block signature
|
|
40
|
+
proof: Additional proof data
|
|
41
|
+
receipts: Transaction receipts
|
|
42
|
+
data: Additional block data
|
|
43
|
+
delay: Block delay
|
|
44
|
+
difficulty: Block difficulty
|
|
45
|
+
accounts: Accounts state
|
|
46
|
+
chain: Chain identifier
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
New Block object
|
|
50
|
+
"""
|
|
51
|
+
# Use current time if timestamp not provided
|
|
52
|
+
if timestamp is None:
|
|
53
|
+
timestamp = int(time.time())
|
|
54
|
+
|
|
55
|
+
# Create and return a new Block object
|
|
56
|
+
return Block(
|
|
57
|
+
number=number,
|
|
58
|
+
validator=validator,
|
|
59
|
+
previous=previous,
|
|
60
|
+
transactions=transactions,
|
|
61
|
+
timestamp=timestamp,
|
|
62
|
+
vdf=vdf,
|
|
63
|
+
signature=signature,
|
|
64
|
+
proof=proof,
|
|
65
|
+
receipts=receipts,
|
|
66
|
+
data=data,
|
|
67
|
+
delay=delay,
|
|
68
|
+
difficulty=difficulty,
|
|
69
|
+
accounts=accounts,
|
|
70
|
+
chain=chain
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def create_genesis_block(validator_address: bytes) -> Block:
|
|
74
|
+
"""
|
|
75
|
+
Create the genesis block.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
validator_address: Address of the genesis block validator
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Genesis Block object
|
|
82
|
+
"""
|
|
83
|
+
return create_block(
|
|
84
|
+
number=0,
|
|
85
|
+
validator=validator_address,
|
|
86
|
+
previous=None,
|
|
87
|
+
transactions=[],
|
|
88
|
+
timestamp=int(time.time()),
|
|
89
|
+
vdf=None,
|
|
90
|
+
signature=None,
|
|
91
|
+
proof=None,
|
|
92
|
+
receipts=[],
|
|
93
|
+
data=None,
|
|
94
|
+
delay=0,
|
|
95
|
+
difficulty=1,
|
|
96
|
+
accounts={},
|
|
97
|
+
chain=hash_data(b"genesis")
|
|
98
|
+
)
|
|
@@ -0,0 +1,81 @@
|
|
|
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()
|
|
@@ -0,0 +1,196 @@
|
|
|
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)
|
|
@@ -0,0 +1,15 @@
|
|
|
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
|
|
@@ -0,0 +1,229 @@
|
|
|
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]
|