dfinity-radix-tree-0.5.0: src/DFINITY/RadixTree/Types.hs
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RecordWildCards #-}
{-# OPTIONS -Wall #-}
{-# OPTIONS -Werror #-}
module DFINITY.RadixTree.Types
( RadixBloom
, RadixBuffer
, RadixCache
, RadixDatabase(..)
, RadixError(..)
, RadixNode(..)
, RadixPrefix(..)
, RadixProof(..)
, RadixRoot
, RadixSearchResult
, RadixTree(..)
) where
import Codec.Serialise as CBOR (Serialise(..), serialise)
import Codec.Serialise.Decoding (decodeBytes, decodeInt, decodeListLen)
import Codec.Serialise.Encoding (encodeBytes, encodeInt, encodeListLen)
import Control.DeepSeq (NFData(..))
import Control.Exception (Exception)
import Control.Monad (void)
import Control.Monad.IO.Class (MonadIO (liftIO))
import Control.Monad.ST (ST)
import Control.Monad.State.Strict as StateT (StateT, get, modify)
import Crypto.Hash.BLAKE2.BLAKE2s (hash)
import Data.BloomFilter (Bloom)
import Data.Bool (bool)
import Data.ByteString.Base16 as Base16 (encode)
import Data.ByteString.Char8 (ByteString, unpack)
import Data.ByteString.Lazy (toStrict)
import Data.ByteString.Short (ShortByteString, fromShort, toShort)
import Data.Data (Data)
import Data.Default.Class (Default(..))
import Data.IORef (IORef, readIORef, modifyIORef')
import Data.List.NonEmpty (NonEmpty(..))
import Data.LruCache (LruCache)
import Data.Map.Strict as Map (Map, insert, lookup)
import Data.Maybe (isJust)
import Data.Monoid ((<>))
import Data.STRef (STRef, readSTRef, modifySTRef')
import Text.Printf (printf)
import qualified Database.LevelDB as LevelDB
import qualified Database.LMDB.Simple as LMDB
import DFINITY.RadixTree.Bits
import DFINITY.RadixTree.Serialise
type RadixBloom = Bloom RadixRoot
type RadixBuffer = Map RadixRoot RadixNode
type RadixCache = LruCache RadixRoot RadixNode
class Monad m => RadixDatabase m database where
load :: database -> ByteString -> m (Maybe ByteString)
store :: database -> ByteString -> ByteString -> m ()
instance Monad m => RadixDatabase (StateT (Map ByteString ByteString) m) () where
load () key = Map.lookup key <$> StateT.get
store () key = StateT.modify . Map.insert key
instance RadixDatabase (ST s) (STRef s (Map ByteString ByteString)) where
load db key = Map.lookup key <$> readSTRef db
store db key val = modifySTRef' db $ Map.insert key val
instance MonadIO m => RadixDatabase m (IORef (Map ByteString ByteString)) where
load db key = liftIO $ Map.lookup key <$> readIORef db
store db key val = liftIO $ modifyIORef' db $ Map.insert key val
instance MonadIO m => RadixDatabase m LevelDB.DB where
load db = LevelDB.get db LevelDB.defaultReadOptions
store db = LevelDB.put db LevelDB.defaultWriteOptions
instance RadixDatabase (LMDB.Transaction LMDB.ReadWrite) (LMDB.Database ByteString ByteString) where
load db key = LMDB.get db key
store db key val = LMDB.put db key $ Just val
data RadixError
= InvalidArgument String
| StateRootDoesNotExist RadixRoot
deriving (Data, Eq, Show)
instance Exception RadixError
data RadixNode
= RadixNode
{ _radixPrefix :: Maybe RadixPrefix
, _radixLeft :: Maybe RadixRoot
, _radixRight :: Maybe RadixRoot
, _radixLeaf :: Maybe ByteString
} deriving (Data, Eq)
instance NFData RadixNode where
rnf RadixNode {..} =
rnf _radixPrefix `seq`
rnf _radixLeft `seq`
rnf _radixRight `seq`
rnf _radixLeaf `seq`
()
instance Default RadixNode where
def = RadixNode Nothing Nothing Nothing Nothing
instance Serialise RadixNode where
encode RadixNode {..} =
encodeListLen len <>
encodeMaybe CBOR.encode _radixPrefix <>
encodeMaybe encodeSide left <>
encodeMaybe encodeSide right <>
maybe mempty encodeBytes _radixLeaf
where
len = bool 3 4 $ isJust _radixLeaf
left = fromShort <$> _radixLeft
right = fromShort <$> _radixRight
decode = do
len <- decodeListLen
prefix <- decodeMaybe decode
left <- decodeMaybe $ toShort <$> decodeSide
right <- decodeMaybe $ toShort <$> decodeSide
leaf <- decodeLeaf len
pure $ RadixNode prefix left right leaf
instance Show RadixNode where
show node@RadixNode {..} =
case unpack <$> _radixLeaf of
Nothing -> printf "\ESC[96m%s\ESC[0m@[\ESC[97m%s\ESC[0m,\ESC[96m%s\ESC[0m,\ESC[96m%s\ESC[0m]" root prefix left right
Just leaf -> printf "\ESC[96m%s\ESC[0m@[\ESC[97m%s\ESC[0m,\ESC[96m%s\ESC[0m,\ESC[96m%s\ESC[0m,\ESC[97m%s\ESC[0m]" root prefix left right leaf
where
root = format $ hash 20 mempty $ toStrict $ serialise node
prefix = guard show _radixPrefix
left = guard format $ fromShort <$> _radixLeft
right = guard format $ fromShort <$> _radixRight
guard = maybe "null"
format = take 8 . unpack . Base16.encode
data RadixPrefix
= RadixPrefix
{ _radixBitLen :: Int
, _radixName :: ByteString
} deriving (Data, Eq)
instance Bitable RadixPrefix where
toBits RadixPrefix {..} = take _radixBitLen $ toBits _radixName
fromBits bits = RadixPrefix bitLen name
where
bitLen = length bits
name = fromBits bits
instance NFData RadixPrefix where
rnf RadixPrefix {..} =
rnf _radixBitLen `seq`
rnf _radixName `seq`
()
instance Serialise RadixPrefix where
encode RadixPrefix {..} =
encodeListLen 2 <>
encodeInt _radixBitLen <>
encodeBytes _radixName
decode = do
void decodeListLen
bitLen <- decodeInt
name <- decodeBytes
pure $ RadixPrefix bitLen name
instance Show RadixPrefix where
show = map compress . toBits
where compress = bool '0' '1'
data RadixProof
= RadixProof
{ _radixPath :: NonEmpty RadixNode
, _radixValue :: ByteString
} deriving (Data, Eq, Show)
instance NFData RadixProof where
rnf RadixProof {..} =
rnf _radixPath `seq`
rnf _radixValue `seq`
()
instance Serialise RadixProof where
encode RadixProof {..} =
CBOR.encode path
where
RadixNode {..} :| nodes = _radixPath
node = RadixNode _radixPrefix _radixLeft _radixRight $ Just _radixValue
path = node :| nodes
decode = do
RadixNode {..} :| nodes <- CBOR.decode
case _radixLeaf of
Nothing -> fail "no proof value"
Just value -> do
let node = RadixNode _radixPrefix _radixLeft _radixRight Nothing
let path = node :| nodes
pure $ RadixProof path value
type RadixRoot = ShortByteString
type RadixSearchResult = (NonEmpty RadixRoot, NonEmpty RadixNode, NonEmpty [Bool], [Bool], [Bool], RadixCache)
data RadixTree database
= RadixTree
{ _radixBloom :: RadixBloom
, _radixBloomSize :: Int
, _radixBuffer :: RadixBuffer
, _radixCache :: RadixCache
, _radixCacheSize :: Int
, _radixCheckpoint :: RadixRoot
, _radixDatabase :: database
, _radixNonce :: Word
, _radixRoot :: RadixRoot
}