blockchain 0.0.1 → 0.0.2
raw patch · 28 files changed
+300/−252 lines, 28 files
Files
- blockchain.cabal +7/−6
- lib/Data/Blockchain/Core/Blockchain.hs +28/−23
- lib/Data/Blockchain/Core/Builder.hs +61/−0
- lib/Data/Blockchain/Core/Builder/Transaction.hs +0/−58
- lib/Data/Blockchain/Core/Crypto.hs +6/−4
- lib/Data/Blockchain/Core/Crypto/ECDSA.hs +10/−10
- lib/Data/Blockchain/Core/Crypto/Hash.hs +10/−10
- lib/Data/Blockchain/Core/Types.hs +10/−5
- lib/Data/Blockchain/Core/Types/BlockchainConfig.hs +24/−6
- lib/Data/Blockchain/Core/Types/Difficulty.hs +3/−3
- lib/Data/Blockchain/Core/Types/Hex.hs +64/−0
- lib/Data/Blockchain/Core/Types/Transaction.hs +3/−4
- lib/Data/Blockchain/Core/Util/Hex.hs +0/−58
- lib/Data/Blockchain/Mining.hs +7/−0
- lib/Data/Blockchain/Mining/Block.hs +14/−9
- lib/Data/Blockchain/Mining/Blockchain.hs +5/−4
- test/ArbitraryInstances.hs +8/−9
- test/Data/Blockchain/Core/Builder/TransactionSpec.hs +2/−2
- test/Data/Blockchain/Core/Crypto/ECDSASpec.hs +1/−1
- test/Data/Blockchain/Core/Crypto/HashSpec.hs +4/−4
- test/Data/Blockchain/Core/Types/BlockchainConfigSpec.hs +2/−3
- test/Data/Blockchain/Core/Types/DifficultySpec.hs +2/−2
- test/Data/Blockchain/Core/Types/HexSpec.hs +11/−0
- test/Data/Blockchain/Core/Types/TransactionSpec.hs +2/−2
- test/Data/Blockchain/Core/Util/HexSpec.hs +0/−11
- test/Data/Blockchain/Mining/BlockSpec.hs +1/−2
- test/Integration/Mining.hs +13/−14
- test/Main.hs +2/−2
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 ]