{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- |
-- Module: Worker.Simulation
-- Copyright: Copyright © 2020 Kadena LLC.
-- License: MIT
-- Maintainer: Lars Kuhtz
-- Stability: experimental
--
-- Simulation Mining Worker
--
-- A fake mining worker that is not actually doing any work. It calculates the
-- solve time base on the assumed hash power of the worker thread and returns
-- the work bytes unchanged after that time has passed.
--
module Worker.Simulation
( HashRate(..)
, defaultHashRate
, simulationWorker
) where
import Control.Concurrent (threadDelay)
import Data.Aeson
import qualified Data.ByteString.Short as BS
import Data.Hashable
import qualified Data.Text as T
import GHC.Generics
import System.LogLevel
import qualified System.Random.MWC as MWC
import qualified System.Random.MWC.Distributions as MWC
-- internal modules
import Logger
import Worker
-- -------------------------------------------------------------------------- --
-- HashRate
newtype HashRate = HashRate Double
deriving (Show, Eq, Ord, Generic)
deriving newtype (Read, Num, Fractional, Floating, Real, Enum, Hashable, ToJSON, FromJSON)
-- | Default is 1MH
defaultHashRate :: HashRate
defaultHashRate = 1_000_000
-- -------------------------------------------------------------------------- --
-- Simulation Mining Worker
-- | A fake mining worker that is not actually doing any work. It calculates the
-- solve time base on the assumed hash power of the worker thread and returns
-- the work bytes unchanged after that time has passed.
--
simulationWorker :: Logger -> MWC.GenIO -> HashRate -> Worker
simulationWorker logger rng rate _nonce (Target targetBytes) work = do
delay <- round <$> MWC.exponential scale rng
logg Info $ "solve time (microseconds): " <> T.pack (show delay)
threadDelay delay
return work
where
logg = writeLog logger
-- expectedMicros = 1_000_000 * difficulty / rate
-- MWC.exponential is parameterized by the rate, i.e. 1 / expected_time
scale = realToFrac $ realToFrac rate / (difficulty * 1_000_000)
-- the expected number of attempts for solving a target is the difficulty.
difficulty :: Rational
difficulty = 2^(256 :: Integer) / targetNum
-- Target is an little endian encoded (unsigned) 256 bit word.
targetNum :: Rational
targetNum = foldr (\b a -> fromIntegral b + 256 * a) 0 $ BS.unpack $ targetBytes