haskoin-store-0.3.0: src/Network/Haskoin/Store/Data.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
module Network.Haskoin.Store.Data where
import Conduit
import Data.Aeson as A
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import Data.Hashable
import Data.Int
import Data.Maybe
import Data.Serialize as S
import Data.String.Conversions
import Data.Time.Clock.System
import Data.Word
import GHC.Generics
import Haskoin
import Network.Socket (SockAddr)
import UnliftIO.Exception
type UnixTime = Int64
newtype InitException = IncorrectVersion Word32
deriving (Show, Read, Eq, Ord, Exception)
class StoreRead r m where
isInitialized :: r -> m (Either InitException Bool)
getBestBlock :: r -> m (Maybe BlockHash)
getBlocksAtHeight :: r -> BlockHeight -> m [BlockHash]
getBlock :: r -> BlockHash -> m (Maybe BlockData)
getTransaction :: r -> TxHash -> m (Maybe Transaction)
getBalance :: r -> Address -> m Balance
class StoreStream r m where
getMempool :: r -> ConduitT () (PreciseUnixTime, TxHash) m ()
getAddressUnspents :: r -> Address -> ConduitT () Unspent m ()
getAddressTxs :: r -> Address -> ConduitT () AddressTx m ()
class StoreWrite w m where
setInit :: w -> m ()
setBest :: w -> BlockHash -> m ()
insertBlock :: w -> BlockData -> m ()
insertAtHeight :: w -> BlockHash -> BlockHeight -> m ()
insertTx :: w -> Transaction -> m ()
setBalance :: w -> Balance -> m ()
insertAddrTx :: w -> AddressTx -> m ()
removeAddrTx :: w -> AddressTx -> m ()
insertAddrUnspent :: w -> Address -> Unspent -> m ()
removeAddrUnspent :: w -> Address -> Unspent -> m ()
insertMempoolTx :: w -> TxHash -> PreciseUnixTime -> m ()
deleteMempoolTx :: w -> TxHash -> PreciseUnixTime -> m ()
-- | Unix time with nanosecond precision for mempool transactions
newtype PreciseUnixTime = PreciseUnixTime Word64
deriving (Show, Eq, Read, Generic, Ord, Hashable, Serialize)
preciseUnixTime :: SystemTime -> PreciseUnixTime
preciseUnixTime s =
PreciseUnixTime . fromIntegral $
(systemSeconds s * 1000) +
(fromIntegral (systemNanoseconds s) `div` (1000 * 1000))
instance ToJSON PreciseUnixTime where
toJSON (PreciseUnixTime w) = toJSON w
toEncoding (PreciseUnixTime w) = toEncoding w
-- | Reference to a block where a transaction is stored.
data BlockRef
= BlockRef { blockRefHeight :: !BlockHeight
-- ^ block height in the chain
, blockRefPos :: !Word32
-- ^ position of transaction within the block
}
| MemRef { memRefTime :: !PreciseUnixTime }
deriving (Show, Read, Eq, Ord, Generic, Serialize, Hashable)
-- | JSON serialization for 'BlockRef'.
blockRefPairs :: A.KeyValue kv => BlockRef -> [kv]
blockRefPairs BlockRef {blockRefHeight = h, blockRefPos = p} =
["height" .= h, "position" .= p]
blockRefPairs MemRef {memRefTime = t} = ["mempool" .= t]
confirmed :: BlockRef -> Bool
confirmed BlockRef {} = True
confirmed MemRef {} = False
instance ToJSON BlockRef where
toJSON = object . blockRefPairs
toEncoding = pairs . mconcat . blockRefPairs
-- | Transaction in relation to an address.
data AddressTx = AddressTx
{ addressTxAddress :: !Address
-- ^ transaction address
, addressTxBlock :: !BlockRef
-- ^ block information
, addressTxHash :: !TxHash
-- ^ transaction hash
} deriving (Show, Eq, Ord, Generic, Serialize, Hashable)
-- | JSON serialization for 'AddressTx'.
addressTxPairs :: A.KeyValue kv => Network -> AddressTx -> [kv]
addressTxPairs net atx =
[ "address" .= addrToJSON net (addressTxAddress atx)
, "txid" .= addressTxHash atx
, "block" .= addressTxBlock atx
]
addressTxToJSON :: Network -> AddressTx -> Value
addressTxToJSON net = object . addressTxPairs net
addressTxToEncoding :: Network -> AddressTx -> Encoding
addressTxToEncoding net = pairs . mconcat . addressTxPairs net
-- | Address balance information.
data Balance = Balance
{ balanceAddress :: !Address
-- ^ address balance
, balanceAmount :: !Word64
-- ^ confirmed balance
, balanceZero :: !Int64
-- ^ unconfirmed balance (can be negative)
, balanceCount :: !Word64
-- ^ number of unspent outputs
} deriving (Show, Read, Eq, Ord, Generic, Serialize, Hashable)
-- | JSON serialization for 'Balance'.
balancePairs :: A.KeyValue kv => Network -> Balance -> [kv]
balancePairs net ab =
[ "address" .= addrToJSON net (balanceAddress ab)
, "confirmed" .= balanceAmount ab
, "unconfirmed" .= balanceZero ab
, "utxo" .= balanceCount ab
]
balanceToJSON :: Network -> Balance -> Value
balanceToJSON net = object . balancePairs net
balanceToEncoding :: Network -> Balance -> Encoding
balanceToEncoding net = pairs . mconcat . balancePairs net
-- | Unspent output.
data Unspent = Unspent
{ unspentBlock :: !BlockRef
-- ^ block information for output
, unspentAmount :: !Word64
-- ^ value of output in satoshi
, unspentScript :: !ByteString
-- ^ pubkey (output) script
, unspentPoint :: !OutPoint
-- ^ txid and index where output located
} deriving (Show, Eq, Ord, Generic, Serialize, Hashable)
unspentPairs :: A.KeyValue kv => Network -> Unspent -> [kv]
unspentPairs net u =
[ "address" .=
eitherToMaybe (addrToJSON net <$> scriptToAddressBS (unspentScript u))
, "block" .= unspentBlock u
, "txid" .= outPointHash (unspentPoint u)
, "index" .= outPointIndex (unspentPoint u)
, "pkscript" .= String (encodeHex (unspentScript u))
, "value" .= unspentAmount u
]
unspentToJSON :: Network -> Unspent -> Value
unspentToJSON net = object . unspentPairs net
unspentToEncoding :: Network -> Unspent -> Encoding
unspentToEncoding net = pairs . mconcat . unspentPairs net
-- | Database value for a block entry.
data BlockData = BlockData
{ blockDataHeight :: !BlockHeight
-- ^ height of the block in the chain
, blockDataMainChain :: !Bool
-- ^ is this block in the main chain?
, blockDataWork :: !BlockWork
-- ^ accumulated work in that block
, blockDataHeader :: !BlockHeader
-- ^ block header
, blockDataSize :: !Word32
-- ^ size of the block including witnesses
, blockDataTxs :: ![TxHash]
-- ^ block transactions
} deriving (Show, Read, Eq, Ord, Generic, Serialize, Hashable)
-- | JSON serialization for 'BlockData'.
blockDataPairs :: A.KeyValue kv => BlockData -> [kv]
blockDataPairs bv =
[ "hash" .= headerHash (blockDataHeader bv)
, "height" .= blockDataHeight bv
, "mainchain" .= blockDataMainChain bv
, "previous" .= prevBlock (blockDataHeader bv)
, "time" .= blockTimestamp (blockDataHeader bv)
, "version" .= blockVersion (blockDataHeader bv)
, "bits" .= blockBits (blockDataHeader bv)
, "nonce" .= bhNonce (blockDataHeader bv)
, "size" .= blockDataSize bv
, "tx" .= blockDataTxs bv
]
instance ToJSON BlockData where
toJSON = object . blockDataPairs
toEncoding = pairs . mconcat . blockDataPairs
-- | Input information.
data Input
= Coinbase { inputPoint :: !OutPoint
-- ^ output being spent (should be null)
, inputSequence :: !Word32
-- ^ sequence
, inputSigScript :: !ByteString
-- ^ input script data (not valid script)
, inputWitness :: !(Maybe WitnessStack)
-- ^ witness data for this input (only segwit)
}
-- ^ coinbase details
| Input { inputPoint :: !OutPoint
-- ^ output being spent
, inputSequence :: !Word32
-- ^ sequence
, inputSigScript :: !ByteString
-- ^ signature (input) script
, inputPkScript :: !ByteString
-- ^ pubkey (output) script from previous tx
, inputAmount :: !Word64
-- ^ amount in satoshi being spent spent
, inputWitness :: !(Maybe WitnessStack)
-- ^ witness data for this input (only segwit)
}
-- ^ input details
deriving (Show, Read, Eq, Ord, Generic, Serialize, Hashable)
-- | Is 'Input' a Coinbase?
isCoinbase :: Input -> Bool
isCoinbase Coinbase {} = True
isCoinbase Input {} = False
-- | JSON serialization for 'Input'.
inputPairs :: A.KeyValue kv => Network -> Input -> [kv]
inputPairs net Input { inputPoint = OutPoint oph opi
, inputSequence = sq
, inputSigScript = ss
, inputPkScript = ps
, inputAmount = val
, inputWitness = wit
} =
[ "coinbase" .= False
, "txid" .= oph
, "output" .= opi
, "sigscript" .= String (encodeHex ss)
, "sequence" .= sq
, "pkscript" .= String (encodeHex ps)
, "value" .= val
, "address" .= eitherToMaybe (addrToJSON net <$> scriptToAddressBS ps)
] ++
["witness" .= fmap (map encodeHex) wit | getSegWit net]
inputPairs net Coinbase { inputPoint = OutPoint oph opi
, inputSequence = sq
, inputSigScript = ss
, inputWitness = wit
} =
[ "coinbase" .= False
, "txid" .= oph
, "output" .= opi
, "sigscript" .= String (encodeHex ss)
, "sequence" .= sq
, "pkscript" .= Null
, "value" .= Null
, "address" .= Null
] ++
["witness" .= fmap (map encodeHex) wit | getSegWit net]
inputToJSON :: Network -> Input -> Value
inputToJSON net = object . inputPairs net
inputToEncoding :: Network -> Input -> Encoding
inputToEncoding net = pairs . mconcat . inputPairs net
-- | Information about input spending output.
data Spender = Spender
{ spenderHash :: !TxHash
-- ^ input transaction hash
, spenderIndex :: !Word32
-- ^ input position in transaction
} deriving (Show, Read, Eq, Ord, Generic, Serialize, Hashable)
-- | JSON serialization for 'Spender'.
spenderPairs :: A.KeyValue kv => Spender -> [kv]
spenderPairs n =
["txid" .= spenderHash n, "input" .= spenderIndex n]
instance ToJSON Spender where
toJSON = object . spenderPairs
toEncoding = pairs . mconcat . spenderPairs
-- | Output information.
data Output = Output
{ outputAmount :: !Word64
-- ^ amount in satoshi
, outputScript :: !ByteString
-- ^ pubkey (output) script
, outputSpender :: !(Maybe Spender)
-- ^ input spending this transaction
} deriving (Show, Read, Eq, Ord, Generic, Serialize, Hashable)
-- | JSON serialization for 'Output'.
outputPairs :: A.KeyValue kv => Network -> Output -> [kv]
outputPairs net d =
[ "address" .=
eitherToMaybe (addrToJSON net <$> scriptToAddressBS (outputScript d))
, "pkscript" .= String (encodeHex (outputScript d))
, "value" .= outputAmount d
, "spent" .= isJust (outputSpender d)
] ++
["spender" .= outputSpender d | isJust (outputSpender d)]
outputToJSON :: Network -> Output -> Value
outputToJSON net = object . outputPairs net
outputToEncoding :: Network -> Output -> Encoding
outputToEncoding net = pairs . mconcat . outputPairs net
-- | Detailed transaction information.
data Transaction = Transaction
{ transactionBlock :: !BlockRef
-- ^ block information for this transaction
, transactionVersion :: !Word32
-- ^ transaction version
, transactionLockTime :: !Word32
-- ^ lock time
, transactionFee :: !Word64
-- ^ transaction fees paid to miners in satoshi
, transactionInputs :: ![Input]
-- ^ transaction inputs
, transactionOutputs :: ![Output]
-- ^ transaction outputs
, transactionDeleted :: !Bool
-- ^ this transaction has been deleted and is no longer valid
, transactionRBF :: !Bool
-- ^ this transaction can be replaced in the mempool
} deriving (Show, Eq, Ord, Generic, Hashable, Serialize)
transactionData :: Transaction -> Tx
transactionData t =
Tx
{ txVersion = transactionVersion t
, txIn = map i (transactionInputs t)
, txOut = map o (transactionOutputs t)
, txWitness = mapMaybe inputWitness (transactionInputs t)
, txLockTime = transactionLockTime t
}
where
i Coinbase {inputPoint = p, inputSequence = q, inputSigScript = s} =
TxIn {prevOutput = p, scriptInput = s, txInSequence = q}
i Input {inputPoint = p, inputSequence = q, inputSigScript = s} =
TxIn {prevOutput = p, scriptInput = s, txInSequence = q}
o Output {outputAmount = v, outputScript = s} =
TxOut {outValue = v, scriptOutput = s}
-- | JSON serialization for 'Transaction'.
transactionPairs :: A.KeyValue kv => Network -> Transaction -> [kv]
transactionPairs net dtx =
[ "txid" .= txHash (transactionData dtx)
, "size" .= B.length (S.encode (transactionData dtx))
, "version" .= transactionVersion dtx
, "locktime" .= transactionLockTime dtx
, "fee" .= transactionFee dtx
, "inputs" .= map (object . inputPairs net) (transactionInputs dtx)
, "outputs" .= map (object . outputPairs net) (transactionOutputs dtx)
, "block" .= transactionBlock dtx
, "deleted" .= transactionDeleted dtx
] ++
["rbf" .= transactionRBF dtx | getReplaceByFee net]
transactionToJSON :: Network -> Transaction -> Value
transactionToJSON net = object . transactionPairs net
transactionToEncoding :: Network -> Transaction -> Encoding
transactionToEncoding net = pairs . mconcat . transactionPairs net
-- | Information about a connected peer.
data PeerInformation
= PeerInformation { peerUserAgent :: !ByteString
-- ^ user agent string
, peerAddress :: !SockAddr
-- ^ network address
, peerVersion :: !Word32
-- ^ version number
, peerServices :: !Word64
-- ^ services field
, peerRelay :: !Bool
-- ^ will relay transactions
}
deriving (Show, Eq, Ord, Generic)
-- | JSON serialization for 'PeerInformation'.
peerInformationPairs :: A.KeyValue kv => PeerInformation -> [kv]
peerInformationPairs p =
[ "useragent" .= String (cs (peerUserAgent p))
, "address" .= String (cs (show (peerAddress p)))
, "version" .= peerVersion p
, "services" .= peerServices p
, "relay" .= peerRelay p
]
instance ToJSON PeerInformation where
toJSON = object . peerInformationPairs
toEncoding = pairs . mconcat . peerInformationPairs