haskoin-store-data-0.47.0: src/Haskoin/Store/WebCommon.hs
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TupleSections #-}
module Haskoin.Store.WebCommon
where
import Control.Applicative ((<|>))
import Control.Monad (guard)
import Data.Bytes.Get
import Data.Bytes.Put
import Data.Bytes.Serial
import Data.Default (Default, def)
import Data.Proxy (Proxy (..))
import Data.String (IsString (..))
import Data.String.Conversions (cs)
import Data.Text (Text)
import qualified Data.Text as T
import Haskoin.Address
import Haskoin.Block (Block, BlockHash, blockHashToHex,
hexToBlockHash)
import Haskoin.Constants
import Haskoin.Crypto (Hash256)
import Haskoin.Keys
import qualified Haskoin.Store.Data as Store
import Haskoin.Transaction
import Network.HTTP.Types (StdMethod (..))
import Numeric.Natural (Natural)
import Text.Read (readMaybe)
import qualified Web.Scotty.Trans as Scotty
-------------------
-- API Resources --
-------------------
class Serial b => ApiResource a b | a -> b where
resourceMethod :: Proxy a -> StdMethod
resourceMethod _ = GET
resourcePath :: Proxy a -> ([Text] -> Text)
queryParams :: a -> ([ParamBox], [ParamBox]) -- (resource, querystring)
queryParams _ = ([],[])
captureParams :: Proxy a -> [ProxyBox]
captureParams _ = []
resourceBody :: a -> Maybe PostBox
resourceBody = const Nothing
data PostBox = forall s . Serial s => PostBox !s
data ParamBox = forall p . (Eq p, Param p) => ParamBox !p
data ProxyBox = forall p . Param p => ProxyBox !(Proxy p)
--------------------
-- Resource Paths --
--------------------
-- Blocks
data GetBlock = GetBlock !BlockHash !NoTx
data GetBlocks = GetBlocks ![BlockHash] !NoTx
newtype GetBlockRaw = GetBlockRaw BlockHash
newtype GetBlockBest = GetBlockBest NoTx
data GetBlockBestRaw = GetBlockBestRaw
newtype GetBlockLatest = GetBlockLatest NoTx
data GetBlockHeight = GetBlockHeight !HeightParam !NoTx
data GetBlockHeights = GetBlockHeights !HeightsParam !NoTx
newtype GetBlockHeightRaw = GetBlockHeightRaw HeightParam
data GetBlockTime = GetBlockTime !TimeParam !NoTx
newtype GetBlockTimeRaw = GetBlockTimeRaw TimeParam
data GetBlockMTP = GetBlockMTP !TimeParam !NoTx
newtype GetBlockMTPRaw = GetBlockMTPRaw TimeParam
-- Transactions
newtype GetTx = GetTx TxHash
newtype GetTxs = GetTxs [TxHash]
newtype GetTxRaw = GetTxRaw TxHash
newtype GetTxsRaw = GetTxsRaw [TxHash]
newtype GetTxsBlock = GetTxsBlock BlockHash
newtype GetTxsBlockRaw = GetTxsBlockRaw BlockHash
data GetTxAfter = GetTxAfter !TxHash !HeightParam
newtype PostTx = PostTx Tx
data GetMempool = GetMempool !(Maybe LimitParam) !OffsetParam
data GetEvents = GetEvents
-- Address
data GetAddrTxs = GetAddrTxs !Address !LimitsParam
data GetAddrsTxs = GetAddrsTxs ![Address] !LimitsParam
data GetAddrTxsFull = GetAddrTxsFull !Address !LimitsParam
data GetAddrsTxsFull = GetAddrsTxsFull ![Address] !LimitsParam
newtype GetAddrBalance = GetAddrBalance Address
newtype GetAddrsBalance = GetAddrsBalance [Address]
data GetAddrUnspent = GetAddrUnspent !Address !LimitsParam
data GetAddrsUnspent = GetAddrsUnspent ![Address] !LimitsParam
-- XPubs
data GetXPub = GetXPub !XPubKey !Store.DeriveType !NoCache
data GetXPubTxs = GetXPubTxs !XPubKey !Store.DeriveType !LimitsParam !NoCache
data GetXPubTxsFull = GetXPubTxsFull !XPubKey !Store.DeriveType !LimitsParam !NoCache
data GetXPubBalances = GetXPubBalances !XPubKey !Store.DeriveType !NoCache
data GetXPubUnspent = GetXPubUnspent !XPubKey !Store.DeriveType !LimitsParam !NoCache
-- Network
data GetPeers = GetPeers
data GetHealth = GetHealth
------------
-- Blocks --
------------
instance ApiResource GetBlock Store.BlockData where
resourcePath _ = ("/block/" <:>)
queryParams (GetBlock h t) = ([ParamBox h], noDefBox t)
captureParams _ = [ProxyBox (Proxy :: Proxy BlockHash)]
instance ApiResource GetBlocks [Store.BlockData] where
resourcePath _ _ = "/blocks"
queryParams (GetBlocks hs t) = ([], [ParamBox hs] <> noDefBox t)
instance ApiResource GetBlockRaw (Store.RawResult Block) where
resourcePath _ = "/block/" <+> "/raw"
queryParams (GetBlockRaw h) = ([ParamBox h], [])
captureParams _ = [ProxyBox (Proxy :: Proxy BlockHash)]
instance ApiResource GetBlockBest Store.BlockData where
resourcePath _ _ = "/block/best"
queryParams (GetBlockBest t) = ([], noDefBox t)
instance ApiResource GetBlockBestRaw (Store.RawResult Block) where
resourcePath _ _ = "/block/best/raw"
instance ApiResource GetBlockLatest [Store.BlockData] where
resourcePath _ _ = "/block/latest"
queryParams (GetBlockLatest t) = ([], noDefBox t)
instance ApiResource GetBlockHeight [Store.BlockData] where
resourcePath _ = ("/block/height/" <:>)
queryParams (GetBlockHeight h t) = ([ParamBox h], noDefBox t)
captureParams _ = [ProxyBox (Proxy :: Proxy HeightParam)]
instance ApiResource GetBlockHeights [Store.BlockData] where
resourcePath _ _ = "/block/heights"
queryParams (GetBlockHeights hs t) = ([], [ParamBox hs] <> noDefBox t)
instance ApiResource GetBlockHeightRaw (Store.RawResultList Block) where
resourcePath _ = "/block/height/" <+> "/raw"
queryParams (GetBlockHeightRaw h) = ([ParamBox h], [])
captureParams _ = [ProxyBox (Proxy :: Proxy HeightParam)]
instance ApiResource GetBlockTime Store.BlockData where
resourcePath _ = ("/block/time/" <:>)
queryParams (GetBlockTime u t) = ([ParamBox u], noDefBox t)
captureParams _ = [ProxyBox (Proxy :: Proxy TimeParam)]
instance ApiResource GetBlockTimeRaw (Store.RawResult Block) where
resourcePath _ = "/block/time/" <+> "/raw"
queryParams (GetBlockTimeRaw u) = ([ParamBox u], [])
captureParams _ = [ProxyBox (Proxy :: Proxy TimeParam)]
instance ApiResource GetBlockMTP Store.BlockData where
resourcePath _ = ("/block/mtp/" <:>)
queryParams (GetBlockMTP u t) = ([ParamBox u], noDefBox t)
captureParams _ = [ProxyBox (Proxy :: Proxy TimeParam)]
instance ApiResource GetBlockMTPRaw (Store.RawResult Block) where
resourcePath _ = "/block/mtp/" <+> "/raw"
queryParams (GetBlockMTPRaw u) = ([ParamBox u], [])
captureParams _ = [ProxyBox (Proxy :: Proxy TimeParam)]
------------------
-- Transactions --
------------------
instance ApiResource GetTx Store.Transaction where
resourcePath _ = ("/transaction/" <:>)
queryParams (GetTx h) = ([ParamBox h], [])
captureParams _ = [ProxyBox (Proxy :: Proxy TxHash)]
instance ApiResource GetTxs [Store.Transaction] where
resourcePath _ _ = "/transactions"
queryParams (GetTxs hs) = ([], [ParamBox hs])
instance ApiResource GetTxRaw (Store.RawResult Tx) where
resourcePath _ = "/transaction/" <+> "/raw"
queryParams (GetTxRaw h) = ([ParamBox h], [])
captureParams _ = [ProxyBox (Proxy :: Proxy TxHash)]
instance ApiResource GetTxsRaw (Store.RawResultList Tx) where
resourcePath _ _ = "/transactions/raw"
queryParams (GetTxsRaw hs) = ([], [ParamBox hs])
instance ApiResource GetTxsBlock [Store.Transaction] where
resourcePath _ = ("/transactions/block/" <:>)
queryParams (GetTxsBlock h) = ([ParamBox h], [])
captureParams _ = [ProxyBox (Proxy :: Proxy BlockHash)]
instance ApiResource GetTxsBlockRaw (Store.RawResultList Tx) where
resourcePath _ = "/transactions/block/" <+> "/raw"
queryParams (GetTxsBlockRaw h) = ([ParamBox h], [])
captureParams _ = [ProxyBox (Proxy :: Proxy BlockHash)]
instance ApiResource GetTxAfter (Store.GenericResult (Maybe Bool)) where
resourcePath _ = "/transaction/" <++> "/after/"
queryParams (GetTxAfter h i) = ([ParamBox h, ParamBox i], [])
captureParams _ =
[ ProxyBox (Proxy :: Proxy TxHash)
, ProxyBox (Proxy :: Proxy HeightParam)
]
instance ApiResource PostTx Store.TxId where
resourceMethod _ = POST
resourcePath _ _ = "/transactions"
resourceBody (PostTx tx) = Just $ PostBox tx
instance ApiResource GetMempool [TxHash] where
resourcePath _ _ = "/mempool"
queryParams (GetMempool l o) = ([], noMaybeBox l <> noDefBox o)
instance ApiResource GetEvents [Store.Event] where
resourcePath _ _ = "/events"
-------------
-- Address --
-------------
instance ApiResource GetAddrTxs [Store.TxRef] where
resourcePath _ = "/address/" <+> "/transactions"
queryParams (GetAddrTxs a (LimitsParam l o sM)) =
([ParamBox a], noMaybeBox l <> noDefBox o <> noMaybeBox sM)
captureParams _ = [ProxyBox (Proxy :: Proxy Address)]
instance ApiResource GetAddrsTxs [Store.TxRef] where
resourcePath _ _ = "/address/transactions"
queryParams (GetAddrsTxs as (LimitsParam l o sM)) =
([] , [ParamBox as] <> noMaybeBox l <> noDefBox o <> noMaybeBox sM)
instance ApiResource GetAddrTxsFull [Store.Transaction] where
resourcePath _ = "/address/" <+> "/transactions/full"
queryParams (GetAddrTxsFull a (LimitsParam l o sM)) =
([ParamBox a], noMaybeBox l <> noDefBox o <> noMaybeBox sM)
captureParams _ = [ProxyBox (Proxy :: Proxy Address)]
instance ApiResource GetAddrsTxsFull [Store.Transaction] where
resourcePath _ _ = "/address/transactions/full"
queryParams (GetAddrsTxsFull as (LimitsParam l o sM)) =
([] , [ParamBox as] <> noMaybeBox l <> noDefBox o <> noMaybeBox sM)
instance ApiResource GetAddrBalance Store.Balance where
resourcePath _ = "/address/" <+> "/balance"
queryParams (GetAddrBalance a) = ([ParamBox a], [])
captureParams _ = [ProxyBox (Proxy :: Proxy Address)]
instance ApiResource GetAddrsBalance [Store.Balance] where
resourcePath _ _ = "/address/balances"
queryParams (GetAddrsBalance as) = ([] , [ParamBox as])
instance ApiResource GetAddrUnspent [Store.Unspent] where
resourcePath _ = "/address/" <+> "/unspent"
queryParams (GetAddrUnspent a (LimitsParam l o sM)) =
([ParamBox a], noMaybeBox l <> noDefBox o <> noMaybeBox sM)
captureParams _ = [ProxyBox (Proxy :: Proxy Address)]
instance ApiResource GetAddrsUnspent [Store.Unspent] where
resourcePath _ _ = "/address/unspent"
queryParams (GetAddrsUnspent as (LimitsParam l o sM)) =
([] , [ParamBox as] <> noMaybeBox l <> noDefBox o <> noMaybeBox sM)
-----------
-- XPubs --
-----------
instance ApiResource GetXPub Store.XPubSummary where
resourcePath _ = ("/xpub/" <:>)
queryParams (GetXPub p d n) = ([ParamBox p], noDefBox d <> noDefBox n)
captureParams _ = [ProxyBox (Proxy :: Proxy XPubKey)]
instance ApiResource GetXPubTxs [Store.TxRef] where
resourcePath _ = "/xpub/" <+> "/transactions"
queryParams (GetXPubTxs p d (LimitsParam l o sM) n) =
( [ParamBox p]
, noDefBox d <> noMaybeBox l <> noDefBox o <> noMaybeBox sM <> noDefBox n
)
captureParams _ = [ProxyBox (Proxy :: Proxy XPubKey)]
instance ApiResource GetXPubTxsFull [Store.Transaction] where
resourcePath _ = "/xpub/" <+> "/transactions/full"
queryParams (GetXPubTxsFull p d (LimitsParam l o sM) n) =
( [ParamBox p]
, noDefBox d <> noMaybeBox l <> noDefBox o <> noMaybeBox sM <> noDefBox n
)
captureParams _ = [ProxyBox (Proxy :: Proxy XPubKey)]
instance ApiResource GetXPubBalances [Store.XPubBal] where
resourcePath _ = "/xpub/" <+> "/balances"
queryParams (GetXPubBalances p d n) = ([ParamBox p], noDefBox d <> noDefBox n)
captureParams _ = [ProxyBox (Proxy :: Proxy XPubKey)]
instance ApiResource GetXPubUnspent [Store.XPubUnspent] where
resourcePath _ = "/xpub/" <+> "/unspent"
queryParams (GetXPubUnspent p d (LimitsParam l o sM) n) =
( [ParamBox p]
, noDefBox d <> noMaybeBox l <> noDefBox o <> noMaybeBox sM <> noDefBox n
)
captureParams _ = [ProxyBox (Proxy :: Proxy XPubKey)]
-------------
-- Network --
-------------
instance ApiResource GetPeers [Store.PeerInformation] where
resourcePath _ _ = "/peers"
instance ApiResource GetHealth Store.HealthCheck where
resourcePath _ _ = "/health"
-------------
-- Helpers --
-------------
(<:>) :: Text -> [Text] -> Text
(<:>) = (<+> "")
(<+>) :: Text -> Text -> [Text] -> Text
a <+> b = fill 1 [a, b]
(<++>) :: Text -> Text -> [Text] -> Text
a <++> b = fill 2 [a, b]
fill :: Int -> [Text] -> [Text] -> Text
fill i a b
| length b /= i = error "Invalid query parameters"
| otherwise = mconcat $ uncurry (<>) <$> zip a (b <> repeat "")
noDefBox :: (Default p, Param p, Eq p) => p -> [ParamBox]
noDefBox p = [ParamBox p | p /= def]
noMaybeBox :: (Param p, Eq p) => Maybe p -> [ParamBox]
noMaybeBox (Just p) = [ParamBox p]
noMaybeBox _ = []
asProxy :: a -> Proxy a
asProxy = const Proxy
queryPath :: ApiResource a b => Network -> a -> Text
queryPath net a = f $ encParam <$> fst (queryParams a)
where
f = resourcePath $ asProxy a
encParam (ParamBox p) =
case encodeParam net p of
Just [res] -> res
_ -> error "Invalid query param"
capturePath :: ApiResource a b => Proxy a -> Scotty.RoutePattern
capturePath proxy =
fromString $ cs $ f $ toLabel <$> captureParams proxy
where
f = resourcePath proxy
toLabel (ProxyBox p) = ":" <> proxyLabel p
paramLabel :: Param p => p -> Text
paramLabel = proxyLabel . asProxy
-------------
-- Options --
-------------
class Param a where
proxyLabel :: Proxy a -> Text
encodeParam :: Network -> a -> Maybe [Text]
parseParam :: Network -> [Text] -> Maybe a
instance Param Address where
proxyLabel = const "address"
encodeParam net a = (:[]) <$> addrToText net a
parseParam net [a] = textToAddr net a
parseParam _ _ = Nothing
instance Param [Address] where
proxyLabel = const "addresses"
encodeParam = mapM . addrToText
parseParam = mapM . textToAddr
data StartParam = StartParamHash
{ startParamHash :: Hash256
}
| StartParamHeight
{ startParamHeight :: Natural
}
| StartParamTime
{ startParamTime :: Store.UnixTime
}
deriving (Eq, Show)
instance Param StartParam where
proxyLabel = const "height"
encodeParam _ p =
case p of
StartParamHash h -> return [txHashToHex (TxHash h)]
StartParamHeight h -> do
guard $ h <= 1230768000
return [cs $ show h]
StartParamTime t -> do
guard $ t > 1230768000
return [cs $ show t]
parseParam _ [s] = parseHash <|> parseHeight <|> parseUnix
where
parseHash = do
guard (T.length s == 32 * 2)
TxHash x <- hexToTxHash s
return $ StartParamHash x
parseHeight = do
x <- readMaybe $ cs s
guard $ x <= 1230768000
return $ StartParamHeight x
parseUnix = do
x <- readMaybe $ cs s
guard $ x > 1230768000
return $ StartParamTime x
parseParam _ _ = Nothing
newtype OffsetParam = OffsetParam
{ getOffsetParam :: Natural
} deriving (Eq, Show, Read, Enum, Ord, Num, Real, Integral)
instance Default OffsetParam where
def = OffsetParam 0
instance Param OffsetParam where
proxyLabel = const "offset"
encodeParam _ (OffsetParam o) = Just [cs $ show o]
parseParam _ [s] = OffsetParam <$> readMaybe (cs s)
parseParam _ _ = Nothing
newtype LimitParam = LimitParam
{ getLimitParam :: Natural
} deriving (Eq, Show, Read, Enum, Ord, Num, Real, Integral)
instance Param LimitParam where
proxyLabel = const "limit"
encodeParam _ (LimitParam l) = Just [cs $ show l]
parseParam _ [s] = LimitParam <$> readMaybe (cs s)
parseParam _ _ = Nothing
data LimitsParam =
LimitsParam
{ paramLimit :: Maybe LimitParam -- 0 means maximum
, paramOffset :: OffsetParam
, paramStart :: Maybe StartParam
}
deriving (Eq, Show)
instance Default LimitsParam where
def = LimitsParam Nothing def Nothing
newtype HeightParam = HeightParam
{ getHeightParam :: Natural
} deriving (Eq, Show, Read, Enum, Ord, Num, Real, Integral)
instance Param HeightParam where
proxyLabel = const "height"
encodeParam _ (HeightParam h) = Just [cs $ show h]
parseParam _ [s] = HeightParam <$> readMaybe (cs s)
parseParam _ _ = Nothing
newtype HeightsParam = HeightsParam
{ getHeightsParam :: [Natural]
} deriving (Eq, Show, Read)
instance Param HeightsParam where
proxyLabel = const "heights"
encodeParam _ (HeightsParam hs) = Just $ cs . show <$> hs
parseParam _ xs = HeightsParam <$> mapM (readMaybe . cs) xs
newtype TimeParam = TimeParam
{ getTimeParam :: Store.UnixTime
} deriving (Eq, Show, Read, Enum, Ord, Num, Real, Integral)
instance Param TimeParam where
proxyLabel = const "time"
encodeParam _ (TimeParam t) = Just [cs $ show t]
parseParam _ [s] = TimeParam <$> readMaybe (cs s)
parseParam _ _ = Nothing
instance Param XPubKey where
proxyLabel = const "xpub"
encodeParam net p = Just [xPubExport net p]
parseParam net [s] = xPubImport net s
parseParam _ _ = Nothing
instance Param Store.DeriveType where
proxyLabel = const "derive"
encodeParam net p = do
guard (getSegWit net || p == Store.DeriveNormal)
case p of
Store.DeriveNormal -> Just ["standard"]
Store.DeriveP2SH -> Just ["compat"]
Store.DeriveP2WPKH -> Just ["segwit"]
parseParam net d = do
res <- case d of
["standard"] -> Just Store.DeriveNormal
["compat"] -> Just Store.DeriveP2SH
["segwit"] -> Just Store.DeriveP2WPKH
_ -> Nothing
guard (getSegWit net || res == Store.DeriveNormal)
return res
newtype NoCache = NoCache
{ getNoCache :: Bool
} deriving (Eq, Show, Read)
instance Default NoCache where
def = NoCache False
instance Param NoCache where
proxyLabel = const "nocache"
encodeParam _ (NoCache True) = Just ["true"]
encodeParam _ (NoCache False) = Just ["false"]
parseParam _ = \case
["true"] -> Just $ NoCache True
["false"] -> Just $ NoCache False
_ -> Nothing
newtype NoTx = NoTx
{ getNoTx :: Bool
} deriving (Eq, Show, Read)
instance Default NoTx where
def = NoTx False
instance Param NoTx where
proxyLabel = const "notx"
encodeParam _ (NoTx True) = Just ["true"]
encodeParam _ (NoTx False) = Just ["false"]
parseParam _ = \case
["true"] -> Just $ NoTx True
["false"] -> Just $ NoTx False
_ -> Nothing
instance Param BlockHash where
proxyLabel = const "block"
encodeParam _ b = Just [blockHashToHex b]
parseParam _ [s] = hexToBlockHash s
parseParam _ _ = Nothing
instance Param [BlockHash] where
proxyLabel = const "blocks"
encodeParam _ bs = Just $ blockHashToHex <$> bs
parseParam _ = mapM hexToBlockHash
instance Param TxHash where
proxyLabel = const "txid"
encodeParam _ t = Just [txHashToHex t]
parseParam _ [s] = hexToTxHash s
parseParam _ _ = Nothing
instance Param [TxHash] where
proxyLabel = const "txids"
encodeParam _ ts = Just $ txHashToHex <$> ts
parseParam _ = mapM hexToTxHash
---------------------------------------
-- Blockchain.info API Compatibility --
---------------------------------------