packages feed

haskoin-store-data 1.6.3 → 1.7.0

raw patch · 3 files changed

+160/−23 lines, 3 filesdep ~haskoin-corePVP ok

version bump matches the API change (PVP)

Dependency ranges changed: haskoin-core

API changes (from Hackage documentation)

- Haskoin.Store.Data: PeerInfo :: !ByteString -> !String -> !Word32 -> !Word64 -> !Bool -> PeerInfo
+ Haskoin.Store.Data: PeerInfo :: !ByteString -> !String -> !Int32 -> !Word64 -> !Bool -> PeerInfo
- Haskoin.Store.Data: toTransaction :: Ctx -> TxData -> Transaction
+ Haskoin.Store.Data: toTransaction :: Network -> Ctx -> TxData -> Transaction

Files

haskoin-store-data.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           haskoin-store-data-version:        1.6.3+version:        1.7.0 synopsis:       Data for Haskoin Store description:    Please see the README on GitHub at <https://github.com/jprupp/haskoin-store#readme> category:       Bitcoin, Finance, Network@@ -41,7 +41,7 @@     , data-default >=0.7.1.1     , deepseq >=1.4.4.0     , hashable >=1.3.0.0-    , haskoin-core >=1.0.0+    , haskoin-core >=1.6.0     , http-client >=0.6.4.1     , http-types >=0.12.3     , lens >=4.18.1@@ -78,7 +78,7 @@     , data-default >=0.7.1.1     , deepseq >=1.4.4.0     , hashable >=1.3.0.0-    , haskoin-core >=1.0.0+    , haskoin-core >=1.6.0     , haskoin-store-data     , hspec >=2.7.1     , http-client >=0.6.4.1
src/Haskoin/Store/Data.hs view
@@ -253,7 +253,10 @@     parent <- deserialize     index <- getWord32be     chain <- deserialize-    key <- PubKey <$> getByteString 64+    -- The internal form of libsecp256k1, as the key was stored; pubKey+    -- checks that it is one, which the raw constructor of secp256k1-haskell+    -- 1.x did not+    key <- maybe (fail "XPubSpec: not a public key") return . pubKey =<< getByteString 64     deriv <- deserialize     return XPubSpec {key = XPubKey {..}, deriv} @@ -529,7 +532,56 @@  instance Coin Unspent where   coinValue = (.value)+  coinInputKind = scriptInputKind . (.script) +-- | Kind of the input that spends an output with this script, for fee+-- estimation. Read from the script bytes, as no context is at hand to parse+-- public keys. A script hash or witness script hash does not tell what it+-- hides: they are taken for the most common single-key case, a nested+-- witness public key hash and a one-of-one witness multisig. Any other+-- script counts as a public key hash.+scriptInputKind :: ByteString -> InputKind+scriptInputKind s+  | len == 25 && B.take 3 s == B.pack [0x76, 0xa9, 0x14] && B.drop 23 s == B.pack [0x88, 0xac] =+      InputPKHash+  | len == 23 && B.take 2 s == B.pack [0xa9, 0x14] && B.last s == 0x87 =+      InputNestedWitnessPKHash+  | len == 22 && B.take 2 s == B.pack [0x00, 0x14] =+      InputWitnessPKHash+  | len == 34 && B.take 2 s == B.pack [0x00, 0x20] =+      InputWitnessMulSig 1 1+  | len == 34 && B.take 2 s == B.pack [0x51, 0x20] =+      InputTaproot+  | (len == 35 && B.head s == 0x21 || len == 67 && B.head s == 0x41) && B.last s == 0xac =+      InputPK+  | Right (Script ops) <- runGetS deserialize s,+    Just (m, n) <- multisig ops =+      InputMulSig m n+  | otherwise =+      InputPKHash+  where+    len = B.length s+    multisig (op_m : rest)+      | Just m <- op_int op_m,+        (op_n : [OP_CHECKMULTISIG]) <- drop (length rest - 2) rest,+        Just n <- op_int op_n,+        keys <- take (length rest - 2) rest,+        length keys == n,+        all is_key keys,+        m >= 1,+        m <= n =+          Just (m, n)+    multisig _ = Nothing+    -- OP_1 to OP_16, or a one-byte push for counts above 16+    op_int op = case scriptOpToInt op of+      Right i -> Just i+      Left _ -> case pushOpData op of+        Just b | B.length b == 1 -> Just (fromIntegral (B.head b))+        _ -> Nothing+    is_key op = case pushOpData op of+      Just k -> B.length k == 33 || B.length k == 65+      Nothing -> False+ instance MarshalJSON Network Unspent where   marshalValue net u =     A.object@@ -956,15 +1008,15 @@   put = serialize   get = deserialize -toInput :: Ctx -> TxIn -> Maybe Prev -> WitnessStack -> StoreInput-toInput ctx i Nothing w =+toInput :: Network -> Ctx -> TxIn -> Maybe Prev -> WitnessStack -> StoreInput+toInput _ _ i Nothing w =   StoreCoinbase     { outpoint = i.outpoint,       sequence = i.sequence,       script = i.script,       witness = w     }-toInput ctx i (Just p) w =+toInput net ctx i (Just p) w =   StoreInput     { outpoint = i.outpoint,       sequence = i.sequence,@@ -972,16 +1024,16 @@       pkscript = p.script,       value = p.value,       witness = w,-      address = eitherToMaybe (scriptToAddressBS ctx p.script)+      address = eitherToMaybe (scriptToAddressBS net ctx p.script)     } -toOutput :: Ctx -> TxOut -> Maybe Spender -> StoreOutput-toOutput ctx o s =+toOutput :: Network -> Ctx -> TxOut -> Maybe Spender -> StoreOutput+toOutput net ctx o s =   StoreOutput     { value = o.value,       script = o.script,       spender = s,-      address = eitherToMaybe (scriptToAddressBS ctx o.script)+      address = eitherToMaybe (scriptToAddressBS net ctx o.script)     }  data TxData = TxData@@ -1031,8 +1083,11 @@     inputs = sum $ map (.value) $ IntMap.elems t.prevs     outputs = sum $ map (.value) t.tx.outputs -toTransaction :: Ctx -> TxData -> Transaction-toTransaction ctx t =+-- | Build the API form of a stored transaction. The network decides which+-- output scripts have an address. A SegWit-shaped script has none on Bitcoin+-- Cash, for example.+toTransaction :: Network -> Ctx -> TxData -> Transaction+toTransaction net ctx t =   Transaction     { version = t.tx.version,       locktime = t.tx.locktime,@@ -1066,8 +1121,8 @@     outs = zipWith g [0 ..] t.tx.outputs     outv = sum $ map (.value) outs     ws = take (length t.tx.inputs) $ t.tx.witness <> repeat []-    f n i = toInput ctx i (IntMap.lookup n t.prevs) (ws !! n)-    g n o = toOutput ctx o $ IntMap.lookup n t.spenders+    f n i = toInput net ctx i (IntMap.lookup n t.prevs) (ws !! n)+    g n o = toOutput net ctx o $ IntMap.lookup n t.spenders  fromTransaction :: Transaction -> TxData fromTransaction t =@@ -1232,8 +1287,10 @@     userAgent :: !ByteString,     -- | network address     address :: !String,-    -- | version number-    version :: !Word32,+    -- | protocol version from the peer's version message. It is a signed+    -- number, as in Bitcoin Core and haskoin-core, so a peer that sends+    -- ffffffff has version -1.+    version :: !Int32,     -- | services field     services :: !Word64,     -- | will relay transactions@@ -1245,13 +1302,13 @@   serialize p = do     putLengthBytes p.userAgent     putLengthBytes $ T.encodeUtf8 $ T.pack p.address-    putWord32be p.version+    putInt32be p.version     putWord64be p.services     serialize p.relay   deserialize = do     userAgent <- getLengthBytes     address <- T.unpack . T.decodeUtf8 <$> getLengthBytes-    version <- getWord32be+    version <- getInt32be     services <- getWord64be     relay <- deserialize     return PeerInfo {..}@@ -1279,7 +1336,7 @@       mconcat         [ "useragent" `A.pair` A.text (T.decodeUtf8 p.userAgent),           "address" `A.pair` toEncoding p.address,-          "version" `A.pair` A.word32 p.version,+          "version" `A.pair` A.int32 p.version,           "services" `A.pair` hexEncoding (runPutL $ serialize p.services),           "relay" `A.pair` A.bool p.relay         ]
test/Haskoin/Store/DataSpec.hs view
@@ -3,6 +3,8 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-}@@ -14,9 +16,14 @@  import Control.Arrow (second) import Control.Monad (forM_)-import Data.Aeson (FromJSON (..))+import Data.Aeson (FromJSON (..), Value)+import Data.Aeson qualified as A import Data.ByteString qualified as B-import Data.Maybe (isJust)+import Data.ByteString.Lazy qualified as BL+import Data.Bytes.Put (runPutS)+import Data.Bytes.Serial (serialize)+import Data.IntMap qualified as IntMap+import Data.Maybe (fromJust, isJust) import Data.String.Conversions (cs) import Haskoin import Haskoin.Store.Data@@ -115,7 +122,80 @@   return (net, x)  spec :: Spec-spec = forM_ allNets $ \net -> prepareContext (testIdentity . identityTests net)+spec = do+  forM_ allNets $ \net -> prepareContext (testIdentity . identityTests net)+  prepareContext $ \ctx -> describe "Coin instance of Unspent" $+    it "reads the input kind from the output script" $+      property $+        forAll (arbitraryScriptOutput btc ctx) $ \so ->+          case encodeOutput ctx so of+            Left _ -> discard+            Right script ->+              let u =+                    Unspent+                      { block = MemRef 0,+                        outpoint = OutPoint (TxHash z) 0,+                        value = 1,+                        script = runPutS (serialize script),+                        address = Nothing+                      }+               in coinInputKind u `shouldBe` expectedKind so+  prepareContext $ \ctx -> describe "toTransaction" $ do+    -- The JSON of the only output of a transaction that pays 1000 satoshi+    -- to a script given in hex.+    let outputJson net scriptHex =+          let script = fromJust (decodeHex scriptHex)+              tx = Tx 2 [TxIn (OutPoint (TxHash z) 0) "" maxBound] [TxOut 1000 script] [] 0+              td =+                TxData+                  { block = MemRef 0,+                    tx = tx,+                    prevs = IntMap.empty,+                    deleted = False,+                    rbf = False,+                    timestamp = 0,+                    spenders = IntMap.empty+                  }+           in map (marshalValue net) (toTransaction net ctx td).outputs+        -- Pay to witness public key hash, from the examples of BIP 173.+        p2wpkh = "0014751e76e8199196d454941c45d1b3a323f1433bd6"+        -- OP_HASH256, a push of 32 bytes, OP_EQUAL.+        hashLock = "aa20bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb87"+    it "gives a SegWit output its address on Bitcoin" $+      outputJson btc p2wpkh+        `shouldBe` [ json+                       "{\"address\":\"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4\",\+                       \\"pkscript\":\"0014751e76e8199196d454941c45d1b3a323f1433bd6\",\+                       \\"value\":1000,\"spent\":false,\"spender\":null}"+                   ]+    it "gives a SegWit-shaped output no address on Bitcoin Cash, which has no SegWit" $+      outputJson bch p2wpkh+        `shouldBe` [ json+                       "{\"address\":null,\+                       \\"pkscript\":\"0014751e76e8199196d454941c45d1b3a323f1433bd6\",\+                       \\"value\":1000,\"spent\":false,\"spender\":null}"+                   ]+    it "gives a 256-bit hash lock output no address on Bitcoin, which has no such address" $+      outputJson btc hashLock+        `shouldBe` [ json+                       "{\"address\":null,\+                       \\"pkscript\":\"aa20bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb87\",\+                       \\"value\":1000,\"spent\":false,\"spender\":null}"+                   ]+  where+    z = sha256 B.empty+    -- Read a JSON literal.+    json :: BL.ByteString -> Value+    json = fromJust . A.decode+    expectedKind = \case+      PayPK {} -> InputPK+      PayPKHash {} -> InputPKHash+      PayMulSig {keys, required} -> InputMulSig required (length keys)+      PayScriptHash {} -> InputNestedWitnessPKHash+      PayWitnessPKHash {} -> InputWitnessPKHash+      PayWitnessScriptHash {} -> InputWitnessMulSig 1 1+      PayWitness 1 bs | B.length bs == 32 -> InputTaproot+      _ -> InputPKHash  instance Arbitrary BlockRef where   arbitrary =