diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,3 +1,36 @@
 # Changelog for raft
 
-## Unreleased changes
+## 0.2.0.0
+
+- Feature: Client requests are now cached by the current leader such that duplicate
+  client requests are not serviced
+- Bug Fix: Fix issue in concurrent timer not resetting properly when the timeout
+  was set to values under 500ms
+- Feature: Client read requests can now query entries by index or range of
+  indices
+- Improvement: Raft nodes now only write to disk if the state changes during
+  handling of events
+- Feature: Users can now specify what severity of log messages should be logged
+- Bug Fix: Receiving all data from a socket in the RaftSocketT monad
+  transformer RaftRecvX typeclass instances inducing a deadlock has been
+  rewritten
+- Feature: A PostgreSQL backend has been added for storage of log entries
+
+## 0.1.2.0
+
+- Added a heartbeat broadcast by leader on read requests from client to ensure
+  that the node is leader before responding to client
+- Each log entry is now linked by sha256 hash to the log entry immediately
+  preceding it
+- Fixed bug where the last log entry was not being read from disk before
+  starting the main event loop
+- Added quickcheck-state-machine tests for black-box testing of the raft-example
+  client program
+
+## 0.1.1.0
+
+- Fixed MonadFail constraints for GHC 8.6
+
+## 0.1.0.0
+
+- Initial release.
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -77,11 +77,11 @@
 node in mode `Follower` is indeed `FollowerState`, etc:
 
 ```haskell
--- | The volatile state of a Raft Node
-data NodeState (a :: Mode) where
-  NodeFollowerState :: FollowerState -> NodeState 'Follower
-  NodeCandidateState :: CandidateState -> NodeState 'Candidate
-  NodeLeaderState :: LeaderState -> NodeState 'Leader
+-- | The volatile state of a Raft node
+data NodeState (a :: Mode) v where
+  NodeFollowerState :: FollowerState v -> NodeState 'Follower v
+  NodeCandidateState :: CandidateState v -> NodeState 'Candidate v
+  NodeLeaderState :: LeaderState v -> NodeState 'Leader v
 ```
 
 The library's main event loop is comprised of a simple flow: Raft nodes receive
@@ -112,6 +112,7 @@
   HigherTermFoundCandidate :: Transition 'Candidate 'Follower
   BecomeLeader             :: Transition 'Candidate 'Leader
 
+  HandleClientReq          :: Transition 'Leader 'Leader
   SendHeartbeat            :: Transition 'Leader 'Leader
   DiscoverNewLeader        :: Transition 'Leader 'Follower
   HigherTermFoundLeader    :: Transition 'Leader 'Follower
@@ -171,14 +172,16 @@
 every node in the network at the same index.
 
 As the only part of the internal event loop that needs to be specified manually,
-We ask users of our library to provide an instance of the `StateMachine`
+We ask users of our library to provide an instance of the state machine `RaftStateMachinePure`
 typeclass. This typeclass relates a state machine type to a command type
-and a single type class function 'applyCommittedLogEntry', a pure function that
+and a single type class function `applyCmdRaftStateMachinePure`, a pure function that
 should return the result of applying the command to the initial state machine.
 
 ```haskell
-class StateMachine sm v | sm -> v where
-  applyCommittedLogEntry :: sm -> v -> sm
+class RaftStateMachinePure sm v | sm -> v where
+  data RaftStateMachinePureError sm v
+  type RaftStateMachinePureCtx sm v = ctx | ctx -> sm v
+  applyCmdRaftStateMachinePure :: RaftStateMachinePureCtx sm v -> sm -> v -> Either (RaftStateMachinePureError sm v) sm
 ```
 
 Everything else related to the core event handling loop is not exposed to
@@ -253,7 +256,6 @@
 ```haskell
 -- | The RaftPersist type class specifies how to read and write the persistent
 -- state to disk.
---
 class Monad m => RaftPersist m where
   type RaftPersistError m
   readPersistentState
@@ -286,23 +288,25 @@
 node to which it was sent:
 
 ```haskell
--- | Provide an interface for nodes to send messages to one
+-- | 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 ()
 
--- | Provide an interface for nodes to receive messages from one
+-- | Interface for nodes to receive messages from one
 -- another
 class RaftRecvRPC m v where
-  receiveRPC :: m (RPCMessage v)
+  type RaftRecvRPCError m v
+  receiveRPC :: m (Either (RaftRecvRPCError m v) (RPCMessage v))
 
--- | Provide an interface for Raft nodes to send messages to clients
-class RaftSendClient m sm where
-  sendClient :: ClientId -> ClientResponse sm -> m ()
+-- | Interface for Raft nodes to send messages to clients
+class RaftSendClient m sm v where
+  sendClient :: ClientId -> ClientResponse sm v -> m ()
 
--- | Provide an interface for Raft nodes to receive messages from clients
+-- | Interface for Raft nodes to receive messages from clients
 class RaftRecvClient m v where
-  receiveClient :: m (ClientRequest v)
+  type RaftRecvClientError m v
+  receiveClient :: m (Either (RaftRecvClientError m v) (ClientRequest v))
 ```
 
 We have written a default implementation for network sockets over TCP in
@@ -321,24 +325,44 @@
 2) In separate terminals, run some raft nodes:
 
     The format of the cmd line invocation is:
-    ``` raft-example <node-id> <peer-1-node-id> ... <peer-n-node-id> ```
+    ``` 
+    $ raft-example node <fresh/existing> <file/postgres> <node-id> <peer-1-node-id> ... <peer-n-node-id> 
+    
+    ```
 
     We are going to run a network of three nodes:
 
     - On terminal 1:
-    ```$ stack exec raft-example localhost:3001 localhost:3002 localhost:3003```
 
+    ```$ stack exec raft-example node fresh file localhost:3001 localhost:3002 localhost:3003```
+
     - On terminal 2:
-    ```$ stack exec raft-example localhost:3002 localhost:3001 localhost:3003```
 
+    ```$ stack exec raft-example node fresh file localhost:3002 localhost:3001 localhost:3003```
+
     - On terminal 3:
-    ```$ stack exec raft-example localhost:3003 localhost:3001 localhost:3002```
 
+    ```$ stack exec raft-example node fresh file localhost:3003 localhost:3001 localhost:3002```
+
     The first node spawned should become candidate once its election's timer
     times out and request votes to other nodes. It will then become the leader,
     once it receives a majority of votes and will broadcast messages to all
     nodes at each heartbeat.
 
+    **Note:** If you want to run a raft example node with _existing_ persistent data,
+    pass the `existing` command line option to the `raft-example` program instead
+    of `fresh`:
+    
+    ```$ stack exec raft-example node existing ...```
+
+
+    **Note:** The example also runs using a PostgreSQL database as long as a
+    user 'libraft_test' with password 'libraft_test' exists in your local
+    postgresql installation. For ease of experimentation, if such a user does
+    not exist, create it like so:
+
+    ```$ sudo -su postgres psql -U postgres -c "CREATE USER libraft_test WITH CREATEDB PASSWORD 'libraft_test';"```
+
 3) Run a client:
 ```$ stack exec raft-example client```
 
@@ -382,12 +406,18 @@
 
 ### Define the state machine
 
-The only requirement for our state machine is to instantiate the `StateMachine`
+The only requirement for our state machine is to instantiate the state machine `RaftStateMachinePure`
 type class.
 
 ```haskell
