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.