2017-10-20 01:21:49 +02:00
|
|
|
from random import choice, shuffle
|
2017-09-25 01:17:04 +02:00
|
|
|
from threading import RLock
|
2017-10-06 12:19:34 +02:00
|
|
|
from time import time
|
2017-09-25 01:17:04 +02:00
|
|
|
|
2017-10-06 12:19:34 +02:00
|
|
|
from bmconfigparser import BMConfigParser
|
2017-09-25 01:17:04 +02:00
|
|
|
from singleton import Singleton
|
|
|
|
|
|
|
|
# randomise routes after 600 seconds
|
|
|
|
REASSIGN_INTERVAL = 600
|
|
|
|
FLUFF_TRIGGER_TIMEOUT = 300
|
2017-10-20 01:21:49 +02:00
|
|
|
MAX_STEMS = 2
|
2017-09-25 01:17:04 +02:00
|
|
|
|
|
|
|
@Singleton
|
2017-10-20 01:21:49 +02:00
|
|
|
class Dandelion():
|
2017-09-25 01:17:04 +02:00
|
|
|
def __init__(self):
|
2017-10-20 01:21:49 +02:00
|
|
|
self.stem = []
|
|
|
|
self.nodeMap = {}
|
|
|
|
self.hashMap = {}
|
2017-11-18 09:47:17 +01:00
|
|
|
self.fluff = {}
|
2017-10-20 01:21:49 +02:00
|
|
|
self.timeout = {}
|
|
|
|
self.refresh = time() + REASSIGN_INTERVAL
|
2017-09-25 01:17:04 +02:00
|
|
|
self.lock = RLock()
|
|
|
|
|
2017-10-20 01:21:49 +02:00
|
|
|
def addHash(self, hashId, source):
|
2017-10-06 12:19:34 +02:00
|
|
|
if BMConfigParser().safeGetInt('network', 'dandelion') == 0:
|
|
|
|
return
|
2017-09-25 01:17:04 +02:00
|
|
|
with self.lock:
|
2017-10-20 01:21:49 +02:00
|
|
|
self.hashMap[hashId] = self.getNodeStem(source)
|
|
|
|
self.timeout[hashId] = time() + FLUFF_TRIGGER_TIMEOUT
|
2017-09-25 01:17:04 +02:00
|
|
|
|
2017-10-20 01:21:49 +02:00
|
|
|
def removeHash(self, hashId):
|
2017-09-25 01:17:04 +02:00
|
|
|
with self.lock:
|
|
|
|
try:
|
2017-10-20 01:21:49 +02:00
|
|
|
del self.hashMap[hashId]
|
2017-09-25 01:17:04 +02:00
|
|
|
except KeyError:
|
|
|
|
pass
|
2017-10-20 01:21:49 +02:00
|
|
|
try:
|
|
|
|
del self.timeout[hashId]
|
|
|
|
except KeyError:
|
|
|
|
pass
|
2017-11-18 09:47:17 +01:00
|
|
|
try:
|
|
|
|
del self.fluff[hashId]
|
|
|
|
except KeyError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
def fluffTrigger(self, hashId):
|
|
|
|
with self.lock:
|
|
|
|
self.fluff[hashId] = None
|
2017-10-20 01:21:49 +02:00
|
|
|
|
|
|
|
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
|