packages feed

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

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE GADTs             #-}
{-# LANGUAGE TypeFamilies      #-}
module Network.Haskoin.Wallet.Store.DbAccount 
( dbGetAcc
, dbNewAcc
, dbNewMS
, dbAddKeys
, yamlAcc
, isMSAcc
) where

import Control.Monad (when, unless)
import Control.Monad.Trans (liftIO)
import Control.Monad.Trans.Either (EitherT, left)

import Data.Time (getCurrentTime)
import Data.Yaml (Value, object, (.=))
import Data.Maybe (fromJust, isJust)
import Data.List (nub)
import qualified Data.Text as T (pack)

import Database.Persist 
    ( PersistQuery
    , PersistUnique
    , PersistStore
    , PersistMonadBackend
    , Entity(..)
    , getBy
    , insert_
    , update
    , count
    , replace
    , (==.), (=.)
    )

import Network.Haskoin.Wallet.Keys
import Network.Haskoin.Wallet.Manager
import Network.Haskoin.Wallet.Store.Util

yamlAcc :: DbAccountGeneric b -> Value
yamlAcc acc = object $ concat
    [ [ "Name" .= dbAccountName acc
      , "Tree" .= dbAccountTree acc
      ]
    , datType, datWarn
    ]
    where msReq = fromJust $ dbAccountMsRequired acc
          msTot = fromJust $ dbAccountMsTotal acc
          ms    = unwords [show msReq,"of",show msTot]
          miss  = msTot - length (dbAccountMsKeys acc) - 1
          datType | isMSAcc acc = ["Type" .= unwords [ "Multisig", ms ]]
                  | otherwise   = ["Type" .= ("Regular" :: String)]
          datWarn | isMSAcc acc && miss > 0 =
                      [ (T.pack "Warning") .= 
                          unwords [show miss,"multisig keys missing"]
                      ]
                  | otherwise = []

isMSAcc :: DbAccountGeneric b -> Bool
isMSAcc acc = (isJust $ dbAccountMsRequired acc) && 
              (isJust $ dbAccountMsTotal acc) 

dbGetAcc :: (PersistUnique m, PersistMonadBackend m ~ b)
         => String 
         -> EitherT String m (Entity (DbAccountGeneric b))
dbGetAcc name = liftMaybe accErr =<< (getBy $ UniqueAccName name)
  where 
    accErr = unwords ["dbGetAcc: Invalid account", name]

dbNewAcc :: ( PersistUnique m
            , PersistQuery m
            , PersistMonadBackend m ~ b
            ) 
         => String -> EitherT String m (DbAccountGeneric b)
dbNewAcc name = do
    time <- liftIO getCurrentTime
    (Entity wk w) <- dbGetWallet "main"
    let keyM = loadMasterKey =<< (xPrvImport $ dbWalletMaster w)
    master <- liftMaybe keyErr keyM
    let deriv = fromIntegral $ dbWalletAccIndex w + 1
        (k,i) = head $ accPubKeys master deriv
        acc   = DbAccount name 
                          (fromIntegral i) 
                          (concat ["m/",show i,"'/"])
                          (xPubExport $ getAccPubKey k)
                          (-1) (-1) (-1) (-1)
                          Nothing Nothing [] wk time
    insert_ acc
    update wk [DbWalletAccIndex =. fromIntegral i]
    return acc
  where 
    keyErr = "dbNewAcc: Could not load master key"

dbNewMS :: ( PersistUnique m
           , PersistQuery m
           , PersistMonadBackend m ~ b
           )
        => String -> Int -> Int -> [XPubKey]
        -> EitherT String m (DbAccountGeneric b)
dbNewMS name m n mskeys = do
    time <- liftIO getCurrentTime
    let keys = nub mskeys
    unless (n >= 1 && n <= 16 && m >= 1 && m <= n) $ left
        "cmdNewMS: Invalid multisig parameters"
    unless (length keys < n) $ left 
        "cmdNewMS: Too many keys"
    (Entity wk w) <- dbGetWallet "main"
    let keyM = loadMasterKey =<< (xPrvImport $ dbWalletMaster w)
    master <- liftMaybe keyErr keyM
    let deriv = fromIntegral $ dbWalletAccIndex w + 1
        (k,i) = head $ accPubKeys master deriv
        acc   = DbAccount name 
                          (fromIntegral i) 
                          (concat ["m/",show i,"'/"])
                          (xPubExport $ getAccPubKey k)
                          (-1) (-1) (-1) (-1) 
                          (Just m) (Just n) 
                          (map xPubExport keys)
                          wk time
    insert_ acc
    update wk [DbWalletAccIndex =. fromIntegral i]
    return acc
  where 
    keyErr = "dbNewMS: Could not load master key"

dbAddKeys :: ( PersistUnique m
             , PersistQuery m
             , PersistMonadBackend m ~ b
             )
          => AccountName -> [XPubKey] 
          -> EitherT String m (DbAccountGeneric b)
dbAddKeys name keys 
    | null keys = left "dbAddKeys: Keys can not be empty"
    | otherwise = do
        (Entity ai acc) <- dbGetAcc name
        unless (isMSAcc acc) $ left $ 
            "dbAddKeys: Can only add keys to a multisig account"
        exists <- mapM (\x -> count [DbAccountKey ==. (xPubExport x)]) keys
        unless (sum exists == 0) $ left $
            "dbAddKeys: Can not add your own keys to a multisig account"
        prevKeys <- liftMaybe keyErr $ mapM xPubImport $ dbAccountMsKeys acc
        when (length prevKeys == (fromJust $ dbAccountMsTotal acc) - 1) $ left $
            "dbAddKeys: Account is complete. No more keys can be added"
        let newKeys = nub $ prevKeys ++ keys
            newAcc  = acc{ dbAccountMsKeys = map xPubExport newKeys }
        unless (length newKeys < (fromJust $ dbAccountMsTotal acc)) $ left $
            "dbAddKeys: Too many keys"
        replace ai newAcc
        return newAcc
  where 
    keyErr = "dbAddKeys: Invalid keys found in account"