Developers
Blueprint P2P Networking
Usage

Using the P2P Networking Utilities

To spin up a P2P network, following two methods are provided on GadgetConfiguration:

  • GadgetConfiguration::libp2p_network_config()
  • GadgetConfiguration::libp2p_start_network()

Example

Here's an example of how to spin up a P2P network and send messages to it.

use blueprint_sdk::networking::service_handle::NetworkServiceHandle;
use blueprint_sdk::networking::InstanceMsgPublicKey;
fn example_usage(config: GadgetConfiguration) -> Result<(), GadgetError> {
    let allowed_keys: HashSet<InstanceMsgPublicKey> = /* ... */;
 
    // Create the `NetworkConfig` based on the `GadgetConfiguration`
    let network_config = config.libp2p_network_config("my/protocol/1.0.0")?;
 
    // Start up the network, getting a handle back
    let network_handle = config.libp2p_start_network(network_config, allowed_keys)?;
 
    // Use the handle to receive p2p messages from the network
    loop {
        if let Some(msg) = network_handle.next_protocol_message() {
            println!("Received message: {:?}", msg);
        }
 
        tokio::time::sleep(std::time::Duration::from_millis(100)).await;
    }
 
    // Use the handle to send p2p messages to the network
    let p2p_routing = MessageRouting {
        /// Unique identifier for this message
        message_id: 1,
        /// The round/sequence number this message belongs to
        round_id: 1,
        /// The sender's information
        sender: ParticipantInfo {
            /// The public key of the sender
            public_key: InstanceMsgPublicKey(/* ... */),
            /// The address of the sender
            address: /* ... */
        },
        /// Recipient information for direct messages
        recipient: Some(ParticipantInfo {
            public_key: InstanceMsgPublicKey(/* ... */),
            address: /* ... */
        }),
    };
    network_handle.send(p2p_routing, /* ...some bytes (Vec<u8>)... */);
 
    // Send gossip messages to the network
    let gossip_routing = MessageRouting {
        message_id: 1,
        round_id: 1,
        sender: ParticipantInfo {
            public_key: InstanceMsgPublicKey(/* ... */),
            address: /* ... */
        },
        recipient: None,
    };
    network_handle.send(gossip_routing, /* ...some bytes (Vec<u8>)... */);
 
    Ok(())
}

Integrating Networking with Service contexts

The P2P networking utilities can be integrated into service contexts to manage network state and handle messages. It exposes an interface for you to send messages to other peers of your service as well as gossip messages to the entire network of service instance operators.

Context Constructor

Create a context that you can pass into your jobs and background services.

/// The context holds necessary information for the service to run.
#[derive(Clone, KeystoreContext, TangleClientContext, ServicesContext)]
pub struct BlsContext {
    #[config]
    pub config: GadgetConfiguration,
    #[call_id]
    pub call_id: Option<u64>,
    pub network_backend: NetworkServiceHandle,
    pub store: Arc<LocalDatabase<BlsState>>,
    pub identity: sp_core::ecdsa::Pair,
}
 
// Core context management implementation
impl BlsContext {
    /// Creates a new service context with the provided configuration
    ///
    /// # Errors
    /// Returns an error if:
    /// - Network initialization fails
    /// - Configuration is invalid
    pub async fn new(config: GadgetConfiguration) -> Result<Self> {
        let operator_keys: HashSet<InstanceMsgPublicKey> = config
            .tangle_client()
            .await?
            .get_operators()
            .await?
            .values()
            .map(|key| InstanceMsgPublicKey(*key))
            .collect();
 
        let network_config = config.libp2p_network_config(NETWORK_PROTOCOL)?;
        let identity = network_config.instance_key_pair.0.clone();
 
        let network_backend = config.libp2p_start_network(network_config, operator_keys)?;
 
        let keystore_dir = PathBuf::from(&config.keystore_uri).join("bls.json");
        let store = Arc::new(LocalDatabase::open(keystore_dir));
 
        Ok(Self {
            config,
            call_id: None,
            network_backend,
            store,
            identity,
        })
    }
}

Round Based Job

round-based is a library for building structure round based protocols (opens in a new tab), especially MPC protocols. There are a variety of benefits to structuring your protocol in this way and it can streamline the separation between networking and protocol logic.

To leverage a round-based protocol that handles sending, receiving, and processing messages use the RoundBasedNetworkAdapter available from the SDK and in the gadget-networking-round-based-extension crate.

#[job(
    id = 0,
    params(t),
    event_listener(
        listener = TangleEventListener<BlsContext, JobCalled>,
        pre_processor = services_pre_processor,
        post_processor = services_post_processor,
    ),
)]
pub async fn keygen(t: u16, context: BlsContext) -> Result<Vec<u8>, GadgetError> {
    // Get configuration and compute deterministic values
    let blueprint_id = context
        .blueprint_id()
        .map_err(|e| KeygenError::ContextError(e.to_string()))?;
    let call_id = context
        .current_call_id()
        .await
        .map_err(|e| KeygenError::ContextError(e.to_string()))?;
    // Setup party information
    let (i, operators) = context
        .get_party_index_and_operators()
        .await
        .map_err(|e| KeygenError::ContextError(e.to_string()))?;
    let parties: HashMap<u16, InstanceMsgPublicKey> = operators
        .into_iter()
        .enumerate()
        .map(|(j, (_, ecdsa))| (j as PartyIndex, InstanceMsgPublicKey(ecdsa)))
        .collect();
    let n = parties.len() as u16;
    let i = i as u16;
    // Create a new round based network adapter
    let network = RoundBasedNetworkAdapter::<KeygenMsg>::new(
        context.network_backend.clone(),
        i,
        parties.clone(),
        crate::context::NETWORK_PROTOCOL,
    );
 
    // Create a new round based party
    let party = round_based::party::MpcParty::connected(network);
 
    // Run the keygen protocol
    let output = crate::keygen_state_machine::bls_keygen_protocol(party, i, t, n, call_id).await?;
 
    Ok(output)
}