astreum 0.1.18__py3-none-any.whl → 0.1.19__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/node/__init__.py +390 -418
- astreum/node/validation/__init__.py +0 -84
- astreum/node/validation/_block/__init__.py +0 -12
- astreum/node/validation/block.py +10 -19
- {astreum-0.1.18.dist-info → astreum-0.1.19.dist-info}/METADATA +1 -1
- {astreum-0.1.18.dist-info → astreum-0.1.19.dist-info}/RECORD +9 -10
- {astreum-0.1.18.dist-info → astreum-0.1.19.dist-info}/WHEEL +1 -1
- astreum/node/validation/state.py +0 -230
- {astreum-0.1.18.dist-info → astreum-0.1.19.dist-info}/licenses/LICENSE +0 -0
- {astreum-0.1.18.dist-info → astreum-0.1.19.dist-info}/top_level.txt +0 -0
astreum/node/__init__.py
CHANGED
|
@@ -1,475 +1,447 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import time
|
|
2
|
+
import threading
|
|
3
|
+
from typing import List
|
|
4
|
+
from cryptography.hazmat.primitives.asymmetric import ed25519
|
|
5
|
+
from cryptography.hazmat.primitives import serialization
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
from .relay import Relay, Topic
|
|
8
|
+
from ..machine import AstreumMachine
|
|
9
|
+
from .utils import hash_data
|
|
10
|
+
from .validation.block import Block
|
|
11
|
+
from .storage import Storage
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
#
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
#
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
# from .validation.state import (
|
|
31
|
-
# add_block_to_state,
|
|
32
|
-
# validate_and_apply_block,
|
|
33
|
-
# create_account_state,
|
|
34
|
-
# get_validator_for_slot,
|
|
35
|
-
# select_best_chain,
|
|
36
|
-
# compare_chains,
|
|
37
|
-
# get_validator_set
|
|
38
|
-
# )
|
|
39
|
-
# from .validation.adapter import BlockAdapter, TransactionAdapter
|
|
40
|
-
|
|
41
|
-
# class Node:
|
|
42
|
-
# def __init__(self, config: dict):
|
|
43
|
-
# # Ensure config is a dictionary, but allow it to be None
|
|
44
|
-
# self.config = config if config is not None else {}
|
|
45
|
-
|
|
46
|
-
# # Handle validation key if provided
|
|
47
|
-
# self.validation_private_key = None
|
|
48
|
-
# self.validation_public_key = None
|
|
49
|
-
# self.is_validator = False
|
|
50
|
-
|
|
51
|
-
# # Extract validation private key from config
|
|
52
|
-
# if 'validation_private_key' in self.config:
|
|
53
|
-
# try:
|
|
54
|
-
# key_bytes = bytes.fromhex(self.config['validation_private_key'])
|
|
55
|
-
# self.validation_private_key = ed25519.Ed25519PrivateKey.from_private_bytes(key_bytes)
|
|
56
|
-
# self.validation_public_key = self.validation_private_key.public_key()
|
|
57
|
-
# self.is_validator = True
|
|
13
|
+
class Node:
|
|
14
|
+
def __init__(self, config: dict):
|
|
15
|
+
# Ensure config is a dictionary, but allow it to be None
|
|
16
|
+
self.config = config if config is not None else {}
|
|
17
|
+
|
|
18
|
+
# Handle validation key if provided
|
|
19
|
+
self.validation_private_key = None
|
|
20
|
+
self.validation_public_key = None
|
|
21
|
+
self.is_validator = False
|
|
22
|
+
|
|
23
|
+
# Extract validation private key from config
|
|
24
|
+
if 'validation_private_key' in self.config:
|
|
25
|
+
try:
|
|
26
|
+
key_bytes = bytes.fromhex(self.config['validation_private_key'])
|
|
27
|
+
self.validation_private_key = ed25519.Ed25519PrivateKey.from_private_bytes(key_bytes)
|
|
28
|
+
self.validation_public_key = self.validation_private_key.public_key()
|
|
29
|
+
self.is_validator = True
|
|
58
30
|
|
|
59
|
-
#
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
#
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
#
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
#
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
#
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
#
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
#
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
#
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
#
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
#
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
#
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
#
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
#
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
#
|
|
131
|
-
|
|
31
|
+
# Set validation_route to True in config so relay will join validation route
|
|
32
|
+
self.config['validation_route'] = True
|
|
33
|
+
print(f"Node is configured as a validator with validation key")
|
|
34
|
+
except Exception as e:
|
|
35
|
+
print(f"Error loading validation private key: {e}")
|
|
36
|
+
|
|
37
|
+
# Initialize relay with our config
|
|
38
|
+
self.relay = Relay(self.config)
|
|
39
|
+
|
|
40
|
+
# Get the node_id from relay
|
|
41
|
+
self.node_id = self.relay.node_id
|
|
42
|
+
|
|
43
|
+
# Initialize storage
|
|
44
|
+
self.storage = Storage(self.config)
|
|
45
|
+
self.storage.node = self # Set the storage node reference to self
|
|
46
|
+
|
|
47
|
+
# Initialize blockchain state
|
|
48
|
+
self.blockchain = create_account_state(self.config)
|
|
49
|
+
|
|
50
|
+
# Store our validator info if we're a validator
|
|
51
|
+
if self.is_validator and self.validation_public_key:
|
|
52
|
+
self.validator_address = self.validation_public_key.public_bytes(
|
|
53
|
+
encoding=serialization.Encoding.Raw,
|
|
54
|
+
format=serialization.PublicFormat.Raw
|
|
55
|
+
)
|
|
56
|
+
self.validator_private_bytes = self.validation_private_key.private_bytes(
|
|
57
|
+
encoding=serialization.Encoding.Raw,
|
|
58
|
+
format=serialization.PrivateFormat.Raw,
|
|
59
|
+
encryption_algorithm=serialization.NoEncryption()
|
|
60
|
+
)
|
|
61
|
+
print(f"Registered validator with address: {self.validator_address.hex()}")
|
|
62
|
+
else:
|
|
63
|
+
self.validator_address = None
|
|
64
|
+
self.validator_private_bytes = None
|
|
65
|
+
|
|
66
|
+
# Latest block of the chain this node is following
|
|
67
|
+
self.latest_block = None
|
|
68
|
+
self.followed_chain_id = self.config.get('followed_chain_id', None)
|
|
69
|
+
|
|
70
|
+
# Initialize machine
|
|
71
|
+
self.machine = AstreumMachine(node=self)
|
|
72
|
+
|
|
73
|
+
# Register message handlers
|
|
74
|
+
self.relay.message_handlers[Topic.PEER_ROUTE] = self._handle_peer_route
|
|
75
|
+
self.relay.message_handlers[Topic.PING] = self._handle_ping
|
|
76
|
+
self.relay.message_handlers[Topic.PONG] = self._handle_pong
|
|
77
|
+
self.relay.message_handlers[Topic.OBJECT_REQUEST] = self._handle_object_request
|
|
78
|
+
self.relay.message_handlers[Topic.OBJECT_RESPONSE] = self._handle_object_response
|
|
79
|
+
self.relay.message_handlers[Topic.ROUTE_REQUEST] = self._handle_route_request
|
|
80
|
+
self.relay.message_handlers[Topic.ROUTE] = self._handle_route
|
|
81
|
+
self.relay.message_handlers[Topic.LATEST_BLOCK_REQUEST] = self._handle_latest_block_request
|
|
82
|
+
self.relay.message_handlers[Topic.LATEST_BLOCK] = self._handle_latest_block
|
|
83
|
+
self.relay.message_handlers[Topic.TRANSACTION] = self._handle_transaction
|
|
84
|
+
self.relay.message_handlers[Topic.BLOCK_REQUEST] = self._handle_block_request
|
|
85
|
+
self.relay.message_handlers[Topic.BLOCK_RESPONSE] = self._handle_block_response
|
|
86
|
+
|
|
87
|
+
# Initialize latest block from storage if available
|
|
88
|
+
self._initialize_latest_block()
|
|
89
|
+
|
|
90
|
+
# Candidate chains that might be adopted
|
|
91
|
+
self.candidate_chains = {} # chain_id -> {'latest_block': block, 'timestamp': time.time()}
|
|
92
|
+
self.pending_blocks = {} # block_hash -> {'block': block, 'timestamp': time.time()}
|
|
93
|
+
|
|
94
|
+
# Threads for validation and chain monitoring
|
|
95
|
+
self.running = False
|
|
96
|
+
self.main_chain_validation_thread = None
|
|
97
|
+
self.candidate_chain_validation_thread = None
|
|
98
|
+
|
|
99
|
+
# Pending transactions for a block
|
|
100
|
+
self.pending_transactions = {} # tx_hash -> {'transaction': tx, 'timestamp': time.time()}
|
|
101
|
+
|
|
102
|
+
# Last block production attempt time
|
|
103
|
+
self.last_block_attempt_time = 0
|
|
132
104
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
#
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
#
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
#
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
105
|
+
def start(self):
|
|
106
|
+
"""Start the node."""
|
|
107
|
+
self.running = True
|
|
108
|
+
|
|
109
|
+
# Start relay
|
|
110
|
+
self.relay.start()
|
|
111
|
+
|
|
112
|
+
# Start chain monitoring thread
|
|
113
|
+
self.main_chain_validation_thread = threading.Thread(
|
|
114
|
+
target=self._main_chain_validation_loop,
|
|
115
|
+
name="MainChainValidation"
|
|
116
|
+
)
|
|
117
|
+
self.main_chain_validation_thread.daemon = True
|
|
118
|
+
self.main_chain_validation_thread.start()
|
|
119
|
+
|
|
120
|
+
self.candidate_chain_validation_thread = threading.Thread(
|
|
121
|
+
target=self._candidate_chain_validation_loop,
|
|
122
|
+
name="CandidateChainValidation"
|
|
123
|
+
)
|
|
124
|
+
self.candidate_chain_validation_thread.daemon = True
|
|
125
|
+
self.candidate_chain_validation_thread.start()
|
|
126
|
+
|
|
127
|
+
# Set up recurring block query tasks
|
|
128
|
+
main_query_thread = threading.Thread(
|
|
129
|
+
target=self._block_query_loop,
|
|
130
|
+
args=('main',),
|
|
131
|
+
daemon=True
|
|
132
|
+
)
|
|
133
|
+
main_query_thread.start()
|
|
134
|
+
|
|
135
|
+
validation_query_thread = threading.Thread(
|
|
136
|
+
target=self._block_query_loop,
|
|
137
|
+
args=('validation',),
|
|
138
|
+
daemon=True
|
|
139
|
+
)
|
|
140
|
+
validation_query_thread.start()
|
|
141
|
+
|
|
142
|
+
print(f"Node started with ID {self.node_id.hex()}")
|
|
171
143
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
144
|
+
def stop(self):
|
|
145
|
+
"""Stop the node and all its services."""
|
|
146
|
+
self.running = False
|
|
175
147
|
|
|
176
|
-
#
|
|
177
|
-
|
|
178
|
-
|
|
148
|
+
# Stop all threads
|
|
149
|
+
if self.main_chain_validation_thread and self.main_chain_validation_thread.is_alive():
|
|
150
|
+
self.main_chain_validation_thread.join(timeout=1.0)
|
|
179
151
|
|
|
180
|
-
|
|
181
|
-
|
|
152
|
+
if self.candidate_chain_validation_thread and self.candidate_chain_validation_thread.is_alive():
|
|
153
|
+
self.candidate_chain_validation_thread.join(timeout=1.0)
|
|
182
154
|
|
|
183
|
-
#
|
|
184
|
-
|
|
185
|
-
|
|
155
|
+
# Stop relay last
|
|
156
|
+
if self.relay:
|
|
157
|
+
self.relay.stop()
|
|
186
158
|
|
|
187
|
-
|
|
159
|
+
print("Node stopped")
|
|
188
160
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
#
|
|
197
|
-
|
|
198
|
-
|
|
161
|
+
def _main_chain_validation_loop(self):
|
|
162
|
+
"""
|
|
163
|
+
Main validation loop for the primary blockchain.
|
|
164
|
+
This thread prioritizes validating blocks on the main chain we're following.
|
|
165
|
+
"""
|
|
166
|
+
while self.running:
|
|
167
|
+
try:
|
|
168
|
+
# Update latest block if we don't have one yet
|
|
169
|
+
if not self.latest_block and hasattr(self.blockchain, 'get_latest_block'):
|
|
170
|
+
self.latest_block = self.blockchain.get_latest_block()
|
|
199
171
|
|
|
200
|
-
#
|
|
201
|
-
|
|
172
|
+
# Process any blocks that extend our main chain immediately
|
|
173
|
+
self._process_main_chain_blocks()
|
|
202
174
|
|
|
203
|
-
#
|
|
204
|
-
|
|
205
|
-
|
|
175
|
+
# Attempt block production if we are a validator
|
|
176
|
+
if self.is_validator and self.validator_address:
|
|
177
|
+
self._attempt_block_production()
|
|
206
178
|
|
|
207
|
-
#
|
|
208
|
-
|
|
179
|
+
# Cleanup old items
|
|
180
|
+
self._prune_pending_items()
|
|
209
181
|
|
|
210
|
-
#
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
182
|
+
# Sleep to prevent high CPU usage
|
|
183
|
+
time.sleep(0.1) # Short sleep for main chain validation
|
|
184
|
+
except Exception as e:
|
|
185
|
+
print(f"Error in main chain validation loop: {e}")
|
|
186
|
+
time.sleep(1) # Longer sleep on error
|
|
215
187
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
#
|
|
225
|
-
|
|
188
|
+
def _candidate_chain_validation_loop(self):
|
|
189
|
+
"""
|
|
190
|
+
Validation loop for candidate chains (potential forks).
|
|
191
|
+
This thread handles validation of blocks from alternate chains
|
|
192
|
+
without slowing down the main chain processing.
|
|
193
|
+
"""
|
|
194
|
+
while self.running:
|
|
195
|
+
try:
|
|
196
|
+
# Process candidate chains
|
|
197
|
+
self._evaluate_candidate_chains()
|
|
226
198
|
|
|
227
|
-
#
|
|
228
|
-
|
|
199
|
+
# Prune old candidate chains
|
|
200
|
+
self._prune_candidate_chains()
|
|
229
201
|
|
|
230
|
-
#
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
202
|
+
# Sleep longer for candidate chain validation (lower priority)
|
|
203
|
+
time.sleep(1) # Longer sleep for candidate chain validation
|
|
204
|
+
except Exception as e:
|
|
205
|
+
print(f"Error in candidate chain validation loop: {e}")
|
|
206
|
+
time.sleep(2) # Even longer sleep on error
|
|
235
207
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
#
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
208
|
+
def _prune_pending_items(self):
|
|
209
|
+
"""Remove old pending blocks and transactions."""
|
|
210
|
+
current_time = time.time()
|
|
211
|
+
|
|
212
|
+
# Prune old pending blocks (older than 1 hour)
|
|
213
|
+
blocks_to_remove = [
|
|
214
|
+
block_hash for block_hash, data in self.pending_blocks.items()
|
|
215
|
+
if current_time - data['timestamp'] > 3600 # 1 hour
|
|
216
|
+
]
|
|
217
|
+
for block_hash in blocks_to_remove:
|
|
218
|
+
del self.pending_blocks[block_hash]
|
|
247
219
|
|
|
248
|
-
#
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
220
|
+
# Prune old pending transactions (older than 30 minutes)
|
|
221
|
+
txs_to_remove = [
|
|
222
|
+
tx_hash for tx_hash, data in self.pending_transactions.items()
|
|
223
|
+
if current_time - data['timestamp'] > 1800 # 30 minutes
|
|
224
|
+
]
|
|
225
|
+
for tx_hash in txs_to_remove:
|
|
226
|
+
del self.pending_transactions[tx_hash]
|
|
255
227
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
#
|
|
262
|
-
|
|
263
|
-
|
|
228
|
+
def _process_main_chain_blocks(self):
|
|
229
|
+
"""
|
|
230
|
+
Process blocks that extend our current main chain.
|
|
231
|
+
Prioritizes blocks that build on our latest block.
|
|
232
|
+
"""
|
|
233
|
+
# Skip if we don't have a latest block yet
|
|
234
|
+
if not self.latest_block:
|
|
235
|
+
return
|
|
264
236
|
|
|
265
|
-
#
|
|
266
|
-
|
|
237
|
+
# Get the hash of our latest block
|
|
238
|
+
latest_hash = self.latest_block.get_hash()
|
|
267
239
|
|
|
268
|
-
#
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
240
|
+
# Find any pending blocks that build on our latest block
|
|
241
|
+
main_chain_blocks = []
|
|
242
|
+
for block_hash, data in list(self.pending_blocks.items()):
|
|
243
|
+
block = data['block']
|
|
272
244
|
|
|
273
|
-
#
|
|
274
|
-
|
|
275
|
-
|
|
245
|
+
# Check if this block extends our latest block
|
|
246
|
+
if block.previous == latest_hash:
|
|
247
|
+
main_chain_blocks.append(block)
|
|
276
248
|
|
|
277
|
-
#
|
|
278
|
-
|
|
279
|
-
|
|
249
|
+
# Process found blocks
|
|
250
|
+
for block in main_chain_blocks:
|
|
251
|
+
self._validate_and_process_main_chain_block(block)
|
|
280
252
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
#
|
|
290
|
-
|
|
253
|
+
def _validate_and_process_main_chain_block(self, block: Block):
|
|
254
|
+
"""
|
|
255
|
+
Validate and process a block that extends our main chain.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
block: Block to validate and process
|
|
259
|
+
"""
|
|
260
|
+
try:
|
|
261
|
+
# Validate block
|
|
262
|
+
is_valid = validate_block(block, self.blockchain.get_accounts_at_block(block.previous), self.blockchain.get_blocks())
|
|
291
263
|
|
|
292
|
-
|
|
293
|
-
#
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
264
|
+
if is_valid:
|
|
265
|
+
# Apply block to our state
|
|
266
|
+
success = validate_and_apply_block(self.blockchain, block)
|
|
267
|
+
if success:
|
|
268
|
+
print(f"Applied valid block {block.number} to blockchain state")
|
|
269
|
+
self._update_latest_block(block)
|
|
270
|
+
blocks_to_remove = [block.get_hash()]
|
|
271
|
+
for block_hash in blocks_to_remove:
|
|
272
|
+
if block_hash in self.pending_blocks:
|
|
273
|
+
del self.pending_blocks[block_hash]
|
|
274
|
+
print(f"Added block {block.number} to blockchain")
|
|
275
|
+
return True
|
|
276
|
+
except Exception as e:
|
|
277
|
+
print(f"Error validating main chain block {block.number}: {e}")
|
|
306
278
|
|
|
307
|
-
|
|
279
|
+
return False
|
|
308
280
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
#
|
|
315
|
-
|
|
316
|
-
|
|
281
|
+
def _evaluate_candidate_chains(self):
|
|
282
|
+
"""
|
|
283
|
+
Evaluate candidate chains to determine if any should become our main chain.
|
|
284
|
+
This will validate pending blocks and look for chains with higher cumulative difficulty.
|
|
285
|
+
"""
|
|
286
|
+
# Skip if no candidate chains
|
|
287
|
+
if not self.candidate_chains:
|
|
288
|
+
return
|
|
317
289
|
|
|
318
|
-
#
|
|
319
|
-
|
|
320
|
-
|
|
290
|
+
# For each candidate chain, validate blocks and calculate metrics
|
|
291
|
+
for chain_id, data in list(self.candidate_chains.items()):
|
|
292
|
+
latest_candidate_block = data['latest_block']
|
|
321
293
|
|
|
322
|
-
#
|
|
323
|
-
|
|
294
|
+
# Build the chain backwards
|
|
295
|
+
chain_blocks = self._build_chain_from_latest(latest_candidate_block)
|
|
324
296
|
|
|
325
|
-
#
|
|
326
|
-
|
|
327
|
-
|
|
297
|
+
# Skip if we couldn't build a complete chain
|
|
298
|
+
if not chain_blocks:
|
|
299
|
+
continue
|
|
328
300
|
|
|
329
|
-
#
|
|
330
|
-
|
|
301
|
+
# Validate the entire chain
|
|
302
|
+
valid_chain = self._validate_candidate_chain(chain_blocks)
|
|
331
303
|
|
|
332
|
-
#
|
|
333
|
-
|
|
334
|
-
|
|
304
|
+
# If valid and better than our current chain, switch to it
|
|
305
|
+
if valid_chain and self._is_better_chain(chain_blocks):
|
|
306
|
+
self._switch_to_new_chain(chain_blocks)
|
|
335
307
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
308
|
+
def _build_chain_from_latest(self, latest_block: Block) -> List[Block]:
|
|
309
|
+
"""
|
|
310
|
+
Build a chain from the latest block back to a known point in our blockchain.
|
|
339
311
|
|
|
340
|
-
|
|
341
|
-
|
|
312
|
+
Args:
|
|
313
|
+
latest_block: Latest block in the candidate chain
|
|
342
314
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
#
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
#
|
|
353
|
-
#
|
|
354
|
-
#
|
|
355
|
-
#
|
|
356
|
-
|
|
357
|
-
|
|
315
|
+
Returns:
|
|
316
|
+
List of blocks in the chain, ordered from oldest to newest
|
|
317
|
+
"""
|
|
318
|
+
chain_blocks = [latest_block]
|
|
319
|
+
current_block = latest_block
|
|
320
|
+
|
|
321
|
+
# Track visited blocks to avoid cycles
|
|
322
|
+
visited = {current_block.get_hash()}
|
|
323
|
+
|
|
324
|
+
# Build chain backwards until we either:
|
|
325
|
+
# 1. Find a block in our main chain
|
|
326
|
+
# 2. Run out of blocks
|
|
327
|
+
# 3. Detect a cycle
|
|
328
|
+
while current_block.number > 0:
|
|
329
|
+
previous_hash = current_block.previous
|
|
358
330
|
|
|
359
|
-
#
|
|
360
|
-
|
|
361
|
-
#
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
331
|
+
# Check if we have this block in our blockchain
|
|
332
|
+
if hasattr(self.blockchain, 'has_block') and self.blockchain.has_block(previous_hash):
|
|
333
|
+
# Found connection to our main chain
|
|
334
|
+
previous_block = self.blockchain.get_block(previous_hash)
|
|
335
|
+
chain_blocks.insert(0, previous_block)
|
|
336
|
+
break
|
|
365
337
|
|
|
366
|
-
#
|
|
367
|
-
|
|
368
|
-
|
|
338
|
+
# Check if block is in pending blocks
|
|
339
|
+
elif previous_hash in self.pending_blocks:
|
|
340
|
+
previous_block = self.pending_blocks[previous_hash]['block']
|
|
369
341
|
|
|
370
|
-
#
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
342
|
+
# Check for cycles
|
|
343
|
+
if previous_hash in visited:
|
|
344
|
+
print(f"Cycle detected in candidate chain at block {previous_block.number}")
|
|
345
|
+
return []
|
|
374
346
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
#
|
|
380
|
-
|
|
381
|
-
|
|
347
|
+
visited.add(previous_hash)
|
|
348
|
+
chain_blocks.insert(0, previous_block)
|
|
349
|
+
current_block = previous_block
|
|
350
|
+
else:
|
|
351
|
+
# Missing block, cannot validate the chain
|
|
352
|
+
print(f"Missing block {previous_hash.hex()} in candidate chain")
|
|
353
|
+
return []
|
|
382
354
|
|
|
383
|
-
|
|
355
|
+
return chain_blocks
|
|
384
356
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
357
|
+
def _validate_candidate_chain(self, chain_blocks: List[Block]) -> bool:
|
|
358
|
+
"""
|
|
359
|
+
Validate a candidate chain of blocks.
|
|
388
360
|
|
|
389
|
-
|
|
390
|
-
|
|
361
|
+
Args:
|
|
362
|
+
chain_blocks: List of blocks in the chain (oldest to newest)
|
|
391
363
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
#
|
|
396
|
-
|
|
397
|
-
#
|
|
398
|
-
|
|
399
|
-
|
|
364
|
+
Returns:
|
|
365
|
+
True if the chain is valid, False otherwise
|
|
366
|
+
"""
|
|
367
|
+
# Validate each block in the chain
|
|
368
|
+
for i, block in enumerate(chain_blocks):
|
|
369
|
+
# Skip first block, it's either genesis or a block we already have
|
|
370
|
+
if i == 0:
|
|
371
|
+
continue
|
|
400
372
|
|
|
401
|
-
#
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
373
|
+
# Validate block connections
|
|
374
|
+
if block.previous != chain_blocks[i-1].get_hash():
|
|
375
|
+
print(f"Invalid chain: block {block.number} does not reference previous block")
|
|
376
|
+
return False
|
|
405
377
|
|
|
406
|
-
#
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
378
|
+
# Validate block
|
|
379
|
+
is_valid = validate_block(block, self.blockchain.get_accounts_at_block(block.previous), self.blockchain.get_blocks())
|
|
380
|
+
if not is_valid:
|
|
381
|
+
print(f"Invalid chain: block {block.number} is invalid")
|
|
382
|
+
return False
|
|
411
383
|
|
|
412
|
-
|
|
384
|
+
return True
|
|
413
385
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
386
|
+
def _is_better_chain(self, chain_blocks: List[Block]) -> bool:
|
|
387
|
+
"""
|
|
388
|
+
Determine if a candidate chain is better than our current chain.
|
|
417
389
|
|
|
418
|
-
|
|
419
|
-
|
|
390
|
+
Args:
|
|
391
|
+
chain_blocks: List of blocks in the candidate chain
|
|
420
392
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
#
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
#
|
|
428
|
-
|
|
429
|
-
|
|
393
|
+
Returns:
|
|
394
|
+
True if the candidate chain is better, False otherwise
|
|
395
|
+
"""
|
|
396
|
+
# Get the latest block from the candidate chain
|
|
397
|
+
candidate_latest = chain_blocks[-1]
|
|
398
|
+
|
|
399
|
+
# If we don't have a latest block, any valid chain is better
|
|
400
|
+
if not self.latest_block:
|
|
401
|
+
return True
|
|
430
402
|
|
|
431
|
-
#
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
403
|
+
# Compare block numbers (longest chain rule)
|
|
404
|
+
if candidate_latest.number > self.latest_block.number:
|
|
405
|
+
print(f"Candidate chain is longer: {candidate_latest.number} vs {self.latest_block.number}")
|
|
406
|
+
return True
|
|
435
407
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
#
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
#
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
408
|
+
return False
|
|
409
|
+
|
|
410
|
+
def _switch_to_new_chain(self, chain_blocks: List[Block]):
|
|
411
|
+
"""
|
|
412
|
+
Switch to a new chain by adding all blocks to our blockchain.
|
|
413
|
+
|
|
414
|
+
Args:
|
|
415
|
+
chain_blocks: List of blocks in the chain (oldest to newest)
|
|
416
|
+
"""
|
|
417
|
+
# Find the point where the chains diverge
|
|
418
|
+
divergence_point = 0
|
|
419
|
+
for i, block in enumerate(chain_blocks):
|
|
420
|
+
# Check if we have this block in our blockchain
|
|
421
|
+
if hasattr(self.blockchain, 'has_block') and self.blockchain.has_block(block.get_hash()):
|
|
422
|
+
divergence_point = i + 1
|
|
423
|
+
else:
|
|
424
|
+
break
|
|
453
425
|
|
|
454
|
-
#
|
|
455
|
-
|
|
456
|
-
|
|
426
|
+
# Add all blocks after the divergence point
|
|
427
|
+
for i in range(divergence_point, len(chain_blocks)):
|
|
428
|
+
block = chain_blocks[i]
|
|
457
429
|
|
|
458
|
-
#
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
430
|
+
# Add block to blockchain
|
|
431
|
+
if hasattr(self.blockchain, 'add_block'):
|
|
432
|
+
try:
|
|
433
|
+
self.blockchain.add_block(block)
|
|
462
434
|
|
|
463
|
-
#
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
435
|
+
# Remove from pending blocks
|
|
436
|
+
block_hash = block.get_hash()
|
|
437
|
+
if block_hash in self.pending_blocks:
|
|
438
|
+
del self.pending_blocks[block_hash]
|
|
467
439
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
440
|
+
print(f"Added block {block.number} to blockchain")
|
|
441
|
+
except Exception as e:
|
|
442
|
+
print(f"Error adding block {block.number} to blockchain: {e}")
|
|
443
|
+
return
|
|
472
444
|
|
|
473
|
-
#
|
|
474
|
-
|
|
475
|
-
|
|
445
|
+
# Update latest block
|
|
446
|
+
self._update_latest_block(chain_blocks[-1])
|
|
447
|
+
print(f"Switched to new chain, latest block: {self.latest_block.number}")
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Validation module for the Astreum blockchain.
|
|
3
|
-
|
|
4
|
-
This module provides functions for validating blocks and transactions,
|
|
5
|
-
computing and verifying VDFs, and selecting validators.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
# Export validation functions
|
|
9
|
-
from .block import (
|
|
10
|
-
validate_block,
|
|
11
|
-
create_block,
|
|
12
|
-
create_genesis_block,
|
|
13
|
-
select_validator,
|
|
14
|
-
select_validator_for_slot
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
# Export VDF functions
|
|
18
|
-
from .vdf import (
|
|
19
|
-
compute_vdf,
|
|
20
|
-
verify_vdf,
|
|
21
|
-
validate_block_vdf
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
# Export account functions
|
|
25
|
-
from .account import (
|
|
26
|
-
Account,
|
|
27
|
-
get_validator_stake,
|
|
28
|
-
is_validator
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
# Export constants
|
|
32
|
-
from .constants import (
|
|
33
|
-
VALIDATION_ADDRESS,
|
|
34
|
-
BURN_ADDRESS,
|
|
35
|
-
MIN_STAKE_AMOUNT,
|
|
36
|
-
SLOT_DURATION,
|
|
37
|
-
VDF_DIFFICULTY
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
# Export blockchain state functions
|
|
41
|
-
from .state import (
|
|
42
|
-
add_block_to_state,
|
|
43
|
-
validate_and_apply_block,
|
|
44
|
-
create_account_state,
|
|
45
|
-
get_validator_for_slot,
|
|
46
|
-
select_best_chain,
|
|
47
|
-
compare_chains,
|
|
48
|
-
get_validator_set
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
__all__ = [
|
|
52
|
-
# Block validation
|
|
53
|
-
'validate_block',
|
|
54
|
-
'create_block',
|
|
55
|
-
'create_genesis_block',
|
|
56
|
-
'select_validator',
|
|
57
|
-
'select_validator_for_slot',
|
|
58
|
-
|
|
59
|
-
# VDF functions
|
|
60
|
-
'compute_vdf',
|
|
61
|
-
'verify_vdf',
|
|
62
|
-
'validate_block_vdf',
|
|
63
|
-
|
|
64
|
-
# Account functions
|
|
65
|
-
'Account',
|
|
66
|
-
'get_validator_stake',
|
|
67
|
-
'is_validator',
|
|
68
|
-
|
|
69
|
-
# Constants
|
|
70
|
-
'VALIDATION_ADDRESS',
|
|
71
|
-
'BURN_ADDRESS',
|
|
72
|
-
'MIN_STAKE_AMOUNT',
|
|
73
|
-
'SLOT_DURATION',
|
|
74
|
-
'VDF_DIFFICULTY',
|
|
75
|
-
|
|
76
|
-
# Blockchain state
|
|
77
|
-
'add_block_to_state',
|
|
78
|
-
'validate_and_apply_block',
|
|
79
|
-
'create_account_state',
|
|
80
|
-
'get_validator_for_slot',
|
|
81
|
-
'select_best_chain',
|
|
82
|
-
'compare_chains',
|
|
83
|
-
'get_validator_set'
|
|
84
|
-
]
|
astreum/node/validation/block.py
CHANGED
|
@@ -1,29 +1,20 @@
|
|
|
1
1
|
class Block:
|
|
2
|
+
|
|
3
|
+
def __init__(self):
|
|
4
|
+
pass
|
|
5
|
+
|
|
2
6
|
@classmethod
|
|
3
|
-
def from_bytes(cls
|
|
7
|
+
def from_bytes(cls) -> 'Block':
|
|
4
8
|
"""
|
|
5
|
-
Deserialize
|
|
6
|
-
|
|
7
|
-
Expected format: [balance, code, counter, data]
|
|
8
|
-
|
|
9
|
-
The public_key (and optional secret_key) must be provided separately.
|
|
9
|
+
Deserialize a block from its byte representation.
|
|
10
10
|
"""
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
return cls(public_key, balance, code, counter, account_data, secret_key=secret_key)
|
|
14
|
-
|
|
11
|
+
return cls()
|
|
12
|
+
|
|
15
13
|
def to_bytes(self) -> bytes:
|
|
16
14
|
"""
|
|
17
|
-
Serialize the
|
|
18
|
-
|
|
19
|
-
Format: [balance, code, counter, data]
|
|
15
|
+
Serialize the block into bytes.
|
|
20
16
|
"""
|
|
21
|
-
return
|
|
22
|
-
self.balance,
|
|
23
|
-
self.code,
|
|
24
|
-
self.counter,
|
|
25
|
-
self.data
|
|
26
|
-
])
|
|
17
|
+
return b""
|
|
27
18
|
|
|
28
19
|
class Chain:
|
|
29
20
|
def __init__(self, latest_block: Block):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: astreum
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.19
|
|
4
4
|
Summary: Python library to interact with the Astreum blockchain and its Lispeum virtual machine.
|
|
5
5
|
Author-email: "Roy R. O. Okello" <roy@stelar.xyz>
|
|
6
6
|
Project-URL: Homepage, https://github.com/astreum/lib
|
|
@@ -21,7 +21,7 @@ astreum/lispeum/special/number/addition.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5
|
|
|
21
21
|
astreum/machine/__init__.py,sha256=GOdZl1tS9uIJHbq5WVcplifMDPDLQroX7CVew-K2YbA,15262
|
|
22
22
|
astreum/machine/environment.py,sha256=K0084U6B7wwjrDZ9b2_7cEcbBzsB7UOy_Zpbrr7B3GY,834
|
|
23
23
|
astreum/machine/error.py,sha256=MvqBaZZt33rNELNhUJ2lER3TE3aS8WVqsWF2hz2AwoA,38
|
|
24
|
-
astreum/node/__init__.py,sha256=
|
|
24
|
+
astreum/node/__init__.py,sha256=obb-ap7YuRrMblNN7IAK79xEq__9aNBVzt1ciSTMvoU,18631
|
|
25
25
|
astreum/node/utils.py,sha256=amGhNYHVMjvAO-9vBRAcim-S5LlLSRudqooBN-XPdm4,702
|
|
26
26
|
astreum/node/crypto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
27
|
astreum/node/crypto/ed25519.py,sha256=FRnvlN0kZlxn4j-sJKl-C9tqiz_0z4LZyXLj3KIj1TQ,1760
|
|
@@ -37,22 +37,21 @@ astreum/node/storage/merkle.py,sha256=sC7gfxPDUBgv3iiFs5PyxPgYbC4CoE-If5TQvkuoTG
|
|
|
37
37
|
astreum/node/storage/patricia.py,sha256=zP4whShdB7yOFcEPLE1-1PIFfx_LdK8DyMSadnF0KT0,11588
|
|
38
38
|
astreum/node/storage/storage.py,sha256=czJDhRK2rQxjOo88fhZ6j10f55RW8hWp1qq7c4yur6Y,10086
|
|
39
39
|
astreum/node/storage/utils.py,sha256=CxKH9GbW31aVYs2Hwg1SirCykSnH_3_JisEayDrpOvY,5169
|
|
40
|
-
astreum/node/validation/__init__.py,sha256=
|
|
40
|
+
astreum/node/validation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
41
|
astreum/node/validation/account.py,sha256=mKbJSi3sfQ5K9a4zwgdyPGqT3gXHekI55yDDVpzcolo,3650
|
|
42
|
-
astreum/node/validation/block.py,sha256=
|
|
42
|
+
astreum/node/validation/block.py,sha256=KRvlpLZOmgJ3WJaSHsbk7ryh2Z1NsC2z-RWKWh1quBE,444
|
|
43
43
|
astreum/node/validation/constants.py,sha256=ImIdLZFtMKx1iWg60YssEKl2tdDqZQnIa2JaJE6CX0o,422
|
|
44
44
|
astreum/node/validation/stake.py,sha256=Z9EPM-X9c92fpsZIYsdVpvgz4DhxQViPM-RDktWUZq8,7141
|
|
45
|
-
astreum/node/validation/state.py,sha256=QMXY7h4da-o79wMjJW93ixtepyhgcbJEf101yVuZurg,7661
|
|
46
45
|
astreum/node/validation/transaction.py,sha256=Lovx1CWIxL45glFU-LBDxI1argBLxSmDAcg1TteaOlY,4701
|
|
47
46
|
astreum/node/validation/vdf.py,sha256=HDdnqn9O_LicfE7SNCmncawKoR-ojLyjlpn74OvRNOU,2194
|
|
48
|
-
astreum/node/validation/_block/__init__.py,sha256=
|
|
47
|
+
astreum/node/validation/_block/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
49
48
|
astreum/node/validation/_block/create.py,sha256=apD9h92b9Y146zeppSzKNk_NJPgyBy7FhxoEkypQQNk,2515
|
|
50
49
|
astreum/node/validation/_block/model.py,sha256=d7x3_tX2MPLnGODL_5_vNjQfLdYa405blc-zL9o8HFA,2589
|
|
51
50
|
astreum/node/validation/_block/validate.py,sha256=niLexCNhEwUJLclyrdNZSvHcVa_J6jlu7J3FiWY7XBU,6232
|
|
52
51
|
astreum/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
53
52
|
astreum/utils/bytes_format.py,sha256=X4tG5GGPweNCE54bHYkLFiuLTbmpy5upO_s1Cef-MGA,2711
|
|
54
|
-
astreum-0.1.
|
|
55
|
-
astreum-0.1.
|
|
56
|
-
astreum-0.1.
|
|
57
|
-
astreum-0.1.
|
|
58
|
-
astreum-0.1.
|
|
53
|
+
astreum-0.1.19.dist-info/licenses/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
|
|
54
|
+
astreum-0.1.19.dist-info/METADATA,sha256=MvTCByomvRA9Tf_iCyOyxeGCsSnNyHSrtoRChhbx0ls,3312
|
|
55
|
+
astreum-0.1.19.dist-info/WHEEL,sha256=wXxTzcEDnjrTwFYjLPcsW_7_XihufBwmpiBeiXNBGEA,91
|
|
56
|
+
astreum-0.1.19.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
|
|
57
|
+
astreum-0.1.19.dist-info/RECORD,,
|
astreum/node/validation/state.py
DELETED
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Blockchain state management.
|
|
3
|
-
|
|
4
|
-
This module manages the blockchain state, including accounts, blocks, and
|
|
5
|
-
transactions.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import time
|
|
9
|
-
from typing import Dict, List, Optional, Set, Any
|
|
10
|
-
from dataclasses import dataclass, field
|
|
11
|
-
from ..utils import hash_data
|
|
12
|
-
from ..models import Block, Transaction, Account as ModelAccount
|
|
13
|
-
from .account import Account
|
|
14
|
-
from .constants import VALIDATION_ADDRESS, BURN_ADDRESS, MIN_STAKE_AMOUNT
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class BlockchainState:
|
|
18
|
-
"""
|
|
19
|
-
Manages the state of the blockchain.
|
|
20
|
-
|
|
21
|
-
This class tracks the current state of accounts, blocks, and transactions,
|
|
22
|
-
and provides methods to update the state with new blocks and transactions.
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
def __init__(self, config: Optional[dict] = None):
|
|
26
|
-
"""
|
|
27
|
-
Initialize blockchain state.
|
|
28
|
-
|
|
29
|
-
Args:
|
|
30
|
-
config: Optional configuration
|
|
31
|
-
"""
|
|
32
|
-
self.config = config or {}
|
|
33
|
-
|
|
34
|
-
# Dictionaries to track blockchain state
|
|
35
|
-
self.accounts = {} # address -> Account
|
|
36
|
-
self.blocks = {} # hash -> Block
|
|
37
|
-
self.transactions = {} # hash -> Transaction
|
|
38
|
-
|
|
39
|
-
# Track the latest block
|
|
40
|
-
self.latest_block = None
|
|
41
|
-
|
|
42
|
-
# Pending transactions
|
|
43
|
-
self.pending_transactions = set() # Set of transaction hashes
|
|
44
|
-
|
|
45
|
-
# State of validators and stakes
|
|
46
|
-
self.validators = {} # address -> stake amount
|
|
47
|
-
|
|
48
|
-
# Initialize the genesis block if not provided
|
|
49
|
-
if not self.latest_block:
|
|
50
|
-
self._initialize_genesis()
|
|
51
|
-
|
|
52
|
-
def _initialize_genesis(self):
|
|
53
|
-
"""Initialize the genesis block and state."""
|
|
54
|
-
# In a full implementation, this would initialize the genesis
|
|
55
|
-
# block and state from configuration
|
|
56
|
-
print("Initializing genesis block and state")
|
|
57
|
-
|
|
58
|
-
def add_block(self, block: Block) -> bool:
|
|
59
|
-
"""
|
|
60
|
-
Add a block to the blockchain state.
|
|
61
|
-
|
|
62
|
-
Args:
|
|
63
|
-
block: Block to add
|
|
64
|
-
|
|
65
|
-
Returns:
|
|
66
|
-
True if block was added successfully, False otherwise
|
|
67
|
-
"""
|
|
68
|
-
# Convert block to validation format directly
|
|
69
|
-
validation_block = {
|
|
70
|
-
'number': block.number,
|
|
71
|
-
'timestamp': block.time,
|
|
72
|
-
'producer': block.validator.public_key if block.validator else b'',
|
|
73
|
-
'previous': block.previous.get_hash() if block.previous else b'',
|
|
74
|
-
'transactions': self._extract_transactions(block),
|
|
75
|
-
'vdf_proof': block.signature[:8], # Use part of signature as VDF proof for demo
|
|
76
|
-
'signature': block.signature
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
# Check for duplicate (already processed) blocks
|
|
80
|
-
block_hash = block.get_hash()
|
|
81
|
-
if block_hash in self.blocks:
|
|
82
|
-
print(f"Block {block_hash.hex()} already in blockchain")
|
|
83
|
-
return True
|
|
84
|
-
|
|
85
|
-
# Convert block's accounts to validation accounts
|
|
86
|
-
account_dict = {}
|
|
87
|
-
# Here we would deserialize the accounts data from the block
|
|
88
|
-
# In a real implementation, this would reconstruct accounts from serialized data
|
|
89
|
-
|
|
90
|
-
# For now, we'll just log that we would process the block
|
|
91
|
-
print(f"Processing block at height {block.number}")
|
|
92
|
-
|
|
93
|
-
# Add the block to our state
|
|
94
|
-
self.blocks[block_hash] = block
|
|
95
|
-
|
|
96
|
-
# Update latest block if this is a new latest block
|
|
97
|
-
if not self.latest_block or block.number > self.latest_block.number:
|
|
98
|
-
self.latest_block = block
|
|
99
|
-
|
|
100
|
-
# Process transactions in the block
|
|
101
|
-
# This would update account states, apply transaction effects, etc.
|
|
102
|
-
|
|
103
|
-
return True
|
|
104
|
-
|
|
105
|
-
def _extract_transactions(self, block: Block) -> List[dict]:
|
|
106
|
-
"""
|
|
107
|
-
Extract transactions from a block.
|
|
108
|
-
|
|
109
|
-
Args:
|
|
110
|
-
block: The model Block instance
|
|
111
|
-
|
|
112
|
-
Returns:
|
|
113
|
-
List of transactions in validation format
|
|
114
|
-
"""
|
|
115
|
-
transactions = []
|
|
116
|
-
# Parse transaction data from the block
|
|
117
|
-
# In a real implementation, this would deserialize the transactions field
|
|
118
|
-
# For now, we'll return an empty list as a placeholder
|
|
119
|
-
return transactions
|
|
120
|
-
|
|
121
|
-
def add_transaction(self, transaction: Transaction) -> bool:
|
|
122
|
-
"""
|
|
123
|
-
Add a transaction to the pending set.
|
|
124
|
-
|
|
125
|
-
Args:
|
|
126
|
-
transaction: Transaction to add
|
|
127
|
-
|
|
128
|
-
Returns:
|
|
129
|
-
True if transaction was added successfully, False otherwise
|
|
130
|
-
"""
|
|
131
|
-
# Convert transaction to validation format directly
|
|
132
|
-
validation_tx = {
|
|
133
|
-
'sender': transaction.sender.public_key if transaction.sender else b'',
|
|
134
|
-
'recipient': transaction.receipient.public_key if transaction.receipient else b'',
|
|
135
|
-
'amount': transaction.amount,
|
|
136
|
-
'counter': transaction.counter,
|
|
137
|
-
'data': transaction.data,
|
|
138
|
-
'signature': transaction.signature
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
# Generate a transaction hash
|
|
142
|
-
tx_hash = hash_data(str(validation_tx).encode())
|
|
143
|
-
|
|
144
|
-
# Check for duplicate transactions
|
|
145
|
-
if tx_hash in self.transactions or tx_hash in self.pending_transactions:
|
|
146
|
-
print(f"Transaction {tx_hash.hex()} already processed or pending")
|
|
147
|
-
return False
|
|
148
|
-
|
|
149
|
-
# Validate the transaction
|
|
150
|
-
# In a real implementation, this would check signature, sender balance, etc.
|
|
151
|
-
|
|
152
|
-
# Add to pending transactions
|
|
153
|
-
self.pending_transactions.add(tx_hash)
|
|
154
|
-
|
|
155
|
-
return True
|
|
156
|
-
|
|
157
|
-
def is_staking_transaction(self, tx: Transaction) -> bool:
|
|
158
|
-
"""
|
|
159
|
-
Check if a transaction is a staking transaction.
|
|
160
|
-
|
|
161
|
-
Args:
|
|
162
|
-
tx: The model Transaction instance
|
|
163
|
-
|
|
164
|
-
Returns:
|
|
165
|
-
True if this is a staking transaction, False otherwise
|
|
166
|
-
"""
|
|
167
|
-
# A transaction is a staking transaction if it's sending to the validation address
|
|
168
|
-
if tx.receipient and hasattr(tx.receipient, 'public_key'):
|
|
169
|
-
return tx.receipient.public_key == VALIDATION_ADDRESS
|
|
170
|
-
return False
|
|
171
|
-
|
|
172
|
-
def get_account(self, address: bytes) -> Optional[Account]:
|
|
173
|
-
"""
|
|
174
|
-
Get an account by address.
|
|
175
|
-
|
|
176
|
-
Args:
|
|
177
|
-
address: Account address
|
|
178
|
-
|
|
179
|
-
Returns:
|
|
180
|
-
Account if found, None otherwise
|
|
181
|
-
"""
|
|
182
|
-
return self.accounts.get(address)
|
|
183
|
-
|
|
184
|
-
def get_validator_stake(self, address: bytes) -> int:
|
|
185
|
-
"""
|
|
186
|
-
Get the stake of a validator.
|
|
187
|
-
|
|
188
|
-
Args:
|
|
189
|
-
address: Validator address
|
|
190
|
-
|
|
191
|
-
Returns:
|
|
192
|
-
Stake amount (0 if not a validator)
|
|
193
|
-
"""
|
|
194
|
-
return self.validators.get(address, 0)
|
|
195
|
-
|
|
196
|
-
def is_validator(self, address: bytes) -> bool:
|
|
197
|
-
"""
|
|
198
|
-
Check if an address is a validator.
|
|
199
|
-
|
|
200
|
-
Args:
|
|
201
|
-
address: Address to check
|
|
202
|
-
|
|
203
|
-
Returns:
|
|
204
|
-
True if address is a validator, False otherwise
|
|
205
|
-
"""
|
|
206
|
-
return self.get_validator_stake(address) >= MIN_STAKE_AMOUNT
|
|
207
|
-
|
|
208
|
-
def get_pending_transactions(self) -> List[Transaction]:
|
|
209
|
-
"""
|
|
210
|
-
Get all pending transactions.
|
|
211
|
-
|
|
212
|
-
Returns:
|
|
213
|
-
List of pending transactions
|
|
214
|
-
"""
|
|
215
|
-
# In a real implementation, this would return the actual transaction objects
|
|
216
|
-
# For now, we'll just return an empty list
|
|
217
|
-
return []
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
def create_blockchain(config: Optional[dict] = None) -> BlockchainState:
|
|
221
|
-
"""
|
|
222
|
-
Create a new blockchain state.
|
|
223
|
-
|
|
224
|
-
Args:
|
|
225
|
-
config: Optional configuration
|
|
226
|
-
|
|
227
|
-
Returns:
|
|
228
|
-
New BlockchainState instance
|
|
229
|
-
"""
|
|
230
|
-
return BlockchainState(config)
|
|
File without changes
|
|
File without changes
|