Trait ForksHandler

Source
pub trait ForksHandler<T: Config, S: ForkScopes>: Logging<BlockNumberFor<T>, Logger = DispatchError> + Sized {
    const TAG: &[u8];
    const MAX_FORKS: u32;
    const MAX_RECOVER_TRAVERSAL: u32;
Show 22 methods // Required methods fn forks_not_enabled() -> DispatchError; fn inconsistent_forks() -> DispatchError; fn max_forks_error() -> DispatchError; // Provided methods fn start<F: FnOnce()>( target: Option<&str>, fmt: Option<LogFormatter<BlockNumberFor<T>, Self::Level>>, ocw: F, ) { ... } fn get_head() -> Option<BlockNumberFor<T>> { ... } fn get_branch_hash(hash: T::Hash) -> Option<[u8; 32]> { ... } fn get_block_branch(hash: T::Hash) -> Option<Branch<T, S>> { ... } fn get_branch(branch_hash: &[u8]) -> Option<Branch<T, S>> { ... } fn get_prev_branch(hash: T::Hash) -> Option<Branch<T, S>> { ... } fn get_prev_block_branch() -> Option<Branch<T, S>> { ... } fn get_divider(hash: T::Hash) -> Option<[u8; 32]> { ... } fn transition( branch: &Branch<T, S>, action: ForkAction, ) -> Option<Branch<T, S>> { ... } fn gen_scope_item_key(item: &S::Item) -> [u8; 32] { ... } fn scope_item_exists( key: &[u8; 32], target: Option<&str>, fmt: Option<LogFormatter<BlockNumberFor<T>, Self::Level>>, ) -> Result<bool, Self::Logger> { ... } fn add_to_scope( item: S::Item, target: Option<&str>, fmt: Option<LogFormatter<BlockNumberFor<T>, Self::Level>>, ) -> Result<[u8; 32], Self::Logger> { ... } fn remove_from_scope( key: &[u8; 32], target: Option<&str>, fmt: Option<LogFormatter<BlockNumberFor<T>, Self::Level>>, ) -> Result<(), Self::Logger> { ... } fn parent_branch_hash_unavailable( block: BlockNumberFor<T>, _target: Option<&str>, _fmt: Option<LogFormatter<BlockNumberFor<T>, Self::Level>>, ) -> Result<(), Self::Logger> { ... } fn parent_branch_unavailable( block: BlockNumberFor<T>, target: Option<&str>, fmt: Option<LogFormatter<BlockNumberFor<T>, Self::Level>>, ) -> Result<(), Self::Logger> { ... } fn inherited_branch_mutation_conflict( block: BlockNumberFor<T>, target: Option<&str>, fmt: Option<LogFormatter<BlockNumberFor<T>, Self::Level>>, ) -> Result<(), Self::Logger> { ... } fn parent_divider_unavailable( block: BlockNumberFor<T>, target: Option<&str>, fmt: Option<LogFormatter<BlockNumberFor<T>, Self::Level>>, ) -> Result<(), Self::Logger> { ... } fn inherited_branch_decode_error( block: BlockNumberFor<T>, target: Option<&str>, fmt: Option<LogFormatter<BlockNumberFor<T>, Self::Level>>, ) -> Result<(), Self::Logger> { ... } fn max_forks( block: BlockNumberFor<T>, target: Option<&str>, fmt: Option<LogFormatter<BlockNumberFor<T>, Self::Level>>, ) -> Result<(), Self::Logger> { ... }
}
Expand description

Fork-aware local branch collection framework for pallet/module state.

This trait allows a pallet (or module using OCWs) to maintain its own deterministic local fork graph independent of chain consensus.

It is tightly coupled to FRAME System and uses:

  • current block number
  • parent hash
  • historical block hashes

to resolve and recover local branch state.

Each implementation defines:

  • its own TAG (storage namespace)
  • its own ForkScope (local state tracked per branch)

This lets every pallet answer:

"what was my local state on this branch?"

