Using the P2P Networking Utilities
To spin up a P2P network, use the networking helpers exposed on the runner environment (BlueprintEnvironment):
BlueprintEnvironment::libp2p_network_config::<K>(network_name, using_evm_address_for_handshake_verification)BlueprintEnvironment::libp2p_start_network(network_config, allowed_keys, allowed_keys_rx)
Example
Here’s an example of how to spin up a P2P network and send messages to it.
use std::collections::HashSet;
use blueprint_sdk::networking::service_handle::NetworkServiceHandle;
use blueprint_sdk::networking::{AllowedKeys, InstanceMsgPublicKey};
use blueprint_sdk::runner::config::BlueprintEnvironment;
use blueprint_sdk::crypto::k256::K256Ecdsa;
fn example_usage(env: BlueprintEnvironment) -> anyhow::Result<()> {
// Whitelist the operators that should participate in this service instance.
// (In production, you typically source this from the on-chain service operator set.)
let allowed_keys: HashSet<InstanceMsgPublicKey> = /* ... */;
// Create the `NetworkConfig` based on the runner environment.
let network_config = env.libp2p_network_config::<K256Ecdsa>(
"my/protocol/1.0.0",
true, // verify handshake against EVM addresses
)?;
// Start up the network, getting a handle back
let (allowed_keys_tx, allowed_keys_rx) = crossbeam_channel::unbounded();
let allowed = AllowedKeys::<K256Ecdsa>::InstancePublicKeys(
allowed_keys.into_iter().map(|k| k.0).collect(),
);
let mut network_handle: NetworkServiceHandle<K256Ecdsa> =
env.libp2p_start_network(network_config, allowed, allowed_keys_rx)?;
// 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.
use blueprint_sdk::runner::config::BlueprintEnvironment;
use blueprint_sdk::networking::service_handle::NetworkServiceHandle;
use blueprint_sdk::crypto::k256::K256Ecdsa;
pub struct BlsContext {
pub env: BlueprintEnvironment,
pub network: NetworkServiceHandle<K256Ecdsa>,
}
impl BlsContext {
pub async fn new(env: BlueprintEnvironment) -> anyhow::Result<Self> {
let (allowed_keys_tx, allowed_keys_rx) = crossbeam_channel::unbounded();
let allowed = blueprint_sdk::networking::AllowedKeys::<K256Ecdsa>::default();
let network_config = env.libp2p_network_config::<K256Ecdsa>(NETWORK_PROTOCOL, true)?;
let network = env.libp2p_start_network(network_config, allowed, allowed_keys_rx)?;
Ok(Self { env, network })
}
}Round Based Job
round-based is a library for building structure round based protocols, 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 blueprint-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>, anyhow::Error> {
// 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)
}