Disclaimer: These are planning documents. The functionalities described here may be unimplemented, partially implemented, or implemented differently than the original design.

Echo Service Design

Summary

This document presents the design for the echo service. This service is a simple service that can be used for testing a circuit between nodes. An echo service sends messages to its peers at a set frequency.

Guide-level explanation

When a splinter circuit is created with an echo service the echo service will send messages to its peer services at the set frequency as well as simulate errors at a set rate. When an echo service receives a new message from a peer it will send a response back. Echo services send simple messages that contain a string and an ID unique to the message.

Echo Arguments

Echo service takes four arguments that determine how the service will act.

  • peers - This is a list of the services that echo service will attempt to send messages to
  • frequency - This is the amount of time, given in seconds, that an echo service will wait between sending messages
  • jitter - This is an amount of time, given in seconds, that is used to generate a random number in the range [-jitter, jitter]. A new value is generated each time a message is sent and added to the frequency value to ensure that the amount of time between each message varies.
  • error_rate - This is the number of errors per second, given as a float, that should be emulated by the echo service.

These arguments are set using the splinter CLI when creating a circuit.

Service API

Echo service implements the new service API in splinter. The principle traits included in the new service API and implemented in echo service are: Lifecycle, MessageHandler, TimerFilter, TimerHandler. Other required traits include the converters, MessageConverter and ArgumentsConverter, and the factories, MessageHandlerFactory and TimerHandlerFactory.

Reference-level explanation

EchoTimerFilter

The EchoTimerFilter contains an echo store factory and implements the TimerFilter trait. The filter method returns the list of service IDs of services that need to be handled. A service needs to be handled if it has at least one peer service and is in the EchoServiceStatus::Finalized state.

pub struct EchoTimerFilter {
    store_factory: Box<dyn PooledEchoStoreFactory>,
}

impl EchoTimerFilter {
    pub fn new(store_factory: Box<dyn PooledEchoStoreFactory>) -> Self {
        Self { store_factory }
    }
}

impl TimerFilter for EchoTimerFilter {
    fn filter(&self) -> Result<Vec<FullyQualifiedServiceId>, InternalError> {
        
    }
}

EchoArguments

The EchoArguments struct contains the information to be used by the EchoTimerHandler to send EchoMessages. The peers field is a list of ServiceIds that the echo service will attempt to send messages to. frequency is the amount of time that an echo service should wait between sending messages. jitter is used as a range [-jitter, jitter] to generate a random number which will be added to the frequency to create variation in the wait time between messages. error_rate, given as a float, is the number of errors per second that should be emulated by the echo service.

pub struct EchoArguments {
    peers: Vec<ServiceId>,
    frequency: Duration,
    jitter: Duration,
    error_rate: f32,
}

EchoMessage

The EchoMessage enum represents the messages sent between echo services. The enum has two variants Request and Response. A Request is the initial type of message sent by an echo service and a Response is what is sent back to the sender when a Request is received.

pub enum EchoMessage {
    Request {
        message: String,
        correlation_id: u64,
    },
    Response {
        message: String,
        correlation_id: u64,
    },
}

EchoTimerHandler

The EchoTimerHandler struct contains an EchoStore and a time stamp used to determine when to simulate an error. EchoTimerHandler implements the TimerHandler trait. The handle_timer function in the implementation sends messages to a service’s peers at a given rate, emulating errors periodically, based on the EchoArguments set for that service.

pub struct EchoTimerHandler {
    store: Box<dyn EchoStore>,
    stamp: Instant,
}

impl EchoTimerHandler {
    pub fn new(store: Box<dyn EchoStore>, stamp: Instant) -> Self {
        EchoTimerHandler { store, stamp }
    }
}

impl TimerHandler for EchoTimerHandler {
    type Message = EchoMessage;

    fn handle_timer(
        &mut self,
        sender: &dyn MessageSender<Self::Message>,
        service: FullyQualifiedServiceId,
    ) -> Result<(), InternalError> {
        
    }
}

EchoMessageHandler

The EchoMessageHandler struct contains an EchoStore and implements the MessageHandler trait. The handle_message function takes a sender, the service ID of the sending service, the service ID of the receiving service and a message, as arguments. Depending on the type of EchoMessage, either a response message will be sent to the sender service or the database will be updated to reflect that a response was received.

pub struct EchoMessageHandler {
    store: Box<dyn EchoStore>,
}

impl EchoMessageHandler {
    pub fn new(store: Box<dyn EchoStore>) -> Self {
        EchoMessageHandler { store }
    }
}

impl MessageHandler for EchoMessageHandler {
    type Message = EchoMessage;

    fn handle_message(
        &mut self,
        sender: &dyn MessageSender<Self::Message>,
        to_service: FullyQualifiedServiceId,
        from_service: FullyQualifiedServiceId,
        message: Self::Message,
    ) -> Result<(), InternalError> {
        
    }
}

EchoLifecycle

The EchoLifecycle struct contains an EchoStoreFactory and implements the Lifecycle trait. Each of the methods in the Lifecycle implementation returns a StoreCommand which when executed, will make the appropriate updates to the underlying database via the EchoStore.

pub struct EchoLifecycle<K> {
    store_factory: Arc<dyn EchoStoreFactory<K>>,
}

impl<K> EchoLifecycle<K> {
    pub fn new(store_factory: Arc<dyn EchoStoreFactory<K>>) -> Self {
        EchoLifecycle { store_factory }
    }
}

impl<K> Lifecycle<K> for EchoLifecycle<K>
where
    K: 'static,
{
    type Arguments = EchoArguments;

    fn command_to_prepare(
        &self,
        service: FullyQualifiedServiceId,
        arguments: Self::Arguments,
    ) -> Result<Box<dyn StoreCommand<Context = K>>, InternalError> {
        
    }

    fn command_to_finalize(
        &self,
        service: FullyQualifiedServiceId,
    ) -> Result<Box<dyn StoreCommand<Context = K>>, InternalError> {
        
    }

    fn command_to_retire(
        &self,
        service: FullyQualifiedServiceId,
    ) -> Result<Box<dyn StoreCommand<Context = K>>, InternalError> {
        
    }

    fn command_to_purge(
        &self,
        service: FullyQualifiedServiceId,
    ) -> Result<Box<dyn StoreCommand<Context = K>>, InternalError> {
        
    }
}

EchoMessageByteConverter

The EchoMessageByteConverter implements the MessageConverter trait. The to_left method converts bytes to an EchoMessage and the to_right method does the inverse.

#[derive(Clone)]
pub struct EchoMessageByteConverter {}

impl MessageConverter<EchoMessage, Vec<u8>> for EchoMessageByteConverter {
    fn to_left(&self, right: Vec<u8>) -> Result<EchoMessage, InternalError> {
        
    }

    fn to_right(&self, left: EchoMessage) -> Result<Vec<u8>, InternalError> {
        
    }
}

EchoArgumentsVecConverter

The EchoArgumentsVecConverter implements the ArgumentsConverter trait. The to_left method converts a list of tuples to EchoArguments and the to_right method does the inverse.

pub struct EchoArgumentsVecConverter {}

impl ArgumentsConverter<EchoArguments, Vec<(String, String)>> for EchoArgumentsVecConverter {
    fn to_right(&self, left: EchoArguments) -> Result<Vec<(String, String)>, InternalError> {
        
    }

    fn to_left(&self, right: Vec<(String, String)>) -> Result<EchoArguments, InternalError> {
        
    }
}

Prior art

This service implements the new service API design for splinter. For more information see: