github.com/prysmaticlabs/prysm@v1.4.4/tools/specs-checker/data/specs/phase0/fork-choice.md (about) 1 ```python 2 def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -> Store: 3 assert anchor_block.state_root == hash_tree_root(anchor_state) 4 anchor_root = hash_tree_root(anchor_block) 5 anchor_epoch = get_current_epoch(anchor_state) 6 justified_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) 7 finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) 8 return Store( 9 time=uint64(anchor_state.genesis_time + SECONDS_PER_SLOT * anchor_state.slot), 10 genesis_time=anchor_state.genesis_time, 11 justified_checkpoint=justified_checkpoint, 12 finalized_checkpoint=finalized_checkpoint, 13 best_justified_checkpoint=justified_checkpoint, 14 blocks={anchor_root: copy(anchor_block)}, 15 block_states={anchor_root: copy(anchor_state)}, 16 checkpoint_states={justified_checkpoint: copy(anchor_state)}, 17 ) 18 ``` 19 ```python 20 def get_slots_since_genesis(store: Store) -> int: 21 return (store.time - store.genesis_time) // SECONDS_PER_SLOT 22 ``` 23 ```python 24 def get_current_slot(store: Store) -> Slot: 25 return Slot(GENESIS_SLOT + get_slots_since_genesis(store)) 26 ``` 27 ```python 28 def compute_slots_since_epoch_start(slot: Slot) -> int: 29 return slot - compute_start_slot_at_epoch(compute_epoch_at_slot(slot)) 30 ``` 31 ```python 32 def get_ancestor(store: Store, root: Root, slot: Slot) -> Root: 33 block = store.blocks[root] 34 if block.slot > slot: 35 return get_ancestor(store, block.parent_root, slot) 36 elif block.slot == slot: 37 return root 38 else: 39 # root is older than queried slot, thus a skip slot. Return most recent root prior to slot 40 return root 41 ``` 42 ```python 43 def get_latest_attesting_balance(store: Store, root: Root) -> Gwei: 44 state = store.checkpoint_states[store.justified_checkpoint] 45 active_indices = get_active_validator_indices(state, get_current_epoch(state)) 46 return Gwei(sum( 47 state.validators[i].effective_balance for i in active_indices 48 if (i in store.latest_messages 49 and get_ancestor(store, store.latest_messages[i].root, store.blocks[root].slot) == root) 50 )) 51 ``` 52 ```python 53 def filter_block_tree(store: Store, block_root: Root, blocks: Dict[Root, BeaconBlock]) -> bool: 54 block = store.blocks[block_root] 55 children = [ 56 root for root in store.blocks.keys() 57 if store.blocks[root].parent_root == block_root 58 ] 59 60 # If any children branches contain expected finalized/justified checkpoints, 61 # add to filtered block-tree and signal viability to parent. 62 if any(children): 63 filter_block_tree_result = [filter_block_tree(store, child, blocks) for child in children] 64 if any(filter_block_tree_result): 65 blocks[block_root] = block 66 return True 67 return False 68 69 # If leaf block, check finalized/justified checkpoints as matching latest. 70 head_state = store.block_states[block_root] 71 72 correct_justified = ( 73 store.justified_checkpoint.epoch == GENESIS_EPOCH 74 or head_state.current_justified_checkpoint == store.justified_checkpoint 75 ) 76 correct_finalized = ( 77 store.finalized_checkpoint.epoch == GENESIS_EPOCH 78 or head_state.finalized_checkpoint == store.finalized_checkpoint 79 ) 80 # If expected finalized/justified, add to viable block-tree and signal viability to parent. 81 if correct_justified and correct_finalized: 82 blocks[block_root] = block 83 return True 84 85 # Otherwise, branch not viable 86 return False 87 ``` 88 ```python 89 def get_filtered_block_tree(store: Store) -> Dict[Root, BeaconBlock]: 90 """ 91 Retrieve a filtered block tree from ``store``, only returning branches 92 whose leaf state's justified/finalized info agrees with that in ``store``. 93 """ 94 base = store.justified_checkpoint.root 95 blocks: Dict[Root, BeaconBlock] = {} 96 filter_block_tree(store, base, blocks) 97 return blocks 98 ``` 99 ```python 100 def get_head(store: Store) -> Root: 101 # Get filtered block tree that only includes viable branches 102 blocks = get_filtered_block_tree(store) 103 # Execute the LMD-GHOST fork choice 104 head = store.justified_checkpoint.root 105 while True: 106 children = [ 107 root for root in blocks.keys() 108 if blocks[root].parent_root == head 109 ] 110 if len(children) == 0: 111 return head 112 # Sort by latest attesting balance with ties broken lexicographically 113 head = max(children, key=lambda root: (get_latest_attesting_balance(store, root), root)) 114 ``` 115 ```python 116 def should_update_justified_checkpoint(store: Store, new_justified_checkpoint: Checkpoint) -> bool: 117 """ 118 To address the bouncing attack, only update conflicting justified 119 checkpoints in the fork choice if in the early slots of the epoch. 120 Otherwise, delay incorporation of new justified checkpoint until next epoch boundary. 121 122 See https://ethresear.ch/t/prevention-of-bouncing-attack-on-ffg/6114 for more detailed analysis and discussion. 123 """ 124 if compute_slots_since_epoch_start(get_current_slot(store)) < SAFE_SLOTS_TO_UPDATE_JUSTIFIED: 125 return True 126 127 justified_slot = compute_start_slot_at_epoch(store.justified_checkpoint.epoch) 128 if not get_ancestor(store, new_justified_checkpoint.root, justified_slot) == store.justified_checkpoint.root: 129 return False 130 131 return True 132 ``` 133 ```python 134 def validate_on_attestation(store: Store, attestation: Attestation) -> None: 135 target = attestation.data.target 136 137 # Attestations must be from the current or previous epoch 138 current_epoch = compute_epoch_at_slot(get_current_slot(store)) 139 # Use GENESIS_EPOCH for previous when genesis to avoid underflow 140 previous_epoch = current_epoch - 1 if current_epoch > GENESIS_EPOCH else GENESIS_EPOCH 141 # If attestation target is from a future epoch, delay consideration until the epoch arrives 142 assert target.epoch in [current_epoch, previous_epoch] 143 assert target.epoch == compute_epoch_at_slot(attestation.data.slot) 144 145 # Attestations target be for a known block. If target block is unknown, delay consideration until the block is found 146 assert target.root in store.blocks 147 148 # Attestations must be for a known block. If block is unknown, delay consideration until the block is found 149 assert attestation.data.beacon_block_root in store.blocks 150 # Attestations must not be for blocks in the future. If not, the attestation should not be considered 151 assert store.blocks[attestation.data.beacon_block_root].slot <= attestation.data.slot 152 153 # LMD vote must be consistent with FFG vote target 154 target_slot = compute_start_slot_at_epoch(target.epoch) 155 assert target.root == get_ancestor(store, attestation.data.beacon_block_root, target_slot) 156 157 # Attestations can only affect the fork choice of subsequent slots. 158 # Delay consideration in the fork choice until their slot is in the past. 159 assert get_current_slot(store) >= attestation.data.slot + 1 160 ``` 161 ```python 162 def store_target_checkpoint_state(store: Store, target: Checkpoint) -> None: 163 # Store target checkpoint state if not yet seen 164 if target not in store.checkpoint_states: 165 base_state = copy(store.block_states[target.root]) 166 if base_state.slot < compute_start_slot_at_epoch(target.epoch): 167 process_slots(base_state, compute_start_slot_at_epoch(target.epoch)) 168 store.checkpoint_states[target] = base_state 169 ``` 170 ```python 171 def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIndex], attestation: Attestation) -> None: 172 target = attestation.data.target 173 beacon_block_root = attestation.data.beacon_block_root 174 for i in attesting_indices: 175 if i not in store.latest_messages or target.epoch > store.latest_messages[i].epoch: 176 store.latest_messages[i] = LatestMessage(epoch=target.epoch, root=beacon_block_root) 177 ``` 178 ```python 179 def on_tick(store: Store, time: uint64) -> None: 180 previous_slot = get_current_slot(store) 181 182 # update store time 183 store.time = time 184 185 current_slot = get_current_slot(store) 186 # Not a new epoch, return 187 if not (current_slot > previous_slot and compute_slots_since_epoch_start(current_slot) == 0): 188 return 189 # Update store.justified_checkpoint if a better checkpoint is known 190 if store.best_justified_checkpoint.epoch > store.justified_checkpoint.epoch: 191 store.justified_checkpoint = store.best_justified_checkpoint 192 ``` 193 ```python 194 def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: 195 block = signed_block.message 196 # Parent block must be known 197 assert block.parent_root in store.block_states 198 # Make a copy of the state to avoid mutability issues 199 pre_state = copy(store.block_states[block.parent_root]) 200 # Blocks cannot be in the future. If they are, their consideration must be delayed until the are in the past. 201 assert get_current_slot(store) >= block.slot 202 203 # Check that block is later than the finalized epoch slot (optimization to reduce calls to get_ancestor) 204 finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) 205 assert block.slot > finalized_slot 206 # Check block is a descendant of the finalized block at the checkpoint finalized slot 207 assert get_ancestor(store, block.parent_root, finalized_slot) == store.finalized_checkpoint.root 208 209 # Check the block is valid and compute the post-state 210 state = pre_state.copy() 211 state_transition(state, signed_block, True) 212 # Add new block to the store 213 store.blocks[hash_tree_root(block)] = block 214 # Add new state for this block to the store 215 store.block_states[hash_tree_root(block)] = state 216 217 # Update justified checkpoint 218 if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch: 219 if state.current_justified_checkpoint.epoch > store.best_justified_checkpoint.epoch: 220 store.best_justified_checkpoint = state.current_justified_checkpoint 221 if should_update_justified_checkpoint(store, state.current_justified_checkpoint): 222 store.justified_checkpoint = state.current_justified_checkpoint 223 224 # Update finalized checkpoint 225 if state.finalized_checkpoint.epoch > store.finalized_checkpoint.epoch: 226 store.finalized_checkpoint = state.finalized_checkpoint 227 228 # Potentially update justified if different from store 229 if store.justified_checkpoint != state.current_justified_checkpoint: 230 # Update justified if new justified is later than store justified 231 if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch: 232 store.justified_checkpoint = state.current_justified_checkpoint 233 return 234 235 # Update justified if store justified is not in chain with finalized checkpoint 236 finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) 237 ancestor_at_finalized_slot = get_ancestor(store, store.justified_checkpoint.root, finalized_slot) 238 if ancestor_at_finalized_slot != store.finalized_checkpoint.root: 239 store.justified_checkpoint = state.current_justified_checkpoint 240 ``` 241 ```python 242 def on_attestation(store: Store, attestation: Attestation) -> None: 243 """ 244 Run ``on_attestation`` upon receiving a new ``attestation`` from either within a block or directly on the wire. 245 246 An ``attestation`` that is asserted as invalid may be valid at a later time, 247 consider scheduling it for later processing in such case. 248 """ 249 validate_on_attestation(store, attestation) 250 store_target_checkpoint_state(store, attestation.data.target) 251 252 # Get state at the `target` to fully validate attestation 253 target_state = store.checkpoint_states[attestation.data.target] 254 indexed_attestation = get_indexed_attestation(target_state, attestation) 255 assert is_valid_indexed_attestation(target_state, indexed_attestation) 256 257 # Update latest messages for attesting indices 258 update_latest_messages(store, indexed_attestation.attesting_indices, attestation) 259 ```