mirror of https://github.com/nucypher/nucypher.git
Past fleet state as a param during learning; only learn if there's a difference.
parent
1635f00626
commit
31f66cf071
|
@ -104,19 +104,26 @@ class RestMiddleware:
|
||||||
url,
|
url,
|
||||||
certificate_filepath,
|
certificate_filepath,
|
||||||
announce_nodes=None,
|
announce_nodes=None,
|
||||||
nodes_i_need=None):
|
nodes_i_need=None,
|
||||||
|
client=requests,
|
||||||
|
fleet_checksum=None):
|
||||||
if nodes_i_need:
|
if nodes_i_need:
|
||||||
# TODO: This needs to actually do something.
|
# TODO: This needs to actually do something.
|
||||||
# Include node_ids in the request; if the teacher node doesn't know about the
|
# Include node_ids in the request; if the teacher node doesn't know about the
|
||||||
# nodes matching these ids, then it will ask other nodes.
|
# nodes matching these ids, then it will ask other nodes.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
if fleet_checksum:
|
||||||
|
params = {'fleet': fleet_checksum}
|
||||||
|
else:
|
||||||
|
params = {}
|
||||||
|
|
||||||
if announce_nodes:
|
if announce_nodes:
|
||||||
payload = bytes().join(bytes(n) for n in announce_nodes)
|
payload = bytes().join(bytes(n) for n in announce_nodes)
|
||||||
response = requests.post("https://{}/node_metadata".format(url),
|
response = client.post("https://{}/node_metadata".format(url),
|
||||||
verify=certificate_filepath,
|
verify=certificate_filepath,
|
||||||
data=payload, timeout=2)
|
data=payload, timeout=2, params=params)
|
||||||
else:
|
else:
|
||||||
response = requests.get("https://{}/node_metadata".format(url),
|
response = client.get("https://{}/node_metadata".format(url),
|
||||||
verify=certificate_filepath, timeout=2)
|
verify=certificate_filepath, timeout=2, params=params)
|
||||||
return response
|
return response
|
||||||
|
|
|
@ -542,7 +542,8 @@ class Learner:
|
||||||
response = self.network_middleware.get_nodes_via_rest(url=rest_url,
|
response = self.network_middleware.get_nodes_via_rest(url=rest_url,
|
||||||
nodes_i_need=self._node_ids_to_learn_about_immediately,
|
nodes_i_need=self._node_ids_to_learn_about_immediately,
|
||||||
announce_nodes=announce_nodes,
|
announce_nodes=announce_nodes,
|
||||||
certificate_filepath=certificate_filepath)
|
certificate_filepath=certificate_filepath,
|
||||||
|
fleet_checksum=self.known_nodes.checksum)
|
||||||
except requests.exceptions.ConnectionError as e:
|
except requests.exceptions.ConnectionError as e:
|
||||||
unresponsive_nodes.add(current_teacher)
|
unresponsive_nodes.add(current_teacher)
|
||||||
teacher_rest_info = current_teacher.rest_information()[0]
|
teacher_rest_info = current_teacher.rest_information()[0]
|
||||||
|
@ -550,9 +551,9 @@ class Learner:
|
||||||
# TODO: This error isn't necessarily "no repsonse" - let's maybe pass on the text of the exception here.
|
# TODO: This error isn't necessarily "no repsonse" - let's maybe pass on the text of the exception here.
|
||||||
self.log.info("No Response from teacher: {}:{}.".format(teacher_rest_info.host, teacher_rest_info.port))
|
self.log.info("No Response from teacher: {}:{}.".format(teacher_rest_info.host, teacher_rest_info.port))
|
||||||
self.cycle_teacher_node()
|
self.cycle_teacher_node()
|
||||||
return
|
raise False
|
||||||
|
|
||||||
if response.status_code != 200:
|
if response.status_code not in (200, 204):
|
||||||
raise RuntimeError("Bad response from teacher: {} - {}".format(response, response.content))
|
raise RuntimeError("Bad response from teacher: {} - {}".format(response, response.content))
|
||||||
|
|
||||||
signature, node_payload = signature_splitter(response.content, return_remainder=True)
|
signature, node_payload = signature_splitter(response.content, return_remainder=True)
|
||||||
|
@ -561,12 +562,20 @@ class Learner:
|
||||||
self.verify_from(current_teacher, node_payload, signature=signature)
|
self.verify_from(current_teacher, node_payload, signature=signature)
|
||||||
except current_teacher.InvalidSignature:
|
except current_teacher.InvalidSignature:
|
||||||
# TODO: What to do if the teacher improperly signed the node payload?
|
# TODO: What to do if the teacher improperly signed the node payload?
|
||||||
raise
|
raise False
|
||||||
|
|
||||||
fleet_state_checksum_bytes, fleet_state_updated_bytes, nodes = FleetState.snapshot_splitter(node_payload, return_remainder=True)
|
fleet_state_checksum_bytes, fleet_state_updated_bytes, nodes = FleetState.snapshot_splitter(node_payload, return_remainder=True)
|
||||||
|
current_teacher.last_seen = maya.now()
|
||||||
|
current_teacher.update_snapshot(checksum=fleet_state_checksum_bytes.hex(),
|
||||||
|
updated=maya.MayaDT(int.from_bytes(fleet_state_updated_bytes, byteorder="big")))
|
||||||
|
|
||||||
|
self.cycle_teacher_node()
|
||||||
|
|
||||||
# TODO: This doesn't make sense - a decentralized node can still learn about a federated-only node.
|
# TODO: This doesn't make sense - a decentralized node can still learn about a federated-only node.
|
||||||
from nucypher.characters.lawful import Ursula
|
from nucypher.characters.lawful import Ursula
|
||||||
|
if response.status_code == 204:
|
||||||
|
return constants.FLEET_STATES_MATCH
|
||||||
|
|
||||||
node_list = Ursula.batch_from_bytes(nodes, federated_only=self.federated_only) # TODO: 466
|
node_list = Ursula.batch_from_bytes(nodes, federated_only=self.federated_only) # TODO: 466
|
||||||
|
|
||||||
new_nodes = []
|
new_nodes = []
|
||||||
|
@ -595,11 +604,6 @@ class Learner:
|
||||||
self._adjust_learning(new_nodes)
|
self._adjust_learning(new_nodes)
|
||||||
|
|
||||||
learning_round_log_message = "Learning round {}. Teacher: {} knew about {} nodes, {} were new."
|
learning_round_log_message = "Learning round {}. Teacher: {} knew about {} nodes, {} were new."
|
||||||
current_teacher.last_seen = maya.now()
|
|
||||||
current_teacher.update_snapshot(checksum=fleet_state_checksum_bytes.hex(),
|
|
||||||
updated=maya.MayaDT(int.from_bytes(fleet_state_updated_bytes, byteorder="big")))
|
|
||||||
|
|
||||||
self.cycle_teacher_node()
|
|
||||||
self.log.info(learning_round_log_message.format(self._learning_round,
|
self.log.info(learning_round_log_message.format(self._learning_round,
|
||||||
current_teacher,
|
current_teacher,
|
||||||
len(node_list),
|
len(node_list),
|
||||||
|
|
|
@ -169,10 +169,20 @@ class ProxyRESTRoutes:
|
||||||
return Response(bytes(signature) + payload, headers=headers)
|
return Response(bytes(signature) + payload, headers=headers)
|
||||||
|
|
||||||
def node_metadata_exchange(self, request: Request, query_params: QueryParams):
|
def node_metadata_exchange(self, request: Request, query_params: QueryParams):
|
||||||
|
# If these nodes already have the same fleet state, no exchange is necessary.
|
||||||
|
learner_fleet_state = query_params.get('fleet')
|
||||||
|
if learner_fleet_state == self._node_tracker.checksum:
|
||||||
|
self.log.debug("Learner already knew fleet state {}; doing nothing.".format(learner_fleet_state))
|
||||||
|
headers = {'Content-Type': 'application/octet-stream'}
|
||||||
|
payload = self._node_tracker.snapshot()
|
||||||
|
signature = self._stamp(payload)
|
||||||
|
return Response(bytes(signature) + payload, headers=headers, status_code=204)
|
||||||
|
|
||||||
nodes = self._node_class.batch_from_bytes(request.body,
|
nodes = self._node_class.batch_from_bytes(request.body,
|
||||||
federated_only=self.federated_only, # TODO: 466
|
federated_only=self.federated_only, # TODO: 466
|
||||||
)
|
)
|
||||||
# TODO: This logic is basically repeated in learn_from_teacher_node. Let's find a better way.
|
|
||||||
|
# TODO: This logic is basically repeated in learn_from_teacher_node and remember_node. Let's find a better way. 555
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
|
|
||||||
if node.checksum_public_address in self._node_tracker:
|
if node.checksum_public_address in self._node_tracker:
|
||||||
|
|
|
@ -80,24 +80,8 @@ class MockRestMiddleware(RestMiddleware):
|
||||||
response = mock_client.get("http://localhost/public_information")
|
response = mock_client.get("http://localhost/public_information")
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def get_nodes_via_rest(self, url, certificate_filepath, announce_nodes=None, nodes_i_need=None):
|
def get_nodes_via_rest(self, url, *args, **kwargs):
|
||||||
|
return super().get_nodes_via_rest(url, client=self._get_mock_client_by_url(url), *args, **kwargs)
|
||||||
mock_client = self._get_mock_client_by_url(url)
|
|
||||||
|
|
||||||
if nodes_i_need:
|
|
||||||
# TODO: This needs to actually do something.
|
|
||||||
# Include node_ids in the request; if the teacher node doesn't know about the
|
|
||||||
# nodes matching these ids, then it will ask other nodes.
|
|
||||||
pass
|
|
||||||
|
|
||||||
if announce_nodes:
|
|
||||||
response = mock_client.post("https://{}/node_metadata".format(url),
|
|
||||||
verify=certificate_filepath,
|
|
||||||
data=bytes().join(bytes(n) for n in announce_nodes))
|
|
||||||
else:
|
|
||||||
response = mock_client.get("https://{}/node_metadata".format(url),
|
|
||||||
verify=certificate_filepath)
|
|
||||||
return response
|
|
||||||
|
|
||||||
def put_treasure_map_on_node(self, node, map_id, map_payload):
|
def put_treasure_map_on_node(self, node, map_id, map_payload):
|
||||||
mock_client = self._get_mock_client_by_ursula(node)
|
mock_client = self._get_mock_client_by_ursula(node)
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
from constant_sorrow.constants import FLEET_STATES_MATCH
|
||||||
|
|
||||||
|
|
||||||
def test_all_nodes_have_same_fleet_state(federated_ursulas):
|
def test_all_nodes_have_same_fleet_state(federated_ursulas):
|
||||||
checksums = [u.known_nodes.checksum for u in federated_ursulas]
|
checksums = [u.known_nodes.checksum for u in federated_ursulas]
|
||||||
assert len(set(checksums)) == 1 # There is only 1 unique value.
|
assert len(set(checksums)) == 1 # There is only 1 unique value.
|
||||||
|
@ -17,4 +20,15 @@ def test_teacher_nodes_cycle(federated_ursulas):
|
||||||
ursula.learn_from_teacher_node()
|
ursula.learn_from_teacher_node()
|
||||||
second_teacher = ursula._current_teacher_node
|
second_teacher = ursula._current_teacher_node
|
||||||
|
|
||||||
assert first_teacher != second_teacher
|
assert first_teacher != second_teacher
|
||||||
|
|
||||||
|
|
||||||
|
def test_nodes_with_equal_fleet_state_do_not_send_anew(federated_ursulas):
|
||||||
|
some_ursula = list(federated_ursulas)[2]
|
||||||
|
another_ursula = list(federated_ursulas)[3]
|
||||||
|
|
||||||
|
# These two have the same fleet state.
|
||||||
|
assert some_ursula.known_nodes.checksum == another_ursula.known_nodes.checksum
|
||||||
|
some_ursula._current_teacher_node = another_ursula
|
||||||
|
result = some_ursula.learn_from_teacher_node()
|
||||||
|
assert result is FLEET_STATES_MATCH
|
Loading…
Reference in New Issue