From dce353edddb79cc61f5b1df9d48c9353e957a57d Mon Sep 17 00:00:00 2001 From: mabredin Date: Fri, 25 Apr 2025 17:51:12 +0300 Subject: [PATCH 01/11] Update free calls token receiving logic Update docs Update state_service.proto --- README.md | 20 +- docs/main/init.md | 3 +- .../freecall_payment_strategy.md | 32 +++- snet/sdk/__init__.py | 17 +- .../freecall_payment_strategy.py | 100 ++++++---- snet/sdk/resources/proto/state_service.proto | 178 ++++++++++-------- snet/sdk/resources/proto/state_service_pb2.py | 26 +-- .../resources/proto/state_service_pb2_grpc.py | 83 +++++--- snet/sdk/service_client.py | 2 +- 9 files changed, 275 insertions(+), 186 deletions(-) diff --git a/README.md b/README.md index 13a6e92..cfa745b 100644 --- a/README.md +++ b/README.md @@ -36,9 +36,9 @@ instance of the base sdk class: from snet import sdk config = sdk.config.Config(private_key="YOUR_PRIVATE_KEY", - eth_rpc_endpoint=f"https://sepolia.infura.io/v3/YOUR_INFURA_KEY", - concurrency=False, - force_update=False) + eth_rpc_endpoint=f"https://sepolia.infura.io/v3/YOUR_INFURA_KEY", + concurrency=False, + force_update=False) snet_sdk = sdk.SnetSDK(config) ``` @@ -140,23 +140,17 @@ visit the [Payment](#payment) section. ### Free call -If you want to use the free calls you will need to pass these arguments to the `create_service_client()` method: +If you want to use the free calls you will need to pass this argument to the `create_service_client()` method: ```python -free_call_auth_token_bin = "f2548d27ffd319b9c05918eeac15ebab934e5cfcd68e1ec3db2b92765", -free_call_token_expiry_block = 172800, -email = "test@test.com" # which using in AI marketplace account +address = "YOUR_ETHEREUM_ADDRESS" ``` -You can receive these for a given service from the [Dapp](https://beta.singularitynet.io/) - Creating a service client with free calls included would look like this: ```python service_client = snet_sdk.create_service_client(org_id="26072b8b6a0e448180f8c0e702ab6d2f", - service_id="Exampleservice" - free_call_auth_token_bin="f2548d27ffd319b9c05918eeac15ebab934e5cfcd68e1ec3db2b92765", - free_call_token_expiry_block=172800, - email="test@mail.com") + service_id="Exampleservice", + address="0xtest") ``` ### Paid call diff --git a/docs/main/init.md b/docs/main/init.md index 7a82f64..1436cc5 100644 --- a/docs/main/init.md +++ b/docs/main/init.md @@ -73,8 +73,7 @@ of the `ServiceClient` class with all the required parameters, which is then ret - `service_id` (str): The ID of the service. - `group_name` (str): The name of the payment group. Defaults to _None_. - `payment_strategy` (PaymentStrategy): The payment channel management strategy. Defaults to _None_. -- `free_call_auth_token_bin` (str): The free call authentication token in binary format. Defaults to _None_. -- `free_call_token_expiry_block` (int): The block number when the free call token expires. Defaults to _None_. +- `address` (str): Wallet address to use free calls. Defaults to _None_. - `options` (dict): Additional options for the service client. Defaults to _None_. - `concurrent_calls` (int): The number of concurrent calls allowed. Defaults to 1. diff --git a/docs/payment_strategies/freecall_payment_strategy.md b/docs/payment_strategies/freecall_payment_strategy.md index 67424ac..e6f5934 100644 --- a/docs/payment_strategies/freecall_payment_strategy.md +++ b/docs/payment_strategies/freecall_payment_strategy.md @@ -7,6 +7,8 @@ Entities: - [is_free_call_available](#is_free_call_available) - [get_payment_metadata](#get_payment_metadata) - [generate_signature](#generate_signature) + - [get_free_call_token_details](#get_free_call_token_details) + - [select_channel](#select_channel) ### Class `FreeCallPaymentStrategy` @@ -17,7 +19,7 @@ is extended by: - #### description The `FreeCallPaymentStrategy` class is a concrete implementation of the `PaymentStrategy` interface. -It allows you to use free calls (which can be received from the [Dapp](https://beta.singularitynet.io/)) to +It allows you to use free calls (which can be received from the daemon) to call services. #### methods @@ -68,3 +70,31 @@ Generates a signature for the given service client using the provided free call ###### raises: - Exception: If any of the required parameters for the free call strategy are missing. + +#### `get_free_call_token_details` + +Sends a request to the daemon and receives a free call token. + +###### args: + +- `service_client` (ServiceClient): The service client instance. + +###### returns: + +- A tuple containing the free call token and the token expiration block number. (tuple[str, int]) + +###### raises: + +- Exception: If an error occurred while receiving the token. + +#### `select_channel` + +Creates a channel to the daemon. + +###### args: + +- `service_client` (ServiceClient): The service client object. + +###### returns: + +- The channel for the service calling. \ No newline at end of file diff --git a/snet/sdk/__init__.py b/snet/sdk/__init__.py index ee8b4e7..3bdf743 100644 --- a/snet/sdk/__init__.py +++ b/snet/sdk/__init__.py @@ -26,7 +26,7 @@ from snet.sdk.client_lib_generator import ClientLibGenerator from snet.sdk.mpe.mpe_contract import MPEContract from snet.sdk.mpe.payment_channel_provider import PaymentChannelProvider -from snet.sdk.payment_strategies.default_payment_strategy import DefaultPaymentStrategy as PaymentStrategy +from snet.sdk.payment_strategies.default_payment_strategy import DefaultPaymentStrategy from snet.sdk.service_client import ServiceClient from snet.sdk.storage_provider.storage_provider import StorageProvider from snet.sdk.custom_typing import ModuleName, ServiceStub @@ -93,9 +93,7 @@ def create_service_client(self, service_id: str, group_name=None, payment_strategy=None, - free_call_auth_token_bin=None, - free_call_token_expiry_block=None, - email=None, + address=None, options=None, concurrent_calls: int = 1): @@ -121,20 +119,13 @@ def create_service_client(self, self.lib_generator.generate_client_library() if payment_strategy is None: - payment_strategy = PaymentStrategy( + payment_strategy = DefaultPaymentStrategy( concurrent_calls=concurrent_calls ) if options is None: options = dict() - options['free_call_auth_token-bin'] = ( - bytes.fromhex(free_call_auth_token_bin) if - free_call_token_expiry_block else "" - ) - options['free-call-token-expiry-block'] = ( - free_call_token_expiry_block if free_call_token_expiry_block else 0 - ) - options['email'] = email if email else "" + options['user_address'] = address if address else "" options['concurrency'] = self._sdk_config.get("concurrency", True) service_metadata = self._metadata_provider.enhance_service_metadata( diff --git a/snet/sdk/payment_strategies/freecall_payment_strategy.py b/snet/sdk/payment_strategies/freecall_payment_strategy.py index aae70b6..5134b50 100644 --- a/snet/sdk/payment_strategies/freecall_payment_strategy.py +++ b/snet/sdk/payment_strategies/freecall_payment_strategy.py @@ -2,87 +2,109 @@ from urllib.parse import urlparse import grpc +from grpc import _channel import web3 -from snet.sdk.resources.root_certificate import certificate -from snet.sdk.utils.utils import RESOURCES_PATH, add_to_path from snet.sdk.payment_strategies.payment_strategy import PaymentStrategy - +from snet.sdk.resources.root_certificate import certificate +from snet.sdk.utils.utils import RESOURCES_PATH, add_to_path class FreeCallPaymentStrategy(PaymentStrategy): - def is_free_call_available(self, service_client): + def is_free_call_available(self, service_client) -> bool: try: - org_id, service_id, group_id, daemon_endpoint = service_client.get_service_details() - email, token_for_free_call, token_expiry_date_block = service_client.get_free_call_config() + self._user_address = service_client.options["user_address"] + self._free_call_token, self._token_expiry_date_block = self.get_free_call_token_details(service_client) - if not token_for_free_call: + if not self._free_call_token: return False - signature, current_block_number = self.generate_signature(service_client) with add_to_path(str(RESOURCES_PATH.joinpath("proto"))): state_service_pb2 = importlib.import_module("state_service_pb2") with add_to_path(str(RESOURCES_PATH.joinpath("proto"))): state_service_pb2_grpc = importlib.import_module("state_service_pb2_grpc") + signature, current_block_number = self.generate_signature(service_client) + request = state_service_pb2.FreeCallStateRequest() - request.user_id = email - request.token_for_free_call = token_for_free_call - request.token_expiry_date_block = token_expiry_date_block + request.user_address = self._user_address + request.token_for_free_call = self._free_call_token + request.token_expiry_date_block = self._token_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(root_certificates=certificate)) - else: - raise ValueError('Unsupported scheme in service metadata ("{}")'.format(endpoint_object.scheme)) + channel = self.select_channel(service_client) stub = state_service_pb2_grpc.FreeCallStateServiceStub(channel) response = stub.GetFreeCallsAvailable(request) if response.free_calls_available > 0: return True return False + except grpc.RpcError as e: + if self._user_address: + print(f"Warning: {e.details()}") + return False except Exception as e: return False - def get_payment_metadata(self, service_client): - email, token_for_free_call, token_expiry_date_block = service_client.get_free_call_config() + def get_payment_metadata(self, service_client) -> list: signature, current_block_number = self.generate_signature(service_client) - metadata = [("snet-free-call-auth-token-bin", token_for_free_call), - ("snet-free-call-token-expiry-block", str(token_expiry_date_block)), + metadata = [("snet-free-call-auth-token-bin", self._free_call_token), + ("snet-free-call-token-expiry-block", str(self._token_expiry_date_block)), ("snet-payment-type", "free-call"), - ("snet-free-call-user-id", email), + ("snet-free-call-user-id", self._user_address), ("snet-current-block-number", str(current_block_number)), ("snet-payment-channel-signature-bin", signature)] return metadata - def select_channel(self, service_client): - pass - - def generate_signature(self, service_client): - org_id, service_id, group_id, daemon_endpoint = service_client.get_service_details() - email, token_for_free_call, token_expiry_date_block = service_client.get_free_call_config() - - if token_expiry_date_block == 0 or len(email) == 0 or len(token_for_free_call) == 0: + def select_channel(self, service_client) -> _channel.Channel: + _, _, _, daemon_endpoint = service_client.get_service_details() + 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(root_certificates=certificate)) + else: + raise ValueError('Unsupported scheme in service metadata ("{}")'.format(endpoint_object.scheme)) + return channel + + def generate_signature(self, service_client) -> tuple[bytes, int]: + org_id, service_id, group_id, _ = service_client.get_service_details() + + if self._token_expiry_date_block == 0 or len(self._user_address) == 0 or len(self._free_call_token) == 0: raise Exception( "You are using default 'FreeCallPaymentStrategy' to use this strategy you need to pass " - "'free_call_auth_token-bin','email','free-call-token-expiry-block' in config") + "'free_call_auth_token-bin','user_address','free-call-token-expiry-block' in config") current_block_number = service_client.get_current_block_number() message = web3.Web3.solidity_keccak( ["string", "string", "string", "string", "string", "uint256", "bytes32"], - ["__prefix_free_trial", email, org_id, service_id, group_id, current_block_number, - token_for_free_call] + ["__prefix_free_trial", self._user_address, org_id, service_id, group_id, current_block_number, + self._free_call_token] ) return service_client.generate_signature(message), current_block_number + + def get_free_call_token_details(self, service_client) -> tuple[bytes, int]: + with add_to_path(str(RESOURCES_PATH.joinpath("proto"))): + state_service_pb2 = importlib.import_module("state_service_pb2") + + request = state_service_pb2.GetFreeCallTokenRequest() + request.address = self._user_address + + with add_to_path(str(RESOURCES_PATH.joinpath("proto"))): + state_service_pb2_grpc = importlib.import_module("state_service_pb2_grpc") + + channel = self.select_channel(service_client) + stub = state_service_pb2_grpc.FreeCallStateServiceStub(channel) + response = stub.GetFreeCallToken(request) + + return response.token, response.token_expiration_block + \ No newline at end of file diff --git a/snet/sdk/resources/proto/state_service.proto b/snet/sdk/resources/proto/state_service.proto index a2ce9aa..e6d505a 100644 --- a/snet/sdk/resources/proto/state_service.proto +++ b/snet/sdk/resources/proto/state_service.proto @@ -1,81 +1,97 @@ -syntax = "proto3"; - -package escrow; - -option java_package = "io.singularitynet.daemon.escrow"; - -// PaymentChannelStateService contains methods to get the MultiPartyEscrow -// payment channel state. -// channel_id, channel_nonce, value and amount fields below in fact are -// Solidity uint256 values. Which are big-endian integers, see -// https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#formal-specification-of-the-encoding -// These values may be or may be not padded by zeros, service supports both -// options. -service PaymentChannelStateService { - // GetChannelState method returns a channel state by channel id. - rpc GetChannelState(ChannelStateRequest) returns (ChannelStateReply) {} -} - -// ChanelStateRequest is a request for channel state. -message ChannelStateRequest { - // channel_id contains id of the channel which state is requested. - bytes channel_id = 1; - - // signature is a client signature of the message which contains - // channel_id. It is used for client authorization. - bytes signature = 2; - - //current block number (signature will be valid only for short time around this block number) - uint64 current_block = 3; -} - -// ChannelStateReply message contains a latest channel state. current_nonce and -// current_value fields can be different from ones stored in the blockchain if -// server started withdrawing funds froms channel but transaction is still not -// finished. -message ChannelStateReply { - // current_nonce is a latest nonce of the payment channel. - bytes current_nonce = 1; - - // current_signed_amount is a last amount which were signed by client with current_nonce - //it could be absent if none message was signed with current_nonce - bytes current_signed_amount = 2; - - // current_signature is a last signature sent by client with current_nonce - // it could be absent if none message was signed with current nonce - bytes current_signature = 3; - - // last amount which was signed by client with nonce=current_nonce - 1 - bytes old_nonce_signed_amount = 4; - - // last signature sent by client with nonce = current_nonce - 1 - bytes old_nonce_signature = 5; - } - -//Used to determine free calls available for a given user. -service FreeCallStateService { - rpc GetFreeCallsAvailable(FreeCallStateRequest) returns (FreeCallStateReply) {} -} - -message FreeCallStateRequest { - //Has the user email id - string user_id = 1; - //signer-token = (user@mail, user-public-key, token_issue_date), this is generated my Market place Dapp - //to leverage free calls from SDK/ snet-cli, you will need this signer-token to be downloaded from Dapp - bytes token_for_free_call = 2; - //Token expiration date in Block number - uint64 token_expiry_date_block = 3 ; - //Signature is made up of the below, user signs with the private key corresponding with the public key used to generate the authorized token - //free-call-metadata = ("__prefix_free_trial",user_id,organization_id,service_id,group_id,current_block,authorized_token) - bytes signature = 4; - //current block number (signature will be valid only for short time around this block number) - uint64 current_block = 5; - -} - -message FreeCallStateReply { - //Has the user email id - string user_id = 1; - //Balance number of free calls available - uint64 free_calls_available = 2; -} +syntax = "proto3"; + +package escrow; + +option java_package = "io.singularitynet.daemon.escrow"; +option go_package = "../escrow"; + +// PaymentChannelStateService contains methods to get the MultiPartyEscrow +// payment channel state. +// channel_id, channel_nonce, value and amount fields below in fact are +// Solidity uint256 values. Which are big-endian integers, see +// https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#formal-specification-of-the-encoding +// These values may be or may be not padded by zeros, service supports both +// options. +service PaymentChannelStateService { + // GetChannelState method returns a channel state by channel id. + rpc GetChannelState(ChannelStateRequest) returns (ChannelStateReply) {} +} + +// ChanelStateRequest is a request for channel state. +message ChannelStateRequest { + // channel_id contains id of the channel which state is requested. + bytes channel_id = 1; + + // signature is a client signature of the message which contains + // channel_id. It is used for client authorization. + bytes signature = 2; + + //current block number (signature will be valid only for short time around this block number) + uint64 current_block = 3; +} + +// ChannelStateReply message contains a latest channel state. current_nonce and +// current_value fields can be different from ones stored in the blockchain if +// server started withdrawing funds froms channel but transaction is still not +// finished. +message ChannelStateReply { + // current_nonce is a latest nonce of the payment channel. + bytes current_nonce = 1; + + // current_signed_amount is a last amount which were signed by client with current_nonce + //it could be absent if none message was signed with current_nonce + bytes current_signed_amount = 2; + + // current_signature is a last signature sent by client with current_nonce + // it could be absent if none message was signed with current nonce + bytes current_signature = 3; + + // last amount which was signed by client with nonce=current_nonce - 1 + bytes old_nonce_signed_amount = 4; + + // last signature sent by client with nonce = current_nonce - 1 + bytes old_nonce_signature = 5; + + //If the client / user chooses to sign upfront , the planned amount in cogs will be indicative of this. + //For pay per use, this will be zero + uint64 planned_amount = 6; + + //If the client / user chooses to sign upfront , the usage amount in cogs will be indicative of how much of the + //planned amount has actually been used. + //For pay per use, this will be zero + uint64 used_amount = 7; +} + +//Used to determine free calls available for a given user. +service FreeCallStateService { + rpc GetFreeCallsAvailable(FreeCallStateRequest) returns (FreeCallStateReply) {} + rpc GetFreeCallToken(GetFreeCallTokenRequest) returns (FreeCallToken) {} +} + +message GetFreeCallTokenRequest{ + string address = 1; +} + +message FreeCallToken{ + // token_hex == token but just in another format + string token_hex = 1; + bytes token = 2; + uint64 token_expiration_block = 3; +} + +message FreeCallStateRequest { + string user_address = 1; + bytes token_for_free_call = 2; + //Token expiration date in Block number + uint64 token_expiry_date_block = 3; + //Signature is made up of the below, user signs with the private key corresponding with the public key used to generate the authorized token + //free-call-metadata = ("__prefix_free_trial",user_address ,organization_id,service_id,group_id,current_block,authorized_token) + bytes signature = 4; + //current block number (signature will be valid only for short time around this block number) + uint64 current_block = 5; +} + +message FreeCallStateReply { + // number of free calls available + uint64 free_calls_available = 1; +} diff --git a/snet/sdk/resources/proto/state_service_pb2.py b/snet/sdk/resources/proto/state_service_pb2.py index d298f8a..8d2530d 100644 --- a/snet/sdk/resources/proto/state_service_pb2.py +++ b/snet/sdk/resources/proto/state_service_pb2.py @@ -13,24 +13,28 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x13state_service.proto\x12\x06\x65scrow\"S\n\x13\x43hannelStateRequest\x12\x12\n\nchannel_id\x18\x01 \x01(\x0c\x12\x11\n\tsignature\x18\x02 \x01(\x0c\x12\x15\n\rcurrent_block\x18\x03 \x01(\x04\"\xa2\x01\n\x11\x43hannelStateReply\x12\x15\n\rcurrent_nonce\x18\x01 \x01(\x0c\x12\x1d\n\x15\x63urrent_signed_amount\x18\x02 \x01(\x0c\x12\x19\n\x11\x63urrent_signature\x18\x03 \x01(\x0c\x12\x1f\n\x17old_nonce_signed_amount\x18\x04 \x01(\x0c\x12\x1b\n\x13old_nonce_signature\x18\x05 \x01(\x0c\"\x8f\x01\n\x14\x46reeCallStateRequest\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12\x1b\n\x13token_for_free_call\x18\x02 \x01(\x0c\x12\x1f\n\x17token_expiry_date_block\x18\x03 \x01(\x04\x12\x11\n\tsignature\x18\x04 \x01(\x0c\x12\x15\n\rcurrent_block\x18\x05 \x01(\x04\"C\n\x12\x46reeCallStateReply\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12\x1c\n\x14\x66ree_calls_available\x18\x02 \x01(\x04\x32i\n\x1aPaymentChannelStateService\x12K\n\x0fGetChannelState\x12\x1b.escrow.ChannelStateRequest\x1a\x19.escrow.ChannelStateReply\"\x00\x32k\n\x14\x46reeCallStateService\x12S\n\x15GetFreeCallsAvailable\x12\x1c.escrow.FreeCallStateRequest\x1a\x1a.escrow.FreeCallStateReply\"\x00\x42!\n\x1fio.singularitynet.daemon.escrowb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x13state_service.proto\x12\x06\x65scrow\"S\n\x13\x43hannelStateRequest\x12\x12\n\nchannel_id\x18\x01 \x01(\x0c\x12\x11\n\tsignature\x18\x02 \x01(\x0c\x12\x15\n\rcurrent_block\x18\x03 \x01(\x04\"\xcf\x01\n\x11\x43hannelStateReply\x12\x15\n\rcurrent_nonce\x18\x01 \x01(\x0c\x12\x1d\n\x15\x63urrent_signed_amount\x18\x02 \x01(\x0c\x12\x19\n\x11\x63urrent_signature\x18\x03 \x01(\x0c\x12\x1f\n\x17old_nonce_signed_amount\x18\x04 \x01(\x0c\x12\x1b\n\x13old_nonce_signature\x18\x05 \x01(\x0c\x12\x16\n\x0eplanned_amount\x18\x06 \x01(\x04\x12\x13\n\x0bused_amount\x18\x07 \x01(\x04\"*\n\x17GetFreeCallTokenRequest\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\"Q\n\rFreeCallToken\x12\x11\n\ttoken_hex\x18\x01 \x01(\t\x12\r\n\x05token\x18\x02 \x01(\x0c\x12\x1e\n\x16token_expiration_block\x18\x03 \x01(\x04\"\x94\x01\n\x14\x46reeCallStateRequest\x12\x14\n\x0cuser_address\x18\x01 \x01(\t\x12\x1b\n\x13token_for_free_call\x18\x02 \x01(\x0c\x12\x1f\n\x17token_expiry_date_block\x18\x03 \x01(\x04\x12\x11\n\tsignature\x18\x04 \x01(\x0c\x12\x15\n\rcurrent_block\x18\x05 \x01(\x04\"2\n\x12\x46reeCallStateReply\x12\x1c\n\x14\x66ree_calls_available\x18\x01 \x01(\x04\x32i\n\x1aPaymentChannelStateService\x12K\n\x0fGetChannelState\x12\x1b.escrow.ChannelStateRequest\x1a\x19.escrow.ChannelStateReply\"\x00\x32\xb9\x01\n\x14\x46reeCallStateService\x12S\n\x15GetFreeCallsAvailable\x12\x1c.escrow.FreeCallStateRequest\x1a\x1a.escrow.FreeCallStateReply\"\x00\x12L\n\x10GetFreeCallToken\x12\x1f.escrow.GetFreeCallTokenRequest\x1a\x15.escrow.FreeCallToken\"\x00\x42,\n\x1fio.singularitynet.daemon.escrowZ\t../escrowb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'state_service_pb2', _globals) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None - DESCRIPTOR._serialized_options = b'\n\037io.singularitynet.daemon.escrow' + DESCRIPTOR._serialized_options = b'\n\037io.singularitynet.daemon.escrowZ\t../escrow' _globals['_CHANNELSTATEREQUEST']._serialized_start=31 _globals['_CHANNELSTATEREQUEST']._serialized_end=114 _globals['_CHANNELSTATEREPLY']._serialized_start=117 - _globals['_CHANNELSTATEREPLY']._serialized_end=279 - _globals['_FREECALLSTATEREQUEST']._serialized_start=282 - _globals['_FREECALLSTATEREQUEST']._serialized_end=425 - _globals['_FREECALLSTATEREPLY']._serialized_start=427 - _globals['_FREECALLSTATEREPLY']._serialized_end=494 - _globals['_PAYMENTCHANNELSTATESERVICE']._serialized_start=496 - _globals['_PAYMENTCHANNELSTATESERVICE']._serialized_end=601 - _globals['_FREECALLSTATESERVICE']._serialized_start=603 - _globals['_FREECALLSTATESERVICE']._serialized_end=710 + _globals['_CHANNELSTATEREPLY']._serialized_end=324 + _globals['_GETFREECALLTOKENREQUEST']._serialized_start=326 + _globals['_GETFREECALLTOKENREQUEST']._serialized_end=368 + _globals['_FREECALLTOKEN']._serialized_start=370 + _globals['_FREECALLTOKEN']._serialized_end=451 + _globals['_FREECALLSTATEREQUEST']._serialized_start=454 + _globals['_FREECALLSTATEREQUEST']._serialized_end=602 + _globals['_FREECALLSTATEREPLY']._serialized_start=604 + _globals['_FREECALLSTATEREPLY']._serialized_end=654 + _globals['_PAYMENTCHANNELSTATESERVICE']._serialized_start=656 + _globals['_PAYMENTCHANNELSTATESERVICE']._serialized_end=761 + _globals['_FREECALLSTATESERVICE']._serialized_start=764 + _globals['_FREECALLSTATESERVICE']._serialized_end=949 # @@protoc_insertion_point(module_scope) diff --git a/snet/sdk/resources/proto/state_service_pb2_grpc.py b/snet/sdk/resources/proto/state_service_pb2_grpc.py index 977dbd7..e29ae1d 100644 --- a/snet/sdk/resources/proto/state_service_pb2_grpc.py +++ b/snet/sdk/resources/proto/state_service_pb2_grpc.py @@ -6,13 +6,13 @@ class PaymentChannelStateServiceStub(object): - """PaymentChannelStateService contains methods to get the MultiPartyEscrow - payment channel state. - channel_id, channel_nonce, value and amount fields below in fact are - Solidity uint256 values. Which are big-endian integers, see - https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#formal-specification-of-the-encoding - These values may be or may be not padded by zeros, service supports both - options. + """PaymentChannelStateService contains methods to get the MultiPartyEscrow + payment channel state. + channel_id, channel_nonce, value and amount fields below in fact are + Solidity uint256 values. Which are big-endian integers, see + https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#formal-specification-of-the-encoding + These values may be or may be not padded by zeros, service supports both + options. """ def __init__(self, channel): @@ -29,17 +29,17 @@ def __init__(self, channel): class PaymentChannelStateServiceServicer(object): - """PaymentChannelStateService contains methods to get the MultiPartyEscrow - payment channel state. - channel_id, channel_nonce, value and amount fields below in fact are - Solidity uint256 values. Which are big-endian integers, see - https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#formal-specification-of-the-encoding - These values may be or may be not padded by zeros, service supports both - options. + """PaymentChannelStateService contains methods to get the MultiPartyEscrow + payment channel state. + channel_id, channel_nonce, value and amount fields below in fact are + Solidity uint256 values. Which are big-endian integers, see + https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#formal-specification-of-the-encoding + These values may be or may be not padded by zeros, service supports both + options. """ def GetChannelState(self, request, context): - """GetChannelState method returns a channel state by channel id. + """GetChannelState method returns a channel state by channel id. """ context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') @@ -61,13 +61,13 @@ def add_PaymentChannelStateServiceServicer_to_server(servicer, server): # This class is part of an EXPERIMENTAL API. class PaymentChannelStateService(object): - """PaymentChannelStateService contains methods to get the MultiPartyEscrow - payment channel state. - channel_id, channel_nonce, value and amount fields below in fact are - Solidity uint256 values. Which are big-endian integers, see - https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#formal-specification-of-the-encoding - These values may be or may be not padded by zeros, service supports both - options. + """PaymentChannelStateService contains methods to get the MultiPartyEscrow + payment channel state. + channel_id, channel_nonce, value and amount fields below in fact are + Solidity uint256 values. Which are big-endian integers, see + https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#formal-specification-of-the-encoding + These values may be or may be not padded by zeros, service supports both + options. """ @staticmethod @@ -89,7 +89,7 @@ def GetChannelState(request, class FreeCallStateServiceStub(object): - """Used to determine free calls available for a given user. + """Used to determine free calls available for a given user. """ def __init__(self, channel): @@ -103,10 +103,15 @@ def __init__(self, channel): request_serializer=state__service__pb2.FreeCallStateRequest.SerializeToString, response_deserializer=state__service__pb2.FreeCallStateReply.FromString, ) + self.GetFreeCallToken = channel.unary_unary( + '/escrow.FreeCallStateService/GetFreeCallToken', + request_serializer=state__service__pb2.GetFreeCallTokenRequest.SerializeToString, + response_deserializer=state__service__pb2.FreeCallToken.FromString, + ) class FreeCallStateServiceServicer(object): - """Used to determine free calls available for a given user. + """Used to determine free calls available for a given user. """ def GetFreeCallsAvailable(self, request, context): @@ -115,6 +120,12 @@ def GetFreeCallsAvailable(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def GetFreeCallToken(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def add_FreeCallStateServiceServicer_to_server(servicer, server): rpc_method_handlers = { @@ -123,6 +134,11 @@ def add_FreeCallStateServiceServicer_to_server(servicer, server): request_deserializer=state__service__pb2.FreeCallStateRequest.FromString, response_serializer=state__service__pb2.FreeCallStateReply.SerializeToString, ), + 'GetFreeCallToken': grpc.unary_unary_rpc_method_handler( + servicer.GetFreeCallToken, + request_deserializer=state__service__pb2.GetFreeCallTokenRequest.FromString, + response_serializer=state__service__pb2.FreeCallToken.SerializeToString, + ), } generic_handler = grpc.method_handlers_generic_handler( 'escrow.FreeCallStateService', rpc_method_handlers) @@ -131,7 +147,7 @@ def add_FreeCallStateServiceServicer_to_server(servicer, server): # This class is part of an EXPERIMENTAL API. class FreeCallStateService(object): - """Used to determine free calls available for a given user. + """Used to determine free calls available for a given user. """ @staticmethod @@ -150,3 +166,20 @@ def GetFreeCallsAvailable(request, state__service__pb2.FreeCallStateReply.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def GetFreeCallToken(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/escrow.FreeCallStateService/GetFreeCallToken', + state__service__pb2.GetFreeCallTokenRequest.SerializeToString, + state__service__pb2.FreeCallToken.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/snet/sdk/service_client.py b/snet/sdk/service_client.py index 9294e59..ffc35b6 100644 --- a/snet/sdk/service_client.py +++ b/snet/sdk/service_client.py @@ -5,7 +5,7 @@ from pathlib import Path from typing import Any -from eth_typing import BlockNumber, ChecksumAddress +from eth_typing import BlockNumber import grpc from hexbytes import HexBytes import web3 From 8e77383802fe5946d9caee96ced7b6abedae773b Mon Sep 17 00:00:00 2001 From: Arondondon Date: Tue, 27 May 2025 19:37:19 +0300 Subject: [PATCH 02/11] Changed proto files and stubs --- .../freecall_payment_strategy.py | 11 ++-- snet/sdk/resources/proto/state_service.proto | 43 ++++++++++++---- snet/sdk/resources/proto/state_service_pb2.py | 26 +++++----- .../resources/proto/state_service_pb2_grpc.py | 50 +++++++++---------- snet/sdk/utils/utils.py | 1 - 5 files changed, 78 insertions(+), 53 deletions(-) diff --git a/snet/sdk/payment_strategies/freecall_payment_strategy.py b/snet/sdk/payment_strategies/freecall_payment_strategy.py index 5134b50..6d3aa24 100644 --- a/snet/sdk/payment_strategies/freecall_payment_strategy.py +++ b/snet/sdk/payment_strategies/freecall_payment_strategy.py @@ -13,7 +13,7 @@ class FreeCallPaymentStrategy(PaymentStrategy): def is_free_call_available(self, service_client) -> bool: try: - self._user_address = service_client.options["user_address"] + self._user_address = service_client.account.signer_address self._free_call_token, self._token_expiry_date_block = self.get_free_call_token_details(service_client) if not self._free_call_token: @@ -51,9 +51,8 @@ def is_free_call_available(self, service_client) -> bool: def get_payment_metadata(self, service_client) -> list: signature, current_block_number = self.generate_signature(service_client) metadata = [("snet-free-call-auth-token-bin", self._free_call_token), - ("snet-free-call-token-expiry-block", str(self._token_expiry_date_block)), ("snet-payment-type", "free-call"), - ("snet-free-call-user-id", self._user_address), + ("snet-free-call-user-address", self._user_address), ("snet-current-block-number", str(current_block_number)), ("snet-payment-channel-signature-bin", signature)] @@ -96,8 +95,10 @@ def get_free_call_token_details(self, service_client) -> tuple[bytes, int]: with add_to_path(str(RESOURCES_PATH.joinpath("proto"))): state_service_pb2 = importlib.import_module("state_service_pb2") - request = state_service_pb2.GetFreeCallTokenRequest() - request.address = self._user_address + request = state_service_pb2.GetFreeCallTokenRequest( + address=self._user_address, + + ) with add_to_path(str(RESOURCES_PATH.joinpath("proto"))): state_service_pb2_grpc = importlib.import_module("state_service_pb2_grpc") diff --git a/snet/sdk/resources/proto/state_service.proto b/snet/sdk/resources/proto/state_service.proto index e6d505a..0b8217a 100644 --- a/snet/sdk/resources/proto/state_service.proto +++ b/snet/sdk/resources/proto/state_service.proto @@ -69,24 +69,49 @@ service FreeCallStateService { } message GetFreeCallTokenRequest{ + // required for all calls string address = 1; + + // ("__prefix_free_trial",user_address, user_id, organization_id, service_id, group_id, current_block) + bytes signature = 2; + + uint64 current_block = 3; + + // only for calls for trusted signers + optional string user_id = 4; + + // Duration of the token's validity, measured in blocks. + // For example, if the average block time is ~12 seconds, then 100 blocks โ‰ˆ 20 minutes. + // Max value: 172800 + optional uint64 token_lifetime_in_blocks = 5; } + message FreeCallToken{ - // token_hex == token but just in another format - string token_hex = 1; - bytes token = 2; + // Token with expiration block encoded in the format: _ + // Example: [binary signature][0x5f][ascii decimal block number] + bytes token = 1; + + // Hex-encoded representation of the `token` field + string token_hex = 2; + + // token_expiration_block = currentBlock + token_lifetime_in_blocks (deadline block) uint64 token_expiration_block = 3; } message FreeCallStateRequest { - string user_address = 1; - bytes token_for_free_call = 2; - //Token expiration date in Block number - uint64 token_expiry_date_block = 3; - //Signature is made up of the below, user signs with the private key corresponding with the public key used to generate the authorized token - //free-call-metadata = ("__prefix_free_trial",user_address ,organization_id,service_id,group_id,current_block,authorized_token) + string address = 1; + + // optional, specify if you trusted signer + optional string user_id = 2; + + // Previously issued token from GetFreeCallToken + bytes free_call_token = 3; + + // Signature is made up of the below, user signs with the private key corresponding with the public key used to generate the authorized token + // ("__prefix_free_trial",user_address, user_id, organization_id, service_id, group_id, current_block, token) bytes signature = 4; + //current block number (signature will be valid only for short time around this block number) uint64 current_block = 5; } diff --git a/snet/sdk/resources/proto/state_service_pb2.py b/snet/sdk/resources/proto/state_service_pb2.py index 8d2530d..1661c44 100644 --- a/snet/sdk/resources/proto/state_service_pb2.py +++ b/snet/sdk/resources/proto/state_service_pb2.py @@ -13,7 +13,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x13state_service.proto\x12\x06\x65scrow\"S\n\x13\x43hannelStateRequest\x12\x12\n\nchannel_id\x18\x01 \x01(\x0c\x12\x11\n\tsignature\x18\x02 \x01(\x0c\x12\x15\n\rcurrent_block\x18\x03 \x01(\x04\"\xcf\x01\n\x11\x43hannelStateReply\x12\x15\n\rcurrent_nonce\x18\x01 \x01(\x0c\x12\x1d\n\x15\x63urrent_signed_amount\x18\x02 \x01(\x0c\x12\x19\n\x11\x63urrent_signature\x18\x03 \x01(\x0c\x12\x1f\n\x17old_nonce_signed_amount\x18\x04 \x01(\x0c\x12\x1b\n\x13old_nonce_signature\x18\x05 \x01(\x0c\x12\x16\n\x0eplanned_amount\x18\x06 \x01(\x04\x12\x13\n\x0bused_amount\x18\x07 \x01(\x04\"*\n\x17GetFreeCallTokenRequest\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\"Q\n\rFreeCallToken\x12\x11\n\ttoken_hex\x18\x01 \x01(\t\x12\r\n\x05token\x18\x02 \x01(\x0c\x12\x1e\n\x16token_expiration_block\x18\x03 \x01(\x04\"\x94\x01\n\x14\x46reeCallStateRequest\x12\x14\n\x0cuser_address\x18\x01 \x01(\t\x12\x1b\n\x13token_for_free_call\x18\x02 \x01(\x0c\x12\x1f\n\x17token_expiry_date_block\x18\x03 \x01(\x04\x12\x11\n\tsignature\x18\x04 \x01(\x0c\x12\x15\n\rcurrent_block\x18\x05 \x01(\x04\"2\n\x12\x46reeCallStateReply\x12\x1c\n\x14\x66ree_calls_available\x18\x01 \x01(\x04\x32i\n\x1aPaymentChannelStateService\x12K\n\x0fGetChannelState\x12\x1b.escrow.ChannelStateRequest\x1a\x19.escrow.ChannelStateReply\"\x00\x32\xb9\x01\n\x14\x46reeCallStateService\x12S\n\x15GetFreeCallsAvailable\x12\x1c.escrow.FreeCallStateRequest\x1a\x1a.escrow.FreeCallStateReply\"\x00\x12L\n\x10GetFreeCallToken\x12\x1f.escrow.GetFreeCallTokenRequest\x1a\x15.escrow.FreeCallToken\"\x00\x42,\n\x1fio.singularitynet.daemon.escrowZ\t../escrowb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x13state_service.proto\x12\x06\x65scrow\"S\n\x13\x43hannelStateRequest\x12\x12\n\nchannel_id\x18\x01 \x01(\x0c\x12\x11\n\tsignature\x18\x02 \x01(\x0c\x12\x15\n\rcurrent_block\x18\x03 \x01(\x04\"\xcf\x01\n\x11\x43hannelStateReply\x12\x15\n\rcurrent_nonce\x18\x01 \x01(\x0c\x12\x1d\n\x15\x63urrent_signed_amount\x18\x02 \x01(\x0c\x12\x19\n\x11\x63urrent_signature\x18\x03 \x01(\x0c\x12\x1f\n\x17old_nonce_signed_amount\x18\x04 \x01(\x0c\x12\x1b\n\x13old_nonce_signature\x18\x05 \x01(\x0c\x12\x16\n\x0eplanned_amount\x18\x06 \x01(\x04\x12\x13\n\x0bused_amount\x18\x07 \x01(\x04\"\xba\x01\n\x17GetFreeCallTokenRequest\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\x12\x11\n\tsignature\x18\x02 \x01(\x0c\x12\x15\n\rcurrent_block\x18\x03 \x01(\x04\x12\x14\n\x07user_id\x18\x04 \x01(\tH\x00\x88\x01\x01\x12%\n\x18token_lifetime_in_blocks\x18\x05 \x01(\x04H\x01\x88\x01\x01\x42\n\n\x08_user_idB\x1b\n\x19_token_lifetime_in_blocks\"Q\n\rFreeCallToken\x12\r\n\x05token\x18\x01 \x01(\x0c\x12\x11\n\ttoken_hex\x18\x02 \x01(\t\x12\x1e\n\x16token_expiration_block\x18\x03 \x01(\x04\"\x8c\x01\n\x14\x46reeCallStateRequest\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\x12\x14\n\x07user_id\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x17\n\x0f\x66ree_call_token\x18\x03 \x01(\x0c\x12\x11\n\tsignature\x18\x04 \x01(\x0c\x12\x15\n\rcurrent_block\x18\x05 \x01(\x04\x42\n\n\x08_user_id\"2\n\x12\x46reeCallStateReply\x12\x1c\n\x14\x66ree_calls_available\x18\x01 \x01(\x04\x32i\n\x1aPaymentChannelStateService\x12K\n\x0fGetChannelState\x12\x1b.escrow.ChannelStateRequest\x1a\x19.escrow.ChannelStateReply\"\x00\x32\xb9\x01\n\x14\x46reeCallStateService\x12S\n\x15GetFreeCallsAvailable\x12\x1c.escrow.FreeCallStateRequest\x1a\x1a.escrow.FreeCallStateReply\"\x00\x12L\n\x10GetFreeCallToken\x12\x1f.escrow.GetFreeCallTokenRequest\x1a\x15.escrow.FreeCallToken\"\x00\x42,\n\x1fio.singularitynet.daemon.escrowZ\t../escrowb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -25,16 +25,16 @@ _globals['_CHANNELSTATEREQUEST']._serialized_end=114 _globals['_CHANNELSTATEREPLY']._serialized_start=117 _globals['_CHANNELSTATEREPLY']._serialized_end=324 - _globals['_GETFREECALLTOKENREQUEST']._serialized_start=326 - _globals['_GETFREECALLTOKENREQUEST']._serialized_end=368 - _globals['_FREECALLTOKEN']._serialized_start=370 - _globals['_FREECALLTOKEN']._serialized_end=451 - _globals['_FREECALLSTATEREQUEST']._serialized_start=454 - _globals['_FREECALLSTATEREQUEST']._serialized_end=602 - _globals['_FREECALLSTATEREPLY']._serialized_start=604 - _globals['_FREECALLSTATEREPLY']._serialized_end=654 - _globals['_PAYMENTCHANNELSTATESERVICE']._serialized_start=656 - _globals['_PAYMENTCHANNELSTATESERVICE']._serialized_end=761 - _globals['_FREECALLSTATESERVICE']._serialized_start=764 - _globals['_FREECALLSTATESERVICE']._serialized_end=949 + _globals['_GETFREECALLTOKENREQUEST']._serialized_start=327 + _globals['_GETFREECALLTOKENREQUEST']._serialized_end=513 + _globals['_FREECALLTOKEN']._serialized_start=515 + _globals['_FREECALLTOKEN']._serialized_end=596 + _globals['_FREECALLSTATEREQUEST']._serialized_start=599 + _globals['_FREECALLSTATEREQUEST']._serialized_end=739 + _globals['_FREECALLSTATEREPLY']._serialized_start=741 + _globals['_FREECALLSTATEREPLY']._serialized_end=791 + _globals['_PAYMENTCHANNELSTATESERVICE']._serialized_start=793 + _globals['_PAYMENTCHANNELSTATESERVICE']._serialized_end=898 + _globals['_FREECALLSTATESERVICE']._serialized_start=901 + _globals['_FREECALLSTATESERVICE']._serialized_end=1086 # @@protoc_insertion_point(module_scope) diff --git a/snet/sdk/resources/proto/state_service_pb2_grpc.py b/snet/sdk/resources/proto/state_service_pb2_grpc.py index e29ae1d..6c47edb 100644 --- a/snet/sdk/resources/proto/state_service_pb2_grpc.py +++ b/snet/sdk/resources/proto/state_service_pb2_grpc.py @@ -6,13 +6,13 @@ class PaymentChannelStateServiceStub(object): - """PaymentChannelStateService contains methods to get the MultiPartyEscrow - payment channel state. - channel_id, channel_nonce, value and amount fields below in fact are - Solidity uint256 values. Which are big-endian integers, see - https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#formal-specification-of-the-encoding - These values may be or may be not padded by zeros, service supports both - options. + """PaymentChannelStateService contains methods to get the MultiPartyEscrow + payment channel state. + channel_id, channel_nonce, value and amount fields below in fact are + Solidity uint256 values. Which are big-endian integers, see + https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#formal-specification-of-the-encoding + These values may be or may be not padded by zeros, service supports both + options. """ def __init__(self, channel): @@ -29,17 +29,17 @@ def __init__(self, channel): class PaymentChannelStateServiceServicer(object): - """PaymentChannelStateService contains methods to get the MultiPartyEscrow - payment channel state. - channel_id, channel_nonce, value and amount fields below in fact are - Solidity uint256 values. Which are big-endian integers, see - https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#formal-specification-of-the-encoding - These values may be or may be not padded by zeros, service supports both - options. + """PaymentChannelStateService contains methods to get the MultiPartyEscrow + payment channel state. + channel_id, channel_nonce, value and amount fields below in fact are + Solidity uint256 values. Which are big-endian integers, see + https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#formal-specification-of-the-encoding + These values may be or may be not padded by zeros, service supports both + options. """ def GetChannelState(self, request, context): - """GetChannelState method returns a channel state by channel id. + """GetChannelState method returns a channel state by channel id. """ context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details('Method not implemented!') @@ -61,13 +61,13 @@ def add_PaymentChannelStateServiceServicer_to_server(servicer, server): # This class is part of an EXPERIMENTAL API. class PaymentChannelStateService(object): - """PaymentChannelStateService contains methods to get the MultiPartyEscrow - payment channel state. - channel_id, channel_nonce, value and amount fields below in fact are - Solidity uint256 values. Which are big-endian integers, see - https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#formal-specification-of-the-encoding - These values may be or may be not padded by zeros, service supports both - options. + """PaymentChannelStateService contains methods to get the MultiPartyEscrow + payment channel state. + channel_id, channel_nonce, value and amount fields below in fact are + Solidity uint256 values. Which are big-endian integers, see + https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#formal-specification-of-the-encoding + These values may be or may be not padded by zeros, service supports both + options. """ @staticmethod @@ -89,7 +89,7 @@ def GetChannelState(request, class FreeCallStateServiceStub(object): - """Used to determine free calls available for a given user. + """Used to determine free calls available for a given user. """ def __init__(self, channel): @@ -111,7 +111,7 @@ def __init__(self, channel): class FreeCallStateServiceServicer(object): - """Used to determine free calls available for a given user. + """Used to determine free calls available for a given user. """ def GetFreeCallsAvailable(self, request, context): @@ -147,7 +147,7 @@ def add_FreeCallStateServiceServicer_to_server(servicer, server): # This class is part of an EXPERIMENTAL API. class FreeCallStateService(object): - """Used to determine free calls available for a given user. + """Used to determine free calls available for a given user. """ @staticmethod diff --git a/snet/sdk/utils/utils.py b/snet/sdk/utils/utils.py index b036be8..281840e 100644 --- a/snet/sdk/utils/utils.py +++ b/snet/sdk/utils/utils.py @@ -1,5 +1,4 @@ import json -import subprocess import sys import importlib.resources from urllib.parse import urlparse From a921a83ca290c05324cf0820462c6a1fe8ccef62 Mon Sep 17 00:00:00 2001 From: Arondondon Date: Tue, 27 May 2025 19:38:03 +0300 Subject: [PATCH 03/11] Added PaymentStrategyType --- snet/sdk/__init__.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/snet/sdk/__init__.py b/snet/sdk/__init__.py index 3bdf743..0d5d89b 100644 --- a/snet/sdk/__init__.py +++ b/snet/sdk/__init__.py @@ -2,6 +2,7 @@ import os import sys import warnings +from enum import Enum import google.protobuf.internal.api_implementation @@ -26,18 +27,28 @@ from snet.sdk.client_lib_generator import ClientLibGenerator from snet.sdk.mpe.mpe_contract import MPEContract from snet.sdk.mpe.payment_channel_provider import PaymentChannelProvider -from snet.sdk.payment_strategies.default_payment_strategy import DefaultPaymentStrategy +from snet.sdk.payment_strategies.default_payment_strategy import * from snet.sdk.service_client import ServiceClient from snet.sdk.storage_provider.storage_provider import StorageProvider from snet.sdk.custom_typing import ModuleName, ServiceStub -from snet.sdk.utils.utils import (bytes32_to_str, find_file_by_keyword, - type_converter) +from snet.sdk.utils.utils import ( + bytes32_to_str, + find_file_by_keyword, + type_converter +) google.protobuf.internal.api_implementation.Type = lambda: 'python' _sym_db = _symbol_database.Default() _sym_db.RegisterMessage = lambda x: None +class PaymentStrategyType(Enum): + PAID_CALL = PaidCallPaymentStrategy + FREE_CALL = FreeCallPaymentStrategy + PREPAID_CALL = PrePaidPaymentStrategy + DEFAULT = DefaultPaymentStrategy + + class SnetSDK: """Base Snet SDK""" @@ -91,8 +102,8 @@ def __init__(self, sdk_config: Config, metadata_provider=None): def create_service_client(self, org_id: str, service_id: str, - group_name=None, - payment_strategy=None, + group_name: str=None, + payment_strategy_type: PaymentStrategyType=PaymentStrategyType.DEFAULT, address=None, options=None, concurrent_calls: int = 1): @@ -118,15 +129,13 @@ def create_service_client(self, print("Generating client library...") self.lib_generator.generate_client_library() - if payment_strategy is None: - payment_strategy = DefaultPaymentStrategy( - concurrent_calls=concurrent_calls - ) - if options is None: options = dict() options['user_address'] = address if address else "" options['concurrency'] = self._sdk_config.get("concurrency", True) + options['concurrent_calls'] = concurrent_calls + + service_metadata = self._metadata_provider.enhance_service_metadata( org_id, service_id @@ -137,7 +146,7 @@ def create_service_client(self, pb2_module = self.get_module_by_keyword(keyword="pb2.py") _service_client = ServiceClient(org_id, service_id, service_metadata, - group, service_stubs, payment_strategy, + group, service_stubs, payment_strategy_type.value(), options, self.mpe_contract, self.account, self.web3, pb2_module, self.payment_channel_provider, From fc53dc74e3021d910d71c71447cb3538f394d1ce Mon Sep 17 00:00:00 2001 From: Arondondon Date: Wed, 28 May 2025 14:01:29 +0300 Subject: [PATCH 04/11] Updated FreeCallPaymentStrategy --- snet/sdk/__init__.py | 7 +- .../default_payment_strategy.py | 13 +-- .../freecall_payment_strategy.py | 97 +++++++++---------- .../prepaid_payment_strategy.py | 10 +- snet/sdk/service_client.py | 3 + 5 files changed, 62 insertions(+), 68 deletions(-) diff --git a/snet/sdk/__init__.py b/snet/sdk/__init__.py index 0d5d89b..89a2585 100644 --- a/snet/sdk/__init__.py +++ b/snet/sdk/__init__.py @@ -103,6 +103,7 @@ def create_service_client(self, org_id: str, service_id: str, group_name: str=None, + payment_strategy: PaymentStrategy = None, payment_strategy_type: PaymentStrategyType=PaymentStrategyType.DEFAULT, address=None, options=None, @@ -135,7 +136,8 @@ def create_service_client(self, options['concurrency'] = self._sdk_config.get("concurrency", True) options['concurrent_calls'] = concurrent_calls - + if payment_strategy is None: + payment_strategy = payment_strategy_type.value() service_metadata = self._metadata_provider.enhance_service_metadata( org_id, service_id @@ -146,7 +148,8 @@ def create_service_client(self, pb2_module = self.get_module_by_keyword(keyword="pb2.py") _service_client = ServiceClient(org_id, service_id, service_metadata, - group, service_stubs, payment_strategy_type.value(), + group, service_stubs, + payment_strategy, options, self.mpe_contract, self.account, self.web3, pb2_module, self.payment_channel_provider, diff --git a/snet/sdk/payment_strategies/default_payment_strategy.py b/snet/sdk/payment_strategies/default_payment_strategy.py index c66f711..a831fe1 100644 --- a/snet/sdk/payment_strategies/default_payment_strategy.py +++ b/snet/sdk/payment_strategies/default_payment_strategy.py @@ -7,14 +7,9 @@ class DefaultPaymentStrategy(PaymentStrategy): - def __init__(self, concurrent_calls: int = 1): - self.concurrent_calls = concurrent_calls - self.concurrencyManager = ConcurrencyManager(concurrent_calls) + def __init__(self): self.channel = None - def set_concurrency_token(self, token): - self.concurrencyManager.__token = token - def set_channel(self, channel): self.channel = channel @@ -25,7 +20,8 @@ def get_payment_metadata(self, service_client): metadata = free_call_payment_strategy.get_payment_metadata(service_client) else: if service_client.get_concurrency_flag(): - payment_strategy = PrePaidPaymentStrategy(self.concurrencyManager) + concurrent_calls = service_client.get_concurrent_calls() + payment_strategy = PrePaidPaymentStrategy(concurrent_calls) metadata = payment_strategy.get_payment_metadata(service_client, self.channel) else: payment_strategy = PaidCallPaymentStrategy() @@ -37,5 +33,6 @@ def get_price(self, service_client): pass def get_concurrency_token_and_channel(self, service_client): - payment_strategy = PrePaidPaymentStrategy(self.concurrencyManager) + concurrent_calls = service_client.get_concurrent_calls() + payment_strategy = PrePaidPaymentStrategy(concurrent_calls) return payment_strategy.get_concurrency_token_and_channel(service_client) diff --git a/snet/sdk/payment_strategies/freecall_payment_strategy.py b/snet/sdk/payment_strategies/freecall_payment_strategy.py index 6d3aa24..17a829a 100644 --- a/snet/sdk/payment_strategies/freecall_payment_strategy.py +++ b/snet/sdk/payment_strategies/freecall_payment_strategy.py @@ -1,42 +1,48 @@ import importlib -from urllib.parse import urlparse import grpc -from grpc import _channel import web3 from snet.sdk.payment_strategies.payment_strategy import PaymentStrategy -from snet.sdk.resources.root_certificate import certificate from snet.sdk.utils.utils import RESOURCES_PATH, add_to_path class FreeCallPaymentStrategy(PaymentStrategy): + def __init__(self): + self._user_address = None + self._free_call_token = None + self._token_expiration_block = None + self._free_calls_available = None + def is_free_call_available(self, service_client) -> bool: - try: + if not self._user_address: self._user_address = service_client.account.signer_address - self._free_call_token, self._token_expiry_date_block = self.get_free_call_token_details(service_client) - if not self._free_call_token: - return False + current_block_number = service_client.get_current_block_number() - with add_to_path(str(RESOURCES_PATH.joinpath("proto"))): - state_service_pb2 = importlib.import_module("state_service_pb2") + if (not self._free_call_token or + not self._token_expiration_block or + current_block_number > self._token_expiration_block): + self._free_call_token, self._token_expiration_block = self.get_free_call_token_details(service_client) - with add_to_path(str(RESOURCES_PATH.joinpath("proto"))): - state_service_pb2_grpc = importlib.import_module("state_service_pb2_grpc") + with add_to_path(str(RESOURCES_PATH.joinpath("proto"))): + state_service_pb2 = importlib.import_module("state_service_pb2") - signature, current_block_number = self.generate_signature(service_client) + with add_to_path(str(RESOURCES_PATH.joinpath("proto"))): + state_service_pb2_grpc = importlib.import_module("state_service_pb2_grpc") - request = state_service_pb2.FreeCallStateRequest() - request.user_address = self._user_address - request.token_for_free_call = self._free_call_token - request.token_expiry_date_block = self._token_expiry_date_block - request.signature = signature - request.current_block = current_block_number + signature = self.generate_signature(service_client, current_block_number) + request = state_service_pb2.FreeCallStateRequest( + address=self._user_address, + free_call_token=self._free_call_token, + signature=signature, + current_block=current_block_number + ) - channel = self.select_channel(service_client) + channel = service_client._get_grpc_channel() + stub = state_service_pb2_grpc.FreeCallStateServiceStub(channel) - stub = state_service_pb2_grpc.FreeCallStateServiceStub(channel) + try: response = stub.GetFreeCallsAvailable(request) if response.free_calls_available > 0: return True @@ -45,10 +51,10 @@ def is_free_call_available(self, service_client) -> bool: if self._user_address: print(f"Warning: {e.details()}") return False - except Exception as e: - return False def get_payment_metadata(self, service_client) -> list: + if self.is_free_call_available(service_client): + raise Exception(f"Free calls limit for address {self._user_address} has expired. Please use another payment strategy") signature, current_block_number = self.generate_signature(service_client) metadata = [("snet-free-call-auth-token-bin", self._free_call_token), ("snet-payment-type", "free-call"), @@ -58,52 +64,39 @@ def get_payment_metadata(self, service_client) -> list: return metadata - def select_channel(self, service_client) -> _channel.Channel: - _, _, _, daemon_endpoint = service_client.get_service_details() - 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(root_certificates=certificate)) - else: - raise ValueError('Unsupported scheme in service metadata ("{}")'.format(endpoint_object.scheme)) - return channel - - def generate_signature(self, service_client) -> tuple[bytes, int]: + def generate_signature(self, service_client, current_block_number=None, with_token=True) -> tuple[bytes, int]: + if not current_block_number: + current_block_number = service_client.get_current_block_number() org_id, service_id, group_id, _ = service_client.get_service_details() - if self._token_expiry_date_block == 0 or len(self._user_address) == 0 or len(self._free_call_token) == 0: - raise Exception( - "You are using default 'FreeCallPaymentStrategy' to use this strategy you need to pass " - "'free_call_auth_token-bin','user_address','free-call-token-expiry-block' in config") + message_types = ["string", "string", "string", "string", "string", "uint256", "bytes32"] + message_values = ["__prefix_free_trial", self._user_address, org_id, service_id, group_id, + current_block_number, self._free_call_token] - current_block_number = service_client.get_current_block_number() + if not with_token: + message_types = message_types[:-1] + message_values = message_values[:-1] - message = web3.Web3.solidity_keccak( - ["string", "string", "string", "string", "string", "uint256", "bytes32"], - ["__prefix_free_trial", self._user_address, org_id, service_id, group_id, current_block_number, - self._free_call_token] - ) + message = web3.Web3.solidity_keccak(message_types, message_values) return service_client.generate_signature(message), current_block_number - def get_free_call_token_details(self, service_client) -> tuple[bytes, int]: + def get_free_call_token_details(self, service_client, current_block_number=None) -> tuple[bytes, int]: + + signature, current_block_number = self.generate_signature(service_client, current_block_number, with_token=False) + with add_to_path(str(RESOURCES_PATH.joinpath("proto"))): state_service_pb2 = importlib.import_module("state_service_pb2") request = state_service_pb2.GetFreeCallTokenRequest( address=self._user_address, - + signature=signature, + current_block=current_block_number ) with add_to_path(str(RESOURCES_PATH.joinpath("proto"))): state_service_pb2_grpc = importlib.import_module("state_service_pb2_grpc") - channel = self.select_channel(service_client) + channel = service_client._get_grpc_channel() stub = state_service_pb2_grpc.FreeCallStateServiceStub(channel) response = stub.GetFreeCallToken(request) diff --git a/snet/sdk/payment_strategies/prepaid_payment_strategy.py b/snet/sdk/payment_strategies/prepaid_payment_strategy.py index 3c6f016..98666a0 100644 --- a/snet/sdk/payment_strategies/prepaid_payment_strategy.py +++ b/snet/sdk/payment_strategies/prepaid_payment_strategy.py @@ -4,18 +4,16 @@ class PrePaidPaymentStrategy(PaymentStrategy): - def __init__(self, concurrency_manager: ConcurrencyManager, - block_offset: int = 240, call_allowance: int = 1): - self.concurrency_manager = concurrency_manager + def __init__(self, concurrent_calls: int, block_offset: int = 240, call_allowance: int = 1): + self.concurrency_manager = ConcurrencyManager(concurrent_calls) self.block_offset = block_offset self.call_allowance = call_allowance def get_price(self, service_client): return service_client.get_price() * self.concurrency_manager.concurrent_calls - def get_payment_metadata(self, service_client, channel): - if channel is None: - channel = self.select_channel(service_client) + def get_payment_metadata(self, service_client): + channel = self.select_channel(service_client) token = self.concurrency_manager.get_token(service_client, channel, self.get_price(service_client)) metadata = [ ("snet-payment-type", "prepaid-call"), diff --git a/snet/sdk/service_client.py b/snet/sdk/service_client.py index ffc35b6..ab8dac8 100644 --- a/snet/sdk/service_client.py +++ b/snet/sdk/service_client.py @@ -240,6 +240,9 @@ def get_concurrency_flag(self) -> bool: def get_concurrency_token_and_channel(self) -> tuple[str, PaymentChannel]: return self.payment_strategy.get_concurrency_token_and_channel(self) + def get_concurrent_calls(self): + return self.options.get('concurrent_calls', 1) + def set_concurrency_token_and_channel(self, token: str, channel: PaymentChannel) -> None: self.payment_strategy.concurrency_token = token From 0aa8ce83946783012233e73afdf685b3a509dfe8 Mon Sep 17 00:00:00 2001 From: Arondondon Date: Wed, 28 May 2025 16:20:30 +0300 Subject: [PATCH 05/11] Updated free call logic, signatures and user interface --- snet/sdk/concurrency_manager.py | 13 ++++++++----- .../default_payment_strategy.py | 5 ++--- .../freecall_payment_strategy.py | 17 +++++++---------- .../prepaid_payment_strategy.py | 5 ++++- snet/sdk/service_client.py | 13 +++++++++++-- 5 files changed, 32 insertions(+), 21 deletions(-) diff --git a/snet/sdk/concurrency_manager.py b/snet/sdk/concurrency_manager.py index 5d86495..7dfb651 100644 --- a/snet/sdk/concurrency_manager.py +++ b/snet/sdk/concurrency_manager.py @@ -3,12 +3,11 @@ import grpc import web3 -from snet.sdk.service_client import ServiceClient from snet.sdk.utils.utils import RESOURCES_PATH, add_to_path class ConcurrencyManager: - def __init__(self, concurrent_calls: int): + def __init__(self, concurrent_calls: int=1): self.__concurrent_calls: int = concurrent_calls self.__token: str = '' self.__planned_amount: int = 0 @@ -18,6 +17,10 @@ def __init__(self, concurrent_calls: int): def concurrent_calls(self) -> int: return self.__concurrent_calls + @concurrent_calls.setter + def concurrent_calls(self, concurrent_calls: int): + self.__concurrent_calls = concurrent_calls + def get_token(self, service_client, channel, service_call_price): if len(self.__token) == 0: self.__token = self.__get_token(service_client, channel, service_call_price) @@ -25,7 +28,7 @@ def get_token(self, service_client, channel, service_call_price): self.__token = self.__get_token(service_client, channel, service_call_price, new_token=True) return self.__token - def __get_token(self, service_client: ServiceClient, channel, service_call_price, new_token=False): + def __get_token(self, service_client, channel, service_call_price, new_token=False): if not new_token: amount = channel.state["last_signed_amount"] if amount != 0: @@ -47,13 +50,13 @@ def __get_token(self, service_client: ServiceClient, channel, service_call_price self.__planned_amount = token_reply.planned_amount return token_reply.token - def __get_stub_for_get_token(self, service_client: ServiceClient): + def __get_stub_for_get_token(self, service_client): grpc_channel = service_client.get_grpc_base_channel() with add_to_path(str(RESOURCES_PATH.joinpath("proto"))): token_service_pb2_grpc = importlib.import_module("token_service_pb2_grpc") return token_service_pb2_grpc.TokenServiceStub(grpc_channel) - def __get_token_for_amount(self, service_client: ServiceClient, channel, amount): + def __get_token_for_amount(self, service_client, channel, amount): nonce = channel.state["nonce"] stub = self.__get_stub_for_get_token(service_client) with add_to_path(str(RESOURCES_PATH.joinpath("proto"))): diff --git a/snet/sdk/payment_strategies/default_payment_strategy.py b/snet/sdk/payment_strategies/default_payment_strategy.py index a831fe1..4b189b1 100644 --- a/snet/sdk/payment_strategies/default_payment_strategy.py +++ b/snet/sdk/payment_strategies/default_payment_strategy.py @@ -1,4 +1,3 @@ -from snet.sdk.concurrency_manager import ConcurrencyManager from snet.sdk.payment_strategies.freecall_payment_strategy import FreeCallPaymentStrategy from snet.sdk.payment_strategies.paidcall_payment_strategy import PaidCallPaymentStrategy from snet.sdk.payment_strategies.prepaid_payment_strategy import PrePaidPaymentStrategy @@ -16,13 +15,13 @@ def set_channel(self, channel): def get_payment_metadata(self, service_client): free_call_payment_strategy = FreeCallPaymentStrategy() - if free_call_payment_strategy.is_free_call_available(service_client): + if free_call_payment_strategy.get_free_calls_available(service_client) > 0: metadata = free_call_payment_strategy.get_payment_metadata(service_client) else: if service_client.get_concurrency_flag(): concurrent_calls = service_client.get_concurrent_calls() payment_strategy = PrePaidPaymentStrategy(concurrent_calls) - metadata = payment_strategy.get_payment_metadata(service_client, self.channel) + metadata = payment_strategy.get_payment_metadata(service_client) else: payment_strategy = PaidCallPaymentStrategy() metadata = payment_strategy.get_payment_metadata(service_client) diff --git a/snet/sdk/payment_strategies/freecall_payment_strategy.py b/snet/sdk/payment_strategies/freecall_payment_strategy.py index 17a829a..099a4d7 100644 --- a/snet/sdk/payment_strategies/freecall_payment_strategy.py +++ b/snet/sdk/payment_strategies/freecall_payment_strategy.py @@ -12,9 +12,8 @@ def __init__(self): self._user_address = None self._free_call_token = None self._token_expiration_block = None - self._free_calls_available = None - def is_free_call_available(self, service_client) -> bool: + def get_free_calls_available(self, service_client) -> int: if not self._user_address: self._user_address = service_client.account.signer_address @@ -31,7 +30,7 @@ def is_free_call_available(self, service_client) -> bool: with add_to_path(str(RESOURCES_PATH.joinpath("proto"))): state_service_pb2_grpc = importlib.import_module("state_service_pb2_grpc") - signature = self.generate_signature(service_client, current_block_number) + signature, _ = self.generate_signature(service_client, current_block_number) request = state_service_pb2.FreeCallStateRequest( address=self._user_address, free_call_token=self._free_call_token, @@ -39,21 +38,19 @@ def is_free_call_available(self, service_client) -> bool: current_block=current_block_number ) - channel = service_client._get_grpc_channel() + channel = service_client.get_grpc_base_channel() stub = state_service_pb2_grpc.FreeCallStateServiceStub(channel) try: response = stub.GetFreeCallsAvailable(request) - if response.free_calls_available > 0: - return True - return False + return response.free_calls_available except grpc.RpcError as e: if self._user_address: print(f"Warning: {e.details()}") - return False + return 0 def get_payment_metadata(self, service_client) -> list: - if self.is_free_call_available(service_client): + if self.get_free_calls_available(service_client) <= 0: raise Exception(f"Free calls limit for address {self._user_address} has expired. Please use another payment strategy") signature, current_block_number = self.generate_signature(service_client) metadata = [("snet-free-call-auth-token-bin", self._free_call_token), @@ -96,7 +93,7 @@ def get_free_call_token_details(self, service_client, current_block_number=None) with add_to_path(str(RESOURCES_PATH.joinpath("proto"))): state_service_pb2_grpc = importlib.import_module("state_service_pb2_grpc") - channel = service_client._get_grpc_channel() + channel = service_client.get_grpc_base_channel() stub = state_service_pb2_grpc.FreeCallStateServiceStub(channel) response = stub.GetFreeCallToken(request) diff --git a/snet/sdk/payment_strategies/prepaid_payment_strategy.py b/snet/sdk/payment_strategies/prepaid_payment_strategy.py index 98666a0..4e41539 100644 --- a/snet/sdk/payment_strategies/prepaid_payment_strategy.py +++ b/snet/sdk/payment_strategies/prepaid_payment_strategy.py @@ -4,7 +4,7 @@ class PrePaidPaymentStrategy(PaymentStrategy): - def __init__(self, concurrent_calls: int, block_offset: int = 240, call_allowance: int = 1): + def __init__(self, concurrent_calls: int=1, block_offset: int = 240, call_allowance: int = 1): self.concurrency_manager = ConcurrencyManager(concurrent_calls) self.block_offset = block_offset self.call_allowance = call_allowance @@ -12,6 +12,9 @@ def __init__(self, concurrent_calls: int, block_offset: int = 240, call_allowanc def get_price(self, service_client): return service_client.get_price() * self.concurrency_manager.concurrent_calls + def set_concurrent_calls(self, concurrent_calls): + self.concurrency_manager.concurrent_calls = concurrent_calls + def get_payment_metadata(self, service_client): channel = self.select_channel(service_client) token = self.concurrency_manager.get_token(service_client, channel, self.get_price(service_client)) diff --git a/snet/sdk/service_client.py b/snet/sdk/service_client.py index ab8dac8..bf840d3 100644 --- a/snet/sdk/service_client.py +++ b/snet/sdk/service_client.py @@ -12,12 +12,12 @@ from eth_account.messages import defunct_hash_message from rfc3986 import urlparse -from snet.sdk import generic_client_interceptor +from snet.sdk import generic_client_interceptor, FreeCallPaymentStrategy from snet.sdk.account import Account from snet.sdk.mpe.mpe_contract import MPEContract from snet.sdk.mpe.payment_channel import PaymentChannel from snet.sdk.mpe.payment_channel_provider import PaymentChannelProvider -from snet.sdk.payment_strategies import default_payment_strategy as strategy +from snet.sdk.payment_strategies.prepaid_payment_strategy import PrePaidPaymentStrategy from snet.sdk.resources.root_certificate import certificate from snet.sdk.storage_provider.service_metadata import MPEServiceMetadata from snet.sdk.custom_typing import ModuleName, ServiceStub @@ -51,6 +51,8 @@ def __init__( self.service_metadata = service_metadata self.group = group self.payment_strategy = payment_strategy + if isinstance(payment_strategy, PrePaidPaymentStrategy): + self.payment_strategy.set_concurrent_calls(options["concurrent_calls"]) self.options = options self.mpe_address = mpe_contract.contract.address self.account = account @@ -322,3 +324,10 @@ def get_services_and_messages_info_as_pretty_string(self) -> str: for field_type, field_name in fields: string_output += f" Field: {field_type} {field_name}\n" return string_output + + def get_free_calls_available(self) -> int: + payment_strategy = self.payment_strategy + if not isinstance(payment_strategy, FreeCallPaymentStrategy): + payment_strategy = FreeCallPaymentStrategy() + + return payment_strategy.get_free_calls_available(self) From 75c23950ca65a2cc4d3bed31d673740aa556cfde Mon Sep 17 00:00:00 2001 From: Arondondon Date: Wed, 28 May 2025 18:11:40 +0300 Subject: [PATCH 06/11] Fixed test_call_to_service test --- testcases/functional_tests/test_sdk_client.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testcases/functional_tests/test_sdk_client.py b/testcases/functional_tests/test_sdk_client.py index f83f59b..c94c3b5 100644 --- a/testcases/functional_tests/test_sdk_client.py +++ b/testcases/functional_tests/test_sdk_client.py @@ -2,12 +2,12 @@ import os from snet import sdk +from snet.sdk import PaymentStrategyType class TestSDKClient(unittest.TestCase): def setUp(self): self.service_client = get_test_service_data() - channel = self.service_client.deposit_and_open_channel(123456, 33333) def test_call_to_service(self): result = self.service_client.call_rpc("mul", "Numbers", a=20, b=3) @@ -17,13 +17,13 @@ def test_call_to_service(self): def get_test_service_data(): config = sdk.config.Config(private_key=os.environ['SNET_TEST_WALLET_PRIVATE_KEY'], eth_rpc_endpoint=f"https://sepolia.infura.io/v3/{os.environ['SNET_TEST_INFURA_KEY']}", - concurrency=False, - force_update=False) + concurrency=False) snet_sdk = sdk.SnetSDK(config) service_client = snet_sdk.create_service_client(org_id="26072b8b6a0e448180f8c0e702ab6d2f", - service_id="Exampleservice", group_name="default_group") + service_id="Exampleservice", group_name="default_group", + payment_strategy_type=PaymentStrategyType.PAID_CALL) return service_client From 8afdcf7358b572f083036239d98057047a87f472 Mon Sep 17 00:00:00 2001 From: Arondondon Date: Fri, 30 May 2025 11:52:57 +0300 Subject: [PATCH 07/11] Updated requirements list --- requirements.txt | 31 ++++++++++--------------------- snet/sdk/utils/utils.py | 1 - 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/requirements.txt b/requirements.txt index 4d81ede..f696c19 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,22 +1,11 @@ -protobuf==4.21.6 -grpcio-tools==1.59.0 -wheel==0.41.2 -jsonrpcclient==4.0.3 -eth-hash==0.5.2 -rlp==3.0.0 -eth-rlp==0.3.0 -web3==6.11.1 -mnemonic==0.20 -pycoin==0.92.20230326 -pyyaml==6.0.1 -ipfshttpclient==0.4.13.2 -rfc3986==2.0.0 -pymultihash==0.8.2 -base58==2.1.1 -argcomplete==3.1.2 -grpcio-health-checking==1.59.0 -jsonschema==4.0.0 -eth-account==0.9.0 +protobuf>=6.30.0 +grpcio-tools>=1.71.0 +wheel>=0.45.0 +rlp>=4.1.0 +web3>=6.11.0 +ipfshttpclient>=0.7.0 +rfc3986>=2.0.0 +base58>=2.1.1 +grpcio-health-checking>=1.71.0 snet-contracts==1.0.0 -lighthouseweb3==0.1.4 -zipp>=3.19.1 +lighthouseweb3>=0.1.4 diff --git a/snet/sdk/utils/utils.py b/snet/sdk/utils/utils.py index b036be8..281840e 100644 --- a/snet/sdk/utils/utils.py +++ b/snet/sdk/utils/utils.py @@ -1,5 +1,4 @@ import json -import subprocess import sys import importlib.resources from urllib.parse import urlparse From f262b983e89e3e09df57ea6ce237a9e68e8d9115 Mon Sep 17 00:00:00 2001 From: Arondondon Date: Fri, 30 May 2025 13:12:56 +0300 Subject: [PATCH 08/11] Resolved dependencies conflicts --- requirements.txt | 6 +++--- version.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index f696c19..e0d7f13 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,11 @@ -protobuf>=6.30.0 +protobuf==5.* grpcio-tools>=1.71.0 wheel>=0.45.0 rlp>=4.1.0 -web3>=6.11.0 +web3==7.* ipfshttpclient>=0.7.0 rfc3986>=2.0.0 base58>=2.1.1 grpcio-health-checking>=1.71.0 -snet-contracts==1.0.0 +snet-contracts==1.0.1 lighthouseweb3>=0.1.4 diff --git a/version.py b/version.py index ce1305b..76ad18b 100644 --- a/version.py +++ b/version.py @@ -1 +1 @@ -__version__ = "4.0.0" +__version__ = "4.0.1" From 42d00f540c5b64d14479372c0fbab747abc91bb6 Mon Sep 17 00:00:00 2001 From: Arondondon Date: Wed, 4 Jun 2025 13:43:47 +0300 Subject: [PATCH 09/11] fixed bugs with web3 and dependencies --- requirements.txt | 3 ++- snet/sdk/account.py | 2 +- snet/sdk/mpe/payment_channel.py | 2 +- snet/sdk/mpe/payment_channel_provider.py | 5 +++-- snet/sdk/service_client.py | 4 ++-- tests/unit_tests/test_service_client.py | 4 ++-- 6 files changed, 11 insertions(+), 9 deletions(-) diff --git a/requirements.txt b/requirements.txt index e0d7f13..91fc6b0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,9 +3,10 @@ grpcio-tools>=1.71.0 wheel>=0.45.0 rlp>=4.1.0 web3==7.* -ipfshttpclient>=0.7.0 +ipfshttpclient==0.4.13 rfc3986>=2.0.0 base58>=2.1.1 grpcio-health-checking>=1.71.0 snet-contracts==1.0.1 lighthouseweb3>=0.1.4 +pymultihash==0.* \ No newline at end of file diff --git a/snet/sdk/account.py b/snet/sdk/account.py index fa88ceb..b88a96f 100644 --- a/snet/sdk/account.py +++ b/snet/sdk/account.py @@ -84,7 +84,7 @@ def _send_signed_transaction(self, contract_fn, *args): signed_txn = self.web3.eth.account.sign_transaction( transaction, private_key=self.private_key) return self.web3.to_hex( - self.web3.eth.send_raw_transaction(signed_txn.rawTransaction) + self.web3.eth.send_raw_transaction(signed_txn.raw_transaction) ) def send_transaction(self, contract_fn, *args): diff --git a/snet/sdk/mpe/payment_channel.py b/snet/sdk/mpe/payment_channel.py index ead629e..0af915a 100644 --- a/snet/sdk/mpe/payment_channel.py +++ b/snet/sdk/mpe/payment_channel.py @@ -52,7 +52,7 @@ def _get_current_channel_state(self): self.channel_id,current_block_number ] ) - signature = self.web3.eth.account.signHash(defunct_hash_message(message), self.account.signer_private_key).signature + signature = self.web3.eth.account._sign_hash(defunct_hash_message(message), self.account.signer_private_key).signature with add_to_path(str(RESOURCES_PATH.joinpath("proto"))): state_service_pb2 = importlib.import_module("state_service_pb2") request = state_service_pb2.ChannelStateRequest( diff --git a/snet/sdk/mpe/payment_channel_provider.py b/snet/sdk/mpe/payment_channel_provider.py index 1948abd..1cefcbe 100644 --- a/snet/sdk/mpe/payment_channel_provider.py +++ b/snet/sdk/mpe/payment_channel_provider.py @@ -18,9 +18,10 @@ def __init__(self, w3, mpe_contract): self.mpe_contract = mpe_contract self.event_topics = [self.web3.keccak( - text="ChannelOpen(uint256,uint256,address,address,address,bytes32,uint256,uint256)").hex()] + text="ChannelOpen(uint256,uint256,address,address,address,bytes32,uint256,uint256)")] self.deployment_block = get_contract_deployment_block(self.web3, "MultiPartyEscrow") self.mpe_address = mpe_contract.contract.address + print(self.mpe_address) self.channels_file = CHANNELS_DIR.joinpath(str(self.mpe_address), "channels.pickle") def update_cache(self): @@ -78,7 +79,7 @@ def _get_all_channels_from_blockchain_logs_to_dicts(self, starting_block_number, "topics": self.event_topics}) from_block = to_block + 1 - event_abi = self.mpe_contract.contract._find_matching_event_abi(event_name="ChannelOpen") + event_abi = self.mpe_contract.contract.events.ChannelOpen._get_event_abi() event_data_list = [get_event_data(codec, event_abi, l)["args"] for l in logs] channels_opened = list(map(self._event_data_args_to_dict, event_data_list)) diff --git a/snet/sdk/service_client.py b/snet/sdk/service_client.py index bf840d3..a223fab 100644 --- a/snet/sdk/service_client.py +++ b/snet/sdk/service_client.py @@ -199,7 +199,7 @@ def get_price(self) -> int: return self.group["pricing"][0]["price_in_cogs"] def generate_signature(self, message: bytes) -> bytes: - return bytes(self.sdk_web3.eth.account.signHash( + return bytes(self.sdk_web3.eth.account._sign_hash( defunct_hash_message(message), self.account.signer_private_key ).signature) @@ -210,7 +210,7 @@ def generate_training_signature(self, text: str, address: str, ["string", "address", "uint256"], [text, address, block_number] ) - return self.sdk_web3.eth.account.signHash( + return self.sdk_web3.eth.account._sign_hash( defunct_hash_message(message), self.account.signer_private_key ).signature diff --git a/tests/unit_tests/test_service_client.py b/tests/unit_tests/test_service_client.py index d04ba37..93e2383 100644 --- a/tests/unit_tests/test_service_client.py +++ b/tests/unit_tests/test_service_client.py @@ -140,7 +140,7 @@ def test_get_current_block_number(self): def test_generate_signature(self): message = b"test_message" mock_signature = MagicMock() - self.client.sdk_web3.eth.account.signHash = MagicMock( + self.client.sdk_web3.eth.account._sign_hash = MagicMock( return_value=MagicMock(signature=mock_signature) ) result = self.client.generate_signature(message) @@ -153,7 +153,7 @@ def test_generate_training_signature(self, mock_solidity_keccak): block_number = "test_block_number" mock_solidity_keccak.return_value = b"test_message" mock_signature = MagicMock() - self.client.sdk_web3.eth.account.signHash = MagicMock( + self.client.sdk_web3.eth.account._sign_hash = MagicMock( return_value=MagicMock(signature=mock_signature) ) result = self.client.generate_training_signature(text, address, From ab89deda69993ac116209e7addf540bec8095434 Mon Sep 17 00:00:00 2001 From: Arondondon Date: Fri, 6 Jun 2025 16:21:31 +0300 Subject: [PATCH 10/11] Updated docs, removed unnecessary methods and parameters --- README.md | 50 ++++++++++++++++--- docs/main/init.md | 24 ++++++++- .../freecall_payment_strategy.md | 29 ++++------- snet/sdk/__init__.py | 2 - snet/sdk/service_client.py | 5 -- 5 files changed, 75 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index a3dd405..a90cfba 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +from snet.sdk import PaymentStrategyType # snet-sdk-python @@ -147,23 +148,53 @@ visit the [Payment](#payment) section. ## Payment -### Free call +When creating a service client, you can select a payment strategy using the `payment_strategy_type` parameter: -If you want to use the free calls you will need to pass this argument to the `create_service_client()` method: +```python +from snet.sdk import PaymentStrategyType -```python -address = "YOUR_ETHEREUM_ADDRESS" +payment_strategy_type = PaymentStrategyType. ``` +These are four payment strategies: + +- `PaymentStrategyType.DEFAULT` +- `PaymentStrategyType.FREE_CALL` +- `PaymentStrategyType.PAID_CALL` +- `PaymentStrategyType.PREPAID_CALL` + +The default payment strategy selects one of the other three each time the service is called, depending on the +availability of free calls, as well as the presence of parameters required for concurrent calls. While choosing +a specific payment strategy will not allow you to switch to another. This is especially convenient when you want +to use free calls without accidentally spending money. + +> Note: If you don't specify ะตั€ัƒ `payment_strategy_type` parameter, the default payment strategy will be used. + +### Free call + +If you want to use the free calls you will need to choose `PaymentStrategyType.FREE_CALL` as the payment strategy type. Creating a service client with free calls included would look like this: + ```python service_client = snet_sdk.create_service_client(org_id="26072b8b6a0e448180f8c0e702ab6d2f", service_id="Exampleservice", - address="0xtest") + payment_strategy_type = PaymentStrategyType.FREE_CALL) ``` ### Paid call +If you want to use regular paid calls you will need to choose `PaymentStrategyType.PAID_CALL` as the payment strategy type. +Creating a service client with paid calls would look like this: + +```python +service_client = snet_sdk.create_service_client(org_id="26072b8b6a0e448180f8c0e702ab6d2f", + service_id="Exampleservice", + payment_strategy_type = PaymentStrategyType.PAID_CALL) +``` + +There is no need to call functions for interacting with payment channels, because they are automatically +managed by the SDK. But anyway you can use them if you want. + #### Open channel with the specified amount of funds and expiration `open_channel()`[[1]](#1-this-method-uses-a-call-to-a-paid-smart-contract-function) opens a payment channel with the specified amount of AGIX tokens in cogs and expiration time. @@ -203,15 +234,20 @@ payment_channel.extend_and_add_funds(amount=123456, expiration=33333) ### Concurrent (Prepaid) call -Concurrent (prepaid) calls allow you to prepay for a batch of service calls in advance. This off-chain strategy is ideal for scenarios requiring high throughput and low latency. Unlike regular paid calls, the payment is done once upfront, and the SDK automatically manages the channel during usage. +Concurrent (prepaid) calls allow you to prepay for a batch of service calls in advance. This off-chain strategy +is ideal for scenarios requiring high throughput and low latency. Unlike regular paid calls, the payment is done +once upfront, and the SDK automatically manages the channel during usage. -To use concurrent prepaid calls, specify the `concurrent_calls` parameter when creating a service client: +If you want to use prepaid calls you will need to choose `PaymentStrategyType.PREPAID_CALL` as the payment strategy type +as well as pass the number of concurrent calls as the `concurrent_calls` parameter. Creating a service client with +prepaid calls would look like this: ```python service_client = snet_sdk.create_service_client( org_id="26072b8b6a0e448180f8c0e702ab6d2f", service_id="Exampleservice", group_name="default_group", + payment_strategy_type=PaymentStrategyType.PREPAID_CALL, concurrent_calls=5 # Number of prepaid calls to allocate ) ``` diff --git a/docs/main/init.md b/docs/main/init.md index 1436cc5..223b956 100644 --- a/docs/main/init.md +++ b/docs/main/init.md @@ -3,7 +3,8 @@ [Link](https://github.com/singnet/snet-sdk-python/blob/master/snet/sdk/__init__.py) to GitHub Entities: -1. [SnetSDK](#class-snetsdk) +1. [PaymentStrategyType](#class-paymentstrategytype) +2. [SnetSDK](#class-snetsdk) - [\_\_init\_\_](#__init__) - [create_service_client](#create_service_client) - [get_service_stub](#get_service_stub) @@ -15,6 +16,24 @@ Entities: - [get_organization_list](#get_organization_list) - [get_services_list](#get_services_list) +### Class `PaymentStrategyType` + +extends: `Enum` + +is extended by: - + +#### description + +This is an `enum` that represents the available payment strategies. It is used to determine which payment +strategy to use when initializing the SDK. + +#### members + +- `DEFAULT` +- `FREE_CALL` +- `PAID_CALL` +- `PREPAID_CALL` + ### Class `SnetSDK` extends: - @@ -73,7 +92,8 @@ of the `ServiceClient` class with all the required parameters, which is then ret - `service_id` (str): The ID of the service. - `group_name` (str): The name of the payment group. Defaults to _None_. - `payment_strategy` (PaymentStrategy): The payment channel management strategy. Defaults to _None_. -- `address` (str): Wallet address to use free calls. Defaults to _None_. +- `payment_strategy_type` (PaymentStrategyType): The type of payment management strategy. +Defaults to `PaymentStrategyType.DEFAULT`. - `options` (dict): Additional options for the service client. Defaults to _None_. - `concurrent_calls` (int): The number of concurrent calls allowed. Defaults to 1. diff --git a/docs/payment_strategies/freecall_payment_strategy.md b/docs/payment_strategies/freecall_payment_strategy.md index e6f5934..7bb48e5 100644 --- a/docs/payment_strategies/freecall_payment_strategy.md +++ b/docs/payment_strategies/freecall_payment_strategy.md @@ -4,11 +4,10 @@ Entities: 1. [FreeCallPaymentStrategy](#class-freecallpaymentstrategy) - - [is_free_call_available](#is_free_call_available) + - [get_free_calls_available](#get_free_calls_available) - [get_payment_metadata](#get_payment_metadata) - [generate_signature](#generate_signature) - [get_free_call_token_details](#get_free_call_token_details) - - [select_channel](#select_channel) ### Class `FreeCallPaymentStrategy` @@ -24,9 +23,10 @@ call services. #### methods -#### `is_free_call_available` +#### `get_free_calls_available` -Checks if a free call is available for a given service client. +Using grpc calls to the daemon, it gets a free call token, and also gets and returns the number of free calls +available. ###### args: @@ -34,13 +34,13 @@ Checks if a free call is available for a given service client. ###### returns: -- True if a free call is available, False otherwise. (bool) +- Amount of free calls available. (int) ###### raises: - Exception: If an error occurs while checking the free call availability. -_Note_: If any exception occurs during the process, it returns False. +_Note_: If an error occurs specifically during the grpc call to `GetFreeCallsAvailable`, 0 will be returned. #### `get_payment_metadata` @@ -62,6 +62,8 @@ Generates a signature for the given service client using the provided free call ###### args: - `service_client` (ServiceClient): The service client instance. +- `current_block_number` (int, optional): The current block number. Defaults to _None_. +- `with_token` (bool, optional): Whether to include the free call token in the signature. Defaults to _True_. ###### returns: @@ -73,11 +75,12 @@ Generates a signature for the given service client using the provided free call #### `get_free_call_token_details` -Sends a request to the daemon and receives a free call token. +Sends a request to the daemon and receives a free call token and its details. ###### args: - `service_client` (ServiceClient): The service client instance. +- `current_block_number` (int, optional): The current block number. Defaults to _None_. ###### returns: @@ -86,15 +89,3 @@ Sends a request to the daemon and receives a free call token. ###### raises: - Exception: If an error occurred while receiving the token. - -#### `select_channel` - -Creates a channel to the daemon. - -###### args: - -- `service_client` (ServiceClient): The service client object. - -###### returns: - -- The channel for the service calling. \ No newline at end of file diff --git a/snet/sdk/__init__.py b/snet/sdk/__init__.py index 89a2585..d35a77e 100644 --- a/snet/sdk/__init__.py +++ b/snet/sdk/__init__.py @@ -105,7 +105,6 @@ def create_service_client(self, group_name: str=None, payment_strategy: PaymentStrategy = None, payment_strategy_type: PaymentStrategyType=PaymentStrategyType.DEFAULT, - address=None, options=None, concurrent_calls: int = 1): @@ -132,7 +131,6 @@ def create_service_client(self, if options is None: options = dict() - options['user_address'] = address if address else "" options['concurrency'] = self._sdk_config.get("concurrency", True) options['concurrent_calls'] = concurrent_calls diff --git a/snet/sdk/service_client.py b/snet/sdk/service_client.py index a223fab..9e367b6 100644 --- a/snet/sdk/service_client.py +++ b/snet/sdk/service_client.py @@ -214,11 +214,6 @@ def generate_training_signature(self, text: str, address: str, defunct_hash_message(message), self.account.signer_private_key ).signature - def get_free_call_config(self) -> tuple[str, str, int]: - return (self.options['email'], - self.options['free_call_auth_token-bin'], - self.options['free-call-token-expiry-block']) - def get_service_details(self) -> tuple[str, str, str, str]: return (self.org_id, self.service_id, From 7db27612ee92cee33af77772d268e4944fa3f58a Mon Sep 17 00:00:00 2001 From: Arondondon Date: Wed, 18 Jun 2025 17:42:42 +0300 Subject: [PATCH 11/11] Fixed readme --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index a90cfba..34902de 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ -from snet.sdk import PaymentStrategyType # snet-sdk-python