haskoin-core-0.2.0: Network/Haskoin/Block/Merkle.hs
module Network.Haskoin.Block.Merkle
( MerkleBlock(..)
, MerkleRoot
, FlagBits
, PartialMerkleTree
, calcTreeHeight
, calcTreeWidth
, buildMerkleRoot
, calcHash
, buildPartialMerkle
, extractMatches
) where
import Control.Monad (forM_, replicateM)
import Control.DeepSeq (NFData, rnf)
import Data.Bits
import Data.Maybe
import Data.Word (Word8, Word32)
import Data.Binary (Binary, get, put)
import Data.Binary.Get (getWord8, getWord32le)
import Data.Binary.Put (putWord8, putWord32le)
import qualified Data.ByteString as BS
import Network.Haskoin.Crypto.Hash
import Network.Haskoin.Block.Types
import Network.Haskoin.Transaction.Types
import Network.Haskoin.Util
import Network.Haskoin.Constants
import Network.Haskoin.Node.Types
type MerkleRoot = Hash256
type FlagBits = [Bool]
type PartialMerkleTree = [Hash256]
data MerkleBlock =
MerkleBlock {
-- | Header information for this merkle block.
merkleHeader :: !BlockHeader
-- | Number of transactions in the block (including
-- unmatched transactions).
, merkleTotalTxns :: !Word32
-- | Hashes in depth-first order. They are used to rebuild a
-- partial merkle tree.
, mHashes :: ![Hash256]
-- | Flag bits, packed per 8 in a byte. Least significant bit
-- first. Flag bits are used to rebuild a partial merkle
-- tree.
, mFlags :: ![Bool]
} deriving (Eq, Show, Read)
instance NFData MerkleBlock where
rnf (MerkleBlock m t h f) = rnf m `seq` rnf t `seq` rnf h `seq` rnf f
instance Binary MerkleBlock where
get = do
header <- get
ntx <- getWord32le
(VarInt matchLen) <- get
hashes <- replicateM (fromIntegral matchLen) get
(VarInt flagLen) <- get
ws <- replicateM (fromIntegral flagLen) getWord8
return $ MerkleBlock header ntx hashes (decodeMerkleFlags ws)
put (MerkleBlock h ntx hashes flags) = do
put h
putWord32le ntx
put $ VarInt $ fromIntegral $ length hashes
forM_ hashes put
let ws = encodeMerkleFlags flags
put $ VarInt $ fromIntegral $ length ws
forM_ ws putWord8
decodeMerkleFlags :: [Word8] -> [Bool]
decodeMerkleFlags ws =
[ b | p <- [0..(length ws)*8-1]
, b <- [testBit (ws !! (p `div` 8)) (p `mod` 8)]
]
encodeMerkleFlags :: [Bool] -> [Word8]
encodeMerkleFlags bs = map boolsToWord8 $ splitIn 8 bs
-- | Computes the height of a merkle tree.
calcTreeHeight :: Int -- ^ Number of transactions (leaf nodes).
-> Int -- ^ Height of the merkle tree.
calcTreeHeight ntx | ntx < 2 = 0
| even ntx = 1 + calcTreeHeight (ntx `div` 2)
| otherwise = calcTreeHeight $ ntx + 1
-- | Computes the width of a merkle tree at a specific height. The transactions
-- are at height 0.
calcTreeWidth :: Int -- ^ Number of transactions (leaf nodes).
-> Int -- ^ Height at which we want to compute the width.
-> Int -- ^ Width of the merkle tree.
calcTreeWidth ntx h = (ntx + (1 `shiftL` h) - 1) `shiftR` h
-- | Computes the root of a merkle tree from a list of leaf node hashes.
buildMerkleRoot :: [TxHash] -- ^ List of transaction hashes (leaf nodes).
-> MerkleRoot -- ^ Root of the merkle tree.
buildMerkleRoot txs = calcHash (calcTreeHeight $ length txs) 0 txs
hash2 :: Hash256 -> Hash256 -> Hash256
hash2 a b = doubleHash256 $ encode' a `BS.append` encode' b
-- | Computes the hash of a specific node in a merkle tree.
calcHash :: Int -- ^ Height of the node in the merkle tree.
-> Int -- ^ Position of the node (0 for the leftmost node).
-> [TxHash] -- ^ Transaction hashes of the merkle tree (leaf nodes).
-> Hash256 -- ^ Hash of the node at the specified position.
calcHash height pos txs
| height < 0 || pos < 0 = error "calcHash: Invalid parameters"
| height == 0 = getTxHash $ txs !! pos
| otherwise = hash2 left right
where
left = calcHash (height-1) (pos*2) txs
right | pos*2+1 < calcTreeWidth (length txs) (height-1) =
calcHash (height-1) (pos*2+1) txs
| otherwise = left
-- | Build a partial merkle tree.
buildPartialMerkle
:: [(TxHash,Bool)]
-- ^ List of transactions hashes forming the leaves of the merkle tree
-- and a bool indicating if that transaction should be included in the
-- partial merkle tree.
-> (FlagBits, PartialMerkleTree)
-- ^ Flag bits (used to parse the partial merkle tree) and the
-- partial merkle tree.
buildPartialMerkle hs = traverseAndBuild (calcTreeHeight $ length hs) 0 hs
traverseAndBuild :: Int -> Int -> [(TxHash,Bool)]
-> (FlagBits, PartialMerkleTree)
traverseAndBuild height pos txs
| height < 0 || pos < 0 = error "traverseAndBuild: Invalid parameters"
| height == 0 || not match = ([match],[calcHash height pos t])
| otherwise = (match : lb ++ rb, lh ++ rh)
where
t = map fst txs
s = pos `shiftL` height
e = min (length txs) $ (pos+1) `shiftL` height
match = or $ map snd $ take (e-s) $ drop s txs
(lb,lh) = traverseAndBuild (height-1) (pos*2) txs
(rb,rh) | (pos*2+1) < calcTreeWidth (length txs) (height-1)
= traverseAndBuild (height-1) (pos*2+1) txs
| otherwise = ([],[])
traverseAndExtract :: Int -> Int -> Int -> FlagBits -> PartialMerkleTree
-> Maybe (MerkleRoot, [TxHash], Int, Int)
traverseAndExtract height pos ntx flags hashes
| length flags == 0 = Nothing
| height == 0 || not match = leafResult
| isNothing leftM = Nothing
| (pos*2+1) >= calcTreeWidth ntx (height-1) =
Just (hash2 lh lh, lm, lcf+1, lch)
| isNothing rightM = Nothing
| otherwise =
Just (hash2 lh rh, lm ++ rm, lcf+rcf+1, lch+rch)
where
leafResult
| null hashes = Nothing
| otherwise = Just
(h, if height == 0 && match then [TxHash h] else [], 1, 1)
(match:fs) = flags
(h:_) = hashes
leftM = traverseAndExtract (height-1) (pos*2) ntx fs hashes
(lh,lm,lcf,lch) = fromMaybe e leftM
rightM = traverseAndExtract (height-1) (pos*2+1) ntx
(drop lcf fs) (drop lch hashes)
(rh,rm,rcf,rch) = fromMaybe e rightM
e = error "traverseAndExtract: unexpected error extracting a Maybe value"
-- | Extracts the matching hashes from a partial merkle tree. This will return
-- the list of transaction hashes that have been included (set to True) in
-- a call to 'buildPartialMerkle'.
extractMatches :: FlagBits -- ^ Flag bits (produced by buildPartialMerkle).
-> PartialMerkleTree -- ^ Partial merkle tree.
-> Int -- ^ Number of transaction at height 0 (leaf nodes).
-> Either String (MerkleRoot, [TxHash])
-- ^ Merkle root and the list of matching transaction hashes.
extractMatches flags hashes ntx
| ntx == 0 = Left $
"extractMatches: number of transactions can not be 0"
| ntx > maxBlockSize `div` 60 = Left $
"extractMatches: number of transactions excessively high"
| length hashes > ntx = Left $
"extractMatches: More hashes provided than the number of transactions"
| length flags < length hashes = Left $
"extractMatches: At least one bit per node and one bit per hash"
| isNothing resM = Left $
"extractMatches: traverseAndExtract failed"
| (nBitsUsed+7) `div` 8 /= (length flags+7) `div` 8 = Left $
"extractMatches: All bits were not consumed"
| nHashUsed /= length hashes = Left $
"extractMatches: All hashes were not consumed: " ++ (show nHashUsed)
| otherwise = return (merkRoot, matches)
where
resM = traverseAndExtract (calcTreeHeight ntx) 0 ntx flags hashes
(merkRoot, matches, nBitsUsed, nHashUsed) = fromMaybe e resM
e = error "extractMatches: unexpected error extracting a Maybe value"
splitIn :: Int -> [a] -> [[a]]
splitIn _ [] = []
splitIn c xs = take c xs : (splitIn c $ drop c xs)
boolsToWord8 :: [Bool] -> Word8
boolsToWord8 [] = 0
boolsToWord8 xs = foldl setBit 0 (map snd $ filter fst $ zip xs [0..7])