refactor: Merge multiplexer & miniprotocols into single crate (#244)

This commit is contained in:
Santiago Carmuega 2023-04-11 00:51:38 +02:00 committed by GitHub
parent b63d2052cb
commit d5f0c2fd80
80 changed files with 1694 additions and 3002 deletions

View file

@ -0,0 +1,201 @@
use std::net::SocketAddr;
use std::path::Path;
use byteorder::{ByteOrder, NetworkEndian};
use thiserror::Error;
use tokio::io::AsyncWriteExt;
use tokio::net::{TcpListener, TcpStream, ToSocketAddrs, UnixStream};
use tokio::time::Instant;
use tracing::trace;
const HEADER_LEN: usize = 8;
pub type Timestamp = u32;
pub type Payload = Vec<u8>;
pub type Protocol = u16;
#[derive(Debug)]
pub struct Header {
pub protocol: Protocol,
pub timestamp: Timestamp,
pub payload_len: u16,
}
impl From<&[u8]> for Header {
fn from(value: &[u8]) -> Self {
let timestamp = NetworkEndian::read_u32(&value[0..4]);
let protocol = NetworkEndian::read_u16(&value[4..6]);
let payload_len = NetworkEndian::read_u16(&value[6..8]);
Self {
timestamp,
protocol,
payload_len,
}
}
}
impl From<Header> for [u8; 8] {
fn from(value: Header) -> Self {
let mut out = [0u8; 8];
NetworkEndian::write_u32(&mut out[0..4], value.timestamp);
NetworkEndian::write_u16(&mut out[4..6], value.protocol);
NetworkEndian::write_u16(&mut out[6..8], value.payload_len);
out
}
}
pub struct Segment {
pub header: Header,
pub payload: Payload,
}
pub enum Bearer {
Tcp(TcpStream),
Unix(UnixStream),
}
const BUFFER_LEN: usize = 1024 * 10;
impl Bearer {
pub async fn connect_tcp(addr: impl ToSocketAddrs) -> Result<Self, tokio::io::Error> {
let stream = TcpStream::connect(addr).await?;
Ok(Self::Tcp(stream))
}
pub async fn accept_tcp(listener: TcpListener) -> tokio::io::Result<(Self, SocketAddr)> {
let (stream, addr) = listener.accept().await?;
Ok((Self::Tcp(stream), addr))
}
pub async fn connect_unix(path: impl AsRef<Path>) -> Result<Self, tokio::io::Error> {
let stream = UnixStream::connect(path).await?;
Ok(Self::Unix(stream))
}
pub async fn readable(&self) -> tokio::io::Result<()> {
match self {
Bearer::Tcp(x) => x.readable().await,
Bearer::Unix(x) => x.readable().await,
}
}
fn try_read(&mut self, buf: &mut [u8]) -> tokio::io::Result<usize> {
match self {
Bearer::Tcp(x) => x.try_read(buf),
Bearer::Unix(x) => x.try_read(buf),
}
}
async fn write_all(&mut self, buf: &[u8]) -> tokio::io::Result<()> {
match self {
Bearer::Tcp(x) => x.write_all(buf).await,
Bearer::Unix(x) => x.write_all(buf).await,
}
}
async fn flush(&mut self) -> tokio::io::Result<()> {
match self {
Bearer::Tcp(x) => x.flush().await,
Bearer::Unix(x) => x.flush().await,
}
}
}
#[derive(Debug, Error)]
pub enum Error {
#[error("no data available in bearer to complete segment")]
NoData,
#[error("unexpected I/O error")]
Io(#[source] tokio::io::Error),
}
pub struct SegmentBuffer(Bearer, Vec<u8>);
impl SegmentBuffer {
pub fn new(bearer: Bearer) -> Self {
Self(bearer, Vec::with_capacity(BUFFER_LEN))
}
/// Cancel-safe loop that reads from bearer until certain len
async fn cancellable_read(&mut self, required: usize) -> Result<(), Error> {
loop {
self.0.readable().await.map_err(Error::Io)?;
trace!("bearer is readable");
let remaining = required - self.1.len();
let mut buf = vec![0u8; remaining];
match self.0.try_read(&mut buf) {
Ok(0) => break Err(Error::NoData),
Ok(n) => {
trace!(n, "found data on bearer");
self.1.extend_from_slice(&buf[0..n]);
if self.1.len() >= required {
break Ok(());
}
}
Err(ref e) if e.kind() == tokio::io::ErrorKind::WouldBlock => {
trace!("reading from bearer would block");
continue;
}
Err(e) => {
return Err(Error::Io(e));
}
}
}
}
/// Peek the available data in search for a frame header
async fn peek_header(&mut self) -> Result<Header, Error> {
trace!("waiting for header buf");
self.cancellable_read(HEADER_LEN).await?;
trace!("found enough data for header");
let header = &self.1[..HEADER_LEN];
Ok(Header::from(header))
}
// Cancel-safe read of a full segment from the bearer
pub async fn read_segment(&mut self) -> Result<(Protocol, Payload), Error> {
let header = self.peek_header().await?;
trace!("waiting for full segment buf");
let segment_size = HEADER_LEN + header.payload_len as usize;
self.cancellable_read(segment_size).await?;
trace!("draining segment buffer");
let segment = self.1.drain(..segment_size);
let payload = segment.skip(HEADER_LEN).collect();
Ok((header.protocol, payload))
}
pub async fn write_segment(
&mut self,
protocol: u16,
clock: &Instant,
payload: &[u8],
) -> Result<(), std::io::Error> {
let header = Header {
protocol,
timestamp: clock.elapsed().as_micros() as u32,
payload_len: payload.len() as u16,
};
let buf: [u8; 8] = header.into();
self.0.write_all(&buf).await?;
self.0.write_all(payload).await?;
self.0.flush().await?;
Ok(())
}
}

View file

@ -0,0 +1,139 @@
use std::path::Path;
use thiserror::Error;
use tokio::task::JoinHandle;
use tracing::{debug, error};
use crate::{
bearer,
miniprotocols::{
blockfetch, chainsync, handshake, localstate, PROTOCOL_N2C_CHAIN_SYNC,
PROTOCOL_N2C_HANDSHAKE, PROTOCOL_N2C_STATE_QUERY,
},
plexer,
};
#[derive(Debug, Error)]
pub enum Error {
#[error("error connecting bearer")]
ConnectFailure(#[source] tokio::io::Error),
#[error("handshake protocol error")]
HandshakeProtocol(handshake::Error),
#[error("handshake version not accepted")]
IncompatibleVersion,
}
pub struct PeerClient {
plexer_handle: JoinHandle<tokio::io::Result<()>>,
pub handshake: handshake::Confirmation<handshake::n2n::VersionData>,
chainsync: chainsync::N2NClient,
blockfetch: blockfetch::Client,
}
impl PeerClient {
pub async fn connect(address: &str, magic: u64) -> Result<Self, Error> {
debug!("connecting");
let bearer = bearer::Bearer::connect_tcp(address)
.await
.map_err(Error::ConnectFailure)?;
let mut plexer = plexer::Plexer::new(bearer);
let channel0 = plexer.subscribe_client(0);
let channel2 = plexer.subscribe_client(2);
let channel3 = plexer.subscribe_client(3);
let plexer_handle = tokio::spawn(async move { plexer.run().await });
let versions = handshake::n2n::VersionTable::v7_and_above(magic);
let mut client = handshake::Client::new(channel0);
let handshake = client
.handshake(versions)
.await
.map_err(Error::HandshakeProtocol)?;
if let handshake::Confirmation::Rejected(reason) = handshake {
error!(?reason, "handshake refused");
return Err(Error::IncompatibleVersion);
}
Ok(Self {
plexer_handle,
handshake,
chainsync: chainsync::Client::new(channel2),
blockfetch: blockfetch::Client::new(channel3),
})
}
pub fn chainsync(&mut self) -> &mut chainsync::N2NClient {
&mut self.chainsync
}
pub fn blockfetch(&mut self) -> &mut blockfetch::Client {
&mut self.blockfetch
}
pub fn abort(&mut self) {
self.plexer_handle.abort();
}
}
pub struct NodeClient {
plexer_handle: JoinHandle<tokio::io::Result<()>>,
pub handshake: handshake::Confirmation<handshake::n2c::VersionData>,
chainsync: chainsync::N2CClient,
statequery: localstate::ClientV10,
}
impl NodeClient {
pub async fn connect(path: impl AsRef<Path>, magic: u64) -> Result<Self, Error> {
debug!("connecting");
let bearer = bearer::Bearer::connect_unix(path)
.await
.map_err(Error::ConnectFailure)?;
let mut plexer = plexer::Plexer::new(bearer);
let hs_channel = plexer.subscribe_client(PROTOCOL_N2C_HANDSHAKE);
let cs_channel = plexer.subscribe_client(PROTOCOL_N2C_CHAIN_SYNC);
let sq_channel = plexer.subscribe_client(PROTOCOL_N2C_STATE_QUERY);
let plexer_handle = tokio::spawn(async move { plexer.run().await });
let versions = handshake::n2c::VersionTable::v10_and_above(magic);
let mut client = handshake::Client::new(hs_channel);
let handshake = client
.handshake(versions)
.await
.map_err(Error::HandshakeProtocol)?;
if let handshake::Confirmation::Rejected(reason) = handshake {
error!(?reason, "handshake refused");
return Err(Error::IncompatibleVersion);
}
Ok(Self {
plexer_handle,
handshake,
chainsync: chainsync::Client::new(cs_channel),
statequery: localstate::Client::new(sq_channel),
})
}
pub fn chainsync(&mut self) -> &mut chainsync::N2CClient {
&mut self.chainsync
}
pub fn statequery(&mut self) -> &mut localstate::ClientV10 {
&mut self.statequery
}
pub fn abort(&mut self) {
self.plexer_handle.abort();
}
}

View file

@ -0,0 +1,4 @@
pub mod bearer;
pub mod facades;
pub mod miniprotocols;
pub mod plexer;

View file

@ -0,0 +1,85 @@
# Pallas Mini-protocols
This crate provides an implementation of the different Ouroboros mini-protocols as defined in the [The Shelley Networking Protocol](https://hydra.iohk.io/build/1070091/download/1/network.pdf#chapter.3) specs.
## Architectural Decisions
The following architectural decisions were made for this particular Rust implementation:
- The mini-protocols will remain agnostic of the concrete ledger implementation. For example, the block-fetch implementation is generic over the particular block data structure.
- The codec implemenation of the messages defined by the Ouroboros specs belongs to this crate, but any ledger-specific structure is out-of-scope.
- The state-machine execution will remain agnostic of the concrete mini-protocol specification.
## Development Status
| mini-protocol | initiator | responder |
| ------------------------------------------- | --------- | --------- |
| block-fetch | done | planned |
| chain-sync | done | planned |
| [handshake](src/handshake/README.md) | done | done |
| local-state | done | planned |
| [tx-submission](src/txsubmission/README.md) | done | done |
| local tx monitor | done | planned |
| local-tx-submission | ongoing | planned |
## Implementation Details
An Ouroboros mini-protocol is defined as a state-machine. This library provides the primitive artifacts to describe the different states and messages of each particular state-machine.
A local agent, either initiator or responder, interacts with a remote agent by exchanging messages and keeping its own version of the state.
By implementing the following trait, a struct can participate as an agent in an Ouroboros communication:
```rust
pub trait Agent: Sized {
type Message;
fn is_done(&self) -> bool;
fn has_agency(&self) -> bool;
fn send_next(self, tx: &impl MachineOutput) -> Transition<Self>;
fn receive_next(self, msg: Self::Message) -> Transition<Self>;
}
```
- The associate type `Message` is an enum with the particular variants of each particular miniprotocol
- The `has_agency` function describes if the agent has agency for the current state.
- The `is_done` function describes if the agent considers that all tasks have been done.
- The `send_next` function instructs the agent to send the next message in the sequence (will be called only if it has agency).
- The `receive_next` function instructs the agent to process the following received message.
The `send_next` and the `receive_next` methods will transition the state-machine from one state to the following. This transition happens without mutating any value, the idea is that each step in the process transition the agent struct into a new struct of the same type describing the new state. This approach allows us to implement the execution of the state machine as a pure function.
To tigger the execution of an agent, the library provides the following entry-point:
```rust
run_agent<T>(agent: T, channel: &mut Channel)
```
Where `T` is the type of the concrete agent to execute and the `Channel` is the Ouroboros multiplexer channel already connected to the remote party.
## Execution Example
The following example shows how to execute a Handshake client against a remote relay node.
```rust
// setup a TCP bearer against a relay node
let bearer = TcpStream::connect("relays-new.cardano-mainnet.iohk.io:3001").unwrap();
bearer.set_nodelay(true).unwrap();
bearer.set_keepalive_ms(Some(30_000u32)).unwrap();
// create a new multiplexer, specifying which mini-protocol IDs we want to sue
let mut muxer = Multiplexer::setup(bearer, &[0]).unwrap();
// get a handle for the (client-side) handhsake mini-protocol handle
let mut channel = muxer.use_client_channel(pallas_miniprotocols::PROTOCOL_N2N_HANDSHAKE);
// create a handshake client agent with an initial state
let agent = handshake::Client::initial(VersionTable::v4_and_above(MAINNET_MAGIC));
// run the agent, which internally executes all the transitions
// until it is done.
let agent = run_agent(agent, &mut channel).unwrap();
// print the final state of the agent
println!("{agent:?}");
```

View file

@ -0,0 +1,191 @@
use thiserror::Error;
use tracing::{debug, info, warn};
use crate::miniprotocols::common::Point;
use crate::plexer;
use super::{Message, State};
#[derive(Error, Debug)]
pub enum Error {
#[error("attempted to receive message while agency is ours")]
AgencyIsOurs,
#[error("attempted to send message while agency is theirs")]
AgencyIsTheirs,
#[error("inbound message is not valid for current state")]
InvalidInbound,
#[error("outbound message is not valid for current state")]
InvalidOutbound,
#[error("requested range doesn't contain any blocks")]
NoBlocks,
#[error("error while sending or receiving data through the multiplexer")]
Plexer(plexer::Error),
}
pub type Body = Vec<u8>;
pub type Range = (Point, Point);
pub type HasBlocks = Option<()>;
pub struct Client(State, plexer::ChannelBuffer);
impl Client {
pub fn new(channel: plexer::AgentChannel) -> Self {
Self(State::Idle, plexer::ChannelBuffer::new(channel))
}
pub fn state(&self) -> &State {
&self.0
}
pub fn is_done(&self) -> bool {
self.0 == State::Done
}
fn has_agency(&self) -> bool {
match self.state() {
State::Idle => true,
State::Busy => false,
State::Streaming => false,
State::Done => false,
}
}
fn assert_agency_is_ours(&self) -> Result<(), Error> {
if !self.has_agency() {
Err(Error::AgencyIsTheirs)
} else {
Ok(())
}
}
fn assert_agency_is_theirs(&self) -> Result<(), Error> {
if self.has_agency() {
Err(Error::AgencyIsOurs)
} else {
Ok(())
}
}
fn assert_outbound_state(&self, msg: &Message) -> Result<(), Error> {
match (&self.0, msg) {
(State::Idle, Message::RequestRange { .. }) => Ok(()),
(State::Idle, Message::ClientDone) => Ok(()),
_ => Err(Error::InvalidOutbound),
}
}
fn assert_inbound_state(&self, msg: &Message) -> Result<(), Error> {
match (&self.0, msg) {
(State::Busy, Message::StartBatch) => Ok(()),
(State::Busy, Message::NoBlocks) => Ok(()),
(State::Streaming, Message::Block { .. }) => Ok(()),
(State::Streaming, Message::BatchDone) => Ok(()),
_ => Err(Error::InvalidInbound),
}
}
pub async fn send_message(&mut self, msg: &Message) -> Result<(), Error> {
self.assert_agency_is_ours()?;
self.assert_outbound_state(msg)?;
self.1.send_msg_chunks(msg).await.map_err(Error::Plexer)?;
Ok(())
}
pub async fn recv_message(&mut self) -> Result<Message, Error> {
self.assert_agency_is_theirs()?;
let msg = self.1.recv_full_msg().await.map_err(Error::Plexer)?;
self.assert_inbound_state(&msg)?;
Ok(msg)
}
pub async fn send_request_range(&mut self, range: (Point, Point)) -> Result<(), Error> {
let msg = Message::RequestRange { range };
self.send_message(&msg).await?;
self.0 = State::Busy;
Ok(())
}
pub async fn recv_while_busy(&mut self) -> Result<HasBlocks, Error> {
match self.recv_message().await? {
Message::StartBatch => {
info!("batch start");
self.0 = State::Streaming;
Ok(Some(()))
}
Message::NoBlocks => {
warn!("no blocks");
self.0 = State::Idle;
Ok(None)
}
_ => Err(Error::InvalidInbound),
}
}
pub async fn request_range(&mut self, range: Range) -> Result<HasBlocks, Error> {
self.send_request_range(range).await?;
debug!("range requested");
self.recv_while_busy().await
}
pub async fn recv_while_streaming(&mut self) -> Result<Option<Body>, Error> {
debug!("waiting for stream");
match self.recv_message().await? {
Message::Block { body } => Ok(Some(body)),
Message::BatchDone => {
self.0 = State::Idle;
Ok(None)
}
_ => Err(Error::InvalidInbound),
}
}
pub async fn fetch_single(&mut self, point: Point) -> Result<Body, Error> {
self.request_range((point.clone(), point))
.await?
.ok_or(Error::NoBlocks)?;
let body = self
.recv_while_streaming()
.await?
.ok_or(Error::InvalidInbound)?;
debug!("body received");
match self.recv_while_streaming().await? {
Some(_) => Err(Error::InvalidInbound),
None => Ok(body),
}
}
pub async fn fetch_range(&mut self, range: Range) -> Result<Vec<Body>, Error> {
self.request_range(range).await?.ok_or(Error::NoBlocks)?;
let mut all = vec![];
while let Some(block) = self.recv_while_streaming().await? {
debug!("body received");
all.push(block);
}
Ok(all)
}
pub async fn send_done(&mut self) -> Result<(), Error> {
let msg = Message::ClientDone;
self.send_message(&msg).await?;
self.0 = State::Done;
Ok(())
}
}

View file

@ -0,0 +1,73 @@
use pallas_codec::minicbor::{data::Tag, decode, encode, Decode, Decoder, Encode, Encoder};
use super::Message;
impl Encode<()> for Message {
fn encode<W: encode::Write>(
&self,
e: &mut Encoder<W>,
_ctx: &mut (),
) -> Result<(), encode::Error<W::Error>> {
match self {
Message::RequestRange { range } => {
e.array(3)?.u16(0)?;
e.encode(&range.0)?;
e.encode(&range.1)?;
Ok(())
}
Message::ClientDone => {
e.array(1)?.u16(1)?;
Ok(())
}
Message::StartBatch => {
e.array(1)?.u16(2)?;
Ok(())
}
Message::NoBlocks => {
e.array(1)?.u16(3)?;
Ok(())
}
Message::Block { body } => {
e.array(2)?.u16(4)?;
e.tag(Tag::Cbor)?;
e.bytes(body)?;
Ok(())
}
Message::BatchDone => {
e.array(1)?.u16(5)?;
Ok(())
}
}
}
}
impl<'b> Decode<'b, ()> for Message {
fn decode(d: &mut Decoder<'b>, _ctx: &mut ()) -> Result<Self, decode::Error> {
d.array()?;
let label = d.u16()?;
match label {
0 => {
let point1 = d.decode()?;
let point2 = d.decode()?;
Ok(Message::RequestRange {
range: (point1, point2),
})
}
1 => Ok(Message::ClientDone),
2 => Ok(Message::StartBatch),
3 => Ok(Message::NoBlocks),
4 => {
d.tag()?;
let body = d.bytes()?;
Ok(Message::Block {
body: Vec::from(body),
})
}
5 => Ok(Message::BatchDone),
_ => Err(decode::Error::message(
"unknown variant for blockfetch message",
)),
}
}
}

View file

@ -0,0 +1,7 @@
mod client;
mod codec;
mod protocol;
pub use client::*;
pub use codec::*;
pub use protocol::*;

View file

@ -0,0 +1,19 @@
use crate::miniprotocols::Point;
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum State {
Idle,
Busy,
Streaming,
Done,
}
#[derive(Debug)]
pub enum Message {
RequestRange { range: (Point, Point) },
ClientDone,
StartBatch,
NoBlocks,
Block { body: Vec<u8> },
BatchDone,
}

View file

@ -0,0 +1,194 @@
use std::collections::{vec_deque::Iter, VecDeque};
use crate::miniprotocols::Point;
/// A memory buffer to handle chain rollbacks
///
/// This structure is intended to facilitate the process of managing rollbacks
/// in a chain sync process. The goal is to keep points in memory until they
/// reach a certain depth (# of confirmations). If a rollback happens, the
/// buffer will try to find the intersection, clear the orphaned points and keep
/// the remaining still in memory. Further forward rolls will accumulate from
/// the intersection.
///
/// It works by keeping a `VecDeque` data structure of points, where
/// roll-forward operations accumulate at the end of the deque and retrieving
/// confirmed points means to pop from the front of the deque.
///
/// Notice that it works by keeping track of points, not blocks. It is meant to
/// be used as a lightweight index where blocks can then be retrieved from a
/// more suitable memory structure / persistent storage.
#[derive(Debug)]
pub struct RollbackBuffer {
points: VecDeque<Point>,
}
impl Default for RollbackBuffer {
fn default() -> Self {
Self::new()
}
}
pub enum RollbackEffect {
Handled,
OutOfScope,
}
impl RollbackBuffer {
pub fn new() -> Self {
Self {
points: VecDeque::new(),
}
}
/// Adds a new point to the back of the buffer
pub fn roll_forward(&mut self, point: Point) {
self.points.push_back(point);
}
/// Retrieves all points above or equal a certain depth
pub fn pop_with_depth(&mut self, min_depth: usize) -> Vec<Point> {
match self.points.len().checked_sub(min_depth) {
Some(ready) => self.points.drain(0..ready).collect(),
None => vec![],
}
}
/// Find the position of a point within the buffer
pub fn position(&self, point: &Point) -> Option<usize> {
self.points.iter().position(|p| p.eq(point))
}
/// Iterates over the contents of the buffer
pub fn peek(&self) -> Iter<Point> {
self.points.iter()
}
/// Returns the size of the buffer (number of points)
pub fn size(&self) -> usize {
self.points.len()
}
/// Returns the newest point in the buffer
pub fn latest(&self) -> Option<&Point> {
self.points.back()
}
/// Returns the oldest point in the buffer
pub fn oldest(&self) -> Option<&Point> {
self.points.front()
}
/// Unwind the buffer up to a certain point, clearing orphaned items
///
/// If the buffer contains the rollback point, we can safely discard from
/// the back and return Ok. If the rollback point is outside the scope of
/// the buffer, we clear the whole buffer and notify a failure
/// in the rollback process.
pub fn roll_back(&mut self, point: &Point) -> RollbackEffect {
if let Some(x) = self.position(point) {
self.points.truncate(x + 1);
RollbackEffect::Handled
} else {
self.points.clear();
RollbackEffect::OutOfScope
}
}
}
#[cfg(test)]
mod tests {
use super::RollbackEffect;
use crate::miniprotocols::Point;
use super::RollbackBuffer;
fn dummy_point(i: u64) -> Point {
Point::new(i, i.to_le_bytes().to_vec())
}
fn build_filled_buffer(n: usize) -> RollbackBuffer {
let mut buffer = RollbackBuffer::new();
for i in 0..n {
let point = dummy_point(i as u64);
buffer.roll_forward(point);
}
buffer
}
#[test]
fn roll_forward_accumulates_points() {
let buffer = build_filled_buffer(3);
assert!(matches!(buffer.position(&dummy_point(0)), Some(0)));
assert!(matches!(buffer.position(&dummy_point(1)), Some(1)));
assert!(matches!(buffer.position(&dummy_point(2)), Some(2)));
assert_eq!(buffer.oldest().unwrap(), &dummy_point(0));
assert_eq!(buffer.latest().unwrap(), &dummy_point(2));
}
#[test]
fn pop_from_valid_depth_works() {
let mut buffer = build_filled_buffer(5);
let ready = buffer.pop_with_depth(2);
assert_eq!(dummy_point(0), ready[0]);
assert_eq!(dummy_point(1), ready[1]);
assert_eq!(dummy_point(2), ready[2]);
assert_eq!(ready.len(), 3);
assert_eq!(buffer.oldest().unwrap(), &dummy_point(3));
assert_eq!(buffer.latest().unwrap(), &dummy_point(4));
}
#[test]
fn pop_from_excessive_depth_returns_empty() {
let mut buffer = build_filled_buffer(6);
let ready = buffer.pop_with_depth(10);
assert_eq!(ready.len(), 0);
assert_eq!(buffer.oldest().unwrap(), &dummy_point(0));
assert_eq!(buffer.latest().unwrap(), &dummy_point(5));
}
#[test]
fn roll_back_within_scope_works() {
let mut buffer = build_filled_buffer(6);
let result = buffer.roll_back(&dummy_point(2));
assert!(matches!(result, RollbackEffect::Handled));
assert_eq!(buffer.size(), 3);
assert_eq!(buffer.oldest().unwrap(), &dummy_point(0));
assert_eq!(buffer.latest().unwrap(), &dummy_point(2));
let remaining = buffer.pop_with_depth(0);
assert_eq!(dummy_point(0), remaining[0]);
assert_eq!(dummy_point(1), remaining[1]);
assert_eq!(dummy_point(2), remaining[2]);
assert_eq!(remaining.len(), 3);
}
#[test]
fn roll_back_outside_scope_works() {
let mut buffer = build_filled_buffer(6);
let result = buffer.roll_back(&dummy_point(100));
assert!(matches!(result, RollbackEffect::OutOfScope));
assert_eq!(buffer.size(), 0);
assert_eq!(buffer.oldest(), None);
assert_eq!(buffer.latest(), None);
}
}

View file

@ -0,0 +1,240 @@
use pallas_codec::Fragment;
use std::marker::PhantomData;
use thiserror::Error;
use tracing::debug;
use crate::miniprotocols::Point;
use crate::plexer;
use super::{BlockContent, HeaderContent, Message, State, Tip};
#[derive(Error, Debug)]
pub enum Error {
#[error("attempted to receive message while agency is ours")]
AgencyIsOurs,
#[error("attempted to send message while agency is theirs")]
AgencyIsTheirs,
#[error("inbound message is not valid for current state")]
InvalidInbound,
#[error("outbound message is not valid for current state")]
InvalidOutbound,
#[error("no intersection point found")]
IntersectionNotFound,
#[error("error while sending or receiving data through the channel")]
Plexer(plexer::Error),
}
pub type IntersectResponse = (Option<Point>, Tip);
#[derive(Debug)]
pub enum NextResponse<CONTENT> {
RollForward(CONTENT, Tip),
RollBackward(Point, Tip),
Await,
}
pub struct Client<O>(State, plexer::ChannelBuffer, PhantomData<O>)
where
Message<O>: Fragment;
impl<O> Client<O>
where
Message<O>: Fragment,
{
pub fn new(channel: plexer::AgentChannel) -> Self {
Self(
State::Idle,
plexer::ChannelBuffer::new(channel),
PhantomData {},
)
}
pub fn state(&self) -> &State {
&self.0
}
pub fn is_done(&self) -> bool {
self.0 == State::Done
}
pub fn has_agency(&self) -> bool {
match self.state() {
State::Idle => true,
State::CanAwait => false,
State::MustReply => false,
State::Intersect => false,
State::Done => false,
}
}
fn assert_agency_is_ours(&self) -> Result<(), Error> {
if !self.has_agency() {
Err(Error::AgencyIsTheirs)
} else {
Ok(())
}
}
fn assert_agency_is_theirs(&self) -> Result<(), Error> {
if self.has_agency() {
Err(Error::AgencyIsOurs)
} else {
Ok(())
}
}
fn assert_outbound_state(&self, msg: &Message<O>) -> Result<(), Error> {
match (&self.0, msg) {
(State::Idle, Message::RequestNext) => Ok(()),
(State::Idle, Message::FindIntersect(_)) => Ok(()),
(State::Idle, Message::Done) => Ok(()),
_ => Err(Error::InvalidOutbound),
}
}
fn assert_inbound_state(&self, msg: &Message<O>) -> Result<(), Error> {
match (&self.0, msg) {
(State::CanAwait, Message::RollForward(_, _)) => Ok(()),
(State::CanAwait, Message::RollBackward(_, _)) => Ok(()),
(State::CanAwait, Message::AwaitReply) => Ok(()),
(State::MustReply, Message::RollForward(_, _)) => Ok(()),
(State::MustReply, Message::RollBackward(_, _)) => Ok(()),
(State::Intersect, Message::IntersectFound(_, _)) => Ok(()),
(State::Intersect, Message::IntersectNotFound(_)) => Ok(()),
_ => Err(Error::InvalidInbound),
}
}
pub async fn send_message(&mut self, msg: &Message<O>) -> Result<(), Error> {
self.assert_agency_is_ours()?;
self.assert_outbound_state(msg)?;
self.1.send_msg_chunks(msg).await.map_err(Error::Plexer)?;
Ok(())
}
pub async fn recv_message(&mut self) -> Result<Message<O>, Error> {
self.assert_agency_is_theirs()?;
let msg = self.1.recv_full_msg().await.map_err(Error::Plexer)?;
self.assert_inbound_state(&msg)?;
Ok(msg)
}
pub async fn send_find_intersect(&mut self, points: Vec<Point>) -> Result<(), Error> {
let msg = Message::FindIntersect(points);
self.send_message(&msg).await?;
self.0 = State::Intersect;
debug!("send find intersect");
Ok(())
}
pub async fn recv_intersect_response(&mut self) -> Result<IntersectResponse, Error> {
debug!("waiting for intersect response");
match self.recv_message().await? {
Message::IntersectFound(point, tip) => {
self.0 = State::Idle;
Ok((Some(point), tip))
}
Message::IntersectNotFound(tip) => {
self.0 = State::Idle;
Ok((None, tip))
}
_ => Err(Error::InvalidInbound),
}
}
pub async fn find_intersect(&mut self, points: Vec<Point>) -> Result<IntersectResponse, Error> {
self.send_find_intersect(points).await?;
self.recv_intersect_response().await
}
pub async fn send_request_next(&mut self) -> Result<(), Error> {
let msg = Message::RequestNext;
self.send_message(&msg).await?;
self.0 = State::CanAwait;
Ok(())
}
pub async fn recv_while_can_await(&mut self) -> Result<NextResponse<O>, Error> {
match self.recv_message().await? {
Message::AwaitReply => {
self.0 = State::MustReply;
Ok(NextResponse::Await)
}
Message::RollForward(a, b) => {
self.0 = State::Idle;
Ok(NextResponse::RollForward(a, b))
}
Message::RollBackward(a, b) => {
self.0 = State::Idle;
Ok(NextResponse::RollBackward(a, b))
}
_ => Err(Error::InvalidInbound),
}
}
pub async fn recv_while_must_reply(&mut self) -> Result<NextResponse<O>, Error> {
match self.recv_message().await? {
Message::RollForward(a, b) => {
self.0 = State::Idle;
Ok(NextResponse::RollForward(a, b))
}
Message::RollBackward(a, b) => {
self.0 = State::Idle;
Ok(NextResponse::RollBackward(a, b))
}
_ => Err(Error::InvalidInbound),
}
}
pub async fn request_next(&mut self) -> Result<NextResponse<O>, Error> {
debug!("requesting next block");
self.send_request_next().await?;
self.recv_while_can_await().await
}
pub async fn intersect_origin(&mut self) -> Result<Point, Error> {
debug!("intersecting origin");
let (point, _) = self.find_intersect(vec![Point::Origin]).await?;
point.ok_or(Error::IntersectionNotFound)
}
pub async fn intersect_tip(&mut self) -> Result<Point, Error> {
let (_, Tip(point, _)) = self.find_intersect(vec![Point::Origin]).await?;
debug!(?point, "found tip value");
let (point, _) = self.find_intersect(vec![point]).await?;
point.ok_or(Error::IntersectionNotFound)
}
pub async fn send_done(&mut self) -> Result<(), Error> {
let msg = Message::Done;
self.send_message(&msg).await?;
self.0 = State::Done;
Ok(())
}
}
pub type N2NClient = Client<HeaderContent>;
pub type N2CClient = Client<BlockContent>;

View file

@ -0,0 +1,210 @@
use pallas_codec::minicbor;
use pallas_codec::minicbor::{decode, encode, Decode, Decoder, Encode, Encoder};
use super::{BlockContent, HeaderContent, Message, SkippedContent, Tip};
impl minicbor::encode::Encode<()> for Tip {
fn encode<W: encode::Write>(
&self,
e: &mut Encoder<W>,
_ctx: &mut (),
) -> Result<(), encode::Error<W::Error>> {
e.array(2)?;
e.encode(&self.0)?;
e.u64(self.1)?;
Ok(())
}
}
impl<'b> Decode<'b, ()> for Tip {
fn decode(d: &mut Decoder<'b>, _ctx: &mut ()) -> Result<Self, decode::Error> {
d.array()?;
let point = d.decode()?;
let block_num = d.u64()?;
Ok(Tip(point, block_num))
}
}
impl<C> Encode<()> for Message<C>
where
C: Encode<()>,
{
fn encode<W: encode::Write>(
&self,
e: &mut Encoder<W>,
_ctx: &mut (),
) -> Result<(), encode::Error<W::Error>> {
match self {
Message::RequestNext => {
e.array(1)?.u16(0)?;
Ok(())
}
Message::AwaitReply => {
e.array(1)?.u16(1)?;
Ok(())
}
Message::RollForward(content, tip) => {
e.array(3)?.u16(2)?;
e.encode(content)?;
e.encode(tip)?;
Ok(())
}
Message::RollBackward(point, tip) => {
e.array(3)?.u16(3)?;
e.encode(point)?;
e.encode(tip)?;
Ok(())
}
Message::FindIntersect(points) => {
e.array(2)?.u16(4)?;
e.array(points.len() as u64)?;
for point in points.iter() {
e.encode(point)?;
}
Ok(())
}
Message::IntersectFound(point, tip) => {
e.array(3)?.u16(5)?;
e.encode(point)?;
e.encode(tip)?;
Ok(())
}
Message::IntersectNotFound(tip) => {
e.array(1)?.u16(6)?;
e.encode(tip)?;
Ok(())
}
Message::Done => {
e.array(1)?.u16(7)?;
Ok(())
}
}
}
}
impl<'b, C> Decode<'b, ()> for Message<C>
where
C: Decode<'b, ()>,
{
fn decode(d: &mut Decoder<'b>, _ctx: &mut ()) -> Result<Self, decode::Error> {
d.array()?;
let label = d.u16()?;
match label {
0 => Ok(Message::RequestNext),
1 => Ok(Message::AwaitReply),
2 => {
let content = d.decode()?;
let tip = d.decode()?;
Ok(Message::RollForward(content, tip))
}
3 => {
let point = d.decode()?;
let tip = d.decode()?;
Ok(Message::RollBackward(point, tip))
}
4 => {
let points = d.decode()?;
Ok(Message::FindIntersect(points))
}
5 => {
let point = d.decode()?;
let tip = d.decode()?;
Ok(Message::IntersectFound(point, tip))
}
6 => {
let tip = d.decode()?;
Ok(Message::IntersectNotFound(tip))
}
7 => Ok(Message::Done),
_ => Err(decode::Error::message(
"unknown variant for chainsync message",
)),
}
}
}
impl<'b> Decode<'b, ()> for HeaderContent {
fn decode(d: &mut Decoder<'b>, _ctx: &mut ()) -> Result<Self, decode::Error> {
d.array()?;
let variant = d.u8()?; // era variant
match variant {
// byron
0 => {
d.array()?;
// can't find a reference anywhere about the structure of these values, but they
// seem to provide the Byron-specific variant of the header
let (a, b): (u8, u64) = d.decode()?;
d.tag()?;
let bytes = d.bytes()?;
Ok(HeaderContent {
variant,
byron_prefix: Some((a, b)),
cbor: Vec::from(bytes),
})
}
// shelley and beyond
_ => {
d.tag()?;
let bytes = d.bytes()?;
Ok(HeaderContent {
variant,
byron_prefix: None,
cbor: Vec::from(bytes),
})
}
}
}
}
impl Encode<()> for HeaderContent {
fn encode<W: encode::Write>(
&self,
_e: &mut Encoder<W>,
_ctx: &mut (),
) -> Result<(), encode::Error<W::Error>> {
todo!()
}
}
impl<'b> Decode<'b, ()> for BlockContent {
fn decode(d: &mut Decoder<'b>, _ctx: &mut ()) -> Result<Self, decode::Error> {
d.tag()?;
let bytes = d.bytes()?;
Ok(BlockContent(Vec::from(bytes)))
}
}
impl Encode<()> for BlockContent {
fn encode<W: encode::Write>(
&self,
_e: &mut Encoder<W>,
_ctx: &mut (),
) -> Result<(), encode::Error<W::Error>> {
todo!()
}
}
impl<'b> Decode<'b, ()> for SkippedContent {
fn decode(d: &mut Decoder<'b>, _ctx: &mut ()) -> Result<Self, decode::Error> {
d.skip()?;
Ok(SkippedContent)
}
}
impl Encode<()> for SkippedContent {
fn encode<W: encode::Write>(
&self,
_e: &mut Encoder<W>,
_ctx: &mut (),
) -> Result<(), encode::Error<W::Error>> {
todo!()
}
}

View file

@ -0,0 +1,9 @@
mod buffer;
mod client;
mod codec;
mod protocol;
pub use buffer::*;
pub use client::*;
pub use codec::*;
pub use protocol::*;

View file

@ -0,0 +1,55 @@
use std::{fmt::Debug, ops::Deref};
use crate::miniprotocols::Point;
#[derive(Debug, Clone)]
pub struct Tip(pub Point, pub u64);
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum State {
Idle,
CanAwait,
MustReply,
Intersect,
Done,
}
/// A generic chain-sync message for either header or block content
#[derive(Debug)]
pub enum Message<C> {
RequestNext,
AwaitReply,
RollForward(C, Tip),
RollBackward(Point, Tip),
FindIntersect(Vec<Point>),
IntersectFound(Point, Tip),
IntersectNotFound(Tip),
Done,
}
#[derive(Debug)]
pub struct HeaderContent {
pub variant: u8,
pub byron_prefix: Option<(u8, u64)>,
pub cbor: Vec<u8>,
}
#[derive(Debug)]
pub struct BlockContent(pub Vec<u8>);
impl Deref for BlockContent {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<BlockContent> for Vec<u8> {
fn from(other: BlockContent) -> Self {
other.0
}
}
#[derive(Debug)]
pub struct SkippedContent;

View file

@ -0,0 +1,122 @@
use std::fmt::Debug;
use pallas_codec::minicbor::{decode, encode, Decode, Decoder, Encode, Encoder};
/// Well-known magic for testnet
pub const TESTNET_MAGIC: u64 = 1097911063;
/// Well-known magic for mainnet
pub const MAINNET_MAGIC: u64 = 764824073;
/// Well-known magic for preview
pub const PREVIEW_MAGIC: u64 = 2;
/// Well-known magic for pre-production
pub const PRE_PRODUCTION_MAGIC: u64 = 1;
/// Bitflag for client-side version of a known protocol
/// # Example
/// ```
/// use pallas_network::miniprotocols::*;
/// let channel = PROTOCOL_CLIENT | PROTOCOL_N2N_HANDSHAKE;
/// ```
pub const PROTOCOL_CLIENT: u16 = 0x0;
/// Bitflag for server-side version of a known protocol
/// # Example
/// ```
/// use pallas_network::miniprotocols::*;
/// let channel = PROTOCOL_SERVER | PROTOCOL_N2N_CHAIN_SYNC;
/// ```
pub const PROTOCOL_SERVER: u16 = 0x8000;
/// Protocol channel number for node-to-node handshakes
pub const PROTOCOL_N2N_HANDSHAKE: u16 = 0;
/// Protocol channel number for node-to-node chain-sync
pub const PROTOCOL_N2N_CHAIN_SYNC: u16 = 2;
/// Protocol channel number for node-to-node block-fetch
pub const PROTOCOL_N2N_BLOCK_FETCH: u16 = 3;
/// Protocol channel number for node-to-node tx-submission
pub const PROTOCOL_N2N_TX_SUBMISSION: u16 = 4;
/// Protocol channel number for node-to-node Keep-alive
pub const PROTOCOL_N2N_KEEP_ALIVE: u16 = 8;
/// Protocol channel number for node-to-client handshakes
pub const PROTOCOL_N2C_HANDSHAKE: u16 = 0;
/// Protocol channel number for node-to-client chain-sync
pub const PROTOCOL_N2C_CHAIN_SYNC: u16 = 5;
/// Protocol channel number for node-to-client tx-submission
pub const PROTOCOL_N2C_TX_SUBMISSION: u16 = 6;
// Protocol channel number for node-to-client state queries
pub const PROTOCOL_N2C_STATE_QUERY: u16 = 7;
/// A point within a chain
#[derive(Clone, Eq, PartialEq, Hash)]
pub enum Point {
Origin,
Specific(u64, Vec<u8>),
}
impl Point {
pub fn slot_or_default(&self) -> u64 {
match self {
Point::Origin => 0,
Point::Specific(slot, _) => *slot,
}
}
}
impl Debug for Point {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Origin => write!(f, "Origin"),
Self::Specific(arg0, arg1) => write!(f, "({}, {})", arg0, hex::encode(arg1)),
}
}
}
impl Point {
pub fn new(slot: u64, hash: Vec<u8>) -> Self {
Point::Specific(slot, hash)
}
}
impl Encode<()> for Point {
fn encode<W: encode::Write>(
&self,
e: &mut Encoder<W>,
_ctx: &mut (),
) -> Result<(), encode::Error<W::Error>> {
match self {
Point::Origin => e.array(0)?,
Point::Specific(slot, hash) => e.array(2)?.u64(*slot)?.bytes(hash)?,
};
Ok(())
}
}
impl<'b> Decode<'b, ()> for Point {
fn decode(d: &mut Decoder<'b>, _ctx: &mut ()) -> Result<Self, decode::Error> {
let size = d.array()?;
match size {
Some(0) => Ok(Point::Origin),
Some(2) => {
let slot = d.u64()?;
let hash = d.bytes()?;
Ok(Point::Specific(slot, Vec::from(hash)))
}
_ => Err(decode::Error::message(
"can't decode Point from array of size",
)),
}
}
}

View file

@ -0,0 +1,93 @@
# Handshake
The Handshake miniprotocol allows a client and server to negotiate a specific protocol version and set of parameters. Note: the specification refers to these as "protocol parameters", but this should not be confused with the usual notion of "protocol parameters" used by the Cardano ledger.
The Handshake miniprotocol is the first protocol to run when a connection opens; in fact, in rare cases, two nodes may connect to eachother simultaneously. This is known as a "simultaneous TCP open", and the protocol is designed to be robust to this.
This miniprotocol simulates a very simple state machine with three states, seen in the mermaid diagram below:
```mermaid
graph LR
A[ ] -->|start| StPropose
StPropose[State::Propose] -->|Message::Propose| StConfirm
StConfirm[::Confirm] -->|::Accept| StDone[::Done]
StConfirm -->|::Propose| StDone
StConfirm -->|::Refuse| StDone
```
There are separate versions of this protocol depending on whether you're communicating between two nodes over TCP, or between a node and a local process, via a unix socket.
This module provides two actors that implement either side of this role: Client and Server, each of which are detailed below.
## Client
You can instantiate a client like so
```rust
let mut n2n_client = handshake::N2NClient::new(channel0);
let mut n2c_client = handshake::N2CClient::new(channel0);
```
As the initiator, you then propose a set of versions you are aware of:
```rust
// Note: Other helper methods exist as well
n2n_client.send_propose(handshake::n2n::VersionTable::v7_and_above(MAINNET_MAGIC))?;
```
The server will then respond, either indicating which version they accept, or an outright refusal:
```rust
match n2n_client.recv_while_confirm()? {
Confirmation::Accepted(version, parameters) => {},
Request::Rejected(reason) => {}
}
```
For convenience, these two steps are wrapped in a `handshake` helper method:
```rust
n2n_client.handshake(handshake::n2n::VersionTable::v7_and_above(MAINNET_MAGIC))?;
```
Putting this all together, it looks something like this:
```rust
let mut client = handshake::N2NClient::new(channel0);
client.handshake(handshake::n2n::VersionTable::v7_and_above(MAINNET_MAGIC))?;
```
## Server
Conversely, you can instantiate a node to node or node to client server ready to shake hands like so
```rust
let mut n2n_server = handshake::N2NServer::new(channel0);
let mut n2c_server = handshake::N2NServer::new(channel0);
```
You should first recieve the set of versions that the client is proposing:
```rust
let versions = n2n_server.receive_proposed_versions()?;
```
Then, you can select one that you understand, and accept it, or refuse the handshake:
```rust
// NOTE: in practice, your version selection is probably more complicated than this
if let Some(params) = versions.values.get(7) {
n2n_server.send_accept_version(7, params)?;
} else {
n2n_server.send_refuse(RefuseReason::VersionMismatch(vec![7]))?;
}
```
All-together, this might look something like:
```rust
let mut server = handshake::N2NServer::new(channel0);
let versions = server.receive_proposed_versions()?;
if let Some(params) = versions.values.get(7) {
server.send_accept_version(7, params)?;
} else {
server.send_refuse(RefuseReason::VersionMismatch(vec![7]))?;
}
```

View file

@ -0,0 +1,132 @@
use pallas_codec::Fragment;
use std::marker::PhantomData;
use tracing::debug;
use super::{Error, Message, RefuseReason, State, VersionNumber, VersionTable};
use crate::plexer;
#[derive(Debug)]
pub enum Confirmation<D> {
Accepted(VersionNumber, D),
Rejected(RefuseReason),
}
pub struct Client<D>(State, plexer::ChannelBuffer, PhantomData<D>);
impl<D> Client<D>
where
D: std::fmt::Debug + Clone,
Message<D>: Fragment,
{
pub fn new(channel: plexer::AgentChannel) -> Self {
Self(
State::Propose,
plexer::ChannelBuffer::new(channel),
PhantomData {},
)
}
pub fn state(&self) -> &State {
&self.0
}
pub fn is_done(&self) -> bool {
self.0 == State::Done
}
pub fn has_agency(&self) -> bool {
match self.state() {
State::Propose => true,
State::Confirm => false,
State::Done => false,
}
}
fn assert_agency_is_ours(&self) -> Result<(), Error> {
if !self.has_agency() {
Err(Error::AgencyIsTheirs)
} else {
Ok(())
}
}
fn assert_agency_is_theirs(&self) -> Result<(), Error> {
if self.has_agency() {
Err(Error::AgencyIsOurs)
} else {
Ok(())
}
}
fn assert_outbound_state(&self, msg: &Message<D>) -> Result<(), Error> {
match (&self.0, msg) {
(State::Propose, Message::Propose(_)) => Ok(()),
_ => Err(Error::InvalidOutbound),
}
}
fn assert_inbound_state(&self, msg: &Message<D>) -> Result<(), Error> {
match (&self.0, msg) {
(State::Confirm, Message::Accept(..)) => Ok(()),
(State::Confirm, Message::Refuse(..)) => Ok(()),
_ => Err(Error::InvalidInbound),
}
}
pub async fn send_message(&mut self, msg: &Message<D>) -> Result<(), Error> {
self.assert_agency_is_ours()?;
self.assert_outbound_state(msg)?;
self.1.send_msg_chunks(msg).await.map_err(Error::Plexer)?;
Ok(())
}
pub async fn recv_message(&mut self) -> Result<Message<D>, Error> {
self.assert_agency_is_theirs()?;
let msg = self.1.recv_full_msg().await.map_err(Error::Plexer)?;
self.assert_inbound_state(&msg)?;
Ok(msg)
}
pub async fn send_propose(&mut self, versions: VersionTable<D>) -> Result<(), Error> {
let msg = Message::Propose(versions);
self.send_message(&msg).await?;
self.0 = State::Confirm;
debug!("version proposed");
Ok(())
}
pub async fn recv_while_confirm(&mut self) -> Result<Confirmation<D>, Error> {
match self.recv_message().await? {
Message::Accept(v, m) => {
self.0 = State::Done;
debug!("handshake accepted");
Ok(Confirmation::Accepted(v, m))
}
Message::Refuse(r) => {
self.0 = State::Done;
debug!("handshake refused");
Ok(Confirmation::Rejected(r))
}
_ => Err(Error::InvalidInbound),
}
}
pub async fn handshake(&mut self, versions: VersionTable<D>) -> Result<Confirmation<D>, Error> {
self.send_propose(versions).await?;
self.recv_while_confirm().await
}
pub fn unwrap(self) -> plexer::AgentChannel {
self.1.unwrap()
}
}
pub type N2NClient = Client<super::n2n::VersionData>;
pub type N2CClient = Client<super::n2c::VersionData>;

View file

@ -0,0 +1,10 @@
mod client;
mod protocol;
mod server;
pub mod n2c;
pub mod n2n;
pub use client::*;
pub use protocol::*;
pub use server::*;

View file

@ -0,0 +1,86 @@
use std::collections::HashMap;
use pallas_codec::minicbor::{decode, encode, Decode, Decoder, Encode, Encoder};
use super::protocol::NetworkMagic;
pub type VersionTable = super::protocol::VersionTable<VersionData>;
const PROTOCOL_V1: u64 = 1;
const PROTOCOL_V2: u64 = 32770;
const PROTOCOL_V3: u64 = 32771;
const PROTOCOL_V4: u64 = 32772;
const PROTOCOL_V5: u64 = 32773;
const PROTOCOL_V6: u64 = 32774;
const PROTOCOL_V7: u64 = 32775;
const PROTOCOL_V8: u64 = 32776;
const PROTOCOL_V9: u64 = 32777;
const PROTOCOL_V10: u64 = 32778;
const PROTOCOL_V11: u64 = 32779;
const PROTOCOL_V12: u64 = 32780;
impl VersionTable {
pub fn v1_and_above(network_magic: u64) -> VersionTable {
let values = vec![
(PROTOCOL_V1, VersionData(network_magic)),
(PROTOCOL_V2, VersionData(network_magic)),
(PROTOCOL_V3, VersionData(network_magic)),
(PROTOCOL_V4, VersionData(network_magic)),
(PROTOCOL_V5, VersionData(network_magic)),
(PROTOCOL_V6, VersionData(network_magic)),
(PROTOCOL_V7, VersionData(network_magic)),
(PROTOCOL_V8, VersionData(network_magic)),
(PROTOCOL_V9, VersionData(network_magic)),
(PROTOCOL_V10, VersionData(network_magic)),
(PROTOCOL_V11, VersionData(network_magic)),
(PROTOCOL_V12, VersionData(network_magic)),
]
.into_iter()
.collect::<HashMap<u64, VersionData>>();
VersionTable { values }
}
pub fn only_v10(network_magic: u64) -> VersionTable {
let values = vec![(PROTOCOL_V10, VersionData(network_magic))]
.into_iter()
.collect::<HashMap<u64, VersionData>>();
VersionTable { values }
}
pub fn v10_and_above(network_magic: u64) -> VersionTable {
let values = vec![
(PROTOCOL_V10, VersionData(network_magic)),
(PROTOCOL_V11, VersionData(network_magic)),
(PROTOCOL_V12, VersionData(network_magic)),
]
.into_iter()
.collect::<HashMap<u64, VersionData>>();
VersionTable { values }
}
}
#[derive(Debug, Clone)]
pub struct VersionData(NetworkMagic);
impl Encode<()> for VersionData {
fn encode<W: encode::Write>(
&self,
e: &mut Encoder<W>,
_ctx: &mut (),
) -> Result<(), encode::Error<W::Error>> {
e.u64(self.0)?;
Ok(())
}
}
impl<'b> Decode<'b, ()> for VersionData {
fn decode(d: &mut Decoder<'b>, _ctx: &mut ()) -> Result<Self, decode::Error> {
let network_magic = d.u64()?;
Ok(Self(network_magic))
}
}

View file

@ -0,0 +1,100 @@
use std::collections::HashMap;
use pallas_codec::minicbor::{decode, encode, Decode, Decoder, Encode, Encoder};
pub type VersionTable = super::protocol::VersionTable<VersionData>;
const PROTOCOL_V4: u64 = 4;
const PROTOCOL_V5: u64 = 5;
const PROTOCOL_V6: u64 = 6;
const PROTOCOL_V7: u64 = 7;
const PROTOCOL_V8: u64 = 8;
const PROTOCOL_V9: u64 = 9;
const PROTOCOL_V10: u64 = 10;
impl VersionTable {
pub fn v4_and_above(network_magic: u64) -> VersionTable {
let values = vec![
(PROTOCOL_V4, VersionData::new(network_magic, false)),
(PROTOCOL_V5, VersionData::new(network_magic, false)),
(PROTOCOL_V6, VersionData::new(network_magic, false)),
(PROTOCOL_V7, VersionData::new(network_magic, false)),
(PROTOCOL_V8, VersionData::new(network_magic, false)),
(PROTOCOL_V9, VersionData::new(network_magic, false)),
(PROTOCOL_V10, VersionData::new(network_magic, false)),
]
.into_iter()
.collect::<HashMap<u64, VersionData>>();
VersionTable { values }
}
pub fn v6_and_above(network_magic: u64) -> VersionTable {
let values = vec![
(PROTOCOL_V6, VersionData::new(network_magic, false)),
(PROTOCOL_V7, VersionData::new(network_magic, false)),
(PROTOCOL_V8, VersionData::new(network_magic, false)),
(PROTOCOL_V9, VersionData::new(network_magic, false)),
(PROTOCOL_V10, VersionData::new(network_magic, false)),
]
.into_iter()
.collect::<HashMap<u64, VersionData>>();
VersionTable { values }
}
pub fn v7_and_above(network_magic: u64) -> VersionTable {
let values = vec![
(PROTOCOL_V7, VersionData::new(network_magic, false)),
(PROTOCOL_V8, VersionData::new(network_magic, false)),
(PROTOCOL_V9, VersionData::new(network_magic, false)),
(PROTOCOL_V10, VersionData::new(network_magic, false)),
]
.into_iter()
.collect::<HashMap<u64, VersionData>>();
VersionTable { values }
}
}
#[derive(Debug, Clone)]
pub struct VersionData {
network_magic: u64,
initiator_and_responder_diffusion_mode: bool,
}
impl VersionData {
pub fn new(network_magic: u64, initiator_and_responder_diffusion_mode: bool) -> Self {
VersionData {
network_magic,
initiator_and_responder_diffusion_mode,
}
}
}
impl Encode<()> for VersionData {
fn encode<W: encode::Write>(
&self,
e: &mut Encoder<W>,
_ctx: &mut (),
) -> Result<(), encode::Error<W::Error>> {
e.array(2)?
.u64(self.network_magic)?
.bool(self.initiator_and_responder_diffusion_mode)?;
Ok(())
}
}
impl<'b> Decode<'b, ()> for VersionData {
fn decode(d: &mut Decoder<'b>, _ctx: &mut ()) -> Result<Self, decode::Error> {
d.array()?;
let network_magic = d.u64()?;
let initiator_and_responder_diffusion_mode = d.bool()?;
Ok(Self {
network_magic,
initiator_and_responder_diffusion_mode,
})
}
}

View file

@ -0,0 +1,214 @@
use itertools::Itertools;
use pallas_codec::minicbor::{decode, encode, Decode, Decoder, Encode, Encoder};
use std::{collections::HashMap, fmt::Debug};
use thiserror::*;
use crate::plexer;
#[derive(Error, Debug)]
pub enum Error {
#[error("attempted to receive message while agency is ours")]
AgencyIsOurs,
#[error("attempted to send message while agency is theirs")]
AgencyIsTheirs,
#[error("inbound message is not valid for current state")]
InvalidInbound,
#[error("outbound message is not valid for current state")]
InvalidOutbound,
#[error("error while sending or receiving data through the channel")]
Plexer(plexer::Error),
}
#[derive(Debug, Clone)]
pub struct VersionTable<T>
where
T: Debug + Clone,
{
pub values: HashMap<u64, T>,
}
impl<T> Encode<()> for VersionTable<T>
where
T: Debug + Clone + Encode<()>,
{
fn encode<W: encode::Write>(
&self,
e: &mut Encoder<W>,
_ctx: &mut (),
) -> Result<(), encode::Error<W::Error>> {
e.map(self.values.len() as u64)?;
for key in self.values.keys().sorted() {
e.u64(*key)?;
e.encode(&self.values[key])?;
}
Ok(())
}
}
impl<'b, T> Decode<'b, ()> for VersionTable<T>
where
T: Debug + Clone + Decode<'b, ()>,
{
fn decode(d: &mut Decoder<'b>, ctx: &mut ()) -> Result<Self, decode::Error> {
let values = d.map_iter_with(ctx)?.collect::<Result<_, _>>()?;
Ok(VersionTable { values })
}
}
pub type NetworkMagic = u64;
pub type VersionNumber = u64;
#[derive(Debug)]
pub enum Message<D>
where
D: Debug + Clone,
{
Propose(VersionTable<D>),
Accept(VersionNumber, D),
Refuse(RefuseReason),
}
impl<D> Encode<()> for Message<D>
where
D: Debug + Clone,
D: Encode<()>,
VersionTable<D>: Encode<()>,
{
fn encode<W: encode::Write>(
&self,
e: &mut Encoder<W>,
_ctx: &mut (),
) -> Result<(), encode::Error<W::Error>> {
match self {
Message::Propose(version_table) => {
e.array(2)?.u16(0)?;
e.encode(version_table)?;
}
Message::Accept(version_number, version_data) => {
e.array(3)?.u16(1)?;
e.u64(*version_number)?;
e.encode(version_data)?;
}
Message::Refuse(reason) => {
e.array(2)?.u16(2)?;
e.encode(reason)?;
}
};
Ok(())
}
}
impl<'b, D> Decode<'b, ()> for Message<D>
where
D: Decode<'b, ()> + Debug + Clone,
VersionTable<D>: Decode<'b, ()>,
{
fn decode(d: &mut Decoder<'b>, _ctx: &mut ()) -> Result<Self, decode::Error> {
d.array()?;
match d.u16()? {
0 => {
let version_table = d.decode()?;
Ok(Message::Propose(version_table))
}
1 => {
let version_number = d.u64()?;
let version_data = d.decode()?;
Ok(Message::Accept(version_number, version_data))
}
2 => {
let reason: RefuseReason = d.decode()?;
Ok(Message::Refuse(reason))
}
_ => Err(decode::Error::message(
"unknown variant for handshake message",
)),
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum State {
Propose,
Confirm,
Done,
}
#[derive(Debug)]
pub enum RefuseReason {
VersionMismatch(Vec<VersionNumber>),
HandshakeDecodeError(VersionNumber, String),
Refused(VersionNumber, String),
}
impl Encode<()> for RefuseReason {
fn encode<W: encode::Write>(
&self,
e: &mut Encoder<W>,
_ctx: &mut (),
) -> Result<(), encode::Error<W::Error>> {
match self {
RefuseReason::VersionMismatch(versions) => {
e.array(2)?;
e.u16(0)?;
e.array(versions.len() as u64)?;
for v in versions.iter() {
e.u64(*v)?;
}
Ok(())
}
RefuseReason::HandshakeDecodeError(version, msg) => {
e.array(3)?;
e.u16(1)?;
e.u64(*version)?;
e.str(msg)?;
Ok(())
}
RefuseReason::Refused(version, msg) => {
e.array(3)?;
e.u16(2)?;
e.u64(*version)?;
e.str(msg)?;
Ok(())
}
}
}
}
impl<'b> Decode<'b, ()> for RefuseReason {
fn decode(d: &mut Decoder<'b>, _ctx: &mut ()) -> Result<Self, decode::Error> {
d.array()?;
match d.u16()? {
0 => {
let versions = d.array_iter::<u64>()?;
let versions = versions.try_collect()?;
Ok(RefuseReason::VersionMismatch(versions))
}
1 => {
let version = d.u64()?;
let msg = d.str()?;
Ok(RefuseReason::HandshakeDecodeError(version, msg.to_string()))
}
2 => {
let version = d.u64()?;
let msg = d.str()?;
Ok(RefuseReason::Refused(version, msg.to_string()))
}
_ => Err(decode::Error::message("unknown variant for refusereason")),
}
}
}

View file

@ -0,0 +1,119 @@
use std::marker::PhantomData;
use pallas_codec::Fragment;
use super::{Error, Message, RefuseReason, State, VersionNumber, VersionTable};
use crate::plexer;
pub struct Server<D>(State, plexer::ChannelBuffer, PhantomData<D>);
impl<D> Server<D>
where
D: std::fmt::Debug + Clone,
Message<D>: Fragment,
{
pub fn new(channel: plexer::AgentChannel) -> Self {
Self(
State::Propose,
plexer::ChannelBuffer::new(channel),
PhantomData {},
)
}
pub fn state(&self) -> &State {
&self.0
}
pub fn is_done(&self) -> bool {
self.0 == State::Done
}
pub fn has_agency(&self) -> bool {
matches!(self.state(), State::Confirm)
}
fn assert_agency_is_ours(&self) -> Result<(), Error> {
if !self.has_agency() {
Err(Error::AgencyIsTheirs)
} else {
Ok(())
}
}
fn assert_agency_is_theirs(&self) -> Result<(), Error> {
if self.has_agency() {
Err(Error::AgencyIsOurs)
} else {
Ok(())
}
}
fn assert_outbound_state(&self, msg: &Message<D>) -> Result<(), Error> {
match (&self.0, msg) {
(State::Confirm, Message::Accept(..)) => Ok(()),
(State::Confirm, Message::Refuse(_)) => Ok(()),
_ => Err(Error::InvalidOutbound),
}
}
fn assert_inbound_state(&self, msg: &Message<D>) -> Result<(), Error> {
match (&self.0, msg) {
(State::Propose, Message::Propose(..)) => Ok(()),
_ => Err(Error::InvalidInbound),
}
}
pub async fn send_message(&mut self, msg: &Message<D>) -> Result<(), Error> {
self.assert_agency_is_ours()?;
self.assert_outbound_state(msg)?;
self.1.send_msg_chunks(msg).await.map_err(Error::Plexer)?;
Ok(())
}
pub async fn recv_message(&mut self) -> Result<Message<D>, Error> {
self.assert_agency_is_theirs()?;
let msg = self.1.recv_full_msg().await.map_err(Error::Plexer)?;
self.assert_inbound_state(&msg)?;
Ok(msg)
}
pub async fn receive_proposed_versions(&mut self) -> Result<VersionTable<D>, Error> {
match self.recv_message().await? {
Message::Propose(v) => {
self.0 = State::Confirm;
Ok(v)
}
_ => Err(Error::InvalidOutbound),
}
}
pub async fn accept_version(
&mut self,
version: VersionNumber,
extra_params: D,
) -> Result<(), Error> {
let message = Message::Accept(version, extra_params);
self.send_message(&message).await?;
self.0 = State::Done;
Ok(())
}
pub async fn refuse(&mut self, reason: RefuseReason) -> Result<(), Error> {
let message = Message::Refuse(reason);
self.send_message(&message).await?;
self.0 = State::Done;
Ok(())
}
pub fn unwrap(self) -> plexer::AgentChannel {
self.1.unwrap()
}
}
pub type N2NServer = Server<super::n2n::VersionData>;
pub type N2CServer = Server<super::n2c::VersionData>;

View file

@ -0,0 +1,176 @@
use std::fmt::Debug;
use pallas_codec::Fragment;
use std::marker::PhantomData;
use thiserror::*;
use super::{AcquireFailure, Message, Query, State};
use crate::miniprotocols::Point;
use crate::plexer;
#[derive(Error, Debug)]
pub enum Error {
#[error("attempted to receive message while agency is ours")]
AgencyIsOurs,
#[error("attempted to send message while agency is theirs")]
AgencyIsTheirs,
#[error("inbound message is not valid for current state")]
InvalidInbound,
#[error("outbound message is not valid for current state")]
InvalidOutbound,
#[error("failure acquiring point, not found")]
AcquirePointNotFound,
#[error("failure acquiring point, too old")]
AcquirePointTooOld,
#[error("error while sending or receiving data through the channel")]
Plexer(plexer::Error),
}
impl From<AcquireFailure> for Error {
fn from(x: AcquireFailure) -> Self {
match x {
AcquireFailure::PointTooOld => Error::AcquirePointTooOld,
AcquireFailure::PointNotOnChain => Error::AcquirePointNotFound,
}
}
}
pub struct Client<Q>(State, plexer::ChannelBuffer, PhantomData<Q>)
where
Q: Query,
Message<Q>: Fragment;
impl<Q> Client<Q>
where
Q: Query,
Message<Q>: Fragment,
{
pub fn new(channel: plexer::AgentChannel) -> Self {
Self(
State::Idle,
plexer::ChannelBuffer::new(channel),
PhantomData {},
)
}
pub fn state(&self) -> &State {
&self.0
}
pub fn is_done(&self) -> bool {
self.0 == State::Done
}
#[allow(clippy::match_like_matches_macro)]
fn has_agency(&self) -> bool {
match self.state() {
State::Idle => true,
State::Acquired => true,
_ => false,
}
}
fn assert_agency_is_ours(&self) -> Result<(), Error> {
if !self.has_agency() {
Err(Error::AgencyIsTheirs)
} else {
Ok(())
}
}
fn assert_agency_is_theirs(&self) -> Result<(), Error> {
if self.has_agency() {
Err(Error::AgencyIsOurs)
} else {
Ok(())
}
}
fn assert_outbound_state(&self, msg: &Message<Q>) -> Result<(), Error> {
match (&self.0, msg) {
(State::Idle, Message::Acquire(_)) => Ok(()),
(State::Idle, Message::Done) => Ok(()),
(State::Acquired, Message::Query(_)) => Ok(()),
(State::Acquired, Message::Release) => Ok(()),
_ => Err(Error::InvalidOutbound),
}
}
fn assert_inbound_state(&self, msg: &Message<Q>) -> Result<(), Error> {
match (&self.0, msg) {
(State::Acquiring, Message::Acquired) => Ok(()),
(State::Acquiring, Message::Failure(_)) => Ok(()),
(State::Querying, Message::Result(_)) => Ok(()),
_ => Err(Error::InvalidInbound),
}
}
pub async fn send_message(&mut self, msg: &Message<Q>) -> Result<(), Error> {
self.assert_agency_is_ours()?;
self.assert_outbound_state(msg)?;
self.1.send_msg_chunks(msg).await.map_err(Error::Plexer)?;
Ok(())
}
pub async fn recv_message(&mut self) -> Result<Message<Q>, Error> {
self.assert_agency_is_theirs()?;
let msg = self.1.recv_full_msg().await.map_err(Error::Plexer)?;
self.assert_inbound_state(&msg)?;
Ok(msg)
}
pub async fn send_acquire(&mut self, point: Option<Point>) -> Result<(), Error> {
let msg = Message::<Q>::Acquire(point);
self.send_message(&msg).await?;
self.0 = State::Acquiring;
Ok(())
}
pub async fn recv_while_acquiring(&mut self) -> Result<(), Error> {
match self.recv_message().await? {
Message::Acquired => {
self.0 = State::Acquired;
Ok(())
}
Message::Failure(x) => {
self.0 = State::Idle;
Err(Error::from(x))
}
_ => Err(Error::InvalidInbound),
}
}
pub async fn acquire(&mut self, point: Option<Point>) -> Result<(), Error> {
self.send_acquire(point).await?;
self.recv_while_acquiring().await
}
pub async fn send_query(&mut self, request: Q::Request) -> Result<(), Error> {
let msg = Message::<Q>::Query(request);
self.send_message(&msg).await?;
self.0 = State::Querying;
Ok(())
}
pub async fn recv_while_querying(&mut self) -> Result<Q::Response, Error> {
match self.recv_message().await? {
Message::Result(x) => {
self.0 = State::Acquired;
Ok(x)
}
_ => Err(Error::InvalidInbound),
}
}
pub async fn query(&mut self, request: Q::Request) -> Result<Q::Response, Error> {
self.send_query(request).await?;
self.recv_while_querying().await
}
}
pub type ClientV10 = Client<super::queries::QueryV10>;

View file

@ -0,0 +1,146 @@
use pallas_codec::minicbor::{decode, encode, Decode, Encode, Encoder};
use super::{AcquireFailure, Message, Query};
impl Encode<()> for AcquireFailure {
fn encode<W: encode::Write>(
&self,
e: &mut Encoder<W>,
_ctx: &mut (),
) -> Result<(), encode::Error<W::Error>> {
let code = match self {
AcquireFailure::PointTooOld => 0,
AcquireFailure::PointNotOnChain => 1,
};
e.u16(code)?;
Ok(())
}
}
impl<'b> Decode<'b, ()> for AcquireFailure {
fn decode(
d: &mut pallas_codec::minicbor::Decoder<'b>,
_ctx: &mut (),
) -> Result<Self, pallas_codec::minicbor::decode::Error> {
let code = d.u16()?;
match code {
0 => Ok(AcquireFailure::PointTooOld),
1 => Ok(AcquireFailure::PointNotOnChain),
_ => Err(decode::Error::message(
"can't infer acquire failure from variant id",
)),
}
}
}
impl<Q> Encode<()> for Message<Q>
where
Q: Query,
Q::Request: Encode<()>,
Q::Response: Encode<()>,
{
fn encode<W: encode::Write>(
&self,
e: &mut Encoder<W>,
_ctx: &mut (),
) -> Result<(), encode::Error<W::Error>> {
match self {
Message::Acquire(Some(point)) => {
e.array(2)?.u16(0)?;
e.encode(point)?;
Ok(())
}
Message::Acquire(None) => {
e.array(1)?.u16(8)?;
Ok(())
}
Message::Acquired => {
e.array(1)?.u16(1)?;
Ok(())
}
Message::Failure(failure) => {
e.array(2)?.u16(2)?;
e.encode(failure)?;
Ok(())
}
Message::Query(query) => {
e.array(2)?.u16(3)?;
e.array(1)?;
e.encode(query)?;
Ok(())
}
Message::Result(result) => {
e.array(2)?.u16(4)?;
e.array(1)?;
e.encode(result)?;
Ok(())
}
Message::ReAcquire(Some(point)) => {
e.array(2)?.u16(6)?;
e.encode(point)?;
Ok(())
}
Message::ReAcquire(None) => {
e.array(1)?.u16(9)?;
Ok(())
}
Message::Release => {
e.array(1)?.u16(5)?;
Ok(())
}
Message::Done => {
e.array(1)?.u16(7)?;
Ok(())
}
}
}
}
impl<'b, Q> Decode<'b, ()> for Message<Q>
where
Q: Query,
Q::Request: Decode<'b, ()>,
Q::Response: Decode<'b, ()>,
{
fn decode(
d: &mut pallas_codec::minicbor::Decoder<'b>,
_ctx: &mut (),
) -> Result<Self, pallas_codec::minicbor::decode::Error> {
d.array()?;
let label = d.u16()?;
match label {
0 => {
let point = d.decode()?;
Ok(Message::Acquire(Some(point)))
}
8 => Ok(Message::Acquire(None)),
1 => Ok(Message::Acquired),
2 => {
let failure = d.decode()?;
Ok(Message::Failure(failure))
}
3 => {
let query = d.decode()?;
Ok(Message::Query(query))
}
4 => {
let response = d.decode()?;
Ok(Message::Result(response))
}
5 => Ok(Message::Release),
6 => {
let point = d.decode()?;
Ok(Message::ReAcquire(point))
}
9 => Ok(Message::ReAcquire(None)),
7 => Ok(Message::Done),
_ => Err(decode::Error::message(
"unknown variant for localstate message",
)),
}
}
}

View file

@ -0,0 +1,8 @@
mod client;
mod codec;
mod protocol;
pub mod queries;
pub use client::*;
pub use codec::*;
pub use protocol::*;

View file

@ -0,0 +1,35 @@
use std::fmt::Debug;
use crate::miniprotocols::Point;
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum State {
Idle,
Acquiring,
Acquired,
Querying,
Done,
}
#[derive(Debug)]
pub enum AcquireFailure {
PointTooOld,
PointNotOnChain,
}
pub trait Query: Debug {
type Request: Clone + Debug;
type Response: Clone + Debug;
}
#[derive(Debug)]
pub enum Message<Q: Query> {
Acquire(Option<Point>),
Failure(AcquireFailure),
Acquired,
Query(Q::Request),
Result(Q::Response),
ReAcquire(Option<Point>),
Release,
Done,
}

View file

@ -0,0 +1,78 @@
use pallas_codec::minicbor::{decode, encode, Decode, Decoder, Encode, Encoder};
use super::Query;
#[derive(Debug, Clone)]
pub struct BlockQuery {}
#[derive(Debug, Clone)]
pub enum RequestV10 {
BlockQuery(BlockQuery),
GetSystemStart,
GetChainBlockNo,
GetChainPoint,
}
impl Encode<()> for RequestV10 {
fn encode<W: encode::Write>(
&self,
e: &mut Encoder<W>,
_ctx: &mut (),
) -> Result<(), encode::Error<W::Error>> {
match self {
Self::BlockQuery(..) => {
todo!()
}
Self::GetSystemStart => {
e.u16(1)?;
Ok(())
}
Self::GetChainBlockNo => {
e.u16(2)?;
Ok(())
}
Self::GetChainPoint => {
e.u16(3)?;
Ok(())
}
}
}
}
impl<'b> Decode<'b, ()> for RequestV10 {
fn decode(_d: &mut Decoder<'b>, _ctx: &mut ()) -> Result<Self, decode::Error> {
todo!()
}
}
#[derive(Debug, Clone)]
pub struct GenericResponse(Vec<u8>);
impl Encode<()> for GenericResponse {
fn encode<W: encode::Write>(
&self,
_e: &mut Encoder<W>,
_ctx: &mut (),
) -> Result<(), encode::Error<W::Error>> {
todo!()
}
}
impl<'b> Decode<'b, ()> for GenericResponse {
fn decode(d: &mut Decoder<'b>, _ctx: &mut ()) -> Result<Self, decode::Error> {
let start = d.position();
d.skip()?;
let end = d.position();
let slice = &d.input()[start..end];
let vec = slice.to_vec();
Ok(GenericResponse(vec))
}
}
#[derive(Debug, Clone)]
pub struct QueryV10 {}
impl Query for QueryV10 {
type Request = RequestV10;
type Response = GenericResponse;
}

View file

@ -0,0 +1,10 @@
mod common;
pub mod blockfetch;
pub mod chainsync;
pub mod handshake;
pub mod localstate;
pub mod txmonitor;
pub mod txsubmission;
pub use common::*;

View file

@ -0,0 +1,205 @@
use std::fmt::Debug;
use thiserror::*;
use super::protocol::*;
use crate::plexer;
#[derive(Error, Debug)]
pub enum Error {
#[error("attempted to receive message while agency is ours")]
AgencyIsOurs,
#[error("attempted to send message while agency is theirs")]
AgencyIsTheirs,
#[error("inbound message is not valid for current state")]
InvalidInbound,
#[error("outbound message is not valid for current state")]
InvalidOutbound,
#[error("error while sending or receiving data through the channel")]
Plexer(plexer::Error),
}
pub struct Client(State, plexer::ChannelBuffer);
impl Client {
pub fn new(channel: plexer::AgentChannel) -> Self {
Self(State::Idle, plexer::ChannelBuffer::new(channel))
}
pub fn state(&self) -> &State {
&self.0
}
pub fn is_done(&self) -> bool {
self.0 == State::Done
}
fn has_agency(&self) -> bool {
match &self.0 {
State::Idle => true,
State::Acquiring => false,
State::Acquired => true,
State::Busy => false,
State::Done => false,
}
}
fn assert_agency_is_ours(&self) -> Result<(), Error> {
if !self.has_agency() {
Err(Error::AgencyIsTheirs)
} else {
Ok(())
}
}
fn assert_agency_is_theirs(&self) -> Result<(), Error> {
if self.has_agency() {
Err(Error::AgencyIsOurs)
} else {
Ok(())
}
}
fn assert_outbound_state(&self, msg: &Message) -> Result<(), Error> {
match (&self.0, msg) {
(State::Idle, Message::Acquire) => Ok(()),
(State::Idle, Message::Done) => Ok(()),
(State::Acquired, Message::Acquire) => Ok(()),
(State::Acquired, Message::RequestHasTx(..)) => Ok(()),
(State::Acquired, Message::RequestNextTx) => Ok(()),
(State::Acquired, Message::RequestSizeAndCapacity) => Ok(()),
_ => Err(Error::InvalidOutbound),
}
}
fn assert_inbound_state(&self, msg: &Message) -> Result<(), Error> {
match (&self.0, msg) {
(State::Acquiring, Message::Acquired(..)) => Ok(()),
(State::Busy, Message::ResponseHasTx(..)) => Ok(()),
(State::Busy, Message::ResponseNextTx(..)) => Ok(()),
(State::Busy, Message::ResponseSizeAndCapacity(..)) => Ok(()),
_ => Err(Error::InvalidInbound),
}
}
pub async fn send_message(&mut self, msg: &Message) -> Result<(), Error> {
self.assert_agency_is_ours()?;
self.assert_outbound_state(msg)?;
self.1.send_msg_chunks(msg).await.map_err(Error::Plexer)?;
Ok(())
}
pub async fn recv_message(&mut self) -> Result<Message, Error> {
self.assert_agency_is_theirs()?;
let msg = self.1.recv_full_msg().await.map_err(Error::Plexer)?;
self.assert_inbound_state(&msg)?;
Ok(msg)
}
async fn send_acquire(&mut self) -> Result<(), Error> {
let msg = Message::Acquire;
self.send_message(&msg).await?;
self.0 = State::Acquiring;
Ok(())
}
async fn recv_while_acquiring(&mut self) -> Result<Slot, Error> {
match self.recv_message().await? {
Message::Acquired(slot) => {
self.0 = State::Acquired;
Ok(slot)
}
_ => Err(Error::InvalidInbound),
}
}
pub async fn acquire(&mut self) -> Result<Slot, Error> {
self.send_acquire().await?;
self.recv_while_acquiring().await
}
async fn send_request_has_tx(&mut self, id: TxId) -> Result<(), Error> {
let msg = Message::RequestHasTx(id);
self.send_message(&msg).await?;
self.0 = State::Busy;
Ok(())
}
async fn recv_while_requesting_has_tx(&mut self) -> Result<bool, Error> {
match self.recv_message().await? {
Message::ResponseHasTx(x) => {
self.0 = State::Acquired;
Ok(x)
}
_ => Err(Error::InvalidInbound),
}
}
pub async fn query_has_tx(&mut self, id: TxId) -> Result<bool, Error> {
self.send_request_has_tx(id).await?;
self.recv_while_requesting_has_tx().await
}
async fn send_request_next_tx(&mut self) -> Result<(), Error> {
let msg = Message::RequestNextTx;
self.send_message(&msg).await?;
self.0 = State::Busy;
Ok(())
}
async fn recv_while_requesting_next_tx(&mut self) -> Result<Option<Tx>, Error> {
match self.recv_message().await? {
Message::ResponseNextTx(x) => {
self.0 = State::Acquired;
Ok(x)
}
_ => Err(Error::InvalidInbound),
}
}
pub async fn query_next_tx(&mut self) -> Result<Option<Tx>, Error> {
self.send_request_next_tx().await?;
self.recv_while_requesting_next_tx().await
}
async fn send_request_size_and_capacity(&mut self) -> Result<(), Error> {
let msg = Message::RequestSizeAndCapacity;
self.send_message(&msg).await?;
self.0 = State::Busy;
Ok(())
}
async fn recv_while_requesting_size_and_capacity(
&mut self,
) -> Result<MempoolSizeAndCapacity, Error> {
match self.recv_message().await? {
Message::ResponseSizeAndCapacity(x) => {
self.0 = State::Acquired;
Ok(x)
}
_ => Err(Error::InvalidInbound),
}
}
pub async fn query_size_and_capacity(&mut self) -> Result<MempoolSizeAndCapacity, Error> {
self.send_request_size_and_capacity().await?;
self.recv_while_requesting_size_and_capacity().await
}
pub async fn release(&mut self) -> Result<(), Error> {
let msg = Message::Release;
self.send_message(&msg).await?;
self.0 = State::Idle;
Ok(())
}
}

View file

@ -0,0 +1,114 @@
use super::protocol::*;
use pallas_codec::minicbor::{decode, encode, Decode, Encode, Encoder};
impl Encode<()> for Message {
fn encode<W: encode::Write>(
&self,
e: &mut Encoder<W>,
_ctx: &mut (),
) -> Result<(), encode::Error<W::Error>> {
match self {
Message::Done => {
e.array(1)?.u16(0)?;
}
Message::Acquire => {
e.array(1)?.u16(1)?;
}
Message::Acquired(slot) => {
e.array(2)?.u16(2)?;
e.encode(slot)?;
}
Message::Release => {
e.array(1)?.u16(3)?;
}
// TODO: confirm if this is valid, I'm just assuming that label 4 is AwaitAcquire, can't
// find the specs
Message::AwaitAcquire => {
e.array(1)?.u16(4)?;
}
Message::RequestNextTx => {
e.array(1)?.u16(5)?;
}
Message::ResponseNextTx(None) => {
e.array(1)?.u16(6)?;
}
Message::ResponseNextTx(Some(tx)) => {
e.array(2)?.u16(6)?;
e.encode(tx)?;
}
Message::RequestHasTx(tx) => {
e.array(2)?.u16(7)?;
e.encode(tx)?;
}
Message::ResponseHasTx(tx) => {
e.array(2)?.u16(8)?;
e.encode(tx)?;
}
Message::RequestSizeAndCapacity => {
e.array(1)?.u16(9)?;
}
Message::ResponseSizeAndCapacity(sz) => {
e.array(2)?.u16(10)?;
e.array(3)?;
e.encode(sz.capacity_in_bytes)?;
e.encode(sz.size_in_bytes)?;
e.encode(sz.number_of_txs)?;
}
}
Ok(())
}
}
impl<'b> Decode<'b, ()> for Message {
fn decode(
d: &mut pallas_codec::minicbor::Decoder<'b>,
_ctx: &mut (),
) -> Result<Self, decode::Error> {
d.array()?;
let label = d.u16()?;
match label {
0 => Ok(Message::Done),
1 => Ok(Message::Acquire),
2 => {
let slot = d.decode()?;
Ok(Message::Acquired(slot))
}
3 => Ok(Message::Release),
// TODO: confirm if this is valid, I'm just assuming that label 4 is AwaitAcquire, can't
// find the specs
4 => Ok(Message::AwaitAcquire),
5 => Ok(Message::RequestNextTx),
6 => match d.array()? {
Some(_) => {
let cbor: pallas_codec::utils::CborWrap<Tx> = d.decode()?;
Ok(Message::ResponseNextTx(Some(cbor.unwrap())))
}
None => Ok(Message::ResponseNextTx(None)),
},
7 => {
let id = d.decode()?;
Ok(Message::RequestHasTx(id))
}
8 => {
let has = d.decode()?;
Ok(Message::ResponseHasTx(has))
}
9 => Ok(Message::RequestSizeAndCapacity),
10 => {
d.array()?;
let capacity_in_bytes = d.decode()?;
let size_in_bytes = d.decode()?;
let number_of_txs = d.decode()?;
Ok(Message::ResponseSizeAndCapacity(MempoolSizeAndCapacity {
capacity_in_bytes,
size_in_bytes,
number_of_txs,
}))
}
_ => Err(decode::Error::message("can't decode Message")),
}
}
}

View file

@ -0,0 +1,7 @@
mod client;
mod codec;
mod protocol;
pub use client::*;
pub use codec::*;
pub use protocol::*;

View file

@ -0,0 +1,34 @@
pub type Slot = u64;
pub type TxId = String;
pub type Tx = Vec<u8>;
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum State {
Idle,
Acquiring,
Acquired,
Busy,
Done,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct MempoolSizeAndCapacity {
pub capacity_in_bytes: u32,
pub size_in_bytes: u32,
pub number_of_txs: u32,
}
#[derive(Debug, Clone)]
pub enum Message {
Acquire,
AwaitAcquire,
Acquired(Slot),
RequestHasTx(TxId),
RequestNextTx,
RequestSizeAndCapacity,
ResponseHasTx(bool),
ResponseNextTx(Option<Tx>),
ResponseSizeAndCapacity(MempoolSizeAndCapacity),
Release,
Done,
}

View file

@ -0,0 +1,193 @@
# TxSubmission
The TxSubmission miniprotocol allows a client with transactions to announce to connect to a server interested in those transactions. This is used, for example, to propogate transactions in each nodes mempool from peer to peer, until they land on a node that is ready to mint a block. Without this protocol, transactions wouldn't get minted until the node they were submitted to produced a block, which may be few and far between.
Initially, the design of this protocol seems backwards: you might expect the server to have the transactions, and the client to download them. However, this design allows robust, two way congestion control: a node with transactions is not required to initiate a connection to anyone, and a node interested in transactions can pick and choose what they download. The name of the miniprotocol, "TxSubmission" rather than "TxDownload", further reinforces this.
Like other miniprotocols, the client and server maintain a state machine between them; each message sent or received transitions the protocol into a new state, where either the client or server is expected to do some work.
The client is expected to initiate a connection and initialize the protocol into the Idle state. While in the Idle state, the client is waiting for the server to request either transaction IDs or transactions; Once the server makes a request, the client responds with the appropriate data.
These state transitions is captured in the mermaid diagram below:
```mermaid
graph TB
A[ ] -->|start| StInit
StInit[State::Init] -->|Message::Init| StIdle
StIdle[::Idle] -->|::RequestTxIds| StTxIdsBlocking
StIdle -->|::RequestTxIds| StTxIdsNonBlocking
StIdle -->|::RequestTxs| StTxs
StTxIdsBlocking[::TxIdsBlocking] -->|::ReplyTxIds| StIdle
StTxIdsBlocking -->|::Done| StDone[::Done]
StTxIdsNonBlocking[::TxIdsNonBlocking] -->|::ReplyTxIds| StIdle
StTxs[::Txs] -->|::ReplyTxs| StIdle
```
This module provides two actors that implement either side of this role: Client and Server, each of which are detailed below.
## Client
You can instantiate a client like so
```rust
let mut client = txsubmission::Client::new(channel4);
```
As the initiator, you then need to send a message to initialize the protocol
```rust
client.send_init()?;
```
The server will then make a series of queries for available transaction Ids and transaction bodies, which you're responsible for handling:
```rust
loop {
match client.next_request()? {
Request::TxIds(acknowledged, next) => {},
Request::TxIdsNonBlocking(acknowledged, next) => {}
Request::Txs(ids) => {}
}
}
```
When the server requests some number of transaction Ids, as the client you should reply with up to that many ids, and the size of the transaction. In the non-blocking variant, you can return what you have immediately, while in the blocking variant you should wait until you can answer with the number requested by the server. We provide some sample code below:
```rust
Request::TxIdsNonBlocking(acknowledged, next) => {
// NOTE: incomplete implementation, see below
client.reply_tx_ids(
// NOTE: we assume some kind of mempool implementation, which maintains a per-connection FIFO
// this is not provided as part of pallas-miniprotocols
mempool.iter()
.take(next)
.map(|tx| TxIdAndSize(tx.id(), tx.len()))
.collect()
)?;
}
```
If the `acknowledged` field is nonzero, the server is letting you know that it either received or doesn't care about some of the previous transactions you sent, so you can advance the queue. Thus, the above code should be extended to
```rust
Request::TxIdsNonBlocking(acknowledged, next) => {
// discards the first N transactions, advancing the queue for this client
mempool.discard(acknowledged);
client.reply_tx_ids(
mempool.iter()
.take(next)
.map(|tx| TxIdAndSize(tx.id(), tx.len()))
.collect()
)?;
}
```
When the server requests to download some transaction bodies, you can respond with the details of those transactions:
```rust
Request::Txs(ids) => {
let txs: Vec<TxBody> = mempool.find(ids);
client.reply_txs(txs)?;
}
```
Finally, if you decide to terminate the miniprotocol while waiting for enough transactions to respond to a blocking `Request::TxIds` request, you can send a Done message to gracefully terminate the protocol.
```rust
client.send_done()?;
```
All together, this becomes:
```rust
let mut client = txsubmission::Client::new(channel4);
client.send_init()?;
loop {
match client.next_request()? {
Request::TxIds(acknowledged, next) => {
mempool.discard(acknowledged)?;
if !mempool.wait_for_at_least(next)? {
client.send_done()?;
break;
}
client.reply_tx_ids(
mempool.iter()
.take(next)
.map(|tx| TxIdAndSize(tx.id(), tx.len()))
.collect()
)?;
},
Request::TxIdsNonBlocking(acknowledged, next) => {
mempool.discard(acknowledged)?;
client.reply_tx_ids(
mempool.iter()
.take(next)
.map(|tx| TxIdAndSize(tx.id(), tx.len()))
)?;
}
Request::Txs(ids) => {
let txs = mempool.find(ids);
client.reply_txs(txs)?;
}
}
}
```
## Server
Conversely, you can instantiate a server ready to learn about new transactions like so
```rust
let mut server = txsubmission::Server::new(channel4);
```
Since this is a client initiated protocol, you first need to wait to receive an initialization message:
```rust
server.wait_for_init()?;
```
Then, you can begin querying for some number of transaction Ids
```rust
server.acknowledge_and_request_ids(true, 0, 16)?;
```
And wait for a response
```rust
match server.receive_next_reply()? {
Reply::TxIds(ids_and_sizes) => { }
Reply::Txs(txs) => { }
Reply::Done => { }
}
```
You can download those transactions with
```rust
server.request_txs(ids.iter().map(|tx_and_size| tx_and_size.0.clone()).collect());
```
After you receive some transaction Ids, if you request more you should acknowledge the ones you received
```rust
server.acknowledge_and_request_ids(true, 16, 16)?
```
All-together, this event loop could look something like this:
```rust
let mut server = txsubmission::Server::new(channel4);
server.wait_for_init()?;
server.acknowledge_and_request_ids(true, 0, 16)?;
loop {
match server.receive_next_reply()? {
Reply::TxIds(ids_and_sizes) => {
server.request_txs(ids_and_sizes.iter().map(|tx| tx.0.clone()).collect())?;
},
Reply::Txs(txs) => {
tx_channel.send(txs);
server.acknowledge_and_request_ids(true, txs.len() as usize, 16)?;
},
Reply::Done => {
break;
}
}
}
```

View file

@ -0,0 +1,158 @@
use std::marker::PhantomData;
use crate::plexer;
use pallas_codec::Fragment;
use super::{
protocol::{Error, Message, State, TxIdAndSize},
EraTxBody, EraTxId,
};
pub enum Request<TxId> {
TxIds(u16, u16),
TxIdsNonBlocking(u16, u16),
Txs(Vec<TxId>),
}
/// A generic Ouroboros client for submitting a generic notion of "transactions"
/// to another server
pub struct GenericClient<TxId, TxBody>(
State,
plexer::ChannelBuffer,
PhantomData<TxId>,
PhantomData<TxBody>,
)
where
Message<TxId, TxBody>: Fragment;
/// A cardano specific instantiation of the ouroboros protocol
pub type Client = GenericClient<EraTxId, EraTxBody>;
impl<TxId, TxBody> GenericClient<TxId, TxBody>
where
Message<TxId, TxBody>: Fragment,
{
pub fn new(channel: plexer::AgentChannel) -> Self {
Self(
State::Init,
plexer::ChannelBuffer::new(channel),
PhantomData {},
PhantomData {},
)
}
pub fn state(&self) -> &State {
&self.0
}
pub fn is_done(&self) -> bool {
self.0 == State::Done
}
fn has_agency(&self) -> bool {
!matches!(self.state(), State::Idle)
}
fn assert_agency_is_ours(&self) -> Result<(), Error> {
if !self.has_agency() {
Err(Error::AgencyIsTheirs)
} else {
Ok(())
}
}
fn assert_agency_is_theirs(&self) -> Result<(), Error> {
if self.has_agency() {
Err(Error::AgencyIsOurs)
} else {
Ok(())
}
}
/// As a client in a specific state, am I allowed to send this message?
fn assert_outbound_state(&self, msg: &Message<TxId, TxBody>) -> Result<(), Error> {
match (&self.0, msg) {
(State::Init, Message::Init) => Ok(()),
(State::TxIdsBlocking, Message::ReplyTxIds(..)) => Ok(()),
(State::TxIdsBlocking, Message::Done) => Ok(()),
(State::TxIdsNonBlocking, Message::ReplyTxIds(..)) => Ok(()),
(State::Txs, Message::ReplyTxs(..)) => Ok(()),
_ => Err(Error::InvalidOutbound),
}
}
/// As a client in a specific state, am I allowed to receive this message?
fn assert_inbound_state(&self, msg: &Message<TxId, TxBody>) -> Result<(), Error> {
match (&self.0, msg) {
(State::Idle, Message::RequestTxIds(..)) => Ok(()),
(State::Idle, Message::RequestTxs(..)) => Ok(()),
_ => Err(Error::InvalidInbound),
}
}
pub async fn send_message(&mut self, msg: &Message<TxId, TxBody>) -> Result<(), Error> {
self.assert_agency_is_ours()?;
self.assert_outbound_state(msg)?;
self.1.send_msg_chunks(msg).await.map_err(Error::Plexer)?;
Ok(())
}
pub async fn recv_message(&mut self) -> Result<Message<TxId, TxBody>, Error> {
self.assert_agency_is_theirs()?;
let msg = self.1.recv_full_msg().await.map_err(Error::Plexer)?;
self.assert_inbound_state(&msg)?;
Ok(msg)
}
pub async fn send_init(&mut self) -> Result<(), Error> {
let msg = Message::Init;
self.send_message(&msg).await?;
self.0 = State::Idle;
Ok(())
}
pub async fn reply_tx_ids(&mut self, ids: Vec<TxIdAndSize<TxId>>) -> Result<(), Error> {
let msg = Message::ReplyTxIds(ids);
self.send_message(&msg).await?;
self.0 = State::Idle;
Ok(())
}
pub async fn reply_txs(&mut self, txs: Vec<TxBody>) -> Result<(), Error> {
let msg = Message::ReplyTxs(txs);
self.send_message(&msg).await?;
self.0 = State::Idle;
Ok(())
}
pub async fn next_request(&mut self) -> Result<Request<TxId>, Error> {
match self.recv_message().await? {
Message::RequestTxIds(blocking, ack, req) => {
self.0 = State::TxIdsBlocking;
match blocking {
true => Ok(Request::TxIds(ack, req)),
false => Ok(Request::TxIdsNonBlocking(ack, req)),
}
}
Message::RequestTxs(x) => {
self.0 = State::Txs;
Ok(Request::Txs(x))
}
_ => Err(Error::InvalidInbound),
}
}
pub async fn send_done(&mut self) -> Result<(), Error> {
let msg = Message::Done;
self.send_message(&msg).await?;
self.0 = State::Done;
Ok(())
}
}

View file

@ -0,0 +1,169 @@
use pallas_codec::minicbor::{data::Tag, decode, encode, Decode, Decoder, Encode, Encoder};
use super::{
protocol::{Message, TxIdAndSize},
EraTxBody, EraTxId,
};
impl<TxId: Encode<()>> Encode<()> for TxIdAndSize<TxId> {
fn encode<W: encode::Write>(
&self,
e: &mut Encoder<W>,
_ctx: &mut (),
) -> Result<(), encode::Error<W::Error>> {
e.array(2)?;
e.encode(&self.0)?;
e.u32(self.1)?;
Ok(())
}
}
impl<'b, TxId: Decode<'b, ()>> Decode<'b, ()> for TxIdAndSize<TxId> {
fn decode(d: &mut Decoder<'b>, _ctx: &mut ()) -> Result<Self, decode::Error> {
d.array()?;
let tx_id = d.decode()?;
let size = d.u32()?;
Ok(Self(tx_id, size))
}
}
impl<TxId: Encode<()>, TxBody: Encode<()>> Encode<()> for Message<TxId, TxBody> {
fn encode<W: encode::Write>(
&self,
e: &mut Encoder<W>,
_ctx: &mut (),
) -> Result<(), encode::Error<W::Error>> {
match self {
Message::Init => {
e.array(1)?.u16(6)?;
Ok(())
}
Message::RequestTxIds(blocking, ack, req) => {
e.array(4)?.u16(0)?;
e.bool(*blocking)?;
e.u16(*ack)?;
e.u16(*req)?;
Ok(())
}
Message::ReplyTxIds(ids) => {
e.array(2)?.u16(1)?;
e.begin_array()?;
for id in ids {
e.encode(id)?;
}
e.end()?;
Ok(())
}
Message::RequestTxs(ids) => {
e.array(2)?.u16(2)?;
e.begin_array()?;
for id in ids {
e.encode(id)?;
}
e.end()?;
Ok(())
}
Message::ReplyTxs(txs) => {
e.array(2)?.u16(3)?;
e.begin_array()?;
for tx in txs {
e.encode(tx)?;
}
e.end()?;
Ok(())
}
Message::Done => {
e.array(1)?.u16(4)?;
Ok(())
}
}
}
}
impl<'b> Decode<'b, ()> for EraTxBody {
fn decode(d: &mut Decoder<'b>, _ctx: &mut ()) -> Result<Self, decode::Error> {
d.array()?;
let era = d.u16()?;
let tag = d.tag()?;
if tag != Tag::Cbor {
return Err(decode::Error::message("Expected encoded CBOR data item"));
}
Ok(EraTxBody(era, d.bytes()?.to_vec()))
}
}
impl Encode<()> for EraTxBody {
fn encode<W: encode::Write>(
&self,
e: &mut Encoder<W>,
_ctx: &mut (),
) -> Result<(), encode::Error<W::Error>> {
e.array(2)?;
e.u16(self.0)?;
e.tag(Tag::Cbor)?;
e.bytes(&self.1)?;
Ok(())
}
}
impl<'b, TxId: Decode<'b, ()>, TxBody: Decode<'b, ()>> Decode<'b, ()> for Message<TxId, TxBody> {
fn decode(d: &mut Decoder<'b>, _ctx: &mut ()) -> Result<Self, decode::Error> {
d.array()?;
let label = d.u16()?;
match label {
0 => {
let blocking = d.bool()?;
let ack = d.u16()?;
let req = d.u16()?;
Ok(Message::RequestTxIds(blocking, ack, req))
}
1 => {
let items = d.decode()?;
Ok(Message::ReplyTxIds(items))
}
2 => {
let ids = d.decode()?;
Ok(Message::RequestTxs(ids))
}
3 => Ok(Message::ReplyTxs(
d.array_iter()?.collect::<Result<_, _>>()?,
)),
4 => Ok(Message::Done),
6 => Ok(Message::Init),
_ => Err(decode::Error::message(
"unknown variant for txsubmission message",
)),
}
}
}
impl Encode<()> for EraTxId {
fn encode<W: encode::Write>(
&self,
e: &mut Encoder<W>,
_ctx: &mut (),
) -> Result<(), encode::Error<W::Error>> {
e.array(2)?;
e.encode(self.0)?;
e.bytes(&self.1)?;
Ok(())
}
}
impl<'b> Decode<'b, ()> for EraTxId {
fn decode(d: &mut Decoder<'b>, _ctx: &mut ()) -> Result<Self, decode::Error> {
d.array()?;
let era = d.u16()?;
let tx_id = d.bytes()?;
Ok(Self(era, tx_id.to_vec()))
}
}

View file

@ -0,0 +1,9 @@
mod client;
mod codec;
mod protocol;
mod server;
pub use client::*;
pub use codec::*;
pub use protocol::*;
pub use server::*;

View file

@ -0,0 +1,61 @@
use thiserror::Error;
use crate::plexer;
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum State {
Init,
Idle,
TxIdsNonBlocking,
TxIdsBlocking,
Txs,
Done,
}
pub type Blocking = bool;
pub type TxCount = u16;
pub type TxSizeInBytes = u32;
// The bytes of a txId, tagged with an era number
#[derive(Debug, Clone)]
pub struct EraTxId(pub u16, pub Vec<u8>);
// The bytes of a transaction, with an era number and some raw CBOR
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct EraTxBody(pub u16, pub Vec<u8>);
#[derive(Debug)]
pub struct TxIdAndSize<TxID>(pub TxID, pub TxSizeInBytes);
#[derive(Error, Debug)]
pub enum Error {
#[error("attempted to receive message while agency is ours")]
AgencyIsOurs,
#[error("attempted to send message while agency is theirs")]
AgencyIsTheirs,
#[error("inbound message is not valid for current state")]
InvalidInbound,
#[error("outbound message is not valid for current state")]
InvalidOutbound,
#[error("protocol is already initialized, no need to wait for init message")]
AlreadyInitialized,
#[error("error while sending or receiving data through the channel")]
Plexer(plexer::Error),
}
#[derive(Debug)]
pub enum Message<TxId, TxBody> {
Init,
RequestTxIds(Blocking, TxCount, TxCount),
ReplyTxIds(Vec<TxIdAndSize<TxId>>),
RequestTxs(Vec<TxId>),
ReplyTxs(Vec<TxBody>),
Done,
}

View file

@ -0,0 +1,163 @@
use std::marker::PhantomData;
use pallas_codec::Fragment;
use super::{
protocol::{Blocking, Error, Message, State, TxCount, TxIdAndSize},
EraTxBody, EraTxId,
};
use crate::plexer;
pub enum Reply<TxId, TxBody> {
TxIds(Vec<TxIdAndSize<TxId>>),
Txs(Vec<TxBody>),
Done,
}
/// A generic implementation of an ouroboros server protocol ready to request
/// and receive transactions from a client
pub struct GenericServer<TxId, TxBody>(
State,
plexer::ChannelBuffer,
PhantomData<TxId>,
PhantomData<TxBody>,
)
where
Message<TxId, TxBody>: Fragment;
/// A Cardano specific server for the ouroboros TxSubmission protocol
pub type Server = GenericServer<EraTxId, EraTxBody>;
impl<TxId, TxBody> GenericServer<TxId, TxBody>
where
Message<TxId, TxBody>: Fragment,
{
pub fn new(channel: plexer::AgentChannel) -> Self {
Self(
State::Init,
plexer::ChannelBuffer::new(channel),
PhantomData {},
PhantomData {},
)
}
pub fn state(&self) -> &State {
&self.0
}
pub fn is_done(&self) -> bool {
self.0 == State::Done
}
fn has_agency(&self) -> bool {
matches!(self.state(), State::Idle)
}
fn assert_agency_is_ours(&self) -> Result<(), Error> {
if !self.has_agency() {
Err(Error::AgencyIsTheirs)
} else {
Ok(())
}
}
fn assert_agency_is_theirs(&self) -> Result<(), Error> {
if self.has_agency() {
Err(Error::AgencyIsOurs)
} else {
Ok(())
}
}
/// As a server in a specific state, am I allowed to send this message?
fn assert_outbound_state(&self, msg: &Message<TxId, TxBody>) -> Result<(), Error> {
match (&self.0, msg) {
(State::Idle, Message::RequestTxIds(..)) => Ok(()),
(State::Idle, Message::RequestTxs(..)) => Ok(()),
_ => Err(Error::InvalidInbound),
}
}
/// As a server in a specific state, am I allowed to receive this message?
fn assert_inbound_state(&self, msg: &Message<TxId, TxBody>) -> Result<(), Error> {
match (&self.0, msg) {
(State::Init, Message::Init) => Ok(()),
(State::TxIdsBlocking, Message::ReplyTxIds(..)) => Ok(()),
(State::TxIdsBlocking, Message::Done) => Ok(()),
(State::TxIdsNonBlocking, Message::ReplyTxIds(..)) => Ok(()),
(State::Txs, Message::ReplyTxs(..)) => Ok(()),
_ => Err(Error::InvalidOutbound),
}
}
pub async fn send_message(&mut self, msg: &Message<TxId, TxBody>) -> Result<(), Error> {
self.assert_agency_is_ours()?;
self.assert_outbound_state(msg)?;
self.1.send_msg_chunks(msg).await.map_err(Error::Plexer)?;
Ok(())
}
pub async fn recv_message(&mut self) -> Result<Message<TxId, TxBody>, Error> {
self.assert_agency_is_theirs()?;
let msg = self.1.recv_full_msg().await.map_err(Error::Plexer)?;
self.assert_inbound_state(&msg)?;
Ok(msg)
}
pub async fn wait_for_init(&mut self) -> Result<(), Error> {
if self.0 != State::Init {
return Err(Error::AlreadyInitialized);
}
// recv_message calls assert_inbound_state, which ensures we get an init message
self.recv_message().await?;
self.0 = State::Idle;
Ok(())
}
pub async fn acknowledge_and_request_tx_ids(
&mut self,
blocking: Blocking,
acknowledge: TxCount,
count: TxCount,
) -> Result<(), Error> {
let msg = Message::RequestTxIds(blocking, acknowledge, count);
self.send_message(&msg).await?;
match blocking {
true => self.0 = State::TxIdsBlocking,
false => self.0 = State::TxIdsNonBlocking,
}
Ok(())
}
pub async fn request_txs(&mut self, ids: Vec<TxId>) -> Result<(), Error> {
let msg = Message::RequestTxs(ids);
self.send_message(&msg).await?;
self.0 = State::Txs;
Ok(())
}
pub async fn receive_next_reply(&mut self) -> Result<Reply<TxId, TxBody>, Error> {
match self.recv_message().await? {
Message::ReplyTxIds(ids_and_sizes) => {
self.0 = State::Idle;
Ok(Reply::TxIds(ids_and_sizes))
}
Message::ReplyTxs(bodies) => {
self.0 = State::Idle;
Ok(Reply::Txs(bodies))
}
Message::Done => {
self.0 = State::Done;
Ok(Reply::Done)
}
_ => Err(Error::InvalidInbound),
}
}
}

View file

@ -0,0 +1,317 @@
use pallas_codec::{minicbor, Fragment};
use thiserror::Error;
use tokio::sync::mpsc::error::SendError;
use tokio::{select, time::Instant};
use tracing::{debug, error, trace};
use crate::bearer::{Bearer, Payload, Protocol, SegmentBuffer};
#[derive(Error, Debug)]
pub enum Error {
#[error("failure to encode channel message")]
Decoding(String),
#[error("failure to decode channel message")]
Encoding(String),
#[error("agent failed to enqueue chunk for protocol {0}")]
AgentEnqueue(Protocol, Payload),
#[error("agent failed to dequeue chunk")]
AgentDequeue,
#[error("plexer failed to dumux chunk for protocol {0}")]
PlexerDemux(Protocol, Payload),
#[error("plexer failed to mux chunk")]
PlexerMux,
#[error("bearer IO error")]
Bearer(tokio::io::Error),
}
pub struct AgentChannel {
enqueue_protocol: crate::bearer::Protocol,
dequeue_protocol: crate::bearer::Protocol,
to_plexer: tokio::sync::mpsc::Sender<(Protocol, Payload)>,
from_plexer: tokio::sync::broadcast::Receiver<(Protocol, Payload)>,
}
impl AgentChannel {
fn for_client(protocol: crate::bearer::Protocol, ingress: &Ingress, egress: &Egress) -> Self {
Self {
enqueue_protocol: protocol,
dequeue_protocol: protocol ^ 0x8000,
to_plexer: ingress.0.clone(),
from_plexer: egress.0.subscribe(),
}
}
fn for_server(protocol: crate::bearer::Protocol, ingress: &Ingress, egress: &Egress) -> Self {
Self {
enqueue_protocol: protocol ^ 0x8000,
dequeue_protocol: protocol,
to_plexer: ingress.0.clone(),
from_plexer: egress.0.subscribe(),
}
}
pub async fn enqueue_chunk(&mut self, chunk: Payload) -> Result<(), Error> {
self.to_plexer
.send((self.enqueue_protocol, chunk))
.await
.map_err(|SendError((protocol, payload))| Error::AgentEnqueue(protocol, payload))
}
pub async fn dequeue_chunk(&mut self) -> Result<Payload, Error> {
loop {
let (protocol, payload) = self
.from_plexer
.recv()
.await
.map_err(|_| Error::AgentDequeue)?;
if protocol == self.dequeue_protocol {
trace!(protocol, "message for our protocol");
break Ok(payload);
}
}
}
}
type Ingress = (
tokio::sync::mpsc::Sender<(Protocol, Payload)>,
tokio::sync::mpsc::Receiver<(Protocol, Payload)>,
);
type Egress = (
tokio::sync::broadcast::Sender<(Protocol, Payload)>,
tokio::sync::broadcast::Receiver<(Protocol, Payload)>,
);
pub struct Plexer {
clock: Instant,
bearer: SegmentBuffer,
ingress: Ingress,
egress: Egress,
}
impl Plexer {
pub fn new(bearer: Bearer) -> Self {
Self {
clock: Instant::now(),
bearer: SegmentBuffer::new(bearer),
ingress: tokio::sync::mpsc::channel(100), // TODO: define buffer
egress: tokio::sync::broadcast::channel(100),
}
}
async fn mux(&mut self, msg: (Protocol, Payload)) -> tokio::io::Result<()> {
self.bearer
.write_segment(msg.0, &self.clock, &msg.1)
.await?;
if tracing::event_enabled!(tracing::Level::TRACE) {
trace!(
protocol = msg.0,
data = hex::encode(&msg.1),
"write to bearer"
);
}
Ok(())
}
async fn demux(&mut self, protocol: Protocol, payload: Payload) -> tokio::io::Result<()> {
if tracing::event_enabled!(tracing::Level::TRACE) {
trace!(protocol, data = hex::encode(&payload), "read from bearer");
}
self.egress.0.send((protocol, payload)).unwrap();
Ok(())
}
pub fn subscribe_client(&mut self, protocol: Protocol) -> AgentChannel {
AgentChannel::for_client(protocol, &self.ingress, &self.egress)
}
pub fn subscribe_server(&mut self, protocol: Protocol) -> AgentChannel {
AgentChannel::for_server(protocol, &self.ingress, &self.egress)
}
pub async fn run(&mut self) -> tokio::io::Result<()> {
loop {
trace!("selecting");
select! {
Ok(x) = self.bearer.read_segment() => {
trace!("demux selected");
self.demux(x.0, x.1).await?
},
Some(x) = self.ingress.1.recv() => {
trace!("mux selected");
self.mux(x).await?
},
_ = tokio::time::sleep(tokio::time::Duration::from_secs(5)) => {
trace!("idle plexer");
}
else => {
error!("something else happened");
}
}
}
}
}
/// Protocol value that defines max segment length
pub const MAX_SEGMENT_PAYLOAD_LENGTH: usize = 65535;
fn try_decode_message<M>(buffer: &mut Vec<u8>) -> Result<Option<M>, Error>
where
M: Fragment,
{
let mut decoder = minicbor::Decoder::new(buffer);
let maybe_msg = decoder.decode();
match maybe_msg {
Ok(msg) => {
let pos = decoder.position();
buffer.drain(0..pos);
Ok(Some(msg))
}
Err(err) if err.is_end_of_input() => Ok(None),
Err(err) => {
error!(?err);
trace!("{}", hex::encode(buffer));
Err(Error::Decoding(err.to_string()))
}
}
}
/// A channel abstraction to hide the complexity of partial payloads
pub struct ChannelBuffer {
channel: AgentChannel,
temp: Vec<u8>,
}
impl ChannelBuffer {
pub fn new(channel: AgentChannel) -> Self {
Self {
channel,
temp: Vec::new(),
}
}
/// Enqueues a msg as a sequence payload chunks
pub async fn send_msg_chunks<M>(&mut self, msg: &M) -> Result<(), Error>
where
M: Fragment,
{
let mut payload = Vec::new();
minicbor::encode(msg, &mut payload).map_err(|err| Error::Encoding(err.to_string()))?;
let chunks = payload.chunks(MAX_SEGMENT_PAYLOAD_LENGTH);
for chunk in chunks {
self.channel.enqueue_chunk(Vec::from(chunk)).await?;
}
Ok(())
}
/// Reads from the channel until a complete message is found
pub async fn recv_full_msg<M>(&mut self) -> Result<M, Error>
where
M: Fragment,
{
trace!(len = self.temp.len(), "waiting for full message");
if !self.temp.is_empty() {
trace!("buffer has data from previous payload");
if let Some(msg) = try_decode_message::<M>(&mut self.temp)? {
debug!("decoding done");
return Ok(msg);
}
}
loop {
let chunk = self.channel.dequeue_chunk().await?;
self.temp.extend(chunk);
if let Some(msg) = try_decode_message::<M>(&mut self.temp)? {
debug!("decoding done");
return Ok(msg);
}
trace!("not enough data");
}
}
pub fn unwrap(self) -> AgentChannel {
self.channel
}
}
impl From<AgentChannel> for ChannelBuffer {
fn from(channel: AgentChannel) -> Self {
ChannelBuffer::new(channel)
}
}
#[cfg(test)]
mod tests {
use super::*;
use pallas_codec::minicbor;
#[tokio::test]
async fn multiple_messages_in_same_payload() {
let mut input = Vec::new();
let in_part1 = (1u8, 2u8, 3u8);
let in_part2 = (6u8, 5u8, 4u8);
minicbor::encode(in_part1, &mut input).unwrap();
minicbor::encode(in_part2, &mut input).unwrap();
let ingress = tokio::sync::mpsc::channel(100);
let egress = tokio::sync::broadcast::channel(100);
let channel = AgentChannel::for_client(0, &ingress, &egress);
egress.0.send((0 ^ 0x8000, input)).unwrap();
let mut buf = ChannelBuffer::new(channel);
let out_part1 = buf.recv_full_msg::<(u8, u8, u8)>().await.unwrap();
let out_part2 = buf.recv_full_msg::<(u8, u8, u8)>().await.unwrap();
assert_eq!(in_part1, out_part1);
assert_eq!(in_part2, out_part2);
}
#[tokio::test]
async fn fragmented_message_in_multiple_payloads() {
let mut input = Vec::new();
let msg = (11u8, 12u8, 13u8, 14u8, 15u8, 16u8, 17u8);
minicbor::encode(msg, &mut input).unwrap();
let ingress = tokio::sync::mpsc::channel(100);
let egress = tokio::sync::broadcast::channel(100);
let channel = AgentChannel::for_client(0, &ingress, &egress);
while !input.is_empty() {
let chunk = Vec::from(input.drain(0..2).as_slice());
egress.0.send((0 ^ 0x8000, chunk)).unwrap();
}
let mut buf = ChannelBuffer::new(channel);
let out_msg = buf
.recv_full_msg::<(u8, u8, u8, u8, u8, u8, u8)>()
.await
.unwrap();
assert_eq!(msg, out_msg);
}
}