#state-machine #ouroboros #pallas #block #generic #structure #mini-protocols

pallas-miniprotocols

Implementation of the Ouroboros network mini-protocols state-machines

52 releases

new 0.18.3 Oct 23, 2024
0.18.2 Aug 24, 2023
0.18.1 Apr 21, 2023
0.18.0 Feb 4, 2023
0.8.0-alpha.0 Mar 23, 2022

#5 in #pallas

Download history 71/week @ 2024-07-03 25/week @ 2024-07-10 31/week @ 2024-07-17 45/week @ 2024-07-24 75/week @ 2024-07-31 62/week @ 2024-08-07 48/week @ 2024-08-14 87/week @ 2024-08-21 54/week @ 2024-08-28 38/week @ 2024-09-04 31/week @ 2024-09-11 74/week @ 2024-09-18 110/week @ 2024-09-25 313/week @ 2024-10-02 26/week @ 2024-10-09 9/week @ 2024-10-16

510 downloads per month
Used in 2 crates (via oura)

Apache-2.0

320KB
5K SLoC

Pallas Mini-protocols

This crate provides an implementation of the different Ouroboros mini-protocols as defined in the The Shelley Networking Protocol specs.

Architectural Decisions

The following architectural decisions were made for this particular Rust implementation:

  • The mini-protocols will remain agnostic of the concrete ledger implementation. For example, the block-fetch implementation is generic over the particular block data structure.
  • The codec implemenation of the messages defined by the Ouroboros specs belongs to this crate, but any ledger-specific structure is out-of-scope.
  • The state-machine execution will remain agnostic of the concrete mini-protocol specification.

Development Status

mini-protocol initiator responder
block-fetch done planned
chain-sync done planned
handshake done planned
local-state done planned
tx-submission planned minimal
local tx monitor done planned
local-tx-submission ongoing planned

Implementation Details

An Ouroboros mini-protocol is defined as a state-machine. This library provides the primitive artifacts to describe the different states and messages of each particular state-machine.

A local agent, either initiator or responder, interacts with a remote agent by exchanging messages and keeping its own version of the state.

By implementing the following trait, a struct can participate as an agent in an Ouroboros communication:

pub trait Agent: Sized {
    type Message;

    fn is_done(&self) -> bool;
    fn has_agency(&self) -> bool;
    fn send_next(self, tx: &impl MachineOutput) -> Transition<Self>;
    fn receive_next(self, msg: Self::Message) -> Transition<Self>;
}
  • The associate type Message is an enum with the particular variants of each particular miniprotocol
  • The has_agency function describes if the agent has agency for the current state.
  • The is_done function describes if the agent considers that all tasks have been done.
  • The send_next function instructs the agent to send the next message in the sequence (will be called only if it has agency).
  • The receive_next function instructs the agent to process the following received message.

The send_next and the receive_next methods will transition the state-machine from one state to the following. This transition happens without mutating any value, the idea is that each step in the process transition the agent struct into a new struct of the same type describing the new state. This approach allows us to implement the execution of the state machine as a pure function.

To tigger the execution of an agent, the library provides the following entry-point:

run_agent<T>(agent: T, channel: &mut Channel)

Where T is the type of the concrete agent to execute and the Channel is the Ouroboros multiplexer channel already connected to the remote party.

Execution Example

The following example shows how to execute a Handshake client against a remote relay node.

// setup a TCP bearer against a relay node
let bearer = TcpStream::connect("relays-new.cardano-mainnet.iohk.io:3001").unwrap();
bearer.set_nodelay(true).unwrap();
bearer.set_keepalive_ms(Some(30_000u32)).unwrap();

// create a new multiplexer, specifying which mini-protocol IDs we want to sue
let mut muxer = Multiplexer::setup(bearer, &[0]).unwrap();

// get a handle for the handhsake mini-protocol handle
let mut channel = muxer.use_channel(0);

// create a handshake client agent with an initial state 
let agent = handshake::Client::initial(VersionTable::v4_and_above(MAINNET_MAGIC));

// run the agent, which internally executes all the transitions
// until it is done.
let agent = run_agent(agent, &mut channel).unwrap();

// print the final state of the agent
println!("{agent:?}");

Dependencies

~1.9–2.8MB
~55K SLoC