Chain Follower
High-Level Overview​
The ChainFollower is the orchestrator of the chain synchronization process in Forest. Its main responsibility is to keep the local chain state up-to-date with the Filecoin network's heaviest tipset. It achieves this by:
- Receiving new tipsets from the network (via libp2p gossip) and from local miners.
- Managing a pool of candidate tipsets that are potential heads of the chain.
- Scheduling tasks to either fetch missing parent tipsets or validate tipsets whose parents are already known.
- Executing intensive validation logic to ensure the integrity of each block and its messages.
- Updating the
ChainStorestruct with newly validated tipsets, which may involve changing the node's view of the heaviest chain (the "head").
This entire process is managed by a state machine within the chain_follower.rs module, ensuring that tipsets are processed in the correct order and that the node can handle multiple competing forks simultaneously.
Visual Workflow​
ChainFollower Working​
The ChainFollower struct spawns 4 concurrent tasks to sync the chain and track its progress:
- Forward tipsets from peers to the SyncStateMachine: Listens for NetworkEvents, processes incoming blocks from gossip, fetches the
FullTipsetif necessary, and submits it to the state machine. - Forward tipsets from miners to the
SyncStateMachine: Listens on a dedicated channel for locally-produced tipsets submitted via the API. - Execute
SyncStateMachinetasks: Manages the main event loop, taking tasks generated by theSyncStateMachinestruct (like fetching or validating) and spawning them for execution. It also updates the node's overall sync status. - Periodically report sync progress: Logs the current sync status at regular intervals, providing visibility into how far behind the network head the node is.
Details of ChainFollower working​
Tipset Fetching​
New tipsets are introduced to the ChainFollower from two main sources:
-
P2P Network (Gossip):
- File:
src/libp2p/service.rs - Flow: Forest nodes listen on the
/fil/blockspubsub topic. When a peer broadcasts a new block, theLibp2pServicestruct receives it in thehandle_gossip_eventfunction. This event is just for a single block's CID. TheChainFollowerreceives thisNetworkEvent::PubsubMessageand realizes it needs the full block and its sibling blocks to form aFullTipset. It then issues a "chain exchange" request to the network using thechain_exchange_ftsmethod of theSyncNetworkContextstruct (present insrc/chain_sync/network_context.rs). This is a direct request to a peer to provide theFullTipsetcorresponding to the block's tipset key.
- File:
-
Local Miner:
- A connected miner can submit a newly created
FullTipsetdirectly to theChainFollowerthrough thetipset_senderchannel field. This bypasses the network fetching step.
- A connected miner can submit a newly created
The SyncStateMachine​
Once a FullTipset struct is acquired, it's handed over to the SyncStateMachine struct. This is the core of the chain follower, managing all candidate tipsets and deciding what to do next.
-
State: The state machine maintains a
tipsetsfield (aHashMap) of all tipsets it is currently aware of but has not yet fully validated. -
SyncEventenum: The state machine is driven bySyncEventvariants:NewFullTipsets: Triggered when a new tipset is discovered. The state machine adds it to its internaltipsetsmap to be processed.BadTipset: Triggered when a tipset fails validation. The state machine will remove it and all its descendants from its internal map.ValidatedTipset: Triggered when a tipset successfully passes validation. The state machine removes it from its map and commits it to theChainStore.
-
SyncTaskGeneration: Thetasks()method of theSyncStateMachineis its heart. It iterates through the known tipsets, builds out the potential fork chains, and generates the next set of actions (SyncTaskenums) required to make progress.- If a tipsets parent is present in the
ChainStore(meaning it's already validated), aSyncTask::ValidateTipsettask is created. - If a tipsets parent is not in the
ChainStore, aSyncTask::FetchTipsettask is created for the missing parent. This recursive fetching is the important mechanism that allows Forest to sync the chain by walking backward from a given head.
- If a tipsets parent is present in the
Tipset Validation​
When a SyncTask::ValidateTipset task is executed, it kicks off a comprehensive validation process defined in the validate_block function in src/chain_sync/tipset_syncer.rs. This is the most computationally intensive part of chain synchronization. For each Block in the FullTipset, the following checks are performed in parallel:
-
Parent Tipset State Execution: This is the most critical step. The
StateManagerstruct loads the parent tipset and re-executes all of its messages to compute the final state root and message receipt root. These computed roots are compared against thestate_rootandmessage_receiptsfields in the current block's header. A mismatch indicates an invalid state transition, and the block is rejected. -
Message Validation: The
check_block_messagesfunction performs several checks:- The aggregate BLS signature for all BLS messages in the block is verified.
- The individual signature of every SecP256k1 message is verified against the sender's key.
- The
nonce(sequence number) of each message is checked against the sender's current nonce in the parent state. - The
gas_limitof all messages is summed to ensure it does not exceed theBLOCK_GAS_LIMIT. - The message root (
TxMetastruct) is re-computed from all messages and compared to themessagesCID in the block header.
-
Block Signature Verification: The block header's
signatureis verified to ensure it was signed by the declaredminer_address. -
Consensus Validation: The
validate_blockmethod of theFilecoinConsensusstruct is called to verify consensus-specific rules, primarily theElectionProof.
Handling Bad Blocks​
When the SyncStateMachine receives a SyncEvent::BadTipset event, it takes two important actions to protect the node:
- Cache the Bad Block: It adds the CID of every block in the failed tipset to the
BadBlockCachestruct. This is an LRU cache that prevents the node from wasting resources by re-fetching or re-validating a block that is already known to be invalid. (src/chain_sync/bad_block_cache.rs) - Prune Descendants: It traverses its internal map of tipsets and removes all known descendants of the bad tipset. Since a child of an invalid block is also invalid, this prunes entire invalid forks from the processing queue.
Committing to the Chain​
If a tipset and all its blocks pass validation, a SyncEvent::ValidatedTipset event is sent to the SyncStateMachine, which triggers the final step of committing it to the local chain. (src/chain/store/chain_store.rs)
- Store the Tipset: The
SyncStateMachinecalls theput_tipsetmethod on theChainStorestruct. - Expand the Tipset: The
put_tipsetmethod first calls theexpand_tipsetmethod, which checks theTipsetTrackerstruct for any other valid blocks at the same epoch with the same parents. This merges them into a single, more complete tipset, making the view of the head more robust. - Update the Head: The new, expanded tipsets weight is compared to the current head's weight in the
update_heaviestmethod. If it's heavier, theset_heaviest_tipsetmethod of theChainStoreis invoked. - Broadcast Head Change: The
set_heaviest_tipsetmethod updates the head in the database and broadcasts aHeadChange::Applyevent. This notification is critical, as it allows other Forest subsystems like the Message Pool and RPC API to update their own state based on the new head of the chain.