Peter Surda
2d34e73648
- fixes and feedback from @gfanti and @amiller - addresses #1049 - minor refactoring - two global child stems with fixed mapping between parent and child stem - allow child stems which don't support dandelion - only allow outbound connections to be stems - adjust stems if opening/closing outbound connections (should allow partial dandelion functionality when not enough outbound connections are available instead of breaking)
109 lines
3.6 KiB
Python
109 lines
3.6 KiB
Python
from random import choice, shuffle
|
|
from threading import RLock
|
|
from time import time
|
|
|
|
from bmconfigparser import BMConfigParser
|
|
from singleton import Singleton
|
|
|
|
# randomise routes after 600 seconds
|
|
REASSIGN_INTERVAL = 600
|
|
FLUFF_TRIGGER_TIMEOUT = 300
|
|
MAX_STEMS = 2
|
|
|
|
@Singleton
|
|
class Dandelion():
|
|
def __init__(self):
|
|
self.stem = []
|
|
self.nodeMap = {}
|
|
self.hashMap = {}
|
|
self.timeout = {}
|
|
self.refresh = time() + REASSIGN_INTERVAL
|
|
self.lock = RLock()
|
|
|
|
def addHash(self, hashId, source):
|
|
if BMConfigParser().safeGetInt('network', 'dandelion') == 0:
|
|
return
|
|
with self.lock:
|
|
self.hashMap[hashId] = self.getNodeStem(source)
|
|
self.timeout[hashId] = time() + FLUFF_TRIGGER_TIMEOUT
|
|
|
|
def removeHash(self, hashId):
|
|
with self.lock:
|
|
try:
|
|
del self.hashMap[hashId]
|
|
except KeyError:
|
|
pass
|
|
try:
|
|
del self.timeout[hashId]
|
|
except KeyError:
|
|
pass
|
|
|
|
def maybeAddStem(self, connection):
|
|
# fewer than MAX_STEMS outbound connections at last reshuffle?
|
|
with self.lock:
|
|
if len(self.stem) < MAX_STEMS:
|
|
self.stem.append(connection)
|
|
# active mappings pointing nowhere
|
|
for k in (k for k, v in self.nodeMap.iteritems() if self.nodeMap[k] is None):
|
|
self.nodeMap[k] = connection
|
|
for k in (k for k, v in self.hashMap.iteritems() if self.hashMap[k] is None):
|
|
self.hashMap[k] = connection
|
|
|
|
def maybeRemoveStem(self, connection):
|
|
# is the stem active?
|
|
with self.lock:
|
|
if connection in self.stem:
|
|
self.stem.remove(connection)
|
|
# active mappings to pointing to the removed node
|
|
for k in (k for k, v in self.nodeMap.iteritems() if self.nodeMap[k] == connection):
|
|
self.nodeMap[k] = None
|
|
for k in (k for k, v in self.hashMap.iteritems() if self.hashMap[k] == connection):
|
|
self.hashMap[k] = None
|
|
if len(self.stem) < MAX_STEMS:
|
|
self.stem.append(connection)
|
|
|
|
def pickStem(self, parent=None):
|
|
try:
|
|
# pick a random from available stems
|
|
stem = choice(range(len(self.stem)))
|
|
if self.stem[stem] == parent:
|
|
# one stem available and it's the parent
|
|
if len(self.stem) == 1:
|
|
return None
|
|
# else, pick the other one
|
|
return self.stem[1 - stem]
|
|
# all ok
|
|
return self.stem[stem]
|
|
except IndexError:
|
|
# no stems available
|
|
return None
|
|
|
|
def getNodeStem(self, node=None):
|
|
with self.lock:
|
|
try:
|
|
return self.nodeMap[node]
|
|
except KeyError:
|
|
self.nodeMap[node] = self.pickStem()
|
|
return self.nodeMap[node]
|
|
|
|
def getHashStem(self, hashId):
|
|
with self.lock:
|
|
return self.hashMap[hashId]
|
|
|
|
def expire(self):
|
|
with self.lock:
|
|
deadline = time()
|
|
toDelete = [k for k, v in self.hashMap.iteritems() if self.timeout[k] < deadline]
|
|
for k in toDelete:
|
|
del self.timeout[k]
|
|
del self.hashMap[k]
|
|
|
|
def reRandomiseStems(self, connections):
|
|
shuffle(connections)
|
|
with self.lock:
|
|
# random two connections
|
|
self.stem = connections[:MAX_STEMS]
|
|
self.nodeMap = {}
|
|
# hashMap stays to cater for pending stems
|
|
self.refresh = time() + REASSIGN_INTERVAL
|