Implement multiplexer and mini-protocols PoC
This commit is contained in:
parent
57788f9e63
commit
65144ce14b
28 changed files with 1398 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
Cargo.lock
|
||||||
9
Cargo.toml
Normal file
9
Cargo.toml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
[workspace]
|
||||||
|
|
||||||
|
members = [
|
||||||
|
"pallas-multiplexer",
|
||||||
|
"pallas-machines",
|
||||||
|
"pallas-handshake",
|
||||||
|
"pallas-blockfetch",
|
||||||
|
"pallas",
|
||||||
|
]
|
||||||
15
README.md
Normal file
15
README.md
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Pallas
|
||||||
|
|
||||||
|
Rust-native building blocks for the Cardano blockchain ecosystem.
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
Pallas is an expanding collection of modules that re-implements common
|
||||||
|
Cardano logic in native Rust. This crate doesn't provide any particular
|
||||||
|
application, it is meant to be used as a base layer to facilitate the
|
||||||
|
development of higher-level use-cases, such as explorers, wallets, etc (who
|
||||||
|
knows, maybe even a full node in the far away future).
|
||||||
|
|
||||||
|
## Etymology
|
||||||
|
|
||||||
|
> Pallas: (Greek mythology) goddess of wisdom and useful arts and prudent warfare;
|
||||||
2
pallas-blockfetch/.gitignore
vendored
Normal file
2
pallas-blockfetch/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
Cargo.lock
|
||||||
24
pallas-blockfetch/Cargo.toml
Normal file
24
pallas-blockfetch/Cargo.toml
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
[package]
|
||||||
|
name = "pallas-blockfetch"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
repository = "https://github.com/txpipe/pallas"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
authors = [
|
||||||
|
"Santiago Carmuega <santiago@carmuega.me>"
|
||||||
|
]
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
pallas-multiplexer = { path = "../pallas-multiplexer/" }
|
||||||
|
pallas-machines = { path = "../pallas-machines/" }
|
||||||
|
minicbor = { version="0.11.4", features=["half"] }
|
||||||
|
minicbor-io = "0.6.0"
|
||||||
|
log = "0.4.14"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
net2 = "0.2.37"
|
||||||
|
env_logger = "0.9.0"
|
||||||
|
pallas-handshake = { path = "../pallas-handshake/" }
|
||||||
|
hex = "0.4.3"
|
||||||
47
pallas-blockfetch/examples/client.rs
Normal file
47
pallas-blockfetch/examples/client.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
use net2::TcpStreamExt;
|
||||||
|
use std::net::TcpStream;
|
||||||
|
|
||||||
|
use pallas_blockfetch::{BlockFetchClient, Point};
|
||||||
|
use pallas_handshake::n2n::{Client, VersionTable};
|
||||||
|
use pallas_handshake::MAINNET_MAGIC;
|
||||||
|
use pallas_machines::run_agent;
|
||||||
|
use pallas_multiplexer::Multiplexer;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
//let bearer = TcpStream::connect("localhost:6000").unwrap();
|
||||||
|
let bearer =
|
||||||
|
TcpStream::connect("relays-new.cardano-mainnet.iohk.io:3001").unwrap();
|
||||||
|
|
||||||
|
bearer.set_nodelay(true).unwrap();
|
||||||
|
bearer.set_keepalive_ms(Some(30_000u32)).unwrap();
|
||||||
|
|
||||||
|
let mut handles = Multiplexer::new(bearer, &vec![0, 3]).unwrap();
|
||||||
|
let (_, rx, tx) = handles.remove(0);
|
||||||
|
|
||||||
|
let versions = VersionTable::v4_and_above(MAINNET_MAGIC);
|
||||||
|
let last = run_agent(Client::initial(versions), rx, &tx).unwrap();
|
||||||
|
println!("{:?}", last);
|
||||||
|
|
||||||
|
let range = (
|
||||||
|
Point(
|
||||||
|
43847831u64,
|
||||||
|
hex::decode("15b9eeee849dd6386d3770b0745e0450190f7560e5159b1b3ab13b14b2684a45")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
Point(
|
||||||
|
43847831u64,
|
||||||
|
hex::decode("15b9eeee849dd6386d3770b0745e0450190f7560e5159b1b3ab13b14b2684a45")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (_, bf_rx, bf_tx) = handles.remove(0);
|
||||||
|
|
||||||
|
let bf = BlockFetchClient::initial(range);
|
||||||
|
|
||||||
|
let bf_last = run_agent(bf, bf_rx, &bf_tx);
|
||||||
|
|
||||||
|
println!("{:?}", bf_last);
|
||||||
|
}
|
||||||
190
pallas-blockfetch/src/lib.rs
Normal file
190
pallas-blockfetch/src/lib.rs
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
use log::info;
|
||||||
|
use pallas_machines::{
|
||||||
|
Agent, DecodePayload, EncodePayload, MachineError, MachineOutput,
|
||||||
|
PayloadDecoder, PayloadEncoder, Transition,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Point(pub u64, pub Vec<u8>);
|
||||||
|
|
||||||
|
impl EncodePayload for Point {
|
||||||
|
fn encode_payload(
|
||||||
|
&self,
|
||||||
|
e: &mut PayloadEncoder,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
e.array(2)?.u64(self.0)?.bytes(&self.1)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodePayload for Point {
|
||||||
|
fn decode_payload(
|
||||||
|
d: &mut PayloadDecoder,
|
||||||
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
|
d.array()?;
|
||||||
|
let slot = d.u64()?;
|
||||||
|
let hash = d.bytes()?;
|
||||||
|
|
||||||
|
Ok(Point(slot, Vec::from(hash)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
pub enum State {
|
||||||
|
Idle,
|
||||||
|
Busy,
|
||||||
|
Streaming,
|
||||||
|
Done,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Message {
|
||||||
|
RequestRange { range: (Point, Point) },
|
||||||
|
ClientDone,
|
||||||
|
StartBatch,
|
||||||
|
NoBlocks,
|
||||||
|
Block { body: Vec<u8> },
|
||||||
|
BatchDone,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncodePayload for Message {
|
||||||
|
fn encode_payload(
|
||||||
|
&self,
|
||||||
|
e: &mut PayloadEncoder,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
match self {
|
||||||
|
Message::RequestRange { range } => {
|
||||||
|
e.array(3)?.u16(0)?;
|
||||||
|
range.0.encode_payload(e)?;
|
||||||
|
range.1.encode_payload(e)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Message::ClientDone => {
|
||||||
|
e.array(1)?.u16(1)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Message::StartBatch => {
|
||||||
|
e.array(1)?.u16(2)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Message::NoBlocks => {
|
||||||
|
e.array(1)?.u16(3)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Message::Block { body } => {
|
||||||
|
e.array(2)?.u16(4)?;
|
||||||
|
e.bytes(&body)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Message::BatchDone => {
|
||||||
|
e.array(1)?.u16(5)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodePayload for Message {
|
||||||
|
fn decode_payload(
|
||||||
|
d: &mut PayloadDecoder,
|
||||||
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
|
d.array()?;
|
||||||
|
let label = d.u16()?;
|
||||||
|
|
||||||
|
match label {
|
||||||
|
0 => {
|
||||||
|
let point1 = Point::decode_payload(d)?;
|
||||||
|
let point2 = Point::decode_payload(d)?;
|
||||||
|
Ok(Message::RequestRange {
|
||||||
|
range: (point1, point2),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
1 => Ok(Message::ClientDone),
|
||||||
|
2 => Ok(Message::StartBatch),
|
||||||
|
3 => Ok(Message::NoBlocks),
|
||||||
|
4 => {
|
||||||
|
d.tag()?;
|
||||||
|
let body = d.bytes()?;
|
||||||
|
Ok(Message::Block {
|
||||||
|
body: Vec::from(body),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
5 => Ok(Message::BatchDone),
|
||||||
|
x => Err(Box::new(MachineError::BadLabel(x))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct BlockFetchClient {
|
||||||
|
pub state: State,
|
||||||
|
pub range: (Point, Point),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockFetchClient {
|
||||||
|
pub fn initial(range: (Point, Point)) -> Self {
|
||||||
|
Self {
|
||||||
|
state: State::Idle,
|
||||||
|
range,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_request_range(self, tx: &impl MachineOutput) -> Transition<Self> {
|
||||||
|
let msg = Message::RequestRange {
|
||||||
|
range: self.range.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
tx.send_msg(&msg)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
state: State::Busy,
|
||||||
|
..self
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Agent for BlockFetchClient {
|
||||||
|
type Message = Message;
|
||||||
|
|
||||||
|
fn is_done(&self) -> bool {
|
||||||
|
self.state == State::Done
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_agency(&self) -> bool {
|
||||||
|
match self.state {
|
||||||
|
State::Idle => true,
|
||||||
|
State::Busy => false,
|
||||||
|
State::Streaming => false,
|
||||||
|
State::Done => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_next(self, tx: &impl MachineOutput) -> Transition<Self> {
|
||||||
|
match self.state {
|
||||||
|
State::Idle => self.send_request_range(tx),
|
||||||
|
_ => panic!("I don't have agency, don't know what to do"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn receive_next(self, msg: Self::Message) -> Transition<Self> {
|
||||||
|
match (&self.state, msg) {
|
||||||
|
(State::Busy, Message::StartBatch) => Ok(Self {
|
||||||
|
state: State::Streaming,
|
||||||
|
..self
|
||||||
|
}),
|
||||||
|
(State::Busy, Message::NoBlocks) => Ok(Self {
|
||||||
|
state: State::Done,
|
||||||
|
..self
|
||||||
|
}),
|
||||||
|
(State::Streaming, Message::Block { body }) => {
|
||||||
|
info!("received block body of size {}", body.len());
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
(State::Streaming, Message::BatchDone) => Ok(Self {
|
||||||
|
state: State::Done,
|
||||||
|
..self
|
||||||
|
}),
|
||||||
|
_ => panic!("I have agency, I don't expect messages"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
pallas-handshake/.gitignore
vendored
Normal file
2
pallas-handshake/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
Cargo.lock
|
||||||
23
pallas-handshake/Cargo.toml
Normal file
23
pallas-handshake/Cargo.toml
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
[package]
|
||||||
|
name = "pallas-handshake"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
repository = "https://github.com/txpipe/pallas"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
authors = [
|
||||||
|
"Santiago Carmuega <santiago@carmuega.me>"
|
||||||
|
]
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
pallas-multiplexer = { path = "../pallas-multiplexer/" }
|
||||||
|
pallas-machines = { path = "../pallas-machines/" }
|
||||||
|
minicbor = { version="0.11.4", features=["half"] }
|
||||||
|
minicbor-io = "0.6.0"
|
||||||
|
itertools = "0.10.1"
|
||||||
|
log = "0.4.14"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
net2 = "0.2.37"
|
||||||
|
env_logger = "0.9.0"
|
||||||
26
pallas-handshake/examples/client.rs
Normal file
26
pallas-handshake/examples/client.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
use net2::TcpStreamExt;
|
||||||
|
use std::net::TcpStream;
|
||||||
|
|
||||||
|
use pallas_handshake::n2c::{Client, VersionTable};
|
||||||
|
use pallas_handshake::MAINNET_MAGIC;
|
||||||
|
use pallas_machines::run_agent;
|
||||||
|
use pallas_multiplexer::Multiplexer;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
//let bearer = TcpStream::connect("localhost:6000").unwrap();
|
||||||
|
let bearer =
|
||||||
|
TcpStream::connect("relays-new.cardano-mainnet.iohk.io:3001").unwrap();
|
||||||
|
|
||||||
|
bearer.set_nodelay(true).unwrap();
|
||||||
|
bearer.set_keepalive_ms(Some(30_000u32)).unwrap();
|
||||||
|
|
||||||
|
let mut handles = Multiplexer::new(bearer, &vec![0]).unwrap();
|
||||||
|
let (_, rx, tx) = handles.remove(0);
|
||||||
|
|
||||||
|
let versions = VersionTable::v1_and_above(MAINNET_MAGIC);
|
||||||
|
let last = run_agent(Client::initial(versions), rx, &tx).unwrap();
|
||||||
|
|
||||||
|
println!("{:?}", last);
|
||||||
|
}
|
||||||
26
pallas-handshake/examples/node.rs
Normal file
26
pallas-handshake/examples/node.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
use net2::TcpStreamExt;
|
||||||
|
use std::net::TcpStream;
|
||||||
|
|
||||||
|
use pallas_handshake::n2n::{Client, VersionTable};
|
||||||
|
use pallas_handshake::MAINNET_MAGIC;
|
||||||
|
use pallas_machines::run_agent;
|
||||||
|
use pallas_multiplexer::Multiplexer;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
//let bearer = TcpStream::connect("localhost:6000").unwrap();
|
||||||
|
let bearer =
|
||||||
|
TcpStream::connect("relays-new.cardano-mainnet.iohk.io:3001").unwrap();
|
||||||
|
|
||||||
|
bearer.set_nodelay(true).unwrap();
|
||||||
|
bearer.set_keepalive_ms(Some(30_000u32)).unwrap();
|
||||||
|
|
||||||
|
let mut handles = Multiplexer::new(bearer, &vec![0]).unwrap();
|
||||||
|
let (_, rx, tx) = handles.remove(0);
|
||||||
|
|
||||||
|
let versions = VersionTable::v4_and_above(MAINNET_MAGIC);
|
||||||
|
let last = run_agent(Client::initial(versions), rx, &tx).unwrap();
|
||||||
|
|
||||||
|
println!("{:?}", last);
|
||||||
|
}
|
||||||
80
pallas-handshake/src/common.rs
Normal file
80
pallas-handshake/src/common.rs
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
use itertools::Itertools;
|
||||||
|
use pallas_machines::{DecodePayload, EncodePayload, PayloadEncoder};
|
||||||
|
use std::{collections::HashMap, fmt::Debug};
|
||||||
|
|
||||||
|
pub const TESTNET_MAGIC: u64 = 1097911063;
|
||||||
|
pub const MAINNET_MAGIC: u64 = 764824073;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct VersionTable<T>
|
||||||
|
where
|
||||||
|
T: Debug + Clone + EncodePayload + DecodePayload,
|
||||||
|
{
|
||||||
|
pub values: HashMap<u64, T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> EncodePayload for VersionTable<T>
|
||||||
|
where
|
||||||
|
T: Debug + Clone + EncodePayload + DecodePayload,
|
||||||
|
{
|
||||||
|
fn encode_payload(
|
||||||
|
&self,
|
||||||
|
e: &mut PayloadEncoder,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
e.map(self.values.len() as u64)?;
|
||||||
|
|
||||||
|
for key in self.values.keys().sorted() {
|
||||||
|
e.u64(*key)?;
|
||||||
|
self.values[key].encode_payload(e)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type NetworkMagic = u64;
|
||||||
|
|
||||||
|
pub type VersionNumber = u64;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum RefuseReason {
|
||||||
|
VersionMismatch(Vec<VersionNumber>),
|
||||||
|
HandshakeDecodeError(VersionNumber, String),
|
||||||
|
Refused(VersionNumber, String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncodePayload for RefuseReason {
|
||||||
|
fn encode_payload(
|
||||||
|
&self,
|
||||||
|
e: &mut PayloadEncoder,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
match self {
|
||||||
|
RefuseReason::VersionMismatch(versions) => {
|
||||||
|
e.array(2)?;
|
||||||
|
e.u16(0)?;
|
||||||
|
e.array(versions.len() as u64)?;
|
||||||
|
for v in versions.iter() {
|
||||||
|
e.u64(*v)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
RefuseReason::HandshakeDecodeError(version, msg) => {
|
||||||
|
e.array(3)?;
|
||||||
|
e.u16(1)?;
|
||||||
|
e.u64(*version)?;
|
||||||
|
e.str(msg)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
RefuseReason::Refused(version, msg) => {
|
||||||
|
e.array(3)?;
|
||||||
|
e.u16(1)?;
|
||||||
|
e.u64(*version)?;
|
||||||
|
e.str(msg)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
pallas-handshake/src/lib.rs
Normal file
6
pallas-handshake/src/lib.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
pub mod n2c;
|
||||||
|
pub mod n2n;
|
||||||
|
|
||||||
|
pub use common::{MAINNET_MAGIC, TESTNET_MAGIC};
|
||||||
203
pallas-handshake/src/n2c.rs
Normal file
203
pallas-handshake/src/n2c.rs
Normal file
|
|
@ -0,0 +1,203 @@
|
||||||
|
use core::panic;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use pallas_machines::{
|
||||||
|
Agent, DecodePayload, EncodePayload, MachineError, MachineOutput,
|
||||||
|
PayloadDecoder, PayloadEncoder,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::common::{NetworkMagic, RefuseReason, VersionNumber};
|
||||||
|
|
||||||
|
pub type VersionTable = crate::common::VersionTable<VersionData>;
|
||||||
|
|
||||||
|
const PROTOCOL_V1: u64 = 1;
|
||||||
|
const PROTOCOL_V2: u64 = 32770;
|
||||||
|
const PROTOCOL_V3: u64 = 32771;
|
||||||
|
const PROTOCOL_V4: u64 = 32772;
|
||||||
|
const PROTOCOL_V5: u64 = 32773;
|
||||||
|
const PROTOCOL_V6: u64 = 32774;
|
||||||
|
const PROTOCOL_V7: u64 = 32775;
|
||||||
|
const PROTOCOL_V8: u64 = 32776;
|
||||||
|
const PROTOCOL_V9: u64 = 32777;
|
||||||
|
// const PROTOCOL_V10: u64 = 32778;
|
||||||
|
|
||||||
|
impl VersionTable {
|
||||||
|
pub fn v1_and_above(network_magic: u64) -> VersionTable {
|
||||||
|
let values = vec![
|
||||||
|
(PROTOCOL_V1, VersionData(network_magic)),
|
||||||
|
(PROTOCOL_V2, VersionData(network_magic)),
|
||||||
|
(PROTOCOL_V3, VersionData(network_magic)),
|
||||||
|
(PROTOCOL_V4, VersionData(network_magic)),
|
||||||
|
(PROTOCOL_V5, VersionData(network_magic)),
|
||||||
|
(PROTOCOL_V6, VersionData(network_magic)),
|
||||||
|
(PROTOCOL_V7, VersionData(network_magic)),
|
||||||
|
(PROTOCOL_V8, VersionData(network_magic)),
|
||||||
|
(PROTOCOL_V9, VersionData(network_magic)),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect::<HashMap<u64, VersionData>>();
|
||||||
|
|
||||||
|
VersionTable { values }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct VersionData (NetworkMagic,);
|
||||||
|
|
||||||
|
impl EncodePayload for VersionData {
|
||||||
|
fn encode_payload(
|
||||||
|
&self,
|
||||||
|
e: &mut PayloadEncoder,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
e.u64(self.0)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodePayload for VersionData {
|
||||||
|
fn decode_payload(
|
||||||
|
d: &mut PayloadDecoder,
|
||||||
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
|
let network_magic = d.u64()?;
|
||||||
|
|
||||||
|
Ok(Self(network_magic))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Message {
|
||||||
|
Propose(VersionTable),
|
||||||
|
Accept(VersionNumber, VersionData),
|
||||||
|
Refuse(RefuseReason),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncodePayload for Message {
|
||||||
|
fn encode_payload(
|
||||||
|
&self,
|
||||||
|
e: &mut PayloadEncoder,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
match self {
|
||||||
|
Message::Propose(version_table) => {
|
||||||
|
e.array(2)?.u16(0)?;
|
||||||
|
version_table.encode_payload(e)?;
|
||||||
|
}
|
||||||
|
Message::Accept(version_number, version_data) => {
|
||||||
|
e.array(3)?.u16(1)?;
|
||||||
|
e.u64(*version_number)?;
|
||||||
|
version_data.encode_payload(e)?;
|
||||||
|
}
|
||||||
|
Message::Refuse(reason) => {
|
||||||
|
e.array(2)?.u16(2)?;
|
||||||
|
reason.encode_payload(e)?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodePayload for Message {
|
||||||
|
fn decode_payload(
|
||||||
|
d: &mut PayloadDecoder,
|
||||||
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
|
d.array()?;
|
||||||
|
|
||||||
|
let msg = match d.u16()? {
|
||||||
|
0 => todo!(),
|
||||||
|
1 => {
|
||||||
|
let version_number = d.u64()?;
|
||||||
|
let version_data = VersionData::decode_payload(d)?;
|
||||||
|
|
||||||
|
Message::Accept(version_number, version_data)
|
||||||
|
}
|
||||||
|
2 => todo!(),
|
||||||
|
x => return Err(Box::new(MachineError::BadLabel(x))),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum State {
|
||||||
|
Propose,
|
||||||
|
Confirm,
|
||||||
|
Done,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Output {
|
||||||
|
Pending,
|
||||||
|
Accepted(VersionNumber, VersionData),
|
||||||
|
Refused(RefuseReason),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Client {
|
||||||
|
state: State,
|
||||||
|
output: Output,
|
||||||
|
version_table: VersionTable,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Client {
|
||||||
|
pub fn initial(version_table: VersionTable) -> Self {
|
||||||
|
Client {
|
||||||
|
state: State::Propose,
|
||||||
|
output: Output::Pending,
|
||||||
|
version_table,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Agent for Client {
|
||||||
|
type Message = Message;
|
||||||
|
|
||||||
|
fn is_done(&self) -> bool {
|
||||||
|
self.state == State::Done
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_agency(&self) -> bool {
|
||||||
|
match self.state {
|
||||||
|
State::Propose => true,
|
||||||
|
State::Confirm => false,
|
||||||
|
State::Done => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_next(
|
||||||
|
self,
|
||||||
|
tx: &impl MachineOutput,
|
||||||
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
|
match self.state {
|
||||||
|
State::Propose => {
|
||||||
|
tx.send_msg(&Message::Propose(self.version_table.clone()))?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
state: State::Confirm,
|
||||||
|
..self
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => panic!("I don't have agency, nothing to send"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn receive_next(
|
||||||
|
self,
|
||||||
|
msg: Self::Message,
|
||||||
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
|
match (self.state, msg) {
|
||||||
|
(State::Confirm, Message::Accept(version, data)) => Ok(Self {
|
||||||
|
state: State::Done,
|
||||||
|
output: Output::Accepted(version, data),
|
||||||
|
..self
|
||||||
|
}),
|
||||||
|
(State::Confirm, Message::Refuse(reason)) => Ok(Self {
|
||||||
|
state: State::Done,
|
||||||
|
output: Output::Refused(reason),
|
||||||
|
..self
|
||||||
|
}),
|
||||||
|
_ => panic!("Current state does't expect to receive a message"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
214
pallas-handshake/src/n2n.rs
Normal file
214
pallas-handshake/src/n2n.rs
Normal file
|
|
@ -0,0 +1,214 @@
|
||||||
|
use core::panic;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use pallas_machines::{
|
||||||
|
Agent, DecodePayload, EncodePayload, MachineError, MachineOutput,
|
||||||
|
PayloadDecoder, PayloadEncoder,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::common::{RefuseReason, VersionNumber};
|
||||||
|
|
||||||
|
pub type VersionTable = crate::common::VersionTable<VersionData>;
|
||||||
|
|
||||||
|
const PROTOCOL_V4: u64 = 4;
|
||||||
|
const PROTOCOL_V5: u64 = 5;
|
||||||
|
const PROTOCOL_V6: u64 = 6;
|
||||||
|
const PROTOCOL_V7: u64 = 7;
|
||||||
|
|
||||||
|
impl VersionTable {
|
||||||
|
pub fn v4_and_above(network_magic: u64) -> VersionTable {
|
||||||
|
let values = vec![
|
||||||
|
(PROTOCOL_V4, VersionData::new(network_magic, false)),
|
||||||
|
(PROTOCOL_V5, VersionData::new(network_magic, false)),
|
||||||
|
(PROTOCOL_V6, VersionData::new(network_magic, false)),
|
||||||
|
(PROTOCOL_V7, VersionData::new(network_magic, false)),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect::<HashMap<u64, VersionData>>();
|
||||||
|
|
||||||
|
VersionTable { values }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct VersionData {
|
||||||
|
network_magic: u64,
|
||||||
|
initiator_and_responder_diffusion_mode: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VersionData {
|
||||||
|
pub fn new(
|
||||||
|
network_magic: u64,
|
||||||
|
initiator_and_responder_diffusion_mode: bool,
|
||||||
|
) -> Self {
|
||||||
|
VersionData {
|
||||||
|
network_magic,
|
||||||
|
initiator_and_responder_diffusion_mode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncodePayload for VersionData {
|
||||||
|
fn encode_payload(
|
||||||
|
&self,
|
||||||
|
e: &mut PayloadEncoder,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
e.array(2)?
|
||||||
|
.u64(self.network_magic)?
|
||||||
|
.bool(self.initiator_and_responder_diffusion_mode)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodePayload for VersionData {
|
||||||
|
fn decode_payload(
|
||||||
|
d: &mut PayloadDecoder,
|
||||||
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
|
d.array()?;
|
||||||
|
let network_magic = d.u64()?;
|
||||||
|
let initiator_and_responder_diffusion_mode = d.bool()?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
network_magic,
|
||||||
|
initiator_and_responder_diffusion_mode,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Message {
|
||||||
|
Propose(VersionTable),
|
||||||
|
Accept(VersionNumber, VersionData),
|
||||||
|
Refuse(RefuseReason),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncodePayload for Message {
|
||||||
|
fn encode_payload(
|
||||||
|
&self,
|
||||||
|
e: &mut PayloadEncoder,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
match self {
|
||||||
|
Message::Propose(version_table) => {
|
||||||
|
e.array(2)?.u16(0)?;
|
||||||
|
version_table.encode_payload(e)?;
|
||||||
|
}
|
||||||
|
Message::Accept(version_number, version_data) => {
|
||||||
|
e.array(3)?.u16(1)?;
|
||||||
|
e.u64(*version_number)?;
|
||||||
|
version_data.encode_payload(e)?;
|
||||||
|
}
|
||||||
|
Message::Refuse(reason) => {
|
||||||
|
e.array(2)?.u16(2)?;
|
||||||
|
reason.encode_payload(e)?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodePayload for Message {
|
||||||
|
fn decode_payload(
|
||||||
|
d: &mut PayloadDecoder,
|
||||||
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
|
d.array()?;
|
||||||
|
|
||||||
|
let msg = match d.u16()? {
|
||||||
|
0 => todo!(),
|
||||||
|
1 => {
|
||||||
|
let version_number = d.u64()?;
|
||||||
|
let version_data = VersionData::decode_payload(d)?;
|
||||||
|
|
||||||
|
Message::Accept(version_number, version_data)
|
||||||
|
}
|
||||||
|
2 => todo!(),
|
||||||
|
x => return Err(Box::new(MachineError::BadLabel(x))),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum State {
|
||||||
|
Propose,
|
||||||
|
Confirm,
|
||||||
|
Done,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Output {
|
||||||
|
Pending,
|
||||||
|
Accepted(VersionNumber, VersionData),
|
||||||
|
Refused(RefuseReason),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Client {
|
||||||
|
state: State,
|
||||||
|
output: Output,
|
||||||
|
version_table: VersionTable,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Client {
|
||||||
|
pub fn initial(version_table: VersionTable) -> Self {
|
||||||
|
Client {
|
||||||
|
state: State::Propose,
|
||||||
|
output: Output::Pending,
|
||||||
|
version_table,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Agent for Client {
|
||||||
|
type Message = Message;
|
||||||
|
|
||||||
|
fn is_done(&self) -> bool {
|
||||||
|
self.state == State::Done
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_agency(&self) -> bool {
|
||||||
|
match self.state {
|
||||||
|
State::Propose => true,
|
||||||
|
State::Confirm => false,
|
||||||
|
State::Done => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_next(
|
||||||
|
self,
|
||||||
|
tx: &impl MachineOutput,
|
||||||
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
|
match self.state {
|
||||||
|
State::Propose => {
|
||||||
|
tx.send_msg(&Message::Propose(self.version_table.clone()))?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
state: State::Confirm,
|
||||||
|
..self
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => panic!("I don't have agency, nothing to send"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn receive_next(
|
||||||
|
self,
|
||||||
|
msg: Self::Message,
|
||||||
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
|
match (self.state, msg) {
|
||||||
|
(State::Confirm, Message::Accept(version, data)) => Ok(Self {
|
||||||
|
state: State::Done,
|
||||||
|
output: Output::Accepted(version, data),
|
||||||
|
..self
|
||||||
|
}),
|
||||||
|
(State::Confirm, Message::Refuse(reason)) => Ok(Self {
|
||||||
|
state: State::Done,
|
||||||
|
output: Output::Refused(reason),
|
||||||
|
..self
|
||||||
|
}),
|
||||||
|
_ => panic!("Current state does't expect to receive a message"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
pallas-machines/.gitignore
vendored
Normal file
2
pallas-machines/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
Cargo.lock
|
||||||
16
pallas-machines/Cargo.toml
Normal file
16
pallas-machines/Cargo.toml
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
[package]
|
||||||
|
name = "pallas-machines"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
repository = "https://github.com/txpipe/pallas"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
authors = [
|
||||||
|
"Santiago Carmuega <santiago@carmuega.me>"
|
||||||
|
]
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
pallas-multiplexer = { path = "../pallas-multiplexer/" }
|
||||||
|
minicbor = { version="0.11.4", features=["half"] }
|
||||||
|
log = "0.4.14"
|
||||||
174
pallas-machines/src/lib.rs
Normal file
174
pallas-machines/src/lib.rs
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
use log::{debug, trace, warn};
|
||||||
|
use minicbor::{Decoder, Encoder};
|
||||||
|
use pallas_multiplexer::Payload;
|
||||||
|
use std::borrow::Borrow;
|
||||||
|
use std::fmt::{Debug, Display};
|
||||||
|
use std::sync::mpsc::{Receiver, Sender};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum MachineError {
|
||||||
|
BadLabel(u16),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for MachineError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
MachineError::BadLabel(label) => {
|
||||||
|
write!(f, "unknown message label [{}]", label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for MachineError {}
|
||||||
|
|
||||||
|
pub type PayloadEncoder<'a> = Encoder<&'a mut Vec<u8>>;
|
||||||
|
|
||||||
|
pub type PayloadDecoder<'a> = Decoder<'a>;
|
||||||
|
|
||||||
|
pub trait EncodePayload {
|
||||||
|
fn encode_payload(
|
||||||
|
&self,
|
||||||
|
e: &mut PayloadEncoder,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_payload(
|
||||||
|
data: &dyn EncodePayload,
|
||||||
|
) -> Result<Payload, Box<dyn std::error::Error>> {
|
||||||
|
let mut payload = Vec::new();
|
||||||
|
let mut encoder = minicbor::encode::Encoder::new(&mut payload);
|
||||||
|
data.encode_payload(&mut encoder)?;
|
||||||
|
|
||||||
|
Ok(payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Message<D>
|
||||||
|
where
|
||||||
|
D: EncodePayload,
|
||||||
|
{
|
||||||
|
pub label: u32,
|
||||||
|
pub data: D,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D> EncodePayload for Message<D>
|
||||||
|
where
|
||||||
|
D: EncodePayload,
|
||||||
|
{
|
||||||
|
fn encode_payload(
|
||||||
|
&self,
|
||||||
|
e: &mut PayloadEncoder,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// TODO: map concrete error to W::Error somehow?
|
||||||
|
// or just implement custom error struct
|
||||||
|
let data = to_payload(&self.data).unwrap();
|
||||||
|
|
||||||
|
e.array(2)?.u32(self.label)?.bytes(&data)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait MachineOutput {
|
||||||
|
fn send_msg(
|
||||||
|
&self,
|
||||||
|
data: &impl EncodePayload,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MachineOutput for Sender<Payload> {
|
||||||
|
fn send_msg(
|
||||||
|
&self,
|
||||||
|
data: &impl EncodePayload,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let payload = to_payload(data.borrow())?;
|
||||||
|
self.send(payload)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DecodePayload: Sized {
|
||||||
|
fn decode_payload(
|
||||||
|
d: &mut PayloadDecoder,
|
||||||
|
) -> Result<Self, Box<dyn std::error::Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PayloadDeconstructor {
|
||||||
|
rx: Receiver<Payload>,
|
||||||
|
remaining: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PayloadDeconstructor {
|
||||||
|
pub fn consume_next_message<T: DecodePayload>(
|
||||||
|
&mut self,
|
||||||
|
) -> Result<T, Box<dyn std::error::Error>> {
|
||||||
|
if self.remaining.len() == 0 {
|
||||||
|
debug!("no remaining payload, fetching next segment");
|
||||||
|
let payload = self.rx.recv()?;
|
||||||
|
self.remaining.extend(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut decoder = minicbor::Decoder::new(&self.remaining);
|
||||||
|
|
||||||
|
match T::decode_payload(&mut decoder) {
|
||||||
|
Ok(t) => {
|
||||||
|
let new_pos = decoder.position();
|
||||||
|
self.remaining.drain(0..new_pos);
|
||||||
|
debug!("consumed {} from payload buffer", new_pos);
|
||||||
|
Ok(t)
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
//TODO: we need to filter this only for correct errors
|
||||||
|
warn!("{:?}", err);
|
||||||
|
|
||||||
|
debug!("payload incomplete, fetching next segment");
|
||||||
|
let payload = self.rx.recv()?;
|
||||||
|
self.remaining.extend(payload);
|
||||||
|
|
||||||
|
self.consume_next_message::<T>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Transition<T> = Result<T, Box<dyn std::error::Error>>;
|
||||||
|
|
||||||
|
pub trait Agent: Sized {
|
||||||
|
type Message: DecodePayload + Debug;
|
||||||
|
|
||||||
|
fn is_done(&self) -> bool;
|
||||||
|
fn has_agency(&self) -> bool;
|
||||||
|
fn send_next(self, tx: &impl MachineOutput) -> Transition<Self>;
|
||||||
|
fn receive_next(self, msg: Self::Message) -> Transition<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_agent<T: Agent + Debug>(
|
||||||
|
agent: T,
|
||||||
|
rx: Receiver<Payload>,
|
||||||
|
output: &impl MachineOutput,
|
||||||
|
) -> Result<T, Box<dyn std::error::Error>> {
|
||||||
|
let mut input = PayloadDeconstructor {
|
||||||
|
rx,
|
||||||
|
remaining: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut agent = agent;
|
||||||
|
|
||||||
|
while !agent.is_done() {
|
||||||
|
debug!("evaluating agent {:?}", agent);
|
||||||
|
|
||||||
|
match agent.has_agency() {
|
||||||
|
true => {
|
||||||
|
agent = agent.send_next(output)?;
|
||||||
|
}
|
||||||
|
false => {
|
||||||
|
let msg = input.consume_next_message::<T::Message>()?;
|
||||||
|
trace!("procesing inbound msg: {:?}", msg);
|
||||||
|
agent = agent.receive_next(msg)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(agent)
|
||||||
|
}
|
||||||
2
pallas-multiplexer/.gitignore
vendored
Normal file
2
pallas-multiplexer/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
Cargo.lock
|
||||||
19
pallas-multiplexer/Cargo.toml
Normal file
19
pallas-multiplexer/Cargo.toml
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
[package]
|
||||||
|
name = "pallas-multiplexer"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
repository = "https://github.com/txpipe/pallas"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
authors = [
|
||||||
|
"Santiago Carmuega <santiago@carmuega.me>"
|
||||||
|
]
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
log = "0.4.14"
|
||||||
|
byteorder = "1.4.3"
|
||||||
|
hex = "0.4.3"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
env_logger = "0.9.0"
|
||||||
27
pallas-multiplexer/examples/listener.rs
Normal file
27
pallas-multiplexer/examples/listener.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
use std::{net::TcpListener, thread, time::Duration};
|
||||||
|
|
||||||
|
use pallas_multiplexer::Multiplexer;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
let server = TcpListener::bind("0.0.0.0:3001").unwrap();
|
||||||
|
let (bearer, _) = server.accept().unwrap();
|
||||||
|
|
||||||
|
let handles =
|
||||||
|
Multiplexer::new(bearer, &vec![0x8002u16, 0x8003u16][..]).unwrap();
|
||||||
|
|
||||||
|
for handle in handles {
|
||||||
|
thread::spawn(move || {
|
||||||
|
let (id, rx, tx) = handle;
|
||||||
|
loop {
|
||||||
|
let payload = rx.recv().unwrap();
|
||||||
|
println!("id:{}, length:{}", id, payload.len());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
thread::sleep(Duration::from_secs(6000));
|
||||||
|
}
|
||||||
|
}
|
||||||
29
pallas-multiplexer/examples/sender.rs
Normal file
29
pallas-multiplexer/examples/sender.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
use std::{net::TcpStream, thread, time::Duration};
|
||||||
|
|
||||||
|
use pallas_multiplexer::Multiplexer;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
let bearer = TcpStream::connect("127.0.0.1:3001").unwrap();
|
||||||
|
let handles =
|
||||||
|
Multiplexer::new(bearer, &vec![0x0002u16, 0x0003u16][..]).unwrap();
|
||||||
|
|
||||||
|
for (idx, handle) in handles.into_iter().enumerate() {
|
||||||
|
thread::spawn(move || {
|
||||||
|
let (id, rx, tx) = handle;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let payload = vec![1; 65545];
|
||||||
|
tx.send(payload).unwrap();
|
||||||
|
thread::sleep(Duration::from_millis(
|
||||||
|
50u64 + (idx as u64 * 10u64),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
thread::sleep(Duration::from_secs(6000));
|
||||||
|
}
|
||||||
|
}
|
||||||
215
pallas-multiplexer/src/lib.rs
Normal file
215
pallas-multiplexer/src/lib.rs
Normal file
|
|
@ -0,0 +1,215 @@
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
io::{Read, Write},
|
||||||
|
net::TcpStream,
|
||||||
|
sync::mpsc::{self, Receiver, Sender, TryRecvError},
|
||||||
|
thread,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
|
use byteorder::{ByteOrder, NetworkEndian, WriteBytesExt};
|
||||||
|
use log::{debug, error, log_enabled, trace, warn};
|
||||||
|
|
||||||
|
pub trait Bearer: Read + Write + Send + Sync + Sized {
|
||||||
|
fn read_segment(&mut self) -> Result<(u16, u32, Payload), std::io::Error>;
|
||||||
|
|
||||||
|
fn write_segment(
|
||||||
|
&mut self,
|
||||||
|
clock: Instant,
|
||||||
|
protocol_id: u16,
|
||||||
|
partial_payload: &[u8],
|
||||||
|
) -> Result<(), std::io::Error>;
|
||||||
|
|
||||||
|
fn clone(&self) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bearer for TcpStream {
|
||||||
|
fn write_segment(
|
||||||
|
&mut self,
|
||||||
|
clock: Instant,
|
||||||
|
protocol_id: u16,
|
||||||
|
payload: &[u8],
|
||||||
|
) -> Result<(), std::io::Error> {
|
||||||
|
let mut msg = Vec::new();
|
||||||
|
msg.write_u32::<NetworkEndian>(clock.elapsed().as_micros() as u32)?;
|
||||||
|
msg.write_u16::<NetworkEndian>(protocol_id)?;
|
||||||
|
msg.write_u16::<NetworkEndian>(payload.len() as u16)?;
|
||||||
|
|
||||||
|
if log_enabled!(log::Level::Trace) {
|
||||||
|
trace!(
|
||||||
|
"sending segment, header {:?}, payload length: {}",
|
||||||
|
hex::encode(&msg),
|
||||||
|
payload.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.write(&payload[..]).unwrap();
|
||||||
|
|
||||||
|
self.write(&msg)?;
|
||||||
|
|
||||||
|
self.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_segment(&mut self) -> Result<(u16, u32, Payload), std::io::Error> {
|
||||||
|
let mut header = [0u8; 8];
|
||||||
|
|
||||||
|
self.read_exact(&mut header)?;
|
||||||
|
|
||||||
|
let length = NetworkEndian::read_u16(&header[6..]) as usize;
|
||||||
|
let mut payload = vec![0u8; length];
|
||||||
|
self.read_exact(&mut payload)?;
|
||||||
|
|
||||||
|
let id = NetworkEndian::read_u16(&mut header[4..6]) as usize ^ 0x8000;
|
||||||
|
let ts = NetworkEndian::read_u32(&mut header[0..4]);
|
||||||
|
|
||||||
|
if log_enabled!(log::Level::Trace) {
|
||||||
|
trace!(
|
||||||
|
"received segment, header: {:?}, payload length: {}",
|
||||||
|
hex::encode(&header),
|
||||||
|
payload.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((id as u16, ts, payload))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
self.try_clone().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_SEGMENT_PAYLOAD_LENGTH: usize = 65535;
|
||||||
|
|
||||||
|
pub type Payload = Vec<u8>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Error {}
|
||||||
|
|
||||||
|
fn tx_round<TBearer>(
|
||||||
|
bearer: &mut TBearer,
|
||||||
|
ingress: &MuxIngress,
|
||||||
|
clock: Instant,
|
||||||
|
) -> Result<u16, std::io::Error>
|
||||||
|
where
|
||||||
|
TBearer: Bearer,
|
||||||
|
{
|
||||||
|
let mut writes = 0u16;
|
||||||
|
|
||||||
|
for (id, rx) in ingress.iter() {
|
||||||
|
match rx.try_recv() {
|
||||||
|
Ok(payload) => {
|
||||||
|
let chunks = payload.chunks(MAX_SEGMENT_PAYLOAD_LENGTH);
|
||||||
|
|
||||||
|
for chunk in chunks {
|
||||||
|
bearer.write_segment(clock, *id, chunk)?;
|
||||||
|
writes += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(TryRecvError::Disconnected) => {
|
||||||
|
//TODO: remove handle from list
|
||||||
|
warn!("protocol handle disconnected");
|
||||||
|
}
|
||||||
|
Err(TryRecvError::Empty) => (),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(writes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tx_loop<TBearer>(bearer: &mut TBearer, ingress: MuxIngress)
|
||||||
|
where
|
||||||
|
TBearer: Bearer,
|
||||||
|
{
|
||||||
|
loop {
|
||||||
|
let clock = Instant::now();
|
||||||
|
match tx_round(bearer, &ingress, clock) {
|
||||||
|
Err(err) => {
|
||||||
|
error!("{:?}", err);
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
Ok(0) => thread::sleep(Duration::from_millis(10)),
|
||||||
|
Ok(_) => (),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rx_loop<TBearer>(bearer: &mut TBearer, egress: DemuxerEgress)
|
||||||
|
where
|
||||||
|
TBearer: Bearer,
|
||||||
|
{
|
||||||
|
let mut tx_map: HashMap<_, _> = egress.into_iter().collect();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match bearer.read_segment() {
|
||||||
|
Err(err) => {
|
||||||
|
error!("{:?}", err);
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
Ok(segment) => {
|
||||||
|
let (id, _ts, payload) = segment;
|
||||||
|
match tx_map.get(&id) {
|
||||||
|
Some(tx) => match tx.send(payload) {
|
||||||
|
Err(err) => {
|
||||||
|
error!("error sending egress tx to protocol, removing protocol from egress output. {:?}", err);
|
||||||
|
tx_map.remove(&id);
|
||||||
|
}
|
||||||
|
Ok(_) => {
|
||||||
|
debug!("successful tx to egress protocol");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => warn!(
|
||||||
|
"received segment for protocol id not being demuxed {}",
|
||||||
|
id
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChannelProtocolHandle = (u16, Receiver<Payload>, Sender<Payload>);
|
||||||
|
type ChannelIngressHandle = (u16, Receiver<Payload>);
|
||||||
|
type ChannelEgressHandle = (u16, Sender<Payload>);
|
||||||
|
type MuxIngress = Vec<ChannelIngressHandle>;
|
||||||
|
type DemuxerEgress = Vec<ChannelEgressHandle>;
|
||||||
|
|
||||||
|
pub struct Multiplexer {}
|
||||||
|
|
||||||
|
impl Multiplexer {
|
||||||
|
pub fn new<TBearer>(
|
||||||
|
bearer: TBearer,
|
||||||
|
protocols: &[u16],
|
||||||
|
) -> Result<Vec<ChannelProtocolHandle>, Error>
|
||||||
|
where
|
||||||
|
TBearer: Bearer + 'static,
|
||||||
|
{
|
||||||
|
let handles = protocols
|
||||||
|
.iter()
|
||||||
|
.map(|id| {
|
||||||
|
let (demux_tx, demux_rx) = mpsc::channel::<Payload>();
|
||||||
|
let (mux_tx, mux_rx) = mpsc::channel::<Payload>();
|
||||||
|
|
||||||
|
let protocol_handle: ChannelProtocolHandle =
|
||||||
|
(*id, demux_rx, mux_tx);
|
||||||
|
let ingress_handle: ChannelIngressHandle = (*id, mux_rx);
|
||||||
|
let egress_handle: ChannelEgressHandle = (*id, demux_tx);
|
||||||
|
|
||||||
|
(protocol_handle, (ingress_handle, egress_handle))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let (protocol_handles, multiplex_handles): (Vec<_>, Vec<_>) =
|
||||||
|
handles.into_iter().unzip();
|
||||||
|
|
||||||
|
let (ingress, egress): (Vec<_>, Vec<_>) =
|
||||||
|
multiplex_handles.into_iter().unzip();
|
||||||
|
|
||||||
|
let mut tx_bearer = bearer.clone();
|
||||||
|
thread::spawn(move || tx_loop(&mut tx_bearer, ingress));
|
||||||
|
|
||||||
|
let mut rx_bearer = bearer.clone();
|
||||||
|
thread::spawn(move || rx_loop(&mut rx_bearer, egress));
|
||||||
|
|
||||||
|
Ok(protocol_handles)
|
||||||
|
}
|
||||||
|
}
|
||||||
2
pallas/.gitignore
vendored
Normal file
2
pallas/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
Cargo.lock
|
||||||
17
pallas/Cargo.toml
Normal file
17
pallas/Cargo.toml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
[package]
|
||||||
|
name = "pallas"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
repository = "https://github.com/txpipe/pallas"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
authors = [
|
||||||
|
"Santiago Carmuega <santiago@carmuega.me>"
|
||||||
|
]
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
pallas-multiplexer = { path = "../pallas-multiplexer/" }
|
||||||
|
pallas-machines = { path = "../pallas-machines/" }
|
||||||
|
pallas-handshake = { path = "../pallas-handshake/" }
|
||||||
|
pallas-blockfetch = { path = "../pallas-blockfetch/" }
|
||||||
12
pallas/src/lib.rs
Normal file
12
pallas/src/lib.rs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
//! Rust-native building blocks for the Cardano blockchain ecosystem
|
||||||
|
//!
|
||||||
|
//! Pallas is an expanding collection of modules that re-implements common
|
||||||
|
//! Cardano logic in native Rust. This crate doesn't provide any particular
|
||||||
|
//! application, it is meant to be used as a base layer to facilitate the
|
||||||
|
//! development of higher-level use-cases, such as explorers, wallets, etc (who
|
||||||
|
//! knows, maybe even a full node in the far away future).
|
||||||
|
|
||||||
|
#![warn(missing_docs)]
|
||||||
|
#![warn(missing_doc_code_examples)]
|
||||||
|
|
||||||
|
pub mod ouroboros;
|
||||||
12
pallas/src/ouroboros.rs
Normal file
12
pallas/src/ouroboros.rs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use pallas_multiplexer as multiplexer;
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use pallas_machines as machines;
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use pallas_handshake as handshake;
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use pallas_blockfetch as blockfetch;
|
||||||
2
rustfmt.toml
Normal file
2
rustfmt.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
edition = "2021"
|
||||||
|
wrap_comments = true
|
||||||
Loading…
Add table
Add a link
Reference in a new issue