{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TypeFamilies #-} module Raft.RPC where import Protolude import Data.Serialize import Raft.Log import Raft.Types -- | Interface for nodes to send messages to one -- another. E.g. Control.Concurrent.Chan, Network.Socket, etc. class RaftSendRPC m v where sendRPC :: NodeId -> RPCMessage v -> m () -- | Interface for nodes to receive messages from one -- another class Show (RaftRecvRPCError m v) => RaftRecvRPC m v where type RaftRecvRPCError m v receiveRPC :: m (Either (RaftRecvRPCError m v) (RPCMessage v)) -- | Representation of a message sent between nodes data RPCMessage v = RPCMessage { sender :: NodeId , rpc :: RPC v } deriving (Show, Generic, Serialize) data RPC v = AppendEntriesRPC (AppendEntries v) | AppendEntriesResponseRPC AppendEntriesResponse | RequestVoteRPC RequestVote | RequestVoteResponseRPC RequestVoteResponse deriving (Show, Generic, Serialize) class RPCType a v where toRPC :: a -> RPC v instance RPCType (AppendEntries v) v where toRPC = AppendEntriesRPC instance RPCType AppendEntriesResponse v where toRPC = AppendEntriesResponseRPC instance RPCType RequestVote v where toRPC = RequestVoteRPC instance RPCType RequestVoteResponse v where toRPC = RequestVoteResponseRPC rpcTerm :: RPC v -> Term rpcTerm = \case AppendEntriesRPC ae -> aeTerm ae AppendEntriesResponseRPC aer -> aerTerm aer RequestVoteRPC rv -> rvTerm rv RequestVoteResponseRPC rvr -> rvrTerm rvr data NoEntriesSpec = FromInconsistency | FromHeartbeat | FromClientReadReq Int deriving (Show) data AppendEntriesSpec v = FromIndex Index | FromNewLeader (Entry v) | FromClientWriteReq (Entry v) | NoEntries NoEntriesSpec deriving (Show) -- | The data used to construct an AppendEntries value, snapshotted from the -- node state at the time the AppendEntries val should be created. data AppendEntriesData v = AppendEntriesData { aedTerm :: Term , aedLeaderCommit :: Index , aedEntriesSpec :: AppendEntriesSpec v } deriving (Show) -- | Representation of a message sent from a leader to its peers data AppendEntries v = AppendEntries { aeTerm :: Term -- ^ Leader's term , aeLeaderId :: LeaderId -- ^ Leader's identifier so that followers can redirect clients , aePrevLogIndex :: Index -- ^ Index of log entry immediately preceding new ones , aePrevLogTerm :: Term -- ^ Term of aePrevLogIndex entry , aeEntries :: Entries v -- ^ Log entries to store (empty for heartbeat) , aeLeaderCommit :: Index -- ^ Leader's commit index , aeReadRequest :: Maybe Int -- ^ which read request the message corresponds to } deriving (Show, Generic, Serialize) -- | Representation of the response from a follower to an AppendEntries message data AppendEntriesResponse = AppendEntriesResponse { aerTerm :: Term -- ^ current term for leader to update itself , aerSuccess :: Bool -- ^ true if follower contained entry matching aePrevLogIndex and aePrevLogTerm , aerReadRequest :: Maybe Int -- ^ which read request the response corresponds to } deriving (Show, Generic, Serialize) -- | Representation of the message sent by candidates to their peers to request -- their vote data RequestVote = RequestVote { rvTerm :: Term -- ^ candidates term , rvCandidateId :: NodeId -- ^ candidate requesting vote , rvLastLogIndex :: Index -- ^ index of candidate's last log entry , rvLastLogTerm :: Term -- ^ term of candidate's last log entry } deriving (Show, Generic, Serialize) -- | Representation of a response to a RequestVote message data RequestVoteResponse = RequestVoteResponse { rvrTerm :: Term -- ^ current term for candidate to update itself , rvrVoteGranted :: Bool -- ^ true means candidate recieved vote } deriving (Show, Generic, Serialize)