module Main where
import Control.Concurrent
import Control.Lens
import qualified Data.Binary as B
import qualified Data.Binary.Get as B
import qualified Data.ByteString.Char8 as B
import qualified Data.ByteString.Lazy as BL
import qualified Data.Text as T
import Foundation
import qualified Prelude (show)
import Network.ABCI
data State = State {
stateHashCount ∷ Integer,
stateTxCount ∷ Integer,
stateSerial ∷ Bool
} deriving (Show, Eq)
main ∷ IO ()
main = serve defaultHost defaultPort =<< counter False
handleInfo ∷ MVar State → RequestInfo → IO ResponseInfo
handleInfo state RequestInfo = do
(State hashCount txCount _) ← readMVar state
return ResponseInfo {
_ResponseInfo'data' = T.concat ["{\"hashes\":", T.pack (Prelude.show hashCount), ",\"txs\":", T.pack (Prelude.show txCount), "}"],
_ResponseInfo'version = "0.1.0",
_ResponseInfo'lastBlockHeight = 0,
_ResponseInfo'lastBlockAppHash = B.empty
}
handleEcho ∷ RequestEcho → IO ResponseEcho
handleEcho (RequestEcho e) = return $ ResponseEcho e
handleFlush ∷ RequestFlush → IO ResponseFlush
handleFlush RequestFlush = return ResponseFlush
handleOption ∷ MVar State → RequestSetOption → IO ResponseSetOption
handleOption state (RequestSetOption key val) = do
if key == "serial" && val == "on" then
modifyMVar_ state (\s → return $ s { stateSerial = True })
else return ()
return $ ResponseSetOption ""
handleInitChain ∷ RequestInitChain → IO ResponseInitChain
handleInitChain _ = return ResponseInitChain
handleBeginBlock ∷ RequestBeginBlock → IO ResponseBeginBlock
handleBeginBlock _ = return ResponseBeginBlock
handleCheckTx ∷ MVar State → RequestCheckTx → IO ResponseCheckTx
handleCheckTx state (RequestCheckTx tx) =
case B.runGetOrFail B.getInt64be $ BL.fromStrict $ B.append (B.replicate (8 - B.length tx) '\NUL') tx of
Right (_, _, num) → do
(State _ txCount serial) ← readMVar state
if not serial || fromIntegral num >= txCount then
return $ ResponseCheckTx OK B.empty T.empty
else
return $ ResponseCheckTx BadNonce B.empty invalidNumber
Left _ → return $ ResponseCheckTx EncodingError B.empty undecodable
handleDeliverTx ∷ MVar State → RequestDeliverTx → IO ResponseDeliverTx
handleDeliverTx state (RequestDeliverTx tx) =
case B.runGetOrFail B.getInt64be $ BL.fromStrict $ B.append (B.replicate (8 - B.length tx) '\NUL') tx of
Right (_, _, num) →
modifyMVar state $ \state@(State _ txCount serial) →
if not serial || fromIntegral num == txCount then
return (state { stateTxCount = txCount + 1 }, ResponseDeliverTx OK B.empty T.empty)
else
return (state, ResponseDeliverTx BadNonce B.empty invalidNumber)
Left _ → return $ ResponseDeliverTx EncodingError B.empty undecodable
handleEndBlock ∷ RequestEndBlock → IO ResponseEndBlock
handleEndBlock _ = return $ ResponseEndBlock []
handleCommit ∷ MVar State → RequestCommit → IO ResponseCommit
handleCommit state _ = do
count ← modifyMVar state $ \state@(State hashCount txCount _) → do
let newCount = hashCount + 1
return (state { stateHashCount = newCount }, fromIntegral txCount ∷ Int64)
return $ ResponseCommit OK (if count == 0 then "" else BL.toStrict $ B.encode count) T.empty
handleQuery ∷ MVar State → RequestQuery → IO ResponseQuery
handleQuery state (RequestQuery _ path _ _) =
case path of
"hash" → do
(State hashCount _ _) ← readMVar state
return $ ResponseQuery OK 0 "" (BL.toStrict $ B.encode hashCount) "" 0 ""
"tx" → do
(State _ txCount _) ← readMVar state
return $ ResponseQuery OK 0 "" (BL.toStrict $ B.encode txCount) "" 0 ""
_ → return $ ResponseQuery EncodingError 0 "" "" "" 0 $ T.append "Invalid query path. Expected hash or tx, got " path
counter ∷ Bool → IO (Request → IO Response)
counter serial = do
state ← newMVar $ State 0 0 serial
let respondWith func val = func .~ val $ emptyResponse
return $ \req →
handleCase _Request'info (fmap (respondWith info) . handleInfo state) req $
handleCase _Request'echo (fmap (respondWith echo) . handleEcho) req $
handleCase _Request'flush (fmap (respondWith flush) . handleFlush) req $
handleCase _Request'setOption (fmap (respondWith setOption) . handleOption state) req $
handleCase _Request'initChain (fmap (respondWith initChain) . handleInitChain) req $
handleCase _Request'checkTx (fmap (respondWith checkTx) . handleCheckTx state) req $
handleCase _Request'beginBlock (fmap (respondWith beginBlock) . handleBeginBlock) req $
handleCase _Request'deliverTx (fmap (respondWith deliverTx) . handleDeliverTx state) req $
handleCase _Request'endBlock (fmap (respondWith endBlock) . handleEndBlock) req $
handleCase _Request'commit (fmap (respondWith commit) . handleCommit state) req $
handleCase _Request'query (fmap (respondWith query) . handleQuery state) req $
return $ exception .~ ResponseException unknown $ emptyResponse
flushResponse ∷ Response
flushResponse = flush .~ ResponseFlush $ emptyResponse
handleCase ∷ ∀ a b c m . (a → Maybe b) → (b → m c) → a → m c → m c
handleCase getter func val cont =
case getter val of
Just x → func x
Nothing → cont
emptyResponse ∷ Response
emptyResponse = Response Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing
unknown ∷ T.Text
unknown = "unknown_request"
invalidNumber ∷ T.Text
invalidNumber = "invalid_number"
undecodable ∷ T.Text
undecodable = "undecodable"