class Signer: def __init__(self, net_id): self.net_id = net_id self.lambda_client = boto3.client("lambda", region_name=REGION_NAME) self.obj_utils = Utils() self.obj_blockchain_utils = BlockChainUtil( provider_type="HTTP_PROVIDER", provider=NETWORKS[self.net_id]["http_provider"], ) self.mpe_address = self.obj_blockchain_utils.read_contract_address( net_id=self.net_id, path=MPE_ADDR_PATH, key="address") self.current_block_no = self.obj_blockchain_utils.get_current_block_no( ) def _get_free_calls_allowed(self, org_id, service_id, group_id): lambda_payload = { "httpMethod": "GET", "pathParameters": { "orgId": org_id, "serviceId": service_id }, } response = self.lambda_client.invoke( FunctionName= GET_SERVICE_DETAILS_FOR_GIVEN_ORG_ID_AND_SERVICE_ID_ARN, InvocationType="RequestResponse", Payload=json.dumps(lambda_payload), ) response_body_raw = json.loads(response.get("Payload").read())["body"] get_service_response = json.loads(response_body_raw) if get_service_response["status"] == "success": groups_data = get_service_response["data"].get("groups", []) for group_data in groups_data: if group_data["group_id"] == group_id: return group_data["free_calls"] raise Exception( "Unable to fetch free calls information for service %s under organization %s for %s group.", service_id, org_id, group_id) def _get_total_calls_made(self, username, org_id, service_id, group_id): lambda_payload = { "httpMethod": "GET", "queryStringParameters": { "organization_id": org_id, "service_id": service_id, "username": username, "group_id": group_id, }, } response = self.lambda_client.invoke( FunctionName=METERING_ARN, InvocationType="RequestResponse", Payload=json.dumps(lambda_payload), ) if response["StatusCode"] == 200: metering_data_raw = json.loads( response.get("Payload").read())["body"] total_calls_made = json.loads(metering_data_raw).get( "total_calls_made", None) if total_calls_made is not None: return total_calls_made raise Exception( "Unable to fetch total calls made for service %s under organization %s for %s group.", service_id, org_id, group_id) def _get_no_of_free_call_available(self, username, org_id, service_id, group_id): token_to_get_free_call, expiry_date_block, signature, current_block_number, daemon_endpoint, free_calls_allowed = self.token_to_get_free_call( username, org_id, service_id, group_id) total_free_call_available = 0 try: total_free_call_available = self._get_no_of_free_calls_from_daemon( username, token_to_get_free_call, expiry_date_block, signature, current_block_number, daemon_endpoint) except Exception as e: logger.info( f"Free call from daemon not available switching to metering {org_id} {service_id} {group_id} {username}" ) total_free_call_made = self._get_total_calls_made( username, org_id, service_id, group_id) total_free_call_available = free_calls_allowed - total_free_call_made return total_free_call_available def _free_calls_allowed(self, username, org_id, service_id, group_id): """ Method to check free calls exists for given user or not. Call monitoring service to get the details """ free_calls_available = self._get_no_of_free_call_available( username, org_id, service_id, group_id) if free_calls_available > 0: return True return False def signature_for_free_call(self, user_data, org_id, service_id, group_id): """ Method to generate signature for free call. """ try: username = user_data["authorizer"]["claims"]["email"] if self._free_calls_allowed(username=username, org_id=org_id, service_id=service_id, group_id=group_id): current_block_no = self.obj_utils.get_current_block_no( ws_provider=NETWORKS[self.net_id]["ws_provider"]) provider = Web3.HTTPProvider( NETWORKS[self.net_id]["http_provider"]) w3 = Web3(provider) message = web3.Web3.soliditySha3( ["string", "string", "string", "string", "uint256"], [ PREFIX_FREE_CALL, username, org_id, service_id, current_block_no ], ) signer_key = SIGNER_KEY if not signer_key.startswith("0x"): signer_key = "0x" + signer_key signature = bytes( w3.eth.account.signHash(defunct_hash_message(message), signer_key).signature) signature = signature.hex() if not signature.startswith("0x"): signature = "0x" + signature return { "snet-free-call-user-id": username, "snet-payment-channel-signature-bin": signature, "snet-current-block-number": current_block_no, "snet-payment-type": "free-call", "snet-free-call-auth-token-bin": "", "snet-free-call-token-expiry-block": 0 } else: raise Exception("Free calls expired for username %s.", username) except Exception as e: logger.error(repr(e)) raise e def signature_for_regular_call(self, user_data, channel_id, nonce, amount): """ Method to generate signature for regular call. """ try: username = user_data["authorizer"]["claims"]["email"] data_types = ["string", "address", "uint256", "uint256", "uint256"] values = [ "__MPE_claim_message", self.mpe_address, channel_id, nonce, amount, ] signature = self.obj_blockchain_utils.generate_signature( data_types=data_types, values=values, signer_key=SIGNER_KEY) return { "snet-payment-channel-signature-bin": signature, "snet-payment-type": "escrow", "snet-payment-channel-id": channel_id, "snet-payment-channel-nonce": nonce, "snet-payment-channel-amount": amount, "snet-current-block-number": self.current_block_no, } except Exception as e: logger.error(repr(e)) raise Exception( "Unable to generate signature for daemon call for username %s", username) def signature_for_state_service(self, user_data, channel_id): """ Method to generate signature for state service. """ try: username = user_data["authorizer"]["claims"]["email"] data_types = ["string", "address", "uint256", "uint256"] values = [ "__get_channel_state", self.mpe_address, channel_id, self.current_block_no, ] signature = self.obj_blockchain_utils.generate_signature( data_types=data_types, values=values, signer_key=SIGNER_KEY) return { "signature": signature, "snet-current-block-number": self.current_block_no, } except Exception as e: logger.error(repr(e)) raise Exception( "Unable to generate signature for daemon call for username %s", username) def signature_for_open_channel_for_third_party(self, recipient, group_id, amount_in_cogs, expiration, message_nonce, sender_private_key, executor_wallet_address): data_types = [ "string", "address", "address", "address", "address", "bytes32", "uint256", "uint256", "uint256" ] values = [ "__openChannelByThirdParty", self.mpe_address, executor_wallet_address, SIGNER_ADDRESS, recipient, group_id, amount_in_cogs, expiration, message_nonce ] signature = self.obj_blockchain_utils.generate_signature( data_types=data_types, values=values, signer_key=sender_private_key) v, r, s = Web3.toInt( hexstr="0x" + signature[-2:]), signature[:66], "0x" + signature[66:130] return {"r": r, "s": s, "v": v, "signature": signature} def _get_no_of_free_calls_from_daemon(self, email, token_to_get_free_call, expiry_date_block, signature, current_block_number, daemon_endpoint): request = state_service_pb2.FreeCallStateRequest() request.user_id = email request.token_for_free_call = token_to_get_free_call request.token_expiry_date_block = expiry_date_block request.signature = signature request.current_block = current_block_number endpoint_object = urlparse(daemon_endpoint) if endpoint_object.port is not None: channel_endpoint = endpoint_object.hostname + ":" + str( endpoint_object.port) else: channel_endpoint = endpoint_object.hostname if endpoint_object.scheme == "http": channel = grpc.insecure_channel(channel_endpoint) elif endpoint_object.scheme == "https": channel = grpc.secure_channel(channel_endpoint, grpc.ssl_channel_credentials()) else: raise ValueError( 'Unsupported scheme in service metadata ("{}")'.format( endpoint_object.scheme)) stub = state_service_pb2_grpc.FreeCallStateServiceStub(channel) response = stub.GetFreeCallsAvailable(request) return response.free_calls_available def _is_free_call_available(self, email, token_for_free_call, expiry_date_block, signature, current_block_number, daemon_endpoint): if self._get_no_of_free_calls_from_daemon( email, token_for_free_call, expiry_date_block, signature, current_block_number, daemon_endpoint) > 0: return True return False def _get_daemon_endpoint_and_free_call_for_group(self, org_id, service_id, group_id): lambda_payload = { "httpMethod": "GET", "pathParameters": { "orgId": org_id, "serviceId": service_id }, } response = self.lambda_client.invoke( FunctionName= GET_SERVICE_DETAILS_FOR_GIVEN_ORG_ID_AND_SERVICE_ID_ARN, InvocationType="RequestResponse", Payload=json.dumps(lambda_payload), ) response_body_raw = json.loads(response.get("Payload").read())["body"] get_service_response = json.loads(response_body_raw) if get_service_response["status"] == "success": groups_data = get_service_response["data"].get("groups", []) for group_data in groups_data: if group_data["group_id"] == group_id: return group_data["endpoints"][0][ "endpoint"], group_data.get("free_calls", 0) raise Exception( "Unable to fetch daemon Endpoint information for service %s under organization %s for %s group.", service_id, org_id, group_id) def token_to_get_free_call(self, email, org_id, service_id, group_id): signer_public_key_checksum = Web3.toChecksumAddress(SIGNER_ADDRESS) current_block_number = self.obj_blockchain_utils.get_current_block_no() expiry_date_block = current_block_number + FREE_CALL_EXPIRY token_to_get_free_call = self.obj_blockchain_utils.generate_signature_bytes( ["string", "address", "uint256"], [email, signer_public_key_checksum, expiry_date_block], SIGNER_KEY) signature = self.obj_blockchain_utils.generate_signature_bytes([ "string", "string", "string", "string", "string", "uint256", "bytes32" ], [ "__prefix_free_trial", email, org_id, service_id, group_id, current_block_number, token_to_get_free_call ], SIGNER_KEY) daemon_endpoint, free_calls_allowed = self._get_daemon_endpoint_and_free_call_for_group( org_id, service_id, group_id) logger.info( f"Got daemon endpoint {daemon_endpoint} for org {org_id} service {service_id} group {group_id}" ) return token_to_get_free_call, expiry_date_block, signature, current_block_number, daemon_endpoint, free_calls_allowed def token_to_make_free_call(self, email, org_id, service_id, group_id, user_public_key): token_to_get_free_call, expiry_date_block, signature, current_block_number, daemon_endpoint, free_calls_allowed = self.token_to_get_free_call( email, org_id, service_id, group_id) token_with_expiry_to_make_free_call = "" if self._is_free_call_available(email, token_to_get_free_call, expiry_date_block, signature, current_block_number, daemon_endpoint): token_with_expiry_to_make_free_call = self.obj_blockchain_utils.generate_signature_bytes( ["string", "address", "uint256"], [ email, Web3.toChecksumAddress(user_public_key), expiry_date_block ], SIGNER_KEY) return { "token_to_make_free_call": token_with_expiry_to_make_free_call.hex(), "token_expiration_block": expiry_date_block }
class Signer: def __init__(self, obj_repo, net_id): self.repo = obj_repo self.net_id = net_id self.lambda_client = boto3.client('lambda') self.obj_utils = Utils() def _free_calls_allowed(self, username, org_id, service_id): """ Method to check free calls exists for given user or not. Call monitoring service to get the details """ try: lambda_payload = {"httpMethod": "GET", "queryStringParameters": {"organization_id": org_id, "service_id": service_id, "username": username}} response = self.lambda_client.invoke(FunctionName=GET_FREE_CALLS_METERING_ARN, InvocationType='RequestResponse', Payload=json.dumps(lambda_payload)) response_body_raw = json.loads( response.get('Payload').read())['body'] response_body = json.loads(response_body_raw) free_calls_allowed = response_body["free_calls_allowed"] total_calls_made = response_body["total_calls_made"] is_free_calls_allowed = True if ( (free_calls_allowed - total_calls_made) > 0) else False return is_free_calls_allowed except Exception as e: print(repr(e)) raise e def signature_for_free_call(self, user_data, org_id, service_id): """ Method to generate signature for free call. """ try: username = user_data['authorizer']['claims']['email'] if self._free_calls_allowed(username=username, org_id=org_id, service_id=service_id): current_block_no = self.obj_utils.get_current_block_no( ws_provider=NETWORKS[self.net_id]['ws_provider']) provider = Web3.HTTPProvider( NETWORKS[self.net_id]['http_provider']) w3 = Web3(provider) message = web3.Web3.soliditySha3(["string", "string", "string", "string", "uint256"], [PREFIX_FREE_CALL, username, org_id, service_id, current_block_no]) if not config['private_key'].startswith("0x"): config['private_key'] = "0x" + config['private_key'] signature = bytes(w3.eth.account.signHash( defunct_hash_message(message), config['private_key']).signature) signature = signature.hex() if not signature.startswith("0x"): signature = "0x" + signature return {"snet-free-call-user-id": username, "snet-payment-channel-signature-bin": signature, "snet-current-block-number": current_block_no, "snet-payment-type": "free-call"} else: raise Exception( "Free calls expired for username %s.", username) except Exception as e: print(repr(e)) raise e def _get_user_address(self, username): try: wallet_address_data = self.repo.execute("SELECT address FROM wallet WHERE username = %s AND status = 1 " "LIMIT 1", [username]) if len(wallet_address_data) == 1: return wallet_address_data[0]['address'] raise Exception( "Unable to find wallet address for username %s", username) except Exception as e: print(repr(e)) raise e def _get_service_metadata(self, org_id, service_id): """ Method to get group details for given org_id and service_id. """ try: result = self.repo.execute( "SELECT O.*, G.* FROM org_group O, service_group G WHERE O.org_id = G.org_id AND G.group_id = " "O.group_id AND G.service_id = %s AND G.org_id = %s AND G.service_row_id IN (SELECT row_id FROM service " "WHERE is_curated = 1 ) LIMIT 1", [org_id, service_id]) if len(result) == 1: metadata = result[0] payment = json.loads(metadata["payment"]) pricing = json.loads(metadata["pricing"]) metadata.update(payment) metadata["pricing"] = pricing return metadata else: raise Exception( "Unable to find service for service_id %s", service_id) except Exception as e: print(repr(e)) raise e def _get_channel_id(self, sender_address, recipient_address, group_id, signer_address): """ Method to fetch channel id from mpe_channel(RDS). """ try: channel_data = self.repo.execute( "SELECT channel_id FROM mpe_channel WHERE sender = %s AND recipient = %s AND " " groupId = %s AND signer = %s LIMIT 1", [sender_address, recipient_address, group_id, signer_address]) if len(channel_data) == 1: return channel_data[0]["channel_id"] raise Exception("Unable to find channels.") except Exception as e: print(repr(e)) raise e def signature_for_regular_call(self, user_data, org_id, service_id): """ Method to generate signature for regular call. """ try: username = user_data['authorizer']['claims']['email'] user_address = self._get_user_address(username=username) metadata = self._get_service_metadata( org_id=org_id, service_id=service_id) recipient_address = metadata['payment']['payment_address'] channel_id = self._get_channel_id(sender_address=user_address, recipient_address=recipient_address, group_id=metadata['group_id'], signer_address=config["signer_address"]) object_service_client = ServiceClient( config=config, metadata=metadata, options=dict()) return object_service_client.get_service_call_metadata(channel_id=channel_id) except Exception as e: print(repr(e)) raise Exception( "Unable to sign regular call for username %s", username)