Implement multiplexer and mini-protocols PoC
This commit is contained in:
parent
89edee6793
commit
c2d537b83f
28 changed files with 1398 additions and 0 deletions
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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue