Record node snapshots only at the end of a learning round, not every time a node is remembered.

pull/574/head
jMyles 2018-11-17 11:27:07 -08:00
parent 1ef2209262
commit 9be9ac7370
4 changed files with 67 additions and 27 deletions

View File

@ -178,7 +178,7 @@ class Character(Learner):
raise
if is_me is True:
self.known_nodes.start_tracking_state()
self.known_nodes.record_fleet_state()
def __eq__(self, other) -> bool:
return bytes(self.stamp) == bytes(other.stamp)

View File

@ -586,7 +586,7 @@ class Ursula(VerifiableNode, Character, Miner):
# Logging / Updating
#
if is_me:
self.known_nodes.start_tracking_state(additional_nodes_to_track=[self]) # To account for nodes loaded from NodeStorage.
self.known_nodes.record_fleet_state(additional_nodes_to_track=[self])
message = "Initialized Self {} | {}".format(self.__class__.__name__, self.checksum_public_address)
self.log.info(message)
else:

View File

@ -92,7 +92,7 @@ class FleetStateTracker:
def __init__(self):
self.additional_nodes_to_track = []
self.updated = maya.now()
self._nodes = {}
self._nodes = OrderedDict()
self.states = OrderedDict()
def __setitem__(self, key, value):
@ -100,7 +100,7 @@ class FleetStateTracker:
if self._tracking:
self.log.info("Updating fleet state after saving node {}".format(value))
self.update_fleet_state()
self.record_fleet_state()
else:
self.log.debug("Not updating fleet state.")
@ -122,6 +122,9 @@ class FleetStateTracker:
def __eq__(self, other):
return self._nodes == other._nodes
def __repr__(self):
return self._nodes.__repr__()
@property
def checksum(self):
return self._checksum
@ -152,8 +155,15 @@ class FleetStateTracker:
fleet_state_updated_bytes = self.updated.epoch.to_bytes(4, byteorder="big")
return fleet_state_checksum_bytes + fleet_state_updated_bytes
def update_fleet_state(self):
checksum = keccak_digest(b"".join(bytes(n) for n in self.sorted())).hex()
def record_fleet_state(self, additional_nodes_to_track=None):
if not self._nodes:
# No news here.
return
if additional_nodes_to_track:
self.additional_nodes_to_track.extend(additional_nodes_to_track)
sorted_nodes = self.sorted()
sorted_nodes_joined = b"".join(bytes(n) for n in sorted_nodes)
checksum = keccak_digest(sorted_nodes_joined).hex()
if checksum != self.checksum:
self.checksum = keccak_digest(b"".join(bytes(n) for n in self.sorted())).hex()
self.updated = maya.now()
@ -161,15 +171,10 @@ class FleetStateTracker:
# its own class, FleetState, and use it as the basis for partial updates.
self.states[checksum] = self.state_template(nickname=self.nickname,
icon=self.icon(),
nodes=self.sorted(),
nodes=sorted_nodes,
updated=self.updated,
)
def start_tracking_state(self, additional_nodes_to_track=[]):
self.additional_nodes_to_track.extend(additional_nodes_to_track)
self._tracking = True
self.update_fleet_state()
def sorted(self):
nodes_to_consider = list(self._nodes.values()) + self.additional_nodes_to_track
return sorted(nodes_to_consider, key=lambda n: n.checksum_public_address)
@ -313,7 +318,7 @@ class Learner:
for node in stored_nodes:
self.remember_node(node)
def remember_node(self, node, force_verification_check=False):
def remember_node(self, node, force_verification_check=False, record_fleet_state=True):
if node == self: # No need to remember self.
return False
@ -342,8 +347,8 @@ class Learner:
listeners = self._learning_listeners.pop(node.checksum_public_address, tuple())
address = node.checksum_public_address
self.__known_nodes[address] = node
if self in self.known_nodes._nodes.values():
self.known_nodes[address] = node
if self in self.known_nodes:
raise RuntimeError
if self.save_metadata:
@ -354,6 +359,9 @@ class Learner:
listener.add(address)
self._node_ids_to_learn_about_immediately.discard(address)
if record_fleet_state:
self.known_nodes.record_fleet_state()
return True
def start_learning_loop(self, now=False):
@ -660,7 +668,7 @@ class Learner:
message = "Suspicious Activity: Discovered node with bad signature: {}. " \
"Propagated by: {}".format(current_teacher.checksum_public_address, rest_url)
self.log.warn(message)
new = self.remember_node(node)
new = self.remember_node(node, record_fleet_state=False)
if new:
new_nodes.append(node)
@ -671,9 +679,11 @@ class Learner:
current_teacher,
len(node_list),
len(new_nodes)), )
if new_nodes and self.known_certificates_dir:
for node in new_nodes:
node.save_certificate_to_disk(self.known_certificates_dir, force=True)
if new_nodes:
self.known_nodes.record_fleet_state()
if self.known_certificates_dir:
for node in new_nodes:
node.save_certificate_to_disk(self.known_certificates_dir, force=True)
return new_nodes

