packages feed

haskoin-wallet-0.0.1: Network/Haskoin/Wallet/Store/Util.hs

{-# LANGUAGE EmptyDataDecls    #-}
{-# LANGUAGE FlexibleContexts  #-}
{-# LANGUAGE GADTs             #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes       #-}
{-# LANGUAGE TemplateHaskell   #-}
{-# LANGUAGE TypeFamilies      #-}
module Network.Haskoin.Wallet.Store.Util 
( DbWalletGeneric(..)
, DbAccountGeneric(..)
, DbAddressGeneric(..)
, DbCoinGeneric(..)
, DbTxGeneric(..)
, DbTxBlobGeneric(..)
, DbWalletId
, DbAccountId
, DbAddressId
, DbCoinId
, DbTxId
, DbTxBlobId
, Unique(..)
, EntityField(..)
, AccountName
, CoinStatus(..)
, catStatus
, dbGetWallet
, dbGetTxBlob
, liftEither
, liftMaybe
, migrateAll
) where

import Control.Monad.Trans (lift)
import Control.Monad.Trans.Either (EitherT, hoistEither)

import Data.Time (UTCTime)
import Data.Yaml
    ( ToJSON, toJSON
    , object, (.=)
    )
import qualified Data.Text as T (pack)
import qualified Data.ByteString as BS (ByteString)
import qualified Data.Conduit as C (transPipe)

import Database.Persist 
    ( PersistStore
    , PersistUnique
    , PersistQuery
    , PersistMonadBackend
    , Entity
    , EntityField
    , Unique
    , get
    , getBy
    , insert_
    , insert
    , count
    , selectKeys
    , selectFirst
    , selectSource
    , updateWhere
    , deleteBy
    , insertUnique
    , updateGet
    , replace
    , repsert
    , insertKey
    , insertMany
    , delete
    , deleteWhere
    , update
    )
import Database.Persist.TH 
    ( mkPersist
    , sqlSettings
    , mkMigrate
    , persistLowerCase
    , share
    )

import Network.Haskoin.Wallet.Store.CoinStatus
import Network.Haskoin.Script
import Network.Haskoin.Protocol
import Network.Haskoin.Crypto
import Network.Haskoin.Util

type AccountName = String

liftEither :: Monad m => Either String a -> EitherT String m a
liftEither = hoistEither

liftMaybe :: Monad m => String -> Maybe a -> EitherT String m a
liftMaybe err = liftEither . (maybeToEither err)

share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
DbWallet json
    name String
    type String
    master String 
    accIndex Int
    created UTCTime default=CURRENT_TIME
    UniqueWalletName name
    deriving Show

DbAccount json
    name String
    index Int
    tree String
    key String
    extIndex Int
    extGap Int
    intIndex Int
    intGap Int
    msRequired Int Maybe
    msTotal Int Maybe
    msKeys [String] 
    wallet DbWalletId
    created UTCTime default=CURRENT_TIME
    UniqueAccName name
    deriving Show

DbAddress json
    base58 String
    label String
    index Int
    tree String
    account DbAccountId
    internal Bool
    created UTCTime default=CURRENT_TIME
    UniqueAddress base58
    UniqueAddressKey account index internal
    deriving Show

DbCoin json
    txid String
    pos Int
    value Int
    script String
    rdmScript String Maybe
    address String 
    status CoinStatus
    account DbAccountId
    orphan Bool
    created UTCTime default=CURRENT_TIME
    CoinOutPoint txid pos
    deriving Show

DbTx json
    txid String
    recipients [String]
    value Int
    account DbAccountId
    orphan Bool
    partial Bool
    created UTCTime default=CURRENT_TIME
    UniqueTx txid account
    deriving Show

DbTxBlob json
    txid String
    value BS.ByteString 
    created UTCTime default=CURRENT_TIME
    UniqueTxBlob txid
    deriving Show

|]

dbGetWallet :: (PersistUnique m, PersistMonadBackend m ~ b)
            => String -> EitherT String m (Entity (DbWalletGeneric b))
dbGetWallet name = liftMaybe walletErr =<< (getBy $ UniqueWalletName name)
  where 
    walletErr = unwords ["dbGetWallet: Invalid wallet", name]

dbGetTxBlob :: (PersistUnique m, PersistMonadBackend m ~ b)
            => String -> EitherT String m (Entity (DbTxBlobGeneric b))
dbGetTxBlob tid = liftMaybe txErr =<< (getBy $ UniqueTxBlob tid)
  where
    txErr = unwords ["dbGetTxBlob: Invalid txid", tid]

instance PersistStore m => PersistStore (EitherT e m) where
    type PersistMonadBackend (EitherT e m) = PersistMonadBackend m
    get         = lift . get
    insert      = lift . insert
    insert_     = lift . insert_
    insertMany  = lift . insertMany
    insertKey k = lift . (insertKey k)
    repsert k   = lift . (repsert k)
    replace k   = lift . (replace k)
    delete      = lift . delete

instance PersistUnique m => PersistUnique (EitherT e m) where
    getBy        = lift . getBy
    deleteBy     = lift . deleteBy
    insertUnique = lift . insertUnique

instance PersistQuery m => PersistQuery (EitherT e m) where
    update k       = lift . (update k)
    updateGet k    = lift . (updateGet k)
    updateWhere f  = lift . (updateWhere f)
    deleteWhere    = lift . deleteWhere
    selectSource f = (C.transPipe lift) . (selectSource f)
    selectFirst f  = lift . (selectFirst f)
    selectKeys f   = (C.transPipe lift) . (selectKeys f)
    count          = lift . count

{- YAML templates -}

instance ToJSON OutPoint where
    toJSON (OutPoint h i) = object
        [ (T.pack "TxID") .= encodeTxid h
        , (T.pack "Index") .= toJSON i
        ]

instance ToJSON TxOut where
    toJSON (TxOut v s) = object $
        [ (T.pack "Value") .= toJSON v
        , (T.pack "Raw Script") .= (bsToHex $ encodeScriptOps s)
        , (T.pack "Script") .= toJSON s
        ] ++ scptPair 
        where scptPair = 
                either (const [])
                       (\out -> [(T.pack "Decoded Script") .= toJSON out]) 
                       (decodeOutput s)

instance ToJSON TxIn where
    toJSON (TxIn o s i) = object $ concat
        [ [ (T.pack "OutPoint") .= toJSON o
          , (T.pack "Sequence") .= toJSON i
          , (T.pack "Raw Script") .= (bsToHex $ encodeScriptOps s)
          , (T.pack "Script") .= toJSON s
          ] 
        , decoded 
        ]
        where decoded = either (const $ either (const []) f $ decodeInput s) 
                               f $ decodeScriptHash s
              f inp = [(T.pack "Decoded Script") .= toJSON inp]
              
instance ToJSON Tx where
    toJSON tx@(Tx v is os i) = object
        [ (T.pack "TxID") .= encodeTxid (txid tx)
        , (T.pack "Version") .= toJSON v
        , (T.pack "Inputs") .= (toJSON $ map input $ zip is [0..])
        , (T.pack "Outputs") .= (toJSON $ map output $ zip os [0..])
        , (T.pack "LockTime") .= toJSON i
        ]
        where input (x,j) = object 
                [(T.pack $ unwords ["Input", show (j :: Int)]) .= toJSON x]
              output (x,j) = object 
                [(T.pack $ unwords ["Output", show (j :: Int)]) .= toJSON x]

instance ToJSON Script where
    toJSON (Script ops) = toJSON $ map show ops

instance ToJSON ScriptOutput where
    toJSON (PayPK p) = object 
        [ (T.pack "PayToPublicKey") .= object
            [ (T.pack "Public Key") .= (bsToHex $ encode' p)
            ]
        ]
    toJSON (PayPKHash a) = object 
        [ (T.pack "PayToPublicKeyHash") .= object
            [ (T.pack "Address Hash160") .= (bsToHex $ encode' $ getAddress a)
            , (T.pack "Address Base58") .= addrToBase58 a
            ]
        ]
    toJSON (PayMulSig ks r) = object 
        [ (T.pack "PayToMultiSig") .= object
            [ (T.pack "Required Keys (M)") .= toJSON r
            , (T.pack "Public Keys") .= (toJSON $ map (bsToHex . encode') ks)
            ]
        ]
    toJSON (PayScriptHash a) = object 
        [ (T.pack "PayToScriptHash") .= object
            [ (T.pack "Address Hash160") .= (bsToHex $ encode' $ getAddress a)
            , (T.pack "Address Base58") .= addrToBase58 a
            ]
        ]

instance ToJSON ScriptInput where
    toJSON (SpendPK s) = object 
        [ (T.pack "SpendPublicKey") .= object
            [ (T.pack "Signature") .= toJSON s
            ]
        ]
    toJSON (SpendPKHash s p) = object 
        [ (T.pack "SpendPublicKeyHash") .= object
            [ (T.pack "Signature") .= toJSON s
            , (T.pack "Public Key") .= (bsToHex $ encode' p)
            , (T.pack "Sender Addr") .= addrToBase58 (pubKeyAddr p)
            ]
        ]
    toJSON (SpendMulSig sigs r) = object 
        [ (T.pack "SpendMultiSig") .= object
            [ (T.pack "Required Keys (M)") .= toJSON r
            , (T.pack "Signatures") .= (toJSON $ map toJSON sigs)
            ]
        ]

instance ToJSON ScriptHashInput where
    toJSON (ScriptHashInput s r) = object
        [ (T.pack "SpendScriptHash") .= object
            [ (T.pack "ScriptInput") .= toJSON s
            , (T.pack "RedeemScript") .= toJSON r
            , (T.pack "Raw Redeem Script") .= 
                (bsToHex $ encodeScriptOps $ encodeOutput r)
            , (T.pack "Sender Addr") .= (addrToBase58 $ scriptAddr  r)
            ]
        ]

instance ToJSON TxSignature where
    toJSON ts@(TxSignature _ h) = object
        [ (T.pack "Raw Sig") .= (bsToHex $ encodeSig ts)
        , (T.pack "SigHash") .= toJSON h
        ]

instance ToJSON SigHash where
    toJSON sh = case sh of
        (SigAll acp) -> object
            [ (T.pack "Type") .= T.pack "SigAll"
            , (T.pack "AnyoneCanPay") .= acp
            ]
        (SigNone acp) -> object
            [ (T.pack "Type") .= T.pack "SigNone"
            , (T.pack "AnyoneCanPay") .= acp
            ]
        (SigSingle acp) -> object
            [ (T.pack "Type") .= T.pack "SigSingle"
            , (T.pack "AnyoneCanPay") .= acp
            ]
        (SigUnknown acp v) -> object
            [ (T.pack "Type") .= T.pack "SigUnknown"
            , (T.pack "AnyoneCanPay") .= acp
            , (T.pack "Value") .= v
            ]