diff --git a/Cargo.toml b/Cargo.toml index 2112f4d..3f93671 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,8 +3,7 @@ members = [ "pallas-codec", "pallas-addresses", - "pallas-multiplexer", - "pallas-miniprotocols", + "pallas-network", "pallas-crypto", "pallas-primitives", "pallas-traverse", diff --git a/examples/block-download/Cargo.toml b/examples/block-download/Cargo.toml index a313e3d..0b1c739 100644 --- a/examples/block-download/Cargo.toml +++ b/examples/block-download/Cargo.toml @@ -10,3 +10,6 @@ publish = false pallas = { path = "../../pallas" } net2 = "0.2.37" hex = "0.4.3" +tracing = "0.1.37" +tracing-subscriber = "0.3.16" +tokio = { version = "1.27.0", features = ["rt-multi-thread"] } diff --git a/examples/block-download/src/main.rs b/examples/block-download/src/main.rs index ac848f6..33cabd0 100644 --- a/examples/block-download/src/main.rs +++ b/examples/block-download/src/main.rs @@ -1,38 +1,27 @@ use pallas::network::{ - miniprotocols::{ - blockfetch, - handshake::{self, n2n::VersionTable}, - Point, MAINNET_MAGIC, PROTOCOL_N2N_BLOCK_FETCH, PROTOCOL_N2N_HANDSHAKE, - }, - multiplexer::{bearers::Bearer, StdPlexer}, + facades::PeerClient, + miniprotocols::{Point, MAINNET_MAGIC}, }; -fn main() { - env_logger::init(); +#[tokio::main] +async fn main() { + tracing::subscriber::set_global_default( + tracing_subscriber::FmtSubscriber::builder() + .with_max_level(tracing::Level::TRACE) + .finish(), + ) + .unwrap(); - let bearer = Bearer::connect_tcp("relays-new.cardano-mainnet.iohk.io:3001").unwrap(); - - let mut plexer = StdPlexer::new(bearer); - let handshake = plexer.use_client_channel(PROTOCOL_N2N_HANDSHAKE); - let blockfetch = plexer.use_client_channel(PROTOCOL_N2N_BLOCK_FETCH); - - plexer.muxer.spawn(); - plexer.demuxer.spawn(); - - let versions = VersionTable::v4_and_above(MAINNET_MAGIC); - let mut hs_client = handshake::N2NClient::new(handshake); - let handshake = hs_client.handshake(versions).unwrap(); - - assert!(matches!(handshake, handshake::Confirmation::Accepted(..))); + let mut peer = PeerClient::connect("relays-new.cardano-mainnet.iohk.io:3001", MAINNET_MAGIC) + .await + .unwrap(); let point = Point::Specific( 49159253, hex::decode("d034a2d0e4c3076f57368ed59319010c265718f0923057f8ff914a3b6bfd1314").unwrap(), ); - let mut bf_client = blockfetch::Client::new(blockfetch); - - let block = bf_client.fetch_single(point).unwrap(); + let block = peer.blockfetch().fetch_single(point).await.unwrap(); println!("downloaded block of size: {}", block.len()); println!("{}", hex::encode(&block)); diff --git a/examples/n2c-miniprotocols/Cargo.toml b/examples/n2c-miniprotocols/Cargo.toml index bfd3dcd..4761a11 100644 --- a/examples/n2c-miniprotocols/Cargo.toml +++ b/examples/n2c-miniprotocols/Cargo.toml @@ -11,3 +11,6 @@ pallas = { path = "../../pallas" } net2 = "0.2.37" hex = "0.4.3" log = "0.4.16" +tracing = "0.1.37" +tracing-subscriber = "0.3.16" +tokio = { version = "1.27.0", features = ["rt-multi-thread"] } diff --git a/examples/n2c-miniprotocols/src/main.rs b/examples/n2c-miniprotocols/src/main.rs index f40a140..145617f 100644 --- a/examples/n2c-miniprotocols/src/main.rs +++ b/examples/n2c-miniprotocols/src/main.rs @@ -1,56 +1,37 @@ use pallas::network::{ - miniprotocols::{chainsync, handshake, localstate, Point, MAINNET_MAGIC}, - multiplexer, + facades::NodeClient, + miniprotocols::{chainsync, localstate, Point, MAINNET_MAGIC}, }; +use tracing::info; -#[derive(Debug)] -struct LoggingObserver; - -#[allow(dead_code)] -fn do_handshake(channel: multiplexer::StdChannel) { - let mut client = handshake::N2CClient::new(channel); - - let confirmation = client - .handshake(handshake::n2c::VersionTable::v1_and_above(MAINNET_MAGIC)) - .unwrap(); - - match confirmation { - handshake::Confirmation::Accepted(v, _) => { - log::info!("hand-shake accepted, using version {}", v) - } - handshake::Confirmation::Rejected(x) => { - log::info!("hand-shake rejected with reason {:?}", x) - } - } -} - -#[allow(dead_code)] -fn do_localstate_query(channel: multiplexer::StdChannel) { - let mut client = localstate::ClientV10::new(channel); - client.acquire(None).unwrap(); +async fn do_localstate_query(client: &mut NodeClient) { + client.statequery().acquire(None).await.unwrap(); let result = client + .statequery() .query(localstate::queries::RequestV10::GetSystemStart) + .await .unwrap(); - log::info!("system start result: {:?}", result); + info!("system start result: {:?}", result); } -#[allow(dead_code)] -fn do_chainsync(channel: multiplexer::StdChannel) { +async fn do_chainsync(client: &mut NodeClient) { let known_points = vec![Point::Specific( 43847831u64, hex::decode("15b9eeee849dd6386d3770b0745e0450190f7560e5159b1b3ab13b14b2684a45").unwrap(), )]; - let mut client = chainsync::N2CClient::new(channel); + let (point, _) = client + .chainsync() + .find_intersect(known_points) + .await + .unwrap(); - let (point, _) = client.find_intersect(known_points).unwrap(); - - log::info!("intersected point is {:?}", point); + info!("intersected point is {:?}", point); for _ in 0..10 { - let next = client.request_next().unwrap(); + let next = client.chainsync().request_next().await.unwrap(); match next { chainsync::NextResponse::RollForward(h, _) => { @@ -62,44 +43,29 @@ fn do_chainsync(channel: multiplexer::StdChannel) { } } -fn main() { - env_logger::builder() - .filter_level(log::LevelFilter::Trace) - .init(); +#[tokio::main] +async fn main() { + tracing::subscriber::set_global_default( + tracing_subscriber::FmtSubscriber::builder() + .with_max_level(tracing::Level::TRACE) + .finish(), + ) + .unwrap(); - #[cfg(not(target_family = "unix"))] - { - panic!("can't use n2c unix socket on non-unix systems"); - } // we connect to the unix socket of the local node. Make sure you have the right // path for your environment - #[cfg(target_family = "unix")] - { - use pallas::network::{ - miniprotocols::{ - PROTOCOL_N2C_CHAIN_SYNC, PROTOCOL_N2C_HANDSHAKE, PROTOCOL_N2C_STATE_QUERY, - }, - multiplexer::bearers::Bearer, - }; - let bearer = Bearer::connect_unix("/tmp/node.socket").unwrap(); + let mut client = NodeClient::connect("/tmp/node.socket", MAINNET_MAGIC) + .await + .unwrap(); - // setup the multiplexer by specifying the bearer and the IDs of the - // miniprotocols to use - let mut plexer = multiplexer::StdPlexer::new(bearer); - let handshake = plexer.use_client_channel(PROTOCOL_N2C_HANDSHAKE); - let statequery = plexer.use_client_channel(PROTOCOL_N2C_STATE_QUERY); - let chainsync = plexer.use_client_channel(PROTOCOL_N2C_CHAIN_SYNC); + // execute an arbitrary "Local State" query against the node + do_localstate_query(&mut client).await; - plexer.muxer.spawn(); - plexer.demuxer.spawn(); - - // execute the required handshake against the relay - do_handshake(handshake); - - // execute an arbitrary "Local State" query against the node - do_localstate_query(statequery); - - // execute the chainsync flow from an arbitrary point in the chain - do_chainsync(chainsync); - } + // execute the chainsync flow from an arbitrary point in the chain + do_chainsync(&mut client).await; +} + +#[cfg(not(target_family = "unix"))] +fn main() { + panic!("can't use n2c unix socket on non-unix systems"); } diff --git a/examples/n2n-miniprotocols/Cargo.toml b/examples/n2n-miniprotocols/Cargo.toml index ac04c27..353d2b0 100644 --- a/examples/n2n-miniprotocols/Cargo.toml +++ b/examples/n2n-miniprotocols/Cargo.toml @@ -11,3 +11,6 @@ pallas = { path = "../../pallas" } net2 = "0.2.37" hex = "0.4.3" log = "0.4.16" +tracing = "0.1.37" +tracing-subscriber = "0.3.16" +tokio = { version = "1.27.0", features = ["rt-multi-thread"] } diff --git a/examples/n2n-miniprotocols/src/main.rs b/examples/n2n-miniprotocols/src/main.rs index 304156b..4a7fbc6 100644 --- a/examples/n2n-miniprotocols/src/main.rs +++ b/examples/n2n-miniprotocols/src/main.rs @@ -1,32 +1,10 @@ use pallas::network::{ - miniprotocols::{ - blockfetch, chainsync, handshake, Point, MAINNET_MAGIC, PROTOCOL_N2N_BLOCK_FETCH, - PROTOCOL_N2N_CHAIN_SYNC, PROTOCOL_N2N_HANDSHAKE, - }, - multiplexer::{bearers::Bearer, StdChannel, StdPlexer}, + facades::PeerClient, + miniprotocols::{chainsync, Point, MAINNET_MAGIC}, }; +use tracing::info; -#[derive(Debug)] -struct LoggingObserver; - -fn do_handshake(channel: StdChannel) { - let mut client = handshake::N2NClient::new(channel); - - let confirmation = client - .handshake(handshake::n2n::VersionTable::v7_and_above(MAINNET_MAGIC)) - .unwrap(); - - match confirmation { - handshake::Confirmation::Accepted(v, _) => { - log::info!("hand-shake accepted, using version {}", v) - } - handshake::Confirmation::Rejected(x) => { - log::info!("hand-shake rejected with reason {:?}", x) - } - } -} - -fn do_blockfetch(channel: StdChannel) { +async fn do_blockfetch(peer: &mut PeerClient) { let range = ( Point::Specific( 43847831, @@ -40,29 +18,25 @@ fn do_blockfetch(channel: StdChannel) { ), ); - let mut client = blockfetch::Client::new(channel); - - let blocks = client.fetch_range(range).unwrap(); + let blocks = peer.blockfetch().fetch_range(range).await.unwrap(); for block in blocks { - log::info!("received block of size: {}", block.len()); + info!("received block of size: {}", block.len()); } } -fn do_chainsync(channel: StdChannel) { +async fn do_chainsync(peer: &mut PeerClient) { let known_points = vec![Point::Specific( 43847831u64, hex::decode("15b9eeee849dd6386d3770b0745e0450190f7560e5159b1b3ab13b14b2684a45").unwrap(), )]; - let mut client = chainsync::N2NClient::new(channel); + let (point, _) = peer.chainsync().find_intersect(known_points).await.unwrap(); - let (point, _) = client.find_intersect(known_points).unwrap(); - - log::info!("intersected point is {:?}", point); + info!("intersected point is {:?}", point); for _ in 0..10 { - let next = client.request_next().unwrap(); + let next = peer.chainsync().request_next().await.unwrap(); match next { chainsync::NextResponse::RollForward(h, _) => { @@ -74,31 +48,24 @@ fn do_chainsync(channel: StdChannel) { } } -fn main() { - env_logger::builder() - .filter_level(log::LevelFilter::Info) - .init(); +#[tokio::main] +async fn main() { + tracing::subscriber::set_global_default( + tracing_subscriber::FmtSubscriber::builder() + .with_max_level(tracing::Level::TRACE) + .finish(), + ) + .unwrap(); // setup a TCP socket to act as data bearer between our agents and the remote // relay. - let bearer = Bearer::connect_tcp("relays-new.cardano-mainnet.iohk.io:3001").unwrap(); - - // setup the multiplexer by specifying the bearer and the IDs of the - // miniprotocols to use - let mut plexer = StdPlexer::new(bearer); - let handshake = plexer.use_client_channel(PROTOCOL_N2N_HANDSHAKE); - let blockfetch = plexer.use_client_channel(PROTOCOL_N2N_BLOCK_FETCH); - let chainsync = plexer.use_client_channel(PROTOCOL_N2N_CHAIN_SYNC); - - plexer.muxer.spawn(); - plexer.demuxer.spawn(); - - // execute the required handshake against the relay - do_handshake(handshake); + let mut peer = PeerClient::connect("relays-new.cardano-mainnet.iohk.io:3001", MAINNET_MAGIC) + .await + .unwrap(); // fetch an arbitrary batch of block - do_blockfetch(blockfetch); + do_blockfetch(&mut peer).await; // execute the chainsync flow from an arbitrary point in the chain - do_chainsync(chainsync); + do_chainsync(&mut peer).await; } diff --git a/pallas-miniprotocols/.gitignore b/pallas-miniprotocols/.gitignore deleted file mode 100644 index 96ef6c0..0000000 --- a/pallas-miniprotocols/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/target -Cargo.lock diff --git a/pallas-miniprotocols/Cargo.toml b/pallas-miniprotocols/Cargo.toml deleted file mode 100644 index 7b1815a..0000000 --- a/pallas-miniprotocols/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "pallas-miniprotocols" -description = "Implementation of the Ouroboros network mini-protocols state-machines" -version = "0.18.0" -edition = "2021" -repository = "https://github.com/txpipe/pallas" -homepage = "https://github.com/txpipe/pallas" -documentation = "https://docs.rs/pallas-machines" -license = "Apache-2.0" -readme = "README.md" -authors = [ - "Santiago Carmuega ", - "Pi Lanningham " -] - -[dependencies] -pallas-codec = { version = "0.18.0", path = "../pallas-codec/" } -pallas-multiplexer = { version = "0.18.0", path = "../pallas-multiplexer/" } -hex = "0.4.3" -itertools = "0.10.3" -thiserror = "1.0.31" -tracing = "0.1.37" - -[dev-dependencies] -tokio = { version = "1.27.0", features = ["macros", "rt"] } diff --git a/pallas-miniprotocols/src/machines.rs b/pallas-miniprotocols/src/machines.rs deleted file mode 100644 index ce3f9e8..0000000 --- a/pallas-miniprotocols/src/machines.rs +++ /dev/null @@ -1,140 +0,0 @@ -use pallas_codec::Fragment; -use pallas_multiplexer::agents::{Channel, ChannelBuffer, ChannelError}; -use std::{cell::Cell, fmt::Debug}; -use thiserror::Error; -use tracing::trace; - -#[derive(Debug, Error)] -pub enum MachineError { - #[error("invalid message for state [{0}]: {1}")] - InvalidMsgForState(String, String), - - #[error("channel error communicating with multiplexer: {0}")] - ChannelError(ChannelError), - - #[error("downstream error while processing business logic {0}")] - DownstreamError(Box), -} - -impl MachineError { - pub fn channel(err: ChannelError) -> Self { - Self::ChannelError(err) - } - - pub fn downstream(err: Box) -> Self { - Self::DownstreamError(err) - } - - pub fn invalid_msg(state: &A::State, msg: &A::Message) -> Self { - Self::InvalidMsgForState(format!("{state:?}"), format!("{msg:?}")) - } -} - -pub type Transition = Result; - -pub trait Agent: Sized { - type Message: std::fmt::Debug; - type State: std::fmt::Debug; - - fn state(&self) -> &Self::State; - fn is_done(&self) -> bool; - fn has_agency(&self) -> bool; - fn build_next(&self) -> Self::Message; - fn apply_start(self) -> Transition; - fn apply_outbound(self, msg: Self::Message) -> Transition; - fn apply_inbound(self, msg: Self::Message) -> Transition; -} - -pub struct Runner -where - A: Agent, - C: Channel, -{ - agent: Cell>, - buffer: ChannelBuffer, -} - -impl Runner -where - A: Agent, - A::Message: Fragment + std::fmt::Debug, - C: Channel, -{ - pub fn new(agent: A, channel: C) -> Self { - Self { - agent: Cell::new(Some(agent)), - buffer: ChannelBuffer::new(channel), - } - } - - pub fn start(&mut self) -> Result<(), MachineError> { - let prev = self.agent.take().unwrap(); - let next = prev.apply_start()?; - self.agent.set(Some(next)); - Ok(()) - } - - pub async fn run_step(&mut self) -> Result { - let prev = self.agent.take().unwrap(); - let next = run_agent_step(prev, &mut self.buffer).await?; - let is_done = next.is_done(); - - self.agent.set(Some(next)); - - Ok(is_done) - } - - pub async fn fulfill(mut self) -> Result<(), MachineError> { - self.start()?; - - while self.run_step().await? {} - - Ok(()) - } -} - -pub async fn run_agent_step(agent: A, channel: &mut ChannelBuffer) -> Transition -where - A: Agent, - A::Message: Fragment + std::fmt::Debug, - C: Channel, -{ - match agent.has_agency() { - true => { - let msg = agent.build_next(); - trace!(?msg, "processing outbound msg"); - - channel - .send_msg_chunks(&msg) - .await - .map_err(MachineError::channel)?; - - agent.apply_outbound(msg) - } - false => { - let msg = channel - .recv_full_msg() - .await - .map_err(MachineError::channel)?; - - trace!(?msg, "processing inbound msg"); - - agent.apply_inbound(msg) - } - } -} - -pub async fn run_agent(agent: A, buffer: &mut ChannelBuffer) -> Transition -where - A: Agent, - A::Message: Fragment + std::fmt::Debug, - C: Channel, -{ - let mut agent = agent.apply_start()?; - - while !agent.is_done() { - agent = run_agent_step(agent, buffer).await?; - } - - Ok(agent) -} diff --git a/pallas-miniprotocols/src/txmonitor/codec.rs b/pallas-miniprotocols/src/txmonitor/codec.rs deleted file mode 100644 index 424b857..0000000 --- a/pallas-miniprotocols/src/txmonitor/codec.rs +++ /dev/null @@ -1,155 +0,0 @@ -use super::{MempoolSizeAndCapacity, Message, MsgRequest, MsgResponse}; -use pallas_codec::minicbor::{decode, encode, Decode, Encode, Encoder}; - -impl Encode<()> for Message { - fn encode( - &self, - e: &mut Encoder, - ctx: &mut (), - ) -> Result<(), encode::Error> { - match self { - Message::MsgDone => { - e.array(1)?.u16(0)?; - } - Message::MsgAcquire => { - e.array(1)?.u16(1)?; - } - Message::MsgAcquired(slot) => { - e.array(2)?.u16(2)?; - e.encode(slot)?; - } - Message::MsgQuery(query) => { - query.encode(e, ctx)?; - } - Message::MsgResponse(response) => { - response.encode(e, ctx)?; - } - } - - Ok(()) - } -} - -impl<'b> Decode<'b, ()> for Message { - fn decode( - d: &mut pallas_codec::minicbor::Decoder<'b>, - _ctx: &mut (), - ) -> Result { - d.array()?; - let label = d.u16()?; - - match label { - 0 => Ok(Message::MsgDone), - 1 => Ok(Message::MsgAcquire), - 2 => { - let slot = d.decode()?; - Ok(Message::MsgAcquired(slot)) - } - 3 => Ok(Message::MsgQuery(MsgRequest::MsgRelease)), - 5 => Ok(Message::MsgQuery(MsgRequest::MsgNextTx)), - 6 => { - d.array()?; - let tag: Result = d.u8(); - let mut tx = None; - - if tag.is_ok() { - d.tag()?; - let cbor = d.bytes()?; - tx = Some(hex::encode(cbor)); - } - Ok(Message::MsgResponse(MsgResponse::MsgReplyNextTx(tx))) - } - 7 => { - let txid = d.decode()?; - Ok(Message::MsgQuery(MsgRequest::MsgHasTx(txid))) - } - 8 => { - let has = d.decode()?; - Ok(Message::MsgResponse(MsgResponse::MsgReplyHasTx(has))) - } - 9 => Ok(Message::MsgQuery(MsgRequest::MsgGetSizes)), - 10 => { - d.array()?; - let capacity_in_bytes = d.decode()?; - let size_in_bytes = d.decode()?; - let number_of_txs = d.decode()?; - - Ok(Message::MsgResponse(MsgResponse::MsgReplyGetSizes( - MempoolSizeAndCapacity { - capacity_in_bytes, - size_in_bytes, - number_of_txs, - }, - ))) - } - _ => Err(decode::Error::message("can't decode Message")), - } - } - - fn nil() -> Option { - None - } -} - -impl Encode<()> for MsgRequest { - fn encode( - &self, - e: &mut Encoder, - _ctx: &mut (), - ) -> Result<(), encode::Error> { - match self { - MsgRequest::MsgAwaitAcquire => { - e.array(1)?.u16(1)?; - } - MsgRequest::MsgGetSizes => { - e.array(1)?.u16(9)?; - } - MsgRequest::MsgHasTx(tx) => { - e.array(2)?.u16(7)?; - e.encode(tx)?; - } - MsgRequest::MsgNextTx => { - e.array(1)?.u16(5)?; - } - MsgRequest::MsgRelease => { - e.array(1)?.u16(3)?; - } - } - - Ok(()) - } -} - -impl Encode<()> for MsgResponse { - fn encode( - &self, - e: &mut Encoder, - _ctx: &mut (), - ) -> Result<(), encode::Error> { - match self { - MsgResponse::MsgReplyGetSizes(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)?; - } - MsgResponse::MsgReplyHasTx(tx) => { - e.array(2)?.u16(8)?; - e.encode(tx)?; - } - MsgResponse::MsgReplyNextTx(None) => { - e.array(1)?.u16(6)?; - } - MsgResponse::MsgReplyNextTx(Some(tx)) => { - e.array(2)?.u16(6)?; - e.encode(tx.to_string())?; - } - } - Ok(()) - } - - fn is_nil(&self) -> bool { - false - } -} diff --git a/pallas-miniprotocols/src/txmonitor/mod.rs b/pallas-miniprotocols/src/txmonitor/mod.rs deleted file mode 100644 index a08645a..0000000 --- a/pallas-miniprotocols/src/txmonitor/mod.rs +++ /dev/null @@ -1,213 +0,0 @@ -mod codec; - -use crate::machines::{Agent, MachineError, Transition}; -use pallas_codec::Fragment; -use std::fmt::Debug; - -type Slot = u64; -type TxId = String; -type Tx = String; - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum StBusyKind { - NextTx, - HasTx, - GetSizes, -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum State { - StIdle, - StAcquiring, - StAcquired, - StBusy(StBusyKind), - StDone, -} - -#[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 { - MsgAcquire, - MsgAcquired(Slot), - MsgQuery(MsgRequest), - MsgResponse(MsgResponse), - MsgDone, -} - -#[derive(Debug, Clone)] -pub enum MsgRequest { - MsgAwaitAcquire, - MsgNextTx, - MsgHasTx(TxId), - MsgGetSizes, - MsgRelease, -} -#[derive(Debug, Clone)] -pub enum MsgResponse { - MsgReplyNextTx(Option), - MsgReplyHasTx(bool), - MsgReplyGetSizes(MempoolSizeAndCapacity), -} - -#[derive(Debug, Clone)] -pub struct LocalTxMonitor { - pub state: State, - pub snapshot: Option, - pub request: Option, - pub output: Option, -} - -impl LocalTxMonitor -where - Message: Fragment, -{ - pub fn initial(state: State) -> Self { - Self { - state, - snapshot: None, - request: None, - output: None, - } - } - - fn on_acquired(self, slot: Slot) -> Transition { - Ok(Self { - state: State::StAcquired, - snapshot: Some(slot), - output: None, - ..self - }) - } - - fn on_reply_next_tx(self, tx: Option) -> Transition { - Ok(Self { - output: Some(MsgResponse::MsgReplyNextTx(tx)), - ..self - }) - } - - fn on_reply_has_tx(self, arg: bool) -> Transition { - Ok(Self { - output: Some(MsgResponse::MsgReplyHasTx(arg)), - ..self - }) - } - - fn on_reply_get_size(self, status: MempoolSizeAndCapacity) -> Transition { - Ok(Self { - output: Some(MsgResponse::MsgReplyGetSizes(status)), - ..self - }) - } -} - -impl Agent for LocalTxMonitor -where - Message: Fragment, -{ - type Message = Message; - type State = State; - - fn state(&self) -> &Self::State { - &self.state - } - - fn is_done(&self) -> bool { - self.state == State::StDone - } - - fn has_agency(&self) -> bool { - match &self.state { - State::StIdle => true, - State::StAcquiring => false, - State::StAcquired => true, - State::StBusy(..) => false, - State::StDone => false, - } - } - - fn build_next(&self) -> Self::Message { - match (&self.state, &self.request, &self.output) { - (State::StIdle, None, None) => Message::MsgAcquire, - (State::StAcquired, None, None) => Message::MsgAcquire, - (State::StAcquired, Some(MsgRequest::MsgAwaitAcquire), None) => Message::MsgAcquire, - (State::StAcquired, Some(MsgRequest::MsgNextTx), None) => { - Message::MsgQuery(MsgRequest::MsgNextTx) - } - (State::StAcquired, Some(MsgRequest::MsgHasTx(tx)), None) => { - Message::MsgQuery(MsgRequest::MsgHasTx(tx.clone())) - } - (State::StAcquired, Some(MsgRequest::MsgGetSizes), None) => { - Message::MsgQuery(MsgRequest::MsgGetSizes) - } - (State::StAcquired, None, Some(_)) => Message::MsgAcquire, - (State::StAcquired, Some(req), Some(_)) => Message::MsgQuery(req.to_owned()), - _ => panic!("I do not have agency, don't know what to do"), - } - } - - fn apply_start(self) -> Transition { - Ok(self) - } - - fn apply_outbound(self, msg: Self::Message) -> Transition { - match (self.state, msg) { - (State::StIdle, Message::MsgAcquire) => Ok(Self { - state: State::StAcquiring, - ..self - }), - (State::StAcquired, Message::MsgQuery(MsgRequest::MsgNextTx)) => Ok(Self { - state: State::StBusy(StBusyKind::NextTx), - ..self - }), - (State::StAcquired, Message::MsgQuery(MsgRequest::MsgHasTx(_))) => Ok(Self { - state: State::StBusy(StBusyKind::HasTx), - ..self - }), - - (State::StAcquired, Message::MsgQuery(MsgRequest::MsgGetSizes)) => Ok(Self { - state: State::StBusy(StBusyKind::GetSizes), - ..self - }), - (State::StAcquired, Message::MsgAcquire) => Ok(Self { - state: State::StAcquiring, - ..self - }), - (State::StAcquired, Message::MsgQuery(MsgRequest::MsgRelease)) => Ok(Self { - state: State::StIdle, - ..self - }), - (State::StIdle, Message::MsgDone) => Ok(Self { - state: State::StDone, - ..self - }), - - _ => panic!("PANIC! Cannot match outbound"), - } - } - - fn apply_inbound(self, msg: Self::Message) -> Transition { - match (&self.state, msg) { - (State::StAcquiring, Message::MsgAcquired(s)) => self.on_acquired(s), - ( - State::StBusy(StBusyKind::NextTx), - Message::MsgResponse(MsgResponse::MsgReplyNextTx(tx)), - ) => self.on_reply_next_tx(tx), - ( - State::StBusy(StBusyKind::HasTx), - Message::MsgResponse(MsgResponse::MsgReplyHasTx(arg)), - ) => self.on_reply_has_tx(arg), - ( - State::StBusy(StBusyKind::GetSizes), - Message::MsgResponse(MsgResponse::MsgReplyGetSizes(msc)), - ) => self.on_reply_get_size(msc), - (state, msg) => Err(MachineError::invalid_msg::(state, &msg)), - } - } -} diff --git a/pallas-miniprotocols/tests/integration.rs b/pallas-miniprotocols/tests/integration.rs deleted file mode 100644 index bd7800c..0000000 --- a/pallas-miniprotocols/tests/integration.rs +++ /dev/null @@ -1,242 +0,0 @@ -use pallas_miniprotocols::{ - blockfetch, - chainsync::{self, NextResponse}, - handshake::{self, Confirmation}, - txsubmission::{self, EraTxId, Reply, TxIdAndSize}, - Point, PROTOCOL_N2N_BLOCK_FETCH, PROTOCOL_N2N_CHAIN_SYNC, PROTOCOL_N2N_HANDSHAKE, - PROTOCOL_N2N_TX_SUBMISSION, -}; -use pallas_multiplexer::{bearers::Bearer, StdChannel, StdPlexer}; - -struct N2NChannels { - chainsync: StdChannel, - blockfetch: StdChannel, - txsubmission: StdChannel, -} - -async fn setup_n2n_client_connection() -> N2NChannels { - let bearer = Bearer::connect_tcp("preview-node.world.dev.cardano.org:30002").unwrap(); - let mut plexer = StdPlexer::new(bearer); - - let handshake = plexer.use_channel(PROTOCOL_N2N_HANDSHAKE); - let chainsync = plexer.use_channel(PROTOCOL_N2N_CHAIN_SYNC); - let blockfetch = plexer.use_channel(PROTOCOL_N2N_BLOCK_FETCH); - let txsubmission = plexer.use_channel(PROTOCOL_N2N_TX_SUBMISSION); - - plexer.muxer.spawn(); - plexer.demuxer.spawn(); - - let mut client = handshake::N2NClient::new(handshake); - - let confirmation = client - .handshake(handshake::n2n::VersionTable::v7_and_above(2)) - .await - .unwrap(); - - assert!(matches!(confirmation, Confirmation::Accepted(..))); - - if let Confirmation::Accepted(v, _) = confirmation { - assert!(v >= 7); - } - - N2NChannels { - chainsync, - blockfetch, - txsubmission, - } -} - -#[tokio::test] -#[ignore] -pub async fn chainsync_history_happy_path() { - let N2NChannels { chainsync, .. } = setup_n2n_client_connection().await; - - let known_point = Point::Specific( - 1654413, - hex::decode("7de1f036df5a133ce68a82877d14354d0ba6de7625ab918e75f3e2ecb29771c2").unwrap(), - ); - - let mut client = chainsync::N2NClient::new(chainsync); - - let (point, _) = client - .find_intersect(vec![known_point.clone()]) - .await - .unwrap(); - - assert!(matches!(client.state(), chainsync::State::Idle)); - - match point { - Some(point) => assert_eq!(point, known_point), - None => panic!("expected point"), - } - - let next = client.request_next().await.unwrap(); - - match next { - NextResponse::RollBackward(point, _) => assert_eq!(point, known_point), - _ => panic!("expected rollback"), - } - - assert!(matches!(client.state(), chainsync::State::Idle)); - - for _ in 0..10 { - let next = client.request_next().await.unwrap(); - - match next { - NextResponse::RollForward(_, _) => (), - _ => panic!("expected roll-forward"), - } - - assert!(matches!(client.state(), chainsync::State::Idle)); - } - - client.send_done().await.unwrap(); - - assert!(matches!(client.state(), chainsync::State::Done)); -} - -#[tokio::test] -#[ignore] -pub async fn chainsync_tip_happy_path() { - let N2NChannels { chainsync, .. } = setup_n2n_client_connection().await; - - let mut client = chainsync::N2NClient::new(chainsync); - - client.intersect_tip().await.unwrap(); - - assert!(matches!(client.state(), chainsync::State::Idle)); - - let next = client.request_next().await.unwrap(); - - assert!(matches!(next, NextResponse::RollBackward(..))); - - let mut await_count = 0; - - for _ in 0..4 { - let next = if client.has_agency() { - client.request_next().await.unwrap() - } else { - await_count += 1; - client.recv_while_must_reply().await.unwrap() - }; - - match next { - NextResponse::RollForward(_, _) => (), - NextResponse::Await => (), - _ => panic!("expected roll-forward or await"), - } - } - - assert!(await_count > 0, "tip was never reached"); - - client.send_done().await.unwrap(); - - assert!(matches!(client.state(), chainsync::State::Done)); -} - -#[tokio::test] -#[ignore] -pub async fn blockfetch_happy_path() { - let N2NChannels { blockfetch, .. } = setup_n2n_client_connection().await; - - let known_point = Point::Specific( - 1654413, - hex::decode("7de1f036df5a133ce68a82877d14354d0ba6de7625ab918e75f3e2ecb29771c2").unwrap(), - ); - - let mut client = blockfetch::Client::new(blockfetch); - - let range_ok = client - .request_range((known_point.clone(), known_point)) - .await; - - assert!(matches!(client.state(), blockfetch::State::Streaming)); - - assert!(matches!(range_ok, Ok(_))); - - for _ in 0..1 { - let next = client.recv_while_streaming().await.unwrap(); - - match next { - Some(body) => assert_eq!(body.len(), 3251), - _ => panic!("expected block body"), - } - - assert!(matches!(client.state(), blockfetch::State::Streaming)); - } - - let next = client.recv_while_streaming().await.unwrap(); - - assert!(matches!(next, None)); - - client.send_done().await.unwrap(); - - assert!(matches!(client.state(), blockfetch::State::Done)); -} - -#[tokio::test] -#[ignore] -pub async fn txsubmission_server_happy_path() { - // TODO(pi): Note that the below doesn't work; we need a node to connect *to us* - // during the integration test which seems awkward; - // Alternatively, we can just set up both a client and server connecting to - // themselves for testing! - - let N2NChannels { txsubmission, .. } = setup_n2n_client_connection().await; - - let mut server = txsubmission::Server::new(txsubmission); - - assert!(matches!(server.wait_for_init().await, Ok(_))); - - assert!(matches!( - server.acknowledge_and_request_tx_ids(false, 0, 3).await, - Ok(_) - )); - - let reply: Result<_, _> = server.receive_next_reply().await; - assert!(matches!(reply, Ok(Reply::TxIds(_)))); - let Ok(Reply::TxIds(tx_ids)) = reply else { unreachable!() }; - - assert!(tx_ids.len() <= 3); - - assert!(matches!( - server - .request_txs( - tx_ids - .into_iter() - .map(|txid: TxIdAndSize| txid.0) - .collect() - ) - .await, - Ok(_) - )); - - let reply = server.receive_next_reply().await; - assert!(matches!(reply, Ok(Reply::Txs(_)))); - let Ok(Reply::Txs(first_txs)) = reply else { unreachable!() }; - - assert!(matches!( - server.acknowledge_and_request_tx_ids(false, 1, 3).await, - Ok(_) - )); - - let reply = server.receive_next_reply().await; - assert!(matches!(reply, Ok(Reply::Txs(_)))); - let Ok(Reply::Txs(second_txs)) = reply else { unreachable!() }; - - // Make sure we receive the second and third tx again, indicating we sent the - // `acknowledge 1` bit correctly - assert_eq!(second_txs[0], first_txs[1]); - assert_eq!(second_txs[1], first_txs[2]); - - assert!(matches!( - server.acknowledge_and_request_tx_ids(true, 3, 3).await, - Ok(_) - )); - - match server.receive_next_reply().await { - Ok(Reply::Done) => (), // Server aint havin none of our sh*t - Ok(Reply::TxIds(tx_ids)) => assert_eq!(tx_ids.len(), 3), - Ok(_) | Err(_) => unreachable!(), - } -} diff --git a/pallas-multiplexer/.gitignore b/pallas-multiplexer/.gitignore deleted file mode 100644 index 96ef6c0..0000000 --- a/pallas-multiplexer/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/target -Cargo.lock diff --git a/pallas-multiplexer/Cargo.toml b/pallas-multiplexer/Cargo.toml deleted file mode 100644 index e3772c7..0000000 --- a/pallas-multiplexer/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "pallas-multiplexer" -description = "Multithreaded Ouroboros multiplexer implementation using mpsc channels" -version = "0.18.0" -edition = "2021" -repository = "https://github.com/txpipe/pallas" -homepage = "https://github.com/txpipe/pallas" -documentation = "https://docs.rs/pallas-multiplexer" -license = "Apache-2.0" -readme = "README.md" -authors = ["Santiago Carmuega "] - -[dependencies] -pallas-codec = { version = "0.18.0", path = "../pallas-codec/" } -log = "0.4.14" -byteorder = "1.4.3" -hex = "0.4.3" -rand = "0.8.4" -thiserror = "1.0.31" -tracing = "0.1.37" - -[features] -std = [] -sync = [] -default = ["std", "sync"] - -[dev-dependencies] -tokio = { version = "1.27.0", features = ["macros", "rt"] } diff --git a/pallas-multiplexer/README.md b/pallas-multiplexer/README.md deleted file mode 100644 index b9fd7da..0000000 --- a/pallas-multiplexer/README.md +++ /dev/null @@ -1,88 +0,0 @@ -# Pallas Multiplexer - -This is an implementation of the Ouroboros multiplexer logic 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: - -- each mini-protocol state machine should be able to work in its own thread -- a bounded queue should serve as buffer to decouple mini-protocol logic from multiplexer work -- the implementation should pipelining-friendly, even if we don't have a current use-case -- the multiplexer should be agnostic of the mini-protocols implementation details. - -## Implementation Details - -Given the above definitions, Rust's _mpsc channels_ seem like the correct artifact to orchestrate the communication between the different threads in the multiplexer process. - -The following diagram provides an overview of the components involved: - -![Multiplexer Diagram](docs/diagram.png) - -## Usage - -The following code provides a very rough example of how to setup a client that connects to a node and spawns two concurrent threads running independently, both communication over the same bearer using _Pallas_ multiplexer. - -```rust -// Setup a new bearer. In this case, we use a unix socket to connect -// to a node running on the local machine. -let bearer = UnixStream::connect("/tmp/pallas").unwrap(); - -// Setup a new multiplexer using the created bearer and a specification -// of the mini-protocol IDs that we'll be using for our session. In this case, we -// pass id #0 (handshake) and #2 (chainsync). -let muxer = Multiplexer::setup(tcp, &[0, 2]) - -// Ask the multiplexer to provide us with the channel for the miniprotocol #0. -let mut handshake = muxer.use_client_channel(PROTOCOL_N2N_HANDSHAKE); - -// Spawn a thread and pass the ownership of the channel. -thread::spawn(move || { - // Deconstruct the channel to get a handle for sending data into the muxer - // ingress and a handle to receive data from the demuxer egress. - let Channel(mux_tx, demux_rx) = handshake; - - // Do something with the channel. In this case, we just keep sending - // dumb data every 50 millis. - loop { - let payload = vec![1; 65545]; - tx.send(payload).unwrap(); - thread::sleep(Duration::from_millis(50)); - } -}); - -// Ask the multiplexer to provide us with the channel for the chainsync miniprotocol. -let mut chainsync = muxer.use_client_channel(PROTOCOL_N2N_CHAINSYNC); - -// Spawn a different thread and pass the ownership of the 2nd channel. -thread::spawn(move || { - // Deconstruct the channel to get a handle for sending data into the muxer - // ingress and a handle to receive data from the demuxer egress. - let Channel(mux_tx, demux_rx) = chainsync; - - // Do something with the channel. In this case, we just print in stdout - // whatever get received for this mini-protocol. - loop { - let payload = rx.recv().unwrap(); - println!("id:{protocol}, length:{}", payload.len()); - } -}); -``` - -## Run Examples - -For a working example of a two peers communicating (a sender and a listener), check the [examples folder](examples). To run the examples, open two different terminals and run a different peer in each one: - -```sh -# on terminal 1, start the listener -RUST_LOG=info cargo run --example listener -``` - -```sh -# on terminal 2, start the sender -RUST_LOG=info cargo run --example sender -``` - -## Real World Usage - -For a more complex, real-world example, check the [Oura](https://github.com/txpipe/oura) repo, it provides a full-blown client tool designed to live-stream block data from a local or remote node. \ No newline at end of file diff --git a/pallas-multiplexer/docs/diagram.png b/pallas-multiplexer/docs/diagram.png deleted file mode 100644 index b91aabc..0000000 Binary files a/pallas-multiplexer/docs/diagram.png and /dev/null differ diff --git a/pallas-multiplexer/src/agents.rs b/pallas-multiplexer/src/agents.rs deleted file mode 100644 index 64121d0..0000000 --- a/pallas-multiplexer/src/agents.rs +++ /dev/null @@ -1,180 +0,0 @@ -//! Interface to interact with the multiplexer as an agent - -use crate::Payload; -use pallas_codec::{minicbor, Fragment}; -use thiserror::Error; -use tracing::{debug, error, trace}; - -#[derive(Debug, Error)] -pub enum ChannelError { - #[error("channel is not connected, failed to send payload")] - NotConnected(Option), - - #[error("failure encoding message into CBOR")] - Encoding(String), - - #[error("failure decoding message from CBOR")] - Decoding(String), -} - -/// A raw link to the ingress / egress of the multiplexer -pub trait Channel { - async fn enqueue_chunk(&mut self, chunk: Payload) -> Result<(), ChannelError>; - async fn dequeue_chunk(&mut self) -> Result; -} - -/// Protocol value that defines max segment length -pub const MAX_SEGMENT_PAYLOAD_LENGTH: usize = 65535; - -fn try_decode_message(buffer: &mut Vec) -> Result, ChannelError> -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); - error!("{}", hex::encode(buffer)); - Err(ChannelError::Decoding(err.to_string())) - } - } -} - -/// A channel abstraction to hide the complexity of partial payloads -pub struct ChannelBuffer { - channel: C, - temp: Vec, -} - -impl ChannelBuffer { - pub fn new(channel: C) -> Self { - Self { - channel, - temp: Vec::new(), - } - } - - /// Enqueues a msg as a sequence payload chunks - pub async fn send_msg_chunks(&mut self, msg: &M) -> Result<(), ChannelError> - where - M: Fragment, - { - let mut payload = Vec::new(); - minicbor::encode(msg, &mut payload) - .map_err(|err| ChannelError::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(&mut self) -> Result - where - M: Fragment, - { - if !self.temp.is_empty() { - if let Some(msg) = try_decode_message::(&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::(&mut self.temp)? { - debug!("decoding done"); - return Ok(msg); - } - - trace!("not enough data"); - } - } - - pub fn unwrap(self) -> C { - self.channel - } -} - -impl From for ChannelBuffer { - fn from(channel: C) -> Self { - ChannelBuffer::new(channel) - } -} - -#[cfg(test)] -mod tests { - use std::collections::VecDeque; - - use super::*; - - impl Channel for VecDeque { - async fn enqueue_chunk(&mut self, chunk: Payload) -> Result<(), ChannelError> { - self.push_back(chunk); - Ok(()) - } - - async fn dequeue_chunk(&mut self) -> Result { - let chunk = self.pop_front().ok_or(ChannelError::NotConnected(None))?; - Ok(chunk) - } - } - - #[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 mut channel = VecDeque::::new(); - channel.push_back(input); - - 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 mut channel = VecDeque::::new(); - - while !input.is_empty() { - let chunk = Vec::from(input.drain(0..2).as_slice()); - channel.push_back(chunk); - } - - 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); - } -} diff --git a/pallas-multiplexer/src/bearers.rs b/pallas-multiplexer/src/bearers.rs deleted file mode 100644 index 64d1d20..0000000 --- a/pallas-multiplexer/src/bearers.rs +++ /dev/null @@ -1,187 +0,0 @@ -use byteorder::{ByteOrder, NetworkEndian, WriteBytesExt}; -use std::io::{Read, Write}; -use std::net::{SocketAddr, TcpListener, ToSocketAddrs}; -use std::{net::TcpStream, time::Instant}; -use tracing::{debug, event_enabled, trace}; - -use crate::Payload; - -#[cfg(target_family = "unix")] -use std::os::unix::net::UnixStream; -use std::time::Duration; - -pub struct Segment { - pub protocol: u16, - pub timestamp: u32, - pub payload: Payload, -} -impl Segment { - pub fn new(clock: Instant, protocol: u16, payload: Payload) -> Self { - Segment { - timestamp: clock.elapsed().as_micros() as u32, - protocol, - payload, - } - } -} - -fn write_segment(writer: &mut impl Write, segment: Segment) -> Result<(), std::io::Error> { - let Segment { - timestamp, - protocol, - payload, - } = segment; - - let mut msg = Vec::new(); - msg.write_u32::(timestamp)?; - msg.write_u16::(protocol)?; - msg.write_u16::(payload.len() as u16)?; - msg.write_all(&payload)?; - - if event_enabled!(tracing::Level::TRACE) { - trace!( - protocol, - length = payload.len(), - message = hex::encode(&msg), - "writing segment" - ); - } - - writer.write_all(&msg)?; - writer.flush() -} - -fn read_segment(reader: &mut impl Read) -> Result { - let mut header = [0u8; 8]; - - reader.read_exact(&mut header)?; - - if event_enabled!(tracing::Level::TRACE) { - trace!(header = hex::encode(header), "segment header read"); - } - - let length = NetworkEndian::read_u16(&header[6..]) as usize; - let protocol = NetworkEndian::read_u16(&header[4..6]) as usize ^ 0x8000; - let timestamp = NetworkEndian::read_u32(&header[0..4]); - - debug!(protocol, timestamp, length, "parsed inbound msg"); - - let mut payload = vec![0u8; length]; - reader.read_exact(&mut payload)?; - - if event_enabled!(tracing::Level::TRACE) { - trace!(payload = hex::encode(&payload), "segment payload read"); - } - - Ok(Segment { - protocol: protocol as u16, - timestamp, - payload, - }) -} - -// This snippet will be useful if we want to switch TCP streams into -// non-blocking mode, but that's not likely (if we want async, we'll probably go -// with Tokio instead of a handcrafted approach). -/* -fn read_segment_with_timeout(reader: &mut impl Read) -> Result, std::io::Error> { - match read_segment(reader) { - Ok(s) => Ok(Some(s)), - Err(err) => match err.kind() { - std::io::ErrorKind::WouldBlock => Ok(None), - std::io::ErrorKind::TimedOut => Ok(None), - std::io::ErrorKind::Interrupted => Ok(None), - _ => Err(err), - }, - } -} - */ - -#[derive(Debug)] -pub enum Bearer { - Tcp(TcpStream), - - #[cfg(target_family = "unix")] - Unix(UnixStream), -} - -impl Bearer { - pub fn connect_tcp(addr: A) -> Result { - let bearer = TcpStream::connect(addr)?; - bearer.set_nodelay(true)?; - - Ok(Bearer::Tcp(bearer)) - } - - pub fn connect_tcp_timeout( - addr: &SocketAddr, - timeout: Duration, - ) -> Result { - let bearer = TcpStream::connect_timeout(addr, timeout)?; - bearer.set_nodelay(true)?; - - Ok(Bearer::Tcp(bearer)) - } - - pub fn accept_tcp(server: TcpListener) -> Result<(Self, SocketAddr), std::io::Error> { - let (bearer, remote_addr) = server.accept().unwrap(); - bearer.set_nodelay(true)?; - - Ok((Bearer::Tcp(bearer), remote_addr)) - } - - #[cfg(target_family = "unix")] - pub fn connect_unix>(path: P) -> Result { - let bearer = UnixStream::connect(path)?; - - Ok(Bearer::Unix(bearer)) - } - - pub fn read_segment(&mut self) -> Result, std::io::Error> { - match self { - Bearer::Tcp(s) => { - // std tcp streams won't be supporting timeout / async. We don't handle - // specific timeout-related errors, these will remain unhandled and bubble up - // to the consumer lib. The Option wrapper is here just for compatiblity with - // other future bearers that might support timeouts - read_segment(s).map(Some) - } - - #[cfg(target_family = "unix")] - Bearer::Unix(s) => read_segment(s).map(Some), - } - } - - pub fn write_segment(&mut self, segment: Segment) -> Result<(), std::io::Error> { - match self { - Bearer::Tcp(s) => write_segment(s, segment), - - #[cfg(target_family = "unix")] - Bearer::Unix(s) => write_segment(s, segment), - } - } -} - -impl From for Bearer { - fn from(stream: TcpStream) -> Self { - Bearer::Tcp(stream) - } -} - -#[cfg(target_family = "unix")] -impl From for Bearer { - fn from(stream: UnixStream) -> Self { - Bearer::Unix(stream) - } -} - -impl Clone for Bearer { - fn clone(&self) -> Self { - match self { - Bearer::Tcp(s) => Bearer::Tcp(s.try_clone().expect("error cloning tcp stream")), - - #[cfg(target_family = "unix")] - Bearer::Unix(s) => Bearer::Unix(s.try_clone().expect("error cloning unix stream")), - } - } -} diff --git a/pallas-multiplexer/src/demux.rs b/pallas-multiplexer/src/demux.rs deleted file mode 100644 index ce9cf2e..0000000 --- a/pallas-multiplexer/src/demux.rs +++ /dev/null @@ -1,67 +0,0 @@ -use std::collections::HashMap; - -use crate::{bearers::Bearer, Payload}; - -pub struct EgressError(pub Payload); - -pub trait Egress { - fn send(&mut self, payload: Payload) -> Result<(), EgressError>; -} - -pub enum DemuxError { - BearerError(std::io::Error), - EgressDisconnected(u16, Payload), - EgressUnknown(u16, Payload), -} - -pub enum TickOutcome { - Busy, - Idle, -} - -/// A demuxer that reads from a bearer into the corresponding egress -pub struct Demuxer { - bearer: Bearer, - egress: HashMap, -} - -impl Demuxer -where - E: Egress, -{ - pub fn new(bearer: Bearer) -> Self { - Demuxer { - bearer, - egress: Default::default(), - } - } - - pub fn register(&mut self, id: u16, tx: E) { - self.egress.insert(id, tx); - } - - pub fn unregister(&mut self, id: u16) -> Option { - self.egress.remove(&id) - } - - fn dispatch(&mut self, protocol: u16, payload: Payload) -> Result<(), DemuxError> { - match self.egress.get_mut(&protocol) { - Some(tx) => match tx.send(payload) { - Err(EgressError(p)) => Err(DemuxError::EgressDisconnected(protocol, p)), - Ok(_) => Ok(()), - }, - None => Err(DemuxError::EgressUnknown(protocol, payload)), - } - } - - pub fn tick(&mut self) -> Result { - match self.bearer.read_segment() { - Err(err) => Err(DemuxError::BearerError(err)), - Ok(None) => Ok(TickOutcome::Idle), - Ok(Some(segment)) => match self.dispatch(segment.protocol, segment.payload) { - Err(err) => Err(err), - Ok(()) => Ok(TickOutcome::Busy), - }, - } - } -} diff --git a/pallas-multiplexer/src/lib.rs b/pallas-multiplexer/src/lib.rs deleted file mode 100644 index dbad307..0000000 --- a/pallas-multiplexer/src/lib.rs +++ /dev/null @@ -1,19 +0,0 @@ -#![feature(async_fn_in_trait)] - -pub mod agents; -pub mod bearers; -pub mod demux; -pub mod mux; - -#[cfg(feature = "std")] -mod std; - -#[cfg(feature = "sync")] -pub mod sync; - -#[cfg(feature = "std")] -pub use crate::std::*; - -pub type Payload = Vec; - -pub type Message = (u16, Payload); diff --git a/pallas-multiplexer/src/mux.rs b/pallas-multiplexer/src/mux.rs deleted file mode 100644 index 4d7f779..0000000 --- a/pallas-multiplexer/src/mux.rs +++ /dev/null @@ -1,60 +0,0 @@ -use std::time::{Duration, Instant}; - -use crate::{ - bearers::{Bearer, Segment}, - Message, -}; - -pub enum IngressError { - Disconnected, - Empty, -} - -/// Source of payloads for a particular protocol -/// -/// To be implemented by any mechanism that allows to submit a payloads from a -/// particular protocol that need to be muxed by the multiplexer. -pub trait Ingress { - fn recv_timeout(&mut self, duration: Duration) -> Result; -} - -pub enum TickOutcome { - BearerError(std::io::Error), - IngressDisconnected, - Idle, - Busy, -} - -pub struct Muxer { - bearer: Bearer, - ingress: I, - clock: Instant, -} - -impl Muxer -where - I: Ingress, -{ - pub fn new(bearer: Bearer, ingress: I) -> Self { - Self { - bearer, - ingress, - clock: Instant::now(), - } - } - - pub fn tick(&mut self) -> TickOutcome { - match self.ingress.recv_timeout(Duration::from_millis(1)) { - Ok((id, payload)) => { - let segment = Segment::new(self.clock, id, payload); - - match self.bearer.write_segment(segment) { - Err(err) => TickOutcome::BearerError(err), - _ => TickOutcome::Busy, - } - } - Err(IngressError::Empty) => TickOutcome::Idle, - Err(IngressError::Disconnected) => TickOutcome::IngressDisconnected, - } - } -} diff --git a/pallas-multiplexer/src/std.rs b/pallas-multiplexer/src/std.rs deleted file mode 100644 index 7806683..0000000 --- a/pallas-multiplexer/src/std.rs +++ /dev/null @@ -1,183 +0,0 @@ -use crate::{ - agents::{self, ChannelBuffer}, - bearers::Bearer, - demux, mux, Message, Payload, -}; - -use std::{ - sync::{ - atomic::{AtomicBool, Ordering}, - mpsc::{channel, Receiver, RecvTimeoutError, SendError, Sender}, - Arc, - }, - thread::{spawn, JoinHandle}, - time::Duration, -}; - -pub type StdIngress = Receiver; - -impl mux::Ingress for StdIngress { - fn recv_timeout(&mut self, duration: Duration) -> Result { - match Receiver::recv_timeout(self, duration) { - Ok(x) => Ok(x), - Err(RecvTimeoutError::Disconnected) => Err(mux::IngressError::Disconnected), - Err(RecvTimeoutError::Timeout) => Err(mux::IngressError::Empty), - } - } -} - -pub type StdEgress = Sender; - -impl demux::Egress for StdEgress { - fn send(&mut self, payload: Payload) -> Result<(), demux::EgressError> { - match Sender::send(self, payload) { - Ok(_) => Ok(()), - Err(SendError(p)) => Err(demux::EgressError(p)), - } - } -} - -pub struct StdPlexer { - pub muxer: mux::Muxer, - pub demuxer: demux::Demuxer, - pub mux_tx: Sender, -} - -const PROTOCOL_SERVER_BIT: u16 = 0x8000; - -impl StdPlexer { - pub fn new(bearer: Bearer) -> Self { - let (mux_tx, mux_rx) = channel::(); - - Self { - muxer: mux::Muxer::new(bearer.clone(), mux_rx), - demuxer: demux::Demuxer::new(bearer), - mux_tx, - } - } - - pub fn use_channel(&mut self, protocol: u16) -> StdChannel { - let (demux_tx, demux_rx) = channel::(); - self.demuxer.register(protocol, demux_tx); - - let mux_tx = self.mux_tx.clone(); - - (protocol, mux_tx, demux_rx) - } - - /// Use the client-side channel for a given protocol - /// Explicitly unsets the most significant bit, forcing use of the client - /// side channel - pub fn use_client_channel(&mut self, protocol: u16) -> StdChannel { - self.use_channel(protocol & !PROTOCOL_SERVER_BIT) - } - - /// Use the server-side channel for a given protocol - /// Explicitly sets the most significant bit, forcing use of the server side - /// channel - pub fn use_server_channel(&mut self, protocol: u16) -> StdChannel { - self.use_channel(protocol | PROTOCOL_SERVER_BIT) - } -} - -impl mux::Muxer { - pub fn block(&mut self, cancel: Cancel) -> Result<(), std::io::Error> { - loop { - match self.tick() { - mux::TickOutcome::BearerError(err) => return Err(err), - mux::TickOutcome::Idle => match cancel.is_set() { - true => break Ok(()), - false => (), - }, - mux::TickOutcome::Busy => (), - mux::TickOutcome::IngressDisconnected => break Ok(()), - } - } - } - - pub fn spawn(mut self) -> Loop { - let cancel = Cancel::default(); - let cancel2 = cancel.clone(); - let thread = spawn(move || self.block(cancel2)); - - Loop { cancel, thread } - } -} - -impl demux::Demuxer { - pub fn block(&mut self, cancel: Cancel) -> Result<(), std::io::Error> { - loop { - match self.tick() { - Ok(demux::TickOutcome::Busy) => (), - Ok(demux::TickOutcome::Idle) => match cancel.is_set() { - true => break Ok(()), - false => (), - }, - Err(demux::DemuxError::BearerError(err)) => return Err(err), - Err(demux::DemuxError::EgressDisconnected(id, _)) => { - log::warn!("disconnected protocol {}", id) - } - Err(demux::DemuxError::EgressUnknown(id, _)) => { - log::warn!("unknown protocol {}", id) - } - } - } - } - - pub fn spawn(mut self) -> Loop { - let cancel = Cancel::default(); - let cancel2 = cancel.clone(); - let thread = spawn(move || self.block(cancel2)); - - Loop { cancel, thread } - } -} - -pub type StdChannel = (u16, Sender, Receiver); - -pub type StdChannelBuffer = ChannelBuffer; - -impl agents::Channel for StdChannel { - async fn enqueue_chunk(&mut self, payload: Payload) -> Result<(), agents::ChannelError> { - match self.1.send((self.0, payload)) { - Ok(_) => Ok(()), - Err(SendError((_, payload))) => Err(agents::ChannelError::NotConnected(Some(payload))), - } - } - - async fn dequeue_chunk(&mut self) -> Result { - match self.2.recv() { - Ok(payload) => Ok(payload), - Err(_) => Err(agents::ChannelError::NotConnected(None)), - } - } -} - -#[derive(Clone, Debug, Default)] -pub struct Cancel(Arc); - -impl Cancel { - pub fn set(&self) { - self.0.store(true, Ordering::SeqCst); - } - - pub fn is_set(&self) -> bool { - self.0.load(Ordering::SeqCst) - } -} - -#[derive(Debug)] -pub struct Loop { - cancel: Cancel, - thread: JoinHandle>, -} - -impl Loop { - pub fn cancel(&self) { - self.cancel.set(); - } - - pub fn join(self) -> Result<(), std::io::Error> { - self.thread.join().unwrap() - } -} diff --git a/pallas-multiplexer/src/sync.rs b/pallas-multiplexer/src/sync.rs deleted file mode 100644 index 3d4ff94..0000000 --- a/pallas-multiplexer/src/sync.rs +++ /dev/null @@ -1,55 +0,0 @@ -use crate::{ - agents::{self, ChannelBuffer}, - bearers::{Bearer, Segment}, - Payload, -}; - -use std::time::Instant; - -pub struct SyncPlexer { - bearer: Bearer, - protocol: u16, - clock: Instant, -} - -impl SyncPlexer { - pub fn new(bearer: Bearer, protocol: u16) -> Self { - Self { - bearer, - protocol, - clock: Instant::now(), - } - } - - pub fn unwrap(self) -> Bearer { - self.bearer - } -} - -pub type SyncChannel = ChannelBuffer; - -impl agents::Channel for SyncPlexer { - async fn enqueue_chunk(&mut self, payload: Payload) -> Result<(), agents::ChannelError> { - let segment = Segment::new(self.clock, self.protocol, payload); - - self.bearer - .write_segment(segment) - .map_err(|_| agents::ChannelError::NotConnected(None)) - } - - async fn dequeue_chunk(&mut self) -> Result { - match self.bearer.read_segment() { - Ok(segment) => match segment { - Some(x) => { - assert_eq!( - x.protocol, self.protocol, - "sync plexer received payload for wrong protocol" - ); - Ok(x.payload) - } - None => Err(agents::ChannelError::NotConnected(None)), - }, - Err(_) => Err(agents::ChannelError::NotConnected(None)), - } - } -} diff --git a/pallas-multiplexer/tests/integration.rs b/pallas-multiplexer/tests/integration.rs deleted file mode 100644 index 0d46fee..0000000 --- a/pallas-multiplexer/tests/integration.rs +++ /dev/null @@ -1,59 +0,0 @@ -use std::{ - net::{Ipv4Addr, SocketAddrV4, TcpListener}, - thread::{self, JoinHandle}, -}; - -use log::info; -use pallas_multiplexer::{agents::Channel, bearers::Bearer, StdPlexer}; -use rand::{distributions::Uniform, Rng}; - -fn setup_passive_muxer() -> JoinHandle { - thread::spawn(|| { - let server = TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, P)).unwrap(); - info!("listening for connections on port {}", P); - - let (bearer, _) = Bearer::accept_tcp(server).unwrap(); - - StdPlexer::new(bearer) - }) -} - -fn setup_active_muxer() -> JoinHandle { - thread::spawn(|| { - let bearer = Bearer::connect_tcp(SocketAddrV4::new(Ipv4Addr::LOCALHOST, P)).unwrap(); - - StdPlexer::new(bearer) - }) -} - -fn random_payload(size: usize) -> Vec { - let range = Uniform::from(0..255); - rand::thread_rng().sample_iter(&range).take(size).collect() -} - -#[tokio::test] -async fn one_way_small_sequence_of_payloads() { - let passive = setup_passive_muxer::<50301>(); - - // HACK: a small sleep seems to be required for Github actions runner to - // formally expose the port - thread::sleep(std::time::Duration::from_secs(1)); - - let active = setup_active_muxer::<50301>(); - - let mut active_plexer = active.join().unwrap(); - let mut passive_plexer = passive.join().unwrap(); - - let mut sender_channel = active_plexer.use_client_channel(0x0003u16); - let mut receiver_channel = passive_plexer.use_server_channel(0x0003u16); - - active_plexer.muxer.spawn(); - passive_plexer.demuxer.spawn(); - - for _ in 0..100 { - let payload = random_payload(50); - sender_channel.enqueue_chunk(payload.clone()).await.unwrap(); - let received_payload = receiver_channel.dequeue_chunk().await.unwrap(); - assert_eq!(payload, received_payload); - } -} diff --git a/pallas-network/Cargo.toml b/pallas-network/Cargo.toml new file mode 100644 index 0000000..6f623a7 --- /dev/null +++ b/pallas-network/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "pallas-network" +description = "Ouroboros networking stack using async IO" +version = "0.18.0" +edition = "2021" +repository = "https://github.com/txpipe/pallas" +homepage = "https://github.com/txpipe/pallas" +documentation = "https://docs.rs/pallas-upstream" +license = "Apache-2.0" +readme = "README.md" +authors = [ + "Santiago Carmuega ", + "Pi Lanningham ", +] + +[dependencies] +byteorder = "1.4.3" +hex = "0.4.3" +itertools = "0.10.5" +pallas-codec = { version = "0.18.0", path = "../pallas-codec" } +pallas-crypto = { version = "0.18.0", path = "../pallas-crypto" } +thiserror = "1.0.31" +tokio = { version = "1", features = ["net", "io-util", "time", "sync"] } +tracing = "0.1.37" + +[dev-dependencies] +tracing-subscriber = "0.3.16" +tokio = { version = "1", features = ["full"] } +rand = "0.8.5" diff --git a/pallas-network/README.md b/pallas-network/README.md new file mode 100644 index 0000000..6d4a3ab --- /dev/null +++ b/pallas-network/README.md @@ -0,0 +1,3 @@ +# Pallas Network + +An implementation of the Ouroboros networking stack. It provides a generic multiplexer and state-machines for the different mini-protocols. It uses async and tokio under the hood. \ No newline at end of file diff --git a/pallas-network/src/bearer.rs b/pallas-network/src/bearer.rs new file mode 100644 index 0000000..bebf602 --- /dev/null +++ b/pallas-network/src/bearer.rs @@ -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; + +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
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 { + 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) -> Result { + 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 { + 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); + +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 { + 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(()) + } +} diff --git a/pallas-network/src/facades.rs b/pallas-network/src/facades.rs new file mode 100644 index 0000000..5bbba2b --- /dev/null +++ b/pallas-network/src/facades.rs @@ -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>, + pub handshake: handshake::Confirmation, + chainsync: chainsync::N2NClient, + blockfetch: blockfetch::Client, +} + +impl PeerClient { + pub async fn connect(address: &str, magic: u64) -> Result { + 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>, + pub handshake: handshake::Confirmation, + chainsync: chainsync::N2CClient, + statequery: localstate::ClientV10, +} + +impl NodeClient { + pub async fn connect(path: impl AsRef, magic: u64) -> Result { + 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(); + } +} diff --git a/pallas-network/src/lib.rs b/pallas-network/src/lib.rs new file mode 100644 index 0000000..750af8f --- /dev/null +++ b/pallas-network/src/lib.rs @@ -0,0 +1,4 @@ +pub mod bearer; +pub mod facades; +pub mod miniprotocols; +pub mod plexer; diff --git a/pallas-miniprotocols/README.md b/pallas-network/src/miniprotocols/README.md similarity index 100% rename from pallas-miniprotocols/README.md rename to pallas-network/src/miniprotocols/README.md diff --git a/pallas-miniprotocols/src/blockfetch/client.rs b/pallas-network/src/miniprotocols/blockfetch/client.rs similarity index 87% rename from pallas-miniprotocols/src/blockfetch/client.rs rename to pallas-network/src/miniprotocols/blockfetch/client.rs index 17b03e3..09a4388 100644 --- a/pallas-miniprotocols/src/blockfetch/client.rs +++ b/pallas-network/src/miniprotocols/blockfetch/client.rs @@ -1,9 +1,8 @@ -use pallas_codec::Fragment; -use pallas_multiplexer::agents::{Channel, ChannelBuffer, ChannelError}; use thiserror::Error; use tracing::{debug, info, warn}; -use crate::common::Point; +use crate::miniprotocols::common::Point; +use crate::plexer; use super::{Message, State}; @@ -24,8 +23,8 @@ pub enum Error { #[error("requested range doesn't contain any blocks")] NoBlocks, - #[error("error while sending or receiving data through the channel")] - ChannelError(ChannelError), + #[error("error while sending or receiving data through the multiplexer")] + Plexer(plexer::Error), } pub type Body = Vec; @@ -34,18 +33,11 @@ pub type Range = (Point, Point); pub type HasBlocks = Option<()>; -pub struct Client(State, ChannelBuffer) -where - H: Channel, - Message: Fragment; +pub struct Client(State, plexer::ChannelBuffer); -impl Client -where - H: Channel, - Message: Fragment, -{ - pub fn new(channel: H) -> Self { - Self(State::Idle, ChannelBuffer::new(channel)) +impl Client { + pub fn new(channel: plexer::AgentChannel) -> Self { + Self(State::Idle, plexer::ChannelBuffer::new(channel)) } pub fn state(&self) -> &State { @@ -102,17 +94,14 @@ where 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::ChannelError)?; + self.1.send_msg_chunks(msg).await.map_err(Error::Plexer)?; Ok(()) } pub async fn recv_message(&mut self) -> Result { self.assert_agency_is_theirs()?; - let msg = self.1.recv_full_msg().await.map_err(Error::ChannelError)?; + let msg = self.1.recv_full_msg().await.map_err(Error::Plexer)?; self.assert_inbound_state(&msg)?; Ok(msg) @@ -149,6 +138,8 @@ where } pub async fn recv_while_streaming(&mut self) -> Result, Error> { + debug!("waiting for stream"); + match self.recv_message().await? { Message::Block { body } => Ok(Some(body)), Message::BatchDone => { diff --git a/pallas-miniprotocols/src/blockfetch/codec.rs b/pallas-network/src/miniprotocols/blockfetch/codec.rs similarity index 100% rename from pallas-miniprotocols/src/blockfetch/codec.rs rename to pallas-network/src/miniprotocols/blockfetch/codec.rs diff --git a/pallas-miniprotocols/src/blockfetch/mod.rs b/pallas-network/src/miniprotocols/blockfetch/mod.rs similarity index 100% rename from pallas-miniprotocols/src/blockfetch/mod.rs rename to pallas-network/src/miniprotocols/blockfetch/mod.rs diff --git a/pallas-miniprotocols/src/blockfetch/protocol.rs b/pallas-network/src/miniprotocols/blockfetch/protocol.rs similarity index 89% rename from pallas-miniprotocols/src/blockfetch/protocol.rs rename to pallas-network/src/miniprotocols/blockfetch/protocol.rs index 594626e..b3958b4 100644 --- a/pallas-miniprotocols/src/blockfetch/protocol.rs +++ b/pallas-network/src/miniprotocols/blockfetch/protocol.rs @@ -1,4 +1,4 @@ -use crate::Point; +use crate::miniprotocols::Point; #[derive(Debug, PartialEq, Eq, Clone)] pub enum State { diff --git a/pallas-miniprotocols/src/chainsync/buffer.rs b/pallas-network/src/miniprotocols/chainsync/buffer.rs similarity index 98% rename from pallas-miniprotocols/src/chainsync/buffer.rs rename to pallas-network/src/miniprotocols/chainsync/buffer.rs index 05fe62b..159c6ce 100644 --- a/pallas-miniprotocols/src/chainsync/buffer.rs +++ b/pallas-network/src/miniprotocols/chainsync/buffer.rs @@ -1,6 +1,6 @@ use std::collections::{vec_deque::Iter, VecDeque}; -use crate::Point; +use crate::miniprotocols::Point; /// A memory buffer to handle chain rollbacks /// @@ -98,7 +98,8 @@ impl RollbackBuffer { #[cfg(test)] mod tests { - use crate::{chainsync::RollbackEffect, Point}; + use super::RollbackEffect; + use crate::miniprotocols::Point; use super::RollbackBuffer; diff --git a/pallas-miniprotocols/src/chainsync/client.rs b/pallas-network/src/miniprotocols/chainsync/client.rs similarity index 90% rename from pallas-miniprotocols/src/chainsync/client.rs rename to pallas-network/src/miniprotocols/chainsync/client.rs index 747d682..6b985d9 100644 --- a/pallas-miniprotocols/src/chainsync/client.rs +++ b/pallas-network/src/miniprotocols/chainsync/client.rs @@ -1,10 +1,10 @@ use pallas_codec::Fragment; -use pallas_multiplexer::agents::{Channel, ChannelBuffer, ChannelError}; use std::marker::PhantomData; use thiserror::Error; use tracing::debug; -use crate::common::Point; +use crate::miniprotocols::Point; +use crate::plexer; use super::{BlockContent, HeaderContent, Message, State, Tip}; @@ -26,7 +26,7 @@ pub enum Error { IntersectionNotFound, #[error("error while sending or receiving data through the channel")] - ChannelError(ChannelError), + Plexer(plexer::Error), } pub type IntersectResponse = (Option, Tip); @@ -38,18 +38,20 @@ pub enum NextResponse { Await, } -pub struct Client(State, ChannelBuffer, PhantomData) +pub struct Client(State, plexer::ChannelBuffer, PhantomData) where - H: Channel, Message: Fragment; -impl Client +impl Client where - H: Channel, Message: Fragment, { - pub fn new(channel: H) -> Self { - Self(State::Idle, ChannelBuffer::new(channel), PhantomData {}) + pub fn new(channel: plexer::AgentChannel) -> Self { + Self( + State::Idle, + plexer::ChannelBuffer::new(channel), + PhantomData {}, + ) } pub fn state(&self) -> &State { @@ -112,10 +114,7 @@ where self.assert_agency_is_ours()?; self.assert_outbound_state(msg)?; - self.1 - .send_msg_chunks(msg) - .await - .map_err(Error::ChannelError)?; + self.1.send_msg_chunks(msg).await.map_err(Error::Plexer)?; Ok(()) } @@ -123,7 +122,7 @@ where pub async fn recv_message(&mut self) -> Result, Error> { self.assert_agency_is_theirs()?; - let msg = self.1.recv_full_msg().await.map_err(Error::ChannelError)?; + let msg = self.1.recv_full_msg().await.map_err(Error::Plexer)?; self.assert_inbound_state(&msg)?; @@ -135,10 +134,14 @@ where self.send_message(&msg).await?; self.0 = State::Intersect; + debug!("send find intersect"); + Ok(()) } pub async fn recv_intersect_response(&mut self) -> Result { + debug!("waiting for intersect response"); + match self.recv_message().await? { Message::IntersectFound(point, tip) => { self.0 = State::Idle; @@ -232,6 +235,6 @@ where } } -pub type N2NClient = Client; +pub type N2NClient = Client; -pub type N2CClient = Client; +pub type N2CClient = Client; diff --git a/pallas-miniprotocols/src/chainsync/codec.rs b/pallas-network/src/miniprotocols/chainsync/codec.rs similarity index 100% rename from pallas-miniprotocols/src/chainsync/codec.rs rename to pallas-network/src/miniprotocols/chainsync/codec.rs diff --git a/pallas-miniprotocols/src/chainsync/mod.rs b/pallas-network/src/miniprotocols/chainsync/mod.rs similarity index 100% rename from pallas-miniprotocols/src/chainsync/mod.rs rename to pallas-network/src/miniprotocols/chainsync/mod.rs diff --git a/pallas-miniprotocols/src/chainsync/protocol.rs b/pallas-network/src/miniprotocols/chainsync/protocol.rs similarity index 96% rename from pallas-miniprotocols/src/chainsync/protocol.rs rename to pallas-network/src/miniprotocols/chainsync/protocol.rs index 8b8e984..ee1eef5 100644 --- a/pallas-miniprotocols/src/chainsync/protocol.rs +++ b/pallas-network/src/miniprotocols/chainsync/protocol.rs @@ -1,6 +1,6 @@ use std::{fmt::Debug, ops::Deref}; -use crate::common::Point; +use crate::miniprotocols::Point; #[derive(Debug, Clone)] pub struct Tip(pub Point, pub u64); diff --git a/pallas-miniprotocols/src/common.rs b/pallas-network/src/miniprotocols/common.rs similarity index 97% rename from pallas-miniprotocols/src/common.rs rename to pallas-network/src/miniprotocols/common.rs index 180783b..562da29 100644 --- a/pallas-miniprotocols/src/common.rs +++ b/pallas-network/src/miniprotocols/common.rs @@ -17,7 +17,7 @@ pub const PRE_PRODUCTION_MAGIC: u64 = 1; /// Bitflag for client-side version of a known protocol /// # Example /// ``` -/// use pallas_miniprotocols::*; +/// use pallas_network::miniprotocols::*; /// let channel = PROTOCOL_CLIENT | PROTOCOL_N2N_HANDSHAKE; /// ``` pub const PROTOCOL_CLIENT: u16 = 0x0; @@ -25,7 +25,7 @@ pub const PROTOCOL_CLIENT: u16 = 0x0; /// Bitflag for server-side version of a known protocol /// # Example /// ``` -/// use pallas_miniprotocols::*; +/// use pallas_network::miniprotocols::*; /// let channel = PROTOCOL_SERVER | PROTOCOL_N2N_CHAIN_SYNC; /// ``` pub const PROTOCOL_SERVER: u16 = 0x8000; diff --git a/pallas-miniprotocols/src/handshake/README.md b/pallas-network/src/miniprotocols/handshake/README.md similarity index 100% rename from pallas-miniprotocols/src/handshake/README.md rename to pallas-network/src/miniprotocols/handshake/README.md diff --git a/pallas-miniprotocols/src/handshake/client.rs b/pallas-network/src/miniprotocols/handshake/client.rs similarity index 82% rename from pallas-miniprotocols/src/handshake/client.rs rename to pallas-network/src/miniprotocols/handshake/client.rs index eb1a0c8..cb15321 100644 --- a/pallas-miniprotocols/src/handshake/client.rs +++ b/pallas-network/src/miniprotocols/handshake/client.rs @@ -1,9 +1,9 @@ use pallas_codec::Fragment; -use pallas_multiplexer::agents::{Channel, ChannelBuffer}; use std::marker::PhantomData; use tracing::debug; use super::{Error, Message, RefuseReason, State, VersionNumber, VersionTable}; +use crate::plexer; #[derive(Debug)] pub enum Confirmation { @@ -11,18 +11,19 @@ pub enum Confirmation { Rejected(RefuseReason), } -pub struct Client(State, ChannelBuffer, PhantomData) -where - H: Channel; +pub struct Client(State, plexer::ChannelBuffer, PhantomData); -impl Client +impl Client where - H: Channel, D: std::fmt::Debug + Clone, Message: Fragment, { - pub fn new(channel: H) -> Self { - Self(State::Propose, ChannelBuffer::new(channel), PhantomData {}) + pub fn new(channel: plexer::AgentChannel) -> Self { + Self( + State::Propose, + plexer::ChannelBuffer::new(channel), + PhantomData {}, + ) } pub fn state(&self) -> &State { @@ -75,17 +76,14 @@ where 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::ChannelError)?; + self.1.send_msg_chunks(msg).await.map_err(Error::Plexer)?; Ok(()) } pub async fn recv_message(&mut self) -> Result, Error> { self.assert_agency_is_theirs()?; - let msg = self.1.recv_full_msg().await.map_err(Error::ChannelError)?; + let msg = self.1.recv_full_msg().await.map_err(Error::Plexer)?; self.assert_inbound_state(&msg)?; Ok(msg) @@ -124,11 +122,11 @@ where self.recv_while_confirm().await } - pub fn unwrap(self) -> H { + pub fn unwrap(self) -> plexer::AgentChannel { self.1.unwrap() } } -pub type N2NClient = Client; +pub type N2NClient = Client; -pub type N2CClient = Client; +pub type N2CClient = Client; diff --git a/pallas-miniprotocols/src/handshake/mod.rs b/pallas-network/src/miniprotocols/handshake/mod.rs similarity index 100% rename from pallas-miniprotocols/src/handshake/mod.rs rename to pallas-network/src/miniprotocols/handshake/mod.rs diff --git a/pallas-miniprotocols/src/handshake/n2c.rs b/pallas-network/src/miniprotocols/handshake/n2c.rs similarity index 100% rename from pallas-miniprotocols/src/handshake/n2c.rs rename to pallas-network/src/miniprotocols/handshake/n2c.rs diff --git a/pallas-miniprotocols/src/handshake/n2n.rs b/pallas-network/src/miniprotocols/handshake/n2n.rs similarity index 100% rename from pallas-miniprotocols/src/handshake/n2n.rs rename to pallas-network/src/miniprotocols/handshake/n2n.rs diff --git a/pallas-miniprotocols/src/handshake/protocol.rs b/pallas-network/src/miniprotocols/handshake/protocol.rs similarity index 98% rename from pallas-miniprotocols/src/handshake/protocol.rs rename to pallas-network/src/miniprotocols/handshake/protocol.rs index 05a0912..845c283 100644 --- a/pallas-miniprotocols/src/handshake/protocol.rs +++ b/pallas-network/src/miniprotocols/handshake/protocol.rs @@ -1,9 +1,10 @@ use itertools::Itertools; use pallas_codec::minicbor::{decode, encode, Decode, Decoder, Encode, Encoder}; -use pallas_multiplexer::agents::ChannelError; 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")] @@ -19,7 +20,7 @@ pub enum Error { InvalidOutbound, #[error("error while sending or receiving data through the channel")] - ChannelError(ChannelError), + Plexer(plexer::Error), } #[derive(Debug, Clone)] diff --git a/pallas-miniprotocols/src/handshake/server.rs b/pallas-network/src/miniprotocols/handshake/server.rs similarity index 80% rename from pallas-miniprotocols/src/handshake/server.rs rename to pallas-network/src/miniprotocols/handshake/server.rs index 5c619f2..e29968e 100644 --- a/pallas-miniprotocols/src/handshake/server.rs +++ b/pallas-network/src/miniprotocols/handshake/server.rs @@ -1,22 +1,23 @@ use std::marker::PhantomData; use pallas_codec::Fragment; -use pallas_multiplexer::agents::{Channel, ChannelBuffer}; use super::{Error, Message, RefuseReason, State, VersionNumber, VersionTable}; +use crate::plexer; -pub struct Server(State, ChannelBuffer, PhantomData) -where - H: Channel; +pub struct Server(State, plexer::ChannelBuffer, PhantomData); -impl Server +impl Server where - H: Channel, D: std::fmt::Debug + Clone, Message: Fragment, { - pub fn new(channel: H) -> Self { - Self(State::Propose, ChannelBuffer::new(channel), PhantomData {}) + pub fn new(channel: plexer::AgentChannel) -> Self { + Self( + State::Propose, + plexer::ChannelBuffer::new(channel), + PhantomData {}, + ) } pub fn state(&self) -> &State { @@ -65,17 +66,14 @@ where 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::ChannelError)?; + self.1.send_msg_chunks(msg).await.map_err(Error::Plexer)?; Ok(()) } pub async fn recv_message(&mut self) -> Result, Error> { self.assert_agency_is_theirs()?; - let msg = self.1.recv_full_msg().await.map_err(Error::ChannelError)?; + let msg = self.1.recv_full_msg().await.map_err(Error::Plexer)?; self.assert_inbound_state(&msg)?; Ok(msg) @@ -111,11 +109,11 @@ where Ok(()) } - pub fn unwrap(self) -> H { + pub fn unwrap(self) -> plexer::AgentChannel { self.1.unwrap() } } -pub type N2NServer = Server; +pub type N2NServer = Server; -pub type N2CServer = Server; +pub type N2CServer = Server; diff --git a/pallas-miniprotocols/src/localstate/client.rs b/pallas-network/src/miniprotocols/localstate/client.rs similarity index 88% rename from pallas-miniprotocols/src/localstate/client.rs rename to pallas-network/src/miniprotocols/localstate/client.rs index f84d724..21af2e6 100644 --- a/pallas-miniprotocols/src/localstate/client.rs +++ b/pallas-network/src/miniprotocols/localstate/client.rs @@ -2,13 +2,12 @@ use std::fmt::Debug; use pallas_codec::Fragment; -use crate::common::Point; - -use pallas_multiplexer::agents::{Channel, ChannelBuffer, ChannelError}; 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 { @@ -25,7 +24,7 @@ pub enum Error { #[error("failure acquiring point, too old")] AcquirePointTooOld, #[error("error while sending or receiving data through the channel")] - ChannelError(ChannelError), + Plexer(plexer::Error), } impl From for Error { @@ -37,20 +36,22 @@ impl From for Error { } } -pub struct Client(State, ChannelBuffer, PhantomData) +pub struct Client(State, plexer::ChannelBuffer, PhantomData) where - H: Channel, Q: Query, Message: Fragment; -impl Client +impl Client where - H: Channel, Q: Query, Message: Fragment, { - pub fn new(channel: H) -> Self { - Self(State::Idle, ChannelBuffer::new(channel), PhantomData {}) + pub fn new(channel: plexer::AgentChannel) -> Self { + Self( + State::Idle, + plexer::ChannelBuffer::new(channel), + PhantomData {}, + ) } pub fn state(&self) -> &State { @@ -108,17 +109,14 @@ where 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::ChannelError)?; + self.1.send_msg_chunks(msg).await.map_err(Error::Plexer)?; Ok(()) } pub async fn recv_message(&mut self) -> Result, Error> { self.assert_agency_is_theirs()?; - let msg = self.1.recv_full_msg().await.map_err(Error::ChannelError)?; + let msg = self.1.recv_full_msg().await.map_err(Error::Plexer)?; self.assert_inbound_state(&msg)?; Ok(msg) @@ -175,4 +173,4 @@ where } } -pub type ClientV10 = Client; +pub type ClientV10 = Client; diff --git a/pallas-miniprotocols/src/localstate/codec.rs b/pallas-network/src/miniprotocols/localstate/codec.rs similarity index 100% rename from pallas-miniprotocols/src/localstate/codec.rs rename to pallas-network/src/miniprotocols/localstate/codec.rs diff --git a/pallas-miniprotocols/src/localstate/mod.rs b/pallas-network/src/miniprotocols/localstate/mod.rs similarity index 100% rename from pallas-miniprotocols/src/localstate/mod.rs rename to pallas-network/src/miniprotocols/localstate/mod.rs diff --git a/pallas-miniprotocols/src/localstate/protocol.rs b/pallas-network/src/miniprotocols/localstate/protocol.rs similarity index 94% rename from pallas-miniprotocols/src/localstate/protocol.rs rename to pallas-network/src/miniprotocols/localstate/protocol.rs index a40b0ca..1c82106 100644 --- a/pallas-miniprotocols/src/localstate/protocol.rs +++ b/pallas-network/src/miniprotocols/localstate/protocol.rs @@ -1,6 +1,6 @@ use std::fmt::Debug; -use crate::common::Point; +use crate::miniprotocols::Point; #[derive(Debug, PartialEq, Eq, Clone)] pub enum State { diff --git a/pallas-miniprotocols/src/localstate/queries.rs b/pallas-network/src/miniprotocols/localstate/queries.rs similarity index 100% rename from pallas-miniprotocols/src/localstate/queries.rs rename to pallas-network/src/miniprotocols/localstate/queries.rs diff --git a/pallas-miniprotocols/src/lib.rs b/pallas-network/src/miniprotocols/mod.rs similarity index 81% rename from pallas-miniprotocols/src/lib.rs rename to pallas-network/src/miniprotocols/mod.rs index be3b7f2..f7bea7b 100644 --- a/pallas-miniprotocols/src/lib.rs +++ b/pallas-network/src/miniprotocols/mod.rs @@ -1,5 +1,4 @@ mod common; -mod machines; pub mod blockfetch; pub mod chainsync; @@ -9,4 +8,3 @@ pub mod txmonitor; pub mod txsubmission; pub use common::*; -pub use machines::*; diff --git a/pallas-network/src/miniprotocols/txmonitor/client.rs b/pallas-network/src/miniprotocols/txmonitor/client.rs new file mode 100644 index 0000000..79eaec9 --- /dev/null +++ b/pallas-network/src/miniprotocols/txmonitor/client.rs @@ -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 { + 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 { + match self.recv_message().await? { + Message::Acquired(slot) => { + self.0 = State::Acquired; + Ok(slot) + } + _ => Err(Error::InvalidInbound), + } + } + + pub async fn acquire(&mut self) -> Result { + 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 { + 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 { + 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, 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, 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 { + 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 { + 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(()) + } +} diff --git a/pallas-network/src/miniprotocols/txmonitor/codec.rs b/pallas-network/src/miniprotocols/txmonitor/codec.rs new file mode 100644 index 0000000..4faccbf --- /dev/null +++ b/pallas-network/src/miniprotocols/txmonitor/codec.rs @@ -0,0 +1,114 @@ +use super::protocol::*; +use pallas_codec::minicbor::{decode, encode, Decode, Encode, Encoder}; + +impl Encode<()> for Message { + fn encode( + &self, + e: &mut Encoder, + _ctx: &mut (), + ) -> Result<(), encode::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 { + 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 = 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")), + } + } +} diff --git a/pallas-network/src/miniprotocols/txmonitor/mod.rs b/pallas-network/src/miniprotocols/txmonitor/mod.rs new file mode 100644 index 0000000..a9eaa04 --- /dev/null +++ b/pallas-network/src/miniprotocols/txmonitor/mod.rs @@ -0,0 +1,7 @@ +mod client; +mod codec; +mod protocol; + +pub use client::*; +pub use codec::*; +pub use protocol::*; diff --git a/pallas-network/src/miniprotocols/txmonitor/protocol.rs b/pallas-network/src/miniprotocols/txmonitor/protocol.rs new file mode 100644 index 0000000..06c91bd --- /dev/null +++ b/pallas-network/src/miniprotocols/txmonitor/protocol.rs @@ -0,0 +1,34 @@ +pub type Slot = u64; +pub type TxId = String; +pub type Tx = Vec; + +#[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), + ResponseSizeAndCapacity(MempoolSizeAndCapacity), + Release, + Done, +} diff --git a/pallas-miniprotocols/src/txsubmission/README.md b/pallas-network/src/miniprotocols/txsubmission/README.md similarity index 100% rename from pallas-miniprotocols/src/txsubmission/README.md rename to pallas-network/src/miniprotocols/txsubmission/README.md diff --git a/pallas-miniprotocols/src/txsubmission/client.rs b/pallas-network/src/miniprotocols/txsubmission/client.rs similarity index 88% rename from pallas-miniprotocols/src/txsubmission/client.rs rename to pallas-network/src/miniprotocols/txsubmission/client.rs index 4b44c16..1d7577f 100644 --- a/pallas-miniprotocols/src/txsubmission/client.rs +++ b/pallas-network/src/miniprotocols/txsubmission/client.rs @@ -1,7 +1,7 @@ use std::marker::PhantomData; +use crate::plexer; use pallas_codec::Fragment; -use pallas_multiplexer::agents::{Channel, ChannelBuffer}; use super::{ protocol::{Error, Message, State, TxIdAndSize}, @@ -16,28 +16,26 @@ pub enum Request { /// A generic Ouroboros client for submitting a generic notion of "transactions" /// to another server -pub struct GenericClient( +pub struct GenericClient( State, - ChannelBuffer, + plexer::ChannelBuffer, PhantomData, PhantomData, ) where - H: Channel, Message: Fragment; /// A cardano specific instantiation of the ouroboros protocol -pub type Client = GenericClient; +pub type Client = GenericClient; -impl GenericClient +impl GenericClient where - H: Channel, Message: Fragment, { - pub fn new(channel: H) -> Self { + pub fn new(channel: plexer::AgentChannel) -> Self { Self( State::Init, - ChannelBuffer::new(channel), + plexer::ChannelBuffer::new(channel), PhantomData {}, PhantomData {}, ) @@ -95,17 +93,14 @@ where 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::ChannelError)?; + self.1.send_msg_chunks(msg).await.map_err(Error::Plexer)?; Ok(()) } pub async fn recv_message(&mut self) -> Result, Error> { self.assert_agency_is_theirs()?; - let msg = self.1.recv_full_msg().await.map_err(Error::ChannelError)?; + let msg = self.1.recv_full_msg().await.map_err(Error::Plexer)?; self.assert_inbound_state(&msg)?; Ok(msg) diff --git a/pallas-miniprotocols/src/txsubmission/codec.rs b/pallas-network/src/miniprotocols/txsubmission/codec.rs similarity index 100% rename from pallas-miniprotocols/src/txsubmission/codec.rs rename to pallas-network/src/miniprotocols/txsubmission/codec.rs diff --git a/pallas-miniprotocols/src/txsubmission/mod.rs b/pallas-network/src/miniprotocols/txsubmission/mod.rs similarity index 100% rename from pallas-miniprotocols/src/txsubmission/mod.rs rename to pallas-network/src/miniprotocols/txsubmission/mod.rs diff --git a/pallas-miniprotocols/src/txsubmission/protocol.rs b/pallas-network/src/miniprotocols/txsubmission/protocol.rs similarity index 94% rename from pallas-miniprotocols/src/txsubmission/protocol.rs rename to pallas-network/src/miniprotocols/txsubmission/protocol.rs index db38009..ba46a2b 100644 --- a/pallas-miniprotocols/src/txsubmission/protocol.rs +++ b/pallas-network/src/miniprotocols/txsubmission/protocol.rs @@ -1,6 +1,7 @@ -use pallas_multiplexer::agents::ChannelError; use thiserror::Error; +use crate::plexer; + #[derive(Debug, PartialEq, Eq, Clone)] pub enum State { Init, @@ -46,7 +47,7 @@ pub enum Error { AlreadyInitialized, #[error("error while sending or receiving data through the channel")] - ChannelError(ChannelError), + Plexer(plexer::Error), } #[derive(Debug)] diff --git a/pallas-miniprotocols/src/txsubmission/server.rs b/pallas-network/src/miniprotocols/txsubmission/server.rs similarity index 88% rename from pallas-miniprotocols/src/txsubmission/server.rs rename to pallas-network/src/miniprotocols/txsubmission/server.rs index 4cb3f8b..eed9050 100644 --- a/pallas-miniprotocols/src/txsubmission/server.rs +++ b/pallas-network/src/miniprotocols/txsubmission/server.rs @@ -1,12 +1,12 @@ use std::marker::PhantomData; use pallas_codec::Fragment; -use pallas_multiplexer::agents::{Channel, ChannelBuffer}; use super::{ protocol::{Blocking, Error, Message, State, TxCount, TxIdAndSize}, EraTxBody, EraTxId, }; +use crate::plexer; pub enum Reply { TxIds(Vec>), @@ -16,28 +16,26 @@ pub enum Reply { /// A generic implementation of an ouroboros server protocol ready to request /// and receive transactions from a client -pub struct GenericServer( +pub struct GenericServer( State, - ChannelBuffer, + plexer::ChannelBuffer, PhantomData, PhantomData, ) where - H: Channel, Message: Fragment; /// A Cardano specific server for the ouroboros TxSubmission protocol -pub type Server = GenericServer; +pub type Server = GenericServer; -impl GenericServer +impl GenericServer where - H: Channel, Message: Fragment, { - pub fn new(channel: H) -> Self { + pub fn new(channel: plexer::AgentChannel) -> Self { Self( State::Init, - ChannelBuffer::new(channel), + plexer::ChannelBuffer::new(channel), PhantomData {}, PhantomData {}, ) @@ -95,17 +93,14 @@ where 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::ChannelError)?; + self.1.send_msg_chunks(msg).await.map_err(Error::Plexer)?; Ok(()) } pub async fn recv_message(&mut self) -> Result, Error> { self.assert_agency_is_theirs()?; - let msg = self.1.recv_full_msg().await.map_err(Error::ChannelError)?; + let msg = self.1.recv_full_msg().await.map_err(Error::Plexer)?; self.assert_inbound_state(&msg)?; Ok(msg) diff --git a/pallas-network/src/plexer.rs b/pallas-network/src/plexer.rs new file mode 100644 index 0000000..b77a1f8 --- /dev/null +++ b/pallas-network/src/plexer.rs @@ -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 { + 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(buffer: &mut Vec) -> Result, 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, +} + +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(&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(&mut self) -> Result + 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::(&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::(&mut self.temp)? { + debug!("decoding done"); + return Ok(msg); + } + + trace!("not enough data"); + } + } + + pub fn unwrap(self) -> AgentChannel { + self.channel + } +} + +impl From 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); + } +} diff --git a/pallas-network/tests/plexer.rs b/pallas-network/tests/plexer.rs new file mode 100644 index 0000000..2f5f70c --- /dev/null +++ b/pallas-network/tests/plexer.rs @@ -0,0 +1,62 @@ +use std::net::{Ipv4Addr, SocketAddrV4}; + +use pallas_network::{bearer::Bearer, plexer::Plexer}; +use rand::{distributions::Uniform, Rng}; +use tokio::net::TcpListener; + +async fn setup_passive_muxer() -> Plexer { + let server = TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, P)) + .await + .unwrap(); + + println!("listening for connections on port {}", P); + + let (bearer, _) = Bearer::accept_tcp(server).await.unwrap(); + + Plexer::new(bearer) +} + +async fn setup_active_muxer() -> Plexer { + let bearer = Bearer::connect_tcp(SocketAddrV4::new(Ipv4Addr::LOCALHOST, P)) + .await + .unwrap(); + + println!("active plexer connected"); + + Plexer::new(bearer) +} + +fn random_payload(size: usize) -> Vec { + let range = Uniform::from(0..255); + rand::thread_rng().sample_iter(&range).take(size).collect() +} + +#[tokio::test] +async fn one_way_small_sequence_of_payloads() { + let passive = tokio::spawn(setup_passive_muxer::<50301>()); + + // HACK: a small sleep seems to be required for Github actions runner to + // formally expose the port + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + + let mut active = setup_active_muxer::<50301>().await; + + let mut passive = passive.await.unwrap(); + + let mut sender_channel = active.subscribe_client(3); + let mut receiver_channel = passive.subscribe_server(3); + + let passive_run = tokio::spawn(async move { passive.run().await }); + let active_run = tokio::spawn(async move { active.run().await }); + + for _ in 0..100 { + let payload = random_payload(50); + println!("sending chunk"); + sender_channel.enqueue_chunk(payload.clone()).await.unwrap(); + let received_payload = receiver_channel.dequeue_chunk().await.unwrap(); + assert_eq!(payload, received_payload); + } + + passive_run.abort(); + active_run.abort(); +} diff --git a/pallas-network/tests/protocols.rs b/pallas-network/tests/protocols.rs new file mode 100644 index 0000000..8f6d922 --- /dev/null +++ b/pallas-network/tests/protocols.rs @@ -0,0 +1,146 @@ +use pallas_network::facades::PeerClient; +use pallas_network::miniprotocols::{ + blockfetch, + chainsync::{self, NextResponse}, + Point, +}; + +#[tokio::test] +#[ignore] +pub async fn chainsync_history_happy_path() { + let mut peer = PeerClient::connect("preview-node.world.dev.cardano.org:30002", 2) + .await + .unwrap(); + + let client = peer.chainsync(); + + let known_point = Point::Specific( + 1654413, + hex::decode("7de1f036df5a133ce68a82877d14354d0ba6de7625ab918e75f3e2ecb29771c2").unwrap(), + ); + + let (point, _) = client + .find_intersect(vec![known_point.clone()]) + .await + .unwrap(); + + println!("{:?}", point); + + assert!(matches!(client.state(), chainsync::State::Idle)); + + match point { + Some(point) => assert_eq!(point, known_point), + None => panic!("expected point"), + } + + let next = client.request_next().await.unwrap(); + + match next { + NextResponse::RollBackward(point, _) => assert_eq!(point, known_point), + _ => panic!("expected rollback"), + } + + assert!(matches!(client.state(), chainsync::State::Idle)); + + for _ in 0..10 { + let next = client.request_next().await.unwrap(); + + match next { + NextResponse::RollForward(_, _) => (), + _ => panic!("expected roll-forward"), + } + + assert!(matches!(client.state(), chainsync::State::Idle)); + } + + client.send_done().await.unwrap(); + + assert!(matches!(client.state(), chainsync::State::Done)); +} + +#[tokio::test] +#[ignore] +pub async fn chainsync_tip_happy_path() { + let mut peer = PeerClient::connect("preview-node.world.dev.cardano.org:30002", 2) + .await + .unwrap(); + + let client = peer.chainsync(); + + client.intersect_tip().await.unwrap(); + + assert!(matches!(client.state(), chainsync::State::Idle)); + + let next = client.request_next().await.unwrap(); + + assert!(matches!(next, NextResponse::RollBackward(..))); + + let mut await_count = 0; + + for _ in 0..4 { + let next = if client.has_agency() { + client.request_next().await.unwrap() + } else { + await_count += 1; + client.recv_while_must_reply().await.unwrap() + }; + + match next { + NextResponse::RollForward(_, _) => (), + NextResponse::Await => (), + _ => panic!("expected roll-forward or await"), + } + } + + assert!(await_count > 0, "tip was never reached"); + + client.send_done().await.unwrap(); + + assert!(matches!(client.state(), chainsync::State::Done)); +} + +#[tokio::test] +#[ignore] +pub async fn blockfetch_happy_path() { + let mut peer = PeerClient::connect("preview-node.world.dev.cardano.org:30002", 2) + .await + .unwrap(); + + let client = peer.blockfetch(); + + let known_point = Point::Specific( + 1654413, + hex::decode("7de1f036df5a133ce68a82877d14354d0ba6de7625ab918e75f3e2ecb29771c2").unwrap(), + ); + + let range_ok = client + .request_range((known_point.clone(), known_point)) + .await; + + assert!(matches!(client.state(), blockfetch::State::Streaming)); + + println!("streaming..."); + + assert!(matches!(range_ok, Ok(_))); + + for _ in 0..1 { + let next = client.recv_while_streaming().await.unwrap(); + + match next { + Some(body) => assert_eq!(body.len(), 3251), + _ => panic!("expected block body"), + } + + assert!(matches!(client.state(), blockfetch::State::Streaming)); + } + + let next = client.recv_while_streaming().await.unwrap(); + + assert!(matches!(next, None)); + + client.send_done().await.unwrap(); + + assert!(matches!(client.state(), blockfetch::State::Done)); +} + +// TODO: redo txsubmission client test diff --git a/pallas-upstream/Cargo.toml b/pallas-upstream/Cargo.toml index 18be522..16aba42 100644 --- a/pallas-upstream/Cargo.toml +++ b/pallas-upstream/Cargo.toml @@ -11,18 +11,16 @@ readme = "README.md" authors = ["Santiago Carmuega "] [dependencies] +async-trait = "0.1.68" byteorder = "1.4.3" gasket = { git = "https://github.com/construkts/gasket-rs" } # gasket = { path = "../../../construkts/gasket-rs" } hex = "0.4.3" -mio = { version = "0.8.6", features = ["net", "os-poll"] } # gasket = { version = "0.1.0", path = "../../../construkts/gasket-rs" } pallas-codec = { version = "0.18.0", path = "../pallas-codec" } pallas-crypto = { version = "0.18.0", path = "../pallas-crypto" } -pallas-miniprotocols = { version = "0.18.0", path = "../pallas-miniprotocols" } -pallas-multiplexer = { version = "0.18.0", path = "../pallas-multiplexer" } +pallas-network = { version = "0.18.0", path = "../pallas-network" } pallas-traverse = { version = "0.18.0", path = "../pallas-traverse" } -rayon = "1.7.0" serde = { version = "1.0.154", features = ["derive"] } thiserror = "1.0.31" tokio = { version = "1", features = ["net", "macros", "io-util"] } diff --git a/pallas-upstream/src/api.rs b/pallas-upstream/src/api.rs deleted file mode 100644 index 1193875..0000000 --- a/pallas-upstream/src/api.rs +++ /dev/null @@ -1,126 +0,0 @@ -pub use crate::framework::{BlockFetchEvent, Cursor, DownstreamPort, Intersection}; - -pub mod n2n { - use crate::{blockfetch, chainsync, framework::*, plexer}; - - use gasket::{ - messaging::{SendAdapter, SendPort}, - runtime::Tether, - }; - - pub struct Runtime { - pub plexer_tether: Tether, - pub chainsync_tether: Tether, - pub blockfetch_tether: Tether, - } - - pub struct Bootstrapper - where - A: SendAdapter, - C: Cursor, - { - cursor: C, - peer_address: String, - network_magic: u64, - output: super::DownstreamPort, - } - - impl Bootstrapper - where - A: SendAdapter + 'static, - C: Cursor + 'static, - { - pub fn new(cursor: C, peer_address: String, network_magic: u64) -> Self { - Bootstrapper { - cursor, - peer_address, - network_magic, - output: Default::default(), - } - } - - pub fn connect_output(&mut self, adapter: A) { - self.output.connect(adapter); - } - - pub fn spawn(self) -> Result { - /* - TODO: this is how we envision the setup of complex pipelines leveraging Rust macros: - - pipeline!( - plexer = plexer::Worker::new(xx), - chainsync = chainsync::Worker::new(yy), - blockfetch = blockfetch::Worker::new(yy), - reducer = reducer::Worker::new(yy), - plexer.demux2 => chainsync.demux2, - plexer.demux3 => blockfetch.demux3, - chainsync.mux2 + blockfetch.mux3 => plexer.mux, - chainsync.downstream => blockfetch.upstream, - blockfetch.downstream => reducer.upstream, - ); - - The above snippet would replace the rest of the code in this function, which is just a more verbose, manual way of saying the same thing. - */ - - let mut mux_input = MuxInputPort::default(); - - let mut demux2_out = DemuxOutputPort::default(); - let mut demux2_in = DemuxInputPort::default(); - gasket::messaging::tokio::connect_ports(&mut demux2_out, &mut demux2_in, 1000); - - let mut demux3_out = DemuxOutputPort::default(); - let mut demux3_in = DemuxInputPort::default(); - gasket::messaging::tokio::connect_ports(&mut demux3_out, &mut demux3_in, 1000); - - let mut mux2_out = MuxOutputPort::default(); - let mut mux3_out = MuxOutputPort::default(); - gasket::messaging::tokio::funnel_ports( - vec![&mut mux2_out, &mut mux3_out], - &mut mux_input, - 1000, - ); - - let mut chainsync_downstream = chainsync::DownstreamPort::default(); - let mut blockfetch_upstream = blockfetch::UpstreamPort::default(); - gasket::messaging::tokio::connect_ports( - &mut chainsync_downstream, - &mut blockfetch_upstream, - 100, - ); - - let plexer_tether = gasket::runtime::spawn_stage( - plexer::Worker::new( - self.peer_address, - self.network_magic, - mux_input, - Some(demux2_out), - Some(demux3_out), - ), - gasket::runtime::Policy::default(), - Some("plexer"), - ); - - let channel2 = ProtocolChannel(2, mux2_out, demux2_in); - - let chainsync_tether = gasket::runtime::spawn_stage( - chainsync::Worker::new(self.cursor, channel2, chainsync_downstream), - gasket::runtime::Policy::default(), - Some("chainsync"), - ); - - let channel3 = ProtocolChannel(3, mux3_out, demux3_in); - - let blockfetch_tether = gasket::runtime::spawn_stage( - blockfetch::Worker::new(channel3, blockfetch_upstream, self.output), - gasket::runtime::Policy::default(), - Some("blockfetch"), - ); - - Ok(Runtime { - plexer_tether, - chainsync_tether, - blockfetch_tether, - }) - } - } -} diff --git a/pallas-upstream/src/blockfetch.rs b/pallas-upstream/src/blockfetch.rs deleted file mode 100644 index 3df0b27..0000000 --- a/pallas-upstream/src/blockfetch.rs +++ /dev/null @@ -1,106 +0,0 @@ -use gasket::messaging::SendAdapter; -use gasket::runtime::WorkSchedule; -use tracing::{error, info, instrument}; - -use pallas_crypto::hash::Hash; -use pallas_miniprotocols::blockfetch; -use pallas_miniprotocols::Point; - -use crate::framework::*; - -pub type UpstreamPort = gasket::messaging::tokio::InputPort; -pub type OuroborosClient = blockfetch::Client; - -pub struct Worker -where - T: Send + Sync, -{ - client: OuroborosClient, - upstream: UpstreamPort, - downstream: DownstreamPort, - block_count: gasket::metrics::Counter, -} - -impl Worker -where - T: Send + Sync, -{ - pub fn new( - plexer: ProtocolChannel, - upstream: UpstreamPort, - downstream: DownstreamPort, - ) -> Self { - let client = OuroborosClient::new(plexer); - - Self { - client, - upstream, - downstream, - block_count: Default::default(), - } - } - - #[instrument(skip(self), fields(slot, %hash))] - async fn fetch_block( - &mut self, - slot: u64, - hash: &Hash<32>, - ) -> Result, gasket::error::Error> { - info!("fetching block"); - - match self - .client - .fetch_single(Point::Specific(slot, hash.to_vec())) - .await - { - Ok(x) => { - info!("block fetch succeeded"); - Ok(x) - } - Err(blockfetch::Error::ChannelError(x)) => { - error!("plexer channel error: {}", x); - Err(gasket::error::Error::RetryableError) - } - Err(x) => { - error!("unrecoverable block fetch error: {}", x); - Err(gasket::error::Error::WorkPanic) - } - } - } -} - -impl gasket::runtime::Worker for Worker -where - A: SendAdapter, -{ - fn metrics(&self) -> gasket::metrics::Registry { - gasket::metrics::Builder::new() - .with_counter("fetched_blocks", &self.block_count) - .build() - } - - type WorkUnit = ChainSyncEvent; - - async fn schedule(&mut self) -> gasket::runtime::ScheduleResult { - let msg = self.upstream.recv().await?; - info!("scheduling block betch"); - Ok(WorkSchedule::Unit(msg.payload)) - } - - async fn execute(&mut self, unit: &Self::WorkUnit) -> Result<(), gasket::error::Error> { - let output = match unit { - ChainSyncEvent::RollForward(s, h) => { - let body = self.fetch_block(*s, h).await?; - - self.block_count.inc(1); - - BlockFetchEvent::RollForward(*s, h.clone(), body) - } - ChainSyncEvent::Rollback(x) => BlockFetchEvent::Rollback(x.clone()), - }; - - self.downstream.send(output.into()).await?; - - Ok(()) - } -} diff --git a/pallas-upstream/src/chainsync.rs b/pallas-upstream/src/chainsync.rs deleted file mode 100644 index 4db09ba..0000000 --- a/pallas-upstream/src/chainsync.rs +++ /dev/null @@ -1,176 +0,0 @@ -use gasket::error::AsWorkError; -use tracing::{debug, info}; - -use pallas_miniprotocols::chainsync::{HeaderContent, NextResponse, Tip}; -use pallas_miniprotocols::{chainsync, Point}; -use pallas_traverse::MultiEraHeader; - -use crate::framework::*; - -fn to_traverse(header: &chainsync::HeaderContent) -> Result, Error> { - let out = match header.byron_prefix { - Some((subtag, _)) => MultiEraHeader::decode(header.variant, Some(subtag), &header.cbor), - None => MultiEraHeader::decode(header.variant, None, &header.cbor), - }; - - out.map_err(Error::parse) -} - -pub type DownstreamPort = gasket::messaging::tokio::OutputPort; - -pub type OuroborosClient = chainsync::N2NClient; - -pub struct Worker -where - C: Cursor, -{ - chain_cursor: C, - client: OuroborosClient, - downstream: DownstreamPort, - block_count: gasket::metrics::Counter, - chain_tip: gasket::metrics::Gauge, -} - -impl Worker -where - C: Cursor, -{ - pub fn new(chain_cursor: C, plexer: ProtocolChannel, downstream: DownstreamPort) -> Self { - let client = OuroborosClient::new(plexer); - - Self { - chain_cursor, - client, - downstream, - block_count: Default::default(), - chain_tip: Default::default(), - } - } - - fn notify_tip(&self, tip: Tip) { - self.chain_tip.set(tip.0.slot_or_default() as i64); - } - - async fn intersect(&mut self) -> Result<(), gasket::error::Error> { - let value = self.chain_cursor.intersection(); - - let intersect = match value { - Intersection::Origin => { - info!("intersecting origin"); - self.client.intersect_origin().await.or_restart()?.into() - } - Intersection::Tip => { - info!("intersecting tip"); - self.client.intersect_tip().await.or_restart()?.into() - } - Intersection::Breadcrumbs(points) => { - info!("intersecting breadcrumbs"); - let (point, tip) = self - .client - .find_intersect(Vec::from(points)) - .await - .or_restart()?; - - self.notify_tip(tip); - - point - } - }; - - info!(?intersect, "intersected"); - - Ok(()) - } - - async fn process_next( - &mut self, - next: NextResponse, - ) -> Result<(), gasket::error::Error> { - match next { - chainsync::NextResponse::RollForward(header, tip) => { - let header = to_traverse(&header).or_panic()?; - - debug!(slot = header.slot(), hash = %header.hash(), "chain sync roll forward"); - - self.downstream - .send(ChainSyncEvent::RollForward(header.slot(), header.hash()).into()) - .await?; - - self.notify_tip(tip); - - Ok(()) - } - chainsync::NextResponse::RollBackward(point, tip) => { - match &point { - Point::Origin => debug!("rollback to origin"), - Point::Specific(slot, _) => debug!(slot, "rollback"), - }; - - self.downstream - .send(ChainSyncEvent::Rollback(point).into()) - .await?; - - self.notify_tip(tip); - - Ok(()) - } - chainsync::NextResponse::Await => { - info!("chain-sync reached the tip of the chain"); - Ok(()) - } - } - } - - async fn request_next(&mut self) -> Result<(), gasket::error::Error> { - info!("requesting next block"); - let next = self.client.request_next().await.or_restart()?; - self.process_next(next).await - } - - async fn await_next(&mut self) -> Result<(), gasket::error::Error> { - info!("awaiting next block (blocking)"); - let next = self.client.recv_while_must_reply().await.or_restart()?; - self.process_next(next).await - } -} - -pub enum WorkUnit { - Intersect, - RequestNext, - AwaitNext, -} - -impl gasket::runtime::Worker for Worker -where - C: Cursor + Sync + Send, -{ - type WorkUnit = WorkUnit; - - fn metrics(&self) -> gasket::metrics::Registry { - gasket::metrics::Builder::new() - .with_counter("received_blocks", &self.block_count) - .with_gauge("chain_tip", &self.chain_tip) - .build() - } - - async fn bootstrap(&mut self) -> gasket::runtime::ScheduleResult { - Ok(gasket::runtime::WorkSchedule::Unit(WorkUnit::Intersect)) - } - - async fn schedule(&mut self) -> gasket::runtime::ScheduleResult { - match self.client.has_agency() { - true => Ok(gasket::runtime::WorkSchedule::Unit(WorkUnit::RequestNext)), - false => Ok(gasket::runtime::WorkSchedule::Unit(WorkUnit::AwaitNext)), - } - } - - async fn execute(&mut self, unit: &Self::WorkUnit) -> Result<(), gasket::error::Error> { - match unit { - WorkUnit::Intersect => self.intersect().await?, - WorkUnit::RequestNext => self.request_next().await?, - WorkUnit::AwaitNext => self.await_next().await?, - }; - - Ok(()) - } -} diff --git a/pallas-upstream/src/framework.rs b/pallas-upstream/src/framework.rs index 9c02397..a81d229 100644 --- a/pallas-upstream/src/framework.rs +++ b/pallas-upstream/src/framework.rs @@ -1,8 +1,5 @@ use pallas_crypto::hash::Hash; -use pallas_miniprotocols::Point; -use pallas_multiplexer as multiplexer; -use thiserror::Error; -use tracing::{error, trace}; +use pallas_network::miniprotocols::Point; pub type BlockSlot = u64; pub type BlockHash = Hash<32>; @@ -20,112 +17,10 @@ pub trait Cursor: Send + Sync { } #[derive(Debug, Clone)] -pub enum ChainSyncEvent { - RollForward(BlockSlot, BlockHash), - Rollback(Point), -} - -#[derive(Debug, Clone)] -pub enum BlockFetchEvent { +pub enum UpstreamEvent { RollForward(BlockSlot, BlockHash, RawBlock), Rollback(Point), } -// ports used by plexer -pub type MuxOutputPort = gasket::messaging::tokio::OutputPort<(u16, multiplexer::Payload)>; -pub type DemuxInputPort = gasket::messaging::tokio::InputPort; - -// ports used by mini-protocols -pub type MuxInputPort = gasket::messaging::tokio::InputPort<(u16, multiplexer::Payload)>; -pub type DemuxOutputPort = gasket::messaging::tokio::OutputPort; - // final output port -pub type DownstreamPort = gasket::messaging::OutputPort; - -pub struct ProtocolChannel(pub u16, pub MuxOutputPort, pub DemuxInputPort); - -impl multiplexer::agents::Channel for ProtocolChannel { - async fn enqueue_chunk( - &mut self, - payload: multiplexer::Payload, - ) -> Result<(), multiplexer::agents::ChannelError> { - trace!( - protocol = self.0, - payload = hex::encode(&payload), - "enqueing" - ); - - let res = self - .1 - .send(gasket::messaging::Message::from((self.0, payload))) - .await; - - match res { - Ok(_) => Ok(()), - Err(error) => { - error!(?error, "enqueue chunk failed"); - Err(multiplexer::agents::ChannelError::NotConnected(None)) - } - } - } - - async fn dequeue_chunk( - &mut self, - ) -> Result { - let res = self.2.recv().await; - - match res { - Ok(msg) => Ok(msg.payload), - Err(error) => { - error!(?error, "dequeue chunk failed"); - Err(multiplexer::agents::ChannelError::NotConnected(None)) - } - } - } -} - -#[derive(Error, Debug)] -pub enum Error { - #[error("{0}")] - Client(String), - - #[error("{0}")] - Parse(String), - - #[error("{0}")] - Server(String), - - #[error("{0}")] - Message(String), - - #[error("{0}")] - Custom(String), -} - -impl Error { - pub fn client(error: impl ToString) -> Error { - Error::Client(error.to_string()) - } - - pub fn parse(error: impl ToString) -> Error { - Error::Parse(error.to_string()) - } - - pub fn server(error: impl ToString) -> Error { - Error::Server(error.to_string()) - } - - pub fn message(error: impl ToString) -> Error { - Error::Message(error.to_string()) - } - - pub fn custom(error: impl Into>) -> Error { - Error::Custom(format!("{}", error.into())) - } -} - -impl From> for Error { - fn from(err: Box) -> Self { - Error::custom(err) - } -} +pub type DownstreamPort = gasket::messaging::OutputPort; diff --git a/pallas-upstream/src/lib.rs b/pallas-upstream/src/lib.rs index f0a274c..bf7234b 100644 --- a/pallas-upstream/src/lib.rs +++ b/pallas-upstream/src/lib.rs @@ -1,10 +1,8 @@ -#![feature(async_fn_in_trait)] - -pub(crate) mod blockfetch; -pub(crate) mod chainsync; pub(crate) mod framework; -pub(crate) mod plexer; +pub(crate) mod worker; -mod api; +pub use crate::framework::{Cursor, DownstreamPort, Intersection, UpstreamEvent}; -pub use api::*; +pub mod n2n { + pub use crate::worker::Worker; +} diff --git a/pallas-upstream/src/plexer.rs b/pallas-upstream/src/plexer.rs deleted file mode 100644 index b0f51b9..0000000 --- a/pallas-upstream/src/plexer.rs +++ /dev/null @@ -1,436 +0,0 @@ -use std::future::ready; - -use byteorder::{ByteOrder, NetworkEndian}; -use gasket::error::AsWorkError; -use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf, ReadHalf, WriteHalf}; -use tokio::net::{TcpStream, ToSocketAddrs}; -use tokio::select; -use tokio::time::Instant; -use tracing::{debug, error, info, trace, warn}; - -use pallas_miniprotocols::handshake; - -use crate::framework::*; - -const HEADER_LEN: usize = 8; - -pub type Timestamp = u32; - -pub type Payload = Vec; - -pub type Protocol = u16; - -/// A `Header` struct represents an Ouroboros segment header. -/// -/// # Examples -/// -/// Converting a `Header` to bytes: -/// -/// ``` -/// use byteorder::{BigEndian, ByteOrder}; -/// use pallas_upstream::plexer::Header; -/// -/// let header = Header { -/// protocol: 0x01, -/// timestamp: 1619804871, -/// payload_len: 42, -/// }; -/// -/// let header_bytes: [u8; 8] = header.into(); -/// assert_eq!(header_bytes, [97, 75, 168, 15, 128, 1, 0, 42]); -/// ``` -/// -/// Converting bytes to a `Header`: -/// -/// ``` -/// use byteorder::{BigEndian, ByteOrder}; -/// use pallas_upstream::plexer::Header; -/// -/// let bytes = [97, 75, 168, 15, 128, 1, 0, 42]; -/// let header: Header = (&bytes[..]).into(); -/// -/// assert_eq!(header.protocol, 0x01); -/// assert_eq!(header.timestamp, 1619804871); -/// assert_eq!(header.payload_len, 42); -/// ``` -#[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]) ^ 0x8000; - let payload_len = NetworkEndian::read_u16(&value[6..8]); - - Self { - timestamp, - protocol, - payload_len, - } - } -} - -impl From
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, -} - -use tokio::io::{AsyncReadExt, AsyncWriteExt}; - -struct AsyncBearer(OwnedReadHalf, OwnedWriteHalf, Instant); - -impl AsyncBearer { - async fn connect_tcp(addr: impl ToSocketAddrs) -> Result { - let stream = TcpStream::connect(addr).await?; - let (read, write) = stream.into_split(); - - Ok(Self(read, write, Instant::now())) - } -} - -impl AsyncBearer { - async fn readable(&self) -> tokio::io::Result<()> { - self.0.readable().await - } - - /// Peek the available data in search for a frame header - async fn peek_header(&mut self) -> tokio::io::Result> { - let mut buf = [0u8; HEADER_LEN]; - let len = self.0.peek(&mut buf).await?; - - if len < HEADER_LEN { - return Ok(None); - } - - Ok(Some(Header::from(buf.as_slice()))) - } - - async fn has_payload(&mut self, payload_len: usize) -> tokio::io::Result { - let segment_size = HEADER_LEN + payload_len; - let mut buf = vec![0u8; segment_size]; - - let available = self.0.peek(&mut buf).await?; - - return Ok(available >= segment_size); - } - - /// Peeks the bearer to see if a full segment is available to be read - async fn has_segment(&mut self) -> std::io::Result { - let header = match self.peek_header().await? { - Some(x) => x, - None => return Ok(false), - }; - - self.has_payload(header.payload_len as usize).await - } - - /// Reads a full segment from the bearer while consuming the bytes - /// - /// This function is NOT "cancel safe", meaning that it shouldn't be used - /// inside the context of a select!. Only call this function once you're - /// sure that you can await until all the required bytes are available. - async fn read_segment(&mut self) -> tokio::io::Result<(Protocol, Payload)> { - let mut buf = [0u8; HEADER_LEN]; - self.0.read_exact(&mut buf).await?; - let header = Header::from(buf.as_slice()); - - // TODO: assert any business invariants regarding timestamp from the other party - - let mut payload = vec![0u8; header.payload_len as usize]; - self.0.read_exact(&mut payload).await?; - - Ok((header.protocol, payload)) - } - - async fn write_segment(&mut self, protocol: u16, payload: &[u8]) -> Result<(), std::io::Error> { - let header = Header { - protocol, - timestamp: self.2.elapsed().as_micros() as u32, - payload_len: payload.len() as u16, - }; - - let buf: [u8; 8] = header.into(); - self.1.write_all(&buf).await?; - - self.1.write_all(&payload).await?; - - Ok(()) - } -} - -pub struct AsyncAgentChannel( - Protocol, - tokio::sync::mpsc::Sender<(Protocol, Payload)>, - tokio::sync::broadcast::Receiver<(Protocol, Payload)>, -); - -impl pallas_multiplexer::agents::Channel for AsyncAgentChannel { - async fn enqueue_chunk( - &mut self, - chunk: pallas_multiplexer::Payload, - ) -> Result<(), pallas_multiplexer::agents::ChannelError> { - let res = self.1.send((self.0, chunk)).await; - - res.map_err(|err| pallas_multiplexer::agents::ChannelError::NotConnected(Some(err.0 .1))) - } - - async fn dequeue_chunk( - &mut self, - ) -> Result { - loop { - let (protocol, payload) = self - .2 - .recv() - .await - .map_err(|err| pallas_multiplexer::agents::ChannelError::NotConnected(None))?; - - if protocol == self.0 { - break Ok(payload); - } - } - } -} - -pub type AsyncIngress = ( - tokio::sync::mpsc::Sender<(Protocol, Payload)>, - tokio::sync::mpsc::Receiver<(Protocol, Payload)>, -); -pub type AsyncEgress = ( - tokio::sync::broadcast::Sender<(Protocol, Payload)>, - tokio::sync::broadcast::Receiver<(Protocol, Payload)>, -); - -struct AsyncPlexer { - bearer: AsyncBearer, - ingress: AsyncIngress, - egress: AsyncEgress, -} - -impl AsyncPlexer { - pub fn new(bearer: AsyncBearer) -> Self { - Self { - 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, &msg.1).await?; - - Ok(()) - } - - async fn demux(&mut self) -> tokio::io::Result<()> { - let (protocol, payload) = self.bearer.read_segment().await?; - - self.egress.0.send((protocol, payload)).unwrap(); - - Ok(()) - } - - pub fn subscribe(&mut self, protocol: Protocol) -> AsyncAgentChannel { - let agent_tx = self.ingress.0.clone(); - let agent_rx = self.egress.0.subscribe(); - - AsyncAgentChannel(protocol, agent_tx, agent_rx) - } - - pub async fn run(&mut self) -> tokio::io::Result<()> { - loop { - select! { - Ok(_) = self.bearer.readable() => { - if let Ok(true) = self.bearer.has_segment().await { - trace!("demux selected"); - self.demux().await? - } - }, - Some(x) = self.ingress.1.recv() => { - trace!("mux selected"); - self.mux(x).await? - }, - } - } - } -} - -impl From for AsyncPlexer { - fn from(value: AsyncBearer) -> Self { - Self::new(value) - } -} - -impl From for AsyncBearer { - fn from(value: AsyncPlexer) -> Self { - value.bearer - } -} - -async fn handshake( - plexer: &mut AsyncPlexer, - network_magic: u64, -) -> Result<(), gasket::error::Error> { - info!("executing handshake"); - - let channel0 = plexer.subscribe(0); - let versions = handshake::n2n::VersionTable::v7_and_above(network_magic); - let mut client = handshake::Client::new(channel0); - - //let p = tokio::spawn(plexer.run()); - //let output = client.handshake(versions).or_restart()?; - - let output = select! { - x = client.handshake(versions) => x.or_restart()?, - x = plexer.run() => { - match x.or_restart() { - Err(x) => return Err(x), - _ => unreachable!(), - }; - }, - }; - - debug!("handshake output: {:?}", output); - //p.abort(); - - match output { - handshake::Confirmation::Accepted(version, _) => { - info!(version, "connected to upstream peer"); - Ok(()) - } - _ => { - error!("couldn't agree on handshake version"); - Err(gasket::error::Error::WorkPanic) - } - } -} - -pub struct Worker { - peer_address: String, - network_magic: u64, - bearer: Option, - mux_input: MuxInputPort, - channel2_out: Option, - channel3_out: Option, - ops_count: gasket::metrics::Counter, -} - -impl Worker { - pub fn new( - peer_address: String, - network_magic: u64, - mux_input: MuxInputPort, - channel2_out: Option, - channel3_out: Option, - ) -> Self { - Self { - peer_address, - network_magic, - channel2_out, - channel3_out, - mux_input, - bearer: None, - ops_count: Default::default(), - } - } -} - -pub enum WorkUnit { - Connect, - Mux((u16, Vec)), - Demux, -} - -impl gasket::runtime::Worker for Worker { - type WorkUnit = WorkUnit; - - fn metrics(&self) -> gasket::metrics::Registry { - // TODO: define networking metrics (bytes in / out, etc) - gasket::metrics::Builder::new() - .with_counter("ops_count", &self.ops_count) - .build() - } - - async fn bootstrap(&mut self) -> gasket::runtime::ScheduleResult { - Ok(gasket::runtime::WorkSchedule::Unit(WorkUnit::Connect)) - } - - async fn schedule(&mut self) -> gasket::runtime::ScheduleResult { - let bearer = self.bearer.as_mut().unwrap(); - trace!("selecting"); - select! { - Ok(msg) = self.mux_input.recv() => { Ok(gasket::runtime::WorkSchedule::Unit(WorkUnit::Mux(msg.payload))) } - Ok(true) = bearer.has_segment() => Ok(gasket::runtime::WorkSchedule::Unit(WorkUnit::Demux)), - _ = tokio::time::sleep(tokio::time::Duration::from_secs(5)) => Ok(gasket::runtime::WorkSchedule::Idle), - } - } - - async fn execute(&mut self, unit: &Self::WorkUnit) -> Result<(), gasket::error::Error> { - match unit { - WorkUnit::Connect => { - debug!("connecting"); - let bearer = AsyncBearer::connect_tcp(&self.peer_address) - .await - .or_retry()?; - - let mut plexer = bearer.into(); - - handshake(&mut plexer, self.network_magic).await?; - - self.bearer = Some(plexer.into()); - } - WorkUnit::Mux(x) => { - trace!("muxing"); - self.bearer - .as_mut() - .unwrap() - .write_segment(x.0, &x.1) - .await - .or_restart()?; - } - WorkUnit::Demux => { - trace!("demuxing"); - - let (protocol, payload) = self - .bearer - .as_mut() - .unwrap() - .read_segment() - .await - .or_restart()?; - - match protocol { - 2 => { - if let Some(channel) = &mut self.channel2_out { - channel.send(payload.into()).await?; - trace!("sent protocol 2 msg"); - } - } - 3 => { - if let Some(channel) = &mut self.channel3_out { - channel.send(payload.into()).await?; - trace!("sent protocol 3 msg"); - } - } - x => warn!("trying to demux unexpected protocol {x}"), - } - } - }; - - Ok(()) - } -} diff --git a/pallas-upstream/src/worker.rs b/pallas-upstream/src/worker.rs new file mode 100644 index 0000000..a050cdf --- /dev/null +++ b/pallas-upstream/src/worker.rs @@ -0,0 +1,197 @@ +use gasket::error::AsWorkError; +use tracing::{debug, info}; + +use pallas_network::facades::PeerClient; +use pallas_network::miniprotocols::chainsync::{self, HeaderContent, NextResponse, Tip}; +use pallas_network::miniprotocols::Point; +use pallas_traverse::MultiEraHeader; + +use crate::framework::*; + +fn to_traverse(header: &HeaderContent) -> Result, gasket::error::Error> { + let out = match header.byron_prefix { + Some((subtag, _)) => MultiEraHeader::decode(header.variant, Some(subtag), &header.cbor), + None => MultiEraHeader::decode(header.variant, None, &header.cbor), + }; + + out.or_panic() +} + +pub type DownstreamPort = gasket::messaging::tokio::OutputPort; + +pub struct Worker +where + C: Cursor, +{ + peer_address: String, + network_magic: u64, + chain_cursor: C, + peer_session: Option, + downstream: DownstreamPort, + block_count: gasket::metrics::Counter, + chain_tip: gasket::metrics::Gauge, +} + +impl Worker +where + C: Cursor, +{ + pub fn new( + peer_address: String, + network_magic: u64, + chain_cursor: C, + downstream: DownstreamPort, + ) -> Self { + Self { + peer_address, + network_magic, + chain_cursor, + downstream, + peer_session: None, + block_count: Default::default(), + chain_tip: Default::default(), + } + } + + fn notify_tip(&self, tip: &Tip) { + self.chain_tip.set(tip.0.slot_or_default() as i64); + } + + async fn intersect(&mut self) -> Result<(), gasket::error::Error> { + let value = self.chain_cursor.intersection(); + + let chainsync = self.peer_session.as_mut().unwrap().chainsync(); + + let intersect = match value { + Intersection::Origin => { + info!("intersecting origin"); + chainsync.intersect_origin().await.or_restart()?.into() + } + Intersection::Tip => { + info!("intersecting tip"); + chainsync.intersect_tip().await.or_restart()?.into() + } + Intersection::Breadcrumbs(points) => { + info!("intersecting breadcrumbs"); + let (point, tip) = chainsync.find_intersect(points).await.or_restart()?; + + self.notify_tip(&tip); + + point + } + }; + + info!(?intersect, "intersected"); + + Ok(()) + } + + async fn process_next( + &mut self, + next: &NextResponse, + ) -> Result<(), gasket::error::Error> { + match next { + NextResponse::RollForward(header, tip) => { + let header = to_traverse(header).or_panic()?; + let slot = header.slot(); + let hash = header.hash(); + + debug!(slot, %hash, "chain sync roll forward"); + + let block = self + .peer_session + .as_mut() + .unwrap() + .blockfetch() + .fetch_single(pallas_network::miniprotocols::Point::Specific( + slot, + hash.to_vec(), + )) + .await + .or_retry()?; + + self.downstream + .send(UpstreamEvent::RollForward(slot, hash, block).into()) + .await?; + + self.notify_tip(tip); + + Ok(()) + } + chainsync::NextResponse::RollBackward(point, tip) => { + match &point { + Point::Origin => debug!("rollback to origin"), + Point::Specific(slot, _) => debug!(slot, "rollback"), + }; + + self.downstream + .send(UpstreamEvent::Rollback(point.clone()).into()) + .await?; + + self.notify_tip(tip); + + Ok(()) + } + chainsync::NextResponse::Await => { + info!("chain-sync reached the tip of the chain"); + Ok(()) + } + } + } +} + +#[async_trait::async_trait] +impl gasket::runtime::Worker for Worker +where + C: Cursor + Sync + Send, +{ + type WorkUnit = NextResponse; + + fn metrics(&self) -> gasket::metrics::Registry { + gasket::metrics::Builder::new() + .with_counter("received_blocks", &self.block_count) + .with_gauge("chain_tip", &self.chain_tip) + .build() + } + + async fn bootstrap(&mut self) -> Result<(), gasket::error::Error> { + debug!("connecting"); + + let peer = PeerClient::connect(&self.peer_address, self.network_magic) + .await + .or_restart()?; + + self.peer_session = Some(peer); + + self.intersect().await?; + + Ok(()) + } + + async fn teardown(&mut self) -> Result<(), gasket::error::Error> { + self.peer_session.as_mut().unwrap().abort(); + + Ok(()) + } + + async fn schedule(&mut self) -> gasket::runtime::ScheduleResult { + let client = self.peer_session.as_mut().unwrap().chainsync(); + + let next = match client.has_agency() { + true => { + info!("requesting next block"); + client.request_next().await.or_restart()? + } + false => { + info!("awaiting next block (blocking)"); + client.recv_while_must_reply().await.or_restart()? + } + }; + + Ok(gasket::runtime::WorkSchedule::Unit(next)) + } + + async fn execute(&mut self, unit: &Self::WorkUnit) -> Result<(), gasket::error::Error> { + self.process_next(unit).await + } +} diff --git a/pallas-upstream/tests/integration.rs b/pallas-upstream/tests/integration.rs index 6999fa5..89734e0 100644 --- a/pallas-upstream/tests/integration.rs +++ b/pallas-upstream/tests/integration.rs @@ -1,24 +1,21 @@ -#![feature(async_fn_in_trait)] - -use std::time::Duration; - use gasket::{ messaging::{ tokio::{InputPort, OutputPort}, RecvPort, SendPort, }, - runtime::{ScheduleResult, WorkSchedule, Worker}, + runtime::{WorkSchedule, Worker}, }; -use pallas_miniprotocols::Point; -use pallas_upstream::{BlockFetchEvent, Cursor}; -use tracing::{error, info}; + +use pallas_upstream::{Cursor, UpstreamEvent}; +use tracing::error; struct Witness { - input: InputPort, + input: InputPort, } +#[async_trait::async_trait] impl Worker for Witness { - type WorkUnit = BlockFetchEvent; + type WorkUnit = UpstreamEvent; fn metrics(&self) -> gasket::metrics::Registry { gasket::metrics::Registry::new() @@ -30,7 +27,7 @@ impl Worker for Witness { Ok(WorkSchedule::Unit(msg.payload)) } - async fn execute(&mut self, unit: &Self::WorkUnit) -> Result<(), gasket::error::Error> { + async fn execute(&mut self, _: &Self::WorkUnit) -> Result<(), gasket::error::Error> { error!("witnessing block event"); Ok(()) @@ -46,6 +43,7 @@ impl Cursor for StaticCursor { } #[test] +#[ignore] fn test_mainnet_upstream() { tracing::subscriber::set_global_default( tracing_subscriber::FmtSubscriber::builder() @@ -54,34 +52,28 @@ fn test_mainnet_upstream() { ) .unwrap(); - let mut b = pallas_upstream::n2n::Bootstrapper::new( - StaticCursor, - "relays-new.cardano-mainnet.iohk.io:3001".into(), - 764824073, - ); - let (send, receive) = gasket::messaging::tokio::channel(200); - // let mut f = Faker { - // output: Default::default(), - // }; + let mut output_port = OutputPort::default(); + output_port.connect(send); - //f.output.connect(send); + let upstream = pallas_upstream::n2n::Worker::new( + "relays-new.cardano-mainnet.iohk.io:3001".into(), + 764824073, + StaticCursor, + output_port, + ); - b.connect_output(send); - - let b = b.spawn().unwrap(); - - let mut w = Witness { + let mut witness = Witness { input: Default::default(), }; - w.input.connect(receive); + witness.input.connect(receive); - //let f = gasket::runtime::spawn_stage(f, Default::default(), Some("faker")); - let w = gasket::runtime::spawn_stage(w, Default::default(), Some("witness")); + let upstream = gasket::runtime::spawn_stage(upstream, Default::default(), Some("upstream")); + let witness = gasket::runtime::spawn_stage(witness, Default::default(), Some("witness")); - let d = gasket::daemon::Daemon(vec![w]); + let daemon = gasket::daemon::Daemon(vec![upstream, witness]); - d.block(); + daemon.block(); } diff --git a/pallas/Cargo.toml b/pallas/Cargo.toml index 002351d..2378841 100644 --- a/pallas/Cargo.toml +++ b/pallas/Cargo.toml @@ -11,8 +11,7 @@ readme = "../README.md" authors = ["Santiago Carmuega "] [dependencies] -pallas-multiplexer = { version = "0.18.0", path = "../pallas-multiplexer/" } -pallas-miniprotocols = { version = "0.18.0", path = "../pallas-miniprotocols/" } +pallas-network = { version = "0.18.0", path = "../pallas-network/" } pallas-primitives = { version = "0.18.0", path = "../pallas-primitives/" } pallas-traverse = { version = "0.18.0", path = "../pallas-traverse/" } pallas-addresses = { version = "0.18.0", path = "../pallas-addresses/" } diff --git a/pallas/src/ledger.rs b/pallas/src/ledger.rs deleted file mode 100644 index c18df38..0000000 --- a/pallas/src/ledger.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Ledger primitives and cbor codecs for different Cardano eras - -#[doc(inline)] -pub use pallas_primitives as primitives; - -#[doc(inline)] -pub use pallas_traverse as traverse; - -#[doc(inline)] -pub use pallas_addresses as addresses; diff --git a/pallas/src/lib.rs b/pallas/src/lib.rs index 067f723..84bfa16 100644 --- a/pallas/src/lib.rs +++ b/pallas/src/lib.rs @@ -9,9 +9,21 @@ #![warn(missing_docs)] #![warn(missing_doc_code_examples)] -pub mod network; +#[doc(inline)] +pub use pallas_network as network; -pub mod ledger; +pub mod ledger { + //! Ledger primitives and cbor codecs for different Cardano eras + + #[doc(inline)] + pub use pallas_primitives as primitives; + + #[doc(inline)] + pub use pallas_traverse as traverse; + + #[doc(inline)] + pub use pallas_addresses as addresses; +} #[doc(inline)] pub use pallas_crypto as crypto; diff --git a/pallas/src/network.rs b/pallas/src/network.rs deleted file mode 100644 index 696884d..0000000 --- a/pallas/src/network.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Network components of the Ouroboros protocol - -#[doc(inline)] -pub use pallas_multiplexer as multiplexer; - -#[doc(inline)] -pub use pallas_miniprotocols as miniprotocols; - -#[doc(inline)] -pub use pallas_upstream as upstream;