View File

@ -41,20 +41,50 @@ def test_old_state_is_preserved(federated_ursulas, ursula_federated_test_config)
ursula_config=ursula_federated_test_config,
quantity=1,
know_each_other=False)
another_ursula = lonely_ursula_maker().pop()
lonely_learner = lonely_ursula_maker().pop()
# This Ursula doesn't know about any nodes.
assert len(another_ursula.known_nodes) == 0
assert len(lonely_learner.known_nodes) == 0
some_ursula_in_the_fleet = list(federated_ursulas)[0]
another_ursula.remember_node(some_ursula_in_the_fleet)
checksum_after_learning_one = another_ursula.known_nodes.checksum
lonely_learner.remember_node(some_ursula_in_the_fleet)
checksum_after_learning_one = lonely_learner.known_nodes.checksum
another_ursula_in_the_fleet = list(federated_ursulas)[1]
another_ursula.remember_node(another_ursula_in_the_fleet)
checksum_after_learning_two = another_ursula.known_nodes.checksum
lonely_learner.remember_node(another_ursula_in_the_fleet)
checksum_after_learning_two = lonely_learner.known_nodes.checksum
assert checksum_after_learning_one != checksum_after_learning_two
assert another_ursula.known_nodes.states[checksum_after_learning_one].nodes == [some_ursula_in_the_fleet, another_ursula]
assert another_ursula.known_nodes.states[checksum_after_learning_two].nodes == [some_ursula_in_the_fleet, another_ursula_in_the_fleet, another_ursula]
proper_first_state = sorted([some_ursula_in_the_fleet, lonely_learner], key=lambda n: n.checksum_public_address)
assert lonely_learner.known_nodes.states[checksum_after_learning_one].nodes == proper_first_state
proper_second_state = sorted([some_ursula_in_the_fleet, another_ursula_in_the_fleet, lonely_learner], key=lambda n: n.checksum_public_address)
assert lonely_learner.known_nodes.states[checksum_after_learning_two].nodes == proper_second_state
def test_state_is_recorded_after_learning(federated_ursulas, ursula_federated_test_config):
"""
Similar to above, but this time we show that the Learner records a new state only once after learning
about a bunch of nodes.
"""
lonely_ursula_maker = partial(make_federated_ursulas,
ursula_config=ursula_federated_test_config,
quantity=1,
know_each_other=False)
lonely_learner = lonely_ursula_maker().pop()
# This Ursula doesn't know about any nodes.
assert len(lonely_learner.known_nodes) == 0
some_ursula_in_the_fleet = list(federated_ursulas)[0]
lonely_learner.remember_node(some_ursula_in_the_fleet)
# The rest of the fucking owl.
lonely_learner.learn_from_teacher_node()
states = list(lonely_learner.known_nodes.states.values())
assert len(states) == 2
assert len(states[0].nodes) == 1
assert len(states[1].nodes) == len(federated_ursulas)