def send_receipts(l5_block: "l5_block_model.L5BlockModel") -> None: receipt_path = "/v1/receipt" get_claim_path = "/v1/claim" chain_id_set = set() _log.info(f"l5 block to loop {l5_block.__dict__}") _log.info(f"Sending receipts to {len(l5_block.l4_blocks)} lower nodes") for l4_block in l5_block.l4_blocks: try: block_dictionary = json.loads(l4_block) chain_id = block_dictionary["l1_dc_id"] block = block_dictionary["l1_block_id"] full_claim_path = f"{get_claim_path}/{block}" # Get the claim data for billing claim_url = f"{matchmaking.get_dragonchain_address(chain_id)}{full_claim_path}" headers, _ = authorization.generate_authenticated_request("GET", chain_id, full_claim_path) _log.info(f"getting claim for {block} from {chain_id}") try: _log.info(f"----> {claim_url}") r = requests.get(claim_url, headers=headers, timeout=30) _log.info(f"<---- {r.status_code} {r.text}") except Exception: _log.exception("Failed to get claim!") if r.status_code != 200: _log.error(f"Claim check failed! Rejecting block {block} from {chain_id}") continue else: claim = r.json() # Add this L5's proof to the block _log.info(f"Claim received from l1 {claim}") _log.info(f"data points blockid {l5_block.block_id} signature {l5_block.proof}") block_data = {} block_data["blockId"] = l5_block.block_id block_data["signature"] = l5_block.proof claim["validations"]["l5"][l5_block.dc_id] = block_data chain_id_set.add(chain_id) _log.info(f"Sending filled claim {claim}") try: claim_check_id = f"{chain_id}-{block}" matchmaking.resolve_claim_check(claim_check_id) except Exception: _log.exception("Failure to finalize claim in matchmaking. Sending reciepts to lower level nodes.") except Exception as e: _log.exception(f"[BROADCAST] Error while trying to broadcast down for l4 block {l4_block}\n{e}\n!Will ignore this broadcast!") payload = l5_block.export_as_at_rest() for chain_id in chain_id_set: try: headers, data = authorization.generate_authenticated_request("POST", chain_id, receipt_path, payload) url = f"{matchmaking.get_dragonchain_address(chain_id)}{receipt_path}" _log.info(f"----> {url}") r = requests.post(url, data=data, headers=headers, timeout=30) _log.info(f"<---- {r.status_code} {r.text}") if r.status_code != 200: # TODO failed to enqueue block to specific l1, consider another call to matchmaking, etc _log.info(f"[BROADCAST] WARNING: failed to transmit to {chain_id} with error {r.text}") else: _log.info("[BROADCAST] Sucessful receipt sent down to L1") except Exception: _log.error(f"[BROADCAST] ERROR: Couldn't broadcast receipt down to {chain_id}! Ignoring")
def make_broadcast_futures(session: aiohttp.ClientSession, block_id: str, level: int, chain_ids: set) -> Optional[Set[asyncio.Task]]: """Initiate broadcasts for a block id to certain higher level nodes Args: session: aiohttp session to use for making http requests block_id: the block id to broadcast level: higher level of the chain_ids to broadcast to chain_ids: set of (level) chains to broadcast to Returns: Set of asyncio futures for the http requests initialized (None if it was not possible to get broadcast dto) """ path = "/v1/enqueue" broadcasts = set() try: broadcast_dto = block_dao.get_broadcast_dto(level, block_id) except exceptions.NotEnoughVerifications as e: _log.warning(f"[BROADCAST PROCESSOR] {str(e)}") _log.info(f"[BROADCAST PROCESSOR] Will attempt to broadcast block {block_id} next run") broadcast_functions.increment_storage_error_sync(block_id, level) return None _log.debug(f"[BROADCAST PROCESSOR] Sending broadcast(s) for {block_id} level {level}:\n{broadcast_dto}") for chain in chain_ids: try: headers, data = authorization.generate_authenticated_request("POST", chain, path, broadcast_dto) if level != 5: headers["deadline"] = str(BROADCAST_RECEIPT_WAIT_TIME) else: headers["deadline"] = str(get_l5_wait_time(chain)) url = f"{matchmaking.get_dragonchain_address(chain)}{path}" _log.info(f"[BROADCAST PROCESSOR] Firing transaction for {chain} (level {level}) at {url}") broadcasts.add(asyncio.create_task(session.post(url=url, data=data, headers=headers, timeout=HTTP_REQUEST_TIMEOUT))) except Exception: _log.exception(f"[BROADCAST PROCESSOR] Exception trying to broadcast to {chain}") return broadcasts
def test_generated_authenticated_request_with_verifier( self, mock_get_auth_key, mock_date, mock_is_replay, mock_get_id): """ This is more of psuedo integration test, ensuring that the generate_authenticated_request 'POST', will generate things are properly validated by verify_request_authorization If this test ever fails, it means that interchain communication will be broken even if all other tests pass """ full_path = "/path" dcid = "test_dcid" timestamp = "2018-11-14T09:05:25.128176Z" json_content = {"thing": "test"} headers, content = authorization.generate_authenticated_request( "POST", dcid, full_path, json_content, "SHA256") auth_str = headers["Authorization"] # Test with SHA256 HMAC Auth authorization.verify_request_authorization(auth_str, "POST", full_path, dcid, timestamp, "application/json", content, False, "api_keys", "create", "create_api_key") headers, content = authorization.generate_authenticated_request( "POST", dcid, full_path, json_content, "BLAKE2b512") auth_str = headers["Authorization"] # Test with BLAKE2b512 HMAC Auth authorization.verify_request_authorization(auth_str, "POST", full_path, dcid, timestamp, "application/json", content, False, "api_keys", "create", "create_api_key") headers, content = authorization.generate_authenticated_request( "POST", dcid, full_path, json_content, "SHA3-256") auth_str = headers["Authorization"] # Test with SHA3-256 HMAC Auth authorization.verify_request_authorization(auth_str, "POST", full_path, dcid, timestamp, "application/json", content, False, "api_keys", "create", "create_api_key")
def make_matchmaking_request( http_verb: str, path: str, json_content: dict = None, retry: bool = True, authenticated: bool = True ) -> requests.Response: """Make an authenticated request to matchmaking and return the response Args: http_verb: GET, POST, etc path: path of the request json_content: OPTIONAL if the request needs a post body, include it as a dictionary retry: boolean whether or not to recursively retry on recoverable errors (i.e. no auth, missing registration) Note: This should not be provided manually, and is only for recursive calls within the function it authenticated: boolean whether or not this matchmaking endpoint is authenticated Returns: Requests response object Raises: exceptions.MatchmakingError when unexpected matchmaking error occurs exceptions.InsufficientFunds when matchmaking responds with payment required exceptions.NotFound when matchmaking responds with a 404 """ if json_content is None: json_content = {} http_verb = http_verb.upper() _log.info(f"[MATCHMAKING] Performing {http_verb} request to {path} with data: {json_content}") headers, data = None, None if authenticated: headers, data = authorization.generate_authenticated_request(http_verb, "matchmaking", path, json_content) else: data = json.dumps(json_content, separators=(",", ":")).encode("utf-8") if json_content else b"" headers = {"Content-Type": "application/json"} if json_content else {} response = requests.request(method=http_verb, url=f"{MATCHMAKING_ADDRESS}{path}", headers=headers, data=data, timeout=REQUEST_TIMEOUT) if response.status_code < 200 or response.status_code >= 300: if retry and response.status_code == 401 and authenticated: _log.warning("[MATCHMAKING] received 401 from matchmaking. Registering new key with matchmaking and trying again") authorization.register_new_key_with_matchmaking() return make_matchmaking_request(http_verb=http_verb, path=path, json_content=json_content, retry=False, authenticated=authenticated) elif retry and response.status_code == 403 and authenticated: _log.warning("[MATCHMAKING] received 403 from matchmaking. Registration is probably expired. Re-registering and trying again") register() return make_matchmaking_request(http_verb=http_verb, path=path, json_content=json_content, retry=False, authenticated=authenticated) elif response.status_code == 402: raise exceptions.InsufficientFunds("received insufficient funds (402) from matchmaking") elif response.status_code == 404: raise exceptions.NotFound("Not found (404) from matchmaking") raise exceptions.MatchmakingError( f"Received unexpected response code {response.status_code} from matchmaking with response:\n{response.text}" ) return response
def test_gen_interchain_request_dcid(self, mock_get_auth_key, date_mock, mock_register, mock_dcid): dcid = "adcid" full_path = "/path" json_content = {"thing": "test"} json_str = json.dumps(json_content, separators=(",", ":")).encode("utf-8") expected_headers = { "Content-Type": "application/json", "timestamp": "timestampZ", "dragonchain": dcid, "Authorization": "DC1-HMAC-SHA256 test_dcid:1oJseWBqbZokioWGWjb2jq1iq493MkgUyc3FkQND5XM=", } # Test valid SHA256 headers, content = authorization.generate_authenticated_request( "POST", dcid, full_path, json_content, "SHA256") self.assertEqual(content, json_str) self.assertDictEqual(headers, expected_headers) # Test valid BLAKE2b512 headers, content = authorization.generate_authenticated_request( "POST", dcid, full_path, json_content, "BLAKE2b512") expected_headers[ "Authorization"] = "DC1-HMAC-BLAKE2b512 test_dcid:JJiXbVuTjJ03/hNW8fZipw5DUiktO2lJSyml824eWS++mmilth7/BABgDYPvprAa99PHzFzYPA41iL45bI4p1w==" self.assertEqual(content, json_str) self.assertDictEqual(headers, expected_headers) # Test valid SHA3-256 headers, content = authorization.generate_authenticated_request( "POST", dcid, full_path, json_content, "SHA3-256") expected_headers[ "Authorization"] = "DC1-HMAC-SHA3-256 test_dcid:ANsT9nToNzhWbxtoank/oLMDZoish5tFVuhAMzF/obo=" self.assertEqual(content, json_str) self.assertDictEqual(headers, expected_headers)
def send_receipt(block: "model.BlockModel") -> None: try: dcid = block.get_associated_l1_dcid() full_path = "/v1/receipt" url = f"{matchmaking.get_dragonchain_address(dcid)}{full_path}" headers, data = authorization.generate_authenticated_request("POST", dcid, full_path, block.export_as_at_rest()) _log.info(f"----> {url}") r = requests.post(url, data=data, headers=headers, timeout=30) _log.info(f"<---- {r.status_code} {r.text}") if r.status_code != 200: _log.info(f"[BROADCAST] WARNING: failed to transmit to {dcid} with error {r.text}") else: _log.info("[BROADCAST] Sucessful receipt sent down to L1") except Exception: _log.error(f"[BROADCAST] ERROR: Couldn't broadcast receipt down to {dcid}! Ignoring")
def test_gen_interchain_request_matchmaking(self, date_mock, mock_register, mock_get, mock_dcid): full_path = "/path" json_content = {"thing": "test"} json_str = json.dumps(json_content, separators=(",", ":")).encode("utf-8") expected_headers = { "Content-Type": "application/json", "timestamp": "timestampZ", "Authorization": "DC1-HMAC-SHA256 test_dcid:ab+hEQC0NNJB7mHwpqsfQqEcOyolNOmDEQe9gvUZTYI=", } headers, content = authorization.generate_authenticated_request( "POST", "matchmaking", full_path, json_content, "SHA256") self.assertEqual(content, json_str) self.assertDictEqual(headers, expected_headers)