66from enum import Enum
77from typing import Dict , List , Optional , Set , Tuple , Union
88
9+ from clvm .casts import int_from_bytes
10+
911from chia .consensus .block_body_validation import validate_block_body
1012from chia .consensus .block_header_validation import validate_finished_header_block , validate_unfinished_header_block
1113from chia .consensus .block_record import BlockRecord
1820from chia .consensus .multiprocess_validation import PreValidationResult , pre_validate_blocks_multiprocessing
1921from chia .full_node .block_store import BlockStore
2022from chia .full_node .coin_store import CoinStore
23+ from chia .full_node .hint_store import HintStore
2124from chia .full_node .mempool_check_conditions import get_name_puzzle_conditions
2225from chia .types .blockchain_format .coin import Coin
2326from chia .types .blockchain_format .sized_bytes import bytes32
2427from chia .types .blockchain_format .sub_epoch_summary import SubEpochSummary
2528from chia .types .blockchain_format .vdf import VDFInfo
2629from chia .types .coin_record import CoinRecord
30+ from chia .types .condition_opcodes import ConditionOpcode
2731from chia .types .end_of_slot_bundle import EndOfSubSlotBundle
2832from chia .types .full_block import FullBlock
2933from chia .types .generator_types import BlockGenerator , GeneratorArg
@@ -83,12 +87,11 @@ class Blockchain(BlockchainInterface):
8387 # Lock to prevent simultaneous reads and writes
8488 lock : asyncio .Lock
8589 compact_proof_lock : asyncio .Lock
90+ hint_store : HintStore
8691
8792 @staticmethod
8893 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
9295 ):
9396 """
9497 Initializes a blockchain with the BlockRecords from disk, assuming they have all been
@@ -112,6 +115,7 @@ async def create(
112115 self ._shut_down = False
113116 await self ._load_chain_from_store ()
114117 self ._seen_compact_proofs = set ()
118+ self .hint_store = hint_store
115119 return self
116120
117121 def shut_down (self ):
@@ -164,7 +168,12 @@ async def receive_block(
164168 block : FullBlock ,
165169 pre_validation_result : Optional [PreValidationResult ] = None ,
166170 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+ ]:
168177 """
169178 This method must be called under the blockchain lock
170179 Adds a new block into the blockchain, if it's valid and connected to the current
@@ -174,18 +183,13 @@ async def receive_block(
174183 """
175184 genesis : bool = block .height == 0
176185 if self .contains_block (block .header_hash ):
177- return ReceiveBlockResult .ALREADY_HAVE_BLOCK , None , None , []
186+ return ReceiveBlockResult .ALREADY_HAVE_BLOCK , None , None , ([], {})
178187
179188 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 , ([], {}))
186190
187191 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 , ([], {})
189193
190194 npc_result : Optional [NPCResult ] = None
191195 if pre_validation_result is None :
@@ -202,7 +206,7 @@ async def receive_block(
202206 try :
203207 block_generator : Optional [BlockGenerator ] = await self .get_block_generator (block )
204208 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 , ([], {})
206210 assert block_generator is not None and block .transactions_info is not None
207211 npc_result = get_name_puzzle_conditions (
208212 block_generator ,
@@ -228,7 +232,7 @@ async def receive_block(
228232 )
229233
230234 if error is not None :
231- return ReceiveBlockResult .INVALID_BLOCK , error .code , None , []
235+ return ReceiveBlockResult .INVALID_BLOCK , error .code , None , ([], {})
232236 else :
233237 npc_result = pre_validation_result .npc_result
234238 required_iters = pre_validation_result .required_iters
@@ -247,7 +251,7 @@ async def receive_block(
247251 self .get_block_generator ,
248252 )
249253 if error_code is not None :
250- return ReceiveBlockResult .INVALID_BLOCK , error_code , None , []
254+ return ReceiveBlockResult .INVALID_BLOCK , error_code , None , ([], {})
251255
252256 block_record = block_to_block_record (
253257 self .constants ,
@@ -263,7 +267,7 @@ async def receive_block(
263267 # Perform the DB operations to update the state, and rollback if something goes wrong
264268 await self .block_store .db_wrapper .begin_transaction ()
265269 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 (
267271 block_record , genesis , fork_point_with_peak , npc_result
268272 )
269273 await self .block_store .db_wrapper .commit_transaction ()
@@ -286,17 +290,35 @@ async def receive_block(
286290 if fork_height is not None :
287291 # new coin records added
288292 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 )
290294 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
292309
293310 async def _reconsider_peak (
294311 self ,
295312 block_record : BlockRecord ,
296313 genesis : bool ,
297314 fork_point_with_peak : Optional [uint32 ],
298315 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+ ]:
300322 """
301323 When a new block is added, this is called, to check if the new block is the new peak of the chain.
302324 This also handles reorgs by reverting blocks which are not in the heaviest chain.
@@ -305,6 +327,8 @@ async def _reconsider_peak(
305327 """
306328 peak = self .get_peak ()
307329 lastest_coin_state : Dict [bytes32 , CoinRecord ] = {}
330+ hint_coin_state : Dict [bytes32 , Dict [bytes32 , CoinRecord ]] = {}
331+
308332 if genesis :
309333 if peak is None :
310334 block : Optional [FullBlock ] = await self .block_store .get_full_block (block_record .header_hash )
@@ -326,8 +350,8 @@ async def _reconsider_peak(
326350 else :
327351 added , _ = [], []
328352 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 , [], ([], {})
331355
332356 assert peak is not None
333357 if block_record .weight > peak .weight :
@@ -372,46 +396,63 @@ async def _reconsider_peak(
372396 records_to_add = []
373397 for fetched_full_block , fetched_block_record in reversed (blocks_to_add ):
374398 records_to_add .append (fetched_block_record )
375- if fetched_block_record .is_transaction_block :
399+ if fetched_full_block .is_transaction_block () :
376400 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 (
378402 fetched_full_block , npc_result
379403 )
380404 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
390407 )
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 ]
404440
405441 # Changes the peak to be the new peak
406442 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+ )
408449
409450 # 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 , [], ([], {} )
411452
412453 async def get_tx_removals_and_additions (
413454 self , block : FullBlock , npc_result : Optional [NPCResult ] = None
414- ) -> Tuple [List [bytes32 ], List [Coin ]]:
455+ ) -> Tuple [List [bytes32 ], List [Coin ], Optional [ NPCResult ] ]:
415456 if block .is_transaction_block ():
416457 if block .transactions_generator is not None :
417458 if npc_result is None :
@@ -424,11 +465,11 @@ async def get_tx_removals_and_additions(
424465 safe_mode = False ,
425466 )
426467 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
428469 else :
429- return [], []
470+ return [], [], None
430471 else :
431- return [], []
472+ return [], [], None
432473
433474 def get_next_difficulty (self , header_hash : bytes32 , new_slot : bool ) -> uint64 :
434475 assert self .contains_block (header_hash )
0 commit comments