summaryrefslogtreecommitdiff
path: root/src/Network/DHT
diff options
context:
space:
mode:
authorjoe <joe@jerkface.net>2017-07-17 04:33:20 -0400
committerjoe <joe@jerkface.net>2017-07-17 04:33:20 -0400
commit01faa099bd7e98137ef2897d5279ea077c75c4a0 (patch)
tree2cb03ecfdf31125d16599ac2fa2efb7335afe16a /src/Network/DHT
parent41c4f64231037f70d7cd6a0c2611b2c6a1d517d9 (diff)
Use Data.Reflection for PSQ-required Ord instance.
Diffstat (limited to 'src/Network/DHT')
-rw-r--r--src/Network/DHT/Routing.hs150
1 files changed, 88 insertions, 62 deletions
diff --git a/src/Network/DHT/Routing.hs b/src/Network/DHT/Routing.hs
index 1821ca1c..58e0cbd3 100644
--- a/src/Network/DHT/Routing.hs
+++ b/src/Network/DHT/Routing.hs
@@ -16,10 +16,12 @@
16{-# LANGUAGE CPP #-} 16{-# LANGUAGE CPP #-}
17{-# LANGUAGE RecordWildCards #-} 17{-# LANGUAGE RecordWildCards #-}
18{-# LANGUAGE BangPatterns #-} 18{-# LANGUAGE BangPatterns #-}
19{-# LANGUAGE RankNTypes #-}
19{-# LANGUAGE ViewPatterns #-} 20{-# LANGUAGE ViewPatterns #-}
20{-# LANGUAGE TypeOperators #-} 21{-# LANGUAGE TypeOperators #-}
21{-# LANGUAGE DeriveGeneric #-} 22{-# LANGUAGE DeriveGeneric #-}
22{-# LANGUAGE DeriveFunctor #-} 23{-# LANGUAGE DeriveFunctor #-}
24{-# LANGUAGE GADTs #-}
23{-# LANGUAGE ScopedTypeVariables #-} 25{-# LANGUAGE ScopedTypeVariables #-}
24{-# LANGUAGE StandaloneDeriving, FlexibleContexts, MultiParamTypeClasses, FlexibleInstances #-} 26{-# LANGUAGE StandaloneDeriving, FlexibleContexts, MultiParamTypeClasses, FlexibleInstances #-}
25{-# OPTIONS_GHC -fno-warn-orphans #-} 27{-# OPTIONS_GHC -fno-warn-orphans #-}
@@ -171,15 +173,22 @@ bucketQ = seqQ
171 173
172newtype Compare a = Compare (a -> a -> Ordering) 174newtype Compare a = Compare (a -> a -> Ordering)
173 175
174newtype Ordered s a = Ordered a 176contramapC :: (b -> a) -> Compare a -> Compare b
177contramapC f (Compare cmp) = Compare $ \a b -> cmp (f a) (f b)
178
179newtype Ordered' s a = Ordered a
180 deriving (Show)
175 181
176-- | Hack to avoid UndecidableInstances 182-- | Hack to avoid UndecidableInstances
177newtype Shrink a = Shrink a 183newtype Shrink a = Shrink a
184 deriving (Show)
185
186type Ordered s a = Ordered' s (Shrink a)
178 187
179instance Reifies s (Compare a) => Eq (Ordered s (Shrink a)) where 188instance Reifies s (Compare a) => Eq (Ordered' s (Shrink a)) where
180 a == b = (compare a b == EQ) 189 a == b = (compare a b == EQ)
181 190
182instance Reifies s (Compare a) => Ord (Ordered s (Shrink a)) where 191instance Reifies s (Compare a) => Ord (Ordered' s (Shrink a)) where
183 compare a b = cmp (coerce a) (coerce b) 192 compare a b = cmp (coerce a) (coerce b)
184 where Compare cmp = reflect (Proxy :: Proxy s) 193 where Compare cmp = reflect (Proxy :: Proxy s)
185 194
@@ -189,16 +198,20 @@ instance Reifies s (Compare a) => Ord (Ordered s (Shrink a)) where
189-- table tree. Size of the bucket should be choosen such that it's 198-- table tree. Size of the bucket should be choosen such that it's
190-- very unlikely that all nodes in bucket fail within an hour of 199-- very unlikely that all nodes in bucket fail within an hour of
191-- each other. 200-- each other.
192data Bucket ni = Bucket 201data Bucket s ni = Bucket
193 { bktNodes :: !(PSQ ni Timestamp) -- current routing nodes 202 { bktNodes :: !(PSQ (Ordered s ni) Timestamp) -- current routing nodes
194 , bktQ :: !(BucketQueue (Timestamp,ni)) -- replacements pending time-outs 203 , bktQ :: !(BucketQueue (Timestamp,ni)) -- replacements pending time-outs
195 } deriving (Generic) 204 } deriving (Generic)
196 205
197deriving instance Show ni => Show (Bucket ni) 206deriving instance Show ni => Show (Bucket s ni)
207
208bucketCompare :: forall p ni s. Reifies s (Compare ni) => p (Bucket s ni) -> Compare ni
209bucketCompare _ = reflect (Proxy :: Proxy s)
198 210
199mapBucket :: Ord ni => (a -> ni) -> Bucket a -> Bucket ni 211mapBucket :: Reifies t (Compare ni) => (a -> ni) -> Bucket s a -> Bucket t ni
200mapBucket f (Bucket ns q) = Bucket (PSQ.fromList $ map (\(ni :-> tm) -> (f ni :-> tm)) $ PSQ.toList ns) 212mapBucket f (Bucket ns q) = Bucket (PSQ.fromList $ map (\(ni :-> tm) -> (f' ni :-> tm)) $ PSQ.toList ns)
201 (fmap (second f) q) 213 (fmap (second f) q)
214 where f' = coerce . f . coerce
202 215
203 216
204#if 0 217#if 0
@@ -255,10 +268,10 @@ updateBucketForInbound curTime info bucket
255 -- timestamp updated here, since 'TryInsert' is called on every inbound packet, 268 -- timestamp updated here, since 'TryInsert' is called on every inbound packet,
256 -- including ping results. 269 -- including ping results.
257 | already_have 270 | already_have
258 = pure ( [], map_ns $ PSQ.insertWith max info curTime ) 271 = pure ( [], map_ns $ PSQ.insertWith max (coerce info) curTime )
259 -- bucket is good, but not full => we can insert a new node 272 -- bucket is good, but not full => we can insert a new node
260 | PSQ.size (bktNodes bucket) < defaultBucketSize 273 | PSQ.size (bktNodes bucket) < defaultBucketSize
261 = pure ( [], map_ns $ PSQ.insert info curTime ) 274 = pure ( [], map_ns $ PSQ.insert (coerce info) curTime )
262 -- If there are any questionable nodes in the bucket have not been 275 -- If there are any questionable nodes in the bucket have not been
263 -- seen in the last 15 minutes, the least recently seen node is 276 -- seen in the last 15 minutes, the least recently seen node is
264 -- pinged. If any nodes in the bucket are known to have become bad, 277 -- pinged. If any nodes in the bucket are known to have become bad,
@@ -267,7 +280,7 @@ updateBucketForInbound curTime info bucket
267 | not (L.null stales) 280 | not (L.null stales)
268 = pure ( stales 281 = pure ( stales
269 , bucket { -- Update timestamps so that we don't redundantly ping. 282 , bucket { -- Update timestamps so that we don't redundantly ping.
270 bktNodes = updateStamps curTime stales $ bktNodes bucket 283 bktNodes = updateStamps curTime (coerce stales) $ bktNodes bucket
271 -- Update queue with the pending NodeInfo in case of ping fail. 284 -- Update queue with the pending NodeInfo in case of ping fail.
272 , bktQ = runIdentity $ pushBack bucketQ (curTime,info) $ bktQ bucket } ) 285 , bktQ = runIdentity $ pushBack bucketQ (curTime,info) $ bktQ bucket } )
273 -- When the bucket is full of good nodes, the new node is simply discarded. 286 -- When the bucket is full of good nodes, the new node is simply discarded.
@@ -281,11 +294,11 @@ updateBucketForInbound curTime info bucket
281 stales = -- One stale: 294 stales = -- One stale:
282 do (n :-> t) <- maybeToList $ PSQ.findMin (bktNodes bucket) 295 do (n :-> t) <- maybeToList $ PSQ.findMin (bktNodes bucket)
283 guard (t < curTime - delta) 296 guard (t < curTime - delta)
284 return n 297 return $ coerce n
285 -- All stale: 298 -- All stale:
286 -- map key \$ PSQ.atMost (curTime - delta) $ bktNodes bucket 299 -- map key \$ PSQ.atMost (curTime - delta) $ bktNodes bucket
287 300
288 already_have = maybe False (const True) $ PSQ.lookup info (bktNodes bucket) 301 already_have = maybe False (const True) $ PSQ.lookup (coerce info) (bktNodes bucket)
289 302
290 map_ns f = bucket { bktNodes = f (bktNodes bucket) } 303 map_ns f = bucket { bktNodes = f (bktNodes bucket) }
291 -- map_q f = bucket { bktQ = runIdentity \$ f (bktQ bucket) } 304 -- map_q f = bucket { bktQ = runIdentity \$ f (bktQ bucket) }
@@ -302,13 +315,13 @@ updateBucketForPingResult bad_node got_response bucket
302 replacements | got_response = [] -- Timestamp was already updated by TryInsert. 315 replacements | got_response = [] -- Timestamp was already updated by TryInsert.
303 | Just info <- top = do 316 | Just info <- top = do
304 -- Insert only if there's a removal. 317 -- Insert only if there's a removal.
305 _ <- maybeToList $ PSQ.lookup bad_node (bktNodes bucket) 318 _ <- maybeToList $ PSQ.lookup (coerce bad_node) (bktNodes bucket)
306 return (bad_node, info) 319 return (bad_node, info)
307 | otherwise = [] 320 | otherwise = []
308 321
309 replace (bad_node, (tm, info)) = 322 replace (bad_node, (tm, info)) =
310 PSQ.insert info tm 323 PSQ.insert (coerce info) tm
311 . PSQ.delete bad_node 324 . PSQ.delete (coerce bad_node)
312 325
313 326
314updateStamps :: Ord ni => Timestamp -> [ni] -> PSQ ni Timestamp -> PSQ ni Timestamp 327updateStamps :: Ord ni => Timestamp -> [ni] -> PSQ ni Timestamp -> PSQ ni Timestamp
@@ -335,12 +348,12 @@ partitionQ imp test q0 = do
335 348
336 349
337split :: -- ( Eq ip , Ord (NodeId) , FiniteBits (NodeId)) => 350split :: -- ( Eq ip , Ord (NodeId) , FiniteBits (NodeId)) =>
338 forall ni. Ord ni => 351 forall ni s. ( Reifies s (Compare ni) ) =>
339 (ni -> Word -> Bool) 352 (ni -> Word -> Bool)
340 -> BitIx -> Bucket ni -> (Bucket ni, Bucket ni) 353 -> BitIx -> Bucket s ni -> (Bucket s ni, Bucket s ni)
341split testNodeIdBit i b = (Bucket ns qs, Bucket ms rs) 354split testNodeIdBit i b = (Bucket ns qs, Bucket ms rs)
342 where 355 where
343 (ns,ms) = (PSQ.fromList *** PSQ.fromList) . partition (spanBit . key) . PSQ.toList $ bktNodes b 356 (ns,ms) = (PSQ.fromList *** PSQ.fromList) . partition (spanBit . coerce . key) . PSQ.toList $ bktNodes b
344 (qs,rs) = runIdentity $ partitionQ bucketQ (spanBit . snd) $ bktQ b 357 (qs,rs) = runIdentity $ partitionQ bucketQ (spanBit . snd) $ bktQ b
345 358
346 spanBit :: ni -> Bool 359 spanBit :: ni -> Bool
@@ -382,11 +395,23 @@ deriving instance (Show ni, Show nid) => Show (Info ni nid)
382-- is always split into two new buckets covering the ranges @0..2 ^ 395-- is always split into two new buckets covering the ranges @0..2 ^
383-- 159@ and @2 ^ 159..2 ^ 160@. 396-- 159@ and @2 ^ 159..2 ^ 160@.
384-- 397--
385data BucketList ni = BucketList !ni !Int ![Bucket ni] 398data BucketList ni = forall s. Reifies s (Compare ni) =>
386 deriving Generic 399 BucketList { thisNode :: !ni
387 400 , bktCount :: !Int
388mapTable :: Ord ni => (a -> ni) -> BucketList a -> BucketList ni 401 -- | Non-empty list of buckets.
389mapTable f (BucketList self n bs) = BucketList (f self) n (map (mapBucket f) bs) 402 , buckets :: [Bucket s ni]
403 }
404
405mapTable :: (b -> t) -> (t -> b) -> BucketList t -> BucketList b
406mapTable g f tbl@(BucketList self n bkts) = reify (contramapC g $ bucketCompare bkts)
407 $ \p -> BucketList
408 { thisNode = f self
409 , bktCount = n
410 , buckets = map (resolve p . mapBucket f) bkts
411 }
412 where
413 resolve :: Proxy s -> Bucket s ni -> Bucket s ni
414 resolve = const id
390 415
391instance (Eq ni) => Eq (BucketList ni) where 416instance (Eq ni) => Eq (BucketList ni) where
392 (==) = (==) `on` Network.DHT.Routing.toList 417 (==) = (==) `on` Network.DHT.Routing.toList
@@ -422,14 +447,19 @@ instance Pretty (BucketList ni) where
422 ss = shape t 447 ss = shape t
423 448
424-- | Empty table with specified /spine/ node id. 449-- | Empty table with specified /spine/ node id.
425nullTable :: ni -> Int -> BucketList ni 450nullTable :: (ni -> ni -> Ordering) -> ni -> Int -> BucketList ni
426nullTable ni n = BucketList 451nullTable cmp ni n =
452 reify (Compare cmp)
453 $ \p -> BucketList
427 ni 454 ni
428 (bucketCount (pred n)) 455 (bucketCount (pred n))
429 [Bucket PSQ.empty (runIdentity $ emptyQueue bucketQ)] 456 [Bucket (empty p) (runIdentity $ emptyQueue bucketQ)]
430 where 457 where
431 bucketCount x = max 0 (min 159 x) 458 bucketCount x = max 0 (min 159 x)
432 459
460 empty :: Proxy s -> PSQ (p s ni) t
461 empty = const $ PSQ.empty
462
433#if 0 463#if 0
434 464
435-- | Test if table is empty. In this case DHT should start 465-- | Test if table is empty. In this case DHT should start
@@ -459,7 +489,7 @@ type NodeCount = Int
459-- | Internally, routing table is similar to list of buckets or a 489-- | Internally, routing table is similar to list of buckets or a
460-- /matrix/ of nodes. This function returns the shape of the matrix. 490-- /matrix/ of nodes. This function returns the shape of the matrix.
461shape :: BucketList ni -> [Int] 491shape :: BucketList ni -> [Int]
462shape = map (PSQ.size . bktNodes) . buckets 492shape (BucketList _ _ tbl) = map (PSQ.size . bktNodes) tbl
463 493
464#if 0 494#if 0
465 495
@@ -473,16 +503,18 @@ depth = L.length . shape
473 503
474#endif 504#endif
475 505
476lookupBucket :: ( FiniteBits nid 506lookupBucket :: forall ni nid x.
507 ( FiniteBits nid
477 , Ord nid 508 , Ord nid
478 ) => (ni -> nid) -> nid -> BucketList ni -> [Bucket ni] 509 ) => (ni -> nid) -> nid -> (forall s. [Bucket s ni] -> x) -> BucketList ni -> x
479lookupBucket nodeId nid (BucketList self _ bkts) = go 0 [] bkts 510lookupBucket nodeId nid kont (BucketList self _ bkts) = kont $ go 0 [] bkts
480 where 511 where
481 d = complement (nid `xor` nodeId self) 512 d = nid `xor` nodeId self
482 513
514 go :: Word -> [Bucket s ni] -> [Bucket s ni] -> [Bucket s ni]
483 go i bs (bucket : buckets) 515 go i bs (bucket : buckets)
484 | testIdBit d i = bucket : buckets ++ bs 516 | testIdBit d i = go (succ i) (bucket:bs) buckets
485 | otherwise = go (succ i) (bucket:bs) buckets 517 | otherwise = bucket : buckets ++ bs
486 go _ bs [] = bs 518 go _ bs [] = bs
487 519
488 520
@@ -563,8 +595,7 @@ kclosest nodeId k nid tbl = take k $ rank nodeId nid (L.concat bucket)
563 where 595 where
564 (bucket,everyone) = 596 (bucket,everyone) =
565 L.splitAt 1 597 L.splitAt 1
566 . L.map (L.map PSQ.key . PSQ.toList . bktNodes) 598 . lookupBucket nodeId nid (L.map (coerce . L.map PSQ.key . PSQ.toList . bktNodes))
567 . lookupBucket nodeId nid
568 $ tbl 599 $ tbl
569 600
570 601
@@ -574,9 +605,9 @@ kclosest nodeId k nid tbl = take k $ rank nodeId nid (L.concat bucket)
574-----------------------------------------------------------------------} 605-----------------------------------------------------------------------}
575 606
576splitTip :: -- ( Eq ip , Ord (NodeId) , FiniteBits (NodeId)) => 607splitTip :: -- ( Eq ip , Ord (NodeId) , FiniteBits (NodeId)) =>
577 Ord ni => 608 ( Reifies s (Compare ni) ) =>
578 (ni -> Word -> Bool) 609 (ni -> Word -> Bool)
579 -> ni -> Int -> BitIx -> Bucket ni -> [ Bucket ni ] 610 -> ni -> Int -> BitIx -> Bucket s ni -> [ Bucket s ni ]
580splitTip testNodeBit ni n i bucket 611splitTip testNodeBit ni n i bucket
581 | testNodeBit ni i = [zeros , ones ] 612 | testNodeBit ni i = [zeros , ones ]
582 | otherwise = [ones , zeros ] 613 | otherwise = [ones , zeros ]
@@ -590,25 +621,24 @@ splitTip testNodeBit ni n i bucket
590-- paper. The rule requiring additional splits is in section 2.4. 621-- paper. The rule requiring additional splits is in section 2.4.
591modifyBucket 622modifyBucket
592 :: -- ( Eq ip , Ord (NodeId) , FiniteBits (NodeId)) => 623 :: -- ( Eq ip , Ord (NodeId) , FiniteBits (NodeId)) =>
593 forall ni nid xs. (Ord ni, Bits nid) => 624 forall ni nid xs.
594 (nid -> Word -> Bool) 625 KademliaSpace nid ni
595 -> (ni -> nid) 626 -> nid -> (forall s. Reifies s (Compare ni) => Bucket s ni -> Maybe (xs, Bucket s ni)) -> BucketList ni -> Maybe (xs,BucketList ni)
596 -> nid -> (Bucket ni -> Maybe (xs, Bucket ni)) -> BucketList ni -> Maybe (xs,BucketList ni) 627modifyBucket space nid f (BucketList self n bkts)
597modifyBucket testIdBit nodeId nid f (BucketList self n bkts)
598 = second (BucketList self n) <$> go (0 :: BitIx) bkts 628 = second (BucketList self n) <$> go (0 :: BitIx) bkts
599 where 629 where
600 d = nid `xor` nodeId self 630 d = kademliaXor space nid (kademliaLocation space self)
601 631
602 go :: BitIx -> [Bucket ni] -> Maybe (xs, [Bucket ni]) 632 -- go :: BitIx -> [Bucket s ni] -> Maybe (xs, [Bucket s ni])
603 633
604 go !i (bucket : buckets@(_:_)) 634 go !i (bucket : buckets@(_:_))
605 | testIdBit d i = second (bucket :) <$> go (succ i) buckets 635 | kademliaTestBit space d i = second (bucket :) <$> go (succ i) buckets
606 | otherwise = second (: buckets) <$> f bucket 636 | otherwise = second (: buckets) <$> f bucket
607 637
608 go !i [bucket] 638 go !i [bucket]
609 | (n == 0) = second (: []) <$> f bucket 639 | (n == 0) = second (: []) <$> f bucket
610 | otherwise = second (: []) <$> f bucket 640 | otherwise = second (: []) <$> f bucket
611 <|> go i (splitTip (testIdBit . nodeId) self n i bucket) 641 <|> go i (splitTip (kademliaTestBit space . kademliaLocation space) self n i bucket)
612 642
613 643
614-- | Triggering event for atomic table update 644-- | Triggering event for atomic table update
@@ -655,13 +685,12 @@ deriving instance ( Show ip
655-- 685--
656-- [ /tbl'/ ] The updated routing 'BucketList'. 686-- [ /tbl'/ ] The updated routing 'BucketList'.
657-- 687--
658updateForInbound :: (Ord ni, Bits nid) => 688updateForInbound ::
659 KademliaSpace nid ni 689 KademliaSpace nid ni
660 -> Timestamp -> ni -> BucketList ni -> (Bool, [ni], BucketList ni) 690 -> Timestamp -> ni -> BucketList ni -> (Bool, [ni], BucketList ni)
661updateForInbound space tm ni tbl = 691updateForInbound space tm ni tbl@(BucketList _ _ bkts) =
662 maybe (False, [],tbl) (\(ps,tbl') -> (True, ps, tbl')) 692 maybe (False, [],tbl) (\(ps,tbl') -> (True, ps, tbl'))
663 $ modifyBucket (kademliaTestBit space) 693 $ modifyBucket space
664 (kademliaLocation space)
665 (kademliaLocation space ni) 694 (kademliaLocation space ni)
666 (updateBucketForInbound tm ni) 695 (updateBucketForInbound tm ni)
667 tbl 696 tbl
@@ -670,16 +699,15 @@ updateForInbound space tm ni tbl =
670-- 699--
671-- Each (a,(tm,b)) in the returned list indicates that the node /a/ was deleted from the 700-- Each (a,(tm,b)) in the returned list indicates that the node /a/ was deleted from the
672-- routing table and the node /b/, with timestamp /tm/, has taken its place. 701-- routing table and the node /b/, with timestamp /tm/, has taken its place.
673updateForPingResult :: (Ord ni, Bits nid) => 702updateForPingResult ::
674 KademliaSpace nid ni 703 KademliaSpace nid ni
675 -> ni -- ^ The pinged node. 704 -> ni -- ^ The pinged node.
676 -> Bool -- ^ True if we got a reply, False if it timed out. 705 -> Bool -- ^ True if we got a reply, False if it timed out.
677 -> BucketList ni -- ^ The routing table. 706 -> BucketList ni -- ^ The routing table.
678 -> ( [(ni,(Timestamp, ni))], BucketList ni ) 707 -> ( [(ni,(Timestamp, ni))], BucketList ni )
679updateForPingResult space ni got_reply tbl = 708updateForPingResult space ni got_reply tbl =
680 fromMaybe ([],tbl) 709 fromMaybe ([],tbl)
681 $ modifyBucket (kademliaTestBit space) 710 $ modifyBucket space
682 (kademliaLocation space)
683 (kademliaLocation space ni) 711 (kademliaLocation space ni)
684 (updateBucketForPingResult ni got_reply) 712 (updateBucketForPingResult ni got_reply)
685 tbl 713 tbl
@@ -694,12 +722,8 @@ type TableEntry ni = (ni, Timestamp)
694tableEntry :: NodeEntry ni -> TableEntry ni 722tableEntry :: NodeEntry ni -> TableEntry ni
695tableEntry (a :-> b) = (a, b) 723tableEntry (a :-> b) = (a, b)
696 724
697-- | Non-empty list of buckets.
698buckets :: BucketList ni -> [Bucket ni]
699buckets (BucketList _ _ bs) = bs
700
701toList :: BucketList ni -> [[TableEntry ni]] 725toList :: BucketList ni -> [[TableEntry ni]]
702toList = L.map (L.map tableEntry . PSQ.toList . bktNodes) . buckets 726toList (BucketList _ _ bkts) = coerce $ L.map (L.map tableEntry . PSQ.toList . bktNodes) bkts
703 727
704data KademliaSpace nid ni = KademliaSpace 728data KademliaSpace nid ni = KademliaSpace
705 { -- | Given a node record (probably including IP address), yields a 729 { -- | Given a node record (probably including IP address), yields a
@@ -709,6 +733,8 @@ data KademliaSpace nid ni = KademliaSpace
709 -- 'Data.Bits.testBit' except that the ordering of bits is reversed, so 733 -- 'Data.Bits.testBit' except that the ordering of bits is reversed, so
710 -- that 0 is the most significant bit. 734 -- that 0 is the most significant bit.
711 , kademliaTestBit :: nid -> Word -> Bool 735 , kademliaTestBit :: nid -> Word -> Bool
736 -- | The Kademlia xor-metric.
737 , kademliaXor :: nid -> nid -> nid
712 } 738 }
713 739
714contramapKS f ks = ks 740contramapKS f ks = ks