From 54b7f25360bf2791f0a964b5ce4df44e7e5c403d Mon Sep 17 00:00:00 2001 From: Dmytro Striletskyi Date: Sun, 17 Feb 2019 17:31:35 +0200 Subject: [PATCH] Add checking faucet balance before sending transactions --- README.md | 8 ++--- gimmeremmetokensbot/app.py | 17 +++++++--- gimmeremmetokensbot/constants.py | 7 ++-- gimmeremmetokensbot/remme/token.py | 53 +++++++++++++++++------------- 4 files changed, 52 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 13d7c3b..4fd4202 100644 --- a/README.md +++ b/README.md @@ -58,11 +58,9 @@ Required environment variables: 3. `MASTER_ACCOUNT_PRIVATE_KEY` - account's private key to send testing token from. 4. `STABLE_REMME_TOKENS_REQUEST_AMOUNT` - amount of the Remme tokens to send from master account. 5. `NODE_HOST` - node, Telegram bot should make requests, host (`i.e. node-genesis-testnet.remme.io`). -6. `NODE_PUBLIC_KEY` - node, Telegram bot should make requests, public key. -7. `STORAGE_PUBLIC_KEY` - storage, Telegram bot should make requests, public key. -8. `PRODUCTION_HOST` - if you run Telegram bot on production, set host (i.e. `https://intense-harbor-47746.herokuapp.com`) -9. `DATABASE_URL` - production database DSN URL to store information about users. -10. `REQUEST_TOKENS_PERIOD_IN_HOURS_LIMIT` - request tokens period in hours limit. +6. `PRODUCTION_HOST` - if you run Telegram bot on production, set host (i.e. `https://intense-harbor-47746.herokuapp.com`) +7. `DATABASE_URL` - production database DSN URL to store information about users. +8. `REQUEST_TOKENS_PERIOD_IN_HOURS_LIMIT` - request tokens period in hours limit. To get node and storage public keys, visit [RPC API](https://remmeio.atlassian.net/wiki/spaces/WikiREMME/pages/292814862/RPC+API+specification) of node. diff --git a/gimmeremmetokensbot/app.py b/gimmeremmetokensbot/app.py index e8c2e7d..cf393f9 100644 --- a/gimmeremmetokensbot/app.py +++ b/gimmeremmetokensbot/app.py @@ -12,6 +12,7 @@ from constants import ( ALREADY_GOTTEN_ACCOUNT_CREDENTIALS_PHRASE, CHECK_MY_BALANCE_KEYBOARD_BUTTON, + FAUCET_IS_EMPTY_PHRASE, REQUEST_TOKENS_KEYBOARD_BUTTON, SOMETHING_WENT_WRONG_PHRASE, START_COMMAND_BOT_GREETING_PHRASE, @@ -27,6 +28,7 @@ update_request_tokens_datetime, ) from remme.account import RemmeAccount +from remme.constants.amount import STABLE_REMME_TOKENS_REQUEST_AMOUNT from remme.token import RemmeToken from utils import send_keystore_file @@ -51,7 +53,7 @@ def render_keyboard(message): bot.send_message(message.from_user.id, 'Choose one of the the following keyboard buttons:', reply_markup=keyboard) -def is_request_tokens_possible(public_key, request_tokens_datetime): +def is_request_tokens_possible(message, public_key): """ Check if last user's tokens request is fit to set period. @@ -64,6 +66,8 @@ def is_request_tokens_possible(public_key, request_tokens_datetime): 4. If user's tokens request time in seconds is not more that periodic limit time in seconds, return False. 5. Else, return True. """ + request_tokens_datetime = get_request_tokens_datetime(message.chat.id) + if request_tokens_datetime is not None: request_tokens_period_in_seconds_limit = REQUEST_TOKENS_PERIOD_IN_HOURS_LIMIT * 60 * 60 @@ -89,16 +93,21 @@ def handle_gimme_tokens_button(message): try: public_key = get_public_key(chat_id=message.chat.id) - request_tokens_datetime = get_request_tokens_datetime(message.chat.id) + master_account = RemmeAccount(private_key_hex=MASTER_ACCOUNT_PRIVATE_KEY) + master_account_token = RemmeToken(private_key_hex=MASTER_ACCOUNT_PRIVATE_KEY) + + if master_account_token.get_balance(address=master_account.address) < STABLE_REMME_TOKENS_REQUEST_AMOUNT: + bot.send_message(message.chat.id, FAUCET_IS_EMPTY_PHRASE) + return - if not is_request_tokens_possible(public_key=public_key, request_tokens_datetime=request_tokens_datetime): + if not is_request_tokens_possible(message=message, public_key=public_key): bot.send_message( message.chat.id, f'You are able to request tokens only once per {REQUEST_TOKENS_PERIOD_IN_HOURS_LIMIT} hours.', ) return - batch_id = RemmeToken(private_key_hex=MASTER_ACCOUNT_PRIVATE_KEY).send_transaction(public_key_to=public_key) + batch_id = master_account_token.send_transaction(public_key_to=public_key) update_request_tokens_datetime(chat_id=message.chat.id) bot.send_message( diff --git a/gimmeremmetokensbot/constants.py b/gimmeremmetokensbot/constants.py index 1ae040b..4c22c66 100644 --- a/gimmeremmetokensbot/constants.py +++ b/gimmeremmetokensbot/constants.py @@ -13,7 +13,7 @@ START_COMMAND_BOT_TESTNET_INTERACTIONS_PHRASE = """ \n[Blockexplorer](https://blockexplorer.remme.io) can be useful to check sent transactions, including yours! -Broaden horizon - [use libraries written in several programming languages](https://docs.remme.io/) to work with blockchain in many different ways! +Broaden horizon — [use libraries written in several programming languages](https://docs.remme.io/) to work with blockchain in many different ways! If you are more a user than a developer, take a look at our [certificate-based authentication](https://webauth-testnet.remme.io/how-to-use) called *WebAuth*. It allows you to log in without the password. Use keystore file generated especially for you. @@ -25,4 +25,7 @@ 'You already got the credentials. Find it at the start of this dialog.' SOMETHING_WENT_WRONG_PHRASE = \ - 'Something went wrong! Please, contact administrator — @SergYelagin.' + 'Something went wrong! Please, contact administrator — @dmytrostriletskyi.' + +FAUCET_IS_EMPTY_PHRASE = \ + 'Faucet is empty. Please, contact administrator to top up its tokens — @dmytrostriletskyi.' diff --git a/gimmeremmetokensbot/remme/token.py b/gimmeremmetokensbot/remme/token.py index e333c43..5a1d6e0 100644 --- a/gimmeremmetokensbot/remme/token.py +++ b/gimmeremmetokensbot/remme/token.py @@ -18,9 +18,31 @@ from sawtooth_sdk.protobuf.transaction_pb2 import TransactionHeader, Transaction +class RequestToNode: + + @staticmethod + def send(method, params=None): + if params is None: + params = {} + + parameters = { + 'jsonrpc': '2.0', + 'id': '11', + 'method': method, + 'params': params, + } + + return requests.post( + 'https://' + os.environ.get('NODE_HOST'), + data=json.dumps(parameters), + verify=False + ) + + class TransactionService: def __init__(self, private_key_hex): + self.request_to_node = RequestToNode() self._remme_account = RemmeAccount(private_key_hex) def create(self, family_name, family_version, inputs, outputs, payload_bytes): @@ -43,10 +65,8 @@ def create(self, family_name, family_version, inputs, outputs, payload_bytes): :param payload_bytes: {bytes} :return: {Couroutine} """ - config = { - "node_public_key": os.environ.get('NODE_PUBLIC_KEY'), - "storage_public_key": os.environ.get('STORAGE_PUBLIC_KEY') - } + node_config = self.request_to_node.send(method='get_node_config') + node_public_key = node_config.json().get('result').get('node_public_key') txn_header_bytes = TransactionHeader( family_name=family_name, @@ -54,17 +74,20 @@ def create(self, family_name, family_version, inputs, outputs, payload_bytes): inputs=inputs + [self._remme_account.address], outputs=outputs + [self._remme_account.address], signer_public_key=self._remme_account.public_key_hex, - batcher_public_key=config.get('node_public_key'), + batcher_public_key=node_public_key, nonce=create_nonce(), dependencies=[], payload_sha512=sha512_hexdigest(payload_bytes) ).SerializeToString() + signature = self._remme_account.sign(txn_header_bytes) + txn = Transaction( header=txn_header_bytes, header_signature=signature, payload=payload_bytes ).SerializeToString() + return b64encode(txn).decode('utf-8') @@ -73,23 +96,9 @@ class RemmeToken: _family_version = "0.1" def __init__(self, private_key_hex=None): + self.request_to_node = RequestToNode() self.transaction_service = TransactionService(private_key_hex=private_key_hex) - @staticmethod - def _send_request(method, params): - parameters = { - 'jsonrpc': '2.0', - 'id': '11', - 'method': method, - 'params': params, - } - - return requests.post( - 'https://' + os.environ.get('NODE_HOST'), - data=json.dumps(parameters), - verify=False - ) - @staticmethod def _validate_public_key(key): if len(key) != 66: @@ -98,7 +107,7 @@ def _validate_public_key(key): def get_balance(self, address): - balance_info = self._send_request('get_balance', { + balance_info = self.request_to_node.send(method='get_balance', params={ 'public_key_address': address, }) @@ -108,7 +117,7 @@ def send_transaction(self, public_key_to, amount=STABLE_REMME_TOKENS_REQUEST_AMO """ Send raw transaction to Remme node. """ - sent_transaction_info = self._send_request('send_raw_transaction', { + sent_transaction_info = self.request_to_node.send(method='send_raw_transaction', params={ 'data': self._create_transaction(public_key_to, amount), })