6
6
from enum import Enum
7
7
from typing import Dict , List , Optional , Set , Tuple , Union
8
8
9
+ from clvm .casts import int_from_bytes
10
+
9
11
from salvia .consensus .block_body_validation import validate_block_body
10
12
from salvia .consensus .block_header_validation import validate_finished_header_block , validate_unfinished_header_block
11
13
from salvia .consensus .block_record import BlockRecord
18
20
from salvia .consensus .multiprocess_validation import PreValidationResult , pre_validate_blocks_multiprocessing
19
21
from salvia .full_node .block_store import BlockStore
20
22
from salvia .full_node .coin_store import CoinStore
23
+ from salvia .full_node .hint_store import HintStore
21
24
from salvia .full_node .mempool_check_conditions import get_name_puzzle_conditions
22
25
from salvia .types .blockchain_format .coin import Coin
23
26
from salvia .types .blockchain_format .sized_bytes import bytes32
24
27
from salvia .types .blockchain_format .sub_epoch_summary import SubEpochSummary
25
28
from salvia .types .blockchain_format .vdf import VDFInfo
26
29
from salvia .types .coin_record import CoinRecord
30
+ from salvia .types .condition_opcodes import ConditionOpcode
27
31
from salvia .types .end_of_slot_bundle import EndOfSubSlotBundle
28
32
from salvia .types .full_block import FullBlock
29
33
from salvia .types .generator_types import BlockGenerator , GeneratorArg
@@ -83,12 +87,11 @@ class Blockchain(BlockchainInterface):
83
87
# Lock to prevent simultaneous reads and writes
84
88
lock : asyncio .Lock
85
89
compact_proof_lock : asyncio .Lock
90
+ hint_store : HintStore
86
91
87
92
@staticmethod
88
93
async def create (
89
- coin_store : CoinStore ,
90
- block_store : BlockStore ,
91
- consensus_constants : ConsensusConstants ,
94
+ coin_store : CoinStore , block_store : BlockStore , consensus_constants : ConsensusConstants , hint_store : HintStore
92
95
):
93
96
"""
94
97
Initializes a blockchain with the BlockRecords from disk, assuming they have all been
@@ -112,6 +115,7 @@ async def create(
112
115
self ._shut_down = False
113
116
await self ._load_chain_from_store ()
114
117
self ._seen_compact_proofs = set ()
118
+ self .hint_store = hint_store
115
119
return self
116
120
117
121
def shut_down (self ):
@@ -164,7 +168,12 @@ async def receive_block(
164
168
block : FullBlock ,
165
169
pre_validation_result : Optional [PreValidationResult ] = None ,
166
170
fork_point_with_peak : Optional [uint32 ] = None ,
167
- ) -> Tuple [ReceiveBlockResult , Optional [Err ], Optional [uint32 ], List [CoinRecord ]]:
171
+ ) -> Tuple [
172
+ ReceiveBlockResult ,
173
+ Optional [Err ],
174
+ Optional [uint32 ],
175
+ Tuple [List [CoinRecord ], Dict [bytes , Dict [bytes32 , CoinRecord ]]],
176
+ ]:
168
177
"""
169
178
This method must be called under the blockchain lock
170
179
Adds a new block into the blockchain, if it's valid and connected to the current
@@ -174,18 +183,13 @@ async def receive_block(
174
183
"""
175
184
genesis : bool = block .height == 0
176
185
if self .contains_block (block .header_hash ):
177
- return ReceiveBlockResult .ALREADY_HAVE_BLOCK , None , None , []
186
+ return ReceiveBlockResult .ALREADY_HAVE_BLOCK , None , None , ([], {})
178
187
179
188
if not self .contains_block (block .prev_header_hash ) and not genesis :
180
- return (
181
- ReceiveBlockResult .DISCONNECTED_BLOCK ,
182
- Err .INVALID_PREV_BLOCK_HASH ,
183
- None ,
184
- [],
185
- )
189
+ return (ReceiveBlockResult .DISCONNECTED_BLOCK , Err .INVALID_PREV_BLOCK_HASH , None , ([], {}))
186
190
187
191
if not genesis and (self .block_record (block .prev_header_hash ).height + 1 ) != block .height :
188
- return ReceiveBlockResult .INVALID_BLOCK , Err .INVALID_HEIGHT , None , []
192
+ return ReceiveBlockResult .INVALID_BLOCK , Err .INVALID_HEIGHT , None , ([], {})
189
193
190
194
npc_result : Optional [NPCResult ] = None
191
195
if pre_validation_result is None :
@@ -202,7 +206,7 @@ async def receive_block(
202
206
try :
203
207
block_generator : Optional [BlockGenerator ] = await self .get_block_generator (block )
204
208
except ValueError :
205
- return ReceiveBlockResult .INVALID_BLOCK , Err .GENERATOR_REF_HAS_NO_GENERATOR , None , []
209
+ return ReceiveBlockResult .INVALID_BLOCK , Err .GENERATOR_REF_HAS_NO_GENERATOR , None , ([], {})
206
210
assert block_generator is not None and block .transactions_info is not None
207
211
npc_result = get_name_puzzle_conditions (
208
212
block_generator ,
@@ -228,7 +232,7 @@ async def receive_block(
228
232
)
229
233
230
234
if error is not None :
231
- return ReceiveBlockResult .INVALID_BLOCK , error .code , None , []
235
+ return ReceiveBlockResult .INVALID_BLOCK , error .code , None , ([], {})
232
236
else :
233
237
npc_result = pre_validation_result .npc_result
234
238
required_iters = pre_validation_result .required_iters
@@ -247,7 +251,7 @@ async def receive_block(
247
251
self .get_block_generator ,
248
252
)
249
253
if error_code is not None :
250
- return ReceiveBlockResult .INVALID_BLOCK , error_code , None , []
254
+ return ReceiveBlockResult .INVALID_BLOCK , error_code , None , ([], {})
251
255
252
256
block_record = block_to_block_record (
253
257
self .constants ,
@@ -263,7 +267,7 @@ async def receive_block(
263
267
# Perform the DB operations to update the state, and rollback if something goes wrong
264
268
await self .block_store .db_wrapper .begin_transaction ()
265
269
await self .block_store .add_full_block (header_hash , block , block_record )
266
- fork_height , peak_height , records , coin_record_change = await self ._reconsider_peak (
270
+ fork_height , peak_height , records , ( coin_record_change , hint_changes ) = await self ._reconsider_peak (
267
271
block_record , genesis , fork_point_with_peak , npc_result
268
272
)
269
273
await self .block_store .db_wrapper .commit_transaction ()
@@ -286,17 +290,35 @@ async def receive_block(
286
290
if fork_height is not None :
287
291
# new coin records added
288
292
assert coin_record_change is not None
289
- return ReceiveBlockResult .NEW_PEAK , None , fork_height , coin_record_change
293
+ return ReceiveBlockResult .NEW_PEAK , None , fork_height , ( coin_record_change , hint_changes )
290
294
else :
291
- return ReceiveBlockResult .ADDED_AS_ORPHAN , None , None , []
295
+ return ReceiveBlockResult .ADDED_AS_ORPHAN , None , None , ([], {})
296
+
297
+ def get_hint_list (self , npc_result : NPCResult ) -> List [Tuple [bytes32 , bytes ]]:
298
+ h_list = []
299
+ for npc in npc_result .npc_list :
300
+ for opcode , conditions in npc .conditions :
301
+ if opcode == ConditionOpcode .CREATE_COIN :
302
+ for condition in conditions :
303
+ if len (condition .vars ) > 2 and condition .vars [2 ] != b"" :
304
+ puzzle_hash , amount_bin = condition .vars [0 ], condition .vars [1 ]
305
+ amount = int_from_bytes (amount_bin )
306
+ coin_id = Coin (npc .coin_name , puzzle_hash , amount ).name ()
307
+ h_list .append ((coin_id , condition .vars [2 ]))
308
+ return h_list
292
309
293
310
async def _reconsider_peak (
294
311
self ,
295
312
block_record : BlockRecord ,
296
313
genesis : bool ,
297
314
fork_point_with_peak : Optional [uint32 ],
298
315
npc_result : Optional [NPCResult ],
299
- ) -> Tuple [Optional [uint32 ], Optional [uint32 ], List [BlockRecord ], List [CoinRecord ]]:
316
+ ) -> Tuple [
317
+ Optional [uint32 ],
318
+ Optional [uint32 ],
319
+ List [BlockRecord ],
320
+ Tuple [List [CoinRecord ], Dict [bytes , Dict [bytes32 , CoinRecord ]]],
321
+ ]:
300
322
"""
301
323
When a new block is added, this is called, to check if the new block is the new peak of the chain.
302
324
This also handles reorgs by reverting blocks which are not in the heaviest chain.
@@ -305,6 +327,8 @@ async def _reconsider_peak(
305
327
"""
306
328
peak = self .get_peak ()
307
329
lastest_coin_state : Dict [bytes32 , CoinRecord ] = {}
330
+ hint_coin_state : Dict [bytes32 , Dict [bytes32 , CoinRecord ]] = {}
331
+
308
332
if genesis :
309
333
if peak is None :
310
334
block : Optional [FullBlock ] = await self .block_store .get_full_block (block_record .header_hash )
@@ -326,8 +350,8 @@ async def _reconsider_peak(
326
350
else :
327
351
added , _ = [], []
328
352
await self .block_store .set_peak (block_record .header_hash )
329
- return uint32 (0 ), uint32 (0 ), [block_record ], added
330
- return None , None , [], []
353
+ return uint32 (0 ), uint32 (0 ), [block_record ], ( added , {})
354
+ return None , None , [], ([], {})
331
355
332
356
assert peak is not None
333
357
if block_record .weight > peak .weight :
@@ -372,46 +396,63 @@ async def _reconsider_peak(
372
396
records_to_add = []
373
397
for fetched_full_block , fetched_block_record in reversed (blocks_to_add ):
374
398
records_to_add .append (fetched_block_record )
375
- if fetched_block_record .is_transaction_block :
399
+ if fetched_full_block .is_transaction_block () :
376
400
if fetched_block_record .header_hash == block_record .header_hash :
377
- tx_removals , tx_additions = await self .get_tx_removals_and_additions (
401
+ tx_removals , tx_additions , npc_res = await self .get_tx_removals_and_additions (
378
402
fetched_full_block , npc_result
379
403
)
380
404
else :
381
- tx_removals , tx_additions = await self .get_tx_removals_and_additions (fetched_full_block , None )
382
- if fetched_full_block .is_transaction_block ():
383
- assert fetched_full_block .foliage_transaction_block is not None
384
- added_rec = await self .coin_store .new_block (
385
- fetched_full_block .height ,
386
- fetched_full_block .foliage_transaction_block .timestamp ,
387
- fetched_full_block .get_included_reward_coins (),
388
- tx_additions ,
389
- tx_removals ,
405
+ tx_removals , tx_additions , npc_res = await self .get_tx_removals_and_additions (
406
+ fetched_full_block , None
390
407
)
391
- removed_rec : List [Optional [CoinRecord ]] = [
392
- await self .coin_store .get_coin_record (name ) for name in tx_removals
393
- ]
394
-
395
- # Set additions first, than removals in order to handle ephemeral coin state
396
- # Add in height order is also required
397
- record : Optional [CoinRecord ]
398
- for record in added_rec :
399
- assert record
400
- lastest_coin_state [record .name ] = record
401
- for record in removed_rec :
402
- assert record
403
- lastest_coin_state [record .name ] = record
408
+
409
+ assert fetched_full_block .foliage_transaction_block is not None
410
+ added_rec = await self .coin_store .new_block (
411
+ fetched_full_block .height ,
412
+ fetched_full_block .foliage_transaction_block .timestamp ,
413
+ fetched_full_block .get_included_reward_coins (),
414
+ tx_additions ,
415
+ tx_removals ,
416
+ )
417
+ removed_rec : List [Optional [CoinRecord ]] = [
418
+ await self .coin_store .get_coin_record (name ) for name in tx_removals
419
+ ]
420
+
421
+ # Set additions first, then removals in order to handle ephemeral coin state
422
+ # Add in height order is also required
423
+ record : Optional [CoinRecord ]
424
+ for record in added_rec :
425
+ assert record
426
+ lastest_coin_state [record .name ] = record
427
+ for record in removed_rec :
428
+ assert record
429
+ lastest_coin_state [record .name ] = record
430
+
431
+ if npc_res is not None :
432
+ hint_list : List [Tuple [bytes32 , bytes ]] = self .get_hint_list (npc_res )
433
+ await self .hint_store .add_hints (hint_list )
434
+ # There can be multiple coins for the same hint
435
+ for coin_id , hint in hint_list :
436
+ key = hint
437
+ if key not in hint_coin_state :
438
+ hint_coin_state [key ] = {}
439
+ hint_coin_state [key ][coin_id ] = lastest_coin_state [coin_id ]
404
440
405
441
# Changes the peak to be the new peak
406
442
await self .block_store .set_peak (block_record .header_hash )
407
- return uint32 (max (fork_height , 0 )), block_record .height , records_to_add , list (lastest_coin_state .values ())
443
+ return (
444
+ uint32 (max (fork_height , 0 )),
445
+ block_record .height ,
446
+ records_to_add ,
447
+ (list (lastest_coin_state .values ()), hint_coin_state ),
448
+ )
408
449
409
450
# This is not a heavier block than the heaviest we have seen, so we don't change the coin set
410
- return None , None , [], list ( lastest_coin_state . values () )
451
+ return None , None , [], ([], {} )
411
452
412
453
async def get_tx_removals_and_additions (
413
454
self , block : FullBlock , npc_result : Optional [NPCResult ] = None
414
- ) -> Tuple [List [bytes32 ], List [Coin ]]:
455
+ ) -> Tuple [List [bytes32 ], List [Coin ], Optional [ NPCResult ] ]:
415
456
if block .is_transaction_block ():
416
457
if block .transactions_generator is not None :
417
458
if npc_result is None :
@@ -424,11 +465,11 @@ async def get_tx_removals_and_additions(
424
465
safe_mode = False ,
425
466
)
426
467
tx_removals , tx_additions = tx_removals_and_additions (npc_result .npc_list )
427
- return tx_removals , tx_additions
468
+ return tx_removals , tx_additions , npc_result
428
469
else :
429
- return [], []
470
+ return [], [], None
430
471
else :
431
- return [], []
472
+ return [], [], None
432
473
433
474
def get_next_difficulty (self , header_hash : bytes32 , new_slot : bool ) -> uint64 :
434
475
assert self .contains_block (header_hash )
0 commit comments