{-# LANGUAGE BangPatterns #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} -- | -- Module: WorkerUtils -- Copyright: Copyright © 2021 Kadena LLC. -- License: MIT -- Maintainer: Lars Kuhtz -- Stability: experimental -- -- TODO -- module WorkerUtils ( -- * Block Creation Time getCurrentTimeMicros , injectTime , injectTime_ , encodeTimeToWord64 , incrementTimeMicros , incrementTimeMicros_ -- * Nonces , injectNonce , injectNonce_ -- * Check Target , checkTarget , fastCheckTarget , powHash , powHashToTargetWords ) where import Crypto.Hash import qualified Data.ByteArray as BA import Data.Bytes.Signed import qualified Data.ByteString.Short as BS import Data.Int import qualified Data.Memory.Endian as BA import Data.Time.Clock.System import Data.Word import Foreign.Ptr (castPtr) import Foreign.Storable (peekElemOff, pokeByteOff, peekByteOff) import GHC.Exts -- internal modules import Target import Utils import Worker import Data.Text.Unsafe (unsafeDupablePerformIO) -- -------------------------------------------------------------------------- -- -- Block Creation Time getCurrentTimeMicros :: IO Int64 getCurrentTimeMicros = do MkSystemTime secs nanos <- getSystemTime return $! secs * 1000000 + (int nanos `div` 1000) encodeTimeToWord64 :: Int64 -> Word64 encodeTimeToWord64 t = BA.unLE . BA.toLE $ unsigned t {-# INLINE encodeTimeToWord64 #-} decodeTimeToInt64 :: Word64 -> Int64 decodeTimeToInt64 t = signed . BA.fromLE $ BA.LE t {-# INLINE decodeTimeToInt64 #-} injectTime_ :: Int64 -> Ptr Word8 -> IO () injectTime_ t buf = pokeByteOff buf 8 $ encodeTimeToWord64 t {-# INLINE injectTime_ #-} injectTime :: Int64 -> Work -> Work injectTime t (Work bytes) = unsafeDupablePerformIO $ BS.useAsCStringLen bytes $ \(ptr, l) -> do injectTime_ t (castPtr ptr) Work <$> BS.packCStringLen (ptr, l) {-# INLINE injectTime #-} incrementTimeMicros_ :: Int64 -> Ptr Word8 -> IO () incrementTimeMicros_ i buf = do t <- decodeTimeToInt64 <$> peekByteOff buf 8 pokeByteOff buf 8 $ encodeTimeToWord64 (t + i) {-# INLINE incrementTimeMicros_ #-} -- | Increment the time value in a work header by the given number of -- microseconds -- incrementTimeMicros :: Int64 -> Work -> Work incrementTimeMicros t (Work bytes) = unsafeDupablePerformIO $ BS.useAsCStringLen bytes $ \(ptr, l) -> do incrementTimeMicros_ t (castPtr ptr) Work <$> BS.packCStringLen (ptr, l) {-# INLINE incrementTimeMicros #-} -- -------------------------------------------------------------------------- -- -- Check Work Headers -- | `injectNonce` makes low-level assumptions about the byte layout of a -- hashed `BlockHeader`. If that layout changes, this functions need to be -- updated. The assumption allows us to iterate on new nonces quickly. -- -- Recall: `Nonce` contains a `Word64`, and is thus 8 bytes long. -- -- See also: https://github.com/kadena-io/chainweb-node/wiki/Block-Header-Binary-Encoding -- injectNonce_ :: Nonce -> Ptr Word8 -> IO () injectNonce_ (Nonce n) buf = pokeByteOff buf 278 $ le64 n {-# INLINE injectNonce_ #-} injectNonce :: Nonce -> Work -> IO Work injectNonce n (Work bytes) = BS.useAsCStringLen bytes $ \(ptr, l) -> do injectNonce_ n (castPtr ptr) Work <$> BS.packCStringLen (ptr, l) {-# INLINE injectNonce #-} -- | `PowHashNat` interprets POW hashes as unsigned 256 bit integral numbers in -- little endian encoding, hence we compare against the target from the end of -- the bytes first, then move toward the front 8 bytes at a time. -- fastCheckTarget :: TargetWords -> Ptr Word64 -> IO Bool fastCheckTarget (TargetWords a b c d) !powPtr = checkTargetWordOff d 3 powPtr >>= \case LT -> return False GT -> return True EQ -> checkTargetWordOff c 2 powPtr >>= \case LT -> return False GT -> return True EQ -> checkTargetWordOff b 1 powPtr >>= \case LT -> return False GT -> return True EQ -> checkTargetWordOff a 0 powPtr >>= \case LT -> return False GT -> return True EQ -> return True {-# INLINE fastCheckTarget #-} checkTargetWordOff :: Word64 -> Int -> Ptr Word64 -> IO Ordering checkTargetWordOff !w !n !powPtr = compare w <$> peekElemOff powPtr n {-# INLINE checkTargetWordOff #-} checkTarget :: Target -> Work -> IO Bool checkTarget t w = do t' <- powHashToTargetWords (powHash w) return $ targetFromWords t' <= t {-# INLINE checkTarget #-} powHashToTargetWords :: Digest Blake2s_256 -> IO TargetWords powHashToTargetWords h = BA.withByteArray h $ \ptr -> TargetWords <$> peekWord64OffLe ptr 0 <*> peekWord64OffLe ptr 8 <*> peekWord64OffLe ptr 16 <*> peekWord64OffLe ptr 24 {-# INLINE powHashToTargetWords #-} powHash :: Work -> Digest Blake2s_256 powHash (Work bytes) = hash (BS.fromShort bytes) {-# INLINE powHash #-}