packages feed

blockchain 0.0.1 → 0.0.2

raw patch · 28 files changed

+300/−252 lines, 28 files

Files

blockchain.cabal view
@@ -3,7 +3,7 @@ -- see: https://github.com/sol/hpack  name:           blockchain-version:        0.0.1+version:        0.0.2 synopsis:       Generic blockchain implementation. description:    Please see README.md category:       Blockchain@@ -41,20 +41,21 @@     , unordered-containers   exposed-modules:       Data.Blockchain.Core.Blockchain-      Data.Blockchain.Core.Builder.Transaction+      Data.Blockchain.Core.Builder       Data.Blockchain.Core.Crypto+      Data.Blockchain.Core.Types+      Data.Blockchain.Mining+  other-modules:       Data.Blockchain.Core.Crypto.ECDSA       Data.Blockchain.Core.Crypto.Hash       Data.Blockchain.Core.Crypto.HashTree-      Data.Blockchain.Core.Types       Data.Blockchain.Core.Types.Block       Data.Blockchain.Core.Types.BlockchainConfig       Data.Blockchain.Core.Types.Difficulty+      Data.Blockchain.Core.Types.Hex       Data.Blockchain.Core.Types.Transaction-      Data.Blockchain.Core.Util.Hex       Data.Blockchain.Mining.Block       Data.Blockchain.Mining.Blockchain-  other-modules:       Paths_blockchain   default-language: Haskell2010 @@ -94,8 +95,8 @@       Data.Blockchain.Core.Crypto.HashTreeSpec       Data.Blockchain.Core.Types.BlockchainConfigSpec       Data.Blockchain.Core.Types.DifficultySpec+      Data.Blockchain.Core.Types.HexSpec       Data.Blockchain.Core.Types.TransactionSpec-      Data.Blockchain.Core.Util.HexSpec       Data.Blockchain.Mining.BlockSpec       Integration.Mining       Integration.Stats
lib/Data/Blockchain/Core/Blockchain.hs view
@@ -1,5 +1,4 @@ module Data.Blockchain.Core.Blockchain-    -- Types     ( Validated     , Unvalidated     , Blockchain@@ -8,14 +7,14 @@     , BlockchainNode(..)     , ValidationException(..)     , BlockException(..)-    -- Construction+    -- * Construction     , construct     , validate     , addBlock-    -- Validation+    -- * Validation     , validateTransaction     , validateTransactions-    -- Chain inspection+    -- * Chain inspection     , blockHeaderHashDifficulty     , addressValues     , unspentTransactionOutputs@@ -23,30 +22,34 @@     , flatten     ) where -import           Control.Monad                 (unless)-import qualified Data.Aeson                    as Aeson-import qualified Data.Aeson.Types              as Aeson-import qualified Data.Char                     as Char-import qualified Data.Either                   as Either-import qualified Data.Either.Combinators       as Either-import qualified Data.Foldable                 as Foldable-import qualified Data.HashMap.Strict           as H-import qualified Data.List                     as List-import qualified Data.List.NonEmpty            as NonEmpty-import           Data.Monoid                   ((<>))-import qualified Data.Ord                      as Ord-import qualified Data.Word                     as Word-import qualified GHC.Generics                  as Generic+import           Control.Monad               (unless)+import qualified Data.Aeson                  as Aeson+import qualified Data.Aeson.Types            as Aeson+import qualified Data.Char                   as Char+import qualified Data.Either                 as Either+import qualified Data.Either.Combinators     as Either+import qualified Data.Foldable               as Foldable+import qualified Data.HashMap.Strict         as H+import qualified Data.List                   as List+import qualified Data.List.NonEmpty          as NonEmpty+import           Data.Monoid                 ((<>))+import qualified Data.Ord                    as Ord+import qualified Data.Word                   as Word+import qualified GHC.Generics                as Generic -import qualified Data.Blockchain.Core.Crypto   as Crypto+import qualified Data.Blockchain.Core.Crypto as Crypto import           Data.Blockchain.Core.Types-import qualified Data.Blockchain.Core.Util.Hex as Hex  -- Types ----------------------------------------------------------------------------------------------------  data Validated data Unvalidated +-- | Core blockchain data type. Uses a validation tag to declare if it is known to abide by expected blockchain rules.+-- Will be either @'Blockchain' 'Validated'@ or @'Blockchain' 'Unvalidated'@.+--+-- Note: both @'Blockchain' 'Validated'@ and @'Blockchain' 'Unvalidated'@ can be serialized to json,+-- while only @'Blockchain' 'Unvalidated'@ can be deserialized from json. data Blockchain a = Blockchain     { _config :: BlockchainConfig     , _node   :: BlockchainNode@@ -105,10 +108,12 @@  -- Construction --------------------------------------------------------------------------------------------- +-- | Constructs an unvalidated blockchain from a config and a node.+-- Allows arbitrary blockchains to be constructed. However, blockchains are generally not useful until validated. construct :: BlockchainConfig -> BlockchainNode -> Blockchain Unvalidated construct = Blockchain -+-- | Validates a blockchain. Returns a 'ValidationException' if provided blockchain does not meet expected rules. validate :: Blockchain Unvalidated -> Either ValidationException (Blockchain Validated) validate (Blockchain config (BlockchainNode genesisBlock nodes)) = do     let (Block header _coinbase txs) = genesisBlock@@ -124,7 +129,7 @@   where     getBlocks (BlockchainNode block ns) = block : (ns >>= getBlocks) -+-- | Adds a block to a validated blockchain. Returns a 'BlockException' if block is not able to be inserted into the blockchain. addBlock :: Block -> Blockchain Validated -> Either BlockException (Blockchain Validated) addBlock newBlock (Blockchain config node) = Blockchain config <$> addBlockToNode mempty node   where@@ -191,7 +196,7 @@ -- Exported util -- TODO: find better place for this function -blockHeaderHashDifficulty :: Hex.Hex256 -> BlockHeader -> Difficulty+blockHeaderHashDifficulty :: Hex256 -> BlockHeader -> Difficulty blockHeaderHashDifficulty diff1 header = fromIntegral $ diff1 `div` Crypto.hashToHex (Crypto.hash header)  
+ lib/Data/Blockchain/Core/Builder.hs view
@@ -0,0 +1,61 @@+module Data.Blockchain.Core.Builder+    ( CreateTransactionException(..)+    , createSimpleTransaction+    ) where++import           Control.Monad                   (unless, when)+import           Control.Monad.Trans.Class       (lift)++import qualified Control.Error.Util              as Error+import qualified Control.Monad.Except            as Except+import qualified Data.HashMap.Strict             as H+import qualified Data.List.NonEmpty              as NonEmpty+import qualified Data.Word                       as Word++import qualified Data.Blockchain.Core.Blockchain as Blockchain+import qualified Data.Blockchain.Core.Crypto     as Crypto+import qualified Data.Blockchain.Core.Types      as Blockchain++data CreateTransactionException+    = SourceAddressEmpty+    | SourceAddressInsufficientFunds+    | InvalidPrivateKey+  deriving (Eq, Show)++-- TODO+-- createTransaction+--     :: [Crypto.KeyPair] -> [(Crypto.PublicKey, Int)] -> Int -> Blockchain.Blockchain Blockchain.Validated+--     -> Either CreateTransactionException Blockchain.Transaction+-- createTransaction _srcs _targets _fee _blockchain = undefined++-- | Create a transaction from a single public key address.+createSimpleTransaction+    :: Crypto.KeyPair   -- ^ KeyPair for source address. PrivateKey will be used to sign transaction.+    -> Crypto.PublicKey -- ^ Target address+    -> Word.Word        -- ^ Transaction value+    -> Word.Word        -- ^ Fee+    -> Blockchain.Blockchain Blockchain.Validated -- ^ Validated blockchain+    -> IO (Either CreateTransactionException Blockchain.Transaction)+createSimpleTransaction (Crypto.KeyPair srcPubKey srcPrivKey) targetPubKey value fee blockchain = Except.runExceptT $ do+    let unspentTransactionOutputs = Blockchain.unspentTransactionOutputs blockchain++    txOutPairs <- Error.failWith SourceAddressEmpty $ H.lookup srcPubKey unspentTransactionOutputs++    let txOuts     = snd <$> txOutPairs+        totalValue = sum $ Blockchain.value <$> txOuts++    when (totalValue < (value + fee)) $ Except.throwError SourceAddressInsufficientFunds++    txIns <- sequence $ flip fmap txOutPairs $ \(txOutRef, txOut) -> do+        sig <- lift $ Blockchain.signTransaction srcPrivKey txOut+        unless (Blockchain.verifyTransactionSignature sig txOut) $ Except.throwError InvalidPrivateKey++        return (Blockchain.TransactionIn txOutRef sig)++    let refund           = totalValue - (value + fee)+        txOut            = Blockchain.TransactionOut value targetPubKey+        maybeRefundTxOut = if refund > 0 then Just (Blockchain.TransactionOut refund srcPubKey)+                                         else Nothing+        txOuts'          = maybe (pure txOut) (: pure txOut) maybeRefundTxOut++    return $ Blockchain.Transaction (NonEmpty.fromList txIns) (NonEmpty.fromList txOuts')
− lib/Data/Blockchain/Core/Builder/Transaction.hs
@@ -1,58 +0,0 @@-module Data.Blockchain.Core.Builder.Transaction-    ( CreateTransactionException(..)-    -- , createTransaction-    , createSimpleTransaction-    ) where--import           Control.Monad                   (unless, when)-import           Control.Monad.Trans.Class       (lift)--import qualified Control.Error.Util              as Error-import qualified Control.Monad.Except            as Except-import qualified Data.HashMap.Strict             as H-import qualified Data.List.NonEmpty              as NonEmpty-import qualified Data.Word                       as Word--import qualified Data.Blockchain.Core.Blockchain as Blockchain-import qualified Data.Blockchain.Core.Crypto     as Crypto-import qualified Data.Blockchain.Core.Types      as Blockchain--data CreateTransactionException-    = SourceAddressEmpty-    | SourceAddressInsufficientFunds-    | InvalidPrivateKey-  deriving (Eq, Show)---- TODO--- createTransaction---     :: [Crypto.KeyPair] -> [(Crypto.PublicKey, Int)] -> Int -> Blockchain.Blockchain Blockchain.Validated---     -> Either CreateTransactionException Blockchain.Transaction--- createTransaction _srcs _targets _fee _blockchain = undefined--createSimpleTransaction-    :: Crypto.KeyPair -> Crypto.PublicKey-    -> Word.Word -> Word.Word -> Blockchain.Blockchain Blockchain.Validated-    -> IO (Either CreateTransactionException Blockchain.Transaction)-createSimpleTransaction (Crypto.KeyPair srcPubKey srcPrivKey) targetPubKey value fee blockchain = Except.runExceptT $ do-    let unspentTransactionOutputs = Blockchain.unspentTransactionOutputs blockchain--    txOutPairs <- Error.failWith SourceAddressEmpty $ H.lookup srcPubKey unspentTransactionOutputs--    let txOuts     = snd <$> txOutPairs-        totalValue = sum $ Blockchain.value <$> txOuts--    when (totalValue < (value + fee)) $ Except.throwError SourceAddressInsufficientFunds--    txIns <- sequence $ flip fmap txOutPairs $ \(txOutRef, txOut) -> do-        sig <- lift $ Blockchain.signTransaction srcPrivKey txOut-        unless (Blockchain.verifyTransactionSignature sig txOut) $ Except.throwError InvalidPrivateKey--        return (Blockchain.TransactionIn txOutRef sig)--    let refund           = totalValue - (value + fee)-        txOut            = Blockchain.TransactionOut value targetPubKey-        maybeRefundTxOut = if refund > 0 then Just (Blockchain.TransactionOut refund srcPubKey)-                                         else Nothing-        txOuts'          = maybe (pure txOut) (: pure txOut) maybeRefundTxOut--    return $ Blockchain.Transaction (NonEmpty.fromList txIns) (NonEmpty.fromList txOuts')
lib/Data/Blockchain/Core/Crypto.hs view
@@ -1,7 +1,9 @@ module Data.Blockchain.Core.Crypto-    ( module X+    ( module Data.Blockchain.Core.Crypto.ECDSA+    , module Data.Blockchain.Core.Crypto.Hash+    , module Data.Blockchain.Core.Crypto.HashTree     ) where -import           Data.Blockchain.Core.Crypto.ECDSA    as X-import           Data.Blockchain.Core.Crypto.Hash     as X-import           Data.Blockchain.Core.Crypto.HashTree as X+import           Data.Blockchain.Core.Crypto.ECDSA+import           Data.Blockchain.Core.Crypto.Hash+import           Data.Blockchain.Core.Crypto.HashTree
lib/Data/Blockchain/Core/Crypto/ECDSA.hs view
@@ -8,17 +8,17 @@     , generate     ) where -import qualified Crypto.Hash                   as Crypto-import qualified Crypto.PubKey.ECC.ECDSA       as Crypto-import qualified Crypto.PubKey.ECC.Generate    as Crypto-import qualified Crypto.PubKey.ECC.Types       as Crypto-import qualified Data.Aeson                    as Aeson-import qualified Data.ByteString               as BS-import qualified Data.Hashable                 as H-import           Data.Monoid                   ((<>))-import qualified Data.Text                     as Text+import qualified Crypto.Hash                    as Crypto+import qualified Crypto.PubKey.ECC.ECDSA        as Crypto+import qualified Crypto.PubKey.ECC.Generate     as Crypto+import qualified Crypto.PubKey.ECC.Types        as Crypto+import qualified Data.Aeson                     as Aeson+import qualified Data.ByteString                as BS+import qualified Data.Hashable                  as H+import           Data.Monoid                    ((<>))+import qualified Data.Text                      as Text -import           Data.Blockchain.Core.Util.Hex+import           Data.Blockchain.Core.Types.Hex  -- Types ----------------------------------------------------------------------------------------------------- 
lib/Data/Blockchain/Core/Crypto/Hash.hs view
@@ -6,17 +6,17 @@     , unsafeFromByteString     ) where -import qualified Crypto.Hash                   as Crypto-import qualified Data.Aeson                    as Aeson-import qualified Data.ByteArray.Encoding       as Byte-import qualified Data.ByteString               as BS-import qualified Data.ByteString.Lazy          as Lazy-import qualified Data.Hashable                 as H-import qualified Data.Maybe                    as Maybe-import qualified Data.Text                     as Text-import qualified Data.Text.Encoding            as Text+import qualified Crypto.Hash                    as Crypto+import qualified Data.Aeson                     as Aeson+import qualified Data.ByteArray.Encoding        as Byte+import qualified Data.ByteString                as BS+import qualified Data.ByteString.Lazy           as Lazy+import qualified Data.Hashable                  as H+import qualified Data.Maybe                     as Maybe+import qualified Data.Text                      as Text+import qualified Data.Text.Encoding             as Text -import qualified Data.Blockchain.Core.Util.Hex as Hex+import qualified Data.Blockchain.Core.Types.Hex as Hex  -- Types ----------------------------------------------------------------------------------------------------- 
lib/Data/Blockchain/Core/Types.hs view
@@ -1,8 +1,13 @@ module Data.Blockchain.Core.Types-    ( module X+    ( module Data.Blockchain.Core.Types.Block+    , module Data.Blockchain.Core.Types.BlockchainConfig+    , module Data.Blockchain.Core.Types.Difficulty+    , module Data.Blockchain.Core.Types.Hex+    , module Data.Blockchain.Core.Types.Transaction     ) where -import           Data.Blockchain.Core.Types.Block            as X-import           Data.Blockchain.Core.Types.BlockchainConfig as X-import           Data.Blockchain.Core.Types.Difficulty       as X-import           Data.Blockchain.Core.Types.Transaction      as X+import           Data.Blockchain.Core.Types.Block+import           Data.Blockchain.Core.Types.BlockchainConfig+import           Data.Blockchain.Core.Types.Difficulty+import           Data.Blockchain.Core.Types.Hex+import           Data.Blockchain.Core.Types.Transaction
lib/Data/Blockchain/Core/Types/BlockchainConfig.hs view
@@ -13,12 +13,12 @@  import           Data.Blockchain.Core.Types.Block import           Data.Blockchain.Core.Types.Difficulty-import qualified Data.Blockchain.Core.Util.Hex         as Hex+import           Data.Blockchain.Core.Types.Hex  data BlockchainConfig = BlockchainConfig     { initialDifficulty             :: Difficulty     -- Maximum hash - difficulties will be calculated using this value-    , difficulty1Target             :: Hex.Hex256+    , difficulty1Target             :: Hex256     , targetSecondsPerBlock         :: Word.Word     , difficultyRecalculationHeight :: Word.Word     , initialMiningReward           :: Word.Word@@ -30,16 +30,32 @@ instance Aeson.FromJSON BlockchainConfig instance Aeson.ToJSON BlockchainConfig +-- | A reasonable default config to use for testing. Mines blocks quickly and changes difficulty and rewards frequently.+-- Note: reward will go to zero after 1100 blocks, which will take about 180 minutes of mining.+--+-- @+-- defaultConfig :: BlockchainConfig+-- defaultConfig = BlockchainConfig+--     { initialDifficulty             = Difficulty 1+--     , difficulty1Target             = hex256LeadingZeros 4+--     , targetSecondsPerBlock         = 10+--     , difficultyRecalculationHeight = 50+--     , initialMiningReward           = 1024+--     , miningRewardHalvingHeight     = 100+--     }+-- @+-- defaultConfig :: BlockchainConfig defaultConfig = BlockchainConfig     { initialDifficulty             = Difficulty 1-    , difficulty1Target             = Hex.hex256LeadingZeros 4+    , difficulty1Target             = hex256LeadingZeros 4     , targetSecondsPerBlock         = 10-    , difficultyRecalculationHeight = 100-    , initialMiningReward           = 100-    , miningRewardHalvingHeight     = 500+    , difficultyRecalculationHeight = 50+    , initialMiningReward           = 1024+    , miningRewardHalvingHeight     = 100     } +-- | Calculates the target reward for a blockchain. Uses the longest chain. targetReward :: BlockchainConfig -> Word.Word -> Word.Word targetReward config height = either id id $ do     let initialReward = initialMiningReward config@@ -58,6 +74,8 @@ -- TODO: array of blocks hold no assurances of expected invariants -- for example block1 could be created more recently than blockN -- should create a `SingleChain` wrapper+-- TODO: take in entire blockchain+-- | Calculates the target difficulty for a blockchain. Uses the longest chain. targetDifficulty :: BlockchainConfig -> [Block] -> Difficulty targetDifficulty config []                                            = initialDifficulty config targetDifficulty config _ | difficultyRecalculationHeight config == 0 = initialDifficulty config
lib/Data/Blockchain/Core/Types/Difficulty.hs view
@@ -2,10 +2,10 @@     ( Difficulty(..)     ) where -import qualified Data.Aeson                    as Aeson-import qualified Numeric.Natural               as Natural+import qualified Data.Aeson                     as Aeson+import qualified Numeric.Natural                as Natural -import qualified Data.Blockchain.Core.Util.Hex as Hex+import qualified Data.Blockchain.Core.Types.Hex as Hex  newtype Difficulty = Difficulty { unDifficulty :: Natural.Natural }   deriving (Aeson.FromJSON, Aeson.ToJSON, Enum, Eq, Integral, Num, Real, Ord, Show)
+ lib/Data/Blockchain/Core/Types/Hex.hs view
@@ -0,0 +1,64 @@+module Data.Blockchain.Core.Types.Hex+    ( Hex256(..)+    , hex256+    , hex256LeadingZeros+    ) where++import           Control.Monad   (unless)+import qualified Data.Aeson      as Aeson+import qualified Data.Maybe      as Maybe+import           Data.Monoid     ((<>))+import qualified Data.Text       as Text+import qualified Data.Word       as Word+import qualified Numeric+import qualified Numeric.Natural as Natural++-- Types -----------------------------------------------------------------------------------------------------++newtype Hex256 = Hex256 { unHex256 :: Natural.Natural }+  deriving (Enum, Eq, Integral, Num, Real, Ord)++instance Show Hex256 where+    show = zeroPadded 64 . showHex . unHex256++instance Bounded Hex256 where+    minBound = 0+    maxBound = Maybe.fromMaybe (error "Unexpected parse failure") $ readHexMaybe $ replicate 64 'f'++instance Aeson.ToJSON Hex256 where+    toJSON = Aeson.String . Text.pack . show++instance Aeson.FromJSON Hex256 where+    parseJSON = Aeson.withText "Hex256" parseHex256+      where+        parseHex256 = maybe (fail "Invalid hex 256 string") return . hex256 . Text.unpack++-- Construction helpers --------------------------------------------------------------------------------------++-- | Create a Hex256 value with the specificed amount of leading zeros.+-- Useful for creating a 'Data.Blockchain.Core.Types.BlockchainConfig.difficulty1Target' when creating a blockchain.+--+-- >>> hex256LeadingZeros 4+-- 0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff+--+hex256LeadingZeros :: Word.Word -> Hex256+hex256LeadingZeros n = maxBound `div` Hex256 (16 ^ n)++hex256 :: String -> Maybe Hex256+hex256 str = do+    unless (length str == 64) Nothing+    x <- readHexMaybe str+    return (Hex256 x)++readHexMaybe :: (Num a, Eq a) => String -> Maybe a+readHexMaybe str = case Numeric.readHex str of+    [(x, "")] -> Just x+    _         -> Nothing++-- Utils -----------------------------------------------------------------------------------------------------++showHex :: (Show a, Integral a) => a -> String+showHex x = Numeric.showHex x mempty++zeroPadded :: Int -> String -> String+zeroPadded x str = replicate (x - length str) '0' <> str
lib/Data/Blockchain/Core/Types/Transaction.hs view
@@ -40,8 +40,7 @@  data TransactionIn = TransactionIn     { transactionOutRef :: TransactionOutRef-     -- Signature from prev transaction, using pubkey from prev transaction-    , signature         :: Crypto.Signature+    , signature         :: Crypto.Signature -- ^ Signature from prev transaction, using pubkey from prev transaction     }   deriving (Generic.Generic, Eq, Show) @@ -49,7 +48,7 @@ instance Aeson.FromJSON TransactionIn instance Crypto.ToHash TransactionIn --- Pointer to a specific TransactionOut+-- | Pointer to a specific TransactionOut data TransactionOutRef = TransactionOutRef     { transactionHash     :: Either (Crypto.Hash CoinbaseTransaction) (Crypto.Hash Transaction)     , transactionOutIndex :: Word.Word@@ -63,7 +62,7 @@  data TransactionOut = TransactionOut     { value           :: Word.Word-    , signaturePubKey :: Crypto.PublicKey -- aka. address of where funds go+    , signaturePubKey :: Crypto.PublicKey -- ^ Address of where funds go     }   deriving (Generic.Generic, Eq, Show) 
− lib/Data/Blockchain/Core/Util/Hex.hs
@@ -1,58 +0,0 @@-module Data.Blockchain.Core.Util.Hex-    ( Hex256(..)-    , hex256-    , hex256LeadingZeros-    ) where--import           Control.Monad   (unless)-import qualified Data.Aeson      as Aeson-import qualified Data.Maybe      as Maybe-import           Data.Monoid     ((<>))-import qualified Data.Text       as Text-import qualified Data.Word       as Word-import qualified Numeric-import qualified Numeric.Natural as Natural---- Types -------------------------------------------------------------------------------------------------------newtype Hex256 = Hex256 { unHex256 :: Natural.Natural }-  deriving (Enum, Eq, Integral, Num, Real, Ord)--instance Show Hex256 where-    show = zeroPadded 64 . showHex . unHex256--instance Bounded Hex256 where-    minBound = 0-    maxBound = Maybe.fromMaybe (error "Unexpected parse failure") $ readHexMaybe $ replicate 64 'f'--instance Aeson.ToJSON Hex256 where-    toJSON = Aeson.String . Text.pack . show--instance Aeson.FromJSON Hex256 where-    parseJSON = Aeson.withText "Hex256" parseHex256-      where-        parseHex256 = maybe (fail "Invalid hex 256 string") return . hex256 . Text.unpack---- Construction helpers ----------------------------------------------------------------------------------------hex256LeadingZeros :: Word.Word -> Hex256-hex256LeadingZeros n = maxBound `div` Hex256 (16 ^ n)--hex256 :: String -> Maybe Hex256-hex256 str = do-    unless (length str == 64) Nothing-    x <- readHexMaybe str-    return (Hex256 x)--readHexMaybe :: (Num a, Eq a) => String -> Maybe a-readHexMaybe str = case Numeric.readHex str of-    [(x, "")] -> Just x-    _         -> Nothing---- Utils -------------------------------------------------------------------------------------------------------showHex :: (Show a, Integral a) => a -> String-showHex x = Numeric.showHex x mempty--zeroPadded :: Int -> String -> String-zeroPadded x str = replicate (x - length str) '0' <> str
+ lib/Data/Blockchain/Mining.hs view
@@ -0,0 +1,7 @@+module Data.Blockchain.Mining+    ( module Data.Blockchain.Mining.Block+    , module Data.Blockchain.Mining.Blockchain+    ) where++import           Data.Blockchain.Mining.Block+import           Data.Blockchain.Mining.Blockchain
lib/Data/Blockchain/Mining/Block.hs view
@@ -13,15 +13,16 @@ import qualified Data.Blockchain.Core.Blockchain as Blockchain import qualified Data.Blockchain.Core.Crypto     as Crypto import qualified Data.Blockchain.Core.Types      as Blockchain-import qualified Data.Blockchain.Core.Util.Hex   as Hex  data MineBlockException     = InvalidTransactionList-    -- | ...   deriving (Eq, Show) +-- | Finds the next block of a blockchain. Depending on blockchain configuration, this function may take a long time to complete. mineBlock-    :: Crypto.PublicKey -> [Blockchain.Transaction] -> Blockchain.Blockchain Blockchain.Validated+    :: Crypto.PublicKey         -- ^ PublicKey address where coinbase reward will be sent+    -> [Blockchain.Transaction] -- ^ List of transactions to include in transaction+    -> Blockchain.Blockchain Blockchain.Validated -- ^ Validated blockchain     -> IO (Either MineBlockException Blockchain.Block) mineBlock pubKey txs blockchain =     case Blockchain.validateTransactions blockchain txs of@@ -36,16 +37,20 @@     prevBlock           = NonEmpty.last prevBlocks     prevBlockHeaderHash = Crypto.hash (Blockchain.blockHeader prevBlock) -+-- | Finds the next block of a blockchain, without including any transactions.+-- Most useful for testing - removes the invariant of an invalid transaction list.+-- Depending on blockchain configuration, this function may take a long time to complete. mineEmptyBlock-    :: Crypto.PublicKey -> Blockchain.Blockchain Blockchain.Validated+    :: Crypto.PublicKey -- ^ PublicKey address where coinbase reward will be sent+    -> Blockchain.Blockchain Blockchain.Validated -- ^ Validated blockchain     -> IO (Either MineBlockException Blockchain.Block) mineEmptyBlock pubKey = mineBlock pubKey mempty -+-- | Finds the first block of a blockchain.+-- Depending on blockchain configuration, this function may take a long time to complete.+-- Note: this generates a keypair but throws away the private key. Coinbase reward in genesis block cannot never be spent. mineGenesisBlock :: Blockchain.BlockchainConfig -> IO Blockchain.Block mineGenesisBlock config = do-    -- Note: ignore private key, coinbase reward in genesis block cannot be spent     (Crypto.KeyPair pubKey _privKey) <- Crypto.generate      mineBlockInternal pubKey reward diff1 difficulty prevBlockHeaderHash mempty@@ -57,7 +62,7 @@  -- TODO: accept multiple public keys mineBlockInternal-    :: Crypto.PublicKey -> Word.Word -> Hex.Hex256 -> Blockchain.Difficulty+    :: Crypto.PublicKey -> Word.Word -> Blockchain.Hex256 -> Blockchain.Difficulty     -> Crypto.Hash Blockchain.BlockHeader -> [Blockchain.Transaction]     -> IO Blockchain.Block mineBlockInternal pubKey reward diff1 difficulty prevBlockHeaderHash txs = do@@ -79,7 +84,7 @@     :: Crypto.Hash Blockchain.BlockHeader     -> Crypto.Hash Blockchain.CoinbaseTransaction     -> Crypto.HashTreeRoot Blockchain.Transaction-    -> Hex.Hex256+    -> Blockchain.Hex256     -> Blockchain.Difficulty     -> IO Blockchain.BlockHeader mineHeader prevBlockHeaderHash coinbaseTransactionHash transactionHashTreeRoot diff1 difficulty = do
lib/Data/Blockchain/Mining/Blockchain.hs view
@@ -1,5 +1,5 @@ module Data.Blockchain.Mining.Blockchain-    ( createBlockchain+    ( mineBlockchain     ) where  import           Data.Monoid                     ((<>))@@ -8,9 +8,10 @@ import qualified Data.Blockchain.Core.Types      as Blockchain import           Data.Blockchain.Mining.Block --createBlockchain :: Blockchain.BlockchainConfig -> IO (Blockchain.Blockchain Blockchain.Validated)-createBlockchain config = either throwValidationError id <$> do+-- | Creates a blockchain from the given config.+-- This includes mining a genesis block.+mineBlockchain :: Blockchain.BlockchainConfig -> IO (Blockchain.Blockchain Blockchain.Validated)+mineBlockchain config = either throwValidationError id <$> do     genesisBlock <- mineGenesisBlock config      let node  = Blockchain.BlockchainNode genesisBlock mempty
test/ArbitraryInstances.hs view
@@ -6,18 +6,17 @@  import           Test.QuickCheck -import qualified Crypto.PubKey.ECC.ECDSA       as Crypto-import qualified Crypto.PubKey.ECC.Types       as Crypto-import qualified Data.ByteString.Char8         as BS-import qualified Data.List.NonEmpty            as NonEmpty-import           Data.Monoid                   ((<>))-import qualified Data.Time.Calendar            as Time-import qualified Data.Time.Clock               as Time-import qualified Data.Word                     as Word+import qualified Crypto.PubKey.ECC.ECDSA     as Crypto+import qualified Crypto.PubKey.ECC.Types     as Crypto+import qualified Data.ByteString.Char8       as BS+import qualified Data.List.NonEmpty          as NonEmpty+import           Data.Monoid                 ((<>))+import qualified Data.Time.Calendar          as Time+import qualified Data.Time.Clock             as Time+import qualified Data.Word                   as Word  import           Data.Blockchain.Core.Crypto import           Data.Blockchain.Core.Types-import           Data.Blockchain.Core.Util.Hex  -- Blockchain types 
test/Data/Blockchain/Core/Builder/TransactionSpec.hs view
@@ -2,10 +2,10 @@  import           TestUtil -import qualified Data.List.NonEmpty                       as NonEmpty+import qualified Data.List.NonEmpty              as NonEmpty  import           Data.Blockchain.Core.Blockchain-import           Data.Blockchain.Core.Builder.Transaction+import           Data.Blockchain.Core.Builder import           Data.Blockchain.Core.Crypto import           Data.Blockchain.Core.Types 
test/Data/Blockchain/Core/Crypto/ECDSASpec.hs view
@@ -2,7 +2,7 @@  import           TestUtil -import           Data.Blockchain.Core.Crypto.ECDSA+import           Data.Blockchain.Core.Crypto  spec :: Spec spec =
test/Data/Blockchain/Core/Crypto/HashSpec.hs view
@@ -2,11 +2,11 @@  import           TestUtil -import qualified Data.Aeson                       as Aeson-import qualified Data.ByteString                  as BS-import qualified Data.ByteString.Char8            as Char8+import qualified Data.Aeson                  as Aeson+import qualified Data.ByteString             as BS+import qualified Data.ByteString.Char8       as Char8 -import           Data.Blockchain.Core.Crypto.Hash+import           Data.Blockchain.Core.Crypto  spec :: Spec spec =
test/Data/Blockchain/Core/Types/BlockchainConfigSpec.hs view
@@ -2,11 +2,10 @@  import           TestUtil -import           Data.Monoid                   ((<>))-import qualified Data.Time.Clock               as Time+import           Data.Monoid                ((<>))+import qualified Data.Time.Clock            as Time  import           Data.Blockchain.Core.Types-import           Data.Blockchain.Core.Util.Hex  testConfig :: BlockchainConfig testConfig = BlockchainConfig
test/Data/Blockchain/Core/Types/DifficultySpec.hs view
@@ -2,9 +2,9 @@  import           TestUtil -import qualified Data.Aeson                            as Aeson+import qualified Data.Aeson                 as Aeson -import           Data.Blockchain.Core.Types.Difficulty+import           Data.Blockchain.Core.Types  spec :: Spec spec =
+ test/Data/Blockchain/Core/Types/HexSpec.hs view
@@ -0,0 +1,11 @@+module Data.Blockchain.Core.Types.HexSpec (spec) where++import           TestUtil++import           Data.Blockchain.Core.Types++spec :: Spec+spec =+    describe "Data.Blockchain.Core.Util.Hex" $ do+        safeJSONDeserializeSpec (Proxy :: Proxy Hex256)+        roundTripJSONSpec (Proxy :: Proxy Hex256)
test/Data/Blockchain/Core/Types/TransactionSpec.hs view
@@ -2,8 +2,8 @@  import           TestUtil -import           Data.Blockchain.Core.Crypto.ECDSA-import           Data.Blockchain.Core.Types.Transaction+import           Data.Blockchain.Core.Crypto+import           Data.Blockchain.Core.Types  spec :: Spec spec =
− test/Data/Blockchain/Core/Util/HexSpec.hs
@@ -1,11 +0,0 @@-module Data.Blockchain.Core.Util.HexSpec (spec) where--import           TestUtil--import           Data.Blockchain.Core.Util.Hex--spec :: Spec-spec =-    describe "Data.Blockchain.Core.Util.Hex" $ do-        safeJSONDeserializeSpec (Proxy :: Proxy Hex256)-        roundTripJSONSpec (Proxy :: Proxy Hex256)
test/Data/Blockchain/Mining/BlockSpec.hs view
@@ -4,8 +4,7 @@  import           Data.Blockchain.Core.Blockchain import           Data.Blockchain.Core.Types-import           Data.Blockchain.Core.Util.Hex-import           Data.Blockchain.Mining.Block+import           Data.Blockchain.Mining  spec :: Spec spec = describe "Data.Blockchain.Mining.Block" $ do
test/Integration/Mining.hs view
@@ -2,20 +2,19 @@     ( mineTestChain     ) where -import qualified Control.Concurrent.Async          as Async-import qualified Control.Concurrent.MVar           as MVar-import           Control.Monad                     (forM, void)-import qualified Data.Aeson                        as Aeson-import qualified Data.ByteString.Lazy              as Lazy-import qualified Data.List.NonEmpty                as NonEmpty-import           Data.Monoid                       ((<>))-import qualified Data.Time.Clock                   as Time+import qualified Control.Concurrent.Async        as Async+import qualified Control.Concurrent.MVar         as MVar+import           Control.Monad                   (forM, void)+import qualified Data.Aeson                      as Aeson+import qualified Data.ByteString.Lazy            as Lazy+import qualified Data.List.NonEmpty              as NonEmpty+import           Data.Monoid                     ((<>))+import qualified Data.Time.Clock                 as Time -import qualified Data.Blockchain.Core.Blockchain   as B-import qualified Data.Blockchain.Core.Crypto       as Crypto-import qualified Data.Blockchain.Core.Types        as B-import qualified Data.Blockchain.Mining.Block      as Mining-import qualified Data.Blockchain.Mining.Blockchain as Mining+import qualified Data.Blockchain.Core.Blockchain as B+import qualified Data.Blockchain.Core.Crypto     as Crypto+import qualified Data.Blockchain.Core.Types      as B+import qualified Data.Blockchain.Mining          as Mining  data MinerSpec = MinerSpec     { mid               :: Int@@ -42,7 +41,7 @@     -> (Int -> B.Blockchain B.Validated -> IO ()) -> IO () runTestMultiMiner numMiners config persist = do     putStrLn "Creating blockchain and mining genesis block"-    blockchain <- Mining.createBlockchain config+    blockchain <- Mining.mineBlockchain config      let idList = [1..numMiners] 
test/Main.hs view
@@ -11,8 +11,8 @@ import qualified Data.Blockchain.Core.Crypto.HashTreeSpec import qualified Data.Blockchain.Core.Types.BlockchainConfigSpec import qualified Data.Blockchain.Core.Types.DifficultySpec+import qualified Data.Blockchain.Core.Types.HexSpec import qualified Data.Blockchain.Core.Types.TransactionSpec-import qualified Data.Blockchain.Core.Util.HexSpec import qualified Data.Blockchain.Mining.BlockSpec import qualified Integration.Mining import qualified Integration.Stats@@ -35,7 +35,7 @@         , Data.Blockchain.Core.Crypto.HashTreeSpec.spec         , Data.Blockchain.Core.Types.BlockchainConfigSpec.spec         , Data.Blockchain.Core.Types.DifficultySpec.spec+        , Data.Blockchain.Core.Types.HexSpec.spec         , Data.Blockchain.Core.Types.TransactionSpec.spec-        , Data.Blockchain.Core.Util.HexSpec.spec         , Data.Blockchain.Mining.BlockSpec.spec         ]