haskoin-store-data 0.52.13 → 0.53.0
raw patch · 7 files changed
+144/−122 lines, 7 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Haskoin.Store.Data: [getBinfoRawAddrBalance] :: BinfoRawAddr -> !Balance
- Haskoin.Store.Data: [getBinfoRawAddrTxs] :: BinfoRawAddr -> ![BinfoTx]
+ Haskoin.Store.Data: [binfoRawAddr] :: BinfoRawAddr -> !BinfoAddr
+ Haskoin.Store.Data: [binfoRawBalance] :: BinfoRawAddr -> !Word64
+ Haskoin.Store.Data: [binfoRawReceived] :: BinfoRawAddr -> !Word64
+ Haskoin.Store.Data: [binfoRawSent] :: BinfoRawAddr -> !Int64
+ Haskoin.Store.Data: [binfoRawTxCount] :: BinfoRawAddr -> !Word64
+ Haskoin.Store.Data: [binfoRawTxs] :: BinfoRawAddr -> ![BinfoTx]
+ Haskoin.Store.Data: [binfoRawUnredeemed] :: BinfoRawAddr -> !Word64
+ Haskoin.Store.Data: deriveTypeToText :: DeriveType -> Text
+ Haskoin.Store.Data: instance Web.Scotty.Action.Parsable Haskoin.Store.Data.DeriveType
+ Haskoin.Store.Data: textToDeriveType :: Text -> Maybe DeriveType
- Haskoin.Store.Data: BinfoRawAddr :: !Balance -> ![BinfoTx] -> BinfoRawAddr
+ Haskoin.Store.Data: BinfoRawAddr :: !BinfoAddr -> !Word64 -> !Word64 -> !Word64 -> !Word64 -> !Int64 -> ![BinfoTx] -> BinfoRawAddr
Files
- CHANGELOG.md +21/−0
- README.md +1/−1
- haskoin-store-data.cabal +2/−2
- src/Haskoin/Store/Data.hs +103/−82
- src/Haskoin/Store/WebClient.hs +0/−1
- src/Haskoin/Store/WebCommon.hs +3/−13
- test/Haskoin/Store/DataSpec.hs +14/−23
CHANGELOG.md view
@@ -4,6 +4,27 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html) +## 0.53.0+### Added+- Blockchain.info endpoint: `q/addresstohash/:addr`.+- Blockchain.info endpoint: `q/addrpubkey/:pubkey`.+- Blockchain.info endpoint: `q/hashpubkey/:pubkey`.+- Blockchain.info endpoint: `q/getblockcount`.+- Blockchain.info endpoint: `q/latesthash`.+- Blockchain.info endpoint: `q/bcperblock`.+- Blockchain.info endpoint: `q/txtotalbtcoutput/:txid`.+- Blockchain.info endpoint: `q/txtotalbtcinput/:txid`.+- Blockchain.info endpoint: `q/txfee/:txid`.+- Blockchain.info endpoint: `q/txresult/:txhash/:addr`.+- Blockchain.info endpoint: `q/getreceivedbyaddress/:addr`.+- Blockchain.info endpoint: `q/addressbalance/:addr`.+- Blockchain.info endpoint: `q/addressfirstseen/:addr`.+- Support xpubs in `blockchain/rawaddr` endpoint.++### Fixed+- Various latest blocks endpoint fixes.+- Allow empty parameters in Blockchain.info POST requests.+ ## 0.52.13 ### Fixed - Upstream fixes for unknown inv types.
README.md view
@@ -44,4 +44,4 @@ ## API Documentation -* [Swagger API Documentation](https://api.haskoin.com/).+* [Swagger API Documentation](https://api.haskoin.com/)
haskoin-store-data.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: af8f78beeb32434af141e0db55de82fa118f20234aa90dbc9e9e34fc1771215d+-- hash: 350002bf6bde26ced5867d69ad29f5e9b105651275decac058ef220ebea8e66e name: haskoin-store-data-version: 0.52.13+version: 0.53.0 synopsis: Data for Haskoin Store description: Please see the README on GitHub at <https://github.com/haskoin/haskoin-store#readme> category: Bitcoin, Finance, Network
src/Haskoin/Store/Data.hs view
@@ -66,6 +66,8 @@ , xPubUnspentParseJSON , XPubSummary(..) , DeriveType(..)+ , textToDeriveType+ , deriveTypeToText -- * Other Data , TxId(..)@@ -164,12 +166,9 @@ import Control.Applicative ((<|>)) import Control.DeepSeq (NFData) import Control.Exception (Exception)-import Control.Monad (join, mzero, replicateM, unless,- (<=<))-import Data.Aeson (Encoding, FromJSON (..),- FromJSONKey (..), ToJSON (..),- ToJSONKey (..), Value (..), (.!=),- (.:), (.:?), (.=))+import Control.Monad (join, mzero, unless, (<=<))+import Data.Aeson (Encoding, FromJSON (..), ToJSON (..),+ Value (..), (.!=), (.:), (.:?), (.=)) import qualified Data.Aeson as A import qualified Data.Aeson.Encoding as AE import Data.Aeson.Types (Parser)@@ -181,11 +180,8 @@ import Data.Bytes.Get import qualified Data.Bytes.Get as Bytes.Get import Data.Bytes.Put-import qualified Data.Bytes.Put as Bytes.Put import Data.Bytes.Serial-import Data.Bytes.Serial (Serial (..)) import Data.Default (Default (..))-import Data.Either (fromRight, lefts, rights) import Data.Foldable (toList) import Data.Function (on) import Data.HashMap.Strict (HashMap)@@ -196,8 +192,6 @@ import Data.Int (Int32, Int64) import qualified Data.IntMap as IntMap import Data.IntMap.Strict (IntMap)-import Data.List (unfoldr)-import Data.Map.Strict (Map) import Data.Maybe (catMaybes, fromMaybe, isJust, isNothing, mapMaybe, maybeToList) import Data.Serialize (Serialize (..))@@ -211,12 +205,9 @@ utcTimeToPOSIXSeconds) import Data.Time.Format (defaultTimeLocale, formatTime, parseTimeM)-import Data.Word (Word32, Word64, Word8)+import Data.Word (Word32, Word64) import GHC.Generics (Generic)-import Haskoin-import Numeric.Natural (Natural)-import Text.Printf (printf)-import Text.Read (readMaybe)+import Haskoin hiding (inputAddress) import Web.Scotty.Trans (Parsable (..), ScottyError (..)) data DeriveType@@ -225,6 +216,17 @@ | DeriveP2WPKH deriving (Show, Eq, Generic, NFData) +textToDeriveType :: Text -> Maybe DeriveType+textToDeriveType "normal" = Just DeriveNormal+textToDeriveType "compat" = Just DeriveP2SH+textToDeriveType "segwit" = Just DeriveP2WPKH+textToDeriveType _ = Nothing++deriveTypeToText :: DeriveType -> Text+deriveTypeToText DeriveNormal = "normal"+deriveTypeToText DeriveP2SH = "compat"+deriveTypeToText DeriveP2WPKH = "segwit"+ instance Serial DeriveType where serialize DeriveNormal = putWord8 0x00 serialize DeriveP2SH = putWord8 0x01@@ -234,6 +236,7 @@ 0x00 -> return DeriveNormal 0x01 -> return DeriveP2SH 0x02 -> return DeriveP2WPKH+ _ -> return DeriveNormal instance Binary DeriveType where put = serialize@@ -246,6 +249,12 @@ instance Default DeriveType where def = DeriveNormal +instance Parsable DeriveType where+ parseParam txt =+ case textToDeriveType (TL.toStrict txt) of+ Nothing -> Left "invalid derivation type"+ Just x -> Right x+ data XPubSpec = XPubSpec { xPubSpecKey :: !XPubKey@@ -799,6 +808,7 @@ inputWitness <- getList getLengthBytes inputAddress <- getMaybe deserialize return StoreInput{..}+ x -> fail $ "Unknown input id: " <> cs (show x) instance Serialize StoreInput where put = serialize@@ -825,7 +835,7 @@ inputWitness = wit, inputAddress = a } =- A.object $+ A.object [ "coinbase" .= False , "txid" .= oph , "output" .= opi@@ -838,7 +848,7 @@ ] storeInputToJSON- net+ _ StoreCoinbase { inputPoint = OutPoint oph opi,@@ -846,7 +856,7 @@ inputSigScript = ss, inputWitness = wit } =- A.object $+ A.object [ "coinbase" .= True , "txid" .= oph , "output" .= opi@@ -883,7 +893,7 @@ "witness" .= map encodeHex wit storeInputToEncoding- net+ _ StoreCoinbase { inputPoint = OutPoint oph opi,@@ -2130,15 +2140,15 @@ BadRequest -> [ "error" .= String "bad-request" , "message" .= String "Invalid request" ]- UserError msg ->+ UserError msg' -> [ "error" .= String "user-error"- , "message" .= String (cs msg) ]- StringError msg ->+ , "message" .= String (cs msg') ]+ StringError msg' -> [ "error" .= String "string-error"- , "message" .= String (cs msg) ]+ , "message" .= String (cs msg') ] TxIndexConflict txids -> [ "error" .= String "multiple-tx-index"- , "message" .= String "Many txs match that tx_index"+ , "message" .= String "Multiple txs match that index" , "txids" .= txids ] ServerTimeout -> [ "error" .= String "server-timeout"@@ -2151,7 +2161,7 @@ parseJSON = A.withObject "Except" $ \o -> do ctr <- o .: "error"- msg <- o .:? "message" .!= ""+ msg' <- o .:? "message" .!= "" case ctr of String "not-found-or-invalid-arg" -> return ThingNotFound@@ -2160,9 +2170,9 @@ String "bad-request" -> return BadRequest String "user-error" ->- return $ UserError msg+ return $ UserError msg' String "string-error" ->- return $ StringError msg+ return $ StringError msg' String "multiple-tx-index" -> do txids <- o .: "txids" return $ TxIndexConflict txids@@ -2299,8 +2309,13 @@ data BinfoRawAddr = BinfoRawAddr- { getBinfoRawAddrBalance :: !Balance- , getBinfoRawAddrTxs :: ![BinfoTx]+ { binfoRawAddr :: !BinfoAddr+ , binfoRawBalance :: !Word64+ , binfoRawTxCount :: !Word64+ , binfoRawUnredeemed :: !Word64+ , binfoRawReceived :: !Word64+ , binfoRawSent :: !Int64+ , binfoRawTxs :: ![BinfoTx] } deriving (Eq, Show, Generic, NFData) @@ -2308,59 +2323,67 @@ binfoRawAddrToJSON net BinfoRawAddr{..} = A.object [- "hash160" .= (encodeHex . runPutS . serialize <$> h160),- "address" .= addrToJSON net balanceAddress,- "n_tx" .= balanceTxCount,- "n_unredeemed" .= balanceUnspentCount,- "total_received" .= balanceTotalReceived,- "total_sent" .= (balanceTotalReceived - bal),- "final_balance" .= bal,- "txs" .= map (binfoTxToJSON net) getBinfoRawAddrTxs+ "hash160" .= h160,+ "address" .= address,+ "n_tx" .= binfoRawTxCount,+ "n_unredeemed" .= binfoRawUnredeemed,+ "total_received" .= binfoRawReceived,+ "total_sent" .= binfoRawSent,+ "final_balance" .= binfoRawBalance,+ "txs" .= map (binfoTxToJSON net) binfoRawTxs ] where- Balance{..} = getBinfoRawAddrBalance- bal = balanceAmount + balanceZero- h160 = case balanceAddress of- PubKeyAddress h -> Just h- ScriptAddress h -> Just h- WitnessPubKeyAddress h -> Just h- _ -> Nothing+ address = case binfoRawAddr of+ BinfoAddr a -> addrToJSON net a+ BinfoXpub x -> xPubToJSON net x+ h160 =+ encodeHex . runPutS . serialize <$>+ case binfoRawAddr of+ BinfoAddr a -> case a of+ PubKeyAddress h -> Just h+ ScriptAddress h -> Just h+ WitnessPubKeyAddress h -> Just h+ _ -> Nothing+ _ -> Nothing binfoRawAddrToEncoding :: Network -> BinfoRawAddr -> Encoding binfoRawAddrToEncoding net BinfoRawAddr{..} = AE.pairs $- "hash160" .= (encodeHex . runPutS . serialize <$> h160) <>- "address" `AE.pair` addrToEncoding net balanceAddress <>- "n_tx" .= balanceTxCount <>- "n_unredeemed" .= balanceUnspentCount <>- "total_received" .= balanceTotalReceived <>- "total_sent" .= (balanceTotalReceived - bal) <>- "final_balance" .= bal <>- "txs" `AE.pair` AE.list (binfoTxToEncoding net) getBinfoRawAddrTxs+ "hash160" .= h160 <>+ "address" `AE.pair` address <>+ "n_tx" .= binfoRawTxCount <>+ "n_unredeemed" .= binfoRawUnredeemed <>+ "total_received" .= binfoRawReceived <>+ "total_sent" .= binfoRawSent <>+ "final_balance" .= binfoRawBalance <>+ "txs" `AE.pair` AE.list (binfoTxToEncoding net) binfoRawTxs where- Balance{..} = getBinfoRawAddrBalance- bal = balanceAmount + balanceZero- h160 = case balanceAddress of- PubKeyAddress h -> Just h- ScriptAddress h -> Just h- WitnessPubKeyAddress h -> Just h- _ -> Nothing+ address = case binfoRawAddr of+ BinfoAddr a -> addrToEncoding net a+ BinfoXpub x -> xPubToEncoding net x+ h160 =+ encodeHex . runPutS . serialize <$>+ case binfoRawAddr of+ BinfoAddr a -> case a of+ PubKeyAddress h -> Just h+ ScriptAddress h -> Just h+ WitnessPubKeyAddress h -> Just h+ _ -> Nothing+ _ -> Nothing binfoRawAddrParseJSON :: Network -> Value -> Parser BinfoRawAddr binfoRawAddrParseJSON net = A.withObject "balancetxs" $ \o -> do- balanceAddress <- addrFromJSON net =<< o .: "address"- balanceAmount <- o .: "final_balance"- let balanceZero = 0- balanceUnspentCount <- o .: "n_unredeemed"- balanceTxCount <- o .: "n_tx"- balanceTotalReceived <- o .: "total_received"- txs <- mapM (binfoTxParseJSON net) =<< o .: "txs"- return- BinfoRawAddr- {- getBinfoRawAddrBalance = Balance{..},- getBinfoRawAddrTxs = txs- }+ addr <- o .: "address"+ binfoRawAddr <-+ BinfoAddr <$> addrFromJSON net addr <|>+ BinfoXpub <$> xPubFromJSON net addr+ binfoRawBalance <- o .: "final_balance"+ binfoRawUnredeemed <- o .: "n_unredeemed"+ binfoRawTxCount <- o .: "n_tx"+ binfoRawReceived <- o .: "total_received"+ binfoRawSent <- o .: "total_sent"+ binfoRawTxs <- mapM (binfoTxParseJSON net) =<< o .: "txs"+ return BinfoRawAddr{..} data BinfoShortBal = BinfoShortBal@@ -2649,7 +2672,7 @@ binfoBlockToJSON :: Network -> BinfoBlock -> Value binfoBlockToJSON net BinfoBlock{..} =- A.object $+ A.object [ "hash" .= getBinfoBlockHash , "ver" .= getBinfoBlockVer , "prev_block" .= getBinfoPrevBlock@@ -2754,9 +2777,9 @@ where bal = case getBinfoTxResultBal of- Nothing -> []- Just (res, bal) -> ["result" .= res, "balance" .= bal]- rbf = if getBinfoTxRBF then ["rbf" .= True] else []+ Nothing -> []+ Just (res, bal') -> ["result" .= res, "balance" .= bal']+ rbf = ["rbf" .= True | getBinfoTxRBF] binfoTxToEncoding :: Network -> BinfoTx -> Encoding binfoTxToEncoding net BinfoTx {..} =@@ -2781,8 +2804,8 @@ where bal = case getBinfoTxResultBal of- Nothing -> mempty- Just (res, bal) -> "result" .= res <> "balance" .= bal+ Nothing -> mempty+ Just (res, bal') -> "result" .= res <> "balance" .= bal' rbf = if getBinfoTxRBF then "rbf" .= True else mempty binfoTxParseJSON :: Network -> Value -> Parser BinfoTx@@ -3291,9 +3314,7 @@ received = sum (map f xs) bal = fromIntegral (sum (map g xs)) sent = if bal <= received then received - bal else 0- count = case HashMap.lookup k xpub_txs of- Nothing -> 0- Just i -> fromIntegral i+ count = maybe 0 fromIntegral $ HashMap.lookup k xpub_txs ax = foldl max 0 (map (i 0) xs) cx = foldl max 0 (map (i 1) xs) in BinfoXPubBalance{ getBinfoXPubKey = k
src/Haskoin/Store/WebClient.hs view
@@ -248,7 +248,6 @@ code = res ^. HTTP.responseStatus . HTTP.statusCode message = res ^. HTTP.responseStatus . HTTP.statusMessage status = mkStatus code message- err = unwords ["Code:", show code, "Message:", cs message] isHealthPath = "/health" `Text.isInfixOf` cs (path req) ---------------
src/Haskoin/Store/WebCommon.hs view
@@ -4,18 +4,13 @@ {-# 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 (..))@@ -490,16 +485,11 @@ 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"]+ Just [Store.deriveTypeToText p] parseParam net d = do res <- case d of- ["standard"] -> Just Store.DeriveNormal- ["compat"] -> Just Store.DeriveP2SH- ["segwit"] -> Just Store.DeriveP2WPKH- _ -> Nothing+ [x] -> Store.textToDeriveType x+ _ -> Nothing guard (getSegWit net || res == Store.DeriveNormal) return res
test/Haskoin/Store/DataSpec.hs view
@@ -8,18 +8,14 @@ ( spec ) where -import Control.Monad (forM_, replicateM)+import Control.Monad (forM_) import Data.Aeson (FromJSON (..)) import qualified Data.ByteString as B-import qualified Data.ByteString.Short as BSS-import qualified Data.Map.Strict as Map-import qualified Data.Serialize as S import Data.String.Conversions (cs) import Haskoin import Haskoin.Store.Data import Haskoin.Util.Arbitrary-import Test.Hspec (Spec, describe, it)-import Test.Hspec.QuickCheck (prop)+import Test.Hspec (Spec, describe) import Test.QuickCheck serialVals :: [SerialBox]@@ -236,20 +232,6 @@ <*> arbitrary <*> arbitrary -arbitraryTransactionNet :: Gen (Network, Transaction)-arbitraryTransactionNet = do- net <- arbitraryNetwork- val <- arbitrary- let val1 | getSegWit net = val- | otherwise = val{ transactionInputs = f <$> transactionInputs val- , transactionWeight = 0- }- res | getReplaceByFee net = val1- | otherwise = val1{ transactionRBF = False }- return (net, res)- where- f i = i {inputWitness = []}- instance Arbitrary PeerInformation where arbitrary = PeerInformation@@ -427,9 +409,18 @@ instance Arbitrary BinfoRawAddr where arbitrary = do- balance <- arbitrary- txs <- arbitrary- return $ BinfoRawAddr balance{balanceZero = 0} txs+ binfoRawAddr <-+ oneof+ [ BinfoAddr <$> arbitraryAddress+ , BinfoXpub . snd <$> arbitraryXPubKey+ ]+ binfoRawBalance <- arbitrary+ binfoRawTxCount <- arbitrary+ binfoRawUnredeemed <- arbitrary+ binfoRawReceived <- arbitrary+ binfoRawSent <- arbitrary+ binfoRawTxs <- arbitrary+ return $ BinfoRawAddr {..} instance Arbitrary BinfoShortBal where arbitrary = BinfoShortBal <$> arbitrary <*> arbitrary <*> arbitrary