Useful for:

  • OCW-derived state
  • local indexing and aggregation
  • branch-aware replay
  • deterministic recovery after storage loss

§OCW execution model

Offchain workers should always begin with:

fn offchain_worker(block_number: BlockNumberFor<T>) {
    <Self as ForksHandler<T, MyForkScope>>::start(
        Some("my-pallet"),
        Some(LogFormatter::default()),
        || {
            // pallet-specific OCW logic here
        }
    );
}

Self::start is the only valid entry point.

It handles:

  • longest-chain extension vs sibling fork creation
  • recovery of missing or corrupted branch state
  • conditions where OCW execution should be skipped

The OCW closure executes only after branch resolution and recovery have completed.

§Scope safety

Fork scope state is recoverable, but not permanently trusted.

Recovery may recreate synthetic forks and restore only the minimum valid state required for continued execution, not exact historical lineage.

Fork recovery guarantees execution continuity, not perfect reconstruction.

§Query model

Branch resolution is intentionally delayed by one block.

The current executing block is not inserted into the fork graph during its own OCW execution.

Instead:

block N OCW executes
-> block N - 1 is resolved and persisted

because the current block hash is not yet safely available for deterministic fork routing.

This means queries during OCW execution resolve the previous block’s branch by default.

Additional traversal across parent, sibling, and canonical ancestry is available through branch access helpers.

Required Associated Constants§

Source

const TAG: &[u8]

Storage namespace prefix for all fork-local keys.

Used to isolate multiple fork-graphs with special scopes.

Source

const MAX_FORKS: u32

Maximum sibling forks allowed from a single branch point.

Once exceeded, no additional sibling branch is created and Self::max_forks is triggered.

Source

const MAX_RECOVER_TRAVERSAL: u32

Maximum reverse traversal attempts during recovery.

Used when branch resolution fails and the system walks backward to find the nearest recoverable branch state.

Prevents unbounded historical scanning.

Required Methods§

Source

fn forks_not_enabled() -> DispatchError

Returns the error used when fork-aware storage is accessed before ForksHandler::start has initialized the fork graph.

Source

fn inconsistent_forks() -> DispatchError

Returns the error used when the fork graph is in an inconsistent state.

Source

fn max_forks_error() -> DispatchError

Error returned when fork creation exceeds Self::MAX_FORKS.

Provided Methods§

Source

fn start<F: FnOnce()>( target: Option<&str>, fmt: Option<LogFormatter<BlockNumberFor<T>, Self::Level>>, ocw: F, )

Start fork resolution for the previous block and execute OCW logic inside the resolved branch environment.

This is the only valid OCW entry point.

It determines:

  • longest-chain extension vs sibling fork creation
  • divider / branch recovery when storage is missing
  • corruption handling for decode failures
  • mutation conflict promotion into sibling forks
  • conditions where OCW execution should be skipped

Resolution is intentionally delayed by one block:

block N OCW executes
-> block N - 1 is resolved and persisted

because the current block hash is not yet safely available for deterministic fork routing.

The provided ocw closure runs only after:

  • branch resolution is complete
  • recovery (if needed) has finished
  • block -> divider -> branch invariants are restored

This guarantees OCW logic executes only inside a valid fork-aware branch context.

Source

fn get_head() -> Option<BlockNumberFor<T>>

Returns the highest known longest-chain boundary used for fork detection.

This is local fork-tracking state, not consensus finality.

Used by Self::start to decide:

block <= HEAD_BLOCK -> sibling fork path
block >  HEAD_BLOCK -> longest-chain extension
Source

fn get_branch_hash(hash: T::Hash) -> Option<[u8; 32]>

Returns the fork-branch key-hash for a persisted block hash.

This should be queried using already finalized block hashes, typically the previous block during OCW execution.

Source

fn get_block_branch(hash: T::Hash) -> Option<Branch<T, S>>

Returns the resolved fork-branch data for a persisted block hash.

This should be queried using already finalized block hashes, typically the previous block during OCW execution.

Source

fn get_branch(branch_hash: &[u8]) -> Option<Branch<T, S>>

