{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TupleSections #-}
{-# OPTIONS -Wall #-}
{-# OPTIONS -Werror=incomplete-patterns #-}
module Network.DFINITY.RadixTree (
RadixDatabase(..)
, RadixError(..)
, RadixRoot
, RadixTree
, createRadixTree
, subtreeRadixTree
, insertRadixTree
, deleteRadixTree
, merkleizeRadixTree
, lookupRadixTree
, lookupMerkleizedRadixTree
, lookupNonMerkleizedRadixTree
, isEmptyRadixTree
, isValidRadixRoot
, sourceMerkleizedRadixTree
, sinkMerkleizedRadixTree
, contentsRadixTree
, contentsMerkleizedRadixTree
, contentsNonMerkleizedRadixTree
, printRadixTree
, printMerkleizedRadixTree
, printNonMerkleizedRadixTree
) where
import Codec.Serialise (deserialise, deserialiseOrFail)
import Control.Concurrent (forkIO, killThread)
import Control.Concurrent.BoundedChan (BoundedChan, readChan, tryWriteChan)
import Control.Concurrent.MVar (modifyMVar_, newMVar, readMVar)
import Control.Exception (throw)
import Control.Monad (foldM, forM_, forever, void, when)
import Control.Monad.IO.Class (MonadIO(..))
import Control.Monad.Trans.Resource (MonadResource, ResourceT, allocate, release)
import Crypto.Hash.SHA256 (hash)
import Data.BloomFilter as Bloom (elem, insert, insertList)
import Data.Bool (bool)
import Data.ByteString.Char8 as Byte (ByteString, take)
import Data.ByteString.Lazy (fromStrict)
import Data.ByteString.Short (fromShort, toShort)
import Data.Conduit (ConduitM, await, yield)
import Data.Default.Class (def)
import Data.List as List (delete, foldl', null)
import Data.List.NonEmpty (NonEmpty(..), fromList)
import Data.LruCache as LRU (empty, insert, lookup)
import Data.Map.Strict as Map ((!), delete, empty, insert, keys, lookup, member, null, singleton)
import Data.Maybe (fromJust, isJust, isNothing, listToMaybe)
import Data.Tuple (swap)
import Database.LevelDB (DB, Options)
import Network.DFINITY.RadixTree.Bits
import Network.DFINITY.RadixTree.Bloom
import Network.DFINITY.RadixTree.Lenses
import Network.DFINITY.RadixTree.Memory
import Network.DFINITY.RadixTree.Types
import Network.DFINITY.RadixTree.Utilities
createRadixTree
:: RadixDatabase config m database
=> Int
-> Int
-> Maybe RadixRoot
-> config
-> m (RadixTree database)
{-# SPECIALISE createRadixTree
:: Int
-> Int
-> Maybe RadixRoot
-> (FilePath, Options)
-> ResourceT IO (RadixTree DB) #-}
createRadixTree bloomSize cacheSize checkpoint config
| bloomSize <= 0 = throw $ InvalidArgument "invalid Bloom filter size"
| cacheSize <= 0 = throw $ InvalidArgument "invalid LRU cache size"
| otherwise = do
database <- create config
(root, cache') <-
case checkpoint of
Nothing -> storeCold def cache database
Just root -> do
result <- loadCold root cache database
case snd <$> result of
Nothing -> throw $ StateRootDoesNotExist root
Just cache' -> pure (root, cache')
pure $ RadixTree bloom bloomSize Map.empty cache' cacheSize root database root
where
bloom = emptyRadixBloom bloomSize
cache = LRU.empty cacheSize
subtreeRadixTree
:: RadixDatabase config m database
=> RadixRoot
-> RadixTree database
-> m (RadixTree database)
{-# SPECIALISE subtreeRadixTree
:: RadixRoot
-> RadixTree DB
-> ResourceT IO (RadixTree DB) #-}
subtreeRadixTree root RadixTree {..} = do
result <- loadCold root cache _radixDatabase
case result of
Nothing -> throw $ StateRootDoesNotExist root
_ -> pure $ RadixTree bloom _radixBloomSize Map.empty cache _radixCacheSize root _radixDatabase root
where
bloom = emptyRadixBloom _radixBloomSize
cache = LRU.empty _radixCacheSize
isEmptyRadixTree
:: RadixTree database
-> Bool
{-# INLINABLE isEmptyRadixTree #-}
isEmptyRadixTree = (==) defaultRoot . _radixRoot
isValidRadixRoot
:: RadixDatabase config m database
=> RadixRoot
-> RadixTree database
-> m Bool
{-# SPECIALISE isValidRadixRoot
:: RadixRoot
-> RadixTree DB
-> ResourceT IO Bool #-}
isValidRadixRoot root RadixTree {..} =
isJust <$> load _radixDatabase key
where
key = fromShort root
searchRadixTree
:: RadixDatabase config m database
=> Bool
-> (RadixTree database -> m (Maybe (RadixNode, RadixCache)))
-> ByteString
-> RadixTree database
-> m (Either RadixError RadixSearchResult)
{-# SPECIALISE searchRadixTree
:: Bool
-> (RadixTree DB -> ResourceT IO (Maybe (RadixNode, RadixCache)))
-> ByteString
-> RadixTree DB
-> ResourceT IO (Either RadixError RadixSearchResult) #-}
searchRadixTree flag strategy = \ key tree@RadixTree {..} -> do
let key' = toBits key
let tree' = tree `bool` setRoot _radixCheckpoint tree $ flag
loop Nothing [] [] [] key' tree' where
loop implicit roots nodes prefixes key tree@RadixTree {..} = do
result <- strategy tree
case result of
Nothing -> pure $ Left $ StateRootDoesNotExist _radixRoot
Just (node@RadixNode {..}, cache') -> do
let bits = maybe id (:) implicit $ maybe [] toBits _radixPrefix
let prefix = matchBits bits key
let n = length prefix
let overflow = drop n bits
let roots' = _radixRoot:roots
let nodes' = node:nodes
let prefixes' = prefix:prefixes
let key' = drop n key
let residue = not $ List.null overflow
let bit = head key'
let child = bool _radixLeft _radixRight bit
if List.null key' || residue || isNothing child
then pure $ Right (fromList roots', fromList nodes', fromList prefixes', overflow, key', cache')
else do
let root' = fromJust child
let tree' = setCache cache' $ setRoot root' tree
let implicit' = Just bit
loop implicit' roots' nodes' prefixes' key' tree'
searchMerkleizedRadixTree
:: RadixDatabase config m database
=> ByteString
-> RadixTree database
-> m (Either RadixError RadixSearchResult)
{-# SPECIALISE searchMerkleizedRadixTree
:: ByteString
-> RadixTree DB
-> ResourceT IO (Either RadixError RadixSearchResult) #-}
searchMerkleizedRadixTree =
searchRadixTree True $ \ RadixTree {..} ->
loadCold _radixRoot _radixCache _radixDatabase
searchNonMerkleizedRadixTree
:: RadixDatabase config m database
=> ByteString
-> RadixTree database
-> m (Either RadixError RadixSearchResult)
{-# SPECIALISE searchNonMerkleizedRadixTree
:: ByteString
-> RadixTree DB
-> ResourceT IO (Either RadixError RadixSearchResult) #-}
searchNonMerkleizedRadixTree =
searchRadixTree False $ \ RadixTree {..} ->
loadHot _radixRoot _radixBuffer _radixCache _radixDatabase
insertRadixTree
:: RadixDatabase config m database
=> ByteString
-> ByteString
-> RadixTree database
-> m (RadixTree database)
{-# SPECIALISE insertRadixTree
:: ByteString
-> ByteString
-> RadixTree DB
-> ResourceT IO (RadixTree DB) #-}
insertRadixTree key value tree =
if isEmptyRadixTree tree
then pure $ initializeRadixTree key value tree
else searchNonMerkleizedRadixTree key tree >>= \ case
Left err -> throw err
Right result@(_, _, _, [], [], _) ->
pure $ insertRadixTreeAt result value tree
Right result@(_, _, _, [], _, _) ->
pure $ insertRadixTreeAfter result value tree
Right result@(_, _, _, _, [], _) ->
pure $ insertRadixTreeBefore result value tree
Right result ->
pure $ insertRadixTreeBetween result value tree
initializeRadixTree
:: ByteString
-> ByteString
-> RadixTree database
-> RadixTree database
{-# INLINABLE initializeRadixTree #-}
initializeRadixTree key value tree@RadixTree {..} =
seq bloom $ setBloom bloom $ setBuffer buffer $ setRoot root tree
where
prefix = createPrefix $ toBits key
node = setPrefix prefix $ Just value `setLeaf` def
root = createRoot node
bloom = Bloom.insert root _radixBloom
buffer = storeHot root node _radixBuffer
insertRadixTreeAt
:: RadixSearchResult
-> ByteString
-> RadixTree database
-> RadixTree database
{-# INLINABLE insertRadixTreeAt #-}
insertRadixTreeAt (_:|roots, node:|nodes, prefix:|_, _, _, cache) value tree@RadixTree {..} =
seq bloom $ setBloom bloom $ setBuffer buffer $ setCache cache $ setRoot state tree
where
node' = Just value `setLeaf` node
root' = createRoot node'
parent = listToMaybe $ zip3 roots nodes prefix
bloom = flip insertList _radixBloom $ root':roots
buffer = merkleSpoof root' parent $ storeHot root' node' _radixBuffer
state = bool _radixRoot root' $ isNothing parent
insertRadixTreeAfter
:: RadixSearchResult
-> ByteString
-> RadixTree database
-> RadixTree database
{-# INLINABLE insertRadixTreeAfter #-}
insertRadixTreeAfter (_:|roots, node:|nodes, prefix:|_, _, keyOverflow, cache) value tree@RadixTree {..} =
seq bloom $ setBloom bloom $ setBuffer buffer $ setCache cache $ setRoot state tree
where
prefix' = createPrefix $ drop 1 keyOverflow
node' = setPrefix prefix' $ Just value `setLeaf` def
root' = createRoot node'
node'' = test `setChild` Just root' $ node
root'' = createRoot node''
test = head keyOverflow
parent = listToMaybe $ zip3 roots nodes prefix
bloom = flip insertList _radixBloom $ root'':root':roots
buffer = merkleSpoof root'' parent $ storeHot root'' node'' $ storeHot root' node' _radixBuffer
state = bool _radixRoot root'' $ isNothing parent
insertRadixTreeBefore
:: RadixSearchResult
-> ByteString
-> RadixTree database
-> RadixTree database
{-# INLINABLE insertRadixTreeBefore #-}
insertRadixTreeBefore (_:|roots, node:|nodes, prefix:|_, prefixOverflow, _, cache) value tree@RadixTree {..} =
seq bloom $ setBloom bloom $ setBuffer buffer $ setCache cache $ setRoot state tree
where
prefix' = createPrefix $ drop 1 prefixOverflow
node' = setPrefix prefix' node
root' = createRoot node'
prefix'' = createPrefix $ drop 1 prefix `bool` prefix $ isNothing parent
node'' = setPrefix prefix'' $ test `setChild` Just root' $ Just value `setLeaf` def
root'' = createRoot node''
test = head prefixOverflow
parent = listToMaybe $ zip3 roots nodes prefix
bloom = flip insertList _radixBloom $ root'':root':roots
buffer = merkleSpoof root'' parent $ storeHot root'' node'' $ storeHot root' node' _radixBuffer
state = bool _radixRoot root'' $ isNothing parent
insertRadixTreeBetween
:: RadixSearchResult
-> ByteString
-> RadixTree database
-> RadixTree database
{-# INLINABLE insertRadixTreeBetween #-}
insertRadixTreeBetween (_:|roots, node:|nodes, prefix:|_, prefixOverflow, keyOverflow, cache) value tree@RadixTree {..} =
seq bloom $ setBloom bloom $ setBuffer buffer $ setCache cache $ setRoot state tree
where
prefix' = createPrefix $ drop 1 keyOverflow
node' = setPrefix prefix' $ Just value `setLeaf` def
root' = createRoot node'
prefix'' = createPrefix $ drop 1 prefixOverflow
node'' = setPrefix prefix'' node
root'' = createRoot node''
prefix''' = createPrefix $ drop 1 prefix `bool` prefix $ isNothing parent
node''' = setPrefix prefix''' $ setChildren children def
root''' = createRoot node'''
test = head keyOverflow
children = bool id swap test (Just root', Just root'')
parent = listToMaybe $ zip3 roots nodes prefix
bloom = flip insertList _radixBloom $ root''':root'':root':roots
buffer = merkleSpoof root''' parent $ storeHot root''' node''' $ storeHot root'' node'' $ storeHot root' node' _radixBuffer
state = bool _radixRoot root''' $ isNothing parent
deleteRadixTree
:: RadixDatabase config m database
=> ByteString
-> RadixTree database
-> m (RadixTree database)
{-# SPECIALISE deleteRadixTree
:: ByteString
-> RadixTree DB
-> ResourceT IO (RadixTree DB) #-}
deleteRadixTree key tree@RadixTree {..} =
if isEmptyRadixTree tree
then pure tree
else searchNonMerkleizedRadixTree key tree >>= \ case
Left err -> throw err
Right result@(_, nodes, prefix:|_, [], [], cache) ->
case nodes of
RadixNode _ Nothing Nothing _:|[] ->
pure $ deleteRadixTreeNoChildrenNoParent result tree
RadixNode _ Nothing Nothing _:|parent:_ | isJust $ getLeaf parent ->
pure $ deleteRadixTreeNoChildrenParentWithLeaf result tree
RadixNode _ Nothing Nothing _:|parent:_ -> do
let test = not $ head prefix
let root = fromJust $ getChild test parent
loadHot root _radixBuffer cache _radixDatabase >>= \ case
Nothing -> throw $ StateRootDoesNotExist root
Just (node, cache') ->
pure $ deleteRadixTreeNoChildrenParentWithoutLeaf result node cache' test tree
RadixNode _ child Nothing _:|_ | isJust child -> do
let test = False
let root = fromJust child
loadHot root _radixBuffer cache _radixDatabase >>= \ case
Nothing -> throw $ StateRootDoesNotExist root
Just (node, cache') ->
pure $ deleteRadixTreeOneChild result node cache' test tree
RadixNode _ Nothing child _:|_ | isJust child -> do
let test = True
let root = fromJust child
loadHot root _radixBuffer cache _radixDatabase >>= \ case
Nothing -> throw $ StateRootDoesNotExist root
Just (node, cache') ->
pure $ deleteRadixTreeOneChild result node cache' test tree
_ -> pure $ deleteRadixTreeTwoChildren result tree
Right _ -> pure tree
deleteRadixTreeNoChildrenNoParent
:: RadixSearchResult
-> RadixTree database
-> RadixTree database
{-# INLINABLE deleteRadixTreeNoChildrenNoParent #-}
deleteRadixTreeNoChildrenNoParent (_, _, _, _, _, cache) tree@RadixTree {..} =
seq bloom $ setBloom bloom $ setBuffer buffer $ setCache cache $ setRoot state tree
where
bloom = Bloom.insert defaultRoot _radixBloom
buffer = storeHot defaultRoot def _radixBuffer
state = defaultRoot
deleteRadixTreeNoChildrenParentWithLeaf
:: RadixSearchResult
-> RadixTree database
-> RadixTree database
{-# INLINABLE deleteRadixTreeNoChildrenParentWithLeaf #-}
deleteRadixTreeNoChildrenParentWithLeaf (_:|_:roots, _:|node:nodes, prefix:|prefixes, _, _, cache) tree@RadixTree {..} =
seq bloom $ setBloom bloom $ setBuffer buffer $ setCache cache $ setRoot state tree
where
node' = setChild test Nothing node
root' = createRoot node'
test = head prefix
parent = listToMaybe $ zip3 roots nodes $ map head prefixes
bloom = flip insertList _radixBloom $ root':roots
buffer = merkleSpoof root' parent $ storeHot root' node' _radixBuffer
state = bool _radixRoot root' $ isNothing parent
deleteRadixTreeNoChildrenParentWithLeaf _ _ =
throw $ InvalidArgument "unknown parent"
deleteRadixTreeNoChildrenParentWithoutLeaf
:: RadixSearchResult
-> RadixNode
-> RadixCache
-> Bool
-> RadixTree database
-> RadixTree database
{-# INLINABLE deleteRadixTreeNoChildrenParentWithoutLeaf #-}
deleteRadixTreeNoChildrenParentWithoutLeaf (_:|_:roots, _:|_:nodes, _:|prefixes, _, _, _) node@RadixNode {..} cache test tree@RadixTree {..} =
seq bloom $ setBloom bloom $ setBuffer buffer $ setCache cache $ setRoot state tree
where
prefix' = createPrefix $ drop 1 bits `bool` bits $ isNothing parent
node' = setPrefix prefix' node
root' = createRoot node'
bits = head prefixes ++ test:maybe [] toBits _radixPrefix
parent = listToMaybe $ zip3 roots nodes $ map head prefixes
bloom = flip insertList _radixBloom $ root':roots
buffer = merkleSpoof root' parent $ storeHot root' node' _radixBuffer
state = bool _radixRoot root' $ isNothing parent
deleteRadixTreeNoChildrenParentWithoutLeaf _ _ _ _ _ =
throw $ InvalidArgument "unknown parent"
deleteRadixTreeOneChild
:: RadixSearchResult
-> RadixNode
-> RadixCache
-> Bool
-> RadixTree database
-> RadixTree database
{-# INLINABLE deleteRadixTreeOneChild #-}
deleteRadixTreeOneChild (_:|roots, _:|nodes, prefix:|_, _, _, _) node@RadixNode {..} cache test tree@RadixTree {..} =
seq bloom $ setBloom bloom $ setBuffer buffer $ setCache cache $ setRoot state tree
where
prefix' = createPrefix $ drop 1 bits `bool` bits $ isNothing parent
node' = setPrefix prefix' node
root' = createRoot node'
bits = prefix ++ test:maybe [] toBits _radixPrefix
parent = listToMaybe $ zip3 roots nodes prefix
bloom = flip insertList _radixBloom $ root':roots
buffer = merkleSpoof root' parent $ storeHot root' node' _radixBuffer
state = bool _radixRoot root' $ isNothing parent
deleteRadixTreeTwoChildren
:: RadixSearchResult
-> RadixTree database
-> RadixTree database
{-# INLINABLE deleteRadixTreeTwoChildren #-}
deleteRadixTreeTwoChildren (_:|roots, node:|nodes, prefix:|_, _, _, cache) tree@RadixTree {..} =
seq bloom $ setBloom bloom $ setBuffer buffer $ setCache cache $ setRoot state tree
where
node' = setLeaf Nothing node
root' = createRoot node'
parent = listToMaybe $ zip3 roots nodes prefix
bloom = flip insertList _radixBloom $ root':roots
buffer = merkleSpoof root' parent $ storeHot root' node' _radixBuffer
state = bool _radixRoot root' $ isNothing parent
lookupRadixTree'
:: RadixDatabase config m database
=> (ByteString -> RadixTree database -> m (Either RadixError RadixSearchResult))
-> ByteString
-> RadixTree database
-> m (Maybe (ByteString, RadixTree database))
{-# SPECIALISE lookupRadixTree'
:: (ByteString -> RadixTree DB -> ResourceT IO (Either RadixError RadixSearchResult))
-> ByteString
-> RadixTree DB
-> ResourceT IO (Maybe (ByteString, RadixTree DB)) #-}
lookupRadixTree' search key tree = do
found <- search key tree
case found of
Left err -> throw err
Right (_, RadixNode {..}:|_, _, prefixOverflow, keyOverflow, cache') ->
if not $ List.null prefixOverflow && List.null keyOverflow
then pure Nothing
else pure $ do
value <- _radixLeaf
let tree' = setCache cache' tree
pure (value, tree')
lookupRadixTree
:: RadixDatabase config m database
=> ByteString
-> RadixTree database
-> m (Maybe (ByteString, RadixTree database))
{-# SPECIALISE lookupRadixTree
:: ByteString
-> RadixTree DB
-> ResourceT IO (Maybe (ByteString, RadixTree DB)) #-}
lookupRadixTree = lookupNonMerkleizedRadixTree
lookupMerkleizedRadixTree
:: RadixDatabase config m database
=> ByteString
-> RadixTree database
-> m (Maybe (ByteString, RadixTree database))
{-# SPECIALISE lookupMerkleizedRadixTree
:: ByteString
-> RadixTree DB
-> ResourceT IO (Maybe (ByteString, RadixTree DB)) #-}
lookupMerkleizedRadixTree = lookupRadixTree' searchMerkleizedRadixTree
lookupNonMerkleizedRadixTree
:: RadixDatabase config m database
=> ByteString
-> RadixTree database
-> m (Maybe (ByteString, RadixTree database))
{-# SPECIALISE lookupNonMerkleizedRadixTree
:: ByteString
-> RadixTree DB
-> ResourceT IO (Maybe (ByteString, RadixTree DB)) #-}
lookupNonMerkleizedRadixTree = lookupRadixTree' searchNonMerkleizedRadixTree
merkleSpoof
:: RadixRoot
-> Maybe (RadixRoot, RadixNode, Bool)
-> RadixBuffer
-> RadixBuffer
{-# INLINABLE merkleSpoof #-}
merkleSpoof mask = \ case
Nothing -> id
Just (root, node, test) ->
storeHot root $ test `setChild` Just mask $ node
merkleizeRadixTree
:: RadixDatabase config m database
=> RadixTree database
-> m (RadixRoot, RadixTree database)
{-# SPECIALISE merkleizeRadixTree
:: RadixTree DB
-> ResourceT IO (RadixRoot, RadixTree DB) #-}
merkleizeRadixTree RadixTree {..} = do
(root, cache) <- loop _radixRoot _radixCache
let tree = RadixTree bloom _radixBloomSize Map.empty cache _radixCacheSize root _radixDatabase root
pure (root, tree)
where
bloom = emptyRadixBloom _radixBloomSize
loop root cache =
if not $ Bloom.elem root _radixBloom
then pure (root, cache)
else do
result <- loadHot root _radixBuffer cache _radixDatabase
case result of
Nothing -> throw $ StateRootDoesNotExist root
Just (node@RadixNode {..}, cache') ->
case (_radixLeft, _radixRight) of
(Nothing, Nothing) ->
storeCold node cache' _radixDatabase
(Just child, Nothing) -> do
(root', cache'') <- loop child cache'
let node' = False `setChild` Just root' $ node
storeCold node' cache'' _radixDatabase
(Nothing, Just child) -> do
(root', cache'') <- loop child cache'
let node' = True `setChild` Just root' $ node
storeCold node' cache'' _radixDatabase
(Just left, Just right) -> do
(root', cache'') <- loop left cache'
(root'', cache''') <- loop right cache''
let node' = setChildren (Just root', Just root'') node
storeCold node' cache''' _radixDatabase
sourceMerkleizedRadixTree
:: MonadResource m
=> RadixDatabase config (ConduitM () ByteString m) database
=> [Bool]
-> Int
-> BoundedChan RadixRoot
-> RadixTree database
-> ConduitM () ByteString m ()
{-# SPECIALISE sourceMerkleizedRadixTree
:: [Bool]
-> Int
-> BoundedChan RadixRoot
-> RadixTree DB
-> ConduitM () ByteString (ResourceT IO) () #-}
sourceMerkleizedRadixTree mask cacheSize chan
| cacheSize <= 0 = throw $ InvalidArgument "invalid LRU cache size"
| otherwise = \ tree -> do
cache <- liftIO $ newMVar $ LRU.empty cacheSize
(,) action _ <- flip allocate killThread $ forkIO $ forever $ do
root <- readChan chan
modifyMVar_ cache $ pure . LRU.insert root ()
loop cache tree []
release action
where
loop cache tree@RadixTree {..} roots = do
seen <- liftIO $ readMVar cache
let roots' = _radixCheckpoint:roots
if flip any roots' $ isJust . flip LRU.lookup seen
then pure ()
else do
let key = fromShort _radixCheckpoint
result <- load _radixDatabase key
case result of
Nothing -> pure ()
Just bytes -> do
let RadixNode {..} = deserialise $ fromStrict bytes
let success = all id $ zipWith (==) mask $ toBits $ fromShort _radixCheckpoint
when success $ yield bytes
forM_ [_radixLeft, _radixRight] $ \ case
Nothing -> pure ()
Just root -> loop cache `flip` roots' $ setCheckpoint root tree
sinkMerkleizedRadixTree
:: MonadResource m
=> RadixDatabase config (ConduitM ByteString () m) database
=> RadixRoot
-> BoundedChan RadixRoot
-> RadixTree database
-> ConduitM ByteString () m (Either [RadixRoot] (RadixTree database))
{-# SPECIALISE sinkMerkleizedRadixTree
:: RadixRoot
-> BoundedChan RadixRoot
-> RadixTree DB
-> ConduitM ByteString () (ResourceT IO) (Either [RadixRoot] (RadixTree DB)) #-}
sinkMerkleizedRadixTree checkpoint chan tree@RadixTree {..} =
loop1 Map.empty $ singleton checkpoint Nothing where
loop1 = \ buffer want ->
if Map.null want
then pure $ Right $ setCheckpoint checkpoint $ setRoot checkpoint tree
else await >>= \ case
Nothing -> pure $ Left $ keys want
Just bytes ->
case deserialiseOrFail $ fromStrict bytes of
Right RadixNode {..} -> do
let key = Byte.take 20 $ hash bytes
let root = toShort key
let wanted = member root want
exists <- if wanted
then pure False
else isJust <$> load _radixDatabase key
if exists
then loop1 buffer $ Map.delete root want
else do
children <- foldM step [] $ maybe id (:) _radixLeft $ maybe id (:) _radixRight []
let buffer' = Map.insert root (key, bytes, children) buffer
if not wanted
then loop1 buffer' want
else loop3 buffer' `uncurry` loop2 buffer' (want, []) root
_ -> loop1 buffer want
where
step accum root = do
valid <- isValidRadixRoot root tree
if valid
then pure accum
else pure $ root:accum
loop2 buffer accum@(want, candidates) root =
case Map.lookup root buffer of
Nothing -> accum
Just (key, bytes, []) -> (want, (root, key, bytes):candidates)
Just (_, _, children) ->
let want' = foldr step want children
in foldl' (loop2 buffer) (want', candidates) children
where
step = flip Map.insert $ Just root
loop3 buffer want = \ case
[] -> loop1 buffer want
(root, key, bytes):candidates -> do
store _radixDatabase key bytes
let buffer' = Map.delete root buffer
case want ! root of
Nothing -> do
let want' = Map.delete root want
loop1 buffer' want'
Just root' -> do
let want' = Map.delete root want
let (key', bytes', siblings') = buffer ! root'
let children' = List.delete root siblings'
if List.null children'
then loop3 buffer' want' $ (root', key', bytes'):candidates
else do
let buffer'' = Map.insert root' (key', bytes', children') buffer'
liftIO $ void $ tryWriteChan chan root
loop3 buffer'' want' candidates
contentsRadixTree'
:: RadixDatabase config m database
=> Bool
-> (RadixTree database -> m (Maybe (RadixNode, RadixCache)))
-> RadixTree database
-> m [(ByteString, ByteString)]
{-# SPECIALISE contentsRadixTree'
:: Bool
-> (RadixTree DB -> ResourceT IO (Maybe (RadixNode, RadixCache)))
-> RadixTree DB
-> ResourceT IO [(ByteString, ByteString)] #-}
contentsRadixTree' flag strategy = \ tree@RadixTree {..} -> do
let tree' = tree `bool` setRoot _radixCheckpoint tree $ flag
loop tree' [] [] where
loop tree@RadixTree {..} prefix accum = do
result <- strategy tree
case fst <$> result of
Nothing -> throw $ StateRootDoesNotExist _radixRoot
Just RadixNode {..} -> do
let prefix' = prefix ++ maybe [] toBits _radixPrefix
let key = fromBits prefix'
let accum' = maybe accum (\ value -> (key, value):accum) _radixLeaf
let children = [(,False) <$> _radixLeft, (,True) <$> _radixRight]
flip foldM accum' `flip` children $ \ accum'' -> \ case
Nothing -> pure accum''
Just (root, test) -> do
let tree' = setRoot root tree
let prefix'' = prefix' ++ [test]
loop tree' prefix'' accum''
contentsRadixTree
:: RadixDatabase config m database
=> RadixTree database
-> m [(ByteString, ByteString)]
{-# SPECIALISE contentsRadixTree
:: RadixTree DB
-> ResourceT IO [(ByteString, ByteString)] #-}
contentsRadixTree = contentsNonMerkleizedRadixTree
contentsMerkleizedRadixTree
:: RadixDatabase config m database
=> RadixTree database
-> m [(ByteString, ByteString)]
{-# SPECIALISE contentsMerkleizedRadixTree
:: RadixTree DB
-> ResourceT IO [(ByteString, ByteString)] #-}
contentsMerkleizedRadixTree =
contentsRadixTree' True $ \ RadixTree {..} ->
loadCold _radixRoot _radixCache _radixDatabase
contentsNonMerkleizedRadixTree
:: RadixDatabase config m database
=> RadixTree database
-> m [(ByteString, ByteString)]
{-# SPECIALISE contentsNonMerkleizedRadixTree
:: RadixTree DB
-> ResourceT IO [(ByteString, ByteString)] #-}
contentsNonMerkleizedRadixTree =
contentsRadixTree' False $ \ RadixTree {..} ->
loadHot _radixRoot _radixBuffer _radixCache _radixDatabase
printRadixTree'
:: MonadIO m
=> RadixDatabase config m database
=> Bool
-> (RadixTree database -> m (Maybe (RadixNode, RadixCache)))
-> RadixTree database
-> m ()
{-# SPECIALISE printRadixTree'
:: Bool
-> (RadixTree DB -> ResourceT IO (Maybe (RadixNode, RadixCache)))
-> RadixTree DB
-> ResourceT IO () #-}
printRadixTree' flag strategy = \ tree@RadixTree {..} -> do
let tree' = tree `bool` setRoot _radixCheckpoint tree $ flag
loop tree' 0 where
loop tree@RadixTree {..} i = do
result <- strategy tree
case fst <$> result of
Nothing -> throw $ StateRootDoesNotExist _radixRoot
Just node@RadixNode {..} -> do
let indent = (++) $ concat $ replicate i "|"
liftIO $ putStrLn $ indent $ show node
let j = i + 1
forM_ [_radixLeft, _radixRight] $ \ case
Nothing -> pure ()
Just root -> setRoot root tree `loop` j
printRadixTree
:: MonadIO m
=> RadixDatabase config m database
=> RadixTree database
-> m ()
{-# SPECIALISE printRadixTree
:: RadixTree DB
-> ResourceT IO () #-}
printRadixTree = printNonMerkleizedRadixTree
printMerkleizedRadixTree
:: MonadIO m
=> RadixDatabase config m database
=> RadixTree database
-> m ()
{-# SPECIALISE printMerkleizedRadixTree
:: RadixTree DB
-> ResourceT IO () #-}
printMerkleizedRadixTree =
printRadixTree' True $ \ RadixTree {..} ->
loadCold _radixRoot _radixCache _radixDatabase
printNonMerkleizedRadixTree
:: MonadIO m
=> RadixDatabase config m database
=> RadixTree database
-> m ()
{-# SPECIALISE printNonMerkleizedRadixTree
:: RadixTree DB
-> ResourceT IO () #-}
printNonMerkleizedRadixTree =
printRadixTree' False $ \ RadixTree {..} ->
loadHot _radixRoot _radixBuffer _radixCache _radixDatabase