EpiChord: create a stub cache-invariant checker

rough logic is implemented, but only for successors and not
predecessors.
Additionally it just returns empty IO actions instead of lookup
operations, until those are implemented.
part of #1

Untested!
This commit is contained in:
Trolli Schmittlauch 2020-03-23 21:15:10 +01:00
parent 88897ea741
commit 0e33d442f9
3 changed files with 53 additions and 2 deletions

View file

@ -58,7 +58,7 @@ library
exposed-modules: Hash2Pub.FediChord
-- Modules included in this library but not exported.
-- other-modules:
other-modules: Hash2Pub.Utils
-- LANGUAGE extensions used by modules in this package.
other-extensions: GeneralizedNewtypeDeriving, DataKinds, OverloadedStrings

View file

@ -36,6 +36,8 @@ import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.UTF8 as BSU
import qualified Data.ByteArray as BA
import Hash2Pub.Utils
-- define protocol constants
-- | static definition of ID length in bits
idBits :: Integer
@ -94,7 +96,7 @@ data NodeState = NodeState {
, apPort :: Maybe PortNumber
-- ^ port of the ActivityPub relay and storage service
-- might have to be queried first
, nodeCache :: Map.Map NodeID CacheEntry
, nodeCache :: NodeCache
-- ^ EpiChord node cache with expiry times for nodes
-- as the map is ordered, lookups for the closes preceding node can be done using @lookupLT@
, successors :: [NodeID]
@ -110,8 +112,13 @@ data NodeState = NodeState {
, pNumParallelQueries :: Int
-- ^ number of parallel sent queries
-- needs to be parameterisable for simulation purposes
, jEntriesPerSlice :: Int
-- ^ number of desired entries per cache slice
-- needs to be parameterisable for simulation purposes
} deriving (Show)
type NodeCache = Map.Map NodeID CacheEntry
-- |an entry of the 'nodeCache'
type CacheEntry = (
NodeState
@ -168,6 +175,44 @@ byteStringToUInteger bs = sum $ parsedBytes 0 bs
-- TODO: needs testing
-- |checks wether the cache entries fulfill the logarithmic EpiChord invariant
-- of having j entries per slice, and creates a list of necessary lookup actions.
-- Should be invoked periodically.
checkCacheSlices :: NodeState -> [IO ()]
checkCacheSlices state =
checkSlice jEntries (nid state) startBound lastSucc cache'
-- TODO: do the same for predecessors
where
jEntries = jEntriesPerSlice state
cache' = nodeCache state
lastSucc = last <$> maybeEmpty (successors state)
startBound = NodeID 2^(255::Integer) + nid state
checkSlice :: Int -> NodeID -> NodeID -> Maybe NodeID -> NodeCache -> [IO ()]
checkSlice _ _ _ Nothing _ = []
checkSlice j ownID upperBound (Just lastSuccNode) cache
| upperBound < lastSuccNode = []
| otherwise =
-- continuously half the DHT namespace, take the upper part as a slice,
-- check for existing entries in that slice and create a lookup action
-- and recursively do this on the lower half.
-- recursion edge case: all successors/ predecessors need to be in the
-- first slice.
let
diff = getNodeID $ upperBound - ownID
lowerBound = ownID + NodeID (diff `div` 2)
in
-- TODO: replace empty IO actions with actual lookups
-- TODO: validate ID before adding to cache
case Map.lookupLT upperBound cache of
Nothing -> return () : checkSlice j ownID lowerBound (Just lastSuccNode) cache
Just (matchID, _) ->
if
matchID <= lowerBound then return () : checkSlice j ownID lowerBound (Just lastSuccNode) cache
else
checkSlice j ownID lowerBound (Just lastSuccNode) cache
-- Todo: DHT backend can learn potential initial bootstrapping points through the instances mentioned in the received AP-relay messages
-- needs to know its own domain anyways for ID generation
-- persist them on disk so they can be used for all following bootstraps

View file

@ -0,0 +1,6 @@
module Hash2Pub.Utils where
-- |wraps a list into a Maybe, by replacing empty lists with Nothing
maybeEmpty :: [a] -> Maybe [a]
maybeEmpty [] = Nothing
maybeEmpty nonemptyList = Just nonemptyList