-class StateMachine sm v | sm -> v where
-  applyCommittedLogEntry :: sm -> v -> sm
+-- | Interface to handle commands in the underlying state machine. Functional
+--dependency permitting only a single state machine command to be defined to
+--update the state machine.
+class RaftStateMachinePure sm v | sm -> v where
+  data RaftStateMachinePureError sm v
+  type RaftStateMachinePureCtx sm v = ctx | ctx -> sm v
+  rsmTransition :: RaftStateMachinePureCtx sm v -> sm -> v -> Either (RaftStateMachinePureError sm v) sm
+  
 ```
 
 In our [example](https://github.com/adjoint-io/raft/blob/master/app/Main.hs) we
@@ -433,11 +463,22 @@
 State](https://github.com/adjoint-io/raft#persistent-state) section above, we
 will create instances for `RaftReadLog`, `RaftWriteLog` and
 `RaftDeleteLog` to specify how we will read, write and
-delete log entries, as well as `RaftPersist`.
+delete log entries, as well as `RaftPersist`. There are actually several data
+that must be stored on disk; 1) the data the raft paper calls "persistent data"
+and 2) the log entries of the node.
 
-We provide an implementation that stores persistent data on files in
-[FileStore.hs](https://github.com/adjoint-io/raft/blob/master/src/Examples/Raft/FileStore.hs)
+We provide an implementation that stores the persistent data in a file in
+[src/Examples/Raft/FileStore/Persistent.hs](https://github.com/adjoint-io/raft/blob/master/src/Examples/Raft/FileStore/Persistent.hs)
 
+An example of storing log entries in a single file (for ease of implementation
+in lieu of good performance for reads/writes) can be found in
+[src/Examples/Raft/FileStore/Log.hs](https://github.com/adjoint-io/raft/blob/master/src/Examples/Raft/FileStore/Log.hs)
+
+Lastly, a more "production ready" example of log entry storage using a
+PostgreSQL database can be found in [src/Raft/Log/PostgreSQL.hs](https://github.com/adjoint-io/raft/blob/master/src/Raft/Log/PostgreSQL.hs).
+This implementation is used in our `quickcheck-state-machine` model testing
+module and is thus thoroughly tested. 
+
 ### Putting it all together
 
 The last step is wrapping our previous data types that deal with
@@ -454,6 +495,19 @@
 [src/Raft.hs](https://github.com/adjoint-io/raft/blob/master/src/Raft.hs)
 file, together with the function we define to run the stack of monads that
 derive our Raft type classes.
+
+# Test suite dependencies
+
+The test suite depends on libfiu (commonly installed with package `fiu-utils`), 
+which it uses to simulate network failures. In addition, the test suite also
+depends on libpq-dev and postgresql. Furthermore, in order to successfully run
+the model tests (which will run autmatically when executing `stack test`, but
+fail immediately with "Failed to spawn node), you will have to run the following
+command:
+
+```
+$ sudo -su postgres psql -U postgres -c "CREATE USER libraft_test WITH CREATEDB PASSWORD 'libraft_test';" 
+```
 
 # References
 
diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -25,30 +25,31 @@
 import Control.Monad.Catch
 import Control.Monad.Trans.Class
 
-import Data.Sequence ((><))
 import qualified Data.Map as Map
 import qualified Data.List as L
 import qualified Data.Set as Set
-import qualified Data.Sequence as Seq
 import qualified Data.Serialize as S
-import qualified Network.Simple.TCP as NS
-import Network.Simple.TCP
-import qualified Network.Socket as N
-import qualified Network.Socket.ByteString as NSB
+
 import Numeric.Natural
+
 import System.Console.Repline
-import System.Console.Haskeline.MonadException hiding (handle)
 import Text.Read hiding (lift)
 import System.Random
 import qualified System.Directory as Directory
 
+import Raft
+import Raft.Config
+import Raft.Log
+import Raft.Log.PostgreSQL
+import Raft.Client
+
+import Database.PostgreSQL.Simple
+
 import qualified Examples.Raft.Socket.Client as RS
-import qualified Examples.Raft.Socket.Node as RS
 import Examples.Raft.Socket.Node
 import qualified Examples.Raft.Socket.Common as RS
-
-import Examples.Raft.FileStore
-import Raft
+import Examples.Raft.FileStore.Log
+import Examples.Raft.FileStore.Persistent
 
 ------------------------------
 -- State Machine & Commands --
@@ -68,18 +69,18 @@
 
 type Store = Map Var Natural
 
-instance RSMP Store StoreCmd where
-  data RSMPError Store StoreCmd = StoreError Text deriving (Show)
-  type RSMPCtx Store StoreCmd = ()
+instance RaftStateMachinePure Store StoreCmd where
+  data RaftStateMachinePureError Store StoreCmd = StoreError Text deriving (Show)
+  type RaftStateMachinePureCtx Store StoreCmd = ()
 
-  applyCmdRSMP _ store cmd =
+  rsmTransition _ store cmd =
     Right $ case cmd of
       Set x n -> Map.insert x n store
       Incr x -> Map.adjust succ x store
 
-instance (sm ~ Store, v ~ StoreCmd, RSMP sm v) => RSM sm v (RaftExampleM sm v) where
+instance (Monad m, sm ~ Store, v ~ StoreCmd, RaftStateMachinePure sm v) => RaftStateMachine (RaftExampleM m sm v) sm v where
   validateCmd _ = pure (Right ())
-  askRSMPCtx = pure ()
+  askRaftStateMachinePureCtx = pure ()
 
 --------------------
 -- Raft instances --
@@ -90,53 +91,73 @@
   , nEnvNodeId :: NodeId
   }
 
-newtype RaftExampleM sm v a = RaftExampleM { unRaftExampleM :: ReaderT (NodeEnv sm) (RaftSocketT v (RaftFileStoreT IO)) a }
-  deriving (Functor, Applicative, Monad, MonadIO, MonadFail, MonadReader (NodeEnv sm), Alternative, MonadPlus)
+newtype RaftExampleM m sm v a = RaftExampleM {
+    unRaftExampleM :: ReaderT (NodeEnv sm) (RaftSocketT v (RaftPersistFileStoreT m)) a
+  }
 
-deriving instance MonadThrow (RaftExampleM sm v)
-deriving instance MonadCatch (RaftExampleM sm v)
-deriving instance MonadMask (RaftExampleM sm v)
-deriving instance MonadConc (RaftExampleM sm v)
+deriving instance Functor m => Functor (RaftExampleM m sm v)
+deriving instance Applicative m => Applicative (RaftExampleM m sm v)
+deriving instance Monad m => Monad (RaftExampleM m sm v)
+deriving instance MonadIO m => MonadIO (RaftExampleM m sm v)
+deriving instance MonadFail m => MonadFail (RaftExampleM m sm v)
+deriving instance Monad m => MonadReader (NodeEnv sm) (RaftExampleM m sm v)
+deriving instance Alternative m => Alternative (RaftExampleM m sm v)
+deriving instance MonadPlus m => MonadPlus (RaftExampleM m sm v)
 
-runRaftExampleM :: NodeEnv sm -> NodeSocketEnv v -> NodeFileStoreEnv -> RaftExampleM sm v a -> IO a
-runRaftExampleM nodeEnv nodeSocketEnv nodeFileStoreEnv raftExampleM =
-  runReaderT (unRaftFileStoreT $
-    runReaderT (unRaftSocketT $
-      runReaderT (unRaftExampleM raftExampleM) nodeEnv) nodeSocketEnv)
-        nodeFileStoreEnv
+deriving instance MonadThrow m => MonadThrow (RaftExampleM m sm v)
+deriving instance MonadCatch m => MonadCatch (RaftExampleM m sm v)
+deriving instance MonadMask m => MonadMask (RaftExampleM m sm v)
+deriving instance MonadConc m => MonadConc (RaftExampleM m sm v)
 
-instance RaftSendClient (RaftExampleM Store StoreCmd) Store where
+runRaftExampleM
+  :: (MonadIO m, MonadConc m)
+  => NodeEnv sm
+  -> NodeSocketEnv v
+  -> RaftPersistFile
+  -> RaftExampleM m sm v a
+  -> m a
+runRaftExampleM nodeEnv nodeSocketEnv raftPersistFile raftExampleM =
+  flip runReaderT raftPersistFile . unRaftPersistFileStoreT $
+    flip runReaderT nodeSocketEnv . unRaftSocketT $
+        flip runReaderT nodeEnv $ unRaftExampleM raftExampleM
+
+instance (MonadIO m, MonadConc m) => RaftSendClient (RaftExampleM m Store StoreCmd) Store StoreCmd where
   sendClient cid msg = (RaftExampleM . lift) $ sendClient cid msg
 
-instance RaftRecvClient (RaftExampleM Store StoreCmd) StoreCmd where
-  type RaftRecvClientError (RaftExampleM Store StoreCmd) StoreCmd = Text
+instance (MonadIO m, MonadConc m) => RaftRecvClient (RaftExampleM m Store StoreCmd) StoreCmd where
+  type RaftRecvClientError (RaftExampleM m Store StoreCmd) StoreCmd = Text
   receiveClient = RaftExampleM $ lift receiveClient
 
-instance RaftSendRPC (RaftExampleM Store StoreCmd) StoreCmd where
+instance (MonadIO m, MonadConc m) => RaftSendRPC (RaftExampleM m Store StoreCmd) StoreCmd where
   sendRPC nid msg = (RaftExampleM . lift) $ sendRPC nid msg
 
-instance RaftRecvRPC (RaftExampleM Store StoreCmd) StoreCmd where
-  type RaftRecvRPCError (RaftExampleM Store StoreCmd) StoreCmd = Text
+instance (MonadIO m, MonadConc m) => RaftRecvRPC (RaftExampleM m Store StoreCmd) StoreCmd where
+  type RaftRecvRPCError (RaftExampleM m Store StoreCmd) StoreCmd = Text
   receiveRPC = RaftExampleM $ lift receiveRPC
 
-instance RaftWriteLog (RaftExampleM Store StoreCmd) StoreCmd where
-  type RaftWriteLogError (RaftExampleM Store StoreCmd) = NodeEnvError
-  writeLogEntries entries = RaftExampleM $ lift $ RaftSocketT (lift $ writeLogEntries entries)
+instance (MonadIO m, MonadConc m) => RaftPersist (RaftExampleM m Store StoreCmd) where
+  type RaftPersistError (RaftExampleM m Store StoreCmd) = RaftPersistFileStoreError
+  initializePersistentState = RaftExampleM $ lift $ lift initializePersistentState
+  writePersistentState ps = RaftExampleM $ lift $ lift $ writePersistentState ps
+  readPersistentState = RaftExampleM $ lift $ lift $ readPersistentState
 
-instance RaftPersist (RaftExampleM Store StoreCmd) where
-  type RaftPersistError (RaftExampleM Store StoreCmd) = NodeEnvError
-  writePersistentState ps = RaftExampleM $ lift $ RaftSocketT (lift $ writePersistentState ps)
-  readPersistentState = RaftExampleM $ lift $ RaftSocketT (lift $ readPersistentState)
+instance (MonadConc m, RaftInitLog m StoreCmd) => RaftInitLog (RaftExampleM m Store StoreCmd) StoreCmd where
+  type RaftInitLogError (RaftExampleM m Store StoreCmd) = RaftInitLogError m
+  initializeLog p = RaftExampleM $ lift $ lift $ lift $ initializeLog p
 
-instance RaftReadLog (RaftExampleM Store StoreCmd) StoreCmd where
-  type RaftReadLogError (RaftExampleM Store StoreCmd) = NodeEnvError
-  readLogEntry idx = RaftExampleM $ lift $ RaftSocketT (lift $ readLogEntry idx)
-  readLastLogEntry = RaftExampleM $ lift $ RaftSocketT (lift readLastLogEntry)
+instance RaftWriteLog m StoreCmd => RaftWriteLog (RaftExampleM m Store StoreCmd) StoreCmd where
+  type RaftWriteLogError (RaftExampleM m Store StoreCmd) = RaftWriteLogError m
+  writeLogEntries entries = RaftExampleM $ lift $ lift $ lift $ writeLogEntries entries
 
-instance RaftDeleteLog (RaftExampleM Store StoreCmd) StoreCmd where
-  type RaftDeleteLogError (RaftExampleM Store StoreCmd) = NodeEnvError
-  deleteLogEntriesFrom idx = RaftExampleM $ lift $ RaftSocketT (lift $ deleteLogEntriesFrom idx)
+instance RaftReadLog m StoreCmd => RaftReadLog (RaftExampleM m Store StoreCmd) StoreCmd where
+  type RaftReadLogError (RaftExampleM m Store StoreCmd) = RaftReadLogError m
+  readLogEntry idx = RaftExampleM $ lift $ lift $ lift $ readLogEntry idx
+  readLastLogEntry = RaftExampleM $ lift $ lift $ lift $ readLastLogEntry
 
+instance RaftDeleteLog m StoreCmd => RaftDeleteLog (RaftExampleM m Store StoreCmd) StoreCmd where
+  type RaftDeleteLogError (RaftExampleM m Store StoreCmd) = RaftDeleteLogError m
+  deleteLogEntriesFrom idx = RaftExampleM $ lift $ lift $ lift $ deleteLogEntriesFrom idx
+
 --------------------
 -- Client console --
 --------------------
@@ -154,138 +175,126 @@
 -- - incr <var>
 --      Increment the value of a variable
 
-data ConsoleState = ConsoleState
-  { csNodeIds :: NodeIds -- ^ Set of node ids that the client is aware of
-  , csSocket :: Socket -- ^ Client's socket
-  , csHost :: HostName -- ^ Client's host
-  , csPort :: ServiceName -- ^ Client's port
-  , csLeaderId :: TVar (STM IO) (Maybe LeaderId) -- ^ Node id of the leader in the Raft network
-  }
-
-newtype ConsoleT m a = ConsoleT
-  { unConsoleT :: StateT ConsoleState m a
-  } deriving (Functor, Applicative, Monad, MonadIO, MonadState ConsoleState)
-
 newtype ConsoleM a = ConsoleM
-  { unConsoleM :: HaskelineT (ConsoleT IO) a
-  } deriving (Functor, Applicative, Monad, MonadIO, MonadState ConsoleState)
+  { unConsoleM :: HaskelineT (RS.RaftSocketClientM Store StoreCmd) a
+  } deriving (Functor, Applicative, Monad, MonadIO)
 
-instance MonadException m => MonadException (ConsoleT m) where
-  controlIO f =
-    ConsoleT $ StateT $ \s ->
-      controlIO $ \(RunIO run) ->
-        let run' = RunIO (fmap (ConsoleT . StateT . const) . run . flip runStateT s . unConsoleT)
-        in flip runStateT s . unConsoleT <$> f run'
+liftRSCM = ConsoleM . lift
 
 -- | Evaluate and handle each line user inputs
 handleConsoleCmd :: [Char] -> ConsoleM ()
 handleConsoleCmd input = do
-  nids <- gets csNodeIds
-  clientSocket <- gets csSocket
-  clientHost <- gets csHost
-  clientPort <- gets csPort
-  leaderIdT <-  gets csLeaderId
-  leaderIdM <- liftIO $ atomically $ readTVar leaderIdT
-  let clientSocketEnv = RS.ClientSocketEnv clientPort clientHost clientSocket
+  nids <- liftRSCM clientGetNodes
   case L.words input of
-    ["addNode", nid] -> modify (\st -> st { csNodeIds = Set.insert (toS nid) (csNodeIds st) })
-    ["getNodes"] -> print nids
-    ["read"] -> if nids == Set.empty
-      then putText "Please add some nodes to query first. Eg. `addNode localhost:3001`"
-      else do
-        respE <- liftIO $ RS.runRaftSocketClientM clientSocketEnv $ case leaderIdM of
-          Nothing -> RS.sendReadRndNode (Proxy :: Proxy StoreCmd) nids
-          Just (LeaderId nid) -> RS.sendRead (Proxy :: Proxy StoreCmd) nid
-        handleClientResponseE input respE leaderIdT
-    ["incr", cmd] -> do
-      respE <- liftIO $ RS.runRaftSocketClientM clientSocketEnv $ case leaderIdM of
-        Nothing -> RS.sendWriteRndNode (Incr (toS cmd)) nids
-        Just (LeaderId nid) -> RS.sendWrite (Incr (toS cmd)) nid
-      handleClientResponseE input respE leaderIdT
-    ["set", var, val] -> do
-      respE <- liftIO $ RS.runRaftSocketClientM clientSocketEnv $ case leaderIdM of
-        Nothing -> RS.sendWriteRndNode (Set (toS var) (read val)) nids
-        Just (LeaderId nid) -> RS.sendWrite (Set (toS var) (read val)) nid
-      handleClientResponseE input respE leaderIdT
+    ["addNode", nid] -> liftRSCM $ clientAddNode (toS nid)
+    ["getNodes"] -> print =<< liftRSCM clientGetNodes
+    ["read"] ->
+      ifNodesAdded nids $
+        handleResponse =<< liftRSCM (RS.socketClientRead ClientReadStateMachine)
+    ["read", n] ->
+      ifNodesAdded nids $
+        handleResponse =<< liftRSCM (RS.socketClientRead (ClientReadEntries (ByIndex (Index (read n)))))
+    ["read", "[", low, high, "]" ] ->
+      ifNodesAdded nids $ do
+        let byInterval = ByIndices $ IndexInterval (Just (Index (read low))) (Just (Index (read high)))
+        handleResponse =<< liftRSCM (RS.socketClientRead (ClientReadEntries byInterval))
+    ["incr", cmd] ->
+      ifNodesAdded nids $
+        handleResponse =<< liftRSCM (RS.socketClientWrite (Incr (toS cmd)))
+    ["set", var, val] ->
+      ifNodesAdded nids $
+        handleResponse =<< liftRSCM (RS.socketClientWrite (Set (toS var) (read val)))
     _ -> print "Invalid command. Press <TAB> to see valid commands"
 
   where
-    handleClientResponseE :: [Char] -> Either [Char] (ClientResponse Store) -> TVar (STM IO) (Maybe LeaderId) -> ConsoleM ()
-    handleClientResponseE input eMsgE leaderIdT =
-      case eMsgE of
-        Left err -> panic $ toS err
-        Right (ClientRedirectResponse (ClientRedirResp leader)) ->
-          case leader of
-            NoLeader -> do
-              putText "Sorry, the system doesn't have a leader at the moment"
-              liftIO $ atomically $ writeTVar leaderIdT Nothing
-            -- If the message was not sent to the leader, that node will
-            -- point to the current leader
-            CurrentLeader lid -> do
-              putText $ "New leader found: " <> show lid
-              liftIO $ atomically $ writeTVar leaderIdT (Just lid)
-              handleConsoleCmd input
-        Right (ClientReadResponse (ClientReadResp sm)) -> putText $ "Received sm: " <> show sm
-        Right (ClientWriteResponse writeResp) -> print writeResp
+    ifNodesAdded nids m
+      | nids == Set.empty =
+          putText "Please add some nodes to query first. Eg. `addNode localhost:3001`"
+      | otherwise = m
 
+    handleResponse :: Show a => Either Text a -> ConsoleM ()
+    handleResponse res = do
+      case res of
+        Left err -> liftIO $ putText err
+        Right resp -> liftIO $ putText (show resp)
 
+data LogStorage = FileStore | PostgreSQL [Char]
+
 main :: IO ()
 main = do
     args <- (toS <$>) <$> getArgs
     case args of
-      ["client"] -> clientMainHandler
-      (nid:nids) -> do
-        removeExampleFiles nid
-        createExampleFiles nid
-
+      ["client"] -> clientMain
+      ("node":"fresh":"file":nid:nids) -> initNode New FileStore (nid:nids)
+      ("node":"existing":"file":nid:nids) -> initNode Existing FileStore (nid:nids)
+      ("node":"fresh":"postgres":nm:nid:nids) -> initNode New (PostgreSQL $ toS nm) (nid:nids)
+      ("node":"existing":"postgres":nm:nid:nids) -> initNode Existing (PostgreSQL $ toS nm) (nid:nids)
+  where
+    initNode storageState storageType (nid:nids) = do
+        nodeDir <- mkExampleDir nid
+        case storageState of
+          New -> cleanStorage nodeDir storageType
+          Existing -> pure ()
         nSocketEnv <- initSocketEnv nid
-        nPersistentEnv <- initRaftFileStoreEnv nid
         nEnv <- initNodeEnv nid
-        runRaftExampleM nEnv nSocketEnv nPersistentEnv $ do
-          let allNodeIds = Set.fromList (nid : nids)
-          let nodeConfig = NodeConfig
-                            { configNodeId = toS nid
-                            , configNodeIds = allNodeIds
-                            , configElectionTimeout = (1500000, 3000000)
-                            , configHeartbeatTimeout = 200000
-                            }
-          RaftExampleM $ lift acceptForkNode :: RaftExampleM Store StoreCmd ()
-          electionTimerSeed <- liftIO randomIO
-          runRaftNode nodeConfig LogStdout electionTimerSeed (mempty :: Store)
-  where
-    initPersistentFile :: NodeId -> IO ()
-    initPersistentFile nid = do
-      psPath <- persistentFile nid
-      writeFile psPath (toS $ S.encode initPersistentState)
+        nPersistFile <- RaftPersistFile <$> persistentFilePath nid
+        case storageType of
+          FileStore -> do
+            nLogsFile <- RaftLogFile <$> logsFilePath nid
+            runRaftLogFileStoreT nLogsFile $
+              runRaftNode' nSocketEnv nEnv nPersistFile
+          PostgreSQL dbName -> do
+            let pgConnInfo = raftDatabaseConnInfo "libraft_test" "libraft_test" dbName
+            runRaftPostgresM pgConnInfo $
+              runRaftNode' nSocketEnv nEnv nPersistFile
+     where
+        runRaftNode'
+          :: ( MonadIO m, MonadConc m, MonadFail m
+             , RaftInitLog m StoreCmd, RaftReadLog m StoreCmd, RaftWriteLog m StoreCmd
+             , RaftDeleteLog m StoreCmd, Exception (RaftInitLogError m), Exception (RaftReadLogError m)
+             , Exception (RaftWriteLogError m), Exception (RaftDeleteLogError m), Typeable m
+             )
+          => NodeSocketEnv StoreCmd
+          -> NodeEnv Store
+          -> RaftPersistFile
+          -> m ()
+        runRaftNode' nSocketEnv nEnv nPersistFile =
+          runRaftExampleM nEnv nSocketEnv nPersistFile $ do
+            let allNodeIds = Set.fromList (nid : nids)
+            let (host, port) = RS.nidToHostPort (toS nid)
+            let nodeConfig = NodeConfig
+                              { configNodeId = toS nid
+                              , configNodeIds = allNodeIds
+                              -- These are recommended timeouts from the original
+                              -- raft paper and the ARC report.
+                              , configElectionTimeout = (150000, 300000)
+                              , configHeartbeatTimeout = 50000
+                              , configStorageState = storageState
+                              }
+            fork $ RaftExampleM $ lift (acceptConnections host port)
+            electionTimerSeed <- liftIO randomIO
+            runRaftNode nodeConfig (LogCtx LogStdout Debug) electionTimerSeed (mempty :: Store)
 
-    persistentFile :: NodeId -> IO FilePath
-    persistentFile nid = do
+    cleanStorage :: FilePath -> LogStorage -> IO ()
+    cleanStorage nodeDir ls = do
+      case ls of
+        PostgreSQL dbName -> do
+          Control.Monad.Catch.bracket (connect initConnInfo) close $ \conn ->
+            void $ deleteDB (raftDatabaseName dbName) conn
+        _ -> pure ()
+      Directory.removePathForcibly nodeDir
+      Directory.createDirectoryIfMissing False nodeDir
+
+    persistentFilePath :: NodeId -> IO FilePath
+    persistentFilePath nid = do
       tmpDir <- Directory.getTemporaryDirectory
       pure $ tmpDir ++ "/" ++ toS nid ++ "/" ++ "persistent"
 
-    initLogsFile :: NodeId -> IO ()
-    initLogsFile nid = do
-      logsPath <- logsFile nid
-      writeFile logsPath (toS $ S.encode (mempty :: Entries StoreCmd))
-
-    logsFile :: NodeId -> IO FilePath
-    logsFile nid = do
+    logsFilePath :: NodeId -> IO FilePath
+    logsFilePath nid = do
       tmpDir <- Directory.getTemporaryDirectory
       pure (tmpDir ++ "/" ++ toS nid ++ "/" ++ "logs")
 
-    createExampleFiles :: NodeId -> IO ()
-    createExampleFiles nid = void $ do
-      tmpDir <- Directory.getTemporaryDirectory
-      Directory.createDirectory (tmpDir ++ "/" ++ toS nid)
-      initPersistentFile nid
-      initLogsFile nid
-
-    removeExampleFiles :: NodeId -> IO ()
-    removeExampleFiles nid = handle (const (pure ()) :: SomeException -> IO ()) $ do
-      tmpDir <- Directory.getTemporaryDirectory
-      Directory.removeDirectoryRecursive (tmpDir ++ "/" ++ toS nid)
-
-
     initNodeEnv :: NodeId -> IO (NodeEnv Store)
     initNodeEnv nid = do
       let (host, port) = RS.nidToHostPort (toS nid)
@@ -295,48 +304,30 @@
         , nEnvNodeId = toS host <> ":" <> toS port
         }
 
-    initRaftFileStoreEnv :: NodeId -> IO NodeFileStoreEnv
-    initRaftFileStoreEnv nid = do
-      psPath <- persistentFile nid
-      psLogs <- logsFile nid
-      pure NodeFileStoreEnv
-            { nfsPersistentState = psPath
-            , nfsLogEntries = psLogs
-            }
-
     initSocketEnv :: NodeId -> IO (NodeSocketEnv v)
     initSocketEnv nid = do
-      let (host, port) = RS.nidToHostPort (toS nid)
-      nodeSocket <- newSock host port
-      socketPeersTVar <- atomically (newTVar mempty)
       msgQueue <- atomically newTChan
       clientReqQueue <- atomically newTChan
       pure NodeSocketEnv
-            { nsPeers = socketPeersTVar
-            , nsSocket = nodeSocket
-            , nsMsgQueue = msgQueue
-            , nsClientReqQueue = clientReqQueue
-            }
+        { nsMsgQueue = msgQueue
+        , nsClientReqQueue = clientReqQueue
+        }
 
-    clientMainHandler :: IO ()
-    clientMainHandler = do
+    mkExampleDir :: NodeId -> IO FilePath
+    mkExampleDir nid = do
+      tmpDir <- Directory.getTemporaryDirectory
+      let nodeDir = tmpDir ++ "/" ++ toS nid
+      pure nodeDir
+
+    clientMain :: IO ()
+    clientMain = do
+      let clientHost = "localhost"
       clientPort <- RS.getFreePort
-      clientSocket <- RS.newSock "localhost" clientPort
-      leaderIdT <- atomically (newTVar Nothing)
-      let initClientState = ConsoleState
-                                  { csNodeIds = mempty
-                                  , csSocket = clientSocket
-                                  , csHost = "localhost"
-                                  , csPort = clientPort
-                                  , csLeaderId = leaderIdT
-                                  }
-      runConsoleT initClientState $
+      let clientId = ClientId $ RS.hostPortToNid (clientHost, clientPort)
+      clientRespChan <- RS.newClientRespChan
+      RS.runRaftSocketClientM clientId mempty clientRespChan $ do
+        fork (lift (RS.clientResponseServer clientHost clientPort))
         evalRepl (pure ">>> ") (unConsoleM . handleConsoleCmd) [] Nothing (Word completer) (pure ())
-
-    runConsoleT :: Monad m => ConsoleState -> ConsoleT m a -> m a
-    runConsoleT consoleState = flip evalStateT consoleState . unConsoleT
-
-
 
     -- Tab Completion: return a completion for partial words entered
     completer :: Monad m => WordCompleter m
diff --git a/libraft.cabal b/libraft.cabal
--- a/libraft.cabal
+++ b/libraft.cabal
@@ -2,11 +2,13 @@
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: 0e9cecc332c8c5b872a193fa8e1bcf55c994bce6217b0d17f13894079c45af32
+-- hash: 2d373c5064dc13cca463f24496ffaca3207f2659d824261e3338e264da11247e
 
 name:           libraft
-version:        0.1.1.0
+version:        0.2.0.0
+synopsis:       Raft consensus algorithm
 description:    Please see the README on GitHub at <https://github.com/adjoint-io/raft#readme>
+category:       Distributed Systems
 homepage:       https://github.com/adjoint-io/raft#readme
 bug-reports:    https://github.com/adjoint-io/raft/issues
 author:         Adjoint Inc.
@@ -27,7 +29,8 @@
 library
   exposed-modules:
       Control.Concurrent.STM.Timer
-      Examples.Raft.FileStore
+      Examples.Raft.FileStore.Log
+      Examples.Raft.FileStore.Persistent
       Examples.Raft.Socket.Client
       Examples.Raft.Socket.Common
       Examples.Raft.Socket.Node
@@ -41,11 +44,13 @@
       Raft.Handle
       Raft.Leader
       Raft.Log
+      Raft.Log.PostgreSQL
       Raft.Logging
       Raft.Monad
       Raft.NodeState
       Raft.Persistent
       Raft.RPC
+      Raft.StateMachine
       Raft.Types
   other-modules:
       Paths_libraft
@@ -54,25 +59,33 @@
   default-extensions: NoImplicitPrelude OverloadedStrings LambdaCase
   ghc-options: -fwarn-unused-binds -fwarn-unused-imports
   build-depends:
-      attoparsec
+      atomic-write
+    , attoparsec
     , base >=4.7 && <5
+    , base16-bytestring
     , bytestring
     , cereal
-    , concurrency
+    , concurrency >=1.3.0.0 && <1.7.0.0
     , containers
+    , cryptohash-sha256
     , directory
     , exceptions
+    , file-embed
     , haskeline
+    , lifted-base
+    , monad-control
     , mtl
     , network
     , network-simple
     , parsec
+    , postgresql-simple
     , protolude
     , random
     , repline
     , text
     , time
     , transformers
+    , transformers-base
     , word8
   default-language: Haskell2010
 
@@ -85,20 +98,27 @@
   default-extensions: NoImplicitPrelude OverloadedStrings LambdaCase
   ghc-options: -threaded -rtsopts -with-rtsopts=-N
   build-depends:
-      attoparsec
+      atomic-write
+    , attoparsec
     , base >=4.7 && <5
+    , base16-bytestring
     , bytestring
     , cereal
-    , concurrency
+    , concurrency >=1.3.0.0 && <1.7.0.0
     , containers
+    , cryptohash-sha256
     , directory
     , exceptions
+    , file-embed
     , haskeline
     , libraft
+    , lifted-base
+    , monad-control
     , mtl
     , network
     , network-simple
     , parsec
+    , postgresql-simple
     , protolude
     , random
     , repline
@@ -106,6 +126,7 @@
     , text
     , time
     , transformers
+    , transformers-base
     , word8
   default-language: Haskell2010
 
@@ -113,8 +134,8 @@
   type: exitcode-stdio-1.0
   main-is: TestDriver.hs
   other-modules:
+      QuickCheckStateMachine
       TestDejaFu
-      TestRaft
       TestUtils
       Paths_libraft
   hs-source-dirs:
@@ -123,23 +144,32 @@
   ghc-options: -threaded -rtsopts -with-rtsopts=-N
   build-depends:
       QuickCheck
+    , atomic-write
     , attoparsec
     , base >=4.7 && <5
+    , base16-bytestring
     , bytestring
     , cereal
-    , concurrency
+    , concurrency >=1.3.0.0 && <1.7.0.0
     , containers
-    , dejafu
+    , cryptohash-sha256
+    , dejafu >=1.0.0.0 && <2.0.0.0
     , directory
     , exceptions
+    , file-embed
     , haskeline
     , hunit-dejafu
     , libraft
+    , lifted-base
+    , monad-control
     , mtl
     , network
     , network-simple
     , parsec
+    , postgresql-simple
+    , process >=1.6.3.0 && <1.7.0.0
     , protolude
+    , quickcheck-state-machine
     , random
     , repline
     , tasty
@@ -151,5 +181,7 @@
     , text
     , time
     , transformers
+    , transformers-base
+    , tree-diff
     , word8
   default-language: Haskell2010
diff --git a/src/Control/Concurrent/STM/Timer.hs b/src/Control/Concurrent/STM/Timer.hs
--- a/src/Control/Concurrent/STM/Timer.hs
+++ b/src/Control/Concurrent/STM/Timer.hs
@@ -1,75 +1,94 @@
 
 module Control.Concurrent.STM.Timer (
   Timer,
-  waitTimer,
-  startTimer,
-  resetTimer,
   newTimer,
   newTimerRange,
+  startTimer,
+  resetTimer,
+  waitTimer,
 ) where
 
-import Protolude hiding (STM, killThread, ThreadId, threadDelay, myThreadId, atomically)
+import Protolude hiding (wait, async, withAsync, cancel, Async, STM, killThread, ThreadId, threadDelay, myThreadId, atomically)
 
 import Control.Monad.Conc.Class
 import Control.Concurrent.Classy.STM
+import Control.Concurrent.Classy.Async
 import System.Random (StdGen, randomR, mkStdGen)
 
 import Numeric.Natural
 
 data Timer m = Timer
-  { timerThread :: TMVar (STM m) (ThreadId m)
+  { timerAsync :: TMVar (STM m) (Async m ())
+    -- ^ The async computation of the timer
   , timerLock :: TMVar (STM m) ()
-  , timerRange :: (Natural, Natural)
+    -- ^ When the TMVar is empty, the timer is being used
   , timerGen :: TVar (STM m) StdGen
+  , timerRange :: (Natural, Natural)
+  , timerAction :: m ()
   }
 
-waitTimer :: MonadConc m => Timer m -> m ()
-waitTimer (Timer _ lock _ _) =
-  atomically $ readTMVar lock
+-- | Create a new timer with the supplied timer action and timer length,
+newTimer :: MonadConc m => m () -> Natural -> m (Timer m)
+newTimer action timeout = newTimerRange action 0 (timeout, timeout)
 
--- | Starting a timer will only work if the timer is currently stopped
-startTimer :: MonadConc m => Timer m -> m ()
+-- | Create a new timer with the supplied timer action, random seed, and range
+-- from which the the timer will choose a random timer length at each
+-- start or reset.
+newTimerRange :: MonadConc m => m () -> Int -> (Natural, Natural) -> m (Timer m)
+newTimerRange action seed timeoutRange = do
+  (timerAsync, timerLock, timerGen) <-
+    atomically $ (,,) <$> newEmptyTMVar <*> newTMVar () <*> newTVar (mkStdGen seed)
+  pure $ Timer timerAsync timerLock timerGen timeoutRange action
+
+--------------------------------------------------------------------------------
+
+-- | Start the timer. If the timer is already running, the timer is not started.
+-- Returns True if the timer was succesfully started.
+startTimer :: MonadConc m => Timer m -> m Bool
 startTimer timer = do
-  lock <- atomically $ tryTakeTMVar (timerLock timer)
-  case lock of
-    Nothing -> pure ()
-    Just () -> spawnTimer timer
+  mlock <- atomically $ tryTakeTMVar (timerLock timer)
+  case mlock of
+    Nothing -> pure False
+    Just () -> resetTimer timer >> pure True
 
+-- | Resets the timer with a new random timeout.
 resetTimer :: MonadConc m => Timer m -> m ()
 resetTimer timer = do
-  -- Kill the old timer
-  mOldTid <- atomically $ tryTakeTMVar (timerThread timer)
-  case mOldTid of
-    Just oldTid -> killThread oldTid
+
+  -- Check if a timer is already running. If it is, asynchronously kill the
+  -- thread.
+  mta <- atomically $ tryTakeTMVar (timerAsync timer)
+  case mta of
     Nothing -> pure ()
-  -- Spawn a new timer
-  spawnTimer timer
+    Just ta -> void $ async (uninterruptibleCancel ta)
 
--- | Spawn a timer thread. This function assumes that there are no other threads
--- are using the timer
-spawnTimer :: MonadConc m => Timer m -> m ()
-spawnTimer timer = do
-  g <- atomically $ readTVar (timerGen timer)
-  let (tmin, tmax) = timerRange timer
-      (n, g') = randomR (toInteger tmin, toInteger tmax) g
-  atomically $ writeTVar (timerGen timer) g'
-  void $ fork $ do
-    threadId <- myThreadId
-    atomically $ do
-      _ <- tryTakeTMVar (timerLock timer)
-      _ <- tryTakeTMVar (timerThread timer)
-      void $ putTMVar (timerThread timer) threadId
-    threadDelay (fromIntegral n)
-    atomically $ do
-      _ <- tryTakeTMVar (timerThread timer)
-      putTMVar (timerLock timer) ()
+  -- Fork a new async computation that waits the specified (random) amount of
+  -- time, performs the timer action, and then puts the lock back signaling the
+  -- timer finishing.
+  ta <- async $ do
+    threadDelay =<< randomDelay timer
+    timerAction timer
+    success <- atomically $ tryPutTMVar (timerLock timer) ()
+    when (not success) $
+      panic "[Failed Invariant]: Putting the timer lock back should succeed"
 
-newTimer :: MonadConc m => Natural -> m (Timer m)
-newTimer timeout = newTimerRange 0 (timeout, timeout)
+  -- Check that putting the new async succeeded. If it did not, there is a race
+  -- condition and the newly created async should be canceled. Warning: This may
+  -- not work for _very_ short timers.
+  success <- atomically $ tryPutTMVar (timerAsync timer) ta
+  when (not success) $
+    void $ async (uninterruptibleCancel ta)
 
--- | Create a new timer with the given random seed and range of timer timeouts.
-newTimerRange :: MonadConc m => Int -> (Natural, Natural) -> m (Timer m)
-newTimerRange seed timeoutRange = do
-  (timerThread, timerLock, timerGen) <-
-    atomically $ (,,) <$> newEmptyTMVar <*> newTMVar () <*> newTVar (mkStdGen seed)
-  pure $ Timer timerThread timerLock timeoutRange timerGen
+-- | Wait for a timer to complete
+waitTimer :: MonadConc m => Timer m -> m ()
+waitTimer timer = atomically $ readTMVar (timerLock timer)
+
+--------------------------------------------------------------------------------
+
+randomDelay :: MonadConc m => Timer m -> m Int
+randomDelay timer = atomically $ do
+  g <- readTVar (timerGen timer)
+  let (tmin, tmax) = timerRange timer
+      (n, g') = randomR (toInteger tmin, toInteger tmax) g
+  writeTVar (timerGen timer) g'
+  pure (fromIntegral n)
diff --git a/src/Examples/Raft/FileStore.hs b/src/Examples/Raft/FileStore.hs
deleted file mode 100644
--- a/src/Examples/Raft/FileStore.hs
+++ /dev/null
@@ -1,102 +0,0 @@
-{-# LANGUAGE TypeSynonymInstances #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE RankNTypes #-}
-{-# LANGUAGE RecordWildCards #-}
-{-# LANGUAGE StandaloneDeriving #-}
-{-# LANGUAGE UndecidableInstances #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE FunctionalDependencies #-}
-
-module Examples.Raft.FileStore where
-
-import Protolude
-
-import Control.Concurrent.Classy hiding (catch, ThreadId)
-import Control.Monad.Fail
-import Control.Monad.Catch
-import Control.Monad.Trans.Class
-
-import qualified Data.ByteString as BS
-import Data.Sequence ((><))
-import qualified Data.Sequence as Seq
-import qualified Data.Serialize as S
-
-import Raft
-
-newtype NodeEnvError = NodeEnvError Text
-  deriving (Show)
-
-instance Exception NodeEnvError
-
-data NodeFileStoreEnv = NodeFileStoreEnv
-  { nfsPersistentState :: FilePath
-  , nfsLogEntries :: FilePath
-  }
-
-newtype RaftFileStoreT m a = RaftFileStoreT { unRaftFileStoreT :: ReaderT NodeFileStoreEnv m a }
-  deriving (Functor, Applicative, Monad, MonadIO, MonadFail, MonadReader NodeFileStoreEnv, Alternative, MonadPlus, MonadTrans)
-
-deriving instance MonadConc m => MonadThrow (RaftFileStoreT m)
-deriving instance MonadConc m => MonadCatch (RaftFileStoreT m)
-deriving instance MonadConc m => MonadMask (RaftFileStoreT m)
-deriving instance MonadConc m => MonadConc (RaftFileStoreT m)
-
---------------------
--- Raft Instances --
---------------------
-
-instance (MonadIO m, MonadConc m, S.Serialize v) => RaftWriteLog (RaftFileStoreT m) v where
-  type RaftWriteLogError (RaftFileStoreT m) = NodeEnvError
-  writeLogEntries newEntries = do
-    entriesPath <- asks nfsLogEntries
-    eLogEntries <- readLogEntries
-    case eLogEntries of
-      Left err -> panic ("writeLogEntries: " <> err)
-      Right currEntries -> liftIO $ Right <$> BS.writeFile entriesPath (S.encode (currEntries >< newEntries))
-
-instance (MonadIO m, MonadConc m) => RaftPersist (RaftFileStoreT m) where
-  type RaftPersistError (RaftFileStoreT m) = NodeEnvError
-  writePersistentState ps = do
-    psPath <- asks nfsPersistentState
-    liftIO $ Right <$> BS.writeFile psPath (S.encode ps)
-
-  readPersistentState = do
-    psPath <- asks nfsPersistentState
-    fileContent <- liftIO $ BS.readFile psPath
-    case S.decode fileContent of
-      Left err -> panic (toS $ "readPersistentState: " ++ err)
-      Right ps -> pure $ Right ps
-
-instance (MonadIO m, MonadConc m, S.Serialize v) => RaftReadLog (RaftFileStoreT m) v where
-  type RaftReadLogError (RaftFileStoreT m) = NodeEnvError
-  readLogEntry (Index idx) = do
-    eLogEntries <- readLogEntries
-    case eLogEntries of
-      Left err -> panic ("readLogEntry: " <> err)
-      Right entries ->
-        case entries Seq.!? fromIntegral (if idx == 0 then 0 else idx - 1) of
-          Nothing -> pure (Right Nothing)
-          Just e -> pure (Right (Just e))
-
-  readLastLogEntry = do
-    eLogEntries <- readLogEntries
-    case eLogEntries of
-      Left err -> panic (toS err)
-      Right entries -> case entries of
-        Seq.Empty -> pure (Right Nothing)
-        (_ Seq.:|> e) -> pure (Right (Just e))
-
-instance (MonadIO m, MonadConc m, S.Serialize v) => RaftDeleteLog (RaftFileStoreT m) v where
-  type RaftDeleteLogError (RaftFileStoreT m) = NodeEnvError
-  deleteLogEntriesFrom idx = do
-    eLogEntries <- readLogEntries
-    case eLogEntries of
-      Left err -> panic ("deleteLogEntriesFrom: " <> err)
-      Right (entries :: Entries v) -> pure $ const (Right DeleteSuccess) $ Seq.dropWhileR ((>= idx) . entryIndex) entries
-
-readLogEntries :: (MonadIO m, S.Serialize v) => RaftFileStoreT m (Either Text (Entries v))
-readLogEntries = liftIO . fmap (first toS . S.decode) . BS.readFile . toS =<< asks nfsLogEntries
diff --git a/src/Examples/Raft/FileStore/Log.hs b/src/Examples/Raft/FileStore/Log.hs
new file mode 100644
--- /dev/null
+++ b/src/Examples/Raft/FileStore/Log.hs
@@ -0,0 +1,100 @@
+{-# LANGUAGE TypeSynonymInstances #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE UndecidableInstances #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Examples.Raft.FileStore.Log where
+
+import Protolude hiding (try)
+
+import Control.Concurrent.Classy hiding (catch, ThreadId)
+import Control.Monad.Fail
+import Control.Monad.Catch
+import Control.Monad.Trans.Class
+
+import qualified Data.ByteString as BS
+import Data.Sequence ((><))
+import qualified Data.Sequence as Seq
+import qualified Data.Serialize as S
+
+import qualified System.AtomicWrite.Writer.ByteString as AW
+
+import Raft.Log
+import Raft.Types
+
+newtype RaftLogFileStoreError = RaftLogFileStoreError Text
+  deriving (Show)
+
+newtype RaftLogFile = RaftLogFile { unRaftLogFile :: FilePath }
+
+instance Exception RaftLogFileStoreError
+
+newtype RaftLogFileStoreT m a = RaftLogFileStoreT { unRaftLogFileStoreT :: ReaderT RaftLogFile m a }
+  deriving (Functor, Applicative, Monad, MonadIO, MonadFail, MonadReader RaftLogFile, Alternative, MonadPlus, MonadTrans)
+
+deriving instance MonadThrow m => MonadThrow (RaftLogFileStoreT m)
+deriving instance MonadCatch m => MonadCatch (RaftLogFileStoreT m)
+deriving instance MonadMask m => MonadMask (RaftLogFileStoreT m)
+deriving instance MonadConc m => MonadConc (RaftLogFileStoreT m)
+
+runRaftLogFileStoreT :: RaftLogFile -> RaftLogFileStoreT m a -> m a
+runRaftLogFileStoreT rlogFile = flip runReaderT rlogFile . unRaftLogFileStoreT
+
+instance (S.Serialize v, MonadIO m, MonadConc m) => RaftInitLog (RaftLogFileStoreT m) v where
+  type RaftInitLogError (RaftLogFileStoreT m) = RaftLogFileStoreError
+  initializeLog _ = do
+    RaftLogFile logFile <- ask
+    eRes <- liftIO $ try (BS.writeFile logFile (S.encode (mempty :: Entries v)))
+    case eRes of
+      Left (e :: SomeException) -> pure $ Left (RaftLogFileStoreError (show e))
+      Right _ -> pure $ Right ()
+
+instance (MonadIO m, MonadConc m, S.Serialize v) => RaftWriteLog (RaftLogFileStoreT m) v where
+  type RaftWriteLogError (RaftLogFileStoreT m) = RaftLogFileStoreError
+  writeLogEntries newEntries = do
+    RaftLogFile logFile <- ask
+    eLogEntries <- readLogEntries
+    case eLogEntries of
+      Left err -> panic ("writeLogEntries: " <> err)
+      Right currEntries -> liftIO $ Right <$> AW.atomicWriteFile logFile (S.encode (currEntries >< newEntries))
+
+instance (MonadIO m, MonadConc m, S.Serialize v) => RaftReadLog (RaftLogFileStoreT m) v where
+  type RaftReadLogError (RaftLogFileStoreT m) = RaftLogFileStoreError
+  readLogEntry (Index idx) = do
+    eLogEntries <- readLogEntries
+    case eLogEntries of
+      Left err -> panic ("readLogEntry: " <> err)
+      Right entries ->
+        case entries Seq.!? fromIntegral (if idx == 0 then 0 else idx - 1) of
+          Nothing -> pure (Right Nothing)
+          Just e -> pure (Right (Just e))
+
+  readLastLogEntry = do
+    eLogEntries <- readLogEntries
+    case eLogEntries of
+      Left err -> panic ("readLastLogEntry: " <> err)
+      Right entries -> case entries of
+        Seq.Empty -> pure (Right Nothing)
+        (_ Seq.:|> e) -> pure (Right (Just e))
+
+instance (MonadIO m, MonadConc m, S.Serialize v) => RaftDeleteLog (RaftLogFileStoreT m) v where
+  type RaftDeleteLogError (RaftLogFileStoreT m) = RaftLogFileStoreError
+  deleteLogEntriesFrom idx = do
+    eLogEntries <- readLogEntries
+    case eLogEntries of
+      Left err -> panic ("deleteLogEntriesFrom: " <> err)
+      Right (entries :: Entries v) -> do
+        let newLogEntries = Seq.dropWhileR ((>= idx) . entryIndex) entries
+        RaftLogFile logFile <- ask
+        liftIO $ AW.atomicWriteFile logFile (S.encode newLogEntries)
+        pure (Right DeleteSuccess)
+
+readLogEntries :: (MonadIO m, S.Serialize v) => RaftLogFileStoreT m (Either Text (Entries v))
+readLogEntries = liftIO . fmap (first toS . S.decode) . BS.readFile . unRaftLogFile =<< ask
diff --git a/src/Examples/Raft/FileStore/Persistent.hs b/src/Examples/Raft/FileStore/Persistent.hs
new file mode 100644
--- /dev/null
+++ b/src/Examples/Raft/FileStore/Persistent.hs
@@ -0,0 +1,81 @@
+{-# LANGUAGE TypeSynonymInstances #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE UndecidableInstances #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Examples.Raft.FileStore.Persistent where
+
+import Protolude hiding (try)
+
+import Control.Concurrent.Classy hiding (catch, ThreadId)
+import Control.Monad.Fail
+import Control.Monad.Catch
+import Control.Monad.Trans.Class
+
+import qualified Data.ByteString as BS
+import qualified Data.Serialize as S
+
+import System.Directory (doesFileExist)
+import qualified System.AtomicWrite.Writer.ByteString as AW
+
+import Raft.Persistent
+
+newtype RaftPersistFileStoreError = RaftPersistFileStoreError Text
+  deriving (Show)
+
+newtype RaftPersistFile = RaftPersistFile FilePath
+
+instance Exception RaftPersistFileStoreError
+
+newtype RaftPersistFileStoreT m a = RaftPersistFileStoreT { unRaftPersistFileStoreT :: ReaderT RaftPersistFile m a }
+  deriving (Functor, Applicative, Monad, MonadIO, MonadFail, MonadReader RaftPersistFile, Alternative, MonadPlus, MonadTrans)
+
+deriving instance MonadThrow m => MonadThrow (RaftPersistFileStoreT m)
+deriving instance MonadCatch m => MonadCatch (RaftPersistFileStoreT m)
+deriving instance MonadMask m => MonadMask (RaftPersistFileStoreT m)
+deriving instance MonadConc m => MonadConc (RaftPersistFileStoreT m)
+
+--------------------------------------------------------------------------------
+-- Raft Instances
+--------------------------------------------------------------------------------
+
+-- A RaftPersist instance for the RaftPersistFileStoreT monad.
+--
+-- Warning: `atomicWriteFile` causes large pauses on the order of hundreds of
+-- milliseconds. This can cause leadership to change since the node become
+-- unresponsive for much longer than the recommended election timeouts.
+instance (MonadIO m, MonadConc m) => RaftPersist (RaftPersistFileStoreT m) where
+  type RaftPersistError (RaftPersistFileStoreT m) = RaftPersistFileStoreError
+
+  initializePersistentState = do
+    RaftPersistFile psFile <- ask
+    fileExists <- liftIO $ doesFileExist psFile
+    if fileExists
+      then pure $ Left (RaftPersistFileStoreError ("Persistent file " <> toS psFile <> " already exists!"))
+      else do
+        eRes <-
+          liftIO . try $
+            AW.atomicWriteFile psFile (toS (S.encode initPersistentState))
+        case eRes of
+          Left (err :: SomeException) -> do
+            let errPrefix = "Failed initializing persistent storage: "
+            pure (Left (RaftPersistFileStoreError (errPrefix <> show err)))
+          Right _ -> pure (Right ())
+
+  writePersistentState ps = do
+    RaftPersistFile psFile <- ask
+    liftIO $ Right <$> AW.atomicWriteFile psFile (S.encode ps)
+
+  readPersistentState = do
+    RaftPersistFile psFile <- ask
+    fileContent <- liftIO $ BS.readFile psFile
+    case S.decode fileContent of
+      Left err -> panic (toS $ "readPersistentState: " ++ err)
+      Right ps -> pure $ Right ps
diff --git a/src/Examples/Raft/Socket/Client.hs b/src/Examples/Raft/Socket/Client.hs
--- a/src/Examples/Raft/Socket/Client.hs
+++ b/src/Examples/Raft/Socket/Client.hs
@@ -2,16 +2,26 @@
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE RecordWildCards #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE UndecidableInstances #-}
+{-# LANGUAGE TypeFamilies #-}
 
 module Examples.Raft.Socket.Client where
 
-import Protolude
+import Protolude hiding (STM, atomically, try, ThreadId, newTChan)
 
-import Control.Concurrent.Classy
+import Control.Concurrent.Classy hiding (catch)
+import Control.Monad.Base
+import Control.Monad.Catch
+import Control.Monad.Trans.Class
+import Control.Monad.Trans.Control
+
 import qualified Data.Serialize as S
 import qualified Network.Simple.TCP as N
-import qualified Data.Set as Set
-import qualified Data.List as L
 import System.Random
 
 import Raft.Client
@@ -19,61 +29,115 @@
 import Raft.Types
 import Examples.Raft.Socket.Common
 
-data ClientSocketEnv
-  = ClientSocketEnv { clientPort :: N.ServiceName
-                    , clientHost :: N.HostName
-                    , clientSocket :: N.Socket
-                    } deriving (Show)
+import System.Timeout.Lifted (timeout)
+import System.Console.Haskeline.MonadException (MonadException(..), RunIO(..))
 
-newtype RaftSocketClientM a
-  = RaftSocketClientM { unRaftSocketClientM :: ReaderT ClientSocketEnv IO a }
-  deriving (Functor, Applicative, Monad, MonadIO, MonadReader ClientSocketEnv, Alternative, MonadPlus)
+newtype ClientRespChan s v
+  = ClientRespChan { clientRespChan :: TChan (STM IO) (ClientResponse s v) }
 
-runRaftSocketClientM :: ClientSocketEnv -> RaftSocketClientM a -> IO a
-runRaftSocketClientM socketEnv = flip runReaderT socketEnv . unRaftSocketClientM
+newClientRespChan :: IO (ClientRespChan s v)
+newClientRespChan = ClientRespChan <$> atomically newTChan
 
--- | Randomly select a node from a set of nodes a send a message to it
-selectRndNode :: NodeIds -> IO NodeId
-selectRndNode nids =
-  (Set.toList nids L.!!) <$> randomRIO (0, length nids - 1)
+newtype RaftClientRespChanT s v m a
+  = RaftClientRespChanT { unRaftClientRespChanT :: ReaderT (ClientRespChan s v) m a }
+  deriving newtype (Functor, Applicative, Monad, MonadIO, MonadReader (ClientRespChan s v), Alternative, MonadPlus)
 
--- | Randomly read the state of a random node
-sendReadRndNode :: (S.Serialize sm, S.Serialize v) => Proxy v -> NodeIds -> RaftSocketClientM (Either [Char] (ClientResponse sm))
-sendReadRndNode proxyV nids =
-  liftIO (selectRndNode nids) >>= sendRead proxyV
+instance MonadTrans (RaftClientRespChanT s v) where
+  lift = RaftClientRespChanT . lift
 
--- | Randomly write to a random node
-sendWriteRndNode :: (S.Serialize v, S.Serialize sm) => v -> NodeIds -> RaftSocketClientM (Either [Char] (ClientResponse sm))
-sendWriteRndNode cmd nids =
-  liftIO (selectRndNode nids) >>= sendWrite cmd
+deriving instance MonadBase IO m => MonadBase IO (RaftClientRespChanT s v m)
 
--- | Request the state of a node. It blocks until the node responds
-sendRead :: forall v sm. (S.Serialize sm, S.Serialize v) => Proxy v -> NodeId -> RaftSocketClientM (Either [Char] (ClientResponse sm))
-sendRead _  nid = do
-  socketEnv@ClientSocketEnv{..} <- ask
-  let (host, port) = nidToHostPort nid
-      clientId = ClientId (hostPortToNid (clientHost, clientPort))
-  liftIO $ fork $ N.connect host port $ \(sock, sockAddr) -> N.send sock
-    (S.encode (ClientRequestEvent (ClientRequest clientId ClientReadReq :: ClientRequest v)))
-  acceptClientConnections
+instance MonadTransControl (RaftClientRespChanT s v) where
+    type StT (RaftClientRespChanT s v) a = StT (ReaderT (ClientRespChan s v)) a
+    liftWith = defaultLiftWith RaftClientRespChanT unRaftClientRespChanT
+    restoreT = defaultRestoreT RaftClientRespChanT
 
--- | Write to a node. It blocks until the node responds
-sendWrite :: (S.Serialize v, S.Serialize sm) => v -> NodeId -> RaftSocketClientM (Either [Char] (ClientResponse sm))
-sendWrite cmd nid = do
-  socketEnv@ClientSocketEnv{..} <- ask
-  let (host, port) = nidToHostPort nid
-      clientId = ClientId (hostPortToNid (clientHost, clientPort))
-  liftIO $ fork $ N.connect host port $ \(sock, sockAddr) -> N.send sock
-    (S.encode (ClientRequestEvent (ClientRequest clientId (ClientWriteReq cmd))))
-  acceptClientConnections
+instance MonadBaseControl IO m => MonadBaseControl IO (RaftClientRespChanT s v m) where
+    type StM (RaftClientRespChanT s v m) a = ComposeSt (RaftClientRespChanT s v) m a
+    liftBaseWith = defaultLiftBaseWith
+    restoreM     = defaultRestoreM
 
--- | Accept a connection and return the client response synchronously
-acceptClientConnections :: S.Serialize sm => RaftSocketClientM (Either [Char] (ClientResponse sm))
-acceptClientConnections = do
-  socketEnv@ClientSocketEnv{..} <- ask
-  liftIO $ N.accept clientSocket $ \(sock', sockAddr') -> do
-    recvSockM <- N.recv sock' 4096
-    case recvSockM of
-      Nothing -> pure $ Left "Received empty data from socket"
-      Just recvSock -> pure (S.decode recvSock)
+deriving instance MonadSTM m => MonadSTM (RaftClientRespChanT s v m)
+deriving instance MonadThrow m => MonadThrow (RaftClientRespChanT s v m)
+deriving instance MonadCatch m => MonadCatch (RaftClientRespChanT s v m)
+deriving instance MonadMask m => MonadMask (RaftClientRespChanT s v m)
+deriving instance MonadConc m => MonadConc (RaftClientRespChanT s v m)
 
+-- This annoying instance is because of the Haskeline library, letting us use a
+-- custom monad transformer stack as the base monad of 'InputT'. IMO it should
+-- be automatically derivable. Why does haskeline use a custom exception
+-- monad... ?
+instance MonadException m => MonadException (RaftClientRespChanT s v m) where
+  controlIO f =
+    RaftClientRespChanT $ ReaderT $ \r ->
+      controlIO $ \(RunIO run) ->
+        let run' = RunIO (fmap (RaftClientRespChanT . ReaderT . const) . run . flip runReaderT r . unRaftClientRespChanT)
+         in fmap (flip runReaderT r . unRaftClientRespChanT) $ f run'
+
+instance (S.Serialize v, MonadIO m) => RaftClientSend (RaftClientRespChanT s v m) v where
+  type RaftClientSendError (RaftClientRespChanT s v m) v = Text
+  raftClientSend nid creq = do
+    let (host,port) = nidToHostPort nid
+    mRes <-
+      liftIO $ timeout 100000 $ try $ do
+        -- Warning: blocks if socket is allocated by OS, even though the socket
+        -- may have been closed by the running process
+        N.connect host port $ \(sock, sockAddr) ->
+          N.send sock (S.encode (ClientRequestEvent creq))
+    let errPrefix = "Failed to send client request: "
+    case mRes of
+      Nothing -> pure (Left (errPrefix <> "'connect' timed out"))
+      Just (Left (err :: SomeException)) -> pure $ Left (errPrefix <> show err)
+      Just (Right _) -> pure (Right ())
+
+instance (S.Serialize s, S.Serialize v, MonadIO m) => RaftClientRecv (RaftClientRespChanT s v m) s v where
+  type RaftClientRecvError (RaftClientRespChanT s v m) s = Text
+  raftClientRecv = do
+    respChan <- asks clientRespChan
+    fmap Right . liftIO . atomically $ readTChan respChan
+
+--------------------------------------------------------------------------------
+
+type RaftSocketClientM s v = RaftClientT s v (RaftClientRespChanT s v IO)
+
+runRaftSocketClientM
+  :: ClientId
+  -> Set NodeId
+  -> ClientRespChan s v
+  -> RaftSocketClientM s v a
+  -> IO a
+runRaftSocketClientM cid nids respChan rscm = do
+  raftClientState <- initRaftClientState nids <$> liftIO newStdGen
+  let raftClientEnv = RaftClientEnv cid
+  flip runReaderT respChan
+    . unRaftClientRespChanT
+    . runRaftClientT raftClientEnv raftClientState
+    $ rscm
+
+clientResponseServer
+  :: forall s v m. (S.Serialize s, S.Serialize v, MonadIO m, MonadConc m)
+  => N.HostName
+  -> N.ServiceName
+  -> RaftClientRespChanT s v m ()
+clientResponseServer host port = do
+  respChan <- asks clientRespChan
+  N.serve (N.Host host) port $ \(sock, _) -> do
+    mBytes <- N.recv sock (4 * 4096)
+    case mBytes of
+      Nothing -> putText "Socket was closed on the other end"
+      Just bytes -> case S.decode bytes of
+        Left err -> putText $ "Failed to decode message: " <> toS err
+        Right cresp -> atomically $ writeTChan respChan cresp
+
+-- | Send a client read request using the example socket interface of RaftSocketClientM
+socketClientRead
+  :: (S.Serialize s, S.Serialize v, Show s, Show v, Show (RaftClientError s v (RaftSocketClientM s v)))
+  => ClientReadReq
+  -> RaftSocketClientM s v (Either Text (ClientReadResp s v))
+socketClientRead rr = first show <$> retryOnRedirect (clientReadTimeout 1000000 rr)
+
+socketClientWrite
+  :: (S.Serialize s, S.Serialize v, Show s, Show v, Show (RaftClientError s v (RaftSocketClientM s v)))
+  => v
+  -> RaftSocketClientM s v (Either Text ClientWriteResp)
+socketClientWrite v = first show <$> retryOnRedirect (clientWriteTimeout 1000000 v)
diff --git a/src/Examples/Raft/Socket/Common.hs b/src/Examples/Raft/Socket/Common.hs
--- a/src/Examples/Raft/Socket/Common.hs
+++ b/src/Examples/Raft/Socket/Common.hs
@@ -3,16 +3,22 @@
 import Protolude
 
 import qualified Data.ByteString as BS
+import qualified Data.Serialize as S
+import qualified Data.Serialize as Get
+import qualified Data.Word8 as W8
+
 import qualified Network.Simple.TCP as N
 import qualified Network.Socket as NS
-import qualified Data.Word8 as W8
 
 import Raft.Types
 
 -- | Convert a host and a port to a valid NodeId
 hostPortToNid :: (N.HostName, N.ServiceName) -> NodeId
-hostPortToNid (host, port) = toS $ host ++ ":" ++ toS port
+hostPortToNid = toS . hostPortToNidBS
 
+hostPortToNidBS :: (N.HostName, N.ServiceName) -> ByteString
+hostPortToNidBS (host, port) = toS $ host ++ ":" ++ toS port
+
 -- | Retrieve the host and port from a valid NodeId
 nidToHostPort :: NodeId -> (N.HostName, N.ServiceName)
 nidToHostPort bs =
@@ -28,3 +34,20 @@
   port <- NS.socketPort sock
   NS.close sock
   pure $ show port
+
+-- | Receive bytes on a socket until an entire message can be decoded.
+-- This function fixes the deserialization of the bytes sent on the socket to
+-- the implementation of the Serialize typeclass.
+recvSerialized :: S.Serialize a => N.Socket -> IO (Maybe a)
+recvSerialized sock = do
+  result <- go $ Get.runGetPartial S.get
+  case result of
+    Get.Fail {} -> pure Nothing
+    Get.Done val _ -> pure . pure $ val
+    Get.Partial {} -> pure Nothing
+  where
+    go getPartial = do
+      bytes <- fromMaybe BS.empty <$> N.recv sock 4096
+      case getPartial bytes  of
+        Get.Partial getNextPartial -> go getNextPartial
+        x -> pure x
diff --git a/src/Examples/Raft/Socket/Node.hs b/src/Examples/Raft/Socket/Node.hs
--- a/src/Examples/Raft/Socket/Node.hs
+++ b/src/Examples/Raft/Socket/Node.hs
@@ -25,7 +25,6 @@
 import Control.Monad.Catch
 import Control.Monad.Trans.Class
 
-import qualified Data.Map as Map
 import qualified Data.Serialize as S
 import qualified Network.Simple.TCP as NS
 import Network.Simple.TCP
@@ -39,29 +38,31 @@
 --------------------------------------------------------------------------------
 
 data NodeSocketEnv v = NodeSocketEnv
-  { nsSocket :: Socket
-  , nsPeers :: TVar (STM IO) (Map NodeId Socket)
-  , nsMsgQueue :: TChan (STM IO) (RPCMessage v)
+  { nsMsgQueue :: TChan (STM IO) (RPCMessage v)
   , nsClientReqQueue :: TChan (STM IO) (ClientRequest v)
   }
 
 newtype RaftSocketT v m a = RaftSocketT { unRaftSocketT :: ReaderT (NodeSocketEnv v) m a }
   deriving (Functor, Applicative, Monad, MonadIO, MonadFail, MonadReader (NodeSocketEnv v), Alternative, MonadPlus, MonadTrans)
 
-deriving instance MonadConc m => MonadThrow (RaftSocketT v m)
-deriving instance MonadConc m => MonadCatch (RaftSocketT v m)
-deriving instance MonadConc m => MonadMask (RaftSocketT v m)
+deriving instance MonadThrow m => MonadThrow (RaftSocketT v m)
+deriving instance MonadCatch m => MonadCatch (RaftSocketT v m)
+deriving instance MonadMask m => MonadMask (RaftSocketT v m)
 deriving instance MonadConc m => MonadConc (RaftSocketT v m)
 
 --------------------
 -- Raft Instances --
 --------------------
 
-instance (MonadIO m, MonadConc m, S.Serialize sm) => RaftSendClient (RaftSocketT v m) sm where
+instance (MonadIO m, MonadConc m, S.Serialize sm, S.Serialize v) => RaftSendClient (RaftSocketT v m) sm v where
   sendClient clientId@(ClientId nid) msg = do
     let (cHost, cPort) = nidToHostPort (toS nid)
-    connect cHost cPort $ \(cSock, _cSockAddr) ->
-      send cSock (S.encode msg)
+    eRes <- Control.Monad.Catch.try $
+      connect cHost cPort $ \(cSock, _cSockAddr) ->
+        send cSock (S.encode msg)
+    case eRes of
+      Left (err :: SomeException) -> putText ("Failed to send Client: " <> show err)
+      Right _ -> pure ()
 
 instance (MonadIO m, MonadConc m, S.Serialize v) => RaftRecvClient (RaftSocketT v m) v where
   type RaftRecvClientError (RaftSocketT v m) v = Text
@@ -71,20 +72,12 @@
 
 instance (MonadIO m, MonadConc m, S.Serialize v, Show v) => RaftSendRPC (RaftSocketT v m) v where
   sendRPC nid msg = do
-    tNodeSocketEnvPeers <- asks nsPeers
-    nodeSocketPeers <- liftIO $ atomically $ readTVar tNodeSocketEnvPeers
-    sockM <- liftIO $
-        case Map.lookup nid nodeSocketPeers of
-          Nothing -> handle (handleFailure tNodeSocketEnvPeers [nid] Nothing) $ do
-            (sock, _) <- connectSock host port
-            NS.send sock (S.encode $ RPCMessageEvent msg)
-            pure $ Just sock
-          Just sock -> handle (retryConnection tNodeSocketEnvPeers nid (Just sock) msg) $ do
-            NS.send sock (S.encode $ RPCMessageEvent msg)
-            pure $ Just sock
-    liftIO $ atomically $ case sockM of
-      Nothing -> pure ()
-      Just sock -> writeTVar tNodeSocketEnvPeers (Map.insert nid sock nodeSocketPeers)
+      eRes <- Control.Monad.Catch.try $
+        connect host port $ \(sock,_) -> do
+          NS.send sock (S.encode $ RPCMessageEvent msg)
+      case eRes of
+        Left (err :: SomeException) -> putText ("Failed to send RPC: " <> show err)
+        Right _ -> pure ()
     where
       (host, port) = nidToHostPort nid
 
@@ -94,70 +87,27 @@
     msgQueue <- asks nsMsgQueue
     fmap Right . liftIO . atomically $ readTChan msgQueue
 
-
------------
--- Utils --
------------
-
--- | Handles connections failures by first trying to reconnect
-retryConnection
-  :: (S.Serialize v, MonadIO m, MonadConc m)
-  => TVar (STM m) (Map NodeId Socket)
-  -> NodeId
-  -> Maybe Socket
-  -> RPCMessage v
-  -> SomeException
-  -> m (Maybe Socket)
-retryConnection tNodeSocketEnvPeers nid sockM msg e =  case sockM of
-  Nothing -> pure Nothing
-  Just sock ->
-    handle (handleFailure tNodeSocketEnvPeers [nid] Nothing) $ do
-      (sock, _) <- connectSock host port
-      NS.send sock (S.encode $ RPCMessageEvent msg)
-      pure $ Just sock
-  where
-    (host, port) = nidToHostPort nid
-
-handleFailure
-  :: (MonadIO m, MonadConc m)
-  => TVar (STM m) (Map NodeId Socket)
-  -> [NodeId]
-  -> Maybe Socket
-  -> SomeException
-  -> m (Maybe Socket)
-handleFailure tNodeSocketEnvPeers nids sockM e = case sockM of
-  Nothing -> pure Nothing
-  Just sock -> do
-    nodeSocketPeers <- atomically $ readTVar tNodeSocketEnvPeers
-    closeSock sock
-    atomically $ mapM_ (\nid -> writeTVar tNodeSocketEnvPeers (Map.delete nid nodeSocketPeers)) nids
-    pure Nothing
-
-
 runRaftSocketT :: (MonadIO m, MonadConc m) => NodeSocketEnv v -> RaftSocketT v m a -> m a
 runRaftSocketT nodeSocketEnv = flip runReaderT nodeSocketEnv . unRaftSocketT
 
--- | Recursively accept a connection.
--- It keeps trying to accept connections even when a node dies
-acceptForkNode
+acceptConnections
   :: forall v m. (S.Serialize v, MonadIO m, MonadConc m)
-  => RaftSocketT v m ()
-acceptForkNode = do
+  => HostName
+  -> ServiceName
+  -> RaftSocketT v m ()
+acceptConnections host port = do
   socketEnv@NodeSocketEnv{..} <- ask
-  void $ fork $ void $ forever $ acceptFork nsSocket $ \(sock', sockAddr') ->
-    forever $ do
-      recvSockM <- recv sock' 4096
-      case recvSockM of
-        Nothing -> panic "Socket was closed on the other end"
-        Just recvSock -> case ((S.decode :: ByteString -> Either [Char] (MessageEvent v)) recvSock) of
-          Left err -> panic $ toS err
-          Right (ClientRequestEvent req@(ClientRequest cid _)) ->
+  serve (Host host) port $ \(sock, _) -> do
+    mVal <- recvSerialized sock
+    case mVal of
+      Nothing -> putText "Socket was closed on the other end"
+      Just val ->
+        case val of
+          ClientRequestEvent req ->
             atomically $ writeTChan nsClientReqQueue req
-          Right (RPCMessageEvent msg) ->
+          RPCMessageEvent msg ->
             atomically $ writeTChan nsMsgQueue msg
 
 newSock :: HostName -> ServiceName -> IO Socket
-newSock host port = do
-  (sock, _) <- bindSock (Host host) port
-  listenSock sock 2048
-  pure sock
+newSock host port =
+  listen (Host host) port (pure . fst)
diff --git a/src/Raft.hs b/src/Raft.hs
--- a/src/Raft.hs
+++ b/src/Raft.hs
@@ -13,8 +13,8 @@
 module Raft
   (
   -- * State machine type class
-    RSMP(..)
-  , RSM(..)
+    RaftStateMachinePure(..)
+  , RaftStateMachine(..)
 
   -- * Networking type classes
   , RaftSendRPC(..)
@@ -59,6 +59,7 @@
   , RaftLogExceptions(..)
 
   -- * Logging
+  , LogCtx(..)
   , LogDest(..)
   , Severity(..)
 
@@ -74,8 +75,8 @@
   , isFollower
   , isCandidate
   , isLeader
-  , setLastLogEntryData
-  , getLastLogEntryData
+  , setLastLogEntry
+  , getLastLogEntry
   , getLastAppliedAndCommitIndex
 
   -- * Persistent state
@@ -103,7 +104,7 @@
   , AppendEntriesData(..)
   ) where
 
-import Protolude hiding (STM, TChan, newTChan, readTChan, writeTChan, atomically)
+import Protolude hiding (STM, TChan, newTChan, readBoundedChan, writeBoundedChan, atomically)
 
 import Control.Monad.Conc.Class
 import Control.Concurrent.STM.Timer
@@ -115,7 +116,9 @@
 import Control.Monad.Trans.Class
 
 import qualified Data.Map as Map
+import Data.Serialize (Serialize)
 import Data.Sequence (Seq(..), singleton)
+import Data.Time.Clock.System (getSystemTime)
 
 import Raft.Action
 import Raft.Client
@@ -123,11 +126,12 @@
 import Raft.Event
 import Raft.Handle
 import Raft.Log
-import Raft.Logging hiding (logInfo, logDebug, logCritical)
+import Raft.Logging hiding (logInfo, logDebug, logCritical, logAndPanic)
 import Raft.Monad hiding (logInfo, logDebug)
 import Raft.NodeState
 import Raft.Persistent
 import Raft.RPC
+import Raft.StateMachine
 import Raft.Types
 
 
@@ -140,12 +144,12 @@
   , resetElectionTimer :: m ()
   , resetHeartbeatTimer :: m ()
   , raftNodeConfig :: NodeConfig
-  , raftNodeLogDest :: LogDest
+  , raftNodeLogCtx :: LogCtx
   }
 
 newtype RaftT v m a = RaftT
-  { unRaftT :: ReaderT (RaftEnv v m) (StateT RaftNodeState m) a
-  } deriving (Functor, Applicative, Monad, MonadReader (RaftEnv v m), MonadState RaftNodeState, MonadFail, Alternative, MonadPlus)
+  { unRaftT :: ReaderT (RaftEnv v m) (StateT (RaftNodeState v) m) a
+  } deriving (Functor, Applicative, Monad, MonadReader (RaftEnv v m), MonadState (RaftNodeState v), MonadFail, Alternative, MonadPlus)
 
 instance MonadTrans (RaftT v) where
   lift = RaftT . lift . lift
@@ -156,13 +160,12 @@
 deriving instance MonadMask m => MonadMask (RaftT v m)
 deriving instance MonadConc m => MonadConc (RaftT v m)
 
-instance Monad m => RaftLogger (RaftT v m) where
-  loggerNodeId = asks (configNodeId . raftNodeConfig)
-  loggerNodeState = get
+instance Monad m => RaftLogger v (RaftT v m) where
+  loggerCtx = (,) <$> asks (configNodeId . raftNodeConfig) <*> get
 
 runRaftT
   :: MonadConc m
-  => RaftNodeState
+  => RaftNodeState v
   -> RaftEnv v m
   -> RaftT v m ()
   -> m ()
@@ -172,23 +175,26 @@
 ------------------------------------------------------------------------------
 
 logDebug :: MonadIO m => Text -> RaftT v m ()
-logDebug msg = flip logDebugIO msg =<< asks raftNodeLogDest
+logDebug msg = flip logDebugIO msg =<< asks raftNodeLogCtx
 
 logCritical :: MonadIO m => Text -> RaftT v m ()
-logCritical msg = flip logCriticalIO msg =<< asks raftNodeLogDest
+logCritical msg = flip logCriticalIO msg =<< asks raftNodeLogCtx
 
+logAndPanic :: MonadIO m => Text -> RaftT v m a
+logAndPanic msg = flip logAndPanicIO msg =<< asks raftNodeLogCtx
+
 ------------------------------------------------------------------------------
 
 -- | Run timers, RPC and client request handlers and start event loop.
 -- It should run forever
 runRaftNode
-  :: ( Show v, Show sm, Show (Action sm v)
-     , MonadIO m, MonadConc m, MonadFail m
-     , RSM sm v m
-     , Show (RSMPError sm v)
+  :: forall m sm v.
+     ( Show v, Show sm, Serialize v, Show (Action sm v), Show (RaftLogError m), Show (RaftStateMachinePureError sm v)
+     , Typeable m, MonadIO m, MonadConc m, MonadFail m
+     , RaftStateMachine m sm v
      , RaftSendRPC m v
      , RaftRecvRPC m v
-     , RaftSendClient m sm
+     , RaftSendClient m sm v
      , RaftRecvClient m v
      , RaftLog m v
      , RaftLogExceptions m
@@ -196,40 +202,60 @@
      , Exception (RaftPersistError m)
      )
    => NodeConfig           -- ^ Node configuration
-   -> LogDest              -- ^ Logs destination
+   -> LogCtx               -- ^ Logs destination
    -> Int                  -- ^ Timer seed
    -> sm                   -- ^ Initial state machine state
    -> m ()
-runRaftNode nodeConfig@NodeConfig{..} logDest timerSeed initRSM = do
+runRaftNode nodeConfig@NodeConfig{..} logCtx timerSeed initRaftStateMachine = do
+  -- Initialize the persistent state and logs storage if specified
+  initializeStorage
+
   eventChan <- atomically newTChan
 
-  electionTimer <- newTimerRange timerSeed configElectionTimeout
-  heartbeatTimer <- newTimer configHeartbeatTimeout
+  electionTimer <-
+    newTimerRange (writeTimeoutEvent eventChan ElectionTimeout) timerSeed configElectionTimeout
 
-  let resetElectionTimer = resetTimer electionTimer
-      resetHeartbeatTimer = resetTimer heartbeatTimer
-      raftEnv = RaftEnv eventChan resetElectionTimer resetHeartbeatTimer nodeConfig logDest
+  heartbeatTimer <-
+    newTimer (writeTimeoutEvent eventChan HeartbeatTimeout) configHeartbeatTimeout
 
+  let resetElectionTimer = void $ resetTimer electionTimer
+      resetHeartbeatTimer = void $ resetTimer heartbeatTimer
+      raftEnv = RaftEnv eventChan resetElectionTimer resetHeartbeatTimer nodeConfig logCtx
+
   runRaftT initRaftNodeState raftEnv $ do
 
     -- Fork all event producers to run concurrently
-    lift $ fork (electionTimeoutTimer electionTimer eventChan)
-    lift $ fork (heartbeatTimeoutTimer heartbeatTimer eventChan)
+    lift $ fork (electionTimeoutTimer electionTimer)
+    lift $ fork (heartbeatTimeoutTimer heartbeatTimer)
     fork (rpcHandler eventChan)
     fork (clientReqHandler eventChan)
 
     -- Start the main event handling loop
-    handleEventLoop initRSM
+    handleEventLoop initRaftStateMachine
 
+  where
+    initializeStorage =
+      case configStorageState of
+        New -> do
+          ipsRes <- initializePersistentState
+          case ipsRes of
+            Left err -> throw err
+            Right _ -> do
+              ilRes <- initializeLog (Proxy :: Proxy v)
+              case ilRes of
+                Left err -> throw err
+                Right _ -> pure ()
+        Existing -> pure ()
+
 handleEventLoop
   :: forall sm v m.
-     ( Show v, Show sm, Show (Action sm v)
+     ( Show v, Serialize v, Show sm, Show (Action sm v), Show (RaftLogError m), Typeable m
      , MonadIO m, MonadConc m, MonadFail m
-     , RSM sm v m
-     , Show (RSMPError sm v)
+     , RaftStateMachine m sm v
+     , Show (RaftStateMachinePureError sm v)
      , RaftPersist m
      , RaftSendRPC m v
-     , RaftSendClient m sm
+     , RaftSendClient m sm v
      , RaftLog m v
      , RaftLogExceptions m
      , RaftPersist m
@@ -237,11 +263,12 @@
      )
   => sm
   -> RaftT v m ()
-handleEventLoop initRSM = do
+handleEventLoop initRaftStateMachine = do
+    setInitLastLogEntry
     ePersistentState <- lift readPersistentState
     case ePersistentState of
       Left err -> throw err
-      Right pstate -> handleEventLoop' initRSM pstate
+      Right pstate -> handleEventLoop' initRaftStateMachine pstate
   where
     handleEventLoop' :: sm -> PersistentState -> RaftT v m ()
     handleEventLoop' stateMachine persistentState = do
@@ -250,29 +277,34 @@
       raftNodeState <- get
       logDebug $ "[Event]: " <> show event
       logDebug $ "[NodeState]: " <> show raftNodeState
-      Right log :: Either (RaftReadLogError m) (Entries v) <- lift $ readLogEntriesFrom index0
-      logDebug $ "[Log]: " <> show log
       logDebug $ "[State Machine]: " <> show stateMachine
       logDebug $ "[Persistent State]: " <> show persistentState
+
       -- Perform core state machine transition, handling the current event
       nodeConfig <- asks raftNodeConfig
       let transitionEnv = TransitionEnv nodeConfig stateMachine raftNodeState
           (resRaftNodeState, resPersistentState, actions, logMsgs) =
             Raft.Handle.handleEvent raftNodeState transitionEnv persistentState event
-      -- Write persistent state to disk
-      eRes <- lift $ writePersistentState resPersistentState
-      case eRes of
-        Left err -> throw err
-        Right _ -> pure ()
+
+      -- Write persistent state to disk.
+      --
+      -- Checking equality of Term + NodeId (what PersistentState is comprised of)
+      -- is very cheap, but writing to disk is not necessarily cheap.
+      when (resPersistentState /= persistentState) $ do
+        eRes <- lift $ writePersistentState resPersistentState
+        case eRes of
+          Left err -> throw err
+          Right _ -> pure ()
+
       -- Update raft node state with the resulting node state
       put resRaftNodeState
-      -- Handle logs producek by core state machine
+      -- Handle logs produced by core state machine
       handleLogs logMsgs
       -- Handle actions produced by core state machine
       handleActions nodeConfig actions
       -- Apply new log entries to the state machine
-      resRSM <- applyLogEntries stateMachine
-      handleEventLoop' resRSM resPersistentState
+      resRaftStateMachine <- applyLogEntries stateMachine
+      handleEventLoop' resRaftStateMachine resPersistentState
 
     -- In the case that a node is a follower receiving an AppendEntriesRPC
     -- Event, read the log at the aePrevLogIndex
@@ -292,12 +324,23 @@
             _ -> pure ()
         _ -> pure ()
 
+    -- Load the last log entry from a existing log
+    setInitLastLogEntry :: RaftT v m ()
+    setInitLastLogEntry = do
+      RaftNodeState rns <- get
+      eLogEntry <- lift readLastLogEntry
+      case eLogEntry of
+        Left err -> throw err
+        Right Nothing -> pure ()
+        Right (Just e) ->
+          put (RaftNodeState (setLastLogEntry rns (singleton e)))
+
 handleActions
-  :: ( Show v, Show sm, Show (Action sm v)
+  :: ( Show v, Show sm, Show (Action sm v), Show (RaftLogError m), Typeable m
      , MonadIO m, MonadConc m
-     , RSM sm v m
+     , RaftStateMachine m sm v
      , RaftSendRPC m v
-     , RaftSendClient m sm
+     , RaftSendClient m sm v
      , RaftLog m v
      , RaftLogExceptions m
      )
@@ -308,11 +351,11 @@
 
 handleAction
   :: forall sm v m.
-     ( Show v, Show sm, Show (Action sm v)
+     ( Show v, Show sm, Show (Action sm v), Show (RaftLogError m), Typeable m
      , MonadIO m, MonadConc m
-     , RSM sm v m
+     , RaftStateMachine m sm v
      , RaftSendRPC m v
-     , RaftSendClient m sm
+     , RaftSendClient m sm v
      , RaftLog m v
      , RaftLogExceptions m
      )
@@ -332,18 +375,63 @@
     BroadcastRPC nids sendRpcAction -> do
       rpcMsg <- mkRPCfromSendRPCAction sendRpcAction
       mapConcurrently_ (lift . flip sendRPC rpcMsg) nids
-    RespondToClient cid cr -> lift $ sendClient cid cr
-    ResetTimeoutTimer tout ->
+    RespondToClient cid cr -> do
+      clientResp <- mkClientResp cr
+      void . fork . lift $ sendClient cid clientResp
+    ResetTimeoutTimer tout -> do
       case tout of
         ElectionTimeout -> lift . resetElectionTimer =<< ask
         HeartbeatTimeout -> lift . resetHeartbeatTimer =<< ask
     AppendLogEntries entries -> do
-      lift (updateLog entries)
-      -- Update the last log entry data
-      modify $ \(RaftNodeState ns) ->
-        RaftNodeState (setLastLogEntryData ns entries)
-
+      eRes <- lift (updateLog entries)
+      case eRes of
+        Left err -> logAndPanic (show err)
+        Right _ -> do
+          -- Update the last log entry data
+          modify $ \(RaftNodeState ns) ->
+            RaftNodeState (setLastLogEntry ns entries)
+    UpdateClientReqCacheFrom idx -> do
+      RaftNodeState ns <- get
+      case ns of
+        NodeLeaderState ls@LeaderState{..} -> do
+          eentries <- lift (readLogEntriesFrom idx)
+          case eentries of
+            Left err -> throw err
+            Right (entries :: Entries v) ->  do
+              let committedClientReqs = clientReqData entries
+              when (Map.size committedClientReqs > 0) $ do
+                mapM_ respondClientWrite (Map.toList committedClientReqs)
+                let creqMap = Map.map (second Just) committedClientReqs
+                put $ RaftNodeState $ NodeLeaderState
+                  ls { lsClientReqCache = creqMap `Map.union` lsClientReqCache }
+        _ -> logAndPanic "Only the leader should update the client request cache..."
   where
+    respondClientWrite :: (ClientId, (SerialNum, Index)) -> RaftT v m ()
+    respondClientWrite (cid, (sn,idx)) =
+      handleAction nodeConfig $
+        RespondToClient cid (ClientWriteRespSpec idx sn :: ClientRespSpec sm)
+
+    mkClientResp :: ClientRespSpec sm -> RaftT v m (ClientResponse sm v)
+    mkClientResp crs =
+      case crs of
+        ClientReadRespSpec crrs ->
+          ClientReadResponse <$>
+            case crrs of
+              ClientReadRespSpecEntries res -> do
+                eRes <- lift (readEntries res)
+                case eRes of
+                  Left err -> throw err
+                  Right res ->
+                    case res of
+                      OneEntry e -> pure (ClientReadRespEntry e)
+                      ManyEntries es -> pure (ClientReadRespEntries es)
+              ClientReadRespSpecStateMachine sm ->
+                pure (ClientReadRespStateMachine sm)
+        ClientWriteRespSpec idx sn ->
+          pure (ClientWriteResponse (ClientWriteResp idx sn))
+        ClientRedirRespSpec cl ->
+          pure (ClientRedirectResponse (ClientRedirResp cl))
+
     mkRPCfromSendRPCAction :: SendRPCAction v -> RaftT v m (RPCMessage v)
     mkRPCfromSendRPCAction sendRPCAction = do
       RaftNodeState ns <- get
@@ -369,7 +457,7 @@
                         case spec of
                           FromClientReadReq n -> Just n
                           _ -> Nothing
-                      (lastLogIndex, lastLogTerm) = getLastLogEntryData ns
+                      (lastLogIndex, lastLogTerm) = lastLogEntryIndexAndTerm (getLastLogEntry ns)
                   pure (Empty, lastLogIndex, lastLogTerm, readReq)
             let leaderId = LeaderId (configNodeId nodeConfig)
             pure . toRPC $
@@ -407,11 +495,12 @@
 applyLogEntries
   :: forall sm m v.
      ( Show sm
+     , MonadIO m
      , MonadConc m
      , RaftReadLog m v
      , Exception (RaftReadLogError m)
-     , RSM sm v m
-     , Show (RSMPError sm v)
+     , RaftStateMachine m sm v
+     , Show (RaftStateMachinePureError sm v)
      )
   => sm
   -> RaftT v m sm
@@ -425,18 +514,18 @@
         eLogEntry <- lift $ readLogEntry newLastAppliedIndex
         case eLogEntry of
           Left err -> throw err
-          Right Nothing -> panic "No log entry at 'newLastAppliedIndex'"
+          Right Nothing -> logAndPanic $ "No log entry at 'newLastAppliedIndex := " <> show newLastAppliedIndex <> "'"
           Right (Just logEntry) -> do
             -- The command should be verified by the leader, thus all node
             -- attempting to apply the committed log entry should not fail when
             -- doing so; failure here means something has gone very wrong.
-            eRes <- lift (applyEntryRSM stateMachine logEntry)
+            eRes <- lift (applyLogEntry stateMachine logEntry)
             case eRes of
-              Left err -> panic $ "Failed to apply committed log entry: " <> show err
+              Left err -> logAndPanic $ "Failed to apply committed log entry: " <> show err
               Right nsm -> applyLogEntries nsm
       else pure stateMachine
   where
-    incrLastApplied :: NodeState ns -> NodeState ns
+    incrLastApplied :: NodeState ns v -> NodeState ns v
     incrLastApplied nodeState =
       case nodeState of
         NodeFollowerState fs ->
@@ -449,10 +538,10 @@
           let lastApplied' = incrIndex (lsLastApplied ls)
            in NodeLeaderState $ ls { lsLastApplied = lastApplied' }
 
-    lastApplied :: NodeState ns -> Index
+    lastApplied :: NodeState ns v -> Index
     lastApplied = fst . getLastAppliedAndCommitIndex
 
-    commitIndex :: NodeState ns -> Index
+    commitIndex :: NodeState ns v -> Index
     commitIndex = snd . getLastAppliedAndCommitIndex
 
 handleLogs
@@ -460,8 +549,8 @@
   => [LogMsg]
   -> RaftT v m ()
 handleLogs logs = do
-  logDest <- asks raftNodeLogDest
-  mapM_ (logToDest logDest) logs
+  logCtx <- asks raftNodeLogCtx
+  mapM_ (logToDest logCtx) logs
 
 ------------------------------------------------------------------------------
 -- Event Producers
@@ -470,7 +559,7 @@
 -- | Producer for rpc message events
 rpcHandler
   :: (MonadIO m, MonadConc m, Show v, RaftRecvRPC m v)
-  => TChan (STM m) (Event v)
+  => EventChan m v
   -> RaftT v m ()
 rpcHandler eventChan =
   forever $ do
@@ -485,7 +574,7 @@
 -- | Producer for rpc message events
 clientReqHandler
   :: (MonadIO m, MonadConc m, RaftRecvClient m v)
-  => TChan (STM m) (Event v)
+  => EventChan m v
   -> RaftT v m ()
 clientReqHandler eventChan =
   forever $ do
@@ -498,15 +587,24 @@
         atomically $ writeTChan eventChan clientReqEvent
 
 -- | Producer for the election timeout event
-electionTimeoutTimer :: MonadConc m => Timer m -> TChan (STM m) (Event v) -> m ()
-electionTimeoutTimer timer eventChan =
+electionTimeoutTimer :: (MonadIO m, MonadConc m) => Timer m -> m ()
+electionTimeoutTimer timer =
   forever $ do
-    startTimer timer >> waitTimer timer
-    atomically $ writeTChan eventChan (TimeoutEvent ElectionTimeout)
+    success <- startTimer timer
+    when (not success) $
+      panic "[Failed invariant]: Election timeout timer failed to start."
+    waitTimer timer
 
 -- | Producer for the heartbeat timeout event
-heartbeatTimeoutTimer :: MonadConc m => Timer m -> TChan (STM m) (Event v) -> m ()
-heartbeatTimeoutTimer timer eventChan =
+heartbeatTimeoutTimer :: (MonadIO m, MonadConc m) => Timer m -> m ()
+heartbeatTimeoutTimer timer =
   forever $ do
-    startTimer timer >> waitTimer timer
-    atomically $ writeTChan eventChan (TimeoutEvent HeartbeatTimeout)
+    success <- startTimer timer
+    when (not success) $
+      panic "[Failed invariant]: Heartbeat timeout timer failed to start."
+    waitTimer timer
+
+writeTimeoutEvent :: (MonadIO m , MonadConc m) => EventChan m v -> Timeout -> m ()
+writeTimeoutEvent eventChan timeout = do
+  now <- liftIO getSystemTime
+  atomically $ writeTChan eventChan (TimeoutEvent now timeout)
diff --git a/src/Raft/Action.hs b/src/Raft/Action.hs
--- a/src/Raft/Action.hs
+++ b/src/Raft/Action.hs
@@ -16,8 +16,9 @@
   | SendRPCs (Map NodeId (SendRPCAction v)) -- ^ Send a unique message to specific nodes in parallel
   | BroadcastRPC NodeIds (SendRPCAction v) -- ^ Broadcast the same message to all nodes
   | AppendLogEntries (Entries v) -- ^ Append entries to the replicated log
-  | RespondToClient ClientId (ClientResponse sm) -- ^ Respond to client after a client request
+  | RespondToClient ClientId (ClientRespSpec sm) -- ^ Respond to client after a client request
   | ResetTimeoutTimer Timeout -- ^ Reset a timeout timer
+  | UpdateClientReqCacheFrom Index -- ^ Update the client request cache from the given index onward
   deriving (Show)
 
 data SendRPCAction v
diff --git a/src/Raft/Candidate.hs b/src/Raft/Candidate.hs
--- a/src/Raft/Candidate.hs
+++ b/src/Raft/Candidate.hs
@@ -20,6 +20,7 @@
 
 import Protolude
 
+import qualified Data.Serialize as S
 import qualified Data.Set as Set
 import qualified Data.Sequence as Seq
 import qualified Data.Map as Map
@@ -41,10 +42,7 @@
 
 handleAppendEntries :: RPCHandler 'Candidate sm (AppendEntries v) v
 handleAppendEntries (NodeCandidateState candidateState@CandidateState{..}) sender AppendEntries {..} = do
-  currentTerm <- gets currentTerm
-  if currentTerm <= aeTerm
-    then stepDown sender aeTerm csCommitIndex csLastApplied csLastLogEntryData
-    else pure $ candidateResultState Noop candidateState
+  pure $ candidateResultState Noop candidateState
 
 -- | Candidates should not respond to 'AppendEntriesResponse' messages.
 handleAppendEntriesResponse :: RPCHandler 'Candidate sm AppendEntriesResponse v
@@ -61,7 +59,7 @@
 
 -- | Candidates should not respond to 'RequestVoteResponse' messages.
 handleRequestVoteResponse
-  :: forall sm v. Show v
+  :: forall sm v. (Show v, S.Serialize v)
   => RPCHandler 'Candidate sm RequestVoteResponse v
 handleRequestVoteResponse (NodeCandidateState candidateState@CandidateState{..}) sender requestVoteResp@RequestVoteResponse{..} = do
   currentTerm <- gets currentTerm
@@ -83,7 +81,7 @@
 
     mkNoopEntry :: TransitionM sm v (Entry v)
     mkNoopEntry = do
-      let (lastLogEntryIdx, _) = csLastLogEntryData
+      let lastLogEntryIdx = lastLogEntryIndex csLastLogEntry
       currTerm <- gets currentTerm
       nid <- asks (configNodeId . nodeConfig)
       pure Entry
@@ -91,12 +89,12 @@
         , entryTerm  = currTerm
         , entryValue = NoValue
         , entryIssuer = LeaderIssuer (LeaderId nid)
+        , entryPrevHash = hashLastLogEntry csLastLogEntry
         }
 
-    becomeLeader :: TransitionM sm v LeaderState
+    becomeLeader :: TransitionM sm v (LeaderState v)
     becomeLeader = do
       currentTerm <- gets currentTerm
-      resetHeartbeatTimeout
       -- In order for leaders to know which entries have been replicated or not,
       -- a "no op" log entry must be created at the start of the term. See
       -- "Client ineraction", Section 8, of https://raft.github.io/raft.pdf.
@@ -108,9 +106,9 @@
           , aedLeaderCommit = csCommitIndex
           , aedEntriesSpec = FromNewLeader noopEntry
           }
+      resetHeartbeatTimeout
       cNodeIds <- asks (configNodeIds . nodeConfig)
       let lastLogEntryIdx = entryIndex noopEntry
-          lastLogEntryTerm = entryTerm noopEntry
       pure LeaderState
        { lsCommitIndex = csCommitIndex
        , lsLastApplied = csLastApplied
@@ -118,9 +116,10 @@
            (,lastLogEntryIdx) <$> Set.toList cNodeIds
        , lsMatchIndex = Map.fromList $
            (,index0) <$> Set.toList cNodeIds
-       , lsLastLogEntryData = (lastLogEntryIdx, lastLogEntryTerm, Nothing)
+       , lsLastLogEntry = csLastLogEntry
        , lsReadReqsHandled = 0
        , lsReadRequest = mempty
+       , lsClientReqCache = csClientReqCache
        }
 
 handleTimeout :: TimeoutHandler 'Candidate sm v
@@ -129,7 +128,7 @@
     HeartbeatTimeout -> pure $ candidateResultState Noop candidateState
     ElectionTimeout ->
       candidateResultState RestartElection <$>
-        startElection csCommitIndex csLastApplied csLastLogEntryData
+        startElection csCommitIndex csLastApplied csLastLogEntry csClientReqCache
 
 -- | When candidates handle a client request, they respond with NoLeader, as the
 -- very reason they are candidate is because there is no leader. This is done
@@ -139,29 +138,3 @@
 handleClientRequest (NodeCandidateState candidateState) (ClientRequest clientId _) = do
   redirectClientToLeader clientId NoLeader
   pure (candidateResultState Noop candidateState)
-
---------------------------------------------------------------------------------
-
-stepDown
-  :: NodeId
-  -> Term
-  -> Index
-  -> Index
-  -> (Index, Term)
-  -> TransitionM a sm (ResultState 'Candidate)
-stepDown sender term commitIndex lastApplied lastLogEntryData = do
-  send sender $
-    SendRequestVoteResponseRPC $
-      RequestVoteResponse
-        { rvrTerm = term
-        , rvrVoteGranted = True
-        }
-  resetElectionTimeout
-  pure $ ResultState DiscoverLeader $
-    NodeFollowerState FollowerState
-      { fsCurrentLeader = CurrentLeader (LeaderId sender)
-      , fsCommitIndex = commitIndex
-      , fsLastApplied = lastApplied
-      , fsLastLogEntryData = lastLogEntryData
-      , fsTermAtAEPrevIndex = Nothing
-      }
diff --git a/src/Raft/Client.hs b/src/Raft/Client.hs
--- a/src/Raft/Client.hs
+++ b/src/Raft/Client.hs
@@ -1,26 +1,113 @@
 {-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE DerivingStrategies #-}
 {-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE StandaloneDeriving #-}
 {-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE FunctionalDependencies #-}
+{-# LANGUAGE UndecidableInstances #-}
 
-module Raft.Client where
+module Raft.Client
+(
+  -- ** Raft Interface
+  RaftSendClient(..)
+, RaftRecvClient(..)
 
-import Protolude
+, SerialNum(..)
 
+-- ** Client Requests
+, ClientRequest(..)
+, ClientReq(..)
+, ClientReadReq(..)
+, ReadEntriesSpec(..)
+
+-- ** Client Responses
+, ClientResponse(..)
+
+, ClientRespSpec(..)
+, ClientReadRespSpec(..)
+
+, ClientReadResp(..)
+, ClientWriteResp(..)
+, ClientRedirResp(..)
+
+
+-- ** Client Interface
+, RaftClientSend(..)
+, RaftClientRecv(..)
+
+, RaftClientState(..)
+, RaftClientEnv(..)
+, initRaftClientState
+
+, RaftClientT
+, runRaftClientT
+
+, RaftClientError(..)
+, clientRead
+, clientReadFrom
+, clientReadTimeout
+
+, clientWrite
+, clientWriteTo
+, clientWriteTimeout
+
+, retryOnRedirect
+
+, clientAddNode
+, clientGetNodes
+
+) where
+
+import Protolude hiding (threadDelay, STM, )
+
+import Control.Concurrent.Classy
+import Control.Monad.Base
+import Control.Monad.Catch
+import Control.Monad.Fail
+import Control.Monad.Trans.Class
+import Control.Monad.Trans.Control
+
+import qualified Data.Set as Set
 import qualified Data.Serialize as S
 
-import Raft.NodeState
+import System.Random
+import System.Console.Haskeline.MonadException (MonadException(..), RunIO(..))
+import System.Timeout.Lifted (timeout)
+
+import Raft.Log (Entry, Entries, ReadEntriesSpec)
 import Raft.Types
 
+--------------------------------------------------------------------------------
+-- Raft Interface
+--------------------------------------------------------------------------------
+
+{- This is the interface with which Raft nodes interact with client programs -}
+
 -- | Interface for Raft nodes to send messages to clients
-class RaftSendClient m sm where
-  sendClient :: ClientId -> ClientResponse sm -> m ()
+--
+-- TODO It would be really nice if 'RSMP' was a superclass, but currently this
+-- can't happen because of cyclic imports.
+class RaftSendClient m sm v where
+  sendClient :: ClientId -> ClientResponse sm v -> m ()
 
 -- | Interface for Raft nodes to receive messages from clients
 class Show (RaftRecvClientError m v) => RaftRecvClient m v where
   type RaftRecvClientError m v
   receiveClient :: m (Either (RaftRecvClientError m v) (ClientRequest v))
 
+--------------------------------------------------------------------------------
+-- Client Requests
+--------------------------------------------------------------------------------
+
 -- | Representation of a client request coupled with the client id
 data ClientRequest v
   = ClientRequest ClientId (ClientReq v)
@@ -30,15 +117,38 @@
 
 -- | Representation of a client request
 data ClientReq v
-  = ClientReadReq -- ^ Request the latest state of the state machine
-  | ClientWriteReq v -- ^ Write a command
+  = ClientReadReq ClientReadReq -- ^ Request the latest state of the state machine
+  | ClientWriteReq SerialNum v -- ^ Write a command
   deriving (Show, Generic)
 
 instance S.Serialize v => S.Serialize (ClientReq v)
 
--- | Representation of a client response
-data ClientResponse s
-  = ClientReadResponse (ClientReadResp s)
+data ClientReadReq
+  = ClientReadEntries ReadEntriesSpec
+  | ClientReadStateMachine
+  deriving (Show, Generic, S.Serialize)
+
+--------------------------------------------------------------------------------
+-- Client Responses
+--------------------------------------------------------------------------------
+
+-- | Specification for the data inside a ClientResponse
+data ClientRespSpec sm
+  = ClientReadRespSpec (ClientReadRespSpec sm)
+  | ClientWriteRespSpec Index SerialNum
+  | ClientRedirRespSpec CurrentLeader
+  deriving (Show, Generic, S.Serialize)
+
+data ClientReadRespSpec sm
+  = ClientReadRespSpecEntries ReadEntriesSpec
+  | ClientReadRespSpecStateMachine sm
+  deriving (Show, Generic, S.Serialize)
+
+--------------------------------------------------------------------------------
+
+-- | The datatype sent back to the client as an actual response
+data ClientResponse sm v
+  = ClientReadResponse (ClientReadResp sm v)
     -- ^ Respond with the latest state of the state machine.
   | ClientWriteResponse ClientWriteResp
     -- ^ Respond with the index of the entry appended to the log
@@ -46,19 +156,20 @@
     -- ^ Respond with the node id of the current leader
   deriving (Show, Generic)
 
-instance S.Serialize s => S.Serialize (ClientResponse s)
+instance (S.Serialize sm, S.Serialize v) => S.Serialize (ClientResponse sm v)
 
 -- | Representation of a read response to a client
--- The `s` stands for the "current" state of the state machine
-newtype ClientReadResp s
-  = ClientReadResp s
+data ClientReadResp sm v
+  = ClientReadRespStateMachine sm
+  | ClientReadRespEntry (Entry v)
+  | ClientReadRespEntries (Entries v)
   deriving (Show, Generic)
 
-instance S.Serialize s => S.Serialize (ClientReadResp s)
+instance (S.Serialize sm, S.Serialize v) => S.Serialize (ClientReadResp sm v)
 
 -- | Representation of a write response to a client
 data ClientWriteResp
-  = ClientWriteResp Index
+  = ClientWriteResp Index SerialNum
   -- ^ Index of the entry appended to the log due to the previous client request
   deriving (Show, Generic)
 
@@ -70,3 +181,342 @@
   deriving (Show, Generic)
 
 instance S.Serialize ClientRedirResp
+
+--------------------------------------------------------------------------------
+-- Client Interface
+--------------------------------------------------------------------------------
+
+{- This is the interface with which clients interact with Raft nodes -}
+
+class Monad m => RaftClientSend m v where
+  type RaftClientSendError m v
+  raftClientSend :: NodeId -> ClientRequest v -> m (Either (RaftClientSendError m v) ())
+
+class Monad m => RaftClientRecv m sm v | m sm -> v where
+  type RaftClientRecvError m sm
+  raftClientRecv :: m (Either (RaftClientRecvError m sm) (ClientResponse sm v))
+
+-- | Each client may have at most one command outstanding at a time and
+-- commands must be dispatched in serial number order.
+data RaftClientState = RaftClientState
+  { raftClientCurrentLeader :: CurrentLeader
+  , raftClientSerialNum :: SerialNum
+  , raftClientRaftNodes :: Set NodeId
+  , raftClientRandomGen :: StdGen
+  }
+
+data RaftClientEnv = RaftClientEnv
+  { raftClientId :: ClientId
+  }
+
+initRaftClientState :: Set NodeId -> StdGen -> RaftClientState
+initRaftClientState = RaftClientState NoLeader 0
+
+newtype RaftClientT s v m a = RaftClientT
+  { unRaftClientT :: ReaderT RaftClientEnv (StateT RaftClientState m) a
+  } deriving newtype (Functor, Applicative, Monad, MonadIO, MonadState RaftClientState, MonadReader RaftClientEnv, MonadFail, Alternative, MonadPlus)
+
+deriving newtype instance MonadThrow m => MonadThrow (RaftClientT s v m)
+deriving newtype instance MonadCatch m => MonadCatch (RaftClientT s v m)
+deriving newtype instance MonadMask m => MonadMask (RaftClientT s v m)
+deriving newtype instance MonadSTM m => MonadSTM (RaftClientT s v m)
+deriving newtype instance MonadConc m => MonadConc (RaftClientT s v m)
+
+instance MonadTrans (RaftClientT s v) where
+  lift = RaftClientT . lift . lift
+
+deriving newtype instance MonadBase IO m => MonadBase IO (RaftClientT s v m)
+
+instance MonadTransControl (RaftClientT s v) where
+    type StT (RaftClientT s v) a = StT (ReaderT RaftClientEnv) (StT (StateT RaftClientState) a)
+    liftWith = defaultLiftWith2 RaftClientT unRaftClientT
+    restoreT = defaultRestoreT2 RaftClientT
+
+instance (MonadBaseControl IO m) => MonadBaseControl IO (RaftClientT s v m) where
+    type StM (RaftClientT s v m) a = ComposeSt (RaftClientT s v) m a
+    liftBaseWith    = defaultLiftBaseWith
+    restoreM        = defaultRestoreM
+
+-- This annoying instance is because of the Haskeline library, letting us use a
+-- custom monad transformer stack as the base monad of 'InputT'. IMO it should
+-- be automatically derivable. Why does haskeline use a custom exception
+-- monad... ?
+instance MonadException m => MonadException (RaftClientT s v m) where
+  controlIO f =
+    RaftClientT $ ReaderT $ \r -> StateT $ \s ->
+      controlIO $ \(RunIO run) ->
+        let run' = RunIO (fmap (RaftClientT . ReaderT . const . StateT . const) . run . flip runStateT s . flip runReaderT r . unRaftClientT)
+         in fmap (flip runStateT s . flip runReaderT r . unRaftClientT) $ f run'
+
+instance RaftClientSend m v => RaftClientSend (RaftClientT s v m) v where
+  type RaftClientSendError (RaftClientT s v m) v = RaftClientSendError m v
+  raftClientSend nid creq = lift (raftClientSend nid creq)
+
+instance RaftClientRecv m s v => RaftClientRecv (RaftClientT s v m) s v where
+  type RaftClientRecvError (RaftClientT s v m) s = RaftClientRecvError m s
+  raftClientRecv = lift raftClientRecv
+
+runRaftClientT :: Monad m => RaftClientEnv -> RaftClientState -> RaftClientT s v m a -> m a
+runRaftClientT raftClientEnv raftClientState =
+  flip evalStateT raftClientState . flip runReaderT raftClientEnv . unRaftClientT
+
+--------------------------------------------------------------------------------
+
+data RaftClientError s v m where
+  RaftClientSendError  :: RaftClientSendError m v -> RaftClientError s v m
+  RaftClientRecvError  :: RaftClientRecvError m s -> RaftClientError s v m
+  RaftClientTimeout    :: Text -> RaftClientError s v m
+  RaftClientUnexpectedReadResp  :: ClientReadResp s v -> RaftClientError s v m
+  RaftClientUnexpectedWriteResp :: ClientWriteResp -> RaftClientError s v m
+  RaftClientUnexpectedRedirect  :: ClientRedirResp -> RaftClientError s v m
+
+deriving instance (Show s, Show v, Show (RaftClientSendError m v), Show (RaftClientRecvError m s)) => Show (RaftClientError s v m)
+
+-- | Send a read request to the curent leader and wait for a response
+clientRead
+  :: (Show (RaftClientSendError m v), RaftClientSend m v, RaftClientRecv m s v)
+  => ClientReadReq
+  -> RaftClientT s v m (Either (RaftClientError s v m) (ClientReadResp s v))
+clientRead crr = do
+  eSend <- clientSendRead crr
+  case eSend of
+    Left err -> pure (Left (RaftClientSendError err))
+    Right _ -> clientRecvRead
+
+-- | Send a read request to a specific raft node, regardless of leader, and wait
+-- for a response.
+clientReadFrom
+  :: (RaftClientSend m v, RaftClientRecv m s v)
+  => NodeId
+  -> ClientReadReq
+  -> RaftClientT s v m (Either (RaftClientError s v m) (ClientReadResp s v))
+clientReadFrom nid crr = do
+  eSend <- clientSendReadTo nid crr
+  case eSend of
+    Left err -> pure (Left (RaftClientSendError err))
+    Right _ -> clientRecvRead
+
+-- | 'clientRead' but with a timeout
+clientReadTimeout
+  :: (MonadBaseControl IO m, Show (RaftClientSendError m v), RaftClientSend m v, RaftClientRecv m s v)
+  => Int
+  -> ClientReadReq
+  -> RaftClientT s v m (Either (RaftClientError s v m) (ClientReadResp s v))
+clientReadTimeout t = clientTimeout "clientRead" t . clientRead
+
+-- | Send a write request to the current leader and wait for a response
+clientWrite
+  :: (Show (RaftClientSendError m v), RaftClientSend m v, RaftClientRecv m s v)
+  => v
+  -> RaftClientT s v m (Either (RaftClientError s v m) ClientWriteResp)
+clientWrite cmd = do
+  eSend <- clientSendWrite cmd
+  case eSend of
+    Left err -> pure (Left (RaftClientSendError err))
+    Right _ -> clientRecvWrite
+
+-- | Send a read request to a specific raft node, regardless of leader, and wait
+-- for a response.
+clientWriteTo
+  :: (RaftClientSend m v, RaftClientRecv m s v)
+  => NodeId
+  -> v
+  -> RaftClientT s v m (Either (RaftClientError s v m) ClientWriteResp)
+clientWriteTo nid cmd = do
+  eSend <- clientSendWriteTo nid cmd
+  case eSend of
+    Left err -> pure (Left (RaftClientSendError err))
+    Right _ -> clientRecvWrite
+
+clientWriteTimeout
+  :: (MonadBaseControl IO m, Show (RaftClientSendError m v), RaftClientSend m v, RaftClientRecv m s v)
+  => Int
+  -> v
+  -> RaftClientT s v m (Either (RaftClientError s v m) ClientWriteResp)
+clientWriteTimeout t cmd = clientTimeout "clientWrite" t (clientWrite cmd)
+
+clientTimeout
+  :: (MonadBaseControl IO m, RaftClientSend m v, RaftClientRecv m s v)
+  => Text
+  -> Int
+  -> RaftClientT s v m (Either (RaftClientError s v m) r)
+  -> RaftClientT s v m (Either (RaftClientError s v m) r)
+clientTimeout fnm t r = do
+  mRes <- timeout t r
+  case mRes of
+    Nothing -> pure (Left (RaftClientTimeout fnm))
+    Just (Left err) -> pure (Left err)
+    Just (Right cresp) -> pure (Right cresp)
+
+-- | Given a blocking client send/receive, retry if the received value is not
+-- expected
+retryOnRedirect
+  :: MonadConc m
+  => RaftClientT s v m (Either (RaftClientError s v m) r)
+  -> RaftClientT s v m (Either (RaftClientError s v m) r)
+retryOnRedirect action = do
+  eRes <- action
+  case eRes of
+    Left (RaftClientUnexpectedRedirect _) -> do
+      threadDelay 10000
+      retryOnRedirect action
+    Left err -> pure (Left err)
+    Right resp -> pure (Right resp)
+
+--------------------------------------------------------------------------------
+-- Helpers
+--------------------------------------------------------------------------------
+
+-- | Send a read request to the current leader. Nonblocking.
+clientSendRead
+  :: (Show (RaftClientSendError m v), RaftClientSend m v)
+  => ClientReadReq
+  -> RaftClientT s v m (Either (RaftClientSendError m v) ())
+clientSendRead crr =
+  asks raftClientId >>= \cid ->
+    clientSend (ClientRequest cid (ClientReadReq crr))
+
+clientSendReadTo
+  :: RaftClientSend m v
+  => NodeId
+  -> ClientReadReq
+  -> RaftClientT s v m (Either (RaftClientSendError m v) ())
+clientSendReadTo nid crr =
+  asks raftClientId >>= \cid ->
+    clientSendTo nid (ClientRequest cid (ClientReadReq crr))
+
+-- | Send a write request to the current leader. Nonblocking.
+clientSendWrite
+  :: (Show (RaftClientSendError m v), RaftClientSend m v)
+  => v
+  -> RaftClientT s v m (Either (RaftClientSendError m v) ())
+clientSendWrite v = do
+  asks raftClientId >>= \cid -> gets raftClientSerialNum >>= \sn ->
+    clientSend (ClientRequest cid (ClientWriteReq sn v))
+
+-- | Send a write request to a specific raft node, ignoring the current
+-- leader. This function is used in testing.
+clientSendWriteTo
+  :: RaftClientSend m v
+  => NodeId
+  -> v
+  -> RaftClientT s v m (Either (RaftClientSendError m v) ())
+clientSendWriteTo nid v =
+  asks raftClientId >>= \cid -> gets raftClientSerialNum >>= \sn ->
+    clientSendTo nid (ClientRequest cid (ClientWriteReq sn v))
+
+-- | Send a request to the current leader. Nonblocking.
+clientSend
+  :: (Show (RaftClientSendError m v), RaftClientSend m v)
+  => ClientRequest v
+  -> RaftClientT s v m (Either (RaftClientSendError m v) ())
+clientSend creq = do
+  currLeader <- gets raftClientCurrentLeader
+  case currLeader of
+    NoLeader -> clientSendRandom creq
+    CurrentLeader (LeaderId nid) -> do
+      eRes <- raftClientSend nid creq
+      case eRes of
+        Left err -> clientSendRandom creq
+        Right resp -> pure (Right resp)
+
+-- | Send a request to a specific raft node, ignoring the current leader.
+-- This function is used in testing.
+clientSendTo
+  :: RaftClientSend m v
+  => NodeId
+  -> ClientRequest v
+  -> RaftClientT s v m (Either (RaftClientSendError m v) ())
+clientSendTo nid creq = raftClientSend nid creq
+
+-- | Send a request to a random node.
+-- This function is used if there is no leader.
+clientSendRandom
+  :: RaftClientSend m v
+  => ClientRequest v
+  -> RaftClientT s v m (Either (RaftClientSendError m v) ())
+clientSendRandom creq = do
+  raftNodes <- gets raftClientRaftNodes
+  randomGen <- gets raftClientRandomGen
+  let (idx, newRandomGen) = randomR (0, length raftNodes - 1) randomGen
+  case atMay (toList raftNodes) idx of
+    Nothing -> panic "No raft nodes known by client"
+    Just nid -> do
+      modify $ \s -> s { raftClientRandomGen = newRandomGen }
+      raftClientSend nid creq
+
+--------------------------------------------------------------------------------
+
+-- | Waits for a write response on the client socket
+-- Warning: This function discards unexpected read and redirect responses
+clientRecvWrite
+  :: (RaftClientSend m v, RaftClientRecv m s v)
+  => RaftClientT s v m (Either (RaftClientError s v m) ClientWriteResp)
+clientRecvWrite = do
+  eRes <- clientRecv
+  case eRes of
+    Left err -> pure (Left (RaftClientRecvError err))
+    Right cresp ->
+      case cresp of
+        ClientRedirectResponse crr -> pure (Left (RaftClientUnexpectedRedirect crr))
+        ClientReadResponse crr -> pure (Left (RaftClientUnexpectedReadResp crr))
+        ClientWriteResponse cwr -> pure (Right cwr)
+
+-- | Waits for a read response on the client socket
+-- Warning: This function discards unexpected write and redirect responses
+clientRecvRead
+  :: (RaftClientSend m v, RaftClientRecv m s v)
+  => RaftClientT s v m (Either (RaftClientError s v m) (ClientReadResp s v))
+clientRecvRead = do
+  eRes <- clientRecv
+  case eRes of
+    Left err -> pure (Left (RaftClientRecvError err))
+    Right cresp ->
+      case cresp of
+        ClientRedirectResponse crr -> pure (Left (RaftClientUnexpectedRedirect crr))
+        ClientWriteResponse cwr -> pure (Left (RaftClientUnexpectedWriteResp cwr))
+        ClientReadResponse crr -> pure (Right crr)
+
+-- | Wait for a response from the current leader.
+-- This function handles leader changes and write request serial numbers.
+clientRecv
+  :: RaftClientRecv m s v
+  => RaftClientT s v m (Either (RaftClientRecvError m s) (ClientResponse s v))
+clientRecv = do
+  ecresp <- raftClientRecv
+  case ecresp of
+    Left err -> pure (Left err)
+    Right cresp ->
+      case cresp of
+        ClientWriteResponse (ClientWriteResp _ (SerialNum n)) -> do
+          SerialNum m <- gets raftClientSerialNum
+          if m == n
+          then do
+            modify $ \s -> s
+              { raftClientSerialNum = SerialNum (succ m) }
+            pure (Right cresp)
+          else
+            -- Here, we ignore the response if we are receiving a response
+            -- regarding a previously committed write request. This regularly
+            -- happens when a write request is submitted, committed, and
+            -- responded to, but the leader subsequently dies before letting all
+            -- other nodes know that the write request has been committed.
+            if n < m
+            then clientRecv
+            else do
+              let errMsg = "Received invalid serial number response: Expected " <> show m <> " but got " <> show n
+              panic $ errMsg
+        ClientRedirectResponse (ClientRedirResp currLdr) -> do
+          modify $ \s -> s
+            { raftClientCurrentLeader = currLdr }
+          pure (Right cresp)
+        _ -> pure (Right cresp)
+
+--------------------------------------------------------------------------------
+
+clientAddNode :: Monad m => NodeId -> RaftClientT s v m ()
+clientAddNode nid = modify $ \s ->
+  s { raftClientRaftNodes = Set.insert nid (raftClientRaftNodes s) }
+
+clientGetNodes :: Monad m => RaftClientT s v m (Set NodeId)
+clientGetNodes = gets raftClientRaftNodes
diff --git a/src/Raft/Config.hs b/src/Raft/Config.hs
--- a/src/Raft/Config.hs
+++ b/src/Raft/Config.hs
@@ -14,5 +14,8 @@
   , configNodeIds :: NodeIds -- ^ Set of all other node ids in the cluster
   , configElectionTimeout :: (Natural, Natural) -- ^ Range of times an election timeout can take
   , configHeartbeatTimeout :: Natural -- ^ Heartbeat timeout timer
+  , configStorageState :: StorageState -- ^ Create a fresh DB or read from existing
   } deriving (Show)
 
+data StorageState = New | Existing
+  deriving Show
diff --git a/src/Raft/Event.hs b/src/Raft/Event.hs
--- a/src/Raft/Event.hs
+++ b/src/Raft/Event.hs
@@ -10,10 +10,12 @@
 import Raft.Client
 import Raft.RPC
 
+import Data.Time.Clock.System
+
 -- | Representation of events a raft node can send and receive
 data Event v
   = MessageEvent (MessageEvent v)
-  | TimeoutEvent Timeout
+  | TimeoutEvent SystemTime Timeout
   deriving (Show)
 
 -- | Representation of timeouts
@@ -31,5 +33,3 @@
   deriving (Show, Generic)
 
 instance S.Serialize v => S.Serialize (MessageEvent v)
-
-
diff --git a/src/Raft/Follower.hs b/src/Raft/Follower.hs
--- a/src/Raft/Follower.hs
+++ b/src/Raft/Follower.hs
@@ -69,6 +69,7 @@
                   -- 5. If leaderCommit > commitIndex, set commitIndex =
                   -- min(leaderCommit, index of last new entry)
                   pure (True, updateFollowerState fs)
+    when success resetElectionTimeout
     send (unLeaderId aeLeaderId) $
       SendAppendEntriesResponseRPC $
         AppendEntriesResponse
@@ -76,28 +77,24 @@
           , aerSuccess = success
           , aerReadRequest = aeReadRequest
           }
-    resetElectionTimeout
     pure (followerResultState Noop newFollowerState)
   where
-    updateFollowerState :: FollowerState -> FollowerState
+    updateFollowerState :: FollowerState v -> FollowerState v
     updateFollowerState fs =
       if aeLeaderCommit > fsCommitIndex fs
         then updateLeader (updateCommitIndex fs)
         else updateLeader fs
 
-    updateCommitIndex :: FollowerState -> FollowerState
+    updateCommitIndex :: FollowerState v -> FollowerState v
     updateCommitIndex followerState =
       case aeEntries of
         Empty ->
-          -- TODO move to action, as this should update to _true_ last log entry
-          -- that follower has written to disk, not supposed commit index
-          -- assuming leader has replicated all logs to this follower
           followerState { fsCommitIndex = aeLeaderCommit }
         _ :|> e ->
           let newCommitIndex = min aeLeaderCommit (entryIndex e)
           in followerState { fsCommitIndex = newCommitIndex }
 
-    updateLeader :: FollowerState -> FollowerState
+    updateLeader :: FollowerState v -> FollowerState v
     updateLeader followerState = followerState { fsCurrentLeader = CurrentLeader (LeaderId sender) }
 
 -- | Followers should not respond to 'AppendEntriesResponse' messages.
@@ -109,16 +106,16 @@
 handleRequestVote ns@(NodeFollowerState fs) sender RequestVote{..} = do
     PersistentState{..} <- get
     let voteGranted = giveVote currentTerm votedFor
-    logDebug $ "Vote granted: " <> show voteGranted
+    when voteGranted $ do
+      modify $ \pstate ->
+        pstate { votedFor = Just sender }
+      resetElectionTimeout
     send sender $
       SendRequestVoteResponseRPC $
         RequestVoteResponse
           { rvrTerm = currentTerm
           , rvrVoteGranted = voteGranted
           }
-    when voteGranted $
-      modify $ \pstate ->
-        pstate { votedFor = Just sender }
     pure $ followerResultState Noop fs
   where
     giveVote term mVotedFor =
@@ -133,9 +130,9 @@
     -- Check if the requesting candidate's log is more up to date
     -- Section 5.4.1 in Raft Paper
     validCandidateLog =
-      let (lastLogEntryIdx, lastLogEntryTerm) = fsLastLogEntryData fs
-       in (rvLastLogTerm > lastLogEntryTerm)
-       || (rvLastLogTerm == lastLogEntryTerm && rvLastLogIndex >= lastLogEntryIdx)
+      let (lastEntryIdx, lastEntryTerm) = lastLogEntryIndexAndTerm (fsLastLogEntry fs)
+       in (rvLastLogTerm > lastEntryTerm)
+       || (rvLastLogTerm == lastEntryTerm && rvLastLogIndex >= lastEntryIdx)
 
 -- | Followers should not respond to 'RequestVoteResponse' messages.
 handleRequestVoteResponse :: RPCHandler 'Follower sm RequestVoteResponse v
@@ -149,7 +146,7 @@
     ElectionTimeout -> do
       logDebug "Follower times out. Starts election. Becomes candidate"
       candidateResultState StartElection <$>
-        startElection (fsCommitIndex fs) (fsLastApplied fs) (fsLastLogEntryData fs)
+        startElection (fsCommitIndex fs) (fsLastApplied fs) (fsLastLogEntry fs) (fsClientReqCache fs)
     -- Follower should ignore heartbeat timeout events
     HeartbeatTimeout -> pure (followerResultState Noop fs)
 
diff --git a/src/Raft/Handle.hs b/src/Raft/Handle.hs
--- a/src/Raft/Handle.hs
+++ b/src/Raft/Handle.hs
@@ -11,6 +11,8 @@
 
 import Protolude
 
+import Data.Serialize (Serialize)
+
 import qualified Raft.Follower as Follower
 import qualified Raft.Candidate as Candidate
 import qualified Raft.Leader as Leader
@@ -21,17 +23,19 @@
 import Raft.NodeState
 import Raft.Persistent
 import Raft.RPC
+import Raft.StateMachine
+import Raft.Types
 import Raft.Logging (LogMsg)
 
 -- | Main entry point for handling events
 handleEvent
   :: forall sm v.
-     (RSMP sm v, Show v)
-  => RaftNodeState
-  -> TransitionEnv sm
+     (RaftStateMachinePure sm v, Show v, Serialize v)
+  => RaftNodeState v
+  -> TransitionEnv sm v
   -> PersistentState
   -> Event v
-  -> (RaftNodeState, PersistentState, [Action sm v], [LogMsg])
+  -> (RaftNodeState v, PersistentState, [Action sm v], [LogMsg])
 handleEvent raftNodeState@(RaftNodeState initNodeState) transitionEnv persistentState event =
     -- Rules for all servers:
     case handleNewerRPCTerm of
@@ -40,7 +44,7 @@
           ((ResultState _ resultState, logMsgs'), persistentState'', outputs') ->
             (RaftNodeState resultState, persistentState'', outputs <> outputs', logMsgs <> logMsgs')
   where
-    handleNewerRPCTerm :: ((RaftNodeState, [LogMsg]), PersistentState, [Action sm v])
+    handleNewerRPCTerm :: ((RaftNodeState v, [LogMsg]), PersistentState, [Action sm v])
     handleNewerRPCTerm =
       case event of
         MessageEvent (RPCMessageEvent (RPCMessage _ rpc)) ->
@@ -61,7 +65,7 @@
               else pure raftNodeState
         _ -> ((raftNodeState, []), persistentState, mempty)
 
-    convertToFollower :: forall s. NodeState s -> ResultState s
+    convertToFollower :: forall s. NodeState s v -> ResultState s v
     convertToFollower nodeState =
       case nodeState of
         NodeFollowerState _ ->
@@ -72,8 +76,9 @@
               { fsCurrentLeader = NoLeader
               , fsCommitIndex = csCommitIndex cs
               , fsLastApplied = csLastApplied cs
-              , fsLastLogEntryData = csLastLogEntryData cs
+              , fsLastLogEntry = csLastLogEntry cs
               , fsTermAtAEPrevIndex = Nothing
+              , fsClientReqCache = csClientReqCache cs
               }
         NodeLeaderState ls ->
           ResultState HigherTermFoundLeader $
@@ -81,10 +86,9 @@
               { fsCurrentLeader = NoLeader
               , fsCommitIndex = lsCommitIndex ls
               , fsLastApplied = lsLastApplied ls
-              , fsLastLogEntryData =
-                  let (lastLogEntryIdx, lastLogEntryTerm, _) = lsLastLogEntryData ls
-                   in (lastLogEntryIdx, lastLogEntryTerm)
+              , fsLastLogEntry = lsLastLogEntry ls
               , fsTermAtAEPrevIndex = Nothing
+              , fsClientReqCache = lsClientReqCache ls
               }
 
 
@@ -97,7 +101,7 @@
   , handleClientRequest :: ClientReqHandler ns sm v
   }
 
-followerRaftHandler :: Show v => RaftHandler 'Follower sm v
+followerRaftHandler :: (Show v, Serialize v) => RaftHandler 'Follower sm v
 followerRaftHandler = RaftHandler
   { handleAppendEntries = Follower.handleAppendEntries
   , handleAppendEntriesResponse = Follower.handleAppendEntriesResponse
@@ -107,7 +111,7 @@
   , handleClientRequest = Follower.handleClientRequest
   }
 
-candidateRaftHandler :: Show v => RaftHandler 'Candidate sm v
+candidateRaftHandler :: (Show v, Serialize v) => RaftHandler 'Candidate sm v
 candidateRaftHandler = RaftHandler
   { handleAppendEntries = Candidate.handleAppendEntries
   , handleAppendEntriesResponse = Candidate.handleAppendEntriesResponse
@@ -117,7 +121,7 @@
   , handleClientRequest = Candidate.handleClientRequest
   }
 
-leaderRaftHandler :: Show v => RaftHandler 'Leader sm v
+leaderRaftHandler :: (Show v, Serialize v) => RaftHandler 'Leader sm v
 leaderRaftHandler = RaftHandler
   { handleAppendEntries = Leader.handleAppendEntries
   , handleAppendEntriesResponse = Leader.handleAppendEntriesResponse
@@ -127,7 +131,7 @@
   , handleClientRequest = Leader.handleClientRequest
   }
 
-mkRaftHandler :: forall ns sm v. Show v => NodeState ns -> RaftHandler ns sm v
+mkRaftHandler :: forall ns sm v. (Show v, Serialize v) => NodeState ns v -> RaftHandler ns sm v
 mkRaftHandler nodeState =
   case nodeState of
     NodeFollowerState _ -> followerRaftHandler
@@ -136,12 +140,12 @@
 
 handleEvent'
   :: forall ns sm v.
-     (RSMP sm v, Show v)
-  => NodeState ns
-  -> TransitionEnv sm
+     (RaftStateMachinePure sm v, Show v, Serialize v)
+  => NodeState ns v
+  -> TransitionEnv sm v
   -> PersistentState
   -> Event v
-  -> ((ResultState ns, [LogMsg]), PersistentState, [Action sm v])
+  -> ((ResultState ns v, [LogMsg]), PersistentState, [Action sm v])
 handleEvent' initNodeState transitionEnv persistentState event =
     runTransitionM transitionEnv persistentState $ do
       case event of
@@ -150,12 +154,12 @@
             RPCMessageEvent rpcMsg -> handleRPCMessage rpcMsg
             ClientRequestEvent cr -> do
               handleClientRequest initNodeState cr
-        TimeoutEvent tout -> do
+        TimeoutEvent _ tout -> do
           handleTimeout initNodeState tout
   where
     RaftHandler{..} = mkRaftHandler initNodeState
 
-    handleRPCMessage :: RPCMessage v -> TransitionM sm v (ResultState ns)
+    handleRPCMessage :: RPCMessage v -> TransitionM sm v (ResultState ns v)
     handleRPCMessage (RPCMessage sender rpc) =
       case rpc of
         AppendEntriesRPC appendEntries ->
diff --git a/src/Raft/Leader.hs b/src/Raft/Leader.hs
--- a/src/Raft/Leader.hs
+++ b/src/Raft/Leader.hs
@@ -1,6 +1,7 @@
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE TupleSections #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE RecordWildCards #-}
 {-# LANGUAGE DataKinds #-}
@@ -18,6 +19,7 @@
 import Protolude
 
 import qualified Data.Map as Map
+import Data.Serialize (Serialize)
 import Data.Sequence (Seq(Empty))
 import qualified Data.Set as Set
 import qualified Data.Sequence as Seq
@@ -42,9 +44,7 @@
 handleAppendEntries (NodeLeaderState ls)_ _  =
   pure (leaderResultState Noop ls)
 
-handleAppendEntriesResponse
-  :: forall sm v. Show v
-  => RPCHandler 'Leader sm AppendEntriesResponse v
+handleAppendEntriesResponse :: forall sm v. RPCHandler 'Leader sm AppendEntriesResponse v
 handleAppendEntriesResponse ns@(NodeLeaderState ls) sender appendEntriesResp
   -- If AppendEntries fails (aerSuccess == False) because of log inconsistency,
   -- decrement nextIndex and retry
@@ -56,41 +56,37 @@
       send sender (SendAppendEntriesRPC aeData)
       pure (leaderResultState Noop newLeaderState)
   | otherwise = do
-      let (lastLogEntryIdx,_,_) = lsLastLogEntryData ls
-          newNextIndices = Map.insert sender (lastLogEntryIdx + 1) (lsNextIndex ls)
-          newMatchIndices = Map.insert sender lastLogEntryIdx (lsMatchIndex ls)
-          newLeaderState = ls { lsNextIndex = newNextIndices, lsMatchIndex = newMatchIndices }
-      -- Increment leader commit index if now a majority of followers have
-      -- replicated an entry at a given term.
-      newestLeaderState <- incrCommitIndex newLeaderState
-      when (lsCommitIndex newestLeaderState > lsCommitIndex newLeaderState) $ do
-        let (entryIdx, _, entryIssuer) = lsLastLogEntryData newestLeaderState
-        case entryIssuer of
-          Nothing -> panic "No last long entry issuer"
-          Just (LeaderIssuer _) -> pure ()
-          Just (ClientIssuer cid) -> tellActions [RespondToClient cid (ClientWriteResponse (ClientWriteResp entryIdx))]
-
       case aerReadRequest appendEntriesResp of
-        Nothing -> pure (leaderResultState Noop newestLeaderState)
-        Just n -> handleReadReq n newestLeaderState
+        Nothing -> leaderResultState Noop <$> do
+          let lastLogEntryIdx = lastLogEntryIndex (lsLastLogEntry ls)
+              newNextIndices = Map.insert sender (lastLogEntryIdx + 1) (lsNextIndex ls)
+              newMatchIndices = Map.insert sender lastLogEntryIdx (lsMatchIndex ls)
+              lsUpdatedIndices = ls { lsNextIndex = newNextIndices, lsMatchIndex = newMatchIndices }
+          -- Increment leader commit index if now a majority of followers have
+          -- replicated an entry at a given term.
+          lsUpdatedCommitIdx <- incrCommitIndex lsUpdatedIndices
+          when (lsCommitIndex lsUpdatedCommitIdx > lsCommitIndex lsUpdatedIndices) $
+            updateClientReqCacheFromIdx (lsCommitIndex lsUpdatedIndices)
+          pure lsUpdatedCommitIdx
+        Just n -> handleReadReq n ls
   where
-    handleReadReq :: Int -> LeaderState -> TransitionM sm v (ResultState 'Leader)
+    handleReadReq :: Int -> LeaderState v -> TransitionM sm v (ResultState 'Leader v)
     handleReadReq n leaderState = do
       networkSize <- Set.size <$> asks (configNodeIds . nodeConfig)
       let initReadReqs = lsReadRequest leaderState
-          (mclientId, newReadReqs) =
+          (mCreqData, newReadReqs) =
             case Map.lookup n initReadReqs of
-              Nothing -> panic "This should not happen"
-              Just (cid,m)
-                | isMajority (succ m) networkSize -> (Just cid, Map.delete n initReadReqs)
+              Nothing -> (Nothing, initReadReqs)
+              Just (creqData, m)
+                | isMajority (succ m) networkSize -> (Just creqData, Map.delete n initReadReqs)
                 | otherwise -> (Nothing, Map.adjust (second succ) n initReadReqs)
-      case mclientId of
+      case mCreqData of
         Nothing ->
           pure $ leaderResultState Noop leaderState
             { lsReadRequest = newReadReqs
             }
-        Just cid -> do
-          respondClientRead cid
+        Just (ClientReadReqData cid res) -> do
+          respondClientRead cid res
           pure $ leaderResultState Noop leaderState
             { lsReadReqsHandled = succ (lsReadReqsHandled leaderState)
             , lsReadRequest = newReadReqs
@@ -120,43 +116,70 @@
 -- | The leader handles all client requests, responding with the current state
 -- machine on a client read, and appending an entry to the log on a valid client
 -- write.
-handleClientRequest :: Show v => ClientReqHandler 'Leader sm v
+handleClientRequest :: (Show v, Serialize v) => ClientReqHandler 'Leader sm v
 handleClientRequest (NodeLeaderState ls@LeaderState{..}) (ClientRequest cid cr) = do
     case cr of
-      ClientReadReq -> do
+      ClientReadReq crr -> do
         heartbeat <- mkAppendEntriesData ls (NoEntries (FromClientReadReq lsReadReqsHandled))
         broadcast (SendAppendEntriesRPC heartbeat)
-        let newLeaderState =
-              ls { lsReadRequest = Map.insert lsReadReqsHandled (cid, 0) lsReadRequest
+        let clientReqData = ClientReadReqData cid crr
+            newLeaderState =
+              ls { lsReadRequest = Map.insert lsReadReqsHandled (clientReqData, 1) lsReadRequest
                  }
-        pure (leaderResultState Noop newLeaderState)
-      ClientWriteReq v -> do
-        newLogEntry <- mkNewLogEntry v
-        appendLogEntries (Empty Seq.|> newLogEntry)
-        aeData <- mkAppendEntriesData ls (FromClientWriteReq newLogEntry)
-        broadcast (SendAppendEntriesRPC aeData)
-        pure (leaderResultState Noop ls)
+        pure (leaderResultState HandleClientReq newLeaderState)
+      ClientWriteReq newSerial v ->
+        leaderResultState HandleClientReq <$> handleClientWriteReq newSerial v
   where
-    mkNewLogEntry v = do
+    mkNewLogEntry v sn = do
       currentTerm <- currentTerm <$> get
-      let (lastLogEntryIdx,_,_) = lsLastLogEntryData
+      let lastLogEntryIdx = lastLogEntryIndex lsLastLogEntry
       pure $ Entry
         { entryIndex = succ lastLogEntryIdx
         , entryTerm = currentTerm
         , entryValue = EntryValue v
-        , entryIssuer = ClientIssuer cid
+        , entryIssuer = ClientIssuer cid sn
+        , entryPrevHash = hashLastLogEntry lsLastLogEntry
         }
 
+    handleClientWriteReq newSerial v =
+      case Map.lookup cid lsClientReqCache of
+        -- This is important case #1
+        Nothing -> do
+          let lsClientReqCache' = Map.insert cid (newSerial, Nothing) lsClientReqCache
+          handleNewEntry
+          pure ls { lsClientReqCache = lsClientReqCache' }
+        Just (currSerial, mResp)
+          | newSerial < currSerial -> do
+              let debugMsg s1 s2 = "Ignoring serial number " <> s1 <> ", current serial is " <> s2
+              logDebug $ debugMsg (show newSerial) (show currSerial)
+              pure ls
+          | currSerial == newSerial -> do
+              case mResp of
+                Nothing -> logDebug $ "Serial " <> show currSerial <> " already exists. Ignoring repeat request."
+                Just idx -> respondClientWrite cid idx newSerial
+              pure ls
+          -- This is important case #2, where newSerial > currSerial
+          | otherwise -> do
+              let lsClientReqCache' = Map.insert cid (newSerial, Nothing) lsClientReqCache
+              handleNewEntry
+              pure ls { lsClientReqCache = lsClientReqCache' }
+      where
+        handleNewEntry = do
+          newLogEntry <- mkNewLogEntry v newSerial
+          appendLogEntries (Empty Seq.|> newLogEntry)
+          aeData <- mkAppendEntriesData ls (FromClientWriteReq newLogEntry)
+          broadcast (SendAppendEntriesRPC aeData)
+
 --------------------------------------------------------------------------------
 
 -- | If there exists an N such that N > commitIndex, a majority of
 -- matchIndex[i] >= N, and log[N].term == currentTerm: set commitIndex = N
-incrCommitIndex :: Show v => LeaderState -> TransitionM sm v LeaderState
+incrCommitIndex :: Show v => LeaderState v -> TransitionM sm v (LeaderState v)
 incrCommitIndex leaderState@LeaderState{..} = do
     logDebug "Checking if commit index should be incremented..."
-    let (_, lastLogEntryTerm, _) = lsLastLogEntryData
+    let lastEntryTerm = lastLogEntryTerm lsLastLogEntry
     currentTerm <- currentTerm <$> get
-    if majorityGreaterThanN && (lastLogEntryTerm == currentTerm)
+    if majorityGreaterThanN && (lastEntryTerm == currentTerm)
       then do
         logDebug $ "Incrementing commit index to: " <> show n
         incrCommitIndex leaderState { lsCommitIndex = n }
@@ -179,8 +202,8 @@
 -- | Construct an AppendEntriesRPC given log entries and a leader state.
 mkAppendEntriesData
   :: Show v
-  => LeaderState
-  -> EntriesSpec v
+  => LeaderState v
+  -> AppendEntriesSpec v
   -> TransitionM sm v (AppendEntriesData v)
 mkAppendEntriesData ls entriesSpec = do
   currentTerm <- gets currentTerm
diff --git a/src/Raft/Log.hs b/src/Raft/Log.hs
--- a/src/Raft/Log.hs
+++ b/src/Raft/Log.hs
@@ -8,27 +8,75 @@
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE DefaultSignatures #-}
 {-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE StandaloneDeriving #-}
 {-# LANGUAGE GADTs #-}
 
-module Raft.Log where
+module Raft.Log (
 
+  EntryIssuer(..),
+  EntryValue(..),
+
+  EntryHash,
+  genesisHash,
+  hashEntry,
+
+  Entry(..),
+  Entries,
+
+  RaftInitLog(..),
+  ReadEntriesSpec(..),
+  ReadEntriesRes(..),
+  IndexInterval(..),
+  RaftReadLog(..),
+  RaftWriteLog(..),
+  RaftDeleteLog(..),
+  DeleteSuccess(..),
+
+  RaftLog(..),
+  RaftLogError,
+  RaftLogExceptions,
+
+  updateLog,
+  clientReqData,
+  readEntries,
+
+) where
+
 import Protolude
 
+import qualified Crypto.Hash.SHA256 as SHA256
+
+import qualified Data.ByteString as BS
+import qualified Data.ByteString.Base16 as BS16
+import qualified Data.Map as Map
 import Data.Serialize
-import Data.Sequence (Seq(..), (|>))
+import Data.Sequence (Seq(..), (|>), foldlWithIndex)
+import qualified Data.Sequence as Seq
 
+import Database.PostgreSQL.Simple.ToField (Action(..), ToField(..))
+import Database.PostgreSQL.Simple.FromField (FromField(..), returnError, ResultError(..))
+
 import Raft.Types
 
 data EntryIssuer
-  = ClientIssuer ClientId
+  = ClientIssuer ClientId SerialNum
   | LeaderIssuer LeaderId
-  deriving (Show, Generic, Serialize)
+  deriving (Show, Read, Eq, Generic, Serialize)
 
 data EntryValue v
   = EntryValue v
   | NoValue -- ^ Used as a first committed entry of a new term
-  deriving (Show, Generic, Serialize)
+  deriving (Show, Eq, Generic, Serialize)
 
+newtype EntryHash = EntryHash ByteString
+  deriving (Show, Eq, Ord, Generic, Serialize)
+
+genesisHash :: EntryHash
+genesisHash = EntryHash $ BS16.encode $ BS.replicate 32 0
+
+hashEntry :: Serialize v => Entry v -> EntryHash
+hashEntry = EntryHash . BS16.encode . SHA256.hash . encode
+
 -- | Representation of an entry in the replicated log
 data Entry v = Entry
   { entryIndex :: Index
@@ -39,12 +87,70 @@
     -- ^ Command to update state machine
   , entryIssuer :: EntryIssuer
     -- ^ Id of the client that issued the command
-  } deriving (Show, Generic, Serialize)
+  , entryPrevHash :: EntryHash
+  } deriving (Show, Eq, Generic, Serialize)
 
 type Entries v = Seq (Entry v)
 
+data InvalidLog
+  = InvalidIndex { expectedIndex :: Index, actualIndex :: Index }
+  | InvalidPrevHash { expectedHash :: EntryHash, actualHash :: EntryHash }
+  deriving (Show)
+
+-- | For debugging & testing purposes
+validateLog :: (Serialize v) => Entries v -> Either InvalidLog ()
+validateLog es =
+    case es of
+      Empty -> Right ()
+      e :<| _ ->
+        second (const ()) $
+          foldlWithIndex accValidateEntry (Right Nothing) es
+  where
+    accValidateEntry (Left err) _ _ = Left err
+    accValidateEntry (Right mPrevEntry) idx e = validateEntry mPrevEntry idx e
+
+    validateEntry mPrevEntry expectedIdx currEntry = do
+        case mPrevEntry of
+          Nothing -> validatePrevHash genesisHash currEntryPrevHash
+          Just prevEntry -> validatePrevHash (hashEntry prevEntry) currEntryPrevHash
+        validateIndex expectedEntryIdx currEntryIdx
+        pure (Just currEntry)
+      where
+        currEntryIdx = entryIndex currEntry
+        expectedEntryIdx = Index (fromIntegral expectedIdx + 1)
+
+        currEntryPrevHash = entryPrevHash currEntry
+
+    validateIndex :: Index -> Index -> Either InvalidLog ()
+    validateIndex  expectedIndex currIndex
+      | expectedIndex /= currIndex =
+          Left (InvalidIndex expectedIndex currIndex)
+      | otherwise = Right ()
+
+    validatePrevHash :: EntryHash -> EntryHash -> Either InvalidLog ()
+    validatePrevHash expectedHash currHash
+      | expectedHash /= currHash =
+          Left (InvalidPrevHash expectedHash currHash)
+      | otherwise = Right ()
+
+clientReqData :: Entries v -> Map ClientId (SerialNum, Index)
+clientReqData = go mempty
+  where
+    go acc es =
+      case es of
+        Empty -> acc
+        e :<| rest ->
+          case entryIssuer e of
+            LeaderIssuer _ -> go acc rest
+            ClientIssuer cid sn -> go (Map.insert cid (sn, entryIndex e) acc) rest
+
+-- | Provides an interface to initialize a fresh log entry storage
+class RaftInitLog m v where
+  type RaftInitLogError m
+  initializeLog :: Proxy v -> m (Either (RaftInitLogError m) ())
+
 -- | Provides an interface for nodes to write log entries to storage.
-class Monad m => RaftWriteLog m v where
+class (Show (RaftWriteLogError m), Monad m) => RaftWriteLog m v where
   type RaftWriteLogError m
   -- | Write the given log entries to storage
   writeLogEntries
@@ -54,7 +160,7 @@
 data DeleteSuccess v = DeleteSuccess
 
 -- | Provides an interface for nodes to delete log entries from storage.
-class Monad m => RaftDeleteLog m v where
+class (Show (RaftDeleteLogError m), Monad m) => RaftDeleteLog m v where
   type RaftDeleteLogError m
   -- | Delete log entries from a given index; e.g. 'deleteLogEntriesFrom 7'
   -- should delete every log entry with an index >= 7.
@@ -63,7 +169,7 @@
     => Index -> m (Either (RaftDeleteLogError m) (DeleteSuccess v))
 
 -- | Provides an interface for nodes to read log entries from storage.
-class Monad m => RaftReadLog m v where
+class (Show (RaftReadLogError m), Monad m) => RaftReadLog m v where
   type RaftReadLogError m
   -- | Read the log at a given index
   readLogEntry
@@ -101,16 +207,19 @@
               Right Nothing -> panic "Malformed log"
               Right (Just logEntry) -> fmap (|> logEntry) <$>  go (decrIndexWithDefault0 idx')
 
-type RaftLog m v = (RaftReadLog m v, RaftWriteLog m v, RaftDeleteLog m v)
-type RaftLogExceptions m = (Exception (RaftReadLogError m), Exception (RaftWriteLogError m), Exception (RaftDeleteLogError m))
+type RaftLog m v = (RaftInitLog m v, RaftReadLog m v, RaftWriteLog m v, RaftDeleteLog m v)
+type RaftLogExceptions m = (Exception (RaftInitLogError m), Exception (RaftReadLogError m), Exception (RaftWriteLogError m), Exception (RaftDeleteLogError m))
 
 -- | Representation of possible errors that come from reading, writing or
 -- deleting logs from the persistent storage
-data RaftLogError m
-  = RaftLogReadError (RaftReadLogError m)
-  | RaftLogWriteError (RaftWriteLogError m)
-  | RaftLogDeleteError (RaftDeleteLogError m)
+data RaftLogError m where
+  RaftLogInitError :: Show (RaftInitLogError m) => RaftInitLogError m -> RaftLogError m
+  RaftLogReadError :: Show (RaftReadLogError m) => RaftReadLogError m -> RaftLogError m
+  RaftLogWriteError :: Show (RaftWriteLogError m) => RaftWriteLogError m -> RaftLogError m
+  RaftLogDeleteError :: Show (RaftDeleteLogError m) => RaftDeleteLogError m -> RaftLogError m
 
+deriving instance Show (RaftLogError m)
+
 updateLog
   :: forall m v.
      ( RaftDeleteLog m v, Exception (RaftDeleteLogError m)
@@ -126,3 +235,107 @@
       case eDel of
         Left err -> pure (Left (RaftLogDeleteError err))
         Right DeleteSuccess -> first RaftLogWriteError <$> writeLogEntries entries
+
+--------------------------------------------------------------------------------
+-- Reading entries by <X>
+--
+-- Note:
+--
+--   This would be best left up to the implementor, instead of
+--   'readEntriesFrom', it would be best to force the programmer to specify a
+--   `readEntriesBetween` function that reads entries from disk between a
+--   specified interval. For efficiency and ease of use, it might be best to
+--   provide an instance of a typeclass from a popular haskell SQL lib for the
+--   'Entry v' type so that reading entries by index _or_ hash is efficient out
+--   of the box.
+--
+--------------------------------------------------------------------------------
+
+data IndexInterval = IndexInterval (Maybe Index) (Maybe Index)
+  deriving (Show, Generic, Serialize)
+
+data ReadEntriesSpec
+  = ByIndex Index
+  | ByIndices IndexInterval
+  deriving (Show, Generic, Serialize)
+
+data ReadEntriesError m where
+  EntryDoesNotExist :: Either EntryHash Index -> ReadEntriesError m
+  InvalidIntervalSpecified :: (Index, Index) -> ReadEntriesError m
+  ReadEntriesError :: Exception (RaftReadLogError m) => RaftReadLogError m -> ReadEntriesError m
+
+deriving instance Show (ReadEntriesError m)
+deriving instance Typeable m => Exception (ReadEntriesError m)
+
+-- | The result of reading one or more
+data ReadEntriesRes v
+  = OneEntry (Entry v)
+  | ManyEntries (Entries v)
+
+readEntries
+  :: forall m v. (RaftReadLog m v, Exception (RaftReadLogError m))
+  => ReadEntriesSpec
+  -> m (Either (ReadEntriesError m) (ReadEntriesRes v))
+readEntries res =
+  case res of
+    ByIndex idx -> do
+      res <- readLogEntry idx
+      case res of
+        Left err -> pure (Left (ReadEntriesError err))
+        Right Nothing -> pure (Left (EntryDoesNotExist (Right idx)))
+        Right (Just e) -> pure (Right (OneEntry e))
+    ByIndices interval -> fmap ManyEntries <$> readEntriesByIndices interval
+
+-- | Read entries from the log between two indices
+readEntriesByIndices
+  :: forall m v. (RaftReadLog m v, Exception (RaftReadLogError m))
+  => IndexInterval
+  -> m (Either (ReadEntriesError m) (Entries v))
+readEntriesByIndices (IndexInterval l h) =
+  case (l,h) of
+    (Nothing, Nothing) ->
+      first ReadEntriesError <$> readLogEntriesFrom (Index 0)
+    (Nothing, Just hidx) ->
+      bimap ReadEntriesError (Seq.takeWhileL ((<= hidx) . entryIndex))
+        <$> readLogEntriesFrom (Index 0)
+    (Just lidx, Nothing) ->
+      first ReadEntriesError <$> readLogEntriesFrom lidx
+    (Just lidx, Just hidx)
+      | lidx >= hidx ->
+          pure (Left (InvalidIntervalSpecified (lidx, hidx)))
+      | otherwise ->
+          bimap ReadEntriesError (Seq.takeWhileL ((<= hidx) . entryIndex))
+            <$> readLogEntriesFrom lidx
+
+--------------------------------------------------------------------------------
+-- PostgreSQL Utils
+--------------------------------------------------------------------------------
+
+instance Serialize v => ToField (EntryValue v) where
+  toField = EscapeByteA . encode
+
+instance (Typeable v, Serialize v) => FromField (EntryValue v) where
+  fromField f mdata = do
+    bs <- fromField f mdata
+    case decode <$> bs of
+      Nothing          -> returnError UnexpectedNull f ""
+      Just (Left err)  -> returnError ConversionFailed f err
+      Just (Right entry) -> return entry
+
+-- TODO don't use show/read
+instance ToField EntryIssuer where
+  toField entryIssuer = Escape (show entryIssuer)
+
+-- TODO don't use show/read
+instance FromField EntryIssuer where
+  fromField f mdata = do
+    case readEither . toS <$> mdata of
+      Nothing -> returnError UnexpectedNull f ""
+      Just (Left err) -> returnError ConversionFailed f err
+      Just (Right entryIssuer) -> return entryIssuer
+
+instance ToField EntryHash where
+  toField (EntryHash hbs) = toField hbs
+
+instance FromField EntryHash where
+  fromField f = fmap EntryHash . fromField f
diff --git a/src/Raft/Log/PostgreSQL.hs b/src/Raft/Log/PostgreSQL.hs
new file mode 100644
--- /dev/null
+++ b/src/Raft/Log/PostgreSQL.hs
@@ -0,0 +1,292 @@
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE TupleSections #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE UndecidableInstances #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+module Raft.Log.PostgreSQL (
+  RaftPostgresT(..),
+  runRaftPostgresT,
+  runRaftPostgresM,
+
+  raftDatabaseName,
+  raftDatabaseConnInfo,
+  initConnInfo,
+
+  setupDB,
+  deleteDB
+
+) where
+
+import Protolude hiding (atomically, try, bracket, catches, tryReadTMVar, newEmptyTMVar, STM, Handler)
+
+import Control.Concurrent.Classy (STM, MonadConc, atomically)
+import Control.Concurrent.Classy.STM.TMVar
+import Control.Monad.Catch
+import Control.Monad.Fail
+import Control.Monad.Trans.Class
+
+import Data.FileEmbed
+import qualified Data.Sequence as Seq
+import Data.Serialize (Serialize)
+
+import Database.PostgreSQL.Simple
+import Database.PostgreSQL.Simple.Types (Identifier(..))
+
+import Raft.Client
+import Raft.RPC
+import Raft.Log
+import Raft.StateMachine
+import Raft.Persistent
+import Raft.Types
+
+data RaftPostgresEnv = RaftPostgresEnv
+  { raftPostgresConnInfo :: ConnectInfo
+  , raftPostgresConn :: TMVar (STM IO) Connection
+  }
+
+-- | A single threaded PostgreSQL storage monad transformer
+newtype RaftPostgresT m a = RaftPostgresT { unRaftPostgresT :: ReaderT RaftPostgresEnv m a }
+  deriving newtype (Functor, Applicative, Monad, MonadIO, MonadFail, MonadReader RaftPostgresEnv, Alternative, MonadPlus, MonadTrans)
+
+deriving newtype instance MonadThrow m => MonadThrow (RaftPostgresT m)
+deriving newtype instance MonadCatch m => MonadCatch (RaftPostgresT m)
+deriving newtype instance MonadMask m => MonadMask (RaftPostgresT m)
+deriving newtype instance MonadConc m => MonadConc (RaftPostgresT m)
+
+runRaftPostgresT :: MonadIO m => ConnectInfo -> RaftPostgresT m a -> m a
+runRaftPostgresT connInfo m = do
+  connTMVar <- liftIO $ atomically newEmptyTMVar
+  let postgresEnv = RaftPostgresEnv connInfo connTMVar
+  runReaderT (unRaftPostgresT m) postgresEnv
+
+type RaftPostgresM = RaftPostgresT IO
+
+runRaftPostgresM :: ConnectInfo -> RaftPostgresM a -> IO a
+runRaftPostgresM = runRaftPostgresT
+
+data RaftPostgresError
+  = RaftPostgresError PGError
+  | RaftPostgresFailedToConnect PGError
+  | RaftPostgresSerializeError Text
+  deriving (Show)
+
+instance Exception (RaftPostgresError)
+
+-- | Helper function for RaftXLog typeclasses such that we can easily catch all
+-- @PGError@s and connect to the DB if we haven't already
+withRaftPostgresConn
+  :: (MonadIO m, MonadConc m)
+  => (Connection -> IO (Either RaftPostgresError a))
+  -> RaftPostgresT m (Either RaftPostgresError a)
+withRaftPostgresConn f = do
+  mconn <- liftIO . atomically . tryReadTMVar =<< asks raftPostgresConn
+  eConn <-
+    case mconn of
+      -- If there is no existing connection, attempt to connect and store the
+      -- connection in the TMVar
+      Nothing -> do
+        eConn <- liftIO . tryPG . connect =<< asks raftPostgresConnInfo
+        case eConn of
+          Left err -> pure (Left (RaftPostgresFailedToConnect err))
+          Right conn -> do
+            connTMVar <- asks raftPostgresConn
+            liftIO . atomically $ putTMVar connTMVar conn
+            pure (Right conn)
+      Just conn -> pure (Right conn)
+  case eConn of
+    Left err -> pure (Left err)
+    Right conn -> do
+      eRes <- liftIO (tryPG (f conn))
+      case eRes of
+        Left err -> pure (Left (RaftPostgresError err))
+        Right (Left err) -> pure (Left err)
+        Right (Right a) -> pure (Right a)
+
+instance (MonadIO m) => RaftInitLog (RaftPostgresT m) v where
+  type RaftInitLogError (RaftPostgresT m) = RaftPostgresError
+  initializeLog _ = do
+    eConn <- liftIO . setupDB =<< asks raftPostgresConnInfo
+    case eConn of
+      Left err -> pure (Left (RaftPostgresError err))
+      Right conn -> do
+        connTMVar <- asks raftPostgresConn
+        fmap Right . liftIO . atomically $ putTMVar connTMVar conn
+
+instance (Typeable v, Serialize v, MonadIO m, MonadConc m) => RaftReadLog (RaftPostgresT m) v where
+  type RaftReadLogError (RaftPostgresT m) = RaftPostgresError
+  readLogEntry idx =
+    withRaftPostgresConn $ \conn ->
+      Right . fmap rowTypeToEntry . listToMaybe <$>
+        query conn "select * from entries where entryIndex = ?" (Only idx)
+
+  readLogEntriesFrom idx =
+    withRaftPostgresConn $ \conn ->
+      Right . Seq.fromList . map rowTypeToEntry <$>
+        query conn "select * from entries where entryIndex >= ?" (Only idx)
+
+  readLastLogEntry =
+    withRaftPostgresConn $ \conn ->
+      Right . fmap rowTypeToEntry . listToMaybe <$>
+        query_ conn "SELECT * FROM entries ORDER BY entryIndex DESC LIMIT 1"
+
+instance (Serialize v, MonadIO m, MonadConc m) => RaftWriteLog (RaftPostgresT m) v where
+  type RaftWriteLogError (RaftPostgresT m) = RaftPostgresError
+  writeLogEntries entries =
+    withRaftPostgresConn $ \conn ->
+      fmap Right . void $
+        executeMany conn "INSERT INTO entries VALUES (?,?,?,?,?,?)" (map entryToRowType (toList entries))
+
+instance (Serialize v, MonadIO m, MonadConc m) => RaftDeleteLog (RaftPostgresT m) v where
+  type RaftDeleteLogError (RaftPostgresT m) = RaftPostgresError
+  deleteLogEntriesFrom idx =
+    withRaftPostgresConn $ \conn ->
+      fmap (const $ Right DeleteSuccess) . void $
+        execute conn "DELETE FROM entries WHERE entryIndex >= ?" (Only idx)
+
+--------------------------------------------------------------------------------
+-- Inherited Raft instances
+--------------------------------------------------------------------------------
+
+instance RaftPersist m => RaftPersist (RaftPostgresT m) where
+  type RaftPersistError (RaftPostgresT m) = RaftPersistError m
+  initializePersistentState = lift initializePersistentState
+  readPersistentState = lift readPersistentState
+  writePersistentState = lift . writePersistentState
+
+instance (Monad m, RaftSendRPC m v) => RaftSendRPC (RaftPostgresT m) v where
+  sendRPC nid msg = lift (sendRPC nid msg)
+
+instance (Monad m, RaftRecvRPC m v) => RaftRecvRPC (RaftPostgresT m) v where
+  type RaftRecvRPCError (RaftPostgresT m) v = RaftRecvRPCError m v
+  receiveRPC = lift receiveRPC
+
+instance (Monad m, RaftSendClient m sm v) => RaftSendClient (RaftPostgresT m) sm v where
+  sendClient clientId = lift . sendClient clientId
+
+instance (Monad m, RaftRecvClient m v) => RaftRecvClient (RaftPostgresT m) v where
+  type RaftRecvClientError (RaftPostgresT m) v = RaftRecvClientError m v
+  receiveClient = lift receiveClient
+
+instance RaftStateMachine m sm v => RaftStateMachine (RaftPostgresT m) sm v where
+  validateCmd = lift . validateCmd
+  askRaftStateMachinePureCtx = lift askRaftStateMachinePureCtx
+
+--------------------------------------------------------------------------------
+
+data EntryRow v = EntryRow
+  { entryRowIndex :: Index
+  , entryRowTerm :: Term
+  , entryRowValueHash :: EntryHash
+  , entryRowValue :: EntryValue v
+  , entryRowIssuer :: EntryIssuer
+  , entryRowPrevHash :: EntryHash
+  } deriving (Show, Generic, ToRow, FromRow)
+
+entryToRowType :: Serialize v => Entry v -> EntryRow v
+entryToRowType entry@Entry{..} =
+  EntryRow
+    { entryRowIndex = entryIndex
+    , entryRowTerm = entryTerm
+    , entryRowValueHash = hashEntry entry
+    , entryRowValue = entryValue
+    , entryRowIssuer = entryIssuer
+    , entryRowPrevHash = entryPrevHash
+    }
+
+rowTypeToEntry :: Serialize v => EntryRow v -> Entry v
+rowTypeToEntry EntryRow{..} = Entry
+  { entryIndex = entryRowIndex
+  , entryTerm = entryRowTerm
+  , entryValue = entryRowValue
+  , entryIssuer = entryRowIssuer
+  , entryPrevHash = entryRowPrevHash
+  }
+
+--------------------------------------------------------------------------------
+-- Create Database for Entry Values
+--------------------------------------------------------------------------------
+
+data PGError
+  = PGSqlError SqlError
+  | PGFormatError FormatError
+  | PGQueryError QueryError
+  | PGResultError ResultError
+  | PGUnexpectedError Text
+  deriving (Show)
+
+raftDatabasePrefix :: [Char]
+raftDatabasePrefix = "libraft_"
+
+raftDatabaseName :: [Char] -> [Char]
+raftDatabaseName suffix = raftDatabasePrefix ++ suffix
+
+raftDatabaseConnInfo :: [Char] -> [Char] -> [Char] -> ConnectInfo
+raftDatabaseConnInfo usrnm pswd dbSuffix =
+  defaultConnectInfo {
+      connectUser = usrnm
+    , connectPassword = pswd
+    , connectDatabase = raftDatabaseName dbSuffix
+    }
+
+initConnInfo :: ConnectInfo
+initConnInfo = defaultConnectInfo
+  { connectDatabase = "postgres"
+  , connectUser = "libraft_test"
+  , connectPassword = "libraft_test"
+  }
+
+tryPG :: IO a -> IO (Either PGError a)
+tryPG action =
+    catches (Right <$> action)
+      [ catchSqlError, catchFmtError, catchQueryError, catchResultError, catchAllError ]
+  where
+    handler :: Exception e => (e -> IO a) -> Handler IO a
+    handler = Handler
+
+    catchSqlError    = handler (pure . Left . PGSqlError)
+    catchFmtError    = handler (pure . Left . PGFormatError)
+    catchQueryError  = handler (pure . Left . PGQueryError)
+    catchResultError = handler (pure . Left . PGResultError)
+    catchAllError    = handler (pure . Left . PGUnexpectedError . (show :: SomeException -> Text))
+
+createDB :: [Char] -> Connection -> IO Int64
+createDB dbName conn =
+  execute conn "CREATE DATABASE ?" (Only $ Identifier (toS dbName))
+
+deleteDB :: [Char] -> Connection -> IO Int64
+deleteDB dbName conn =
+  execute conn "DROP DATABASE IF EXISTS ?" (Only $ Identifier (toS dbName))
+
+-- | Load the raft schema at compile time using TH
+raftSchema :: IsString a => a
+raftSchema = $(makeRelativeToProject "postgres/entries.sql" >>= embedStringFile)
+
+-- | Execute DB schema to build the raft database
+createEntriesTable :: Connection -> IO Int64
+createEntriesTable conn = execute_ conn raftSchema
+
+-- | Create the libraft database to store all log entries
+setupDB :: ConnectInfo -> IO (Either PGError Connection)
+setupDB connInfo = tryPG $ do
+    -- Create the database with the "postgres" user
+    bracket (connect initConnInfo) close $ \conn ->
+      createDB dbName conn
+    -- Connect to the DB with the provided connection info
+    conn <- connect connInfo
+    -- Create the log entries table with the resulting connection
+    createEntriesTable conn
+    pure conn
+  where
+
+    dbName = connectDatabase connInfo
diff --git a/src/Raft/Logging.hs b/src/Raft/Logging.hs
--- a/src/Raft/Logging.hs
+++ b/src/Raft/Logging.hs
@@ -1,6 +1,10 @@
 {-# LANGUAGE BangPatterns #-}
 {-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE FunctionalDependencies #-}
+{-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE RecordWildCards #-}
 
 module Raft.Logging where
@@ -11,25 +15,33 @@
 import Control.Monad.State (modify')
 
 import Data.Time
+import Data.Time.Clock.System
 
 import Raft.NodeState
 import Raft.Types
 
+-- | Representation of the logs' context
+data LogCtx
+  = LogCtx
+    { logCtxDest :: LogDest
+    , logCtxSeverity :: Severity
+    }
+  | NoLogs
+
 -- | Representation of the logs' destination
 data LogDest
   = LogFile FilePath
   | LogStdout
-  | NoLogs
 
 -- | Representation of the severity of the logs
 data Severity
-  = Info
-  | Debug
+  = Debug
+  | Info
   | Critical
-  deriving (Show)
+  deriving (Show, Eq, Ord)
 
 data LogMsg = LogMsg
-  { mTime :: Maybe UTCTime
+  { mTime :: Maybe SystemTime
   , severity :: Severity
   , logMsgData :: LogMsgData
   }
@@ -44,8 +56,10 @@
 logMsgToText (LogMsg mt s d) =
     maybe "" timeToText mt <> "(" <> show s <> ")" <> " " <> logMsgDataToText d
   where
-    timeToText :: UTCTime -> Text
-    timeToText t = "[" <> toS (timeToText' t) <> "]"
+    timeToText :: SystemTime -> Text
+    timeToText sysTime = "[" <> toS (timeToText' (systemToUTCTime sysTime)) <> ":" <> show ms <> "]"
+      where
+        ms = (systemNanoseconds sysTime) `div` 1000000
 
     timeToText' = formatTime defaultTimeLocale (iso8601DateFormat (Just "%H:%M:%S"))
 
@@ -53,85 +67,101 @@
 logMsgDataToText LogMsgData{..} =
   "<" <> toS logMsgNodeId <> " | " <> show logMsgNodeState <> ">: " <> logMsg
 
-class Monad m => RaftLogger m where
-  loggerNodeId :: m NodeId
-  loggerNodeState :: m RaftNodeState
+class Monad m => RaftLogger v m | m -> v where
+  loggerCtx :: m (NodeId, RaftNodeState v)
 
-mkLogMsgData :: RaftLogger m => Text -> m LogMsgData
+mkLogMsgData :: RaftLogger v m => Text -> m (LogMsgData)
 mkLogMsgData msg = do
-  nid <- loggerNodeId
-  ns <- nodeMode <$> loggerNodeState
-  pure $ LogMsgData nid ns msg
+  (nid, nodeState) <- loggerCtx
+  let mode = nodeMode nodeState
+  pure $ LogMsgData nid mode msg
 
-instance RaftLogger m => RaftLogger (RaftLoggerT m) where
-  loggerNodeId = lift loggerNodeId
-  loggerNodeState = lift loggerNodeState
+instance RaftLogger v m => RaftLogger v (RaftLoggerT v m) where
+  loggerCtx = lift loggerCtx
 
 --------------------------------------------------------------------------------
 -- Logging with IO
 --------------------------------------------------------------------------------
 
-logToDest :: MonadIO m => LogDest -> LogMsg -> m ()
-logToDest logDest logMsg =
-  case logDest of
-    LogStdout -> putText (logMsgToText logMsg)
-    LogFile fp -> liftIO $ appendFile fp (logMsgToText logMsg <> "\n")
-    NoLogs -> pure ()
+logToDest :: MonadIO m => LogCtx -> LogMsg -> m ()
+logToDest LogCtx{..} logMsg =
+  case logCtxDest of
+    LogStdout -> if severity logMsg >= logCtxSeverity
+                    then liftIO $ putText (logMsgToText logMsg)
+                    else pure ()
+    LogFile fp -> if severity logMsg >= logCtxSeverity
+                    then liftIO $ appendFile fp (logMsgToText logMsg <> "\n")
+                    else pure ()
+logToDest NoLogs _ = pure ()
 
-logToStdout :: MonadIO m => LogMsg -> m ()
-logToStdout = logToDest LogStdout
+logToStdout :: MonadIO m => Severity -> LogMsg -> m ()
+logToStdout s = logToDest $ LogCtx LogStdout s
 
-logToFile :: MonadIO m => FilePath -> LogMsg -> m ()
-logToFile fp = logToDest (LogFile fp)
+logToFile :: MonadIO m => FilePath -> Severity -> LogMsg -> m ()
+logToFile fp s = logToDest $ LogCtx (LogFile fp) s
 
-logWithSeverityIO :: (RaftLogger m, MonadIO m) => Severity -> LogDest -> Text -> m ()
-logWithSeverityIO s logDest msg = do
+logWithSeverityIO :: forall m v. (RaftLogger v m, MonadIO m) => Severity -> LogCtx -> Text -> m ()
+logWithSeverityIO s logCtx msg = do
   logMsgData <- mkLogMsgData msg
-  now <- liftIO getCurrentTime
-  let logMsg = LogMsg (Just now) s logMsgData
-  logToDest logDest logMsg
+  sysTime <- liftIO getSystemTime
+  let logMsg = LogMsg (Just sysTime) s logMsgData
+  logToDest logCtx logMsg
 
-logInfoIO :: (RaftLogger m, MonadIO m) => LogDest -> Text -> m ()
+logInfoIO :: (RaftLogger v m, MonadIO m) => LogCtx -> Text -> m ()
 logInfoIO = logWithSeverityIO Info
 
-logDebugIO :: (RaftLogger m, MonadIO m) => LogDest -> Text -> m ()
+logDebugIO :: (RaftLogger v m, MonadIO m) => LogCtx -> Text -> m ()
 logDebugIO = logWithSeverityIO Debug
 
-logCriticalIO :: (RaftLogger m, MonadIO m) => LogDest -> Text -> m ()
+logCriticalIO :: (RaftLogger v m, MonadIO m) => LogCtx -> Text -> m ()
 logCriticalIO = logWithSeverityIO Critical
 
 --------------------------------------------------------------------------------
 -- Pure Logging
 --------------------------------------------------------------------------------
 
-newtype RaftLoggerT m a = RaftLoggerT {
+newtype RaftLoggerT v m a = RaftLoggerT {
     unRaftLoggerT :: StateT [LogMsg] m a
   } deriving (Functor, Applicative, Monad, MonadState [LogMsg], MonadTrans)
 
 runRaftLoggerT
   :: Monad m
-  => RaftLoggerT m a -- ^ The computation from which to extract the logs
+  => RaftLoggerT v m a -- ^ The computation from which to extract the logs
   -> m (a, [LogMsg])
 runRaftLoggerT = flip runStateT [] . unRaftLoggerT
 
-type RaftLoggerM = RaftLoggerT Identity
+type RaftLoggerM v = RaftLoggerT v Identity
 
 runRaftLoggerM
-  :: RaftLoggerM a
+  :: RaftLoggerM v a
   -> (a, [LogMsg])
 runRaftLoggerM = runIdentity . runRaftLoggerT
 
-logWithSeverity :: RaftLogger m => Severity -> Text -> RaftLoggerT m ()
+logWithSeverity :: RaftLogger v m => Severity -> Text -> RaftLoggerT v m ()
 logWithSeverity s txt = do
   !logMsgData <- mkLogMsgData txt
   let !logMsg = LogMsg Nothing s logMsgData
   modify' (++ [logMsg])
 
-logInfo :: RaftLogger m => Text -> RaftLoggerT m ()
+logInfo :: RaftLogger v m => Text -> RaftLoggerT v m ()
 logInfo = logWithSeverity Info
 
-logDebug :: RaftLogger m => Text -> RaftLoggerT m ()
+logDebug :: RaftLogger v m => Text -> RaftLoggerT v m ()
 logDebug = logWithSeverity Debug
 
-logCritical :: RaftLogger m => Text -> RaftLoggerT m ()
+logCritical :: RaftLogger v m => Text -> RaftLoggerT v m ()
 logCritical = logWithSeverity Critical
+
+--------------------------------------------------------------------------------
+-- Panic after logging
+--------------------------------------------------------------------------------
+
+logAndPanic :: RaftLogger v m => Text -> m a
+logAndPanic msg = do
+  runRaftLoggerT $ logCritical msg
+  panic ("logAndPanic: " <> msg)
+
+logAndPanicIO :: (RaftLogger v m, MonadIO m) => LogCtx -> Text -> m a
+logAndPanicIO logCtx msg = do
+  logCriticalIO logCtx msg
+  panic ("logAndPanicIO: " <> msg)
diff --git a/src/Raft/Monad.hs b/src/Raft/Monad.hs
--- a/src/Raft/Monad.hs
+++ b/src/Raft/Monad.hs
@@ -13,7 +13,10 @@
 module Raft.Monad where
 
 import Protolude hiding (pass)
+
+import Control.Arrow ((&&&))
 import Control.Monad.RWS
+
 import qualified Data.Set as Set
 
 import Raft.Action
@@ -28,37 +31,9 @@
 import Raft.Logging (RaftLogger, runRaftLoggerT, RaftLoggerT(..), LogMsg)
 import qualified Raft.Logging as Logging
 
---------------------------------------------------------------------------------
--- State Machine
---------------------------------------------------------------------------------
 
--- | Interface to handle commands in the underlying state machine. Functional
---dependency permitting only a single state machine command to be defined to
---update the state machine.
-
-class RSMP sm v | sm -> v where
-  data RSMPError sm v
-  type RSMPCtx sm v = ctx | ctx -> sm v
-  applyCmdRSMP :: RSMPCtx sm v -> sm -> v -> Either (RSMPError sm v) sm
-
-class (Monad m, RSMP sm v) => RSM sm v m | m sm -> v where
-  validateCmd :: v -> m (Either (RSMPError sm v) ())
-  askRSMPCtx :: m (RSMPCtx sm v)
-
-applyEntryRSM :: RSM sm v m => sm -> Entry v -> m (Either (RSMPError sm v) sm)
-applyEntryRSM sm e  =
-  case entryValue e of
-    NoValue -> pure (Right sm)
-    EntryValue v -> do
-      res <- validateCmd v
-      case res of
-        Left err -> pure (Left err)
-        Right () -> do
-          ctx <- askRSMPCtx
-          pure (applyCmdRSMP ctx sm v)
-
 --------------------------------------------------------------------------------
--- Raft Monad
+-- Raft Transition Monad
 --------------------------------------------------------------------------------
 
 tellAction :: Action sm v -> TransitionM sm v ()
@@ -67,14 +42,14 @@
 tellActions :: [Action sm v] -> TransitionM sm v ()
 tellActions as = tell as
 
-data TransitionEnv sm = TransitionEnv
+data TransitionEnv sm v = TransitionEnv
   { nodeConfig :: NodeConfig
   , stateMachine :: sm
-  , nodeState :: RaftNodeState
+  , nodeState :: RaftNodeState v
   }
 
 newtype TransitionM sm v a = TransitionM
-  { unTransitionM :: RaftLoggerT (RWS (TransitionEnv sm) [Action sm v] PersistentState) a
+  { unTransitionM :: RaftLoggerT v (RWS (TransitionEnv sm v) [Action sm v] PersistentState) a
   } deriving (Functor, Applicative, Monad)
 
 instance MonadWriter [Action sm v] (TransitionM sm v) where
@@ -82,7 +57,7 @@
   listen = TransitionM . RaftLoggerT . listen . unRaftLoggerT . unTransitionM
   pass = TransitionM . RaftLoggerT . pass . unRaftLoggerT . unTransitionM
 
-instance MonadReader (TransitionEnv sm) (TransitionM sm v) where
+instance MonadReader (TransitionEnv sm v) (TransitionM sm v) where
   ask = TransitionM . RaftLoggerT $ ask
   local f = TransitionM . RaftLoggerT . local f . unRaftLoggerT . unTransitionM
 
@@ -90,12 +65,11 @@
   get = TransitionM . RaftLoggerT $ lift get
   put = TransitionM . RaftLoggerT . lift . put
 
-instance RaftLogger (RWS (TransitionEnv sm) [Action sm v] PersistentState) where
-  loggerNodeId = configNodeId <$> asks nodeConfig
-  loggerNodeState = asks nodeState
+instance RaftLogger v (RWS (TransitionEnv sm v) [Action sm v] PersistentState) where
+  loggerCtx = asks ((configNodeId . nodeConfig) &&& nodeState)
 
 runTransitionM
-  :: TransitionEnv sm
+  :: TransitionEnv sm v
   -> PersistentState
   -> TransitionM sm v a
   -> ((a, [LogMsg]), PersistentState, [Action sm v])
@@ -109,9 +83,9 @@
 -- Handlers
 --------------------------------------------------------------------------------
 
-type RPCHandler ns sm r v = RPCType r v => NodeState ns -> NodeId -> r -> TransitionM sm v (ResultState ns)
-type TimeoutHandler ns sm v = NodeState ns -> Timeout -> TransitionM sm v (ResultState ns)
-type ClientReqHandler ns sm v = NodeState ns -> ClientRequest v -> TransitionM sm v (ResultState ns)
+type RPCHandler ns sm r v = (RPCType r v, Show v) => NodeState ns v -> NodeId -> r -> TransitionM sm v (ResultState ns v)
+type TimeoutHandler ns sm v = Show v => NodeState ns v -> Timeout -> TransitionM sm v (ResultState ns v)
+type ClientReqHandler ns sm v = Show v => NodeState ns v -> ClientRequest v -> TransitionM sm v (ResultState ns v)
 
 --------------------------------------------------------------------------------
 -- RWS Helpers
@@ -136,25 +110,43 @@
 
 redirectClientToLeader :: ClientId -> CurrentLeader -> TransitionM sm v ()
 redirectClientToLeader clientId currentLeader = do
-  let clientRedirResp = ClientRedirectResponse (ClientRedirResp currentLeader)
-  tellAction (RespondToClient clientId clientRedirResp)
+  let clientRedirRespSpec = ClientRedirRespSpec currentLeader
+  tellAction (RespondToClient clientId clientRedirRespSpec)
 
-respondClientRead :: ClientId -> TransitionM sm v ()
-respondClientRead clientId = do
-  clientReadResp <- ClientReadResponse . ClientReadResp <$> asks stateMachine
-  tellAction (RespondToClient clientId clientReadResp)
+respondClientRead :: ClientId -> ClientReadReq -> TransitionM sm v ()
+respondClientRead clientId readReq = do
+  readReqData <-
+    case readReq of
+      ClientReadEntries res -> pure (ClientReadRespSpecEntries res)
+      ClientReadStateMachine -> do
+        sm <- asks stateMachine
+        pure (ClientReadRespSpecStateMachine sm)
+  tellAction . RespondToClient clientId . ClientReadRespSpec $ readReqData
 
+
+respondClientWrite :: ClientId -> Index -> SerialNum -> TransitionM sm v ()
+respondClientWrite cid entryIdx sn =
+  tellAction (RespondToClient cid (ClientWriteRespSpec entryIdx sn))
+
+respondClientRedir :: ClientId -> CurrentLeader -> TransitionM sm v ()
+respondClientRedir cid cl =
+  tellAction (RespondToClient cid (ClientRedirRespSpec cl))
+
 appendLogEntries :: Show v => Seq (Entry v) -> TransitionM sm v ()
 appendLogEntries = tellAction . AppendLogEntries
 
+updateClientReqCacheFromIdx :: Index -> TransitionM sm v ()
+updateClientReqCacheFromIdx = tellAction . UpdateClientReqCacheFrom
+
 --------------------------------------------------------------------------------
 
 startElection
   :: Index
   -> Index
-  -> (Index, Term) -- ^ Last log entry data
-  -> TransitionM sm v CandidateState
-startElection commitIndex lastApplied lastLogEntryData = do
+  -> LastLogEntry v
+  -> ClientWriteReqCache
+  -> TransitionM sm v (CandidateState v)
+startElection commitIndex lastApplied lastLogEntry clientReqCache  = do
     incrementTerm
     voteForSelf
     resetElectionTimeout
@@ -165,7 +157,8 @@
       { csCommitIndex = commitIndex
       , csLastApplied = lastApplied
       , csVotes = Set.singleton selfNodeId
-      , csLastLogEntryData = lastLogEntryData
+      , csLastLogEntry = lastLogEntry
+      , csClientReqCache = clientReqCache
       }
   where
     requestVoteMessage = do
@@ -175,8 +168,8 @@
         RequestVote
           { rvTerm = term
           , rvCandidateId = selfNodeId
-          , rvLastLogIndex = fst lastLogEntryData
-          , rvLastLogTerm = snd lastLogEntryData
+          , rvLastLogIndex = lastLogEntryIndex lastLogEntry
+          , rvLastLogTerm = lastLogEntryTerm lastLogEntry
           }
 
     incrementTerm = do
diff --git a/src/Raft/NodeState.hs b/src/Raft/NodeState.hs
--- a/src/Raft/NodeState.hs
+++ b/src/Raft/NodeState.hs
@@ -4,6 +4,7 @@
 {-# LANGUAGE StandaloneDeriving #-}
 {-# LANGUAGE DeriveGeneric #-}
 {-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE StrictData #-}
 
 module Raft.NodeState where
 
@@ -12,7 +13,9 @@
 import qualified Data.Serialize as S
 import Data.Sequence (Seq(..))
 
+import Raft.Client
 import Raft.Log
+import Raft.Client (SerialNum)
 import Raft.Types
 
 data Mode
@@ -31,6 +34,7 @@
   HigherTermFoundCandidate :: Transition 'Candidate 'Follower
   BecomeLeader             :: Transition 'Candidate 'Leader
 
+  HandleClientReq          :: Transition 'Leader 'Leader
   SendHeartbeat            :: Transition 'Leader 'Leader
   DiscoverNewLeader        :: Transition 'Leader 'Follower
   HigherTermFoundLeader    :: Transition 'Leader 'Follower
@@ -39,37 +43,46 @@
 deriving instance Show (Transition init res)
 
 -- | Existential type hiding the result type of a transition
-data ResultState init where
-  ResultState :: Transition init res -> NodeState res -> ResultState init
+data ResultState init v where
+  ResultState
+    :: Show v
+    => Transition init res
+    -> NodeState res v
+    -> ResultState init v
 
-deriving instance Show (ResultState init)
+deriving instance Show v => Show (ResultState init v)
 
 followerResultState
-  :: Transition init 'Follower
-  -> FollowerState
-  -> ResultState init
+  :: Show v
+  => Transition init 'Follower
+  -> FollowerState v
+  -> ResultState init v
 followerResultState transition fstate =
   ResultState transition (NodeFollowerState fstate)
 
 candidateResultState
-  :: Transition init 'Candidate
-  -> CandidateState
-  -> ResultState init
+  :: Show v
+  => Transition init 'Candidate
+  -> CandidateState v
+  -> ResultState init v
 candidateResultState transition cstate =
   ResultState transition (NodeCandidateState cstate)
 
 leaderResultState
-  :: Transition init 'Leader
-  -> LeaderState
-  -> ResultState init
+  :: Show v
+  => Transition init 'Leader
+  -> LeaderState v
+  -> ResultState init v
 leaderResultState transition lstate =
   ResultState transition (NodeLeaderState lstate)
 
 -- | Existential type hiding the internal node state
-data RaftNodeState where
-  RaftNodeState :: { unRaftNodeState :: NodeState s } -> RaftNodeState
+data RaftNodeState v where
+  RaftNodeState :: { unRaftNodeState :: NodeState s v } -> RaftNodeState v
 
-nodeMode :: RaftNodeState -> Mode
+deriving instance Show v => Show (RaftNodeState v)
+
+nodeMode :: RaftNodeState v -> Mode
 nodeMode (RaftNodeState rns) =
   case rns of
     NodeFollowerState _ -> Follower
@@ -77,63 +90,99 @@
     NodeLeaderState _ -> Leader
 
 -- | A node in Raft begins as a follower
-initRaftNodeState :: RaftNodeState
+initRaftNodeState :: RaftNodeState v
 initRaftNodeState =
   RaftNodeState $
     NodeFollowerState FollowerState
       { fsCommitIndex = index0
       , fsLastApplied = index0
       , fsCurrentLeader = NoLeader
-      , fsLastLogEntryData = (index0, term0)
+      , fsLastLogEntry = NoLogEntries
       , fsTermAtAEPrevIndex = Nothing
+      , fsClientReqCache = mempty
       }
 
-deriving instance Show RaftNodeState
-
 -- | The volatile state of a Raft Node
-data NodeState (a :: Mode) where
-  NodeFollowerState :: FollowerState -> NodeState 'Follower
-  NodeCandidateState :: CandidateState -> NodeState 'Candidate
-  NodeLeaderState :: LeaderState -> NodeState 'Leader
+data NodeState (a :: Mode) v where
+  NodeFollowerState :: FollowerState v -> NodeState 'Follower v
+  NodeCandidateState :: CandidateState v -> NodeState 'Candidate v
+  NodeLeaderState :: LeaderState v -> NodeState 'Leader v
 
-deriving instance Show (NodeState v)
+deriving instance Show v => Show (NodeState s v)
 
--- | Representation of the current leader in the cluster. The system is
--- considered to be unavailable if there is no leader
-data CurrentLeader
-  = CurrentLeader LeaderId
-  | NoLeader
-  deriving (Show, Eq, Generic)
+data LastLogEntry v
+  = LastLogEntry (Entry v)
+  | NoLogEntries
+  deriving (Show)
 
-instance S.Serialize CurrentLeader
+hashLastLogEntry :: S.Serialize v => LastLogEntry v -> EntryHash
+hashLastLogEntry = \case
+  LastLogEntry e -> hashEntry e
+  NoLogEntries -> genesisHash
 
-data FollowerState = FollowerState
+lastLogEntryIndex :: LastLogEntry v -> Index
+lastLogEntryIndex = \case
+  LastLogEntry e -> entryIndex e
+  NoLogEntries -> index0
+
+lastLogEntryTerm :: LastLogEntry v -> Term
+lastLogEntryTerm = \case
+  LastLogEntry e -> entryTerm e
+  NoLogEntries -> term0
+
+lastLogEntryIndexAndTerm :: LastLogEntry v -> (Index, Term)
+lastLogEntryIndexAndTerm lle = (lastLogEntryIndex lle, lastLogEntryTerm lle)
+
+lastLogEntryIssuer :: LastLogEntry v -> Maybe EntryIssuer
+lastLogEntryIssuer = \case
+  LastLogEntry e -> Just (entryIssuer e)
+  NoLogEntries -> Nothing
+
+data FollowerState v = FollowerState
   { fsCurrentLeader :: CurrentLeader
     -- ^ Id of the current leader
   , fsCommitIndex :: Index
     -- ^ Index of highest log entry known to be committed
   , fsLastApplied :: Index
     -- ^ Index of highest log entry applied to state machine
-  , fsLastLogEntryData :: (Index, Term)
+  , fsLastLogEntry :: LastLogEntry v
     -- ^ Index and term of the last log entry in the node's log
   , fsTermAtAEPrevIndex :: Maybe Term
     -- ^ The term of the log entry specified in and AppendEntriesRPC
+  , fsClientReqCache :: ClientWriteReqCache
+    -- ^ The client write request cache, growing linearly with the number of
+    -- clients
   } deriving (Show)
 
-data CandidateState = CandidateState
+data CandidateState v = CandidateState
   { csCommitIndex :: Index
     -- ^ Index of highest log entry known to be committed
   , csLastApplied :: Index
     -- ^ Index of highest log entry applied to state machine
   , csVotes :: NodeIds
     -- ^ Votes from other nodes in the raft network
-  , csLastLogEntryData :: (Index, Term)
+  , csLastLogEntry :: LastLogEntry v
     -- ^ Index and term of the last log entry in the node's log
+  , csClientReqCache :: ClientWriteReqCache
+    -- ^ The client write request cache, growing linearly with the number of
+    -- clients
   } deriving (Show)
 
-type ClientReadReqs = Map Int (ClientId, Int)
+data ClientReadReqData = ClientReadReqData
+  { crrdClientId :: ClientId
+  , crrdReadReq  :: ClientReadReq
+  } deriving (Show)
 
-data LeaderState = LeaderState
+-- | The type mapping the number of the read request serviced to the id of the
+-- client that issued it and the number of success responses from followers
+-- confirming the leadership of the current leader
+type ClientReadReqs = Map Int (ClientReadReqData, Int)
+
+-- | The type mapping client ids to the serial number of their latest write
+-- requests and the index of the entry if it has been replicated.
+type ClientWriteReqCache = Map ClientId (SerialNum, Maybe Index)
+
+data LeaderState v = LeaderState
   { lsCommitIndex :: Index
     -- ^ Index of highest log entry known to be committed
   , lsLastApplied :: Index
@@ -142,11 +191,7 @@
     -- ^ For each server, index of the next log entry to send to that server
   , lsMatchIndex :: Map NodeId Index
     -- ^ For each server, index of highest log entry known to be replicated on server
-  , lsLastLogEntryData
-      :: ( Index
-         , Term
-         , Maybe EntryIssuer
-         )
+  , lsLastLogEntry :: LastLogEntry v
     -- ^ Index, term, and client id of the last log entry in the node's log.
     -- The only time `Maybe ClientId` will be Nothing is at the initial term.
   , lsReadReqsHandled :: Int
@@ -154,6 +199,8 @@
   , lsReadRequest :: ClientReadReqs
     -- ^ The number of successful responses received regarding a specific read
     -- request heartbeat.
+  , lsClientReqCache :: ClientWriteReqCache
+    -- ^ The cache of client write requests received by the leader
   } deriving (Show)
 
 --------------------------------------------------------------------------------
@@ -161,35 +208,32 @@
 --------------------------------------------------------------------------------
 
 -- | Update the last log entry in the node's log
-setLastLogEntryData :: NodeState ns -> Entries v -> NodeState ns
-setLastLogEntryData nodeState entries =
+setLastLogEntry :: NodeState s v -> Entries v -> NodeState s v
+setLastLogEntry nodeState entries =
   case entries of
     Empty -> nodeState
-    _ :|> e ->
+    _ :|> e -> do
+      let lastLogEntry = LastLogEntry e
       case nodeState of
         NodeFollowerState fs ->
-          NodeFollowerState fs
-            { fsLastLogEntryData = (entryIndex e, entryTerm e) }
+          NodeFollowerState fs { fsLastLogEntry = lastLogEntry }
         NodeCandidateState cs ->
-          NodeCandidateState cs
-            { csLastLogEntryData = (entryIndex e, entryTerm e) }
+          NodeCandidateState cs { csLastLogEntry = lastLogEntry }
         NodeLeaderState ls ->
-          NodeLeaderState ls
-            { lsLastLogEntryData = (entryIndex e, entryTerm e, Just (entryIssuer e)) }
+          NodeLeaderState ls { lsLastLogEntry = lastLogEntry }
 
 -- | Get the last applied index and the commit index of the last log entry in
 -- the node's log
-getLastLogEntryData :: NodeState ns -> (Index, Term)
-getLastLogEntryData nodeState =
+getLastLogEntry :: NodeState ns v -> LastLogEntry v
+getLastLogEntry nodeState =
   case nodeState of
-    NodeFollowerState fs -> fsLastLogEntryData fs
-    NodeCandidateState cs -> csLastLogEntryData cs
-    NodeLeaderState ls -> let (peTerm, peIndex, _) = lsLastLogEntryData ls
-                           in (peTerm, peIndex)
+    NodeFollowerState fs -> fsLastLogEntry fs
+    NodeCandidateState cs -> csLastLogEntry cs
+    NodeLeaderState ls -> lsLastLogEntry ls
 
 -- | Get the index of highest log entry applied to state machine and the index
 -- of highest log entry known to be committed
-getLastAppliedAndCommitIndex :: NodeState ns -> (Index, Index)
+getLastAppliedAndCommitIndex :: NodeState ns v -> (Index, Index)
 getLastAppliedAndCommitIndex nodeState =
   case nodeState of
     NodeFollowerState fs -> (fsLastApplied fs, fsCommitIndex fs)
@@ -197,21 +241,21 @@
     NodeLeaderState ls -> (lsLastApplied ls, lsCommitIndex ls)
 
 -- | Check if node is in a follower state
-isFollower :: NodeState s  -> Bool
+isFollower :: NodeState s v -> Bool
 isFollower nodeState =
   case nodeState of
     NodeFollowerState _ -> True
     _ -> False
 
 -- | Check if node is in a candidate state
-isCandidate :: NodeState s  -> Bool
+isCandidate :: NodeState s v -> Bool
 isCandidate nodeState =
   case nodeState of
     NodeCandidateState _ -> True
     _ -> False
 
 -- | Check if node is in a leader state
-isLeader :: NodeState s  -> Bool
+isLeader :: NodeState s v -> Bool
 isLeader nodeState =
   case nodeState of
     NodeLeaderState _ -> True
diff --git a/src/Raft/Persistent.hs b/src/Raft/Persistent.hs
--- a/src/Raft/Persistent.hs
+++ b/src/Raft/Persistent.hs
@@ -17,6 +17,9 @@
 -- state to disk.
 class Monad m => RaftPersist m where
   type RaftPersistError m
+  initializePersistentState
+    :: Exception (RaftPersistError m)
+    => m (Either (RaftPersistError m) ())
   readPersistentState
     :: Exception (RaftPersistError m)
     => m (Either (RaftPersistError m) PersistentState)
@@ -30,7 +33,7 @@
     -- ^ Last term server has seen
   , votedFor :: Maybe NodeId
     -- ^ Candidate id that received vote in current term
-  } deriving (Show, Generic, S.Serialize)
+  } deriving (Show, Eq, Generic, S.Serialize)
 
 -- | A node initiates its persistent state with term 0 and with its vote blank
 initPersistentState :: PersistentState
diff --git a/src/Raft/RPC.hs b/src/Raft/RPC.hs
--- a/src/Raft/RPC.hs
+++ b/src/Raft/RPC.hs
@@ -66,7 +66,7 @@
   | FromClientReadReq Int
   deriving (Show)
 
-data EntriesSpec v
+data AppendEntriesSpec v
   = FromIndex Index
   | FromNewLeader (Entry v)
   | FromClientWriteReq (Entry v)
@@ -78,7 +78,7 @@
 data AppendEntriesData v = AppendEntriesData
   { aedTerm :: Term
   , aedLeaderCommit :: Index
-  , aedEntriesSpec :: EntriesSpec v
+  , aedEntriesSpec :: AppendEntriesSpec v
   } deriving (Show)
 
 -- | Representation of a message sent from a leader to its peers
diff --git a/src/Raft/StateMachine.hs b/src/Raft/StateMachine.hs
new file mode 100644
--- /dev/null
+++ b/src/Raft/StateMachine.hs
@@ -0,0 +1,52 @@
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE FunctionalDependencies #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE TypeFamilyDependencies #-}
+
+module Raft.StateMachine (
+  RaftStateMachinePure(..),
+  RaftStateMachinePureError(..),
+  RaftStateMachine(..),
+
+  applyLogEntry
+) where
+
+import Protolude
+
+import Raft.Log (Entry(..), EntryValue(..))
+
+--------------------------------------------------------------------------------
+-- State Machine
+--------------------------------------------------------------------------------
+
+-- | Interface to handle commands in the underlying state machine. Functional
+--dependency permitting only a single state machine command to be defined to
+--update the state machine.
+class RaftStateMachinePure sm v | sm -> v where
+  data RaftStateMachinePureError sm v
+  type RaftStateMachinePureCtx sm v = ctx | ctx -> sm v
+  rsmTransition
+    :: RaftStateMachinePureCtx sm v
+    -> sm
+    -> v
+    -> Either (RaftStateMachinePureError sm v) sm
+
+class (Monad m, RaftStateMachinePure sm v) => RaftStateMachine m sm v | m sm -> v where
+  validateCmd :: v -> m (Either (RaftStateMachinePureError sm v) ())
+  askRaftStateMachinePureCtx :: m (RaftStateMachinePureCtx sm v)
+
+applyLogEntry
+  :: RaftStateMachine m sm v
+  => sm
+  -> Entry v
+  -> m (Either (RaftStateMachinePureError sm v) sm)
+applyLogEntry sm e  =
+  case entryValue e of
+    NoValue -> pure (Right sm)
+    EntryValue v -> do
+      res <- validateCmd v
+      case res of
+        Left err -> pure (Left err)
+        Right () -> do
+          ctx <- askRaftStateMachinePureCtx
+          pure (rsmTransition ctx sm v)
diff --git a/src/Raft/Types.hs b/src/Raft/Types.hs
--- a/src/Raft/Types.hs
+++ b/src/Raft/Types.hs
@@ -1,6 +1,8 @@
 {-# LANGUAGE ConstraintKinds #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE DeriveAnyClass #-}
 {-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
 {-# LANGUAGE DataKinds #-}
 {-# LANGUAGE KindSignatures #-}
 {-# LANGUAGE RankNTypes #-}
@@ -12,6 +14,9 @@
 import Data.Serialize
 import Numeric.Natural (Natural)
 
+import Database.PostgreSQL.Simple.ToField
+import Database.PostgreSQL.Simple.FromField
+
 --------------------------------------------------------------------------------
 -- NodeIds
 --------------------------------------------------------------------------------
@@ -22,20 +27,46 @@
 
 -- | Unique identifier of a client
 newtype ClientId = ClientId NodeId
-  deriving (Show, Eq, Ord, Generic, Serialize)
+  deriving stock (Show, Read, Eq, Ord, Generic)
+  deriving newtype Serialize
 
 -- | Unique identifier of a leader
 newtype LeaderId = LeaderId { unLeaderId :: NodeId }
-  deriving (Show, Eq, Generic, Serialize)
+  deriving stock (Show, Read, Eq, Generic)
+  deriving newtype Serialize
 
+-- | Representation of the current leader in the cluster. The system is
+-- considered to be unavailable if there is no leader
+data CurrentLeader
+  = CurrentLeader LeaderId
+  | NoLeader
+  deriving stock (Show, Eq, Generic)
+  deriving anyclass (Serialize)
+
 ----------
 -- Term --
 ----------
 
 -- | Representation of monotonic election terms
 newtype Term = Term Natural
-  deriving (Show, Eq, Ord, Enum, Generic, Serialize)
+  deriving stock (Generic)
+  deriving newtype (Show, Eq, Ord, Enum, Serialize)
 
+instance ToField Term where
+  toField (Term n) = toField (fromIntegral n :: Int)
+
+instance FromField Term where
+  fromField f mdata =
+    case (intToNatural <=< readEither) . toS <$> mdata of
+      Nothing -> returnError UnexpectedNull f ""
+      Just (Left err) -> returnError ConversionFailed f err
+      Just (Right nat) -> return (Term nat)
+
+intToNatural :: Int -> Either [Char] Natural
+intToNatural n
+  | n < 0 = Left "Natural numbers must be >= 0"
+  | otherwise = Right (fromIntegral n)
+
 -- | Initial term. Terms start at 0
 term0 :: Term
 term0 = Term 0
@@ -53,8 +84,19 @@
 
 -- | Representation of monotonic indices
 newtype Index = Index Natural
-  deriving (Show, Eq, Ord, Enum, Num, Integral, Real, Generic, Serialize)
+  deriving stock (Show, Generic)
+  deriving newtype (Eq, Ord, Enum, Num, Integral, Real, Serialize)
 
+instance ToField Index where
+  toField (Index n) = toField (fromIntegral n :: Int)
+
+instance FromField Index where
+  fromField f mdata =
+    case (intToNatural <=< readEither) . toS <$> mdata of
+      Nothing -> returnError UnexpectedNull f ""
+      Just (Left err) -> returnError ConversionFailed f err
+      Just (Right nat) -> return (Index nat)
+
 -- | Initial index. Indeces start at 0
 index0 :: Index
 index0 = Index 0
@@ -67,3 +109,11 @@
 decrIndexWithDefault0 :: Index -> Index
 decrIndexWithDefault0 (Index 0) = index0
 decrIndexWithDefault0 i = pred i
+
+-----------------------------------
+-- Client Request Serial Numbers --
+-----------------------------------
+
+newtype SerialNum = SerialNum Natural
+  deriving stock (Show, Read, Generic)
+  deriving newtype (Eq, Ord, Enum, Num, Serialize)
diff --git a/test/QuickCheckStateMachine.hs b/test/QuickCheckStateMachine.hs
new file mode 100644
--- /dev/null
+++ b/test/QuickCheckStateMachine.hs
@@ -0,0 +1,396 @@
+{-# LANGUAGE DeriveAnyClass     #-}
+{-# LANGUAGE DeriveGeneric      #-}
+{-# LANGUAGE FlexibleInstances  #-}
+{-# LANGUAGE PolyKinds          #-}
+{-# LANGUAGE RecordWildCards    #-}
+{-# LANGUAGE StandaloneDeriving #-}
+
+module QuickCheckStateMachine where
+
+import           Control.Concurrent            (threadDelay)
+import           Control.Exception             (bracket)
+import           Control.Monad.IO.Class        (liftIO)
+import           Data.Bifunctor                (bimap)
+import           Data.Char                     (isDigit)
+import           Data.List                     (isInfixOf, (\\))
+import           Data.Maybe                    (isJust, isNothing)
+import qualified Data.Set                      as Set
+import           Data.TreeDiff                 (ToExpr)
+import           GHC.Generics                  (Generic, Generic1)
+import           Prelude                       hiding (notElem)
+import           System.Directory              (removePathForcibly)
+import           System.IO                     (BufferMode (NoBuffering),
+                                                Handle, IOMode (WriteMode),
+                                                hClose, hGetLine, hPutStrLn,
+                                                hPutStrLn, hSetBuffering,
+                                                openFile)
+import           System.Process                (ProcessHandle, StdStream (CreatePipe, UseHandle),
+                                                callCommand, createProcess_,
+                                                getPid, getProcessExitCode, waitForProcess,
+                                                proc, std_err, std_in, std_out,
+                                                terminateProcess)
+import           System.Timeout                (timeout)
+import           Test.QuickCheck               (Gen, Property, arbitrary,
+                                                elements, frequency,
+                                                noShrinking, shrink,
+                                                verboseCheck, withMaxSuccess,
+                                                (===))
+import           Test.QuickCheck.Monadic       (monadicIO)
+import           Test.StateMachine             (Concrete, GenSym, Logic (..),
+                                                Opaque (..), Reason (Ok),
+                                                Reference, StateMachine (..),
+                                                Symbolic, forAllCommands, notElem,
+                                                genSym, opaque, prettyCommands,
+                                                reference, runCommands, (.&&),
+                                                (.//), (.<), (.==), (.>=))
+import           Test.StateMachine.Types       (Command(..), Commands (..), Reference(..), Symbolic(..), Var(..))
+import qualified Test.StateMachine.Types.Rank2 as Rank2
+import           Text.Read                     (readEither)
+
+import qualified Data.Functor.Classes
+import           Debug.Trace                   (trace)
+
+------------------------------------------------------------------------
+
+type Port = Int
+
+data Persistence = Fresh | Existing
+  deriving (Show)
+
+data ClientHandleRefs (r :: * -> *) = ClientHandleRefs
+  { client_hin :: Reference (Opaque Handle) r
+  , client_hout :: Reference (Opaque Handle) r
+  } deriving (Show, Generic, Generic1, Rank2.Functor, Rank2.Foldable, Rank2.Traversable)
+
+deriving instance ToExpr (ClientHandleRefs Concrete)
+
+type ProcessHandleRef r = Reference (Opaque ProcessHandle) r
+
+data Action (r :: * -> *)
+  = SpawnNode Port Persistence
+  | SpawnClient Port
+  | KillNode (Port, ProcessHandleRef r)
+  | Set (ClientHandleRefs r) Integer
+  | Read (ClientHandleRefs r)
+  | Incr (ClientHandleRefs r)
+  | BreakConnection (Port, ProcessHandleRef r)
+  | FixConnection   (Port, ProcessHandleRef r)
+  deriving (Show, Generic1, Rank2.Functor, Rank2.Foldable, Rank2.Traversable)
+
+data Response (r :: * -> *)
+  = SpawnedNode (ProcessHandleRef r)
+  | SpawnedClient (ClientHandleRefs r) (ProcessHandleRef r)
+  | SpawnFailed Port
+  | BrokeConnection
+  | FixedConnection
+  | Ack
+  | Timeout
+  | Value (Either String Integer)
+  deriving (Show, Generic1, Rank2.Foldable)
+
+data Model (r :: * -> *) = Model
+  { nodes    :: [(Port, ProcessHandleRef r)]
+  , client   :: Maybe (ClientHandleRefs r, ProcessHandleRef r)
+  , started  :: Bool
+  , value    :: Maybe Integer
+  , isolated :: [(Port, ProcessHandleRef r)]
+  }
+  deriving (Show, Generic)
+
+deriving instance ToExpr (Model Concrete)
+
+initModel :: Model r
+initModel = Model [] Nothing False Nothing []
+
+transition :: Data.Functor.Classes.Show1 r => Model r -> Action r -> Response r -> Model r
+transition Model {..} act resp = case (act, resp) of
+  (SpawnNode port _, SpawnedNode ph) ->
+    let newNodes = nodes ++ [(port, ph)]
+     in if length newNodes == 3
+           then Model { nodes = newNodes, started = True, .. }
+           else Model { nodes = newNodes, .. }
+  (SpawnNode {}, SpawnFailed _)    -> Model {..}
+  (SpawnClient {}, SpawnedClient chrs cph) -> Model { client = Just (chrs, cph), .. }
+  (SpawnClient {}, SpawnFailed _)  -> Model {..}
+  (KillNode (port, _ph), Ack)      -> Model { nodes = filter ((/= port) . fst) nodes, isolated = filter ((/= port) . fst) isolated, .. }
+  (Set _ i, Ack)                   -> Model { value = Just i, .. }
+  (Read {}, Value _i)              -> Model {..}
+  (Incr {}, Ack)                   -> Model { value = succ <$> value, ..}
+  (BreakConnection node, BrokeConnection) -> Model { isolated = node:isolated, .. }
+  (FixConnection (port,_), FixedConnection)  -> Model { isolated = filter ((/= port) . fst) isolated, .. }
+  (Read {}, Timeout)                  -> Model {..}
+  (Set {}, Timeout)                -> Model {..}
+  (Incr {}, Timeout)               -> Model {..}
+  unaccounted                      -> trace (show unaccounted) $ error "transition"
+
+-- TODO I don't think the precondition is being checked...
+precondition :: Model Symbolic -> Action Symbolic -> Logic
+precondition Model {..} act = case act of
+  SpawnNode {}       -> length nodes .< 3
+  SpawnClient {}     -> length nodes .== 3 .&& Boolean (isNothing client)
+  KillNode  {}       -> length nodes .== 3
+  Set _ i            -> length nodes .== 3 .&& i .>= 0
+  Read _             -> length nodes .== 3 .&& Boolean (isJust value)
+  Incr _             -> length nodes .== 3 .&& Boolean (isJust value)
+  BreakConnection (port, _) -> length nodes .== 3 .&& port `notElem` map fst isolated
+  FixConnection   {} -> length nodes .== 3 .&& Boolean (not (null isolated))
+
+postcondition :: Model Concrete -> Action Concrete -> Response Concrete -> Logic
+postcondition Model {..} act resp = case (act, resp) of
+  (Read _, Value (Right i))      -> Just i .== value
+  (Read _, Value (Left e))       -> Bot .// e
+  (SpawnNode {}, SpawnedNode {}) -> Top
+  (SpawnNode {}, SpawnFailed {}) -> Bot .// "SpawnFailed - Node"
+  (SpawnClient {}, SpawnedClient {}) -> Top
+  (SpawnClient {}, SpawnFailed {}) -> Bot .// "SpawnFailed - Client"
+  (KillNode {}, Ack)             -> Top
+  (Set {}, Ack)                  -> Top
+  (Incr {}, Ack)                 -> Top
+  (BreakConnection {}, BrokeConnection) -> Top
+  (FixConnection {}, FixedConnection)   -> Top
+  (Read _,    Timeout)           -> Boolean (not (null isolated)) .// "Read timeout"
+  (Set {},  Timeout)             -> Boolean (not (null isolated)) .// "Set timeout"
+  (Incr {}, Timeout)             -> Boolean (not (null isolated)) .// "Incr timeout"
+  (_,            _)              -> Bot .// "postcondition"
+
+command :: Handle -> ClientHandleRefs Concrete -> String -> IO (Int, Maybe String)
+command h chs@ClientHandleRefs{..} cmd = go 3 0
+  where
+    go 0 unex = pure (unex, Nothing)
+    go n unex = do
+      eRes <- do
+        hPutStrLn h cmd
+        hPutStrLn (opaque client_hin) cmd
+        mresp <- getResponse (opaque client_hout)
+        case mresp of
+          Nothing   -> do
+            hPutStrLn h "'getResponse' timed out"
+            pure (Just (unex, Nothing))
+          Just resp ->
+            if "Timeout" `isInfixOf` resp
+            then do
+              hPutStrLn h ("Command timed out, retrying: " ++ show resp)
+              pure (Just (unex, Nothing))
+            else if "Unexpected" `isInfixOf` resp
+              then do
+                hPutStrLn h ("Unexpected read/write response, retrying: " ++ show resp)
+                pure (Just (unex + 1, Nothing))
+              else do
+                hPutStrLn h ("got response `" ++ resp ++ "'")
+                pure (Just (unex, Just resp))
+      case eRes of
+        Nothing -> pure (unex, Nothing)
+        -- Recurse if command successful
+        Just (unex, Nothing) -> threadDelay 1000000 >> go (n-1) unex
+        Just (unex, Just resp) -> pure (unex, Just resp)
+
+    getResponse :: Handle -> IO (Maybe String)
+    getResponse hout = do
+      mline <- timeout 10000000 (hGetLine hout)
+      case mline of
+        Nothing   -> return Nothing
+        Just line
+          | "New leader found" `isInfixOf` line -> getResponse hout
+          | otherwise -> return (Just line)
+
+semantics :: Handle -> Action Concrete -> IO (Response Concrete)
+semantics h (SpawnNode port1 p) = do
+  hPutStrLn h ("Spawning node on port " ++ show port1)
+  removePathForcibly ("/tmp/raft-log-" ++ show port1 ++ ".txt")
+  h' <- openFile ("/tmp/raft-log-" ++ show port1 ++ ".txt") WriteMode
+  let port2, port3 :: Int
+      (port2, port3) = case port1 of
+        3000 -> (3001, 3002)
+        3001 -> (3000, 3002)
+        3002 -> (3000, 3001)
+        _    -> error "semantics: invalid port1"
+  let persistence Fresh    = "fresh"
+      persistence Existing = "existing"
+  (_, _, _, ph) <- createProcess_ "raft node"
+    (proc "fiu-run" [ "-x", "stack", "exec", "raft-example", "node"
+                    , persistence p, "postgres", show port1
+                    , "localhost:" ++ show port1
+                    , "localhost:" ++ show port2
+                    , "localhost:" ++ show port3
+                    ])
+      { std_out = UseHandle h'
+      , std_err = UseHandle h'
+      }
+  threadDelay 1500000
+  mec <- getProcessExitCode ph
+  case mec of
+    Nothing -> return (SpawnedNode (reference (Opaque ph)))
+    Just ec -> do
+      hPutStrLn h (show ec)
+      return (SpawnFailed port1)
+
+semantics h (SpawnClient cport) = do
+  hPutStrLn h "Spawning client"
+  (Just hin, Just hout, _, ph) <- createProcess_ "raft client"
+    (proc "stack" [ "exec", "raft-example", "client" ])
+     { std_out = CreatePipe
+     , std_in  = CreatePipe
+     , std_err = CreatePipe
+     }
+
+  threadDelay 1000000
+  mec <- getProcessExitCode ph
+  case mec of
+    Just ec -> do
+      hPutStrLn h (show ec)
+      return (SpawnFailed cport)
+    Nothing -> do
+      threadDelay 100000
+      hSetBuffering hin  NoBuffering
+      hSetBuffering hout NoBuffering
+      hPutStrLn hin "addNode localhost:3000"
+      hPutStrLn hin "addNode localhost:3001"
+      hPutStrLn hin "addNode localhost:3002"
+      let refClient_hin = reference (Opaque hin)
+          refClient_hout = reference (Opaque hout)
+          clientHandleRefs = ClientHandleRefs refClient_hin refClient_hout
+      return (SpawnedClient clientHandleRefs (reference (Opaque ph)))
+
+semantics h (KillNode (_port, ph)) = do
+  hPutStrLn h $ "Attempting to kill node on port " ++ show _port ++ "..."
+  terminateProcess (opaque ph)
+  waitForProcess (opaque ph)
+  hPutStrLn h $ "Successfully killed node on port " ++ show _port
+  return Ack
+semantics h (Set chs i) = do
+  (_, mresp) <- command h chs ("set x " ++ show i)
+  case mresp of
+    Nothing    -> return Timeout
+    Just _resp -> return Ack
+semantics h (Read chs) = do
+  (ue, mresp) <- command h chs "read"
+  case mresp of
+    Nothing   -> return Timeout
+    Just resp -> do
+      let parse = readEither
+                . takeWhile isDigit
+                . drop 1
+                . snd
+                . break (== ',')
+      return (Value (bimap (++ (": " ++ resp)) id (parse resp)))
+semantics h (Incr chs) = do
+  (_, mresp) <- command h chs "incr x"
+  case mresp of
+    Nothing   -> return Timeout
+    Just resp -> return Ack
+semantics h (BreakConnection (port, ph)) = do
+  hPutStrLn h ("Break connection, port: " ++ show port)
+  Just pid <- getPid (opaque ph)
+  callCommand ("fiu-ctrl -c \"enable name=posix/io/net/send\" " ++ show pid)
+  callCommand ("fiu-ctrl -c \"enable name=posix/io/net/recv\" " ++ show pid)
+  threadDelay 2000000
+  return BrokeConnection
+semantics h (FixConnection (port, ph)) = do
+  hPutStrLn h ("Fix connection, port: " ++ show port)
+  Just pid <- getPid (opaque ph)
+  callCommand ("fiu-ctrl -c \"disable name=posix/io/net/send\" " ++ show pid)
+  callCommand ("fiu-ctrl -c \"disable name=posix/io/net/recv\" " ++ show pid)
+  threadDelay 2000000
+  return FixedConnection
+
+generator :: Model Symbolic -> Gen (Action Symbolic)
+generator Model {..}
+  | length nodes < 3  =
+      if started
+        then flip SpawnNode Existing <$> elements ([3000..3002] \\ map fst nodes)
+        else flip SpawnNode Fresh <$> elements ([3000..3002] \\ map fst nodes)
+  | otherwise        =
+      case client of
+        Nothing -> SpawnClient <$> elements [3003..3010]
+        Just (chs, _) ->
+          case value of
+            Nothing -> Set chs <$> arbitrary
+            Just _
+              | length isolated <= 1 -> frequency $
+                  [ (1, Set chs <$> arbitrary)
+                  , (5, pure (Incr chs))
+                  , (3, pure (Read chs))
+                  , (1, KillNode <$> elements nodes)
+                  , (1, BreakConnection <$> elements nodes)
+                  ] ++ case isolated of
+                         [] -> []
+                         _ -> [(1, FixConnection <$> elements isolated)]
+              | otherwise ->
+                  let notIsolated = filter (\(p,_) -> not (p `elem` map fst isolated)) nodes
+                  in frequency $
+                       [ (1, KillNode <$> elements nodes)
+                       ] ++ case notIsolated of
+                              [] -> []
+                              _ -> [(1, BreakConnection <$> elements notIsolated )]
+                         ++ case isolated of
+                              [] -> []
+                              _ -> [(1, FixConnection <$> elements isolated)]
+
+shrinker :: Action Symbolic -> [Action Symbolic]
+shrinker (Set cph i) = [ Set cph i' | i' <- shrink i ]
+shrinker _       = []
+
+mock :: Model Symbolic -> Action Symbolic -> GenSym (Response Symbolic)
+mock _m SpawnNode {}       = SpawnedNode <$> genSym
+mock _m SpawnClient {}     = SpawnedClient <$> (ClientHandleRefs <$> genSym <*> genSym) <*> genSym
+mock _m KillNode {}        = pure Ack
+mock _m Set {}             = pure Ack
+mock _m Read {}            = pure (Value (Right 0))
+mock _m Incr {}            = pure Ack
+mock _m BreakConnection {} = pure BrokeConnection
+mock _m FixConnection {}   = pure FixedConnection
+
+setup :: IO Handle
+setup = do
+  removePathForcibly "/tmp/raft-log.txt"
+  h <- openFile "/tmp/raft-log.txt" WriteMode
+  hSetBuffering h NoBuffering
+  return h
+
+sm :: Handle -> StateMachine Model Action IO Response
+sm h = StateMachine initModel transition precondition postcondition
+               Nothing generator Nothing shrinker (semantics h) mock
+
+prop_sequential :: Property
+prop_sequential = withMaxSuccess 10 $ noShrinking $
+  forAllCommands (sm undefined) (Just 20) $ \cmds -> monadicIO $ do
+    h <- liftIO setup
+    let sm' = sm h
+    (hist, model, res) <- runCommands sm' cmds
+    prettyCommands sm' hist (res === Ok)
+    liftIO (hClose h)
+    -- Terminate node processes
+    liftIO (mapM_ (terminateProcess . opaque . snd) (nodes model))
+    -- Terminate client process
+    case client model of
+      Nothing -> pure ()
+      Just (ClientHandleRefs chin chout, cph) -> do
+        liftIO (terminateProcess (opaque cph))
+        liftIO (hClose (opaque chin) >> hClose (opaque chout))
+
+------------------------------------------------------------------------
+
+runMany :: Commands Action -> Handle -> Property
+runMany cmds log = monadicIO $ do
+  (hist, model, res) <- runCommands (sm log) cmds
+  prettyCommands (sm log) hist (res === Ok)
+  -- Terminate node processes
+  liftIO (mapM_ (terminateProcess . opaque . snd) (nodes model))
+  -- Terminate client process
+  case client model of
+    Nothing -> pure ()
+    Just (ClientHandleRefs chin chout, cph) -> do
+      liftIO (terminateProcess (opaque cph))
+      liftIO (hClose (opaque chin) >> hClose (opaque chout))
+
+-------------------------------------------------------------------------------
+
+-- unit_exampleUnit :: IO ()
+-- unit_exampleUnit = bracket setup hClose (verboseCheck . exampleUnit)
+--
+-- exampleUnit :: Handle -> Property
+-- exampleUnit = runMany cmds
+--   where
+--     cmds = Commands
+--       [ -- Commands go here...
+--       ]
diff --git a/test/TestDejaFu.hs b/test/TestDejaFu.hs
--- a/test/TestDejaFu.hs
+++ b/test/TestDejaFu.hs
@@ -7,6 +7,7 @@
 {-# LANGUAGE StandaloneDeriving #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE UndecidableInstances #-}
+{-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeFamilies #-}
 
 module TestDejaFu where
@@ -31,12 +32,16 @@
 import Test.Tasty
 import Test.Tasty.DejaFu hiding (get)
 
-import System.Random (mkStdGen)
+import System.Random (mkStdGen, newStdGen)
 
 import TestUtils
 
 import Raft
+import Raft.Log
+import Raft.Client
 
+import Data.Time.Clock.System (getSystemTime)
+
 --------------------------------------------------------------------------------
 -- Test State Machine & Commands
 --------------------------------------------------------------------------------
@@ -54,25 +59,28 @@
 
 data StoreCtx = StoreCtx
 
-instance RSMP Store StoreCmd where
-  data RSMPError Store StoreCmd = StoreError Text deriving (Show)
-  type RSMPCtx Store StoreCmd = StoreCtx
-  applyCmdRSMP _ store cmd =
+instance RaftStateMachinePure Store StoreCmd where
+  data RaftStateMachinePureError Store StoreCmd = StoreError Text deriving (Show)
+  type RaftStateMachinePureCtx Store StoreCmd = StoreCtx
+  rsmTransition _ store cmd =
     Right $ case cmd of
       Set x n -> Map.insert x n store
       Incr x -> Map.adjust succ x store
 
-instance RSM Store StoreCmd RaftTestM where
+instance RaftStateMachine RaftTestM Store StoreCmd where
   validateCmd _ = pure (Right ())
-  askRSMPCtx = pure StoreCtx
+  askRaftStateMachinePureCtx = pure StoreCtx
 
 type TestEventChan = EventChan ConcIO StoreCmd
-type TestClientRespChan = TChan (STM ConcIO) (ClientResponse Store)
+type TestEventChans = Map NodeId TestEventChan
 
+type TestClientRespChan = TChan (STM ConcIO) (ClientResponse Store StoreCmd)
+type TestClientRespChans = Map ClientId TestClientRespChan
+
 -- | Node specific environment
 data TestNodeEnv = TestNodeEnv
-  { testNodeEventChans :: Map NodeId TestEventChan
-  , testClientRespChans :: Map ClientId TestClientRespChan
+  { testNodeEventChans :: TestEventChans
+  , testClientRespChans :: TestClientRespChans
   , testNodeConfig :: NodeConfig
   }
 
@@ -130,6 +138,7 @@
 
 instance RaftPersist RaftTestM where
   type RaftPersistError RaftTestM = RaftTestError
+  initializePersistentState = pure (Right ())
   writePersistentState pstate' = do
     nid <- askSelfNodeId
     fmap Right $ modify $ \testState ->
@@ -150,13 +159,18 @@
     eventChan <- lookupNodeEventChan nid
     atomically $ writeTChan eventChan (MessageEvent (RPCMessageEvent rpc))
 
-instance RaftSendClient RaftTestM Store where
+instance RaftSendClient RaftTestM Store StoreCmd where
   sendClient cid cr = do
     clientRespChans <- asks testClientRespChans
     case Map.lookup cid clientRespChans of
       Nothing -> panic "Failed to find client id in environment"
       Just clientRespChan -> atomically (writeTChan clientRespChan cr)
 
+instance RaftInitLog RaftTestM StoreCmd where
+  type RaftInitLogError RaftTestM = RaftTestError
+  -- No log initialization needs to be done here, everything is in memory.
+  initializeLog _ = pure (Right ())
+
 instance RaftWriteLog RaftTestM StoreCmd where
   type RaftWriteLogError RaftTestM = RaftTestError
   writeLogEntries entries = do
@@ -195,6 +209,43 @@
 
 --------------------------------------------------------------------------------
 
+data TestClientEnv = TestClientEnv
+  { testClientEnvRespChan :: TestClientRespChan
+  , testClientEnvNodeEventChans :: TestEventChans
+  }
+
+type RaftTestClientM' = ReaderT TestClientEnv ConcIO
+type RaftTestClientM = RaftClientT Store StoreCmd RaftTestClientM'
+
+instance RaftClientSend RaftTestClientM' StoreCmd where
+  type RaftClientSendError RaftTestClientM' StoreCmd = ()
+  raftClientSend nid creq = do
+    Just nodeEventChan <- asks (Map.lookup nid . testClientEnvNodeEventChans)
+    lift $ atomically $ writeTChan nodeEventChan (MessageEvent (ClientRequestEvent creq))
+    pure (Right ())
+
+instance RaftClientRecv RaftTestClientM' Store StoreCmd where
+  type RaftClientRecvError RaftTestClientM' Store = ()
+  raftClientRecv = do
+    clientRespChan <- asks testClientEnvRespChan
+    fmap Right $ lift $ atomically $ readTChan clientRespChan
+
+runRaftTestClientM
+  :: ClientId
+  -> TestClientRespChan
+  -> TestEventChans
+  -> RaftTestClientM a
+  -> ConcIO a
+runRaftTestClientM cid chan chans rtcm = do
+  raftClientState <- initRaftClientState mempty <$> liftIO newStdGen
+  let raftClientEnv = RaftClientEnv cid
+      testClientEnv = TestClientEnv chan chans
+   in flip runReaderT testClientEnv
+    . runRaftClientT raftClientEnv raftClientState { raftClientRaftNodes = Map.keysSet chans }
+    $ rtcm
+
+--------------------------------------------------------------------------------
+
 initTestChanMaps :: ConcIO (Map NodeId TestEventChan, Map ClientId TestClientRespChan)
 initTestChanMaps = do
   eventChans <-
@@ -232,9 +283,6 @@
 
 --------------------------------------------------------------------------------
 
-type TestEventChans = Map NodeId TestEventChan
-type TestClientRespChans = Map ClientId TestClientRespChan
-
 test_concurrency :: [TestTree]
 test_concurrency =
     [ testGroup "Leader Election" [ testConcurrentProps (leaderElection node0) mempty ]
@@ -279,46 +327,51 @@
         teardown = mapM_ killThread . fst
 
 leaderElection :: NodeId -> TestEventChans -> TestClientRespChans -> ConcIO Store
-leaderElection nid eventChans clientRespChans = do
-    atomically $ writeTChan nodeEventChan (TimeoutEvent ElectionTimeout)
-    pollForReadResponse nodeEventChan client0RespChan
+leaderElection nid eventChans clientRespChans =
+    runRaftTestClientM client0 client0RespChan eventChans $
+      leaderElection' nid eventChans
   where
+     Just client0RespChan = Map.lookup client0 clientRespChans
+
+leaderElection' :: NodeId -> TestEventChans -> RaftTestClientM Store
+leaderElection' nid eventChans = do
+    sysTime <- liftIO getSystemTime
+    lift $ lift $ atomically $ writeTChan nodeEventChan (TimeoutEvent sysTime ElectionTimeout)
+    pollForReadResponse nid
+  where
     Just nodeEventChan = Map.lookup nid eventChans
-    Just client0RespChan = Map.lookup client0 clientRespChans
 
 incrValue :: TestEventChans -> TestClientRespChans -> ConcIO (Store, Index)
 incrValue eventChans clientRespChans = do
     leaderElection node0 eventChans clientRespChans
-    Right idx <- do
-      syncClientWrite node0EventChan (client0, client0RespChan) (Set "x" 41)
-      syncClientWrite node0EventChan (client0, client0RespChan) (Incr"x")
-    store <- pollForReadResponse node0EventChan client0RespChan
-    pure (store, idx)
+    runRaftTestClientM client0 client0RespChan eventChans $ do
+      Right idx <- do
+        syncClientWrite node0 (Set "x" 41)
+        syncClientWrite node0 (Incr"x")
+      store <- pollForReadResponse node0
+      pure (store, idx)
   where
-    Just node0EventChan = Map.lookup node0 eventChans
     Just client0RespChan = Map.lookup client0 clientRespChans
 
 multIncrValue :: TestEventChans -> TestClientRespChans -> ConcIO (Store, Index)
 multIncrValue eventChans clientRespChans = do
-    leaderElection node0 eventChans clientRespChans
-    syncClientWrite node0EventChan (client0, client0RespChan) (Set "x" 0)
+  leaderElection node0 eventChans clientRespChans
+  runRaftTestClientM client0 client0RespChan eventChans $ do
+    syncClientWrite node0 (Set "x" 0)
     Right idx <-
-      fmap (Maybe.fromJust . lastMay) $ replicateM 10 $ do
-        res <- syncClientWrite node0EventChan (client0, client0RespChan) (Incr "x")
-        pollForReadResponse node0EventChan client0RespChan
-        pure res
-    store <- pollForReadResponse node0EventChan client0RespChan
+      fmap (Maybe.fromJust . lastMay) $
+        replicateM 10 $ syncClientWrite node0 (Incr "x")
+    store <- pollForReadResponse node0
     pure (store, idx)
   where
-    Just node0EventChan = Map.lookup node0 eventChans
     Just client0RespChan = Map.lookup client0 clientRespChans
 
 leaderRedirect :: TestEventChans -> TestClientRespChans -> ConcIO CurrentLeader
-leaderRedirect eventChans clientRespChans = do
-    Left resp <- syncClientWrite node1EventChan (client0, client0RespChan) (Set "x" 42)
+leaderRedirect eventChans clientRespChans =
+  runRaftTestClientM client0 client0RespChan eventChans $ do
+    Left resp <- syncClientWrite node1 (Set "x" 42)
     pure resp
   where
-    Just node1EventChan = Map.lookup node1 eventChans
     Just client0RespChan = Map.lookup client0 clientRespChans
 
 followerRedirNoLeader :: TestEventChans -> TestClientRespChans -> ConcIO CurrentLeader
@@ -335,96 +388,100 @@
     leaderElection node1 eventChans clientRespChans
     leaderElection node2 eventChans clientRespChans
     leaderElection node1 eventChans clientRespChans
-    atomically $ writeTChan node0EventChan $ clientReadReq client0
-    ClientRedirectResponse (ClientRedirResp ldr) <- atomically $ readTChan client0RespChan
-    pure ldr
+    runRaftTestClientM client0 client0RespChan eventChans $ do
+      Left ldr <- syncClientRead node0
+      pure ldr
   where
-    Just node0EventChan = Map.lookup node0 eventChans
     Just client0RespChan = Map.lookup client0 clientRespChans
 
 comprehensive :: TestEventChans -> TestClientRespChans -> ConcIO (Index, Store, CurrentLeader)
-comprehensive eventChans clientRespChans = do
-    leaderElection node0 eventChans clientRespChans
-    Right idx2 <- syncClientWriteClient0 node0EventChan (Set "x" 7)
-    Right idx3 <- syncClientWriteClient0 node0EventChan (Set "y" 3)
-    Left (CurrentLeader _) <- syncClientWriteClient0 node1EventChan (Incr "y")
-    Right _ <- syncClientRead node0EventChan (client0, client0RespChan)
+comprehensive eventChans clientRespChans =
+  runRaftTestClientM client0 client0RespChan eventChans $ do
+    leaderElection'' node0
+    Right idx2 <- syncClientWrite node0 (Set "x" 7)
+    Right idx3 <- syncClientWrite node0 (Set "y" 3)
+    Left (CurrentLeader _) <- syncClientWrite node1 (Incr "y")
+    Right _ <- syncClientRead node0
 
-    leaderElection node1 eventChans clientRespChans
-    Right idx5 <- syncClientWriteClient0 node1EventChan (Incr "x")
-    Right idx6 <- syncClientWriteClient0 node1EventChan (Incr "y")
-    Right idx7 <- syncClientWriteClient0 node1EventChan (Set "z" 40)
-    Left (CurrentLeader _) <- syncClientWriteClient0 node2EventChan (Incr "y")
-    Right _ <- syncClientRead node1EventChan (client0, client0RespChan)
+    leaderElection'' node1
+    Right idx5 <- syncClientWrite node1 (Incr "x")
+    Right idx6 <- syncClientWrite node1 (Incr "y")
+    Right idx7 <- syncClientWrite node1 (Set "z" 40)
+    Left (CurrentLeader _) <- syncClientWrite node2 (Incr "y")
+    Right _ <- syncClientRead node1
 
-    leaderElection node2 eventChans clientRespChans
-    Right idx9 <- syncClientWriteClient0 node2EventChan (Incr "z")
-    Right idx10 <- syncClientWriteClient0 node2EventChan (Incr "x")
-    Left _ <- syncClientWriteClient0 node1EventChan (Set "q" 100)
-    Right idx11 <- syncClientWriteClient0 node2EventChan (Incr "y")
-    Left _ <- syncClientWriteClient0 node0EventChan (Incr "z")
-    Right idx12 <- syncClientWriteClient0 node2EventChan (Incr "y")
-    Left (CurrentLeader _) <- syncClientWriteClient0 node0EventChan (Incr "y")
-    Right _ <- syncClientRead node2EventChan (client0, client0RespChan)
+    leaderElection'' node2
+    Right idx9 <- syncClientWrite node2 (Incr "z")
+    Right idx10 <- syncClientWrite node2 (Incr "x")
+    Left _ <- syncClientWrite node1 (Set "q" 100)
+    Right idx11 <- syncClientWrite node2 (Incr "y")
+    Left _ <- syncClientWrite node0 (Incr "z")
+    Right idx12 <- syncClientWrite node2 (Incr "y")
+    Left (CurrentLeader _) <- syncClientWrite node0 (Incr "y")
+    Right _ <- syncClientRead node2
 
-    leaderElection node0 eventChans clientRespChans
-    Right idx14 <- syncClientWriteClient0 node0EventChan (Incr "z")
-    Left (CurrentLeader _) <- syncClientWriteClient0 node1EventChan (Incr "y")
+    leaderElection'' node0
+    Right idx14 <- syncClientWrite node0 (Incr "z")
+    Left (CurrentLeader _) <- syncClientWrite node1 (Incr "y")
 
-    Right store <- syncClientRead node0EventChan (client0, client0RespChan)
-    Left ldr <- syncClientRead node1EventChan (client0, client0RespChan)
+    Right store <- syncClientRead node0
+    Left ldr <- syncClientRead node1
 
     pure (idx14, store, ldr)
   where
-    syncClientWriteClient0 = flip syncClientWrite (client0, client0RespChan)
-
-    Just node0EventChan = Map.lookup node0 eventChans
-    Just node1EventChan = Map.lookup node1 eventChans
-    Just node2EventChan = Map.lookup node2 eventChans
-
+    leaderElection'' nid = leaderElection' nid eventChans
     Just client0RespChan = Map.lookup client0 clientRespChans
 
 --------------------------------------------------------------------------------
 -- Helpers
 --------------------------------------------------------------------------------
 
-pollForReadResponse :: TestEventChan -> TestClientRespChan -> ConcIO Store
-pollForReadResponse nodeEventChan clientRespChan = do
-  -- Warning: Do not change the separate "atomically" calls, or you may
-  -- introduce a deadlock
-  atomically $ writeTChan nodeEventChan $ clientReadReq client0
-  res <- atomically $ readTChan clientRespChan
-  case res of
-    ClientReadResponse (ClientReadResp res) -> pure res
+-- | This function can be safely "run" without worry about impacting the client
+-- SerialNum of the client requests.
+--
+-- Warning: If read requests start to include serial numbers, this function will
+-- no longer be safe to `runRaftTestClientM` on.
+pollForReadResponse :: NodeId -> RaftTestClientM Store
+pollForReadResponse nid = do
+  eRes <- clientReadFrom nid ClientReadStateMachine
+  case eRes of
+    -- TODO Handle other cases of 'ClientReadResp'
+    Right (ClientReadRespStateMachine res) -> pure res
     _ -> do
-      liftIO $ Control.Monad.Conc.Class.threadDelay 1000
-      pollForReadResponse nodeEventChan clientRespChan
+      liftIO $ Control.Monad.Conc.Class.threadDelay 10000
+      pollForReadResponse nid
 
-syncClientRead :: TestEventChan -> (ClientId, TestClientRespChan) -> ConcIO (Either CurrentLeader Store)
-syncClientRead nodeEventChan (cid, clientRespChan) = do
-  atomically $ writeTChan nodeEventChan $ clientReadReq client0
-  res <- atomically $ readTChan clientRespChan
-  case res of
-    ClientReadResponse (ClientReadResp store) -> pure $ Right store
-    ClientRedirectResponse (ClientRedirResp ldr) -> pure $ Left ldr
+syncClientRead :: NodeId -> RaftTestClientM (Either CurrentLeader Store)
+syncClientRead nid = do
+  eRes <- clientReadFrom nid ClientReadStateMachine
+  case eRes of
+    -- TODO Handle other cases of 'ClientReadResp'
+    Right (ClientReadRespStateMachine store) -> pure $ Right store
+    Left (RaftClientUnexpectedRedirect (ClientRedirResp ldr)) -> pure $ Left ldr
     _ -> panic "Failed to recieve valid read response"
 
-syncClientWrite :: TestEventChan -> (ClientId, TestClientRespChan) -> StoreCmd -> ConcIO (Either CurrentLeader Index)
-syncClientWrite nodeEventChan (cid, clientRespChan) cmd = do
-  atomically $ writeTChan nodeEventChan (clientWriteReq cid cmd)
-  res <- atomically $ readTChan clientRespChan
-  case res of
-    ClientWriteResponse (ClientWriteResp idx) -> do
-      heartbeat nodeEventChan
+syncClientWrite
+  :: NodeId
+  -> StoreCmd
+  -> RaftTestClientM (Either CurrentLeader Index)
+syncClientWrite nid cmd = do
+  eRes <- clientWriteTo nid cmd
+  case eRes of
+    Right (ClientWriteResp idx sn) -> do
+      Just nodeEventChan <- lift (asks (Map.lookup nid . testClientEnvNodeEventChans))
       pure $ Right idx
-    ClientRedirectResponse (ClientRedirResp ldr) -> pure $ Left ldr
+    Left (RaftClientUnexpectedRedirect (ClientRedirResp ldr)) -> pure $ Left ldr
     _ -> panic "Failed to receive client write response..."
 
 heartbeat :: TestEventChan -> ConcIO ()
-heartbeat eventChan = atomically $ writeTChan eventChan (TimeoutEvent HeartbeatTimeout)
+heartbeat eventChan = do
+  sysTime <- liftIO getSystemTime
+  atomically $ writeTChan eventChan (TimeoutEvent sysTime HeartbeatTimeout)
 
 clientReadReq :: ClientId -> Event StoreCmd
-clientReadReq cid = MessageEvent $ ClientRequestEvent $ ClientRequest cid ClientReadReq
+clientReadReq cid = MessageEvent $ ClientRequestEvent $ ClientRequest cid (ClientReadReq ClientReadStateMachine)
 
-clientWriteReq :: ClientId -> StoreCmd -> Event StoreCmd
-clientWriteReq cid v = MessageEvent $ ClientRequestEvent $ ClientRequest cid $ ClientWriteReq v
+clientReadRespChan :: RaftTestClientM (ClientResponse Store StoreCmd)
+clientReadRespChan = do
+  clientRespChan <- lift (asks testClientEnvRespChan)
+  lift $ lift $ atomically $ readTChan clientRespChan
diff --git a/test/TestRaft.hs b/test/TestRaft.hs
deleted file mode 100644
--- a/test/TestRaft.hs
+++ /dev/null
@@ -1,573 +0,0 @@
-{-# LANGUAGE RecordWildCards #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE TupleSections #-}
-{-# LANGUAGE GADTs #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE DeriveGeneric #-}
-{-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-
-module TestRaft where
-
-import Protolude
-import qualified Data.Sequence as Seq
-import Data.Sequence (Seq(..), (|>))
-import qualified Data.Map as Map
-import qualified Data.Set as Set
-import qualified Data.Serialize as S
-import Numeric.Natural
-import Control.Monad.Conc.Class (throw)
-
-import qualified Test.Tasty.HUnit as HUnit
-
-import TestUtils
-
-import Raft hiding (sendClient)
-import Raft.Logging (logMsgToText, logMsgData, logMsgNodeId, LogMsg)
-import Raft.Action
-import Raft.Handle
-import Raft.Log
-import Raft.Monad
-import Raft.Types
-import Raft.RPC
-
-------------------------------
--- State Machine & Commands --
-------------------------------
-
-type Var = ByteString
-
-data StoreCmd
-  = Set Var Natural
-  | Incr Var
-  deriving (Show, Generic)
-
-instance S.Serialize StoreCmd
-
-type Store = Map Var Natural
-
-instance RSMP Store StoreCmd where
-  data RSMPError Store StoreCmd = StoreError Text deriving (Show)
-  type RSMPCtx Store StoreCmd = ()
-  applyCmdRSMP _ store cmd =
-    Right $ case cmd of
-      Set x n -> Map.insert x n store
-      Incr x -> Map.adjust succ x store
-
-testVar :: Var
-testVar = "test"
-
-testInitVal :: Natural
-testInitVal = 1
-
-testSetCmd :: StoreCmd
-testSetCmd = Set testVar testInitVal
-
-testIncrCmd :: StoreCmd
-testIncrCmd = Incr testVar
-
---------------------
--- Scenario Monad --
---------------------
-
-type ClientResps = Map ClientId (Seq (ClientResponse Store))
-
-data TestState = TestState
-  { testNodeIds :: NodeIds
-  , testNodeLogs :: Map NodeId (Entries StoreCmd)
-  , testNodeSMs :: Map NodeId Store
-  , testNodeRaftStates :: Map NodeId RaftNodeState
-  , testNodePersistentStates :: Map NodeId PersistentState
-  , testNodeConfigs :: Map NodeId NodeConfig
-  , testClientResps :: ClientResps
-  } deriving (Show)
-
-type Scenario v = StateT TestState IO v
-
--- | Run scenario monad with initial state
-runScenario :: Scenario () -> IO ()
-runScenario scenario = do
-  let initPersistentState = PersistentState term0 Nothing
-  let initTestState = TestState
-                    { testNodeIds = nodeIds
-                    , testNodeLogs = Map.fromList $ (, mempty) <$> Set.toList nodeIds
-                    , testNodeSMs = Map.fromList $ (, mempty) <$> Set.toList nodeIds
-                    , testNodeRaftStates = Map.fromList $ (, initRaftNodeState) <$> Set.toList nodeIds
-                    , testNodePersistentStates = Map.fromList $ (, initPersistentState) <$> Set.toList nodeIds
-                    , testNodeConfigs = Map.fromList $ zip (Set.toList nodeIds) testConfigs
-                    , testClientResps = Map.fromList [(client0, mempty)]
-                    }
-
-  evalStateT scenario initTestState
-
-updateStateMachine :: NodeId -> Store -> Scenario ()
-updateStateMachine nodeId sm
-  = modify $ \testState@TestState{..}
-      -> testState
-          { testNodeSMs = Map.insert nodeId sm testNodeSMs
-          }
-
-updatePersistentState :: NodeId -> PersistentState -> Scenario ()
-updatePersistentState nodeId persistentState
-  = modify $ \testState@TestState{..}
-      -> testState
-          { testNodePersistentStates = Map.insert nodeId persistentState testNodePersistentStates
-          }
-
-updateRaftNodeState :: NodeId -> RaftNodeState -> Scenario ()
-updateRaftNodeState nodeId raftState
-  = modify $ \testState@TestState{..}
-      -> testState
-          { testNodeRaftStates = Map.insert nodeId raftState testNodeRaftStates
-          }
-
-getNodeInfo :: NodeId -> Scenario (NodeConfig, Store, RaftNodeState, PersistentState)
-getNodeInfo nId = do
-  nodeConfigs <- gets testNodeConfigs
-  nodeSMs <- gets testNodeSMs
-  nodeRaftStates <- gets testNodeRaftStates
-  nodePersistentStates <- gets testNodePersistentStates
-  let Just nodeInfo = Map.lookup nId nodeConfigs >>= \config ->
-                  Map.lookup nId nodeSMs >>= \store ->
-                  Map.lookup nId nodeRaftStates >>= \raftState ->
-                  Map.lookup nId nodePersistentStates >>= \persistentState ->
-                  pure (config, store, raftState, persistentState)
-  pure nodeInfo
-
-
-lookupClientResps :: ClientId -> ClientResps -> Seq (ClientResponse Store)
-lookupClientResps clientId cResps =
-  case Map.lookup clientId cResps of
-    Nothing -> panic "Client id not found"
-    Just resps -> resps
-
-lookupLastClientResp :: ClientId -> ClientResps -> ClientResponse Store
-lookupLastClientResp clientId cResps = r
-  where
-    (_ :|> r) = lookupClientResps clientId cResps
-
-sendClient :: ClientId -> ClientResponse Store -> Scenario ()
-sendClient clientId resp = do
-  cResps <- gets testClientResps
-  let resps = lookupClientResps clientId cResps
-  modify (\st -> st { testClientResps = Map.insert clientId (resps |> resp) (testClientResps st) })
-
--------------------
--- Log instances --
--------------------
-
-newtype NodeEnvError = NodeEnvError Text
-  deriving (Show)
-
-instance Exception NodeEnvError
-
-type RTLog = ReaderT NodeId (StateT TestState IO)
-
-instance RaftWriteLog RTLog StoreCmd where
-  type RaftWriteLogError RTLog = NodeEnvError
-  writeLogEntries newEntries = do
-    nid <- ask
-    Just log <- Map.lookup nid <$> gets testNodeLogs
-    fmap Right $ modify $ \testState@TestState{..} ->
-      testState { testNodeLogs = Map.insert nid (log Seq.>< newEntries) testNodeLogs }
-
-instance RaftReadLog RTLog StoreCmd where
-  type RaftReadLogError RTLog = NodeEnvError
-  readLogEntry (Index idx) = do
-    nid <- ask
-    Just log <- Map.lookup nid <$> gets testNodeLogs
-    case log Seq.!? fromIntegral (if idx == 0 then 0 else idx - 1) of
-      Nothing -> pure (Right Nothing)
-      Just e -> pure (Right (Just e))
-  readLastLogEntry = do
-    nid <- ask
-    Just log <- Map.lookup nid <$> gets testNodeLogs
-    case log of
-      Seq.Empty -> pure (Right Nothing)
-      (_ Seq.:|> e) -> pure (Right (Just e))
-
-instance RaftDeleteLog RTLog StoreCmd where
-  type RaftDeleteLogError RTLog = NodeEnvError
-  deleteLogEntriesFrom idx = do
-    nid <- ask
-    Just log <- Map.lookup nid <$> gets testNodeLogs
-    fmap (const (Right DeleteSuccess)) $ modify $ \testState@TestState{..} ->
-      testState { testNodeLogs = Map.insert nid (Seq.dropWhileR ((>= idx) . entryIndex) log) testNodeLogs }
-
--------------------------------
--- Handle actions and events --
--------------------------------
-
-testHandleLogs :: Maybe [NodeId] -> (Text -> IO ()) -> [LogMsg] -> Scenario ()
-testHandleLogs nIdsM f logs = liftIO $
-  case nIdsM of
-    Nothing -> mapM_ (f . logMsgToText) logs
-    Just nIds ->
-      mapM_ (f . logMsgToText) $ flip filter logs $ \log ->
-        logMsgNodeId (logMsgData log) `elem` nIds
-
-testHandleActions :: NodeId -> [Action Store StoreCmd] -> Scenario ()
-testHandleActions sender =
-  mapM_ (testHandleAction sender)
-
-testHandleAction :: NodeId -> Action Store StoreCmd -> Scenario ()
-testHandleAction sender action = do
-  case action of
-    SendRPC nId rpcAction -> do
-      msg <- mkRPCfromSendRPCAction sender rpcAction
-      testHandleEvent nId (MessageEvent (RPCMessageEvent msg))
-    SendRPCs msgs ->
-      mapM_ (\(nId, rpcAction) -> do
-          msg <- mkRPCfromSendRPCAction sender rpcAction
-          testHandleEvent nId (MessageEvent (RPCMessageEvent msg))
-        ) (Map.toList msgs)
-    BroadcastRPC nIds rpcAction -> mapM_ (\nId -> do
-      msg <- mkRPCfromSendRPCAction sender rpcAction
-      testHandleEvent nId (MessageEvent (RPCMessageEvent msg))) nIds
-    RespondToClient clientId resp -> sendClient clientId resp
-    ResetTimeoutTimer _ -> noop
-    AppendLogEntries entries -> do
-      runReaderT (updateLog entries) sender
-      modify $ \testState@TestState{..}
-        -> case Map.lookup sender testNodeRaftStates of
-            Nothing -> panic "No NodeState"
-            Just (RaftNodeState ns) -> testState
-              { testNodeRaftStates = Map.insert sender (RaftNodeState (setLastLogEntryData ns entries)) testNodeRaftStates
-              }
-    where
-      noop = pure ()
-
-      mkRPCfromSendRPCAction
-        :: NodeId -> SendRPCAction StoreCmd -> Scenario (RPCMessage StoreCmd)
-      mkRPCfromSendRPCAction nId sendRPCAction = do
-        sc <- get
-        (nodeConfig, _, raftState@(RaftNodeState ns), _) <- getNodeInfo nId
-        RPCMessage (configNodeId nodeConfig) <$>
-          case sendRPCAction of
-            SendAppendEntriesRPC aeData -> do
-              (entries, prevLogIndex, prevLogTerm, aeReadReq) <-
-                case aedEntriesSpec aeData of
-                  FromIndex idx -> do
-                    eLogEntries <- runReaderT (readLogEntriesFrom (decrIndexWithDefault0 idx)) nId
-                    case eLogEntries of
-                      Left err -> throw err
-                      Right log ->
-                        case log of
-                          pe :<| entries@(e :<| _)
-                            | idx == 1 -> pure (log, index0, term0, Nothing)
-                            | otherwise -> pure (entries, entryIndex pe, entryTerm pe, Nothing)
-                          _ -> pure (log, index0, term0, Nothing)
-                  FromClientWriteReq e -> prevEntryData nId e
-                  FromNewLeader e -> prevEntryData nId e
-                  NoEntries spec -> do
-                    let readReq' =
-                          case spec of
-                            FromClientReadReq n -> Just n
-                            _ -> Nothing
-                        (lastLogIndex, lastLogTerm) = getLastLogEntryData ns
-                    pure (Empty, lastLogIndex, lastLogTerm, readReq')
-              let leaderId = LeaderId (configNodeId nodeConfig)
-              pure . toRPC $
-                AppendEntries
-                  { aeTerm = aedTerm aeData
-                  , aeLeaderId = leaderId
-                  , aePrevLogIndex = prevLogIndex
-                  , aePrevLogTerm = prevLogTerm
-                  , aeEntries = entries
-                  , aeLeaderCommit = aedLeaderCommit aeData
-                  , aeReadRequest = aeReadReq
-                  }
-            SendAppendEntriesResponseRPC aer -> do
-              pure (toRPC aer)
-            SendRequestVoteRPC rv -> pure (toRPC rv)
-            SendRequestVoteResponseRPC rvr -> pure (toRPC rvr)
-
-      prevEntryData nId e = do
-        (x,y,z) <- prevEntryData' nId e
-        pure (x,y,z,Nothing)
-
-      prevEntryData' nId e
-        | entryIndex e == Index 1 = pure (Seq.singleton e, index0, term0)
-        | otherwise = do
-            eLogEntry <- runReaderT (readLogEntry (decrIndexWithDefault0 (entryIndex e))) nId
-            case eLogEntry of
-              Left err -> throw err
-              Right Nothing -> pure (Seq.singleton e, index0, term0)
-              Right (Just (prevEntry :: Entry StoreCmd)) ->
-                pure (Seq.singleton e, entryIndex prevEntry, entryTerm prevEntry)
-
-testHandleEvent :: NodeId -> Event StoreCmd -> Scenario ()
-testHandleEvent nodeId event = do
-  (nodeConfig, sm, raftState', persistentState) <- getNodeInfo nodeId
-  raftState <- loadLogEntryTermAtAePrevLogIndex raftState'
-  let transitionEnv = TransitionEnv nodeConfig sm raftState
-  let (newRaftState, newPersistentState, actions, logMsgs) = handleEvent raftState transitionEnv persistentState event
-  updatePersistentState nodeId newPersistentState
-  updateRaftNodeState nodeId newRaftState
-  testHandleActions nodeId actions
-  testHandleLogs Nothing (const $ pure ()) logMsgs
-  applyLogEntries nodeId sm
-  where
-    applyLogEntries
-      :: NodeId
-      -> Store
-      -> Scenario ()
-    applyLogEntries nId stateMachine  = do
-        (_, _, raftNodeState@(RaftNodeState nodeState), _) <- getNodeInfo nId
-        let lastAppliedIndex = lastApplied nodeState
-        when (commitIndex nodeState > lastAppliedIndex) $ do
-          let resNodeState = incrLastApplied nodeState
-          modify $ \testState@TestState{..} -> testState {
-              testNodeRaftStates = Map.insert nId (RaftNodeState resNodeState) testNodeRaftStates }
-          let newLastAppliedIndex = lastApplied resNodeState
-          eLogEntry <- runReaderT (readLogEntry newLastAppliedIndex) nId
-          case eLogEntry of
-            Left err -> throw err
-            Right Nothing -> panic "No log entry at 'newLastAppliedIndex'"
-            Right (Just logEntry) -> do
-              case entryValue logEntry of
-                NoValue -> applyLogEntries nId stateMachine
-                EntryValue v -> do
-                  let Right newStateMachine = applyCmdRSMP () stateMachine v
-                  updateStateMachine nId newStateMachine
-                  applyLogEntries nId newStateMachine
-
-      where
-        incrLastApplied :: NodeState ns -> NodeState ns
-        incrLastApplied nodeState =
-          case nodeState of
-            NodeFollowerState fs ->
-              let lastApplied' = incrIndex (fsLastApplied fs)
-               in NodeFollowerState $ fs { fsLastApplied = lastApplied' }
-            NodeCandidateState cs ->
-              let lastApplied' = incrIndex (csLastApplied cs)
-               in  NodeCandidateState $ cs { csLastApplied = lastApplied' }
-            NodeLeaderState ls ->
-              let lastApplied' = incrIndex (lsLastApplied ls)
-               in NodeLeaderState $ ls { lsLastApplied = lastApplied' }
-
-        lastApplied :: NodeState ns -> Index
-        lastApplied = fst . getLastAppliedAndCommitIndex
-
-        commitIndex :: NodeState ns -> Index
-        commitIndex = snd . getLastAppliedAndCommitIndex
-
-    -- In the case that a node is a follower receiving an AppendEntriesRPC
-    -- Event, read the log at the aePrevLogIndex
-    loadLogEntryTermAtAePrevLogIndex :: RaftNodeState -> Scenario RaftNodeState
-    loadLogEntryTermAtAePrevLogIndex (RaftNodeState rns) =
-      case event of
-        MessageEvent (RPCMessageEvent (RPCMessage _ (AppendEntriesRPC ae))) -> do
-          case rns of
-            NodeFollowerState fs -> do
-              eEntry <- runReaderT (readLogEntry (aePrevLogIndex ae)) nodeId
-              case eEntry of
-                Left err -> throw err
-                Right (mEntry :: Maybe (Entry StoreCmd)) ->
-                  pure $ RaftNodeState $ NodeFollowerState fs
-                    { fsTermAtAEPrevIndex = entryTerm <$> mEntry }
-            _ -> pure (RaftNodeState rns)
-        _ -> pure (RaftNodeState rns)
-
-testHeartbeat :: NodeId -> Scenario ()
-testHeartbeat sender = do
-  nodeRaftStates <- gets testNodeRaftStates
-  nodePersistentStates <- gets testNodePersistentStates
-  nIds <- gets testNodeIds
-  let Just raftState = Map.lookup sender nodeRaftStates
-      Just persistentState = Map.lookup sender nodePersistentStates
-  unless (isRaftLeader raftState) $ panic $ toS (show sender ++ " must a be a leader to heartbeat")
-  let LeaderState{..} = getInnerLeaderState raftState
-  let aeData = AppendEntriesData
-                        { aedTerm = currentTerm persistentState
-                        , aedEntriesSpec = NoEntries FromHeartbeat
-                        , aedLeaderCommit = lsCommitIndex
-                        }
-
-  -- Broadcast AppendEntriesRPC
-  testHandleAction sender
-    (BroadcastRPC (Set.filter (sender /=) nIds) (SendAppendEntriesRPC aeData))
-  where
-    getInnerLeaderState :: RaftNodeState -> LeaderState
-    getInnerLeaderState nodeState = case nodeState of
-      (RaftNodeState (NodeLeaderState leaderState)) -> leaderState
-      _ -> panic "Node must be a leader to access its leader state"
-
-
-----------------------
--- Test raft events --
-----------------------
-
-testInitLeader :: NodeId -> Scenario ()
-testInitLeader nId =
-  testHandleEvent nId (TimeoutEvent ElectionTimeout)
-
-testClientReadRequest :: NodeId -> Scenario ()
-testClientReadRequest nId =
-  testHandleEvent nId (MessageEvent
-        (ClientRequestEvent
-          (ClientRequest client0 ClientReadReq)))
-
-testClientWriteRequest :: StoreCmd -> NodeId -> Scenario ()
-testClientWriteRequest cmd nId =
-  testHandleEvent nId (MessageEvent
-        (ClientRequestEvent
-          (ClientRequest client0 (ClientWriteReq cmd))))
-
-----------------
--- Unit tests --
-----------------
-
--- When the protocol starts, every node is a follower
-unit_init_protocol :: IO ()
-unit_init_protocol = runScenario $ do
-  -- Node 0 becomes the leader
-  testInitLeader node0
-
-  raftStates <- gets testNodeRaftStates
-
-  -- Node0 has become leader and other nodes are followers
-  liftIO $ assertLeader raftStates [(node0, NoLeader), (node1, CurrentLeader (LeaderId node0)), (node2, CurrentLeader (LeaderId node0))]
-  liftIO $ assertNodeState raftStates [(node0, isRaftLeader), (node1, isRaftFollower), (node2, isRaftFollower)]
-
-unit_append_entries_client_request :: IO ()
-unit_append_entries_client_request = runScenario $ do
-
-  testInitLeader node0
-
-  raftStates0 <- gets testNodeRaftStates
-  sms0 <- gets testNodeSMs
-  logs0 <- gets testNodeLogs
-
-  liftIO $ assertPersistedLogs logs0 [(node0, 1), (node1, 1), (node2, 1)]
-  liftIO $ assertCommittedLogIndex raftStates0 [(node0, Index 1), (node1, Index 0), (node2, Index 0)]
-  liftIO $ assertAppliedLogIndex raftStates0 [(node0, Index 1), (node1, Index 0), (node2, Index 0)]
-  liftIO $ assertSMs sms0 [(node0, mempty), (node1, mempty), (node2, mempty)]
-
-  testClientWriteRequest testSetCmd node0
-
-  raftStates1 <- gets testNodeRaftStates
-  sms1 <- gets testNodeSMs
-  logs1 <- gets testNodeLogs
-
-  liftIO $ assertPersistedLogs logs1 [(node0, 2), (node1, 2), (node2, 2)]
-  liftIO $ assertCommittedLogIndex raftStates1 [(node0, Index 2), (node1, Index 1), (node2, Index 1)]
-  liftIO $ assertAppliedLogIndex raftStates1 [(node0, Index 2), (node1, Index 1), (node2, Index 1)]
-  liftIO $ assertSMs sms1 [(node0, Map.fromList [(testVar, testInitVal)]), (node1, mempty), (node2, mempty)]
-
-  ---------------------------- HEARTBEAT 1 ------------------------------
-  -- After leader heartbeats, followers commit and apply leader's entries
-  testHeartbeat node0
-
-  raftStates2 <- gets testNodeRaftStates
-  sms2 <- gets testNodeSMs
-  logs2 <- gets testNodeLogs
-
-  liftIO $ assertPersistedLogs logs2 [(node0, 2), (node1, 2), (node2, 2)]
-  liftIO $ assertCommittedLogIndex raftStates2 [(node0, Index 2), (node1, Index 2), (node2, Index 2)]
-  liftIO $ assertAppliedLogIndex raftStates2 [(node0, Index 2), (node1, Index 2), (node2, Index 2)]
-  liftIO $ assertSMs sms2 [(node0, Map.fromList [(testVar, testInitVal)]), (node1, Map.fromList [(testVar, testInitVal)]), (node2, Map.fromList [(testVar, testInitVal)])]
-
-
-
-unit_incr_value :: IO ()
-unit_incr_value = runScenario $ do
-  testInitLeader node0
-  testClientWriteRequest testSetCmd node0
-  testClientWriteRequest testIncrCmd node0
-
-  testHeartbeat node0
-
-  sms <- gets testNodeSMs
-  liftIO $ assertSMs sms [(node0, Map.fromList [(testVar, succ testInitVal)]), (node1, Map.fromList [(testVar, succ testInitVal)]), (node2, Map.fromList [(testVar, succ testInitVal)])]
-
-
-unit_mult_incr_value :: IO ()
-unit_mult_incr_value = runScenario $ do
-  testInitLeader node0
-  testClientWriteRequest testSetCmd node0
-  let reps = 10
-  replicateM_ (fromIntegral 10) (testClientWriteRequest testIncrCmd node0)
-  testHeartbeat node0
-
-  sms <- gets testNodeSMs
-  liftIO $ assertSMs sms [(node0, Map.fromList [(testVar, testInitVal + reps)]), (node1, Map.fromList [(testVar, testInitVal + reps)]), (node2, Map.fromList [(testVar, testInitVal + reps)])]
-
-unit_client_req_no_leader :: IO ()
-unit_client_req_no_leader = runScenario $ do
-  testClientWriteRequest testSetCmd node1
-  cResps <- gets testClientResps
-  let ClientRedirectResponse (ClientRedirResp lResp) = lookupLastClientResp client0 cResps
-  liftIO $ HUnit.assertBool "A follower should return a NoLeader response" (lResp == NoLeader)
-
-unit_redirect_leader :: IO ()
-unit_redirect_leader = runScenario $ do
-  testInitLeader node0
-  testClientWriteRequest testSetCmd node1
-  cResps <- gets testClientResps
-  let ClientRedirectResponse (ClientRedirResp (CurrentLeader (LeaderId lResp))) = lookupLastClientResp client0 cResps
-  liftIO $ HUnit.assertBool "A follower should point to the current leader" (lResp == node0)
-
-unit_client_read_response :: IO ()
-unit_client_read_response = runScenario $ do
-  testInitLeader node0
-  testClientWriteRequest testSetCmd node0
-  testClientReadRequest node0
-  cResps <- gets testClientResps
-  let ClientReadResponse (ClientReadResp store) = lookupLastClientResp client0 cResps
-  liftIO $ HUnit.assertBool "A client should receive the current state of the store"
-    (store == Map.fromList [(testVar, testInitVal)])
-
-unit_client_write_response :: IO ()
-unit_client_write_response = runScenario $ do
-  testInitLeader node0
-  testClientReadRequest node0
-  testClientWriteRequest testSetCmd node0
-  cResps <- gets testClientResps
-  let ClientWriteResponse (ClientWriteResp idx) = lookupLastClientResp client0 cResps
-  liftIO $ HUnit.assertBool "A client should receive an aknowledgement of a writing request"
-    (idx == Index 2)
-
-unit_new_leader :: IO ()
-unit_new_leader = runScenario $ do
-  testInitLeader node0
-  testHandleEvent node1 (TimeoutEvent ElectionTimeout)
-  raftStates <- gets testNodeRaftStates
-
-  liftIO $ assertNodeState raftStates [(node0, isRaftFollower), (node1, isRaftLeader), (node2, isRaftFollower)]
-  liftIO $ assertLeader raftStates [(node0, CurrentLeader (LeaderId node1)), (node1, NoLeader), (node2, CurrentLeader (LeaderId node1))]
-
-------------------
--- Assert utils --
-------------------
-
-assertNodeState :: Map NodeId RaftNodeState -> [(NodeId, RaftNodeState -> Bool)] -> IO ()
-assertNodeState raftNodeStates =
-  mapM_ (\(nId, isNodeState) -> HUnit.assertBool (show nId ++ " should be in a different state")
-    (maybe False isNodeState (Map.lookup nId raftNodeStates)))
-
-assertLeader :: Map NodeId RaftNodeState -> [(NodeId, CurrentLeader)] -> IO ()
-assertLeader raftNodeStates =
-  mapM_ (\(nId, leader) -> HUnit.assertBool (show nId ++ " should recognize " ++ show leader ++ " as its leader")
-    (maybe False ((== leader) . checkCurrentLeader) (Map.lookup nId raftNodeStates)))
-
-assertCommittedLogIndex :: Map NodeId RaftNodeState -> [(NodeId, Index)] -> IO ()
-assertCommittedLogIndex raftNodeStates =
-  mapM_ (\(nId, idx) -> HUnit.assertBool (show nId ++ " should have " ++ show idx ++ " as its last committed index")
-    (maybe False ((== idx) . getCommittedLogIndex) (Map.lookup nId raftNodeStates)))
-
-assertAppliedLogIndex :: Map NodeId RaftNodeState -> [(NodeId, Index)] -> IO ()
-assertAppliedLogIndex raftNodeStates =
-  mapM_ (\(nId, idx) -> HUnit.assertBool (show nId ++ " should have " ++ show idx ++ " as its last applied index")
-    (maybe False ((== idx) . getLastAppliedLog) (Map.lookup nId raftNodeStates)))
-
-assertPersistedLogs :: Map NodeId (Entries v) -> [(NodeId, Int)] -> IO ()
-assertPersistedLogs persistedLogs =
-  mapM_ (\(nId, len) -> HUnit.assertBool (show nId ++ " should have appended " ++ show len ++ " logs")
-    (maybe False ((== len) . Seq.length) (Map.lookup nId persistedLogs)))
-
-assertSMs :: Map NodeId Store -> [(NodeId, Store)] -> IO ()
-assertSMs sms =
-  mapM_ (\(nId, sm) -> HUnit.assertBool (show nId ++ " state machine " ++ show sm ++ " is not valid")
-    (maybe False (== sm) (Map.lookup nId sms)))
diff --git a/test/TestUtils.hs b/test/TestUtils.hs
--- a/test/TestUtils.hs
+++ b/test/TestUtils.hs
@@ -8,27 +8,28 @@
 import qualified Data.Map.Merge.Lazy as Merge
 
 import Raft
+import Raft.Config
 
-isRaftLeader :: RaftNodeState -> Bool
+isRaftLeader :: RaftNodeState v -> Bool
 isRaftLeader (RaftNodeState rns) = isLeader rns
 
-isRaftCandidate :: RaftNodeState -> Bool
+isRaftCandidate :: RaftNodeState v -> Bool
 isRaftCandidate (RaftNodeState rns) = isCandidate rns
 
-isRaftFollower :: RaftNodeState -> Bool
+isRaftFollower :: RaftNodeState v -> Bool
 isRaftFollower (RaftNodeState rns) = isFollower rns
 
-checkCurrentLeader :: RaftNodeState -> CurrentLeader
+checkCurrentLeader :: RaftNodeState v -> CurrentLeader
 checkCurrentLeader (RaftNodeState (NodeFollowerState FollowerState{..})) = fsCurrentLeader
 checkCurrentLeader (RaftNodeState (NodeCandidateState _)) = NoLeader
 checkCurrentLeader (RaftNodeState (NodeLeaderState _)) = NoLeader
 
-getLastAppliedLog :: RaftNodeState -> Index
+getLastAppliedLog :: RaftNodeState v -> Index
 getLastAppliedLog (RaftNodeState (NodeFollowerState FollowerState{..})) = fsLastApplied
 getLastAppliedLog (RaftNodeState (NodeCandidateState CandidateState{..})) = csLastApplied
 getLastAppliedLog (RaftNodeState (NodeLeaderState LeaderState{..})) = lsLastApplied
 
-getCommittedLogIndex :: RaftNodeState -> Index
+getCommittedLogIndex :: RaftNodeState v -> Index
 getCommittedLogIndex (RaftNodeState (NodeFollowerState FollowerState{..})) = fsCommitIndex
 getCommittedLogIndex (RaftNodeState (NodeCandidateState CandidateState{..})) = csCommitIndex
 getCommittedLogIndex (RaftNodeState (NodeLeaderState LeaderState{..})) = lsCommitIndex
@@ -59,18 +60,21 @@
   , configNodeIds = nodeIds
   , configElectionTimeout = pairMsToMicroS (150, 300)
   , configHeartbeatTimeout = msToMicroS 50
+  , configStorageState = New
   }
 testConfig1 = NodeConfig
   { configNodeId = node1
   , configNodeIds = nodeIds
   , configElectionTimeout = pairMsToMicroS (150, 300)
   , configHeartbeatTimeout = msToMicroS 50
+  , configStorageState = New
   }
 testConfig2 = NodeConfig
   { configNodeId = node2
   , configNodeIds = nodeIds
   , configElectionTimeout = pairMsToMicroS (150, 300)
   , configHeartbeatTimeout = msToMicroS 50
+  , configStorageState = New
   }
 
 -- | Zip maps using function. Throws away items left and right
