packages feed

blockchain-0.0.0.1: test/Integration/Stats.hs

module Integration.Stats
    ( printStats
    ) where

-- TODO: this module might be useful elsewhere outside of "Test" group

import qualified Data.HashMap.Strict             as H
import qualified Data.List                       as List
import qualified Data.List.NonEmpty              as NonEmpty
import           Data.Monoid                     ((<>))
import           Data.Ratio                      (Ratio)
import qualified Data.Time.Clock                 as Time
import           Data.Word                       (Word)
import           Numeric.Natural                 (Natural)

import qualified Data.Blockchain.Core.Blockchain as B
import qualified Data.Blockchain.Core.Types      as B

data BlockchainStats = BlockchainStats
    { numBlocks             :: Word
    , totalTime             :: Time.NominalDiffTime
    , totalHashes           :: Word
    , difficultyProgression :: [Natural]
    , rewardProgression     :: [Word]
    , config                :: B.BlockchainConfig
    }

printStats :: B.Blockchain B.Validated -> IO ()
printStats blockchain = do
    let stats    = computeStats blockchain
        outputs  = H.toList $ B.addressValues blockchain
        outputsP = List.intercalate "\n" $ (\(k, v) -> show k <> ": " <> show v) <$> outputs

    putStrLn $ List.intercalate "\n"
        [ mempty
        , "Final test chain mining stats"
        , "  Length:                 " <> show (numBlocks stats)
        , "  Total time:             " <> show (totalTime stats)
        , "  Time per block:         " <> show (timePerBlock stats)
        , "  Total hashes:           " <> show (totalHashes stats)
        , "  Hashes per block:       " <> show (hashesPerBlock stats)
        , "  Hash rate (hash/s):     " <> show (hashRate stats)
        , "  Last difficulty:        " <> show (last (difficultyProgression stats))
        , "  Difficulty progression: " <> show (difficultyProgression stats)
        , "  Last reward:            " <> show (last (rewardProgression stats))
        , "  Reward progression:     " <> show (rewardProgression stats)
        , mempty
        , "Config used"
        , show (config stats)
        , mempty
        , "Current address values"
        , outputsP
        ]


computeStats :: B.Blockchain B.Validated -> BlockchainStats
computeStats blockchain = BlockchainStats{..}
  where
    numBlocks             = fromIntegral (length mainChain)
    totalTime             = Time.diffUTCTime (B.time lastHeader) (B.time firstHeader)
    totalHashes           = fromIntegral $ sum $ (succ . B.nonce) <$> headers -- Add 1 to each nonce to account for first hash
    mainChain             = NonEmpty.toList $ B.longestChain blockchain
    headers               = B.blockHeader <$> mainChain
    coinbaseTxOuts        = B.coinbaseTransactionOut . B.coinbaseTransaction <$> mainChain
    firstHeader           = head headers
    lastHeader            = last headers
    difficultyProgression = List.nub $ (B.unDifficulty . B.difficulty) <$> headers
    rewardProgression     = List.nub $ (sum . fmap B.value) <$> coinbaseTxOuts
    config                = B.blockchainConfig blockchain

timePerBlock :: BlockchainStats -> Time.NominalDiffTime
timePerBlock BlockchainStats{..} = totalTime / fromIntegral numBlocks

hashesPerBlock :: BlockchainStats -> Word
hashesPerBlock BlockchainStats{..} = totalHashes `div` numBlocks

-- TODO: better return type?
-- TODO: compute hash rate only for last N blocks
hashRate :: BlockchainStats -> Word
hashRate BlockchainStats{..} = round (fromIntegral totalHashes / realToFrac totalTime :: Ratio Integer)