Loads a branch directly from its branch hash (key).

Source

fn get_prev_branch(hash: T::Hash) -> Option<Branch<T, S>>

Returns the structural parent branch of a resolved block.

Useful for manual traversal across fork ancestry.

This should be queried using already finalized block hashes, typically the previous block during OCW execution.

Source

fn get_prev_block_branch() -> Option<Branch<T, S>>

Source

fn get_divider(hash: T::Hash) -> Option<[u8; 32]>

Returns the divider for a persisted block hash.

Divider is the routing layer that allows multiple sibling branches to coexist from the same parent ancestry.

This should be queried using already finalized block hashes, typically the previous block during OCW execution.

Source

fn transition(branch: &Branch<T, S>, action: ForkAction) -> Option<Branch<T, S>>

Deterministic traversal across persisted local fork branches.

This moves between already resolved Branch states created by ForksHandler::start during pallet OCW execution.

Since fork resolution is delayed by one block:

block N executes
-> block N - 1 is resolved and persisted

the current executing block is not yet inserted into the local fork graph during its own OCW execution.

For normal OCW access, use ForksHandler::get_prev_block_branch to retrieve the previous block’s (N - 1) resolved branch.

From that branch, navigation can continue using ForkAction and ForksHandler::transition, or other helpers such as:

A branch represents a continuous stream of blocks on the same path:

A -> B -> C -> D
same branch

When a division occurs, a new sibling branch is created:

A -> B -> C
        |-- D
        |-- D'

Here:

  • D is the original branch continuation
  • D' is the new sibling branch

The branch that existed before the split (A -> B -> C) becomes the parent branch for both paths.

A child branch is any branch created from that fork point.

Example:

parent branch
A -> B -> C

child branches
        |-- D   [0]
        |-- D'  [1]

Nested forks create deeper child branches:

A -> B -> C
        |-- D'      [0]
             |-- E' [0,0]

This function performs branch navigation, not direct block traversal, so block numbers are not passed here.

If block position is needed, it can be inferred from:

  • the branch Branch::head
  • the previously resolved branch head

Traversal uses ForkAction to move between:

  • parent branches
  • child branches
  • sibling branches
  • root ancestry

Returns Some(branch) if the target exists, otherwise None.

Source

fn gen_scope_item_key(item: &S::Item) -> [u8; 32]

Derives a 32-byte scope key for a given scope item.

Delegates to Accrete::make_key which produces a stable, content-addressed key from the item.

The same item always produces the same key regardless of the block or fork it is called from, making scope keys fork-independent.

Source

fn scope_item_exists( key: &[u8; 32], target: Option<&str>, fmt: Option<LogFormatter<BlockNumberFor<T>, Self::Level>>, ) -> Result<bool, Self::Logger>

Returns true if the given scope key exists in the current fork’s branch.

Resolves the branch for block - 1 via Self::get_prev_block_branch and checks both the local scope (items written on this exact branch) and the inherited scope (items promoted from ancestor branches via accrete()).

Returns Err(OCWForksNotEnabled) if no branch exists for the previous block, which indicates the fork graph has not been initialized via ForksHandler::start.

Source

fn add_to_scope( item: S::Item, target: Option<&str>, fmt: Option<LogFormatter<BlockNumberFor<T>, Self::Level>>, ) -> Result<[u8; 32], Self::Logger>

Registers a scope item in the local scope of the current branch.

Resolves the branch for block - 1 using block_hash(block - 1) as the lookup key.

The item is added to branch.scope.local_keys so it is visible to Self::scope_item_exists on the same branch and propagates into inherited_keys of any child branch created via accrete() during the next ForksHandler::start call.

Returns the 32-byte scope key assigned to the item.

Returns:

  • Err(OCWForksNotEnabled) if block_hash(block - 1) has no corresponding branch entry, meaning ForksHandler::start has not yet run at the current block.
  • Err(OCWForksInconsistent) if the branch hash resolves but the branch itself cannot be loaded.
Source

