Reading and Writing Messages
Let's first learn about the MessageActor
we created just before instantiating the Peer
type, tasked with reading messages from the corresponding peer.
TCP Message Actor
The MessageActor
type is a simple struct that wraps the transport reader and communicates to the Peer
via an unbound channel.
Filename: p2p_wire/peer.rs
// Path: floresta-wire/src/p2p_wire/peer.rs
pub struct MessageActor<R: AsyncRead + Unpin + Send> {
pub transport: ReadTransport<R>,
pub sender: UnboundedSender<ReaderMessage>,
}
// Path: floresta-wire/src/p2p_wire/peer.rs
pub fn create_actors<R: AsyncRead + Unpin + Send>(
transport: ReadTransport<R>,
) -> (UnboundedReceiver<ReaderMessage>, MessageActor<R>) {
// Open an unbound channel to communicate read peer messages
let (actor_sender, actor_receiver) = unbounded_channel();
// Initialize the actor with the `actor_sender` and the transport reader
let actor = MessageActor {
transport,
sender: actor_sender,
};
// Return the `actor_receiver` (to receive P2P messages from the actor), and the actor
(actor_receiver, actor)
}
This MessageActor
implements a run
method, which independently handles all incoming messages from the corresponding peer, and sends them to the Peer
type.
Note that the messages of the channel between MessageActor
and Peer
are of type ReaderMessage
. Let's briefly see what is this type, which is also defined in peer.rs.
// Path: floresta-wire/src/p2p_wire/peer.rs
pub enum ReaderMessage {
Block(UtreexoBlock),
Message(NetworkMessage),
Error(PeerError),
}
UtreexoBlock
is a type defined infloresta-chain
that wraps thebitcoin::Block
type as well as the utreexo data needed for validation (proofs and spent UTXOs).NetworkMessage
is a type from thebitcoin
crate (used here for all messages that are notReaderMessage::Block
).PeerError
is the unified error type for thePeer
struct (similar to howWireError
is the error type forUtreexoNode
).
Reading Messages
The run
method simply invokes the inner
method, and if it fails we notify the error to the Peer
. The inner
method is responsible for continuously reading messages from the transport via read_message
and sending them to the Peer
.
// Path: floresta-wire/src/p2p_wire/peer.rs
async fn inner(&mut self) -> std::result::Result<(), PeerError> {
loop {
match self.transport.read_message().await? {
UtreexoMessage::Standard(msg) => {
self.sender.send(ReaderMessage::Message(msg))?;
}
UtreexoMessage::Block(block) => {
self.sender.send(ReaderMessage::Block(block))?;
}
}
}
}
pub async fn run(mut self) -> Result<()> {
if let Err(err) = self.inner().await {
self.sender.send(ReaderMessage::Error(err))?;
}
Ok(())
}
We see two kinds of messages that we get from the transport component: standard messages (bitcoin::p2p::message::NetworkMessage
) and block messages (the UtreexoBlock
we have mentioned).
The read_message
method, implemented in transport.rs will actually read all the data from the peer, using either the v1 or v2 transport protocols.
Writing Messages
In order to write messages via the transport, we use the following write
method on Peer
:
// Path: floresta-wire/src/p2p_wire/peer.rs
pub async fn write(&mut self, msg: NetworkMessage) -> Result<()> {
debug!("Writing {} to peer {}", msg.command(), self.id);
self.writer.write_message(msg).await?;
Ok(())
}
Once again, here we delegate on a write_message
method on the transport writer component. This method serializes the data and sends it via the respective transport protocol.
And that's all about how we read and write P2P messages! Next, we'll explore a few Peer
methods and how it handles network messages.