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
ChainStore
struct 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
FullTipset
if 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
SyncStateMachine
tasks: Manages the main event loop, taking tasks generated by theSyncStateMachine
struct (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/blocks
pubsub topic. When a peer broadcasts a new block, theLibp2pService
struct receives it in thehandle_gossip_event
function. This event is just for a single block's CID. TheChainFollower
receives thisNetworkEvent::PubsubMessage
and 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_fts
method of theSyncNetworkContext
struct (present insrc/chain_sync/network_context.rs
). This is a direct request to a peer to provide theFullTipset
corresponding to the block's tipset key.
- File:
-
Local Miner:
- A connected miner can submit a newly created
FullTipset
directly to theChainFollower
through thetipset_sender
channel 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
tipsets
field (aHashMap
) of all tipsets it is currently aware of but has not yet fully validated. -
SyncEvent
enum: The state machine is driven bySyncEvent
variants:NewFullTipsets
: Triggered when a new tipset is discovered. The state machine adds it to its internaltipsets
map 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
.
-
SyncTask
Generation: Thetasks()
method of theSyncStateMachine
is its heart. It iterates through the known tipsets, builds out the potential fork chains, and generates the next set of actions (SyncTask
enums) required to make progress.- If a tipsets parent is present in the
ChainStore
(meaning it's already validated), aSyncTask::ValidateTipset
task is created. - If a tipsets parent is not in the
ChainStore
, aSyncTask::FetchTipset
task 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
StateManager
struct 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_root
andmessage_receipts
fields in the current block's header. A mismatch indicates an invalid state transition, and the block is rejected. -
Message Validation: The
check_block_messages
function 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_limit
of all messages is summed to ensure it does not exceed theBLOCK_GAS_LIMIT
. - The message root (
TxMeta
struct) is re-computed from all messages and compared to themessages
CID in the block header.
-
Block Signature Verification: The block header's
signature
is verified to ensure it was signed by the declaredminer_address
. -
Consensus Validation: The
validate_block
method of theFilecoinConsensus
struct 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
BadBlockCache
struct. 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
SyncStateMachine
calls theput_tipset
method on theChainStore
struct. - Expand the Tipset: The
put_tipset
method first calls theexpand_tipset
method, which checks theTipsetTracker
struct 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_heaviest
method. If it's heavier, theset_heaviest_tipset
method of theChainStore
is invoked. - Broadcast Head Change: The
set_heaviest_tipset
method updates the head in the database and broadcasts aHeadChange::Apply
event. 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.