# Fuel Core P2P Synchronization Logic: Architecture and Implementation

> Explore Fuel Core's P2P synchronization logic architecture. Learn about block propagation, transaction gossiping, and peer management using a libp2p-based stack.

- Repository: [Fuel Labs/fuel-core](https://github.com/FuelLabs/fuel-core)
- Tags: architecture
- Published: 2026-03-06

---

**Fuel Core's P2P synchronization logic orchestrates block propagation, transaction gossiping, and peer reputation management through a modular libp2p-based stack centered on `FuelP2PService`, `FuelBehaviour`, and `PeerManager`.**

The peer-to-peer layer in [FuelLabs/fuel-core](https://github.com/FuelLabs/fuel-core) enables decentralized consensus by keeping nodes synchronized through efficient data exchange and peer discovery. Built on **libp2p**, the synchronization system handles everything from DNS address resolution to real-time block height propagation across the Fuel network. This article examines the key architectural components, request-response protocols, and reputation mechanisms that power Fuel Core's P2P synchronization logic.

## Architecture Overview

Fuel Core's P2P implementation separates concerns across distinct modules that manage transport, protocol negotiation, and peer lifecycle. The architecture relies on the **`FuelP2PService`** as the primary orchestrator, which wraps a libp2p `Swarm` and translates low-level network events into high-level `FuelP2PEvent`s consumed by the core node logic.

## Core Components

### FuelP2PService

The **`FuelP2PService`** defined in [`crates/services/p2p/src/p2p_service.rs`](https://github.com/FuelLabs/fuel-core/blob/main/crates/services/p2p/src/p2p_service.rs) serves as the main entry point for all network operations. During initialization via `FuelP2PService::new`, the service resolves DNS multiaddrs through `parse_multiaddrs`, constructs the transport layer through `build_transport_function`, and instantiates the `Swarm` with the composite `FuelBehaviour`.

### FuelBehaviour

Located in [`crates/services/p2p/src/behaviour.rs`](https://github.com/FuelLabs/fuel-core/blob/main/crates/services/p2p/src/behaviour.rs), the **`FuelBehaviour`** struct implements the libp2p `NetworkBehaviour` trait and aggregates all sub-protocols required for synchronization:

- **Gossipsub** for transaction and block header propagation
- **Request-Response** for on-demand data queries (sealed headers, transactions)
- **Identify** for peer metadata exchange
- **Heartbeat** for block height propagation
- **Discovery** for peer lookup and bootstrap

### PeerManager

The **`PeerManager`** in [`crates/services/p2p/src/peer_manager.rs`](https://github.com/FuelLabs/fuel-core/blob/main/crates/services/p2p/src/peer_manager.rs) maintains connection state and enforces network health through a dual-score reputation system. It tracks **gossip scores** derived from message validation results and **application scores** provided by services like the transaction pool. Reserved peers remain immune to gossip-score bans, ensuring critical infrastructure stays connected.

### Request-Response and Gossipsub Handlers

Message serialization uses the **`PostcardCodec`** for efficient binary encoding. The **`RequestResponseMessageHandler`** in [`crates/services/p2p/src/request_response/mod.rs`](https://github.com/FuelLabs/fuel-core/blob/main/crates/services/p2p/src/request_response/mod.rs) manages concrete request types including `SealedHeaders` and `Transactions`, while **`GossipsubTopics`** in [`crates/services/p2p/src/gossipsub/topics.rs`](https://github.com/FuelLabs/fuel-core/blob/main/crates/services/p2p/src/gossipsub/topics.rs) maps raw `TopicHash` values to logical tags like `Transaction` and `BlockHeader`.

## Synchronization Flow

### Startup and Bootstrap

When a node starts, `FuelP2PService::new` parses the configuration and resolves bootstrap node addresses. The service initiates listening via `start()`, which creates TCP multiaddrs and blocks until the listener confirms availability through `await_listeners_address`.

### Event Loop Processing

The `next_event` method drives the main async loop, pulling `SwarmEvent`s from libp2p and dispatching them to specialized handlers:

- **Gossipsub events** convert to `FuelP2PEvent::GossipsubMessage` or `NewSubscription`
- **Identify events** update peer addresses, increment unique peer metrics in [`crates/fuel-core-metrics/src/p2p_metrics.rs`](https://github.com/FuelLabs/fuel-core/blob/main/crates/fuel-core-metrics/src/p2p_metrics.rs), and notify the `PeerManager`
- **Heartbeat events** propagate current `BlockHeight` as `FuelP2PEvent::PeerInfoUpdated`
- **Request-Response events** populate inbound/outbound tables and forward `InboundRequestMessage` completions

### On-Demand Data Synchronization

For historical data retrieval, the `send_request_msg` method selects a random connected peer (unless specified), transmits a `RequestMessage`, and stores a `ResponseSender` channel mapping. When responses arrive via `request_response::Event::Message::Response`, the service routes results back to callers through the stored `OutboundRequestId` mapping.

### Reputation Decay and Banning

The `PeerManager` periodically triggers `PeerReportEvent::PerformDecay` to lower scores over time, encouraging healthy participation. Misbehaving peers face connection termination or bans based on cumulative scoring violations.

## Code Examples

### Bootstrapping a FuelP2PService

```rust
use fuel_core::{
    config::Config,
    p2p_test_helpers::bootstrap_nodes,
};
use fuel_core::crates::services::p2p::p2p_service::FuelP2PService;
use fuel_core::crates::codecs::gossipsub::GossipsubMessageHandler;
use fuel_core::crates::codecs::request_response::RequestResponseMessageHandler;
use tokio::sync::broadcast;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Load a node config (could be from a TOML file)
    let mut config = Config::default();

    // Example: add a DNS‑addr bootstrap node
    config.bootstrap_nodes.push("/dnsaddr/bootstrap.fuel.network".parse()?);

    // Create a channel that the p2p service uses to broadcast the number of
    // reserved peers that have been discovered.
    let (reserved_tx, _) = broadcast::channel(16);

    // Initialise codecs
    let gossipsub_codec = GossipsubMessageHandler::new();
    let request_response_codec = RequestResponseMessageHandler::new();

    // Build the service
    let mut p2p = FuelP2PService::new(
        reserved_tx,
        config,
        gossipsub_codec,
        request_response_codec,
    )
    .await?;

    // Start listening on the configured address
    p2p.start().await?;

    // Run the event loop (simplified)
    loop {
        if let Some(event) = p2p.next_event().await {
            println!("P2P event: {event:?}");
        }
    }
}

```

### Publishing Transactions via Gossipsub

```rust
use fuel_core::crates::services::p2p::gossipsub::messages::GossipsubBroadcastRequest;
use fuel_core::crates::services::p2p::types::TransactionGossip;

// Assume `p2p` is a mutable `FuelP2PService` already started.
async fn gossip_tx(p2p: &mut FuelP2PService) -> Result<(), PublishError> {
    // Build a transaction gossip payload (the concrete type depends on your
    // application; here we use a placeholder).
    let tx_gossip = TransactionGossip::from(tx);
    let request = GossipsubBroadcastRequest::Transaction(tx_gossip);

    // Publish – internally the service converts the request to a topic hash,
    // encodes it with `PostcardCodec`, and forwards to libp2p.
    p2p.publish_message(request)
}

```

### Requesting Sealed Headers from Peers

```rust
use fuel_core::crates::services::p2p::request_response::messages::{RequestMessage, ResponseSender};

async fn request_sealed_headers(
    p2p: &mut FuelP2PService,
    peer: Option<PeerId>,
) -> anyhow::Result<Vec<SealedHeader>> {
    // Build the request message
    let request = RequestMessage::SealedHeaders { start: 0, count: 10 };

    // Create a oneshot channel that will receive the response.
    let (tx, rx) = tokio::sync::oneshot::channel();

    // Register the sender in the service's outbound map.
    p2p.send_request_msg(peer, request, ResponseSender::SealedHeaders(tx))?;

    // Await the response (the service will forward the V2ResponseMessage
    // back via the stored channel).
    let (peer_id, result) = rx.await??;
    println!("Received sealed headers from {peer_id}");
    Ok(result)
}

```

## Summary

- **FuelP2PService** orchestrates the libp2p swarm and converts raw network events into `FuelP2PEvent`s for the core orchestrator.
- **FuelBehaviour** aggregates Gossipsub, Request-Response, Heartbeat, Identify, and Discovery protocols into a single libp2p behaviour.
- **PeerManager** enforces network health through reputation scoring (gossip and application scores) and handles bans for misbehaving peers.
- **Request-Response protocol** enables on-demand synchronization of sealed headers and transactions via `send_request_msg` and channel-based response routing.
- **Gossipsub topics** defined in `GossipsubTopics` handle broadcast messaging for transactions and block headers using `PostcardCodec` serialization.
- **DNS resolution** occurs at startup via `parse_multiaddrs`, supporting bootstrap nodes configured with `/dnsaddr/` multiaddrs.

## Frequently Asked Questions

### How does Fuel Core handle peer reputation and banning?

The `PeerManager` in [`crates/services/p2p/src/peer_manager.rs`](https://github.com/FuelLabs/fuel-core/blob/main/crates/services/p2p/src/peer_manager.rs) maintains two distinct scoring mechanisms: **gossip scores** derived from message validation results and **application scores** provided by higher-level services like the transaction pool. Peers with consistently low scores face disconnection or bans, though reserved peers are immune to gossip-score bans. Periodic decay events gradually lower scores over time to encourage renewed good behavior.

### What serialization format does Fuel Core use for P2P messages?

Fuel Core uses the **`PostcardCodec`** for serializing and deserializing request-response payloads and gossipsub messages. This codec provides efficient binary encoding defined in the request-response and gossipsub modules, ensuring minimal bandwidth overhead during node synchronization.

### How does a node request specific block data from peers?

Nodes use the **request-response protocol** implemented in `FuelP2PService::send_request_msg`. The caller provides a `RequestMessage` variant (such as `SealedHeaders` or `Transactions`) and a `ResponseSender` channel. The service selects a random connected peer if none is specified, transmits the request, and routes the response back through the stored channel when the peer replies.

### What happens during the Fuel Core P2P startup sequence?

During startup, `FuelP2PService::new` parses the node configuration, resolves DNS addresses via `parse_multiaddrs`, builds the libp2p transport, and creates the `Swarm` with the composite `FuelBehaviour`. The `start()` method then initiates TCP listeners and blocks until the addresses are confirmed, establishing the node's presence on the network before processing events through `next_event`.