fn remove_from_scope( key: &[u8; 32], target: Option<&str>, fmt: Option<LogFormatter<BlockNumberFor<T>, Self::Level>>, ) -> Result<(), Self::Logger>

Removes a scope item from the current branch’s local or inherited scope.

Resolves the branch for block - 1 and removes the key from whichever scope layer it occupies:

  • If the key is in local_keys, it is removed directly and the branch is persisted.
  • If the key is in inherited_keys, it is removed from the inherited layer and the branch is persisted.
  • If the key is in neither layer, the call is a no-op and returns Ok(()).

Returns:

  • Err(OCWForksNotEnabled) if the branch cannot be resolved.
  • Err(OCWForksInconsistent) if the branch hash exists but the branch itself cannot be loaded.
Source

fn parent_branch_hash_unavailable( block: BlockNumberFor<T>, _target: Option<&str>, _fmt: Option<LogFormatter<BlockNumberFor<T>, Self::Level>>, ) -> Result<(), Self::Logger>

Recovery path when a target block’s parent block does not have a resolvable branch available.

The target block is usually (N-1) and parent (N-2)

Storage corruption may permanently lose the parent’s local fork scope for that generation, which cannot be reconstructed from chain history alone.

Recovery walks backward to the nearest recoverable branch and restores only the minimum valid scope required for execution continuity.

This is intentionally scope-first, not lineage-first: synthetic recovery branches may be created instead of exact historical fork reconstruction.

Source

fn parent_branch_unavailable( block: BlockNumberFor<T>, target: Option<&str>, fmt: Option<LogFormatter<BlockNumberFor<T>, Self::Level>>, ) -> Result<(), Self::Logger>

Recovery path when a divider exists for a target block’s (N-1) parent (N-2) but the resolved branch data is missing.

The stale divider is cleared first, then recovery falls back to Self::parent_branch_hash_unavailable to rebuild the minimum valid branch state.

Source

fn inherited_branch_mutation_conflict( block: BlockNumberFor<T>, target: Option<&str>, fmt: Option<LogFormatter<BlockNumberFor<T>, Self::Level>>, ) -> Result<(), Self::Logger>

Recovery path when optimistic branch mutation fails due to concurrent OCW modification.

Unlike other recovery paths, this handles the target block (N-1) itself, not its missing parent (N-2) ancestry. It does not return control to Self::start for retry, because no parent recovery is needed.

Instead, it directly performs branch update for the target block by cloning the conflicting structure - the inherited branch from parent into a new sibling branch.

The conflicting writer keeps the original branch, while the later writer continues on a cloned sibling fork.

This preserves deterministic execution without retrying mutation on already committed branch state.

Source

fn parent_divider_unavailable( block: BlockNumberFor<T>, target: Option<&str>, fmt: Option<LogFormatter<BlockNumberFor<T>, Self::Level>>, ) -> Result<(), Self::Logger>

Recovery path when divider routing is missing for the target block’s (N-1) parent (N-2).

Since divider loss breaks ancestry routing entirely, recovery falls back directly to Self::parent_branch_hash_unavailable.

Source

fn inherited_branch_decode_error( block: BlockNumberFor<T>, target: Option<&str>, fmt: Option<LogFormatter<BlockNumberFor<T>, Self::Level>>, ) -> Result<(), Self::Logger>

Recovery path when branch decoding fails for the target block’s (N-1) inherited branch from parent (N-2) for extension.

This means the branch exists in storage, but its payload is corrupted or unreadable.

It is treated the same as a missing branch and delegated to Self::parent_branch_unavailable.

Source

fn max_forks( block: BlockNumberFor<T>, target: Option<&str>, fmt: Option<LogFormatter<BlockNumberFor<T>, Self::Level>>, ) -> Result<(), Self::Logger>

Triggered when no additional sibling branch slot can be allocated under the configured limit as the sibling of the target block’s (N-1) parent (N-2).

This occurs when:

sibling_count > MAX_FORKS

and branch creation must stop.

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.

Implementors§