From 7830f456c0e11132383c9768f35264eee2e92352 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20S=C3=A1nchez=20Terraf?= Date: Wed, 15 Oct 2025 18:29:30 -0300 Subject: [PATCH 01/21] First attempt at LeiosNotify --- pallas-network/src/facades.rs | 19 +- pallas-network/src/miniprotocols/common.rs | 3 + .../src/miniprotocols/leiosnotify/client.rs | 180 ++++++++++++++++++ .../src/miniprotocols/leiosnotify/mod.rs | 8 + .../src/miniprotocols/leiosnotify/protocol.rs | 42 ++++ .../src/miniprotocols/leiosnotify/server.rs | 158 +++++++++++++++ pallas-network/src/miniprotocols/mod.rs | 1 + pallas-network/tests/leiosnotify.rs | 101 ++++++++++ 8 files changed, 510 insertions(+), 2 deletions(-) create mode 100644 pallas-network/src/miniprotocols/leiosnotify/client.rs create mode 100644 pallas-network/src/miniprotocols/leiosnotify/mod.rs create mode 100644 pallas-network/src/miniprotocols/leiosnotify/protocol.rs create mode 100644 pallas-network/src/miniprotocols/leiosnotify/server.rs create mode 100644 pallas-network/tests/leiosnotify.rs diff --git a/pallas-network/src/facades.rs b/pallas-network/src/facades.rs index bc30f398e..27ac14b4c 100644 --- a/pallas-network/src/facades.rs +++ b/pallas-network/src/facades.rs @@ -13,12 +13,12 @@ use crate::miniprotocols::handshake::n2n::VersionData; use crate::miniprotocols::handshake::{n2c, n2n, Confirmation, VersionNumber, VersionTable}; use crate::miniprotocols::{ - blockfetch, chainsync, handshake, keepalive, localmsgnotification, localmsgsubmission, + blockfetch, chainsync, handshake, keepalive, leiosnotify, localmsgnotification, localmsgsubmission, localstate, localtxsubmission, peersharing, txmonitor, txsubmission, PROTOCOL_N2C_CHAIN_SYNC, PROTOCOL_N2C_HANDSHAKE, PROTOCOL_N2C_MSG_NOTIFICATION, PROTOCOL_N2C_MSG_SUBMISSION, PROTOCOL_N2C_STATE_QUERY, PROTOCOL_N2C_TX_MONITOR, PROTOCOL_N2C_TX_SUBMISSION, PROTOCOL_N2N_BLOCK_FETCH, PROTOCOL_N2N_CHAIN_SYNC, PROTOCOL_N2N_HANDSHAKE, - PROTOCOL_N2N_KEEP_ALIVE, PROTOCOL_N2N_PEER_SHARING, PROTOCOL_N2N_TX_SUBMISSION, + PROTOCOL_N2N_KEEP_ALIVE, PROTOCOL_N2N_LEIOS_NOTIFY, PROTOCOL_N2N_PEER_SHARING, PROTOCOL_N2N_TX_SUBMISSION, }; use crate::multiplexer::{self, Bearer, RunningPlexer}; @@ -108,6 +108,7 @@ pub struct PeerClient { pub blockfetch: blockfetch::Client, pub txsubmission: txsubmission::Client, pub peersharing: peersharing::Client, + pub leiosnotify: leiosnotify::Client, } impl PeerClient { @@ -125,6 +126,7 @@ impl PeerClient { let bf_channel = plexer.subscribe_client(PROTOCOL_N2N_BLOCK_FETCH); let txsub_channel = plexer.subscribe_client(PROTOCOL_N2N_TX_SUBMISSION); let peersharing_channel = plexer.subscribe_client(PROTOCOL_N2N_PEER_SHARING); + let leiosnotify_channel = plexer.subscribe_client(PROTOCOL_N2N_LEIOS_NOTIFY); let channel = plexer.subscribe_client(PROTOCOL_N2N_KEEP_ALIVE); let keepalive = keepalive::Client::new(channel); @@ -156,6 +158,7 @@ impl PeerClient { blockfetch: blockfetch::Client::new(bf_channel), txsubmission: txsubmission::Client::new(txsub_channel), peersharing: peersharing::Client::new(peersharing_channel), + leiosnotify: leiosnotify::Client::new(leiosnotify_channel), }; Ok(client) @@ -226,6 +229,10 @@ impl PeerClient { &mut self.peersharing } + pub fn leiosnotify(&mut self) -> &mut leiosnotify::Client { + &mut self.leiosnotify + } + pub async fn abort(self) { self.plexer.abort().await } @@ -240,6 +247,7 @@ pub struct PeerServer { pub txsubmission: txsubmission::Server, pub keepalive: keepalive::Server, pub peersharing: peersharing::Server, + pub leiosnotify: leiosnotify::Server, accepted_address: Option, accepted_version: Option<(u64, n2n::VersionData)>, } @@ -254,6 +262,7 @@ impl PeerServer { let txsub_channel = plexer.subscribe_server(PROTOCOL_N2N_TX_SUBMISSION); let keepalive_channel = plexer.subscribe_server(PROTOCOL_N2N_KEEP_ALIVE); let peersharing_channel = plexer.subscribe_server(PROTOCOL_N2N_PEER_SHARING); + let leiosnotify_channel = plexer.subscribe_server(PROTOCOL_N2N_LEIOS_NOTIFY); let hs = handshake::N2NServer::new(hs_channel); let cs = chainsync::N2NServer::new(cs_channel); @@ -261,6 +270,7 @@ impl PeerServer { let txsub = txsubmission::Server::new(txsub_channel); let keepalive = keepalive::Server::new(keepalive_channel); let peersharing = peersharing::Server::new(peersharing_channel); + let leiosnotify = leiosnotify::Server::new(leiosnotify_channel); let plexer = plexer.spawn(); @@ -272,6 +282,7 @@ impl PeerServer { txsubmission: txsub, keepalive, peersharing, + leiosnotify, accepted_address: None, accepted_version: None, } @@ -324,6 +335,10 @@ impl PeerServer { &mut self.peersharing } + pub fn leiosnotify(&mut self) -> &mut leiosnotify::Server { + &mut self.leiosnotify + } + pub fn accepted_address(&self) -> Option<&SocketAddr> { self.accepted_address.as_ref() } diff --git a/pallas-network/src/miniprotocols/common.rs b/pallas-network/src/miniprotocols/common.rs index b97bce8a3..0bfbf14ea 100644 --- a/pallas-network/src/miniprotocols/common.rs +++ b/pallas-network/src/miniprotocols/common.rs @@ -54,6 +54,9 @@ pub const PROTOCOL_N2N_KEEP_ALIVE: u16 = 8; /// Protocol channel number for node-to-node Peer-sharing pub const PROTOCOL_N2N_PEER_SHARING: u16 = 10; +/// Protocol channel number for node-to-node Leios Notify +pub const PROTOCOL_N2N_LEIOS_NOTIFY: u16 = 12; + /// Protocol channel number for node-to-client handshakes pub const PROTOCOL_N2C_HANDSHAKE: u16 = 0; diff --git a/pallas-network/src/miniprotocols/leiosnotify/client.rs b/pallas-network/src/miniprotocols/leiosnotify/client.rs new file mode 100644 index 000000000..1bc7c69f6 --- /dev/null +++ b/pallas-network/src/miniprotocols/leiosnotify/client.rs @@ -0,0 +1,180 @@ +use std::fmt::Debug; +use thiserror::*; +use tracing::debug; + +use super::protocol::*; +use crate::multiplexer; + +#[derive(Error, Debug)] +pub enum ClientError { + #[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("")] + // ProtocolSpecificError, + + #[error("error while sending or receiving data through the channel")] + Plexer(multiplexer::Error), +} + +pub struct Client(State, multiplexer::ChannelBuffer); + +impl Client { + pub fn new(channel: multiplexer::AgentChannel) -> Self { + Self(State::Idle, multiplexer::ChannelBuffer::new(channel)) + } + + /// Returns the current state of the client. + pub fn state(&self) -> &State { + &self.0 + } + + /// Checks if the client is done. + pub fn is_done(&self) -> bool { + self.state() == &State::Done + } + + /// Checks if the client has agency. + fn has_agency(&self) -> bool { + self.state() == &State::Idle + } + + fn assert_agency_is_ours(&self) -> Result<(), ClientError> { + if !self.has_agency() { + Err(ClientError::AgencyIsTheirs) + } else { + Ok(()) + } + } + + fn assert_agency_is_theirs(&self) -> Result<(), ClientError> { + if self.has_agency() { + Err(ClientError::AgencyIsOurs) + } else { + Ok(()) + } + } + + fn assert_outbound_state(&self, msg: &Message) -> Result<(), ClientError> { + if self.state() == &State::Idle && matches!(msg, Message::RequestNext | Message::Done) { + Ok(()) + } else { + Err(ClientError::InvalidOutbound) + } + } + + fn assert_inbound_state(&self, msg: &Message) -> Result<(), ClientError> { + if self.state() != &State::Busy || matches!(msg, Message::RequestNext | Message::Done) { + Err(ClientError::InvalidInbound) + } else { + Ok(()) + } + } + + pub async fn send_message(&mut self, msg: &Message) -> Result<(), ClientError> { + self.assert_agency_is_ours()?; + self.assert_outbound_state(msg)?; + self.1 + .send_msg_chunks(msg) + .await + .map_err(ClientError::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(ClientError::Plexer)?; + self.assert_inbound_state(&msg)?; + + Ok(msg) + } + + pub async fn send_request_next(&mut self) -> Result<(), ClientError> { + let msg = Message::RequestNext; + self.send_message(&msg).await?; + self.0 = State::Busy; + debug!("sent notification request next message"); + + Ok(()) + } + + pub async fn recv_block_announcement(&mut self) -> Result { + let msg = self.recv_message().await?; + match msg { + Message::BlockAnnouncement(params) => { + debug!( + ?params, + "received " + ); + self.0 = State::Idle; + Ok(params) + } + _ => Err(ClientError::InvalidInbound), + } + } + + pub async fn recv_block_offer(&mut self) -> Result { + let msg = self.recv_message().await?; + match msg { + Message::BlockOffer(slot, hash) => { + debug!( + ?slot, + ?hash, + "received " + ); + self.0 = State::Idle; + Ok((slot, hash)) + } + _ => Err(ClientError::InvalidInbound), + } + } + + pub async fn recv_block_tx_offer(&mut self) -> Result { + let msg = self.recv_message().await?; + match msg { + Message::BlockTxsOffer(slot, hash) => { + debug!( + ?slot, + ?hash, + "received " + ); + self.0 = State::Idle; + Ok((slot, hash)) + } + _ => Err(ClientError::InvalidInbound), + } + } + + pub async fn recv_vote_offer(&mut self) -> Result, ClientError> { + let msg = self.recv_message().await?; + match msg { + Message::VoteOffer(params) => { + debug!( + ?params, + "received " + ); + self.0 = State::Idle; + Ok(params) + } + _ => Err(ClientError::InvalidInbound), + } + } + + pub async fn send_done(&mut self) -> Result<(), ClientError> { + let msg = Message::Done; + self.send_message(&msg).await?; + self.0 = State::Done; + + Ok(()) + } +} diff --git a/pallas-network/src/miniprotocols/leiosnotify/mod.rs b/pallas-network/src/miniprotocols/leiosnotify/mod.rs new file mode 100644 index 000000000..d9699e6ad --- /dev/null +++ b/pallas-network/src/miniprotocols/leiosnotify/mod.rs @@ -0,0 +1,8 @@ +mod client; +// mod codec; +mod protocol; +mod server; + +pub use client::*; +pub use protocol::*; +pub use server::*; diff --git a/pallas-network/src/miniprotocols/leiosnotify/protocol.rs b/pallas-network/src/miniprotocols/leiosnotify/protocol.rs new file mode 100644 index 000000000..c1c6c19d5 --- /dev/null +++ b/pallas-network/src/miniprotocols/leiosnotify/protocol.rs @@ -0,0 +1,42 @@ +use pallas_codec::{ + minicbor::{self, Decode, Encode}, + // utils::AnyCbor, +}; +use std::fmt::Debug; + +pub type Slot = u64; + +// TODO: Add `pallas_primitives::babbage::Header` +pub type Header = Vec; + +pub type Hash = Vec; + +pub type VoteIssuerId = Vec; + +pub type BlockOffer = (Slot, Hash); + +pub type BlockTxsOffer = (Slot, Hash); + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum State { + Idle, + Busy, + Done, +} + +#[derive(Debug, Encode, Decode)] +#[cbor(flat)] +pub enum Message { + #[n(0)] + RequestNext, + #[n(1)] + BlockAnnouncement(#[n(0)] Header), + #[n(2)] + BlockOffer(#[n(0)] Slot, #[n(1)] Hash), + #[n(3)] + BlockTxsOffer(#[n(0)] Slot, #[n(1)] Hash), + #[n(4)] + VoteOffer(#[n(0)] Vec<(Slot, VoteIssuerId)>), + #[n(5)] + Done, +} diff --git a/pallas-network/src/miniprotocols/leiosnotify/server.rs b/pallas-network/src/miniprotocols/leiosnotify/server.rs new file mode 100644 index 000000000..aff155265 --- /dev/null +++ b/pallas-network/src/miniprotocols/leiosnotify/server.rs @@ -0,0 +1,158 @@ +use std::fmt::Debug; +use thiserror::*; +use tracing::debug; + +use super::protocol::*; +use crate::multiplexer; + +#[derive(Error, Debug)] +pub enum ServerError { + #[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(multiplexer::Error), +} + +pub struct Server(State, multiplexer::ChannelBuffer); + +impl Server { + pub fn new(channel: multiplexer::AgentChannel) -> Self { + Self(State::Idle, multiplexer::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 { + matches!(&self.0, State::Busy) + } + + fn assert_agency_is_ours(&self) -> Result<(), ServerError> { + if !self.has_agency() { + Err(ServerError::AgencyIsTheirs) + } else { + Ok(()) + } + } + + fn assert_agency_is_theirs(&self) -> Result<(), ServerError> { + if self.has_agency() { + Err(ServerError::AgencyIsOurs) + } else { + Ok(()) + } + } + + fn assert_outbound_state(&self, msg: &Message) -> Result<(), ServerError> { + if self.state() != &State::Busy || matches!(msg, Message::RequestNext | Message::Done) { + Err(ServerError::InvalidOutbound) + } else { + Ok(()) + } + } + + fn assert_inbound_state(&self, msg: &Message) -> Result<(), ServerError> { + if self.state() == &State::Idle && matches!(msg, Message::RequestNext | Message::Done) { + Ok(()) + } else { + Err(ServerError::InvalidInbound) + } + } + + pub async fn send_message(&mut self, msg: &Message) -> Result<(), ServerError> { + self.assert_agency_is_ours()?; + self.assert_outbound_state(msg)?; + self.1 + .send_msg_chunks(msg) + .await + .map_err(ServerError::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(ServerError::Plexer)?; + self.assert_inbound_state(&msg)?; + + Ok(msg) + } + + pub async fn recv_request_next(&mut self) -> Result<(), ServerError> { + let msg = self.recv_message().await?; + match msg { + Message::RequestNext => { + debug!("received Notification Request Next"); + self.0 = State::Busy; + Ok(()) + } + Message::Done => { + debug!("client sent Done message in LeiosNotify protocol"); + self.0 = State::Done; + Ok(()) + } + _ => Err(ServerError::InvalidInbound), + } + } + + pub async fn send_block_announcement( + &mut self, + response: Header, + ) -> Result<(), ServerError> { + let msg = Message::BlockAnnouncement(response); + self.send_message(&msg).await?; + self.0 = State::Idle; + + Ok(()) + } + + pub async fn send_block_offer( + &mut self, + slot: Slot, + header: Header, + ) -> Result<(), ServerError> { + let msg = Message::BlockOffer(slot, header); + self.send_message(&msg).await?; + self.0 = State::Idle; + + Ok(()) + } + + pub async fn send_block_txs_offer( + &mut self, + slot: Slot, + header: Header, + ) -> Result<(), ServerError> { + let msg = Message::BlockTxsOffer(slot, header); + self.send_message(&msg).await?; + self.0 = State::Idle; + + Ok(()) + } + + pub async fn send_vote_offer( + &mut self, + response: Vec<(Slot, VoteIssuerId)>, + ) -> Result<(), ServerError> { + let msg = Message::VoteOffer(response); + self.send_message(&msg).await?; + self.0 = State::Idle; + + Ok(()) + } +} diff --git a/pallas-network/src/miniprotocols/mod.rs b/pallas-network/src/miniprotocols/mod.rs index 21281934c..77233a83b 100644 --- a/pallas-network/src/miniprotocols/mod.rs +++ b/pallas-network/src/miniprotocols/mod.rs @@ -6,6 +6,7 @@ pub mod blockfetch; pub mod chainsync; pub mod handshake; pub mod keepalive; +pub mod leiosnotify; pub mod localmsgnotification; pub mod localmsgsubmission; pub mod localstate; diff --git a/pallas-network/tests/leiosnotify.rs b/pallas-network/tests/leiosnotify.rs new file mode 100644 index 000000000..c0ec1cf8f --- /dev/null +++ b/pallas-network/tests/leiosnotify.rs @@ -0,0 +1,101 @@ +// use pallas_codec::utils::AnyCbor; +// use pallas_crypto::hash::Hash; +use pallas_network::{ + facades::{PeerClient, PeerServer}, + miniprotocols::leiosnotify, +}; +use std::{ + net::{Ipv4Addr, SocketAddrV4}, + time::Duration, +}; + +use tokio::net::TcpListener; + +#[cfg(unix)] +#[tokio::test] +pub async fn leiosnotify_server_and_client_happy_path() { + use tracing::info; + + tracing_subscriber::fmt::init(); + + let _block_hash: leiosnotify::Hash = hex::decode( + "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + ).unwrap(); + + let rb_header: leiosnotify::Header = hex::decode( + "eade0000eade0000eade0000eade0000eade0000eade0000eade0000eade0000" + ).unwrap(); + + let _block_txs_hash: leiosnotify::Hash = hex::decode( + "bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0" + ).unwrap(); + + let _block_slot: leiosnotify::Slot = 123456789; + let _block_txs_slot: leiosnotify::Slot = 222222222; + + let _vote_issuer_id: leiosnotify::Hash = hex::decode( + "beedbeedbeedbeedbeedbeedbeedbeedbeedbeedbeedbeedbeedbeedbeedbeed" + ).unwrap(); + + let listener = TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 30003)) + .await + .unwrap(); + + let server = tokio::spawn({ + let sent_header = rb_header.clone(); + async move { + // server setup + + let mut peer_server = PeerServer::accept(&listener, 0).await.unwrap(); + + let server_ln = peer_server.leiosnotify(); + + // server receives share request from client + + info!("server waiting for share request"); + + server_ln.recv_request_next().await.unwrap(); + + assert_eq!(*server_ln.state(), leiosnotify::State::Busy); + + // Server sends peer addresses + + server_ln.send_block_announcement(sent_header).await.unwrap(); + + assert_eq!(*server_ln.state(), leiosnotify::State::Idle); + + // Server receives Done message from client + + server_ln.recv_request_next().await.unwrap(); + + assert_eq!(*server_ln.state(), leiosnotify::State::Done); + } + }); + + let client = tokio::spawn(async move { + tokio::time::sleep(Duration::from_secs(1)).await; + + // client setup + + let mut client_to_server_conn = PeerClient::connect("localhost:30003", 0).await.unwrap(); + + let client_ln = client_to_server_conn.leiosnotify(); + + // client sends peers request, receives peer addresses + + client_ln.send_request_next().await.unwrap(); + + assert_eq!( + client_ln.recv_block_announcement().await.unwrap(), + rb_header, + ); + + // client sends Done + + client_ln.send_done().await.unwrap(); + + assert!(client_ln.is_done()) + }); + + tokio::try_join!(client, server).unwrap(); +} From 5be3132130ee7f4eebeaa7fbbe6697a6bba2579f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20S=C3=A1nchez=20Terraf?= Date: Thu, 16 Oct 2025 09:28:26 -0300 Subject: [PATCH 02/21] Complete happy path, fmt --- pallas-network/src/facades.rs | 13 +- .../src/miniprotocols/leiosnotify/client.rs | 25 +--- .../src/miniprotocols/leiosnotify/server.rs | 5 +- pallas-network/tests/leiosnotify.rs | 113 +++++++++++++----- 4 files changed, 94 insertions(+), 62 deletions(-) diff --git a/pallas-network/src/facades.rs b/pallas-network/src/facades.rs index 27ac14b4c..bfd513970 100644 --- a/pallas-network/src/facades.rs +++ b/pallas-network/src/facades.rs @@ -13,12 +13,13 @@ use crate::miniprotocols::handshake::n2n::VersionData; use crate::miniprotocols::handshake::{n2c, n2n, Confirmation, VersionNumber, VersionTable}; use crate::miniprotocols::{ - blockfetch, chainsync, handshake, keepalive, leiosnotify, localmsgnotification, localmsgsubmission, - localstate, localtxsubmission, peersharing, txmonitor, txsubmission, PROTOCOL_N2C_CHAIN_SYNC, - PROTOCOL_N2C_HANDSHAKE, PROTOCOL_N2C_MSG_NOTIFICATION, PROTOCOL_N2C_MSG_SUBMISSION, - PROTOCOL_N2C_STATE_QUERY, PROTOCOL_N2C_TX_MONITOR, PROTOCOL_N2C_TX_SUBMISSION, - PROTOCOL_N2N_BLOCK_FETCH, PROTOCOL_N2N_CHAIN_SYNC, PROTOCOL_N2N_HANDSHAKE, - PROTOCOL_N2N_KEEP_ALIVE, PROTOCOL_N2N_LEIOS_NOTIFY, PROTOCOL_N2N_PEER_SHARING, PROTOCOL_N2N_TX_SUBMISSION, + blockfetch, chainsync, handshake, keepalive, leiosnotify, localmsgnotification, + localmsgsubmission, localstate, localtxsubmission, peersharing, txmonitor, txsubmission, + PROTOCOL_N2C_CHAIN_SYNC, PROTOCOL_N2C_HANDSHAKE, PROTOCOL_N2C_MSG_NOTIFICATION, + PROTOCOL_N2C_MSG_SUBMISSION, PROTOCOL_N2C_STATE_QUERY, PROTOCOL_N2C_TX_MONITOR, + PROTOCOL_N2C_TX_SUBMISSION, PROTOCOL_N2N_BLOCK_FETCH, PROTOCOL_N2N_CHAIN_SYNC, + PROTOCOL_N2N_HANDSHAKE, PROTOCOL_N2N_KEEP_ALIVE, PROTOCOL_N2N_LEIOS_NOTIFY, + PROTOCOL_N2N_PEER_SHARING, PROTOCOL_N2N_TX_SUBMISSION, }; use crate::multiplexer::{self, Bearer, RunningPlexer}; diff --git a/pallas-network/src/miniprotocols/leiosnotify/client.rs b/pallas-network/src/miniprotocols/leiosnotify/client.rs index 1bc7c69f6..8258afbdf 100644 --- a/pallas-network/src/miniprotocols/leiosnotify/client.rs +++ b/pallas-network/src/miniprotocols/leiosnotify/client.rs @@ -21,7 +21,6 @@ pub enum ClientError { // #[error("")] // ProtocolSpecificError, - #[error("error while sending or receiving data through the channel")] Plexer(multiplexer::Error), } @@ -112,10 +111,7 @@ impl Client { let msg = self.recv_message().await?; match msg { Message::BlockAnnouncement(params) => { - debug!( - ?params, - "received " - ); + debug!(?params, "received "); self.0 = State::Idle; Ok(params) } @@ -127,11 +123,7 @@ impl Client { let msg = self.recv_message().await?; match msg { Message::BlockOffer(slot, hash) => { - debug!( - ?slot, - ?hash, - "received " - ); + debug!(?slot, ?hash, "received "); self.0 = State::Idle; Ok((slot, hash)) } @@ -139,15 +131,11 @@ impl Client { } } - pub async fn recv_block_tx_offer(&mut self) -> Result { + pub async fn recv_block_txs_offer(&mut self) -> Result { let msg = self.recv_message().await?; match msg { Message::BlockTxsOffer(slot, hash) => { - debug!( - ?slot, - ?hash, - "received " - ); + debug!(?slot, ?hash, "received "); self.0 = State::Idle; Ok((slot, hash)) } @@ -159,10 +147,7 @@ impl Client { let msg = self.recv_message().await?; match msg { Message::VoteOffer(params) => { - debug!( - ?params, - "received " - ); + debug!(?params, "received "); self.0 = State::Idle; Ok(params) } diff --git a/pallas-network/src/miniprotocols/leiosnotify/server.rs b/pallas-network/src/miniprotocols/leiosnotify/server.rs index aff155265..518eec7a4 100644 --- a/pallas-network/src/miniprotocols/leiosnotify/server.rs +++ b/pallas-network/src/miniprotocols/leiosnotify/server.rs @@ -110,10 +110,7 @@ impl Server { } } - pub async fn send_block_announcement( - &mut self, - response: Header, - ) -> Result<(), ServerError> { + pub async fn send_block_announcement(&mut self, response: Header) -> Result<(), ServerError> { let msg = Message::BlockAnnouncement(response); self.send_message(&msg).await?; self.0 = State::Idle; diff --git a/pallas-network/tests/leiosnotify.rs b/pallas-network/tests/leiosnotify.rs index c0ec1cf8f..470a63c6f 100644 --- a/pallas-network/tests/leiosnotify.rs +++ b/pallas-network/tests/leiosnotify.rs @@ -14,35 +14,35 @@ use tokio::net::TcpListener; #[cfg(unix)] #[tokio::test] pub async fn leiosnotify_server_and_client_happy_path() { - use tracing::info; + use tracing::debug; tracing_subscriber::fmt::init(); - - let _block_hash: leiosnotify::Hash = hex::decode( - "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" - ).unwrap(); - - let rb_header: leiosnotify::Header = hex::decode( - "eade0000eade0000eade0000eade0000eade0000eade0000eade0000eade0000" - ).unwrap(); - - let _block_txs_hash: leiosnotify::Hash = hex::decode( - "bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0" - ).unwrap(); - - let _block_slot: leiosnotify::Slot = 123456789; - let _block_txs_slot: leiosnotify::Slot = 222222222; - - let _vote_issuer_id: leiosnotify::Hash = hex::decode( - "beedbeedbeedbeedbeedbeedbeedbeedbeedbeedbeedbeedbeedbeedbeedbeed" - ).unwrap(); - + + let block_hash: leiosnotify::Hash = + hex::decode("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef").unwrap(); + + let rb_header: leiosnotify::Header = + hex::decode("eade0000eade0000eade0000eade0000eade0000eade0000eade0000eade0000").unwrap(); + + let block_txs_hash: leiosnotify::Hash = + hex::decode("bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0").unwrap(); + + let block_slot: leiosnotify::Slot = 123456789; + let block_txs_slot: leiosnotify::Slot = 222222222; + + let vote_issuer_id: leiosnotify::Hash = + hex::decode("beedbeedbeedbeedbeedbeedbeedbeedbeedbeedbeedbeedbeedbeedbeedbeed").unwrap(); + let listener = TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 30003)) .await .unwrap(); let server = tokio::spawn({ let sent_header = rb_header.clone(); + let sent_block_hash = block_hash.clone(); + let sent_block_txs_hash = block_txs_hash.clone(); + let sent_vote_issuer_id = vote_issuer_id.clone(); + async move { // server setup @@ -50,24 +50,56 @@ pub async fn leiosnotify_server_and_client_happy_path() { let server_ln = peer_server.leiosnotify(); - // server receives share request from client + // server receives `RequestNext` from client + debug!("server waiting for share request"); + server_ln.recv_request_next().await.unwrap(); + assert_eq!(*server_ln.state(), leiosnotify::State::Busy); - info!("server waiting for share request"); + // Server sends header + server_ln + .send_block_announcement(sent_header) + .await + .unwrap(); + assert_eq!(*server_ln.state(), leiosnotify::State::Idle); + // server receives `RequestNext` from client + debug!("server waiting for share request"); server_ln.recv_request_next().await.unwrap(); + assert_eq!(*server_ln.state(), leiosnotify::State::Busy); + // Server sends block offer + server_ln + .send_block_offer(block_slot, sent_block_hash) + .await + .unwrap(); + assert_eq!(*server_ln.state(), leiosnotify::State::Idle); + + // server receives `RequestNext` from client + debug!("server waiting for share request"); + server_ln.recv_request_next().await.unwrap(); assert_eq!(*server_ln.state(), leiosnotify::State::Busy); - // Server sends peer addresses + // Server sends txs offer + server_ln + .send_block_txs_offer(block_txs_slot, sent_block_txs_hash) + .await + .unwrap(); + assert_eq!(*server_ln.state(), leiosnotify::State::Idle); - server_ln.send_block_announcement(sent_header).await.unwrap(); + // server receives `RequestNext` from client + debug!("server waiting for share request"); + server_ln.recv_request_next().await.unwrap(); + assert_eq!(*server_ln.state(), leiosnotify::State::Busy); + // Server sends votes offer + server_ln + .send_vote_offer(vec![(block_slot, sent_vote_issuer_id)]) + .await + .unwrap(); assert_eq!(*server_ln.state(), leiosnotify::State::Idle); // Server receives Done message from client - server_ln.recv_request_next().await.unwrap(); - assert_eq!(*server_ln.state(), leiosnotify::State::Done); } }); @@ -81,19 +113,36 @@ pub async fn leiosnotify_server_and_client_happy_path() { let client_ln = client_to_server_conn.leiosnotify(); - // client sends peers request, receives peer addresses - + // client sends `RequestNext`, receives block announcement client_ln.send_request_next().await.unwrap(); - assert_eq!( client_ln.recv_block_announcement().await.unwrap(), rb_header, ); - // client sends Done + // client sends `RequestNext`, receives block offer + client_ln.send_request_next().await.unwrap(); + assert_eq!( + client_ln.recv_block_offer().await.unwrap(), + (block_slot, block_hash), + ); - client_ln.send_done().await.unwrap(); + // client sends `RequestNext`, receives tx offer + client_ln.send_request_next().await.unwrap(); + assert_eq!( + client_ln.recv_block_txs_offer().await.unwrap(), + (block_txs_slot, block_txs_hash), + ); + + // client sends `RequestNext`, receives votes offer + client_ln.send_request_next().await.unwrap(); + assert_eq!( + client_ln.recv_vote_offer().await.unwrap(), + vec![(block_slot, vote_issuer_id)], + ); + // client sends Done + client_ln.send_done().await.unwrap(); assert!(client_ln.is_done()) }); From 6695e45f3d1d2e074cd3180a783376f3d2c5b605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20S=C3=A1nchez=20Terraf?= Date: Thu, 16 Oct 2025 11:02:03 -0300 Subject: [PATCH 03/21] Negative tests on agency --- .../src/miniprotocols/leiosnotify/client.rs | 9 ++- .../src/miniprotocols/leiosnotify/server.rs | 9 ++- pallas-network/tests/leiosnotify.rs | 68 +++++++++++++++++++ 3 files changed, 84 insertions(+), 2 deletions(-) diff --git a/pallas-network/src/miniprotocols/leiosnotify/client.rs b/pallas-network/src/miniprotocols/leiosnotify/client.rs index 8258afbdf..13586cd79 100644 --- a/pallas-network/src/miniprotocols/leiosnotify/client.rs +++ b/pallas-network/src/miniprotocols/leiosnotify/client.rs @@ -13,6 +13,9 @@ pub enum ClientError { #[error("attempted to send message while agency is theirs")] AgencyIsTheirs, + #[error("attempted to send message after protocol is done")] + ProtocolDone, + #[error("inbound message is not valid for current state")] InvalidInbound, @@ -48,7 +51,9 @@ impl Client { } fn assert_agency_is_ours(&self) -> Result<(), ClientError> { - if !self.has_agency() { + if self.is_done() { + Err(ClientError::ProtocolDone) + } else if !self.has_agency() { Err(ClientError::AgencyIsTheirs) } else { Ok(()) @@ -58,6 +63,8 @@ impl Client { fn assert_agency_is_theirs(&self) -> Result<(), ClientError> { if self.has_agency() { Err(ClientError::AgencyIsOurs) + } else if self.is_done() { + Err(ClientError::ProtocolDone) } else { Ok(()) } diff --git a/pallas-network/src/miniprotocols/leiosnotify/server.rs b/pallas-network/src/miniprotocols/leiosnotify/server.rs index 518eec7a4..c0f56b5b0 100644 --- a/pallas-network/src/miniprotocols/leiosnotify/server.rs +++ b/pallas-network/src/miniprotocols/leiosnotify/server.rs @@ -13,6 +13,9 @@ pub enum ServerError { #[error("attempted to send message while agency is theirs")] AgencyIsTheirs, + #[error("attempted to send message after protocol is done")] + ProtocolDone, + #[error("inbound message is not valid for current state")] InvalidInbound, @@ -43,7 +46,9 @@ impl Server { } fn assert_agency_is_ours(&self) -> Result<(), ServerError> { - if !self.has_agency() { + if self.is_done() { + Err(ServerError::ProtocolDone) + } else if !self.has_agency() { Err(ServerError::AgencyIsTheirs) } else { Ok(()) @@ -53,6 +58,8 @@ impl Server { fn assert_agency_is_theirs(&self) -> Result<(), ServerError> { if self.has_agency() { Err(ServerError::AgencyIsOurs) + } else if self.is_done() { + Err(ServerError::ProtocolDone) } else { Ok(()) } diff --git a/pallas-network/tests/leiosnotify.rs b/pallas-network/tests/leiosnotify.rs index 470a63c6f..dbd59d8ad 100644 --- a/pallas-network/tests/leiosnotify.rs +++ b/pallas-network/tests/leiosnotify.rs @@ -148,3 +148,71 @@ pub async fn leiosnotify_server_and_client_happy_path() { tokio::try_join!(client, server).unwrap(); } + +#[cfg(unix)] +#[tokio::test] +pub async fn leiosnotify_outbound_no_agency() { + let rb_header: leiosnotify::Header = + hex::decode("eade0000eade0000eade0000eade0000eade0000eade0000eade0000eade0000").unwrap(); + + let listener = TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 30004)) + .await + .unwrap(); + + let server = tokio::spawn({ + async move { + // server setup + + let mut peer_server = PeerServer::accept(&listener, 0).await.unwrap(); + + let server_ln = peer_server.leiosnotify(); + + // server is Idle + assert_eq!(*server_ln.state(), leiosnotify::State::Idle); + + // Server incorrectly tries to send message + let res = server_ln.send_block_announcement(rb_header).await; + + match res { + Err(leiosnotify::ServerError::AgencyIsTheirs) => {} + Err(leiosnotify::ServerError::InvalidOutbound) => { + tracing::warn!("Expected ServerError `AgencyIsTheirs`, got `InvalidOutbound`") + } + Err(e) => panic!("Unexpected error: {:?}", e), + Ok(_) => panic!("Server has no agency"), + } + + // Server receives Done message from client + server_ln.recv_request_next().await.unwrap(); + assert_eq!(*server_ln.state(), leiosnotify::State::Done); + } + }); + + let client = tokio::spawn(async move { + tokio::time::sleep(Duration::from_secs(1)).await; + + // client setup + + let mut client_to_server_conn = PeerClient::connect("localhost:30004", 0).await.unwrap(); + + let client_ln = client_to_server_conn.leiosnotify(); + + // client sends Done + client_ln.send_done().await.unwrap(); + assert!(client_ln.is_done()); + + // client sends `RequestNext` while not having agency + let res = client_ln.send_request_next().await; + + match res { + Err(leiosnotify::ClientError::ProtocolDone) => {} + Err(leiosnotify::ClientError::InvalidOutbound) => { + tracing::warn!("Expected ClientError `ProtocolDone`, got `InvalidOutbound`") + } + Err(e) => panic!("Unexpected error: {:?}", e), + Ok(_) => panic!("Client has no agency"), + } + }); + + tokio::try_join!(client, server).unwrap(); +} From cfb52a4bf82e96c6d744a243570d6f7e2d2926ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20S=C3=A1nchez=20Terraf?= Date: Thu, 16 Oct 2025 13:32:43 -0300 Subject: [PATCH 04/21] Leios feature flag --- pallas-network/Cargo.toml | 1 + pallas-network/src/facades.rs | 26 +++++++++++++------ .../src/miniprotocols/leiosnotify/mod.rs | 1 - pallas-network/src/miniprotocols/mod.rs | 3 +++ pallas-network/tests/leiosnotify.rs | 1 + 5 files changed, 23 insertions(+), 9 deletions(-) diff --git a/pallas-network/Cargo.toml b/pallas-network/Cargo.toml index 6598fb97f..3cdc34cea 100644 --- a/pallas-network/Cargo.toml +++ b/pallas-network/Cargo.toml @@ -28,3 +28,4 @@ tokio = { version = "1", features = ["full"] } [features] blueprint = [] +leios = [] diff --git a/pallas-network/src/facades.rs b/pallas-network/src/facades.rs index bfd513970..f4b26b401 100644 --- a/pallas-network/src/facades.rs +++ b/pallas-network/src/facades.rs @@ -11,17 +11,19 @@ use tokio::net::{unix::SocketAddr as UnixSocketAddr, UnixListener}; use crate::miniprotocols::handshake::n2n::VersionData; use crate::miniprotocols::handshake::{n2c, n2n, Confirmation, VersionNumber, VersionTable}; - use crate::miniprotocols::{ - blockfetch, chainsync, handshake, keepalive, leiosnotify, localmsgnotification, - localmsgsubmission, localstate, localtxsubmission, peersharing, txmonitor, txsubmission, - PROTOCOL_N2C_CHAIN_SYNC, PROTOCOL_N2C_HANDSHAKE, PROTOCOL_N2C_MSG_NOTIFICATION, - PROTOCOL_N2C_MSG_SUBMISSION, PROTOCOL_N2C_STATE_QUERY, PROTOCOL_N2C_TX_MONITOR, - PROTOCOL_N2C_TX_SUBMISSION, PROTOCOL_N2N_BLOCK_FETCH, PROTOCOL_N2N_CHAIN_SYNC, - PROTOCOL_N2N_HANDSHAKE, PROTOCOL_N2N_KEEP_ALIVE, PROTOCOL_N2N_LEIOS_NOTIFY, - PROTOCOL_N2N_PEER_SHARING, PROTOCOL_N2N_TX_SUBMISSION, + blockfetch, chainsync, handshake, keepalive, localmsgnotification, localmsgsubmission, + localstate, localtxsubmission, peersharing, txmonitor, txsubmission, PROTOCOL_N2C_CHAIN_SYNC, + PROTOCOL_N2C_HANDSHAKE, PROTOCOL_N2C_MSG_NOTIFICATION, PROTOCOL_N2C_MSG_SUBMISSION, + PROTOCOL_N2C_STATE_QUERY, PROTOCOL_N2C_TX_MONITOR, PROTOCOL_N2C_TX_SUBMISSION, + PROTOCOL_N2N_BLOCK_FETCH, PROTOCOL_N2N_CHAIN_SYNC, PROTOCOL_N2N_HANDSHAKE, + PROTOCOL_N2N_KEEP_ALIVE, PROTOCOL_N2N_LEIOS_NOTIFY, PROTOCOL_N2N_PEER_SHARING, + PROTOCOL_N2N_TX_SUBMISSION, }; +#[cfg(feature = "leios")] +use crate::miniprotocols::leiosnotify; + use crate::multiplexer::{self, Bearer, RunningPlexer}; #[derive(Debug, Error)] @@ -109,6 +111,7 @@ pub struct PeerClient { pub blockfetch: blockfetch::Client, pub txsubmission: txsubmission::Client, pub peersharing: peersharing::Client, + #[cfg(feature = "leios")] pub leiosnotify: leiosnotify::Client, } @@ -159,6 +162,7 @@ impl PeerClient { blockfetch: blockfetch::Client::new(bf_channel), txsubmission: txsubmission::Client::new(txsub_channel), peersharing: peersharing::Client::new(peersharing_channel), + #[cfg(feature = "leios")] leiosnotify: leiosnotify::Client::new(leiosnotify_channel), }; @@ -230,6 +234,7 @@ impl PeerClient { &mut self.peersharing } + #[cfg(feature = "leios")] pub fn leiosnotify(&mut self) -> &mut leiosnotify::Client { &mut self.leiosnotify } @@ -248,6 +253,7 @@ pub struct PeerServer { pub txsubmission: txsubmission::Server, pub keepalive: keepalive::Server, pub peersharing: peersharing::Server, + #[cfg(feature = "leios")] pub leiosnotify: leiosnotify::Server, accepted_address: Option, accepted_version: Option<(u64, n2n::VersionData)>, @@ -263,6 +269,7 @@ impl PeerServer { let txsub_channel = plexer.subscribe_server(PROTOCOL_N2N_TX_SUBMISSION); let keepalive_channel = plexer.subscribe_server(PROTOCOL_N2N_KEEP_ALIVE); let peersharing_channel = plexer.subscribe_server(PROTOCOL_N2N_PEER_SHARING); + #[cfg(feature = "leios")] let leiosnotify_channel = plexer.subscribe_server(PROTOCOL_N2N_LEIOS_NOTIFY); let hs = handshake::N2NServer::new(hs_channel); @@ -271,6 +278,7 @@ impl PeerServer { let txsub = txsubmission::Server::new(txsub_channel); let keepalive = keepalive::Server::new(keepalive_channel); let peersharing = peersharing::Server::new(peersharing_channel); + #[cfg(feature = "leios")] let leiosnotify = leiosnotify::Server::new(leiosnotify_channel); let plexer = plexer.spawn(); @@ -283,6 +291,7 @@ impl PeerServer { txsubmission: txsub, keepalive, peersharing, + #[cfg(feature = "leios")] leiosnotify, accepted_address: None, accepted_version: None, @@ -336,6 +345,7 @@ impl PeerServer { &mut self.peersharing } + #[cfg(feature = "leios")] pub fn leiosnotify(&mut self) -> &mut leiosnotify::Server { &mut self.leiosnotify } diff --git a/pallas-network/src/miniprotocols/leiosnotify/mod.rs b/pallas-network/src/miniprotocols/leiosnotify/mod.rs index d9699e6ad..d99027931 100644 --- a/pallas-network/src/miniprotocols/leiosnotify/mod.rs +++ b/pallas-network/src/miniprotocols/leiosnotify/mod.rs @@ -1,5 +1,4 @@ mod client; -// mod codec; mod protocol; mod server; diff --git a/pallas-network/src/miniprotocols/mod.rs b/pallas-network/src/miniprotocols/mod.rs index 77233a83b..b77b01917 100644 --- a/pallas-network/src/miniprotocols/mod.rs +++ b/pallas-network/src/miniprotocols/mod.rs @@ -6,7 +6,10 @@ pub mod blockfetch; pub mod chainsync; pub mod handshake; pub mod keepalive; + +#[cfg(feature = "leios")] pub mod leiosnotify; + pub mod localmsgnotification; pub mod localmsgsubmission; pub mod localstate; diff --git a/pallas-network/tests/leiosnotify.rs b/pallas-network/tests/leiosnotify.rs index dbd59d8ad..f863edd87 100644 --- a/pallas-network/tests/leiosnotify.rs +++ b/pallas-network/tests/leiosnotify.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "leios")] // use pallas_codec::utils::AnyCbor; // use pallas_crypto::hash::Hash; use pallas_network::{ From 3f186ba84b0d399411df65ef0e1c302b254c1896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20S=C3=A1nchez=20Terraf?= Date: Fri, 17 Oct 2025 18:17:11 -0300 Subject: [PATCH 05/21] cddl from conway up to date --- pallas-primitives/src/leios/defs.cddl | 733 ++++++++++++++++++++++++++ 1 file changed, 733 insertions(+) create mode 100644 pallas-primitives/src/leios/defs.cddl diff --git a/pallas-primitives/src/leios/defs.cddl b/pallas-primitives/src/leios/defs.cddl new file mode 100644 index 000000000..a7afa4097 --- /dev/null +++ b/pallas-primitives/src/leios/defs.cddl @@ -0,0 +1,733 @@ +; This file was auto-generated from huddle. Please do not modify it directly! + +; Valid blocks must also satisfy the following two constraints: +; 1. the length of transaction_bodies and transaction_witness_sets +; must be the same +; 2. every transaction_index must be strictly smaller than the +; length of transaction_bodies +block = + [ header + , transaction_bodies : [* transaction_body] + , transaction_witness_sets : [* transaction_witness_set] + , auxiliary_data_set : {* transaction_index => auxiliary_data} + , invalid_transactions : [* transaction_index] + ] + + +transaction = + [transaction_body, transaction_witness_set, bool, auxiliary_data/ nil] + +kes_signature = bytes .size 448 + +language = 0/ 1/ 2 + +potential_languages = 0 .. 255 + +signkeyKES = bytes .size 64 + +certificate = + [ stake_registration + // stake_deregistration + // stake_delegation + // pool_registration + // pool_retirement + // reg_cert + // unreg_cert + // vote_deleg_cert + // stake_vote_deleg_cert + // stake_reg_deleg_cert + // vote_reg_deleg_cert + // stake_vote_reg_deleg_cert + // auth_committee_hot_cert + // resign_committee_cold_cert + // reg_drep_cert + // unreg_drep_cert + // update_drep_cert + ] + + +; This will be deprecated in a future era +stake_registration = (0, stake_credential) + +; This will be deprecated in a future era +stake_deregistration = (1, stake_credential) + +stake_delegation = (2, stake_credential, pool_keyhash) + +pool_registration = (3, pool_params) + +pool_retirement = (4, pool_keyhash, epoch_no) + +reg_cert = (7, stake_credential, coin) + +unreg_cert = (8, stake_credential, coin) + +vote_deleg_cert = (9, stake_credential, drep) + +stake_vote_deleg_cert = (10, stake_credential, pool_keyhash, drep) + +stake_reg_deleg_cert = (11, stake_credential, pool_keyhash, coin) + +vote_reg_deleg_cert = (12, stake_credential, drep, coin) + +stake_vote_reg_deleg_cert = (13, stake_credential, pool_keyhash, drep, coin) + +auth_committee_hot_cert = + (14, committee_cold_credential, committee_hot_credential) + +resign_committee_cold_cert = (15, committee_cold_credential, anchor/ nil) + +reg_drep_cert = (16, drep_credential, coin, anchor/ nil) + +unreg_drep_cert = (17, drep_credential, coin) + +update_drep_cert = (18, drep_credential, anchor/ nil) + +header = [header_body, body_signature : kes_signature] + +header_body = + [ block_number : block_no + , slot : slot_no + , prev_hash : hash32/ nil + , issuer_vkey : vkey + , vrf_vkey : vrf_vkey + , vrf_result : vrf_cert + , block_body_size : uint .size 4 + , block_body_hash : hash32 + , operational_cert + , protocol_version + ] + + +block_no = uint .size 8 + +slot_no = uint .size 8 + +hash32 = bytes .size 32 + +vkey = bytes .size 32 + +vrf_vkey = bytes .size 32 + +vrf_cert = [bytes, bytes .size 80] + +operational_cert = + [ hot_vkey : kes_vkey + , sequence_number : uint .size 8 + , kes_period : uint + , sigma : signature + ] + + +kes_vkey = bytes .size 32 + +signature = bytes .size 64 + +protocol_version = [major_protocol_version, uint] + +major_protocol_version = 0 .. 12 + +transaction_body = + { 0 : set + , 1 : [* transaction_output] + , 2 : coin + , ? 3 : slot_no + , ? 4 : certificates + , ? 5 : withdrawals + , ? 7 : auxiliary_data_hash + , ? 8 : slot_no + , ? 9 : mint + , ? 11 : script_data_hash + , ? 13 : nonempty_set + , ? 14 : required_signers + , ? 15 : network_id + , ? 16 : transaction_output + , ? 17 : coin + , ? 18 : nonempty_set + , ? 19 : voting_procedures + , ? 20 : proposal_procedures + , ? 21 : coin + , ? 22 : positive_coin + } + + +set = #6.258([* a0])/ [* a0] + +transaction_input = [transaction_id : transaction_id, index : uint .size 2] + +transaction_id = hash32 + +; Both of the Alonzo and Babbage style TxOut formats are equally valid +; and can be used interchangeably +transaction_output = shelley_transaction_output/ babbage_transaction_output + +; hash32: datum_hash +shelley_transaction_output = [address, amount : value, ? hash32] + +; address = bytes +; +; address format: +; [ 8 bit header | payload ]; +; +; shelley payment addresses: +; bit 7: 0 +; bit 6: base/other +; bit 5: pointer/enterprise [for base: stake cred is keyhash/scripthash] +; bit 4: payment cred is keyhash/scripthash +; bits 3-0: network id +; +; reward addresses: +; bits 7-5: 111 +; bit 4: credential is keyhash/scripthash +; bits 3-0: network id +; +; byron addresses: +; bits 7-4: 1000 +; +; 0000: base address: keyhash28,keyhash28 +; 0001: base address: scripthash28,keyhash28 +; 0010: base address: keyhash28,scripthash28 +; 0011: base address: scripthash28,scripthash28 +; 0100: pointer address: keyhash28, 3 variable length uint +; 0101: pointer address: scripthash28, 3 variable length uint +; 0110: enterprise address: keyhash28 +; 0111: enterprise address: scripthash28 +; 1000: byron address +; 1110: reward account: keyhash28 +; 1111: reward account: scripthash28 +; 1001-1101: future formats +address = + h'001000000000000000000000000000000000000000000000000000000011000000000000000000000000000000000000000000000000000000' + / h'102000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000' + / h'203000000000000000000000000000000000000000000000000000000033000000000000000000000000000000000000000000000000000000' + / h'304000000000000000000000000000000000000000000000000000000044000000000000000000000000000000000000000000000000000000' + / h'405000000000000000000000000000000000000000000000000000000087680203' + / h'506000000000000000000000000000000000000000000000000000000087680203' + / h'6070000000000000000000000000000000000000000000000000000000' + / h'7080000000000000000000000000000000000000000000000000000000' + +value = coin/ [coin, multiasset] + +coin = uint + +multiasset = {* policy_id => {+ asset_name => a0}} + +policy_id = script_hash + +; To compute a script hash, note that you must prepend +; a tag to the bytes of the script before hashing. +; The tag is determined by the language. +; The tags in the Conway era are: +; "\x00" for multisig scripts +; "\x01" for Plutus V1 scripts +; "\x02" for Plutus V2 scripts +; "\x03" for Plutus V3 scripts +script_hash = hash28 + +hash28 = bytes .size 28 + +asset_name = bytes .size (0 .. 32) + +positive_coin = 1 .. maxWord64 + +maxWord64 = 18446744073709551615 + +; NEW starting with babbage +; datum_option +; script_ref +babbage_transaction_output = + {0 : address, 1 : value, ? 2 : datum_option, ? 3 : script_ref} + +datum_option = [0, hash32// 1, data] + +data = #6.24(bytes .cbor plutus_data) + +plutus_data = + constr + / {* plutus_data => plutus_data} + / [* plutus_data] + / big_int + / bounded_bytes + +constr = + #6.121([* a0]) + / #6.122([* a0]) + / #6.123([* a0]) + / #6.124([* a0]) + / #6.125([* a0]) + / #6.126([* a0]) + / #6.127([* a0]) + / #6.102([uint, [* a0]]) + +big_int = int/ big_uint/ big_nint + +big_uint = #6.2(bounded_bytes) + +; The real bounded_bytes does not have this limit. it instead has +; a different limit which cannot be expressed in CDDL. +; +; The limit is as follows: +; - bytes with a definite-length encoding are limited to size 0..64 +; - for bytes with an indefinite-length CBOR encoding, each chunk is +; limited to size 0..64 +; ( reminder: in CBOR, the indefinite-length encoding of +; bytestrings consists of a token #2.31 followed by a sequence +; of definite-length encoded bytestrings and a stop code ) +bounded_bytes = bytes .size (0 .. 64) + +big_nint = #6.3(bounded_bytes) + +script_ref = #6.24(bytes .cbor script) + +script = + [ 0, native_script + // 1, plutus_v1_script + // 2, plutus_v2_script + // 3, plutus_v3_script + ] + + +native_script = + [ script_pubkey + // script_all + // script_any + // script_n_of_k + // invalid_before + // invalid_hereafter + ] + + +script_pubkey = (0, addr_keyhash) + +addr_keyhash = hash28 + +script_all = (1, [* native_script]) + +script_any = (2, [* native_script]) + +script_n_of_k = (3, n : int64, [* native_script]) + +int64 = -9223372036854775808 .. 9223372036854775807 + +invalid_before = (4, slot_no) + +invalid_hereafter = (5, slot_no) + +; The real type of plutus_v1_script, plutus_v2_script and +; plutus_v3_script is bytes. However, because we enforce +; uniqueness when many scripts are supplied, we need to hack +; around for tests in order to avoid generating duplicates, since +; the cddl tool we use for roundtrip testing doesn't generate +; distinct collections. +plutus_v1_script = distinct_VBytes + +; A type for distinct values. +; The type parameter must support .size, for example: bytes or uint +distinct_VBytes = + bytes .size 8 + / bytes .size 16 + / bytes .size 20 + / bytes .size 24 + / bytes .size 30 + / bytes .size 32 + +plutus_v2_script = distinct_VBytes + +plutus_v3_script = distinct_VBytes + +certificates = nonempty_oset + +nonempty_oset = #6.258([+ a0])/ [+ a0] + +stake_credential = credential + +credential = [0, addr_keyhash// 1, script_hash] + +pool_keyhash = hash28 + +; pool_keyhash: operator +; coin: pledge +; coin: cost +; unit_interval: margin +; set: pool_owners +pool_params = + ( operator : pool_keyhash + , vrf_keyhash : vrf_keyhash + , pledge : coin + , cost : coin + , margin : unit_interval + , reward_account : reward_account + , pool_owners : set + , relays : [* relay] + , pool_metadata : pool_metadata/ nil + ) + +vrf_keyhash = hash32 + +; The real unit_interval is: #6.30([uint, uint]) +; +; A unit interval is a number in the range between 0 and 1, which +; means there are two extra constraints: +; 1. numerator <= denominator +; 2. denominator > 0 +; +; The relation between numerator and denominator can be +; expressed in CDDL, but we have a limitation currently +; (see: https://github.com/input-output-hk/cuddle/issues/30) +; which poses a problem for testing. We need to be able to +; generate random valid data for testing implementation of +; our encoders/decoders. Which means we cannot use the actual +; definition here and we hard code the value to 1/2 +unit_interval = #6.30([1, 2]) + +; reward_account = bytes +reward_account = + h'E090000000000000000000000000000000000000000000000000000000' + / h'F0A0000000000000000000000000000000000000000000000000000000' + +relay = [single_host_addr// single_host_name// multi_host_name] + +single_host_addr = (0, port/ nil, ipv4/ nil, ipv6/ nil) + +port = uint .le 65535 + +ipv4 = bytes .size 4 + +ipv6 = bytes .size 16 + +; dns_name: An A or AAAA DNS record +single_host_name = (1, port/ nil, dns_name) + +dns_name = text .size (0 .. 128) + +; dns_name: An SRV DNS record +multi_host_name = (2, dns_name) + +pool_metadata = [url, bytes] + +url = text .size (0 .. 128) + +epoch_no = uint .size 8 + +drep = [0, addr_keyhash// 1, script_hash// 2// 3] + +committee_cold_credential = credential + +committee_hot_credential = credential + +anchor = [anchor_url : url, anchor_data_hash : hash32] + +drep_credential = credential + +withdrawals = {+ reward_account => coin} + +auxiliary_data_hash = hash32 + +mint = {+ policy_id => {+ asset_name => nonZeroInt64}} + +nonZeroInt64 = negInt64/ posInt64 + +negInt64 = -9223372036854775808 .. -1 + +posInt64 = 1 .. 9223372036854775807 + +; This is a hash of data which may affect evaluation of a script. +; This data consists of: +; - The redeemers from the transaction_witness_set (the value of field 5). +; - The datums from the transaction_witness_set (the value of field 4). +; - The value in the cost_models map corresponding to the script's language +; (in field 18 of protocol_param_update.) +; (In the future it may contain additional protocol parameters.) +; +; Since this data does not exist in contiguous form inside a transaction, it needs +; to be independently constructed by each recipient. +; +; The bytestring which is hashed is the concatenation of three things: +; redeemers || datums || language views +; The redeemers are exactly the data present in the transaction witness set. +; Similarly for the datums, if present. If no datums are provided, the middle +; field is omitted (i.e. it is the empty/null bytestring). +; +; language views CDDL: +; { * language => script_integrity_data } +; +; This must be encoded canonically, using the same scheme as in +; RFC7049 section 3.9: +; - Maps, strings, and bytestrings must use a definite-length encoding +; - Integers must be as small as possible. +; - The expressions for map length, string length, and bytestring length +; must be as short as possible. +; - The keys in the map must be sorted as follows: +; - If two keys have different lengths, the shorter one sorts earlier. +; - If two keys have the same length, the one with the lower value +; in (byte-wise) lexical order sorts earlier. +; +; For PlutusV1 (language id 0), the language view is the following: +; - the value of cost_models map at key 0 (in other words, the script_integrity_data) +; is encoded as an indefinite length list and the result is encoded as a bytestring. +; (our apologies) +; For example, the script_integrity_data corresponding to the all zero costmodel for V1 +; would be encoded as (in hex): +; 58a89f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff +; - the language ID tag is also encoded twice. first as a uint then as +; a bytestring. (our apologies) +; Concretely, this means that the language version for V1 is encoded as +; 4100 in hex. +; For PlutusV2 (language id 1), the language view is the following: +; - the value of cost_models map at key 1 is encoded as an definite length list. +; For example, the script_integrity_data corresponding to the all zero costmodel for V2 +; would be encoded as (in hex): +; 98af0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +; - the language ID tag is encoded as expected. +; Concretely, this means that the language version for V2 is encoded as +; 01 in hex. +; For PlutusV3 (language id 2), the language view is the following: +; - the value of cost_models map at key 2 is encoded as a definite length list. +; +; Note that each Plutus language represented inside a transaction must have +; a cost model in the cost_models protocol parameter in order to execute, +; regardless of what the script integrity data is. +; +; Finally, note that in the case that a transaction includes datums but does not +; include the redeemers field, the script data format becomes (in hex): +; [ A0 | datums | A0 ] +; corresponding to a CBOR empty map and an empty map for language view. +; This empty redeeemer case has changed from the previous eras, since default +; representation for redeemers has been changed to a map. Also whenever redeemers are +; supplied either as a map or as an array they must contain at least one element, +; therefore there is no way to override this behavior by providing a custom +; representation for empty redeemers. +script_data_hash = hash32 + +nonempty_set = #6.258([+ a0])/ [+ a0] + +required_signers = nonempty_set + +network_id = 0/ 1 + +voting_procedures = {+ voter => {+ gov_action_id => voting_procedure}} + +voter = + [ 0 + , addr_keyhash + // 1 + , script_hash + // 2 + , addr_keyhash + // 3 + , script_hash + // 4 + , addr_keyhash + ] + + +gov_action_id = + [transaction_id : transaction_id, gov_action_index : uint .size 2] + +voting_procedure = [vote, anchor/ nil] + +vote = 0 .. 2 + +proposal_procedures = nonempty_oset + +proposal_procedure = [deposit : coin, reward_account, gov_action, anchor] + +gov_action = + [ parameter_change_action + // hard_fork_initiation_action + // treasury_withdrawals_action + // no_confidence + // update_committee + // new_constitution + // info_action + ] + + +parameter_change_action = + (0, gov_action_id/ nil, protocol_param_update, policy_hash/ nil) + +protocol_param_update = + { ? 0 : coin ; minfeeA + , ? 1 : coin ; minfeeB + , ? 2 : uint .size 4 ; max block body size + , ? 3 : uint .size 4 ; max transaction size + , ? 4 : uint .size 2 ; max block header size + , ? 5 : coin ; key deposit + , ? 6 : coin ; pool deposit + , ? 7 : epoch_interval ; maximum epoch + , ? 8 : uint .size 2 ; n_opt: desired number of stake pools + , ? 9 : nonnegative_interval ; pool pledge influence + , ? 10 : unit_interval ; expansion rate + , ? 11 : unit_interval ; treasury growth rate + , ? 16 : coin ; min pool cost + , ? 17 : coin ; ada per utxo byte + , ? 18 : cost_models ; cost models for script languages + , ? 19 : ex_unit_prices ; execution costs + , ? 20 : ex_units ; max tx ex units + , ? 21 : ex_units ; max block ex units + , ? 22 : uint .size 4 ; max value size + , ? 23 : uint .size 2 ; collateral percentage + , ? 24 : uint .size 2 ; max collateral inputs + , ? 25 : pool_voting_thresholds ; pool voting thresholds + , ? 26 : drep_voting_thresholds ; drep voting thresholds + , ? 27 : uint .size 2 ; min committee size + , ? 28 : epoch_interval ; committee term limit + , ? 29 : epoch_interval ; goveranance action validity period + , ? 30 : coin ; governance action deposit + , ? 31 : coin ; drep deposit + , ? 32 : epoch_interval ; drep inactivity period + , ? 33 : nonnegative_interval ; minfee refscriptcoinsperbyte + } + + +epoch_interval = uint .size 4 + +nonnegative_interval = #6.30([uint, positive_int]) + +positive_int = 1 .. maxWord64 + +; The format for cost_models is flexible enough to allow adding +; Plutus built-ins and language versions in the future. +; +; Plutus v1: only 166 integers are used, but more are accepted (and ignored) +; Plutus v2: only 175 integers are used, but more are accepted (and ignored) +; Plutus v3: only 223 integers are used, but more are accepted (and ignored) +; +; Any 8-bit unsigned number can be used as a key. +cost_models = + {? 0 : [* int64], ? 1 : [* int64], ? 2 : [* int64], * 3 .. 255 => [* int64]} + +ex_unit_prices = + [mem_price : nonnegative_interval, step_price : nonnegative_interval] + +ex_units = [mem : uint, steps : uint] + +pool_voting_thresholds = + [ unit_interval ; motion no confidence + , unit_interval ; committee normal + , unit_interval ; committee no confidence + , unit_interval ; hard fork initiation + , unit_interval ; security relevant parameter voting threshold + ] + + +drep_voting_thresholds = + [ unit_interval ; motion no confidence + , unit_interval ; committee normal + , unit_interval ; committee no confidence + , unit_interval ; update constitution + , unit_interval ; hard fork initiation + , unit_interval ; PP network group + , unit_interval ; PP economic group + , unit_interval ; PP technical group + , unit_interval ; PP governance group + , unit_interval ; treasury withdrawal + ] + + +policy_hash = script_hash + +hard_fork_initiation_action = (1, gov_action_id/ nil, protocol_version) + +treasury_withdrawals_action = (2, {* reward_account => coin}, policy_hash/ nil) + +no_confidence = (3, gov_action_id/ nil) + +update_committee = + ( 4 + , gov_action_id/ nil + , set + , {* committee_cold_credential => epoch_no} + , unit_interval + ) + +new_constitution = (5, gov_action_id/ nil, constitution) + +constitution = [anchor, script_hash/ nil] + +info_action = 6 + +transaction_witness_set = + { ? 0 : nonempty_set + , ? 1 : nonempty_set + , ? 2 : nonempty_set + , ? 3 : nonempty_set + , ? 4 : nonempty_set + , ? 5 : redeemers + , ? 6 : nonempty_set + , ? 7 : nonempty_set + } + + +vkeywitness = [vkey, signature] + +bootstrap_witness = + [ public_key : vkey + , signature : signature + , chain_code : bytes .size 32 + , attributes : bytes + ] + + +; Flat Array support is included for backwards compatibility and +; will be removed in the next era. It is recommended for tools to +; adopt using a Map instead of Array going forward. +redeemers = + [ + [ tag : redeemer_tag + , index : uint .size 4 + , data : plutus_data + , ex_units : ex_units + ] + + + ] + / { + [tag : redeemer_tag, index : uint .size 4] => [ data : plutus_data + , ex_units : ex_units + ] + + + } + +redeemer_tag = + 0 ; spend + / 1 ; mint + / 2 ; cert + / 3 ; reward + / 4 ; voting + / 5 ; proposing + +transaction_index = uint .size 2 + +auxiliary_data = + shelley_auxiliary_data/ shelley_ma_auxiliary_data/ alonzo_auxiliary_data + +shelley_auxiliary_data = metadata + +metadata = {* transaction_metadatum_label => transaction_metadatum} + +transaction_metadatum_label = uint .size 8 + +transaction_metadatum = + {* transaction_metadatum => transaction_metadatum} + / [* transaction_metadatum] + / int + / bytes .size (0 .. 64) + / text .size (0 .. 64) + +shelley_ma_auxiliary_data = + [transaction_metadata : metadata, auxiliary_scripts : [* native_script]] + +alonzo_auxiliary_data = + #6.259( + { ? 0 : metadata + , ? 1 : [* native_script] + , ? 2 : [* plutus_v1_script] + , ? 3 : [* plutus_v2_script] + , ? 4 : [* plutus_v3_script] + } + + ) + + From 68790a2c08f00f27539732a064de0b81b92b5bc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20S=C3=A1nchez=20Terraf?= Date: Fri, 17 Oct 2025 18:19:43 -0300 Subject: [PATCH 06/21] keeping only leios-relevant fragments --- pallas-primitives/src/leios/defs.cddl | 704 -------------------------- 1 file changed, 704 deletions(-) diff --git a/pallas-primitives/src/leios/defs.cddl b/pallas-primitives/src/leios/defs.cddl index a7afa4097..52e4f94df 100644 --- a/pallas-primitives/src/leios/defs.cddl +++ b/pallas-primitives/src/leios/defs.cddl @@ -13,76 +13,6 @@ block = , invalid_transactions : [* transaction_index] ] - -transaction = - [transaction_body, transaction_witness_set, bool, auxiliary_data/ nil] - -kes_signature = bytes .size 448 - -language = 0/ 1/ 2 - -potential_languages = 0 .. 255 - -signkeyKES = bytes .size 64 - -certificate = - [ stake_registration - // stake_deregistration - // stake_delegation - // pool_registration - // pool_retirement - // reg_cert - // unreg_cert - // vote_deleg_cert - // stake_vote_deleg_cert - // stake_reg_deleg_cert - // vote_reg_deleg_cert - // stake_vote_reg_deleg_cert - // auth_committee_hot_cert - // resign_committee_cold_cert - // reg_drep_cert - // unreg_drep_cert - // update_drep_cert - ] - - -; This will be deprecated in a future era -stake_registration = (0, stake_credential) - -; This will be deprecated in a future era -stake_deregistration = (1, stake_credential) - -stake_delegation = (2, stake_credential, pool_keyhash) - -pool_registration = (3, pool_params) - -pool_retirement = (4, pool_keyhash, epoch_no) - -reg_cert = (7, stake_credential, coin) - -unreg_cert = (8, stake_credential, coin) - -vote_deleg_cert = (9, stake_credential, drep) - -stake_vote_deleg_cert = (10, stake_credential, pool_keyhash, drep) - -stake_reg_deleg_cert = (11, stake_credential, pool_keyhash, coin) - -vote_reg_deleg_cert = (12, stake_credential, drep, coin) - -stake_vote_reg_deleg_cert = (13, stake_credential, pool_keyhash, drep, coin) - -auth_committee_hot_cert = - (14, committee_cold_credential, committee_hot_credential) - -resign_committee_cold_cert = (15, committee_cold_credential, anchor/ nil) - -reg_drep_cert = (16, drep_credential, coin, anchor/ nil) - -unreg_drep_cert = (17, drep_credential, coin) - -update_drep_cert = (18, drep_credential, anchor/ nil) - header = [header_body, body_signature : kes_signature] header_body = @@ -97,637 +27,3 @@ header_body = , operational_cert , protocol_version ] - - -block_no = uint .size 8 - -slot_no = uint .size 8 - -hash32 = bytes .size 32 - -vkey = bytes .size 32 - -vrf_vkey = bytes .size 32 - -vrf_cert = [bytes, bytes .size 80] - -operational_cert = - [ hot_vkey : kes_vkey - , sequence_number : uint .size 8 - , kes_period : uint - , sigma : signature - ] - - -kes_vkey = bytes .size 32 - -signature = bytes .size 64 - -protocol_version = [major_protocol_version, uint] - -major_protocol_version = 0 .. 12 - -transaction_body = - { 0 : set - , 1 : [* transaction_output] - , 2 : coin - , ? 3 : slot_no - , ? 4 : certificates - , ? 5 : withdrawals - , ? 7 : auxiliary_data_hash - , ? 8 : slot_no - , ? 9 : mint - , ? 11 : script_data_hash - , ? 13 : nonempty_set - , ? 14 : required_signers - , ? 15 : network_id - , ? 16 : transaction_output - , ? 17 : coin - , ? 18 : nonempty_set - , ? 19 : voting_procedures - , ? 20 : proposal_procedures - , ? 21 : coin - , ? 22 : positive_coin - } - - -set = #6.258([* a0])/ [* a0] - -transaction_input = [transaction_id : transaction_id, index : uint .size 2] - -transaction_id = hash32 - -; Both of the Alonzo and Babbage style TxOut formats are equally valid -; and can be used interchangeably -transaction_output = shelley_transaction_output/ babbage_transaction_output - -; hash32: datum_hash -shelley_transaction_output = [address, amount : value, ? hash32] - -; address = bytes -; -; address format: -; [ 8 bit header | payload ]; -; -; shelley payment addresses: -; bit 7: 0 -; bit 6: base/other -; bit 5: pointer/enterprise [for base: stake cred is keyhash/scripthash] -; bit 4: payment cred is keyhash/scripthash -; bits 3-0: network id -; -; reward addresses: -; bits 7-5: 111 -; bit 4: credential is keyhash/scripthash -; bits 3-0: network id -; -; byron addresses: -; bits 7-4: 1000 -; -; 0000: base address: keyhash28,keyhash28 -; 0001: base address: scripthash28,keyhash28 -; 0010: base address: keyhash28,scripthash28 -; 0011: base address: scripthash28,scripthash28 -; 0100: pointer address: keyhash28, 3 variable length uint -; 0101: pointer address: scripthash28, 3 variable length uint -; 0110: enterprise address: keyhash28 -; 0111: enterprise address: scripthash28 -; 1000: byron address -; 1110: reward account: keyhash28 -; 1111: reward account: scripthash28 -; 1001-1101: future formats -address = - h'001000000000000000000000000000000000000000000000000000000011000000000000000000000000000000000000000000000000000000' - / h'102000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000' - / h'203000000000000000000000000000000000000000000000000000000033000000000000000000000000000000000000000000000000000000' - / h'304000000000000000000000000000000000000000000000000000000044000000000000000000000000000000000000000000000000000000' - / h'405000000000000000000000000000000000000000000000000000000087680203' - / h'506000000000000000000000000000000000000000000000000000000087680203' - / h'6070000000000000000000000000000000000000000000000000000000' - / h'7080000000000000000000000000000000000000000000000000000000' - -value = coin/ [coin, multiasset] - -coin = uint - -multiasset = {* policy_id => {+ asset_name => a0}} - -policy_id = script_hash - -; To compute a script hash, note that you must prepend -; a tag to the bytes of the script before hashing. -; The tag is determined by the language. -; The tags in the Conway era are: -; "\x00" for multisig scripts -; "\x01" for Plutus V1 scripts -; "\x02" for Plutus V2 scripts -; "\x03" for Plutus V3 scripts -script_hash = hash28 - -hash28 = bytes .size 28 - -asset_name = bytes .size (0 .. 32) - -positive_coin = 1 .. maxWord64 - -maxWord64 = 18446744073709551615 - -; NEW starting with babbage -; datum_option -; script_ref -babbage_transaction_output = - {0 : address, 1 : value, ? 2 : datum_option, ? 3 : script_ref} - -datum_option = [0, hash32// 1, data] - -data = #6.24(bytes .cbor plutus_data) - -plutus_data = - constr - / {* plutus_data => plutus_data} - / [* plutus_data] - / big_int - / bounded_bytes - -constr = - #6.121([* a0]) - / #6.122([* a0]) - / #6.123([* a0]) - / #6.124([* a0]) - / #6.125([* a0]) - / #6.126([* a0]) - / #6.127([* a0]) - / #6.102([uint, [* a0]]) - -big_int = int/ big_uint/ big_nint - -big_uint = #6.2(bounded_bytes) - -; The real bounded_bytes does not have this limit. it instead has -; a different limit which cannot be expressed in CDDL. -; -; The limit is as follows: -; - bytes with a definite-length encoding are limited to size 0..64 -; - for bytes with an indefinite-length CBOR encoding, each chunk is -; limited to size 0..64 -; ( reminder: in CBOR, the indefinite-length encoding of -; bytestrings consists of a token #2.31 followed by a sequence -; of definite-length encoded bytestrings and a stop code ) -bounded_bytes = bytes .size (0 .. 64) - -big_nint = #6.3(bounded_bytes) - -script_ref = #6.24(bytes .cbor script) - -script = - [ 0, native_script - // 1, plutus_v1_script - // 2, plutus_v2_script - // 3, plutus_v3_script - ] - - -native_script = - [ script_pubkey - // script_all - // script_any - // script_n_of_k - // invalid_before - // invalid_hereafter - ] - - -script_pubkey = (0, addr_keyhash) - -addr_keyhash = hash28 - -script_all = (1, [* native_script]) - -script_any = (2, [* native_script]) - -script_n_of_k = (3, n : int64, [* native_script]) - -int64 = -9223372036854775808 .. 9223372036854775807 - -invalid_before = (4, slot_no) - -invalid_hereafter = (5, slot_no) - -; The real type of plutus_v1_script, plutus_v2_script and -; plutus_v3_script is bytes. However, because we enforce -; uniqueness when many scripts are supplied, we need to hack -; around for tests in order to avoid generating duplicates, since -; the cddl tool we use for roundtrip testing doesn't generate -; distinct collections. -plutus_v1_script = distinct_VBytes - -; A type for distinct values. -; The type parameter must support .size, for example: bytes or uint -distinct_VBytes = - bytes .size 8 - / bytes .size 16 - / bytes .size 20 - / bytes .size 24 - / bytes .size 30 - / bytes .size 32 - -plutus_v2_script = distinct_VBytes - -plutus_v3_script = distinct_VBytes - -certificates = nonempty_oset - -nonempty_oset = #6.258([+ a0])/ [+ a0] - -stake_credential = credential - -credential = [0, addr_keyhash// 1, script_hash] - -pool_keyhash = hash28 - -; pool_keyhash: operator -; coin: pledge -; coin: cost -; unit_interval: margin -; set: pool_owners -pool_params = - ( operator : pool_keyhash - , vrf_keyhash : vrf_keyhash - , pledge : coin - , cost : coin - , margin : unit_interval - , reward_account : reward_account - , pool_owners : set - , relays : [* relay] - , pool_metadata : pool_metadata/ nil - ) - -vrf_keyhash = hash32 - -; The real unit_interval is: #6.30([uint, uint]) -; -; A unit interval is a number in the range between 0 and 1, which -; means there are two extra constraints: -; 1. numerator <= denominator -; 2. denominator > 0 -; -; The relation between numerator and denominator can be -; expressed in CDDL, but we have a limitation currently -; (see: https://github.com/input-output-hk/cuddle/issues/30) -; which poses a problem for testing. We need to be able to -; generate random valid data for testing implementation of -; our encoders/decoders. Which means we cannot use the actual -; definition here and we hard code the value to 1/2 -unit_interval = #6.30([1, 2]) - -; reward_account = bytes -reward_account = - h'E090000000000000000000000000000000000000000000000000000000' - / h'F0A0000000000000000000000000000000000000000000000000000000' - -relay = [single_host_addr// single_host_name// multi_host_name] - -single_host_addr = (0, port/ nil, ipv4/ nil, ipv6/ nil) - -port = uint .le 65535 - -ipv4 = bytes .size 4 - -ipv6 = bytes .size 16 - -; dns_name: An A or AAAA DNS record -single_host_name = (1, port/ nil, dns_name) - -dns_name = text .size (0 .. 128) - -; dns_name: An SRV DNS record -multi_host_name = (2, dns_name) - -pool_metadata = [url, bytes] - -url = text .size (0 .. 128) - -epoch_no = uint .size 8 - -drep = [0, addr_keyhash// 1, script_hash// 2// 3] - -committee_cold_credential = credential - -committee_hot_credential = credential - -anchor = [anchor_url : url, anchor_data_hash : hash32] - -drep_credential = credential - -withdrawals = {+ reward_account => coin} - -auxiliary_data_hash = hash32 - -mint = {+ policy_id => {+ asset_name => nonZeroInt64}} - -nonZeroInt64 = negInt64/ posInt64 - -negInt64 = -9223372036854775808 .. -1 - -posInt64 = 1 .. 9223372036854775807 - -; This is a hash of data which may affect evaluation of a script. -; This data consists of: -; - The redeemers from the transaction_witness_set (the value of field 5). -; - The datums from the transaction_witness_set (the value of field 4). -; - The value in the cost_models map corresponding to the script's language -; (in field 18 of protocol_param_update.) -; (In the future it may contain additional protocol parameters.) -; -; Since this data does not exist in contiguous form inside a transaction, it needs -; to be independently constructed by each recipient. -; -; The bytestring which is hashed is the concatenation of three things: -; redeemers || datums || language views -; The redeemers are exactly the data present in the transaction witness set. -; Similarly for the datums, if present. If no datums are provided, the middle -; field is omitted (i.e. it is the empty/null bytestring). -; -; language views CDDL: -; { * language => script_integrity_data } -; -; This must be encoded canonically, using the same scheme as in -; RFC7049 section 3.9: -; - Maps, strings, and bytestrings must use a definite-length encoding -; - Integers must be as small as possible. -; - The expressions for map length, string length, and bytestring length -; must be as short as possible. -; - The keys in the map must be sorted as follows: -; - If two keys have different lengths, the shorter one sorts earlier. -; - If two keys have the same length, the one with the lower value -; in (byte-wise) lexical order sorts earlier. -; -; For PlutusV1 (language id 0), the language view is the following: -; - the value of cost_models map at key 0 (in other words, the script_integrity_data) -; is encoded as an indefinite length list and the result is encoded as a bytestring. -; (our apologies) -; For example, the script_integrity_data corresponding to the all zero costmodel for V1 -; would be encoded as (in hex): -; 58a89f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff -; - the language ID tag is also encoded twice. first as a uint then as -; a bytestring. (our apologies) -; Concretely, this means that the language version for V1 is encoded as -; 4100 in hex. -; For PlutusV2 (language id 1), the language view is the following: -; - the value of cost_models map at key 1 is encoded as an definite length list. -; For example, the script_integrity_data corresponding to the all zero costmodel for V2 -; would be encoded as (in hex): -; 98af0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -; - the language ID tag is encoded as expected. -; Concretely, this means that the language version for V2 is encoded as -; 01 in hex. -; For PlutusV3 (language id 2), the language view is the following: -; - the value of cost_models map at key 2 is encoded as a definite length list. -; -; Note that each Plutus language represented inside a transaction must have -; a cost model in the cost_models protocol parameter in order to execute, -; regardless of what the script integrity data is. -; -; Finally, note that in the case that a transaction includes datums but does not -; include the redeemers field, the script data format becomes (in hex): -; [ A0 | datums | A0 ] -; corresponding to a CBOR empty map and an empty map for language view. -; This empty redeeemer case has changed from the previous eras, since default -; representation for redeemers has been changed to a map. Also whenever redeemers are -; supplied either as a map or as an array they must contain at least one element, -; therefore there is no way to override this behavior by providing a custom -; representation for empty redeemers. -script_data_hash = hash32 - -nonempty_set = #6.258([+ a0])/ [+ a0] - -required_signers = nonempty_set - -network_id = 0/ 1 - -voting_procedures = {+ voter => {+ gov_action_id => voting_procedure}} - -voter = - [ 0 - , addr_keyhash - // 1 - , script_hash - // 2 - , addr_keyhash - // 3 - , script_hash - // 4 - , addr_keyhash - ] - - -gov_action_id = - [transaction_id : transaction_id, gov_action_index : uint .size 2] - -voting_procedure = [vote, anchor/ nil] - -vote = 0 .. 2 - -proposal_procedures = nonempty_oset - -proposal_procedure = [deposit : coin, reward_account, gov_action, anchor] - -gov_action = - [ parameter_change_action - // hard_fork_initiation_action - // treasury_withdrawals_action - // no_confidence - // update_committee - // new_constitution - // info_action - ] - - -parameter_change_action = - (0, gov_action_id/ nil, protocol_param_update, policy_hash/ nil) - -protocol_param_update = - { ? 0 : coin ; minfeeA - , ? 1 : coin ; minfeeB - , ? 2 : uint .size 4 ; max block body size - , ? 3 : uint .size 4 ; max transaction size - , ? 4 : uint .size 2 ; max block header size - , ? 5 : coin ; key deposit - , ? 6 : coin ; pool deposit - , ? 7 : epoch_interval ; maximum epoch - , ? 8 : uint .size 2 ; n_opt: desired number of stake pools - , ? 9 : nonnegative_interval ; pool pledge influence - , ? 10 : unit_interval ; expansion rate - , ? 11 : unit_interval ; treasury growth rate - , ? 16 : coin ; min pool cost - , ? 17 : coin ; ada per utxo byte - , ? 18 : cost_models ; cost models for script languages - , ? 19 : ex_unit_prices ; execution costs - , ? 20 : ex_units ; max tx ex units - , ? 21 : ex_units ; max block ex units - , ? 22 : uint .size 4 ; max value size - , ? 23 : uint .size 2 ; collateral percentage - , ? 24 : uint .size 2 ; max collateral inputs - , ? 25 : pool_voting_thresholds ; pool voting thresholds - , ? 26 : drep_voting_thresholds ; drep voting thresholds - , ? 27 : uint .size 2 ; min committee size - , ? 28 : epoch_interval ; committee term limit - , ? 29 : epoch_interval ; goveranance action validity period - , ? 30 : coin ; governance action deposit - , ? 31 : coin ; drep deposit - , ? 32 : epoch_interval ; drep inactivity period - , ? 33 : nonnegative_interval ; minfee refscriptcoinsperbyte - } - - -epoch_interval = uint .size 4 - -nonnegative_interval = #6.30([uint, positive_int]) - -positive_int = 1 .. maxWord64 - -; The format for cost_models is flexible enough to allow adding -; Plutus built-ins and language versions in the future. -; -; Plutus v1: only 166 integers are used, but more are accepted (and ignored) -; Plutus v2: only 175 integers are used, but more are accepted (and ignored) -; Plutus v3: only 223 integers are used, but more are accepted (and ignored) -; -; Any 8-bit unsigned number can be used as a key. -cost_models = - {? 0 : [* int64], ? 1 : [* int64], ? 2 : [* int64], * 3 .. 255 => [* int64]} - -ex_unit_prices = - [mem_price : nonnegative_interval, step_price : nonnegative_interval] - -ex_units = [mem : uint, steps : uint] - -pool_voting_thresholds = - [ unit_interval ; motion no confidence - , unit_interval ; committee normal - , unit_interval ; committee no confidence - , unit_interval ; hard fork initiation - , unit_interval ; security relevant parameter voting threshold - ] - - -drep_voting_thresholds = - [ unit_interval ; motion no confidence - , unit_interval ; committee normal - , unit_interval ; committee no confidence - , unit_interval ; update constitution - , unit_interval ; hard fork initiation - , unit_interval ; PP network group - , unit_interval ; PP economic group - , unit_interval ; PP technical group - , unit_interval ; PP governance group - , unit_interval ; treasury withdrawal - ] - - -policy_hash = script_hash - -hard_fork_initiation_action = (1, gov_action_id/ nil, protocol_version) - -treasury_withdrawals_action = (2, {* reward_account => coin}, policy_hash/ nil) - -no_confidence = (3, gov_action_id/ nil) - -update_committee = - ( 4 - , gov_action_id/ nil - , set - , {* committee_cold_credential => epoch_no} - , unit_interval - ) - -new_constitution = (5, gov_action_id/ nil, constitution) - -constitution = [anchor, script_hash/ nil] - -info_action = 6 - -transaction_witness_set = - { ? 0 : nonempty_set - , ? 1 : nonempty_set - , ? 2 : nonempty_set - , ? 3 : nonempty_set - , ? 4 : nonempty_set - , ? 5 : redeemers - , ? 6 : nonempty_set - , ? 7 : nonempty_set - } - - -vkeywitness = [vkey, signature] - -bootstrap_witness = - [ public_key : vkey - , signature : signature - , chain_code : bytes .size 32 - , attributes : bytes - ] - - -; Flat Array support is included for backwards compatibility and -; will be removed in the next era. It is recommended for tools to -; adopt using a Map instead of Array going forward. -redeemers = - [ + [ tag : redeemer_tag - , index : uint .size 4 - , data : plutus_data - , ex_units : ex_units - ] - - - ] - / { + [tag : redeemer_tag, index : uint .size 4] => [ data : plutus_data - , ex_units : ex_units - ] - - - } - -redeemer_tag = - 0 ; spend - / 1 ; mint - / 2 ; cert - / 3 ; reward - / 4 ; voting - / 5 ; proposing - -transaction_index = uint .size 2 - -auxiliary_data = - shelley_auxiliary_data/ shelley_ma_auxiliary_data/ alonzo_auxiliary_data - -shelley_auxiliary_data = metadata - -metadata = {* transaction_metadatum_label => transaction_metadatum} - -transaction_metadatum_label = uint .size 8 - -transaction_metadatum = - {* transaction_metadatum => transaction_metadatum} - / [* transaction_metadatum] - / int - / bytes .size (0 .. 64) - / text .size (0 .. 64) - -shelley_ma_auxiliary_data = - [transaction_metadata : metadata, auxiliary_scripts : [* native_script]] - -alonzo_auxiliary_data = - #6.259( - { ? 0 : metadata - , ? 1 : [* native_script] - , ? 2 : [* plutus_v1_script] - , ? 3 : [* plutus_v2_script] - , ? 4 : [* plutus_v3_script] - } - - ) - - From 3370a0802e51ccde9905900340a12bd644cc982b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20S=C3=A1nchez=20Terraf?= Date: Fri, 17 Oct 2025 18:23:47 -0300 Subject: [PATCH 07/21] adding elements from CIP --- pallas-primitives/src/leios/defs.cddl | 51 +++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/pallas-primitives/src/leios/defs.cddl b/pallas-primitives/src/leios/defs.cddl index 52e4f94df..d24d7df5f 100644 --- a/pallas-primitives/src/leios/defs.cddl +++ b/pallas-primitives/src/leios/defs.cddl @@ -1,15 +1,11 @@ -; This file was auto-generated from huddle. Please do not modify it directly! +; conway.cddl fragments integrated to CIP draft fetched 17 Oct 2025 from cardano-scaling -; Valid blocks must also satisfy the following two constraints: -; 1. the length of transaction_bodies and transaction_witness_sets -; must be the same -; 2. every transaction_index must be strictly smaller than the -; length of transaction_bodies block = [ header , transaction_bodies : [* transaction_body] , transaction_witness_sets : [* transaction_witness_set] , auxiliary_data_set : {* transaction_index => auxiliary_data} + , ? eb_certificate : leios_certificate , invalid_transactions : [* transaction_index] ] @@ -24,6 +20,49 @@ header_body = , vrf_result : vrf_cert , block_body_size : uint .size 4 , block_body_hash : hash32 + , ? ( announced_eb : hash32 + , announced_eb_size : uint32 + ) + , ? certified_eb : bool , operational_cert , protocol_version ] + + +endorser_block = + [ transaction_references : [* tx_reference] + ] + +; Reference structures +tx_reference = + [ tx_hash : hash32 ; Hash of complete transaction bytes + , tx_size : uint16 ; Transaction size in bytes + ] + +leios_certificate = + [ election_id : election_id + , endorser_block_hash : hash32 + , persistent_voters : [* persistent_voter_id] + , nonpersistent_voters : {* pool_id => bls_signature} + , ? aggregate_elig_sig : bls_signature + , aggregate_vote_sig : bls_signature + ] + +leios_vote = persistent_vote / non_persistent_vote + +persistent_vote = + [ 0 + , election_id + , persistent_voter_id + , endorser_block_hash + , vote_signature + ] + +non_persistent_vote = + [ 1 + , election_id + , pool_id + , eligibility_signature + , endorser_block_hash + , vote_signature + ] From 0d941a78c64f6cf3d289b84f8381d0d38c0ab107 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20S=C3=A1nchez=20Terraf?= Date: Fri, 17 Oct 2025 18:45:09 -0300 Subject: [PATCH 08/21] supplementing cddl defs --- pallas-primitives/src/leios/defs.cddl | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/pallas-primitives/src/leios/defs.cddl b/pallas-primitives/src/leios/defs.cddl index d24d7df5f..4e184d8af 100644 --- a/pallas-primitives/src/leios/defs.cddl +++ b/pallas-primitives/src/leios/defs.cddl @@ -21,7 +21,7 @@ header_body = , block_body_size : uint .size 4 , block_body_hash : hash32 , ? ( announced_eb : hash32 - , announced_eb_size : uint32 + , announced_eb_size : uint .size 4 ) , ? certified_eb : bool , operational_cert @@ -35,8 +35,8 @@ endorser_block = ; Reference structures tx_reference = - [ tx_hash : hash32 ; Hash of complete transaction bytes - , tx_size : uint16 ; Transaction size in bytes + [ tx_hash : hash32 ; Hash of complete transaction bytes + , tx_size : uint .size 2 ; Transaction size in bytes ] leios_certificate = @@ -61,8 +61,24 @@ persistent_vote = non_persistent_vote = [ 1 , election_id - , pool_id + , pool_keyhash , eligibility_signature , endorser_block_hash , vote_signature ] + +; approx size 136 + ⌈m/8⌉ + 76(n - m), where +; - n = committee size +; - m = number of persistent voters +; (see https://github.com/input-output-hk/ouroboros-leios/blob/d5f1a9bc940e69f406c3e25c0d7d9aa58cf701f8/crypto-benchmarks.rs/Specification.md?plain=1#L106) +bls_signature = bytes .size 48 + +election_id = bytes .size 8 + +persistent_voter_id = bytes .size 2 + +endorser_block_hash = hash32 + +vote_signature = bytes .size 48 + +eligibility_signature = bytes .size 48 From f83f2fa717c81f961d5af93c02c292ace2a81da2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20S=C3=A1nchez=20Terraf?= Date: Fri, 17 Oct 2025 20:03:36 -0300 Subject: [PATCH 09/21] First try at Leios ledger primitives --- pallas-primitives/Cargo.toml | 1 + pallas-primitives/src/leios/mod.rs | 3 + pallas-primitives/src/leios/model.rs | 192 +++++++++++++++++++++++++++ pallas-primitives/src/lib.rs | 3 + 4 files changed, 199 insertions(+) create mode 100644 pallas-primitives/src/leios/mod.rs create mode 100644 pallas-primitives/src/leios/model.rs diff --git a/pallas-primitives/Cargo.toml b/pallas-primitives/Cargo.toml index b3fb4506c..8fdeda1c6 100644 --- a/pallas-primitives/Cargo.toml +++ b/pallas-primitives/Cargo.toml @@ -24,3 +24,4 @@ test-case = "3.3.1" [features] json = ["serde", "serde_json"] default = ["json"] +leios = [] diff --git a/pallas-primitives/src/leios/mod.rs b/pallas-primitives/src/leios/mod.rs new file mode 100644 index 000000000..4a7ebf60c --- /dev/null +++ b/pallas-primitives/src/leios/mod.rs @@ -0,0 +1,3 @@ +mod model; + +pub use model::*; diff --git a/pallas-primitives/src/leios/model.rs b/pallas-primitives/src/leios/model.rs new file mode 100644 index 000000000..40eb7e43d --- /dev/null +++ b/pallas-primitives/src/leios/model.rs @@ -0,0 +1,192 @@ +//! Ledger primitives and cbor codec for the Conway era +//! +//! Handcrafted, idiomatic rust artifacts based on based on the [CIP proposal](https://github.com/cardano-scaling/CIPs/blob/leios/CIP-0164/README.md) at the cardano-scaling repo. + +use serde::{Deserialize, Serialize}; + +use pallas_codec::minicbor::{self, Decode, Encode}; + +pub use pallas_codec::codec_by_datatype; + +pub use crate::{ + plutus_data::*, AddrKeyhash, AssetName, Bytes, Coin, CostModel, DnsName, Epoch, ExUnits, + GenesisDelegateHash, Genesishash, Hash, IPv4, IPv6, KeepRaw, Metadata, Metadatum, + MetadatumLabel, NetworkId, NonEmptySet, NonZeroInt, Nonce, NonceVariant, Nullable, + PlutusScript, PolicyId, PoolKeyhash, PoolMetadata, PoolMetadataHash, Port, PositiveCoin, + PositiveInterval, ProtocolVersion, RationalNumber, Relay, RewardAccount, ScriptHash, Set, + StakeCredential, TransactionIndex, TransactionInput, UnitInterval, VrfCert, VrfKeyhash, +}; + +use crate::{babbage, conway, BTreeMap}; + +pub use babbage::{ + derive_tagged_vrf_output, DatumHash, DatumOption, OperationalCert, VrfDerivation, +}; + +pub use crate::alonzo::{AuxiliaryData, BootstrapWitness, NativeScript, VKeyWitness}; + +pub use conway::{ + Anchor, Certificate, CommitteeColdCredential, CommitteeHotCredential, Constitution, CostModels, + DRep, DRepCredential, DRepVotingThresholds, ExUnitPrices, GovActionId, Language, + LegacyTransactionOutput, Mint, Multiasset, PoolVotingThresholds, PostAlonzoAuxiliaryData, + PostAlonzoTransactionOutput, ProposalProcedure, ProtocolParamUpdate, Redeemer, RedeemerTag, + Redeemers, RedeemersKey, RedeemersValue, RequiredSigners, ScriptRef, TransactionBody, + TransactionOutput, Tx, Update, Value, Vote, Voter, VotingProcedure, VotingProcedures, + Withdrawals, WitnessSet, +}; + +/// Leios ranking block. +/// +/// This is the original Praos (Conway) block, with additional fields for announcing and +/// and certifying previously announced endorser blocks. +#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone)] +pub struct Block<'b> { + #[n(0)] + pub header: KeepRaw<'b, Header>, + + #[b(1)] + pub transaction_bodies: Vec>>, + + #[n(2)] + pub transaction_witness_sets: Vec>>, + + #[n(3)] + pub auxiliary_data_set: BTreeMap>, + + #[n(4)] + pub eb_certificate: Option>, + + #[n(5)] + pub invalid_transactions: Option>, +} + +#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)] +pub struct HeaderBody { + #[n(0)] + pub block_number: u64, + + #[n(1)] + pub slot: u64, + + #[n(2)] + pub prev_hash: Option>, + + #[n(3)] + pub issuer_vkey: Bytes, + + #[n(4)] + pub vrf_vkey: Bytes, + + #[n(5)] + pub vrf_result: VrfCert, + + #[n(6)] + pub block_body_size: u64, + + #[n(7)] + pub block_body_hash: Hash<32>, + + #[n(8)] + pub operational_cert: OperationalCert, + + #[n(9)] + pub protocol_version: ProtocolVersion, +} + +#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)] +pub struct Header { + #[n(0)] + pub header_body: HeaderBody, + + #[n(1)] + pub body_signature: Bytes, +} + +impl HeaderBody { + pub fn leader_vrf_output(&self) -> Vec { + derive_tagged_vrf_output(&self.vrf_result.0, VrfDerivation::Leader) + } + + pub fn nonce_vrf_output(&self) -> Vec { + derive_tagged_vrf_output(&self.vrf_result.0, VrfDerivation::Nonce) + } +} + +#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)] +pub struct EndorserBlock { + #[n(0)] + pub transaction_references: Vec, +} + +#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)] +pub struct TxReference { + #[n(0)] + pub tx_hash: Hash<32>, + + #[n(1)] + pub tx_size: u16, +} + +pub type BlsSignature = Bytes; // 48 bytes + +// pub type ElectionId = Bytes; // 8 bytes +// pub type PersistentVoterId = Bytes; // 2 bytes +// pub type EndorserBlockHash = Hash<32>; + +#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)] +pub struct LeiosCertificate { + #[n(0)] + pub election_id: Bytes, + + #[n(1)] + pub endorser_block_hash: Hash<32>, + + #[n(2)] + pub persistent_voters: Vec, + + #[n(3)] + pub nonpersistent_voters: BTreeMap, + + #[n(4)] + pub aggregate_elig_sig: Option, + + #[n(5)] + pub aggregate_vote_sig: BlsSignature, +} + +#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)] +#[cbor(flat)] +pub enum LeiosVote { + #[n(0)] + Persistent { + #[n(0)] + election_id: Bytes, + + #[n(1)] + persistent_voter_id: Bytes, + + #[n(2)] + endorser_block_hash: Hash<32>, + + #[n(3)] + vote_signature: BlsSignature, + }, + + #[n(1)] + NonPersistent { + #[n(0)] + election_id: Bytes, + + #[n(1)] + pool_id: PoolKeyhash, + + #[n(2)] + eligibility_signature: BlsSignature, + + #[n(3)] + endorser_block_hash: Hash<32>, + + #[n(5)] + vote_signature: BlsSignature, + }, +} diff --git a/pallas-primitives/src/lib.rs b/pallas-primitives/src/lib.rs index 03269ff5c..cc67bb5e9 100644 --- a/pallas-primitives/src/lib.rs +++ b/pallas-primitives/src/lib.rs @@ -7,6 +7,9 @@ pub mod alonzo; pub mod babbage; pub mod byron; pub mod conway; + +#[cfg(feature = "leios")] +pub mod leios; pub use plutus_data::*; pub use framework::*; From 5e713ea3675a6e29a787840fdae9b7874bcfc6ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20S=C3=A1nchez=20Terraf?= Date: Fri, 17 Oct 2025 20:23:32 -0300 Subject: [PATCH 10/21] eb elements at HeaderBody, as per original CIP version --- pallas-primitives/src/leios/model.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pallas-primitives/src/leios/model.rs b/pallas-primitives/src/leios/model.rs index 40eb7e43d..ff85145dd 100644 --- a/pallas-primitives/src/leios/model.rs +++ b/pallas-primitives/src/leios/model.rs @@ -86,10 +86,20 @@ pub struct HeaderBody { #[n(7)] pub block_body_hash: Hash<32>, + // Two fields, according to the old CIP (not a tuple as recently) #[n(8)] - pub operational_cert: OperationalCert, + pub announced_eb: Option>, #[n(9)] + pub announced_eb_size: Option, + + #[n(10)] + pub certified_eb: Option, + + #[n(11)] + pub operational_cert: OperationalCert, + + #[n(12)] pub protocol_version: ProtocolVersion, } From 369ec0c06bccb22bdd0720b67b3a301b7eadd598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20S=C3=A1nchez=20Terraf?= Date: Mon, 20 Oct 2025 09:01:59 -0300 Subject: [PATCH 11/21] Making eb announcements a struct --- pallas-primitives/src/leios/model.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/pallas-primitives/src/leios/model.rs b/pallas-primitives/src/leios/model.rs index ff85145dd..3b773c2a3 100644 --- a/pallas-primitives/src/leios/model.rs +++ b/pallas-primitives/src/leios/model.rs @@ -60,6 +60,15 @@ pub struct Block<'b> { pub invalid_transactions: Option>, } +#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)] +pub struct EbAnnouncement { + #[n(0)] + pub announced_eb: Option>, + + #[n(1)] + pub announced_eb_size: Option, +} + #[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)] pub struct HeaderBody { #[n(0)] @@ -86,20 +95,16 @@ pub struct HeaderBody { #[n(7)] pub block_body_hash: Hash<32>, - // Two fields, according to the old CIP (not a tuple as recently) #[n(8)] - pub announced_eb: Option>, + pub eb_announcement: Option, #[n(9)] - pub announced_eb_size: Option, - - #[n(10)] pub certified_eb: Option, - #[n(11)] + #[n(10)] pub operational_cert: OperationalCert, - #[n(12)] + #[n(11)] pub protocol_version: ProtocolVersion, } From 39e6432667ef987170f169f622563714a8c6d6f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20S=C3=A1nchez=20Terraf?= Date: Mon, 20 Oct 2025 09:15:10 -0300 Subject: [PATCH 12/21] cert in last position as per @will-break-it's answer --- pallas-primitives/src/leios/model.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallas-primitives/src/leios/model.rs b/pallas-primitives/src/leios/model.rs index 3b773c2a3..f73aed3ac 100644 --- a/pallas-primitives/src/leios/model.rs +++ b/pallas-primitives/src/leios/model.rs @@ -54,10 +54,10 @@ pub struct Block<'b> { pub auxiliary_data_set: BTreeMap>, #[n(4)] - pub eb_certificate: Option>, + pub invalid_transactions: Option>, #[n(5)] - pub invalid_transactions: Option>, + pub eb_certificate: Option>, } #[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)] From dd722792f46b29dc2a3aafab929ac09cd769eb77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20S=C3=A1nchez=20Terraf?= Date: Mon, 20 Oct 2025 09:44:54 -0300 Subject: [PATCH 13/21] Leios Tx and hash calculation --- pallas-primitives/src/leios/model.rs | 5 +++++ pallas-traverse/Cargo.toml | 1 + pallas-traverse/src/hashes.rs | 19 +++++++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/pallas-primitives/src/leios/model.rs b/pallas-primitives/src/leios/model.rs index f73aed3ac..309f07f41 100644 --- a/pallas-primitives/src/leios/model.rs +++ b/pallas-primitives/src/leios/model.rs @@ -127,6 +127,11 @@ impl HeaderBody { } } +/// Leios transaction type +/// +/// It saves CBOR data for hashing purposes. +pub type LeiosTx<'a> = KeepRaw<'a, Tx<'a>>; + #[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)] pub struct EndorserBlock { #[n(0)] diff --git a/pallas-traverse/Cargo.toml b/pallas-traverse/Cargo.toml index c6e62f1a4..217076f7b 100644 --- a/pallas-traverse/Cargo.toml +++ b/pallas-traverse/Cargo.toml @@ -25,3 +25,4 @@ serde = "1.0.155" [features] unstable = [] +leios = ["pallas-primitives/leios"] diff --git a/pallas-traverse/src/hashes.rs b/pallas-traverse/src/hashes.rs index 5ba9c7131..b45084566 100644 --- a/pallas-traverse/src/hashes.rs +++ b/pallas-traverse/src/hashes.rs @@ -6,6 +6,9 @@ use pallas_crypto::{ }; use pallas_primitives::{alonzo, babbage, byron, conway}; +#[cfg(feature = "leios")] +use pallas_primitives::leios; + impl ComputeHash<32> for byron::EbbHead { fn compute_hash(&self) -> Hash<32> { // hash expects to have a prefix for the type of block @@ -159,6 +162,22 @@ impl ComputeHash<28> for PublicKey { } } +// leios + +#[cfg(feature = "leios")] +impl ComputeHash<32> for leios::Tx<'_> { + fn compute_hash(&self) -> Hash<32> { + Hasher::<256>::hash_cbor(self) + } +} + +#[cfg(feature = "leios")] +impl OriginalHash<32> for leios::LeiosTx<'_> { + fn original_hash(&self) -> pallas_crypto::hash::Hash<32> { + Hasher::<256>::hash(self.raw_cbor()) + } +} + #[cfg(test)] mod tests { use crate::{Era, MultiEraTx}; From a6940d7c0bd2a8199ab7b6a16991beed0f28cc08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20S=C3=A1nchez=20Terraf?= Date: Wed, 22 Oct 2025 14:53:12 -0300 Subject: [PATCH 14/21] LeiosFetch (untested) --- .../src/miniprotocols/leiosfetch/client.rs | 230 ++++++++++++++++++ .../src/miniprotocols/leiosfetch/mod.rs | 8 + .../miniprotocols/leiosfetch/primitives.rs | 48 ++++ .../src/miniprotocols/leiosfetch/protocol.rs | 57 +++++ .../src/miniprotocols/leiosfetch/server.rs | 206 ++++++++++++++++ pallas-network/src/miniprotocols/mod.rs | 3 + pallas-primitives/src/leios/model.rs | 2 + 7 files changed, 554 insertions(+) create mode 100644 pallas-network/src/miniprotocols/leiosfetch/client.rs create mode 100644 pallas-network/src/miniprotocols/leiosfetch/mod.rs create mode 100644 pallas-network/src/miniprotocols/leiosfetch/primitives.rs create mode 100644 pallas-network/src/miniprotocols/leiosfetch/protocol.rs create mode 100644 pallas-network/src/miniprotocols/leiosfetch/server.rs diff --git a/pallas-network/src/miniprotocols/leiosfetch/client.rs b/pallas-network/src/miniprotocols/leiosfetch/client.rs new file mode 100644 index 000000000..b7ecb495c --- /dev/null +++ b/pallas-network/src/miniprotocols/leiosfetch/client.rs @@ -0,0 +1,230 @@ +use std::fmt::Debug; +use thiserror::*; +use tracing::debug; + +use super::protocol::*; +use crate::{ + miniprotocols::leiosnotify::{Hash, Slot, VoteIssuerId}, + multiplexer, +}; + +#[derive(Error, Debug)] +pub enum ClientError { + #[error("attempted to receive message while agency is ours")] + AgencyIsOurs, + + #[error("attempted to send message while agency is theirs")] + AgencyIsTheirs, + + #[error("attempted to send message after protocol is done")] + ProtocolDone, + + #[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(multiplexer::Error), +} + +pub struct Client(State, multiplexer::ChannelBuffer); + +impl Client { + pub fn new(channel: multiplexer::AgentChannel) -> Self { + Self(State::Idle, multiplexer::ChannelBuffer::new(channel)) + } + + /// Returns the current state of the client. + pub fn state(&self) -> &State { + &self.0 + } + + /// Checks if the client is done. + pub fn is_done(&self) -> bool { + self.state() == &State::Done + } + + /// Checks if the client has agency. + fn has_agency(&self) -> bool { + self.state() == &State::Idle + } + + fn assert_agency_is_ours(&self) -> Result<(), ClientError> { + if self.is_done() { + Err(ClientError::ProtocolDone) + } else if !self.has_agency() { + Err(ClientError::AgencyIsTheirs) + } else { + Ok(()) + } + } + + fn assert_agency_is_theirs(&self) -> Result<(), ClientError> { + if self.has_agency() { + Err(ClientError::AgencyIsOurs) + } else if self.is_done() { + Err(ClientError::ProtocolDone) + } else { + Ok(()) + } + } + + fn assert_outbound_state(&self, msg: &Message) -> Result<(), ClientError> { + use Message::*; + + if self.state() == &State::Idle + && matches!( + msg, + BlockRequest(..) | BlockTxsRequest(..) | VoteRequest(..) | RangeRequest { .. } + ) + { + Ok(()) + } else { + Err(ClientError::InvalidOutbound) + } + } + + fn assert_inbound_state(&self, msg: &Message) -> Result<(), ClientError> { + use Message::*; + + match (self.state(), msg) { + (State::Block, Block(_)) => Ok(()), + (State::BlockTxs, BlockTxs(_)) => Ok(()), + (State::Votes, VoteDelivery(..)) => Ok(()), + (State::BlockRange, NextBlockAndTxs(..)) => Ok(()), + (State::BlockRange, LastBlockAndTxs(..)) => Ok(()), + _ => Err(ClientError::InvalidOutbound), + } + } + + pub async fn send_message(&mut self, msg: &Message) -> Result<(), ClientError> { + self.assert_agency_is_ours()?; + self.assert_outbound_state(msg)?; + self.1 + .send_msg_chunks(msg) + .await + .map_err(ClientError::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(ClientError::Plexer)?; + self.assert_inbound_state(&msg)?; + + Ok(msg) + } + + pub async fn send_block_request(&mut self, slot: Slot, hash: Hash) -> Result<(), ClientError> { + let msg = Message::BlockRequest(slot, hash); + self.send_message(&msg).await?; + self.0 = State::Block; + debug!("sent block request"); + + Ok(()) + } + + pub async fn send_block_txs_request( + &mut self, + slot: Slot, + hash: Hash, + tx_map: TxMap, + ) -> Result<(), ClientError> { + let msg = Message::BlockTxsRequest(slot, hash, tx_map); + self.send_message(&msg).await?; + self.0 = State::BlockTxs; + debug!("sent block and txs request"); + + Ok(()) + } + + pub async fn send_vote_request( + &mut self, + req: Vec<(Slot, VoteIssuerId)>, + ) -> Result<(), ClientError> { + let msg = Message::VoteRequest(req); + self.send_message(&msg).await?; + self.0 = State::Votes; + debug!("sent vote request"); + + Ok(()) + } + + pub async fn send_range_request( + &mut self, + first: (Slot, Hash), + last: (Slot, Hash), + ) -> Result<(), ClientError> { + let msg = Message::RangeRequest { first, last }; + self.send_message(&msg).await?; + self.0 = State::BlockRange; + debug!("sent vote request"); + + Ok(()) + } + + pub async fn send_done(&mut self) -> Result<(), ClientError> { + let msg = Message::Done; + self.send_message(&msg).await?; + self.0 = State::Done; + + Ok(()) + } + pub async fn recv_block(&mut self) -> Result { + let msg = self.recv_message().await?; + match msg { + Message::Block(block) => { + self.0 = State::Idle; + Ok(block) + } + _ => Err(ClientError::InvalidInbound), + } + } + + pub async fn recv_block_txs(&mut self) -> Result, ClientError> { + let msg = self.recv_message().await?; + match msg { + Message::BlockTxs(response) => { + tracing::trace!(?response, "received"); + self.0 = State::Idle; + Ok(response) + } + _ => Err(ClientError::InvalidInbound), + } + } + + pub async fn recv_vote_delivery(&mut self) -> Result, ClientError> { + let msg = self.recv_message().await?; + match msg { + Message::VoteDelivery(votes) => { + tracing::trace!(?votes, "received"); + self.0 = State::Idle; + Ok(votes) + } + _ => Err(ClientError::InvalidInbound), + } + } + + pub async fn recv_while_block_range( + &mut self, + ) -> Result<(EndorserBlock, Vec), ClientError> { + match self.recv_message().await? { + Message::NextBlockAndTxs(block, txs) => { + debug!("Receiving next block and txs"); + tracing::trace!(?block, ?txs, "received"); + self.0 = State::Idle; + Ok((block, txs)) + } + Message::LastBlockAndTxs(block, txs) => { + debug!("Receiving last block and txs"); + tracing::trace!(?block, ?txs, "received"); + self.0 = State::Idle; + Ok((block, txs)) + } + _ => Err(ClientError::InvalidInbound), + } + } +} diff --git a/pallas-network/src/miniprotocols/leiosfetch/mod.rs b/pallas-network/src/miniprotocols/leiosfetch/mod.rs new file mode 100644 index 000000000..c3cd06d4c --- /dev/null +++ b/pallas-network/src/miniprotocols/leiosfetch/mod.rs @@ -0,0 +1,8 @@ +mod client; +mod primitives; +mod protocol; +mod server; + +pub use client::*; +pub use protocol::*; +pub use server::*; diff --git a/pallas-network/src/miniprotocols/leiosfetch/primitives.rs b/pallas-network/src/miniprotocols/leiosfetch/primitives.rs new file mode 100644 index 000000000..18b24a69a --- /dev/null +++ b/pallas-network/src/miniprotocols/leiosfetch/primitives.rs @@ -0,0 +1,48 @@ +// Material brought from `pallas-primitives` +// TODO: Refactor in order to avoid repetition. + +use pallas_codec::{ + minicbor::{self, Decode, Encode}, + utils::Bytes, +}; + +use crate::miniprotocols::{leiosnotify::Hash, localtxsubmission::primitives::PoolKeyhash}; + +pub type BlsSignature = Bytes; // 48 bytes + +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)] +#[cbor(flat)] +pub enum LeiosVote { + #[n(0)] + Persistent { + #[n(0)] + election_id: Bytes, + + #[n(1)] + persistent_voter_id: Bytes, + + #[n(2)] + endorser_block_hash: Hash, + + #[n(3)] + vote_signature: BlsSignature, + }, + + #[n(1)] + NonPersistent { + #[n(0)] + election_id: Bytes, + + #[n(1)] + pool_id: PoolKeyhash, + + #[n(2)] + eligibility_signature: BlsSignature, + + #[n(3)] + endorser_block_hash: Hash, + + #[n(5)] + vote_signature: BlsSignature, + }, +} diff --git a/pallas-network/src/miniprotocols/leiosfetch/protocol.rs b/pallas-network/src/miniprotocols/leiosfetch/protocol.rs new file mode 100644 index 000000000..99e6c42bb --- /dev/null +++ b/pallas-network/src/miniprotocols/leiosfetch/protocol.rs @@ -0,0 +1,57 @@ +pub use pallas_codec::{ + minicbor::{self, Decode, Encode}, + utils::AnyCbor, +}; + +pub use crate::miniprotocols::leiosnotify::{ + BlockOffer, BlockTxsOffer, Hash, Header, Slot, VoteIssuerId, +}; +use std::{collections::BTreeMap, fmt::Debug}; + +pub use super::primitives::LeiosVote; + +pub type Tx = AnyCbor; // Mock Txs +pub type TxReference = Hash; +pub type EndorserBlock = Vec; +pub type BitMap = Vec<(u16, u64)>; +pub type TxMap = BTreeMap; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum State { + Idle, + Block, + BlockTxs, + Votes, + BlockRange, + Done, +} + +#[derive(Debug, Encode, Decode)] +#[cbor(flat)] +pub enum Message { + #[n(0)] + BlockRequest(#[n(0)] Slot, #[n(1)] Hash), + #[n(1)] + Block(#[n(0)] EndorserBlock), + #[n(2)] + BlockTxsRequest(#[n(0)] Slot, #[n(1)] Hash, #[n(2)] TxMap), + #[n(3)] + BlockTxs(#[n(0)] Vec), + #[n(4)] + VoteRequest(#[n(0)] Vec<(Slot, VoteIssuerId)>), + #[n(5)] + VoteDelivery(#[n(0)] Vec), + #[n(6)] + RangeRequest { + #[n(0)] + first: (Slot, Hash), + #[n(1)] + last: (Slot, Hash), + }, + #[n(7)] + NextBlockAndTxs(#[n(0)] EndorserBlock, #[n(1)] Vec), + #[n(8)] + LastBlockAndTxs(#[n(0)] EndorserBlock, #[n(1)] Vec), + #[n(9)] + Done, +} diff --git a/pallas-network/src/miniprotocols/leiosfetch/server.rs b/pallas-network/src/miniprotocols/leiosfetch/server.rs new file mode 100644 index 000000000..1ca892a14 --- /dev/null +++ b/pallas-network/src/miniprotocols/leiosfetch/server.rs @@ -0,0 +1,206 @@ +use std::fmt::Debug; +use thiserror::*; + +use super::protocol::*; +use crate::multiplexer; + +#[derive(Error, Debug)] +pub enum ServerError { + #[error("attempted to receive message while agency is ours")] + AgencyIsOurs, + + #[error("attempted to send message while agency is theirs")] + AgencyIsTheirs, + + #[error("attempted to send message after protocol is done")] + ProtocolDone, + + #[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(multiplexer::Error), +} + +#[derive(Debug)] +pub enum ClientRequest { + BlockRequest(Slot, Hash), + BlockTxsRequest(Slot, Hash, TxMap), + VoteRequest(Vec<(Slot, VoteIssuerId)>), + RangeRequest { + first: (Slot, Hash), + last: (Slot, Hash), + }, + Done, +} + +pub struct Server(State, multiplexer::ChannelBuffer); + +impl Server { + pub fn new(channel: multiplexer::AgentChannel) -> Self { + Self(State::Idle, multiplexer::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 { + use State::*; + + matches!(&self.0, Block | BlockTxs | Votes | BlockRange) + } + + fn assert_agency_is_ours(&self) -> Result<(), ServerError> { + if self.is_done() { + Err(ServerError::ProtocolDone) + } else if !self.has_agency() { + Err(ServerError::AgencyIsTheirs) + } else { + Ok(()) + } + } + + fn assert_agency_is_theirs(&self) -> Result<(), ServerError> { + if self.has_agency() { + Err(ServerError::AgencyIsOurs) + } else if self.is_done() { + Err(ServerError::ProtocolDone) + } else { + Ok(()) + } + } + + fn assert_outbound_state(&self, msg: &Message) -> Result<(), ServerError> { + use Message::*; + match (self.state(), msg) { + (State::Block, Block(_)) => Ok(()), + (State::BlockTxs, BlockTxs(_)) => Ok(()), + (State::Votes, VoteDelivery(..)) => Ok(()), + (State::BlockRange, NextBlockAndTxs(..)) => Ok(()), + (State::BlockRange, LastBlockAndTxs(..)) => Ok(()), + _ => Err(ServerError::InvalidOutbound), + } + } + + fn assert_inbound_state(&self, msg: &Message) -> Result<(), ServerError> { + use Message::*; + + if self.state() == &State::Idle + && matches!( + msg, + BlockRequest(..) | BlockTxsRequest(..) | VoteRequest(..) | RangeRequest { .. } + ) + { + Ok(()) + } else { + Err(ServerError::InvalidOutbound) + } + } + + pub async fn send_message(&mut self, msg: &Message) -> Result<(), ServerError> { + self.assert_agency_is_ours()?; + self.assert_outbound_state(msg)?; + self.1 + .send_msg_chunks(msg) + .await + .map_err(ServerError::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(ServerError::Plexer)?; + self.assert_inbound_state(&msg)?; + + Ok(msg) + } + + pub async fn recv_while_idle(&mut self) -> Result, ServerError> { + use ClientRequest::*; + + match self.recv_message().await? { + Message::BlockRequest(slot, hash) => { + self.0 = State::Block; + Ok(Some(BlockRequest(slot, hash))) + } + Message::BlockTxsRequest(slot, hash, tx_map) => { + self.0 = State::BlockTxs; + Ok(Some(BlockTxsRequest(slot, hash, tx_map))) + } + Message::VoteRequest(req) => { + self.0 = State::Votes; + Ok(Some(VoteRequest(req))) + } + Message::RangeRequest { first, last } => { + self.0 = State::BlockRange; + Ok(Some(RangeRequest { first, last })) + } + Message::Done => { + self.0 = State::Done; + + Ok(None) + } + _ => Err(ServerError::InvalidInbound), + } + } + + pub async fn send_block(&mut self, response: EndorserBlock) -> Result<(), ServerError> { + let msg = Message::Block(response); + self.send_message(&msg).await?; + self.0 = State::Idle; + + Ok(()) + } + + pub async fn send_block_txs(&mut self, response: Vec) -> Result<(), ServerError> { + let msg = Message::BlockTxs(response); + self.send_message(&msg).await?; + self.0 = State::Idle; + + Ok(()) + } + + pub async fn send_vote_delivery( + &mut self, + response: Vec, + ) -> Result<(), ServerError> { + let msg = Message::VoteDelivery(response); + self.send_message(&msg).await?; + self.0 = State::Idle; + + Ok(()) + } + + pub async fn send_next_block_and_txs( + &mut self, + block: EndorserBlock, + txs: Vec, + ) -> Result<(), ServerError> { + let msg = Message::NextBlockAndTxs(block, txs); + self.send_message(&msg).await?; + self.0 = State::BlockRange; + + Ok(()) + } + + pub async fn send_last_block_and_txs( + &mut self, + block: EndorserBlock, + txs: Vec, + ) -> Result<(), ServerError> { + let msg = Message::NextBlockAndTxs(block, txs); + self.send_message(&msg).await?; + self.0 = State::Idle; + + Ok(()) + } +} diff --git a/pallas-network/src/miniprotocols/mod.rs b/pallas-network/src/miniprotocols/mod.rs index b77b01917..71032d4c8 100644 --- a/pallas-network/src/miniprotocols/mod.rs +++ b/pallas-network/src/miniprotocols/mod.rs @@ -10,6 +10,9 @@ pub mod keepalive; #[cfg(feature = "leios")] pub mod leiosnotify; +#[cfg(feature = "leios")] +pub mod leiosfetch; + pub mod localmsgnotification; pub mod localmsgsubmission; pub mod localstate; diff --git a/pallas-primitives/src/leios/model.rs b/pallas-primitives/src/leios/model.rs index 309f07f41..5fc7a76fb 100644 --- a/pallas-primitives/src/leios/model.rs +++ b/pallas-primitives/src/leios/model.rs @@ -133,6 +133,8 @@ impl HeaderBody { pub type LeiosTx<'a> = KeepRaw<'a, Tx<'a>>; #[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)] +#[cbor(transparent)] +#[serde(transparent)] pub struct EndorserBlock { #[n(0)] pub transaction_references: Vec, From f873ef5a973ac28f2a20e09f73a3ffd6ebba72eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20S=C3=A1nchez=20Terraf?= Date: Wed, 22 Oct 2025 15:11:49 -0300 Subject: [PATCH 15/21] Missing infra for LeiosFetch --- pallas-network/src/facades.rs | 32 ++++++++++++++++++++-- pallas-network/src/miniprotocols/common.rs | 5 ++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/pallas-network/src/facades.rs b/pallas-network/src/facades.rs index f4b26b401..a6c8484b2 100644 --- a/pallas-network/src/facades.rs +++ b/pallas-network/src/facades.rs @@ -17,12 +17,13 @@ use crate::miniprotocols::{ PROTOCOL_N2C_HANDSHAKE, PROTOCOL_N2C_MSG_NOTIFICATION, PROTOCOL_N2C_MSG_SUBMISSION, PROTOCOL_N2C_STATE_QUERY, PROTOCOL_N2C_TX_MONITOR, PROTOCOL_N2C_TX_SUBMISSION, PROTOCOL_N2N_BLOCK_FETCH, PROTOCOL_N2N_CHAIN_SYNC, PROTOCOL_N2N_HANDSHAKE, - PROTOCOL_N2N_KEEP_ALIVE, PROTOCOL_N2N_LEIOS_NOTIFY, PROTOCOL_N2N_PEER_SHARING, - PROTOCOL_N2N_TX_SUBMISSION, + PROTOCOL_N2N_KEEP_ALIVE, PROTOCOL_N2N_PEER_SHARING, PROTOCOL_N2N_TX_SUBMISSION, }; #[cfg(feature = "leios")] -use crate::miniprotocols::leiosnotify; +use crate::miniprotocols::{ + leiosfetch, leiosnotify, PROTOCOL_N2N_LEIOS_FETCH, PROTOCOL_N2N_LEIOS_NOTIFY, +}; use crate::multiplexer::{self, Bearer, RunningPlexer}; @@ -113,6 +114,8 @@ pub struct PeerClient { pub peersharing: peersharing::Client, #[cfg(feature = "leios")] pub leiosnotify: leiosnotify::Client, + #[cfg(feature = "leios")] + pub leiosfetch: leiosfetch::Client, } impl PeerClient { @@ -130,7 +133,10 @@ impl PeerClient { let bf_channel = plexer.subscribe_client(PROTOCOL_N2N_BLOCK_FETCH); let txsub_channel = plexer.subscribe_client(PROTOCOL_N2N_TX_SUBMISSION); let peersharing_channel = plexer.subscribe_client(PROTOCOL_N2N_PEER_SHARING); + #[cfg(feature = "leios")] let leiosnotify_channel = plexer.subscribe_client(PROTOCOL_N2N_LEIOS_NOTIFY); + #[cfg(feature = "leios")] + let leiosfetch_channel = plexer.subscribe_client(PROTOCOL_N2N_LEIOS_FETCH); let channel = plexer.subscribe_client(PROTOCOL_N2N_KEEP_ALIVE); let keepalive = keepalive::Client::new(channel); @@ -164,6 +170,8 @@ impl PeerClient { peersharing: peersharing::Client::new(peersharing_channel), #[cfg(feature = "leios")] leiosnotify: leiosnotify::Client::new(leiosnotify_channel), + #[cfg(feature = "leios")] + leiosfetch: leiosfetch::Client::new(leiosfetch_channel), }; Ok(client) @@ -239,6 +247,11 @@ impl PeerClient { &mut self.leiosnotify } + #[cfg(feature = "leios")] + pub fn leiosfetch(&mut self) -> &mut leiosfetch::Client { + &mut self.leiosfetch + } + pub async fn abort(self) { self.plexer.abort().await } @@ -255,6 +268,8 @@ pub struct PeerServer { pub peersharing: peersharing::Server, #[cfg(feature = "leios")] pub leiosnotify: leiosnotify::Server, + #[cfg(feature = "leios")] + pub leiosfetch: leiosfetch::Server, accepted_address: Option, accepted_version: Option<(u64, n2n::VersionData)>, } @@ -271,6 +286,8 @@ impl PeerServer { let peersharing_channel = plexer.subscribe_server(PROTOCOL_N2N_PEER_SHARING); #[cfg(feature = "leios")] let leiosnotify_channel = plexer.subscribe_server(PROTOCOL_N2N_LEIOS_NOTIFY); + #[cfg(feature = "leios")] + let leiosfetch_channel = plexer.subscribe_server(PROTOCOL_N2N_LEIOS_FETCH); let hs = handshake::N2NServer::new(hs_channel); let cs = chainsync::N2NServer::new(cs_channel); @@ -280,6 +297,8 @@ impl PeerServer { let peersharing = peersharing::Server::new(peersharing_channel); #[cfg(feature = "leios")] let leiosnotify = leiosnotify::Server::new(leiosnotify_channel); + #[cfg(feature = "leios")] + let leiosfetch = leiosfetch::Server::new(leiosfetch_channel); let plexer = plexer.spawn(); @@ -293,6 +312,8 @@ impl PeerServer { peersharing, #[cfg(feature = "leios")] leiosnotify, + #[cfg(feature = "leios")] + leiosfetch, accepted_address: None, accepted_version: None, } @@ -350,6 +371,11 @@ impl PeerServer { &mut self.leiosnotify } + #[cfg(feature = "leios")] + pub fn leiosfetch(&mut self) -> &mut leiosfetch::Server { + &mut self.leiosfetch + } + pub fn accepted_address(&self) -> Option<&SocketAddr> { self.accepted_address.as_ref() } diff --git a/pallas-network/src/miniprotocols/common.rs b/pallas-network/src/miniprotocols/common.rs index 0bfbf14ea..532d33746 100644 --- a/pallas-network/src/miniprotocols/common.rs +++ b/pallas-network/src/miniprotocols/common.rs @@ -54,9 +54,14 @@ pub const PROTOCOL_N2N_KEEP_ALIVE: u16 = 8; /// Protocol channel number for node-to-node Peer-sharing pub const PROTOCOL_N2N_PEER_SHARING: u16 = 10; +#[cfg(feature = "leios")] /// Protocol channel number for node-to-node Leios Notify pub const PROTOCOL_N2N_LEIOS_NOTIFY: u16 = 12; +#[cfg(feature = "leios")] +/// Protocol channel number for node-to-node Leios Notify +pub const PROTOCOL_N2N_LEIOS_FETCH: u16 = 14; + /// Protocol channel number for node-to-client handshakes pub const PROTOCOL_N2C_HANDSHAKE: u16 = 0; From 81bdbb61c6948038c403a2edf8b8c34697d0d196 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20S=C3=A1nchez=20Terraf?= Date: Wed, 22 Oct 2025 17:18:42 -0300 Subject: [PATCH 16/21] simple test for LeiosFetch and fixes arising from it --- .../src/miniprotocols/leiosfetch/client.rs | 8 +- .../src/miniprotocols/leiosfetch/server.rs | 10 +- pallas-network/tests/leiosfetch.rs | 95 +++++++++++++++++++ pallas-network/tests/leiosnotify.rs | 10 +- 4 files changed, 112 insertions(+), 11 deletions(-) create mode 100644 pallas-network/tests/leiosfetch.rs diff --git a/pallas-network/src/miniprotocols/leiosfetch/client.rs b/pallas-network/src/miniprotocols/leiosfetch/client.rs index b7ecb495c..a1fb205b1 100644 --- a/pallas-network/src/miniprotocols/leiosfetch/client.rs +++ b/pallas-network/src/miniprotocols/leiosfetch/client.rs @@ -77,7 +77,11 @@ impl Client { if self.state() == &State::Idle && matches!( msg, - BlockRequest(..) | BlockTxsRequest(..) | VoteRequest(..) | RangeRequest { .. } + BlockRequest(..) + | BlockTxsRequest(..) + | VoteRequest(..) + | RangeRequest { .. } + | Done ) { Ok(()) @@ -95,7 +99,7 @@ impl Client { (State::Votes, VoteDelivery(..)) => Ok(()), (State::BlockRange, NextBlockAndTxs(..)) => Ok(()), (State::BlockRange, LastBlockAndTxs(..)) => Ok(()), - _ => Err(ClientError::InvalidOutbound), + _ => Err(ClientError::InvalidInbound), } } diff --git a/pallas-network/src/miniprotocols/leiosfetch/server.rs b/pallas-network/src/miniprotocols/leiosfetch/server.rs index 1ca892a14..31ffd25a8 100644 --- a/pallas-network/src/miniprotocols/leiosfetch/server.rs +++ b/pallas-network/src/miniprotocols/leiosfetch/server.rs @@ -25,7 +25,7 @@ pub enum ServerError { Plexer(multiplexer::Error), } -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub enum ClientRequest { BlockRequest(Slot, Hash), BlockTxsRequest(Slot, Hash, TxMap), @@ -96,12 +96,16 @@ impl Server { if self.state() == &State::Idle && matches!( msg, - BlockRequest(..) | BlockTxsRequest(..) | VoteRequest(..) | RangeRequest { .. } + BlockRequest(..) + | BlockTxsRequest(..) + | VoteRequest(..) + | RangeRequest { .. } + | Done ) { Ok(()) } else { - Err(ServerError::InvalidOutbound) + Err(ServerError::InvalidInbound) } } diff --git a/pallas-network/tests/leiosfetch.rs b/pallas-network/tests/leiosfetch.rs new file mode 100644 index 000000000..9a951fbea --- /dev/null +++ b/pallas-network/tests/leiosfetch.rs @@ -0,0 +1,95 @@ +#![cfg(feature = "leios")] +use pallas_network::{ + facades::{PeerClient, PeerServer}, + miniprotocols::leiosfetch::{self, ClientRequest}, +}; +use std::{ + net::{Ipv4Addr, SocketAddrV4}, + time::Duration, +}; + +use tokio::net::TcpListener; + +#[cfg(unix)] +#[tokio::test] +pub async fn leiosfetch_server_and_client_happy_path() { + use tracing::debug; + + tracing_subscriber::fmt::init(); + + let block_hash: leiosfetch::Hash = + hex::decode("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef").unwrap(); + + let endorser_block: leiosfetch::EndorserBlock = vec![]; + + let rb_header: leiosfetch::Header = + hex::decode("eade0000eade0000eade0000eade0000eade0000eade0000eade0000eade0000").unwrap(); + + let block_txs_hash: leiosfetch::Hash = + hex::decode("bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0").unwrap(); + + let block_slot: leiosfetch::Slot = 123456789; + let _block_txs_slot: leiosfetch::Slot = 222222222; + + let vote_issuer_id: leiosfetch::Hash = + hex::decode("beedbeedbeedbeedbeedbeedbeedbeedbeedbeedbeedbeedbeedbeedbeedbeed").unwrap(); + + let listener = TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 30003)) + .await + .unwrap(); + + let server = tokio::spawn({ + let _header = rb_header.clone(); + let block_hash = block_hash.clone(); + let block = endorser_block.clone(); + let _block_txs_hash = block_txs_hash.clone(); + let _vote_issuer_id = vote_issuer_id.clone(); + + async move { + // server setup + + let mut peer_server = PeerServer::accept(&listener, 0).await.unwrap(); + + let server_lf = peer_server.leiosfetch(); + + // server receives `BlockRequest` from client + debug!("server waiting for block request"); + assert_eq!( + server_lf.recv_while_idle().await.unwrap().unwrap(), + ClientRequest::BlockRequest(block_slot, block_hash), + ); + assert_eq!(*server_lf.state(), leiosfetch::State::Block); + + // Server sends EB + server_lf.send_block(block).await.unwrap(); + assert_eq!(*server_lf.state(), leiosfetch::State::Idle); + + // Server receives Done message from client + assert!(server_lf.recv_while_idle().await.unwrap().is_none()); + assert_eq!(*server_lf.state(), leiosfetch::State::Done); + } + }); + + let client = tokio::spawn(async move { + tokio::time::sleep(Duration::from_secs(1)).await; + + // client setup + let mut client_to_server_conn = PeerClient::connect("localhost:30003", 0).await.unwrap(); + + let client_lf = client_to_server_conn.leiosfetch(); + + // client sends `BlockRequest`, receives block announcement + client_lf + .send_block_request(block_slot, block_hash) + .await + .unwrap(); + assert_eq!(client_lf.recv_block().await.unwrap(), endorser_block); + assert_eq!(*client_lf.state(), leiosfetch::State::Idle); + + // client sends Done + client_lf.send_done().await.unwrap(); + assert!(client_lf.is_done()) + }); + + tokio::try_join!(client, server).unwrap(); +} diff --git a/pallas-network/tests/leiosnotify.rs b/pallas-network/tests/leiosnotify.rs index f863edd87..b82719dc7 100644 --- a/pallas-network/tests/leiosnotify.rs +++ b/pallas-network/tests/leiosnotify.rs @@ -1,6 +1,4 @@ #![cfg(feature = "leios")] -// use pallas_codec::utils::AnyCbor; -// use pallas_crypto::hash::Hash; use pallas_network::{ facades::{PeerClient, PeerServer}, miniprotocols::leiosnotify, @@ -52,7 +50,7 @@ pub async fn leiosnotify_server_and_client_happy_path() { let server_ln = peer_server.leiosnotify(); // server receives `RequestNext` from client - debug!("server waiting for share request"); + debug!("server waiting for request next"); server_ln.recv_request_next().await.unwrap(); assert_eq!(*server_ln.state(), leiosnotify::State::Busy); @@ -64,7 +62,7 @@ pub async fn leiosnotify_server_and_client_happy_path() { assert_eq!(*server_ln.state(), leiosnotify::State::Idle); // server receives `RequestNext` from client - debug!("server waiting for share request"); + debug!("server waiting for request next"); server_ln.recv_request_next().await.unwrap(); assert_eq!(*server_ln.state(), leiosnotify::State::Busy); @@ -76,7 +74,7 @@ pub async fn leiosnotify_server_and_client_happy_path() { assert_eq!(*server_ln.state(), leiosnotify::State::Idle); // server receives `RequestNext` from client - debug!("server waiting for share request"); + debug!("server waiting for request next"); server_ln.recv_request_next().await.unwrap(); assert_eq!(*server_ln.state(), leiosnotify::State::Busy); @@ -88,7 +86,7 @@ pub async fn leiosnotify_server_and_client_happy_path() { assert_eq!(*server_ln.state(), leiosnotify::State::Idle); // server receives `RequestNext` from client - debug!("server waiting for share request"); + debug!("server waiting for request next"); server_ln.recv_request_next().await.unwrap(); assert_eq!(*server_ln.state(), leiosnotify::State::Busy); From 7c7b204daa477111183f2a0922e66f55e92382df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20S=C3=A1nchez=20Terraf?= Date: Wed, 22 Oct 2025 18:18:33 -0300 Subject: [PATCH 17/21] cert in last position --- pallas-primitives/src/leios/defs.cddl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallas-primitives/src/leios/defs.cddl b/pallas-primitives/src/leios/defs.cddl index 4e184d8af..6d63342e4 100644 --- a/pallas-primitives/src/leios/defs.cddl +++ b/pallas-primitives/src/leios/defs.cddl @@ -5,8 +5,8 @@ block = , transaction_bodies : [* transaction_body] , transaction_witness_sets : [* transaction_witness_set] , auxiliary_data_set : {* transaction_index => auxiliary_data} - , ? eb_certificate : leios_certificate , invalid_transactions : [* transaction_index] + , ? eb_certificate : leios_certificate ] header = [header_body, body_signature : kes_signature] From 0478e431908ca8db7064704daf1069b0cac30c51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20S=C3=A1nchez=20Terraf?= Date: Mon, 27 Oct 2025 19:57:06 -0300 Subject: [PATCH 18/21] Updated LeiosFetch test with data from demo --- .../src/miniprotocols/leiosfetch/protocol.rs | 8 +- pallas-network/tests/leiosfetch.rs | 133 +++++++++++++++++- 2 files changed, 131 insertions(+), 10 deletions(-) diff --git a/pallas-network/src/miniprotocols/leiosfetch/protocol.rs b/pallas-network/src/miniprotocols/leiosfetch/protocol.rs index 99e6c42bb..5344f017e 100644 --- a/pallas-network/src/miniprotocols/leiosfetch/protocol.rs +++ b/pallas-network/src/miniprotocols/leiosfetch/protocol.rs @@ -1,6 +1,6 @@ pub use pallas_codec::{ minicbor::{self, Decode, Encode}, - utils::AnyCbor, + utils::{AnyCbor, Bytes}, }; pub use crate::miniprotocols::leiosnotify::{ @@ -11,9 +11,9 @@ use std::{collections::BTreeMap, fmt::Debug}; pub use super::primitives::LeiosVote; pub type Tx = AnyCbor; // Mock Txs -pub type TxReference = Hash; -pub type EndorserBlock = Vec; -pub type BitMap = Vec<(u16, u64)>; +pub type TxHash = Hash; +pub type EndorserBlock = BTreeMap; +pub type BitMap = u64; pub type TxMap = BTreeMap; #[derive(Debug, PartialEq, Eq, Clone)] diff --git a/pallas-network/tests/leiosfetch.rs b/pallas-network/tests/leiosfetch.rs index 9a951fbea..f16c2c91d 100644 --- a/pallas-network/tests/leiosfetch.rs +++ b/pallas-network/tests/leiosfetch.rs @@ -1,7 +1,7 @@ #![cfg(feature = "leios")] use pallas_network::{ facades::{PeerClient, PeerServer}, - miniprotocols::leiosfetch::{self, ClientRequest}, + miniprotocols::leiosfetch::{self, minicbor, AnyCbor, ClientRequest}, }; use std::{ net::{Ipv4Addr, SocketAddrV4}, @@ -18,9 +18,19 @@ pub async fn leiosfetch_server_and_client_happy_path() { tracing_subscriber::fmt::init(); let block_hash: leiosfetch::Hash = - hex::decode("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef").unwrap(); + hex::decode("c579268ab0275662d47a3fe2dfcb41981426ddfc217ed3091364ae8f58198809").unwrap(); - let endorser_block: leiosfetch::EndorserBlock = vec![]; + // CBOR bytes obtained from `leiosdemo202510` binary @ ccbe69384bd3d352dc5d31 + let endorser_block = hex::decode( + "bf5820521cacab5d8886db5c111290f8901276a44bc3f3b11b781bef5233\ + ddab1b2db618375820daa5ecee19aa3f240024a59103b37ceb3f4dc7d7ea\ + d8b0c675ff5939d7faa143183758200b1457b31bd0d0293cde0ca2b9f4d4\ + 8707e63d2959914c78a798536f9d310850183758205723adfca7765e74f4\ + a0659abeaffadc09be35325aa306e3ff1f6f4f74bb47491903e8ff", + ) + .unwrap(); + + let endorser_block: leiosfetch::EndorserBlock = minicbor::decode(&endorser_block).unwrap(); let rb_header: leiosfetch::Header = hex::decode("eade0000eade0000eade0000eade0000eade0000eade0000eade0000eade0000").unwrap(); @@ -28,7 +38,10 @@ pub async fn leiosfetch_server_and_client_happy_path() { let block_txs_hash: leiosfetch::Hash = hex::decode("bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0").unwrap(); - let block_slot: leiosfetch::Slot = 123456789; + // Selects first 3 transactions + let tx_map = leiosfetch::TxMap::from([(0, 0xe000000000000000)]); + + let block_slot: leiosfetch::Slot = 5; let _block_txs_slot: leiosfetch::Slot = 222222222; let vote_issuer_id: leiosfetch::Hash = @@ -42,7 +55,8 @@ pub async fn leiosfetch_server_and_client_happy_path() { let _header = rb_header.clone(); let block_hash = block_hash.clone(); let block = endorser_block.clone(); - let _block_txs_hash = block_txs_hash.clone(); + let tx_map = tx_map.clone(); + let block_txs_hash = block_txs_hash.clone(); let _vote_issuer_id = vote_issuer_id.clone(); async move { @@ -64,6 +78,21 @@ pub async fn leiosfetch_server_and_client_happy_path() { server_lf.send_block(block).await.unwrap(); assert_eq!(*server_lf.state(), leiosfetch::State::Idle); + // server receives `BlockTxsRequest` from client + debug!("server waiting for txs request"); + assert_eq!( + server_lf.recv_while_idle().await.unwrap().unwrap(), + ClientRequest::BlockTxsRequest(block_slot, block_txs_hash, tx_map.clone()), + ); + assert_eq!(*server_lf.state(), leiosfetch::State::BlockTxs); + + // Server selects Txs according to map and sends + server_lf + .send_block_txs(tx_selection(tx_map, &eb_tx())) + .await + .unwrap(); + assert_eq!(*server_lf.state(), leiosfetch::State::Idle); + // Server receives Done message from client assert!(server_lf.recv_while_idle().await.unwrap().is_none()); assert_eq!(*server_lf.state(), leiosfetch::State::Done); @@ -78,7 +107,7 @@ pub async fn leiosfetch_server_and_client_happy_path() { let client_lf = client_to_server_conn.leiosfetch(); - // client sends `BlockRequest`, receives block announcement + // client sends `BlockRequest`, receives endorser block client_lf .send_block_request(block_slot, block_hash) .await @@ -86,6 +115,13 @@ pub async fn leiosfetch_server_and_client_happy_path() { assert_eq!(client_lf.recv_block().await.unwrap(), endorser_block); assert_eq!(*client_lf.state(), leiosfetch::State::Idle); + // client sends `BlockTxsRequest`, receives vec of txs + client_lf + .send_block_txs_request(block_slot, block_txs_hash, tx_map) + .await + .unwrap(); + assert_eq!(client_lf.recv_block_txs().await.unwrap(), eb_tx()[0..3]); + // client sends Done client_lf.send_done().await.unwrap(); assert!(client_lf.is_done()) @@ -93,3 +129,88 @@ pub async fn leiosfetch_server_and_client_happy_path() { tokio::try_join!(client, server).unwrap(); } + +fn bitmap_to_indices(bitmap: u64) -> Vec { + (0..64) + .rev() + .enumerate() + .filter(|(_, y)| (bitmap >> y) & 1 == 1) + .map(|(x, _)| x) + .collect() +} + +fn tx_selection(tx_map: leiosfetch::TxMap, data: &[AnyCbor]) -> Vec { + tx_map + .into_iter() + .map(|(index, bitmap)| { + bitmap_to_indices(bitmap) + .into_iter() + .map(move |i| data[64 * index as usize + i].clone()) + }) + .flatten() + .collect() +} + +fn eb_tx() -> Vec { + vec![ + hex::decode( + "58359719B92F47E7ABC8436813A42C1A5780C4ADDBF008E58E6CB8A4A3142067\ + E2BD47E713EBDB3672446C8DD5697D6F29477DA5ABD6F9", + ) + .unwrap(), + hex::decode( + "583551C27E9FD7D03351C243B98F6E33E9D29AD62CE9061580358B9CD4754505\ + 7B54A726322F849C5D73C01AE9881AA458F3A5F9DEA664", + ) + .unwrap(), + hex::decode( + "58356764A66870461BD63041BF1028FF898BDC58E95DA9EA6E684EBCC225F97A\ + ECF647BC7EA72BAC069D1FF9E3E9CB59C72181585FD4F0", + ) + .unwrap(), + hex::decode( + "5903E584035557626AE726D5BCE067C798B43B3DE035C3618F86CA1CF31969EB\ + B6711D354C445650D52E34F9E9A2057ECB363FE04FD3D5CE76B05E7C0CE7C563\ + C8F89AF65F3B57D6E34481A13889FACCE87AF020F0044B5EEA3C1BD48387506D\ + BD3C75ED4B9EFD7605DC3571A95B6E97F349C61C5D444A93DDE14F27C7B6EF74\ + F802EA1AB809ECBBEFD9229A85B42BC959B70BD207C06F30675B177096931759\ + 462E64B9F9F90EA5E5C5AA975A454F12AC6E4D21BC641A00B994B15E54BE2D79\ + 382A5ECF65BAA76496433D191CD0BEEB1AD979CD070CDC94FFFECD01CB3BF1E9\ + 86FEA8FE343C419AE71FC9CE7053697BCB75A45552006EFB1D4F36A34E9D70FE\ + 663C5B28D497373DB42AE1A6B8B5BD05390FBF580FCD75D857C9047FBB2A3FA8\ + 265702FD21773E124A5338E88D922A892331B9A7EE3F7375F9864E6990901D32\ + 3E37AB088528FC456B9082F40527C9565248D1D0403CEBEAE8BE8DDF290D0C0F\ + C415487747EFA5D256FA3F997E0D0F111C9F22D9F41C384C0FAA22AFE97BCCCB\ + D663268AE89A7BEC8898D5CEED1ECDFABC33205F8B01CEC18079B03BB7D5BBD8\ + EF80D6FB65FDC4F0445C8712CD717E5879663400652C16C8ECA980AFEC745A2C\ + C17D6A3EA1F9D2A4B0D534F784B35BAD97CCBB495E961D010C0A3FCF89FE7EAE\ + 091B00991EFF8BDB6E36C47FCBD1620130CAE67D68E68CFBE8D43BEBBA8B2331\ + F89F931D9FAA722789BFF1A6A0070480D87D59A94C62A8944EF5D327E7200030\ + 5502F26E7F3FF43C7C46097204C449F07C2F3DA9A9962B7AE51E6117FBF2B591\ + AB4273BA88F9C758EE64CF10FB2BF5F25B0B287F5081A79CEFDBBB0CBB70B9D9\ + DACBC1868C37B731C6C73F49F31C4F047D236DF3ED0BD2C41F4F19B9164D2DA3\ + CAC0067168746965C1B77EDE72A35F0BBD478FF21AE128D20FED009FCA1653CC\ + 16B7DE7F4FC1FBA75062B2E41BA0FFCBB8CA7213694C6947678BA2547BEF34FE\ + CD165A8ABB1DF0E52EBC0600361EFDE93031B290FA63F72F7DBA8F94FB34E6E3\ + 331C84367E4E887BBE982A905564993D7432BD2FE60061B39F0411486669FACA\ + F43E2A589EEBCC635F3D1C887C8444BD8994C2AE726F402CC846E6E150688FA9\ + EEAF836AC0EA978C776C4A14B4ECD9A54104A0D4FA8EEABBB5FBD4EEE80A19A0\ + 01547A1893BF3FAFF98994AD3E127CC4E35E13DA8EDF587DE0DB61824B2601C0\ + 46B83088A95B3DAE5CE118516F7E95E90DBD22A7315A1B990FBB81C264D4E903\ + 5935536ED84FF3D9951EED006ADB6C15F09691DC27037F19227004AE54D682F3\ + 6EE41C20A27E07F10CC3BF2CF68C92E4429D9AA75D2AE487C759AD1EF37263F3\ + 0BD4A50B4145C2B41C833C382FE4A5D15456346BF039A1E840BBF32F99AC80B4\ + A1930D5E838254F5", + ) + .unwrap(), + ] + .into_iter() + .map(|x| minicbor::decode(&x).unwrap()) + .collect() +} + +// For testing purposes: +// +// println!("{:?}", bitmap_to_indices(0xd000000000000002)); // [0, 1, 3, 62] +// println!("{:?}", bitmap_to_indices(0xe000000000000000)); // [0, 1, 2] +// println!("{:02X?}", tx_selection(0, 0x4000000000000000, &eb_tx())); // [second tx] From 2018273c43553be020b80dcaefcef7b5abe460de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20S=C3=A1nchez=20Terraf?= Date: Tue, 28 Oct 2025 16:42:31 -0300 Subject: [PATCH 19/21] Moving helper fns and adding tests. Feature flag at `pallas` --- pallas-network/src/facades.rs | 1 + .../src/miniprotocols/leiosfetch/server.rs | 74 +++++++++++++++++++ pallas-network/tests/leiosfetch.rs | 31 +------- pallas/Cargo.toml | 1 + 4 files changed, 78 insertions(+), 29 deletions(-) diff --git a/pallas-network/src/facades.rs b/pallas-network/src/facades.rs index a6c8484b2..74069f18e 100644 --- a/pallas-network/src/facades.rs +++ b/pallas-network/src/facades.rs @@ -11,6 +11,7 @@ use tokio::net::{unix::SocketAddr as UnixSocketAddr, UnixListener}; use crate::miniprotocols::handshake::n2n::VersionData; use crate::miniprotocols::handshake::{n2c, n2n, Confirmation, VersionNumber, VersionTable}; + use crate::miniprotocols::{ blockfetch, chainsync, handshake, keepalive, localmsgnotification, localmsgsubmission, localstate, localtxsubmission, peersharing, txmonitor, txsubmission, PROTOCOL_N2C_CHAIN_SYNC, diff --git a/pallas-network/src/miniprotocols/leiosfetch/server.rs b/pallas-network/src/miniprotocols/leiosfetch/server.rs index 31ffd25a8..dd67d3e75 100644 --- a/pallas-network/src/miniprotocols/leiosfetch/server.rs +++ b/pallas-network/src/miniprotocols/leiosfetch/server.rs @@ -208,3 +208,77 @@ impl Server { Ok(()) } } + +pub fn bitmap_to_indices(bitmap: u64) -> Vec { + (0..64) + .rev() + .enumerate() + .filter(|(_, y)| (bitmap >> y) & 1 == 1) + .map(|(x, _)| x) + .collect() +} + +pub fn bitmap_selection, Tx: Clone>( + tx_map: TMap, + data: &[Tx], +) -> Vec { + tx_map + .into_iter() + .map(|(index, bitmap)| { + bitmap_to_indices(bitmap) + .into_iter() + .map(move |i| data[64 * index as usize + i].clone()) + }) + .flatten() + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::BTreeMap; + + #[test] + fn test_bitmap_indices() { + assert_eq!(bitmap_to_indices(0xd000000000000002), vec![0, 1, 3, 62]); + assert_eq!(bitmap_to_indices(0xe000000000000000), vec![0, 1, 2]); + } + + #[test] + fn test_bitmap_selection() { + let map = BTreeMap::::from([(0, 0x5000000000000000), (1, 0x8000000000000000)]); + assert_eq!( + bitmap_selection(map, &eb_tx()), + [eb_tx()[1].clone(), eb_tx()[3].clone(), eb_tx()[64].clone()] + ); + } + + fn eb_tx() -> Vec> { + let mut list = vec![ + hex::decode( + "58359719B92F47E7ABC8436813A42C1A5780C4ADDBF008E58E6CB8A4A3142067\ + E2BD47E713EBDB3672446C8DD5697D6F29477DA5ABD6F9", + ) + .unwrap(), + hex::decode( + "583551C27E9FD7D03351C243B98F6E33E9D29AD62CE9061580358B9CD4754505\ + 7B54A726322F849C5D73C01AE9881AA458F3A5F9DEA664", + ) + .unwrap(), + hex::decode( + "58356764A66870461BD63041BF1028FF898BDC58E95DA9EA6E684EBCC225F97A\ + ECF647BC7EA72BAC069D1FF9E3E9CB59C72181585FD4F0", + ) + .unwrap(), + hex::decode( + "5903E584035557626AE726D5BCE067C798B43B3DE035C3618F86CA1CF31969EB\ + B6711D354C445650D52E34F9E9A2057ECB363FE04FD3D5CE76B05E7C0CE7C563", + ) + .unwrap(), + ]; + list.append(&mut vec![vec![]; 60]); + list.append(&mut vec![vec![10]]); + + list + } +} diff --git a/pallas-network/tests/leiosfetch.rs b/pallas-network/tests/leiosfetch.rs index f16c2c91d..e807d67d6 100644 --- a/pallas-network/tests/leiosfetch.rs +++ b/pallas-network/tests/leiosfetch.rs @@ -1,7 +1,7 @@ #![cfg(feature = "leios")] use pallas_network::{ facades::{PeerClient, PeerServer}, - miniprotocols::leiosfetch::{self, minicbor, AnyCbor, ClientRequest}, + miniprotocols::leiosfetch::{self, bitmap_selection, minicbor, AnyCbor, ClientRequest}, }; use std::{ net::{Ipv4Addr, SocketAddrV4}, @@ -88,7 +88,7 @@ pub async fn leiosfetch_server_and_client_happy_path() { // Server selects Txs according to map and sends server_lf - .send_block_txs(tx_selection(tx_map, &eb_tx())) + .send_block_txs(bitmap_selection(tx_map, &eb_tx())) .await .unwrap(); assert_eq!(*server_lf.state(), leiosfetch::State::Idle); @@ -130,27 +130,6 @@ pub async fn leiosfetch_server_and_client_happy_path() { tokio::try_join!(client, server).unwrap(); } -fn bitmap_to_indices(bitmap: u64) -> Vec { - (0..64) - .rev() - .enumerate() - .filter(|(_, y)| (bitmap >> y) & 1 == 1) - .map(|(x, _)| x) - .collect() -} - -fn tx_selection(tx_map: leiosfetch::TxMap, data: &[AnyCbor]) -> Vec { - tx_map - .into_iter() - .map(|(index, bitmap)| { - bitmap_to_indices(bitmap) - .into_iter() - .map(move |i| data[64 * index as usize + i].clone()) - }) - .flatten() - .collect() -} - fn eb_tx() -> Vec { vec![ hex::decode( @@ -208,9 +187,3 @@ fn eb_tx() -> Vec { .map(|x| minicbor::decode(&x).unwrap()) .collect() } - -// For testing purposes: -// -// println!("{:?}", bitmap_to_indices(0xd000000000000002)); // [0, 1, 3, 62] -// println!("{:?}", bitmap_to_indices(0xe000000000000000)); // [0, 1, 2] -// println!("{:02X?}", tx_selection(0, 0x4000000000000000, &eb_tx())); // [second tx] diff --git a/pallas/Cargo.toml b/pallas/Cargo.toml index 37c327ce2..ea5d02f6e 100644 --- a/pallas/Cargo.toml +++ b/pallas/Cargo.toml @@ -26,6 +26,7 @@ pallas-hardano = { version = "=1.0.0-alpha.2", path = "../pallas-hardano/", opti [features] hardano = ["pallas-hardano"] unstable = ["hardano", "pallas-traverse/unstable"] +leios = ["pallas-network/leios", "pallas-primitives/leios", "pallas-traverse/leios"] # pallas-validate feature flags phase2 = ["pallas-validate/phase2"] From f7187b2e0f9a979894c4a51ffa3e02d3bc977917 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20S=C3=A1nchez=20Terraf?= Date: Tue, 28 Oct 2025 20:59:34 -0300 Subject: [PATCH 20/21] Demo binary for `LeiosFetch` --- Cargo.toml | 1 + examples/leios-fetch-demo/Cargo.toml | 13 ++ examples/leios-fetch-demo/src/main.rs | 239 ++++++++++++++++++++++++++ 3 files changed, 253 insertions(+) create mode 100644 examples/leios-fetch-demo/Cargo.toml create mode 100644 examples/leios-fetch-demo/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index e5d298467..dc978366e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,4 +21,5 @@ members = [ "examples/n2n-miniprotocols", "examples/n2c-miniprotocols", "examples/p2p", + "examples/leios-fetch-demo", ] diff --git a/examples/leios-fetch-demo/Cargo.toml b/examples/leios-fetch-demo/Cargo.toml new file mode 100644 index 000000000..57d3f9f02 --- /dev/null +++ b/examples/leios-fetch-demo/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "leios-fetch-demo" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +pallas = { path = "../../pallas", features = ["leios"]} +hex = "0.4.3" +tracing = "0.1.37" +tracing-subscriber = "0.3.16" +tokio = { version = "1.27.0", features = ["rt-multi-thread"] } +clap = { version = "4.5.50", features = ["derive"] } diff --git a/examples/leios-fetch-demo/src/main.rs b/examples/leios-fetch-demo/src/main.rs new file mode 100644 index 000000000..dfe9b357f --- /dev/null +++ b/examples/leios-fetch-demo/src/main.rs @@ -0,0 +1,239 @@ +use pallas::network::{ + facades::{PeerClient, PeerServer}, + miniprotocols::leiosfetch::{self, bitmap_selection, minicbor, AnyCbor, ClientRequest}, +}; +use std::{ + net::{Ipv4Addr, SocketAddrV4}, + time::Duration, +}; + +use tokio::net::TcpListener; +use tracing::{debug, info}; + +use clap::Parser; + +/// Demonstration of `LeiosFetch` miniprotocol +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +struct Args { + /// Which part we take in the miniprotocol ("server" or "client"). + #[arg(short, long)] + role: String, + + /// Connect through this port + #[arg(short, long, default_value_t = 30003)] + port: u16, +} + +async fn server(port: u16) { + let block_hash: leiosfetch::Hash = + hex::decode("c579268ab0275662d47a3fe2dfcb41981426ddfc217ed3091364ae8f58198809").unwrap(); + + // CBOR bytes obtained from `leiosdemo202510` binary @ ccbe69384bd3d352dc5d31 + let endorser_block = hex::decode( + "bf5820521cacab5d8886db5c111290f8901276a44bc3f3b11b781bef5233\ + ddab1b2db618375820daa5ecee19aa3f240024a59103b37ceb3f4dc7d7ea\ + d8b0c675ff5939d7faa143183758200b1457b31bd0d0293cde0ca2b9f4d4\ + 8707e63d2959914c78a798536f9d310850183758205723adfca7765e74f4\ + a0659abeaffadc09be35325aa306e3ff1f6f4f74bb47491903e8ff", + ) + .unwrap(); + + let endorser_block: leiosfetch::EndorserBlock = minicbor::decode(&endorser_block).unwrap(); + + let block_txs_hash: leiosfetch::Hash = + hex::decode("bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0").unwrap(); + + // Selects first 3 transactions + let tx_map = leiosfetch::TxMap::from([(0, 0xe000000000000000)]); + + let block_slot: leiosfetch::Slot = 5; + let _block_txs_slot: leiosfetch::Slot = 222222222; + + // server setup + + let listener = TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, port)) + .await + .unwrap(); + + let mut peer_server = PeerServer::accept(&listener, 0).await.unwrap(); + + let server_lf = peer_server.leiosfetch(); + + debug!("server waiting for block request"); + assert_eq!( + server_lf.recv_while_idle().await.unwrap().unwrap(), + ClientRequest::BlockRequest(block_slot, block_hash), + ); + info!("Server received `BlockRequest` from client"); + + assert_eq!(*server_lf.state(), leiosfetch::State::Block); + + info!("Server sends EB"); + server_lf.send_block(endorser_block).await.unwrap(); + assert_eq!(*server_lf.state(), leiosfetch::State::Idle); + + debug!("server waiting for txs request"); + assert_eq!( + server_lf.recv_while_idle().await.unwrap().unwrap(), + ClientRequest::BlockTxsRequest(block_slot, block_txs_hash, tx_map.clone()), + ); + info!("Server received `BlockTxsRequest` from client"); + + assert_eq!(*server_lf.state(), leiosfetch::State::BlockTxs); + + info!("Server selects Txs according to map and sends"); + server_lf + .send_block_txs(bitmap_selection(tx_map, &eb_tx())) + .await + .unwrap(); + assert_eq!(*server_lf.state(), leiosfetch::State::Idle); + + assert!(server_lf.recv_while_idle().await.unwrap().is_none()); + info!("Server received Done message from client"); + assert_eq!(*server_lf.state(), leiosfetch::State::Done); +} + +async fn client(listen_on: u16) { + let block_hash: leiosfetch::Hash = + hex::decode("c579268ab0275662d47a3fe2dfcb41981426ddfc217ed3091364ae8f58198809").unwrap(); + + let block_txs_hash: leiosfetch::Hash = + hex::decode("bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0bee0").unwrap(); + + // Selects first 3 transactions + let tx_map = leiosfetch::TxMap::from([(0, 0xe000000000000000)]); + + let block_slot: leiosfetch::Slot = 5; + + // let vote_issuer_id: leiosfetch::Hash = + // hex::decode("beedbeedbeedbeedbeedbeedbeedbeedbeedbeedbeedbeedbeedbeedbeedbeed").unwrap(); + + tokio::time::sleep(Duration::from_secs(1)).await; + + // client setup + let mut client_to_server_conn = PeerClient::connect(format!("localhost:{}", listen_on), 0) + .await + .unwrap(); + + info!("Connecting to server"); + let client_lf = client_to_server_conn.leiosfetch(); + + info!("Client sends `BlockRequest`"); + client_lf + .send_block_request(block_slot, block_hash) + .await + .unwrap(); + let endorser_block = client_lf.recv_block().await.unwrap(); + info!("Client received endorser block:"); + info!("{:02x?}", endorser_block); + + assert_eq!(*client_lf.state(), leiosfetch::State::Idle); + + tokio::time::sleep(Duration::from_secs(1)).await; + + info!("Client sends `BlockTxsRequest`"); + client_lf + .send_block_txs_request(block_slot, block_txs_hash, tx_map) + .await + .unwrap(); + + tokio::time::sleep(Duration::from_secs(1)).await; + let txs = client_lf.recv_block_txs().await.unwrap(); + info!("Client received vec of txs:"); + info!("{:02x?}", txs); + + info!("Client sends Done"); + client_lf.send_done().await.unwrap(); + assert!(client_lf.is_done()); + + // Delay needed for proper disconnect + tokio::time::sleep(Duration::from_secs(1)).await; +} + +#[cfg(unix)] +#[tokio::main] +async fn main() { + tracing::subscriber::set_global_default( + tracing_subscriber::FmtSubscriber::builder() + .with_max_level(tracing::Level::TRACE) + .finish(), + ) + .unwrap(); + + let args = Args::parse(); + + match &args.role[..] { + "server" => { + server(args.port).await; + } + "client" => { + client(args.port).await; + } + _ => { + tracing::error!( + "Please choose a valid role: `server` or `client`.\n\\---> Role provided: {:?}", + args.role, + ); + return; + } + } +} + +fn eb_tx() -> Vec { + vec![ + hex::decode( + "58359719B92F47E7ABC8436813A42C1A5780C4ADDBF008E58E6CB8A4A3142067\ + E2BD47E713EBDB3672446C8DD5697D6F29477DA5ABD6F9", + ) + .unwrap(), + hex::decode( + "583551C27E9FD7D03351C243B98F6E33E9D29AD62CE9061580358B9CD4754505\ + 7B54A726322F849C5D73C01AE9881AA458F3A5F9DEA664", + ) + .unwrap(), + hex::decode( + "58356764A66870461BD63041BF1028FF898BDC58E95DA9EA6E684EBCC225F97A\ + ECF647BC7EA72BAC069D1FF9E3E9CB59C72181585FD4F0", + ) + .unwrap(), + hex::decode( + "5903E584035557626AE726D5BCE067C798B43B3DE035C3618F86CA1CF31969EB\ + B6711D354C445650D52E34F9E9A2057ECB363FE04FD3D5CE76B05E7C0CE7C563\ + C8F89AF65F3B57D6E34481A13889FACCE87AF020F0044B5EEA3C1BD48387506D\ + BD3C75ED4B9EFD7605DC3571A95B6E97F349C61C5D444A93DDE14F27C7B6EF74\ + F802EA1AB809ECBBEFD9229A85B42BC959B70BD207C06F30675B177096931759\ + 462E64B9F9F90EA5E5C5AA975A454F12AC6E4D21BC641A00B994B15E54BE2D79\ + 382A5ECF65BAA76496433D191CD0BEEB1AD979CD070CDC94FFFECD01CB3BF1E9\ + 86FEA8FE343C419AE71FC9CE7053697BCB75A45552006EFB1D4F36A34E9D70FE\ + 663C5B28D497373DB42AE1A6B8B5BD05390FBF580FCD75D857C9047FBB2A3FA8\ + 265702FD21773E124A5338E88D922A892331B9A7EE3F7375F9864E6990901D32\ + 3E37AB088528FC456B9082F40527C9565248D1D0403CEBEAE8BE8DDF290D0C0F\ + C415487747EFA5D256FA3F997E0D0F111C9F22D9F41C384C0FAA22AFE97BCCCB\ + D663268AE89A7BEC8898D5CEED1ECDFABC33205F8B01CEC18079B03BB7D5BBD8\ + EF80D6FB65FDC4F0445C8712CD717E5879663400652C16C8ECA980AFEC745A2C\ + C17D6A3EA1F9D2A4B0D534F784B35BAD97CCBB495E961D010C0A3FCF89FE7EAE\ + 091B00991EFF8BDB6E36C47FCBD1620130CAE67D68E68CFBE8D43BEBBA8B2331\ + F89F931D9FAA722789BFF1A6A0070480D87D59A94C62A8944EF5D327E7200030\ + 5502F26E7F3FF43C7C46097204C449F07C2F3DA9A9962B7AE51E6117FBF2B591\ + AB4273BA88F9C758EE64CF10FB2BF5F25B0B287F5081A79CEFDBBB0CBB70B9D9\ + DACBC1868C37B731C6C73F49F31C4F047D236DF3ED0BD2C41F4F19B9164D2DA3\ + CAC0067168746965C1B77EDE72A35F0BBD478FF21AE128D20FED009FCA1653CC\ + 16B7DE7F4FC1FBA75062B2E41BA0FFCBB8CA7213694C6947678BA2547BEF34FE\ + CD165A8ABB1DF0E52EBC0600361EFDE93031B290FA63F72F7DBA8F94FB34E6E3\ + 331C84367E4E887BBE982A905564993D7432BD2FE60061B39F0411486669FACA\ + F43E2A589EEBCC635F3D1C887C8444BD8994C2AE726F402CC846E6E150688FA9\ + EEAF836AC0EA978C776C4A14B4ECD9A54104A0D4FA8EEABBB5FBD4EEE80A19A0\ + 01547A1893BF3FAFF98994AD3E127CC4E35E13DA8EDF587DE0DB61824B2601C0\ + 46B83088A95B3DAE5CE118516F7E95E90DBD22A7315A1B990FBB81C264D4E903\ + 5935536ED84FF3D9951EED006ADB6C15F09691DC27037F19227004AE54D682F3\ + 6EE41C20A27E07F10CC3BF2CF68C92E4429D9AA75D2AE487C759AD1EF37263F3\ + 0BD4A50B4145C2B41C833C382FE4A5D15456346BF039A1E840BBF32F99AC80B4\ + A1930D5E838254F5", + ) + .unwrap(), + ] + .into_iter() + .map(|x| minicbor::decode(&x).unwrap()) + .collect() +} From b308d37f3175ff97fcfbcc9e6bd445b9dfb4b1e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20S=C3=A1nchez=20Terraf?= Date: Wed, 29 Oct 2025 12:12:07 -0300 Subject: [PATCH 21/21] say you are waiting --- examples/leios-fetch-demo/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/leios-fetch-demo/src/main.rs b/examples/leios-fetch-demo/src/main.rs index dfe9b357f..bf014f796 100644 --- a/examples/leios-fetch-demo/src/main.rs +++ b/examples/leios-fetch-demo/src/main.rs @@ -52,6 +52,7 @@ async fn server(port: u16) { // server setup + info!("Server waiting for client..."); let listener = TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, port)) .await .unwrap();