Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement getting node configurations #25

Merged
merged 13 commits into from
Apr 25, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* [Configuration file](#configuration-file)
* [Service](#service)
* [Account](#account)
* [Node](#node)
* [Development](#development)
* [Requirements](#development-requirements)
* [Docker](#docker)
Expand Down Expand Up @@ -45,7 +46,8 @@ $ pip3 install remme-core-cli
You can use the following list of the addresses of the nodes to execute commands to:

- `node-genesis-testnet.remme.io`,
- `node-6-testnet.remme.io`.
- `node-6-testnet.remme.io`,
- `node-1-testnet.remme.io`.

### Configuration file

Expand Down Expand Up @@ -112,6 +114,42 @@ $ remme account get-balance \
368440.0
```

Transfer tokens to address — ``remme account transfer-tokens``:

| Arguments | Type | Required | Description |
| :--------------: | :-----: | :-------: | ---------------------------------------------- |
| private-key-from | String | Yes | Account's private key to transfer tokens from. |
| address-to | String | Yes | Account address to transfer tokens to. |
| amount | Integer | Yes | Amount to transfer. |
| node-url | String | No | Node URL to apply a command to. |

```bash
$ remme account transfer-tokens \
--private-key-from=1067b42e24b4c533706f7c6e62278773c8ec7bf9e78bf570e9feb58ba8274acc \
--address-to=112007d71fa7e120c60fb392a64fd69de891a60c667d9ea9e5d9d9d617263be6c20202 \
--amount=1000 \
--node-url=node-genesis-testnet.remme.io
{
"batch_id": "37809770b004dcbc7dae116fd9f17428255ddddee3304c9b3d14609d2792e78f08f5308af03fd4aa18ff1d868f043b12dd7b0a792e141f000a2505acd4b7a956"
}
```

### Node

Get node configurations — ``remme node get-configurations``:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Get node configurations — ``remme node get-configurations``:
Get node configurations — ``remme node get-configs``:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


| Arguments | Type | Required | Description |
| :-------: | :----: | :-------: | ------------------------------------ |
| node-url | String | No | Node URL to apply a command to. |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
| node-url | String | No | Node URL to apply a command to. |
| node-url | String | No | Node URL to apply a command to. |

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


```bash
$ remme node get-configurations --node-url=node-genesis-testnet.remme.io
{
"node_address": "1168296ecf036e857f42129b58303bcf1e03723764a1702cbe98529802aad8514ee3cf",
"node_public_key": "03738df3f4ac3621ba8e89413d3ff4ad036c3a0a4dbb164b695885aab6aab614ad"
}
```

## Development

<h3 id="development-requirements">Requirements</h4>
Expand Down
54 changes: 50 additions & 4 deletions cli/account/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,16 @@
import click
from remme import Remme

from cli.account.forms import GetAccountBalanceForm
from cli.account.help import GET_ACCOUNT_BALANCE_ADDRESS_ARGUMENT_HELP_MESSAGE
from cli.account.forms import (
GetAccountBalanceForm,
TransferTokensForm,
)
from cli.account.help import (
ADDRESS_TO_ARGUMENT_HELP_MESSAGE,
AMOUNT_ARGUMENT_HELP_MESSAGE,
GET_ACCOUNT_BALANCE_ADDRESS_ARGUMENT_HELP_MESSAGE,
PRIVATE_KEY_FROM_ARGUMENT_HELP_MESSAGE,
)
from cli.account.service import Account
from cli.constants import (
FAILED_EXIT_FROM_COMMAND_CODE,
Expand Down Expand Up @@ -44,7 +52,7 @@ def get_balance(address, node_url):
})

if errors:
print_errors(errors)
print_errors(errors=errors)
sys.exit(FAILED_EXIT_FROM_COMMAND_CODE)

address = arguments.get('address')
Expand All @@ -57,4 +65,42 @@ def get_balance(address, node_url):
account_service = Account(service=remme)
balance = loop.run_until_complete(account_service.get_balance(address=address))

print_result(balance)
print_result(result=balance)


@click.option('--private-key-from', type=str, required=True, help=PRIVATE_KEY_FROM_ARGUMENT_HELP_MESSAGE)
@click.option('--address-to', type=str, required=True, help=ADDRESS_TO_ARGUMENT_HELP_MESSAGE)
@click.option('--amount', type=int, required=True, help=AMOUNT_ARGUMENT_HELP_MESSAGE)
@click.option('--node-url', type=str, required=False, help=NODE_URL_ARGUMENT_HELP_MESSAGE, default=default_node_url())
@account_commands.command('transfer-tokens')
def transfer_tokens(private_key_from, address_to, amount, node_url):
"""
Transfer tokens to address.
"""
arguments, errors = TransferTokensForm().load({
'private_key_from': private_key_from,
'address_to': address_to,
'amount': amount,
'node_url': node_url,
})

if errors:
print_errors(errors=errors)
sys.exit(FAILED_EXIT_FROM_COMMAND_CODE)

private_key_from = arguments.get('private_key_from')
address_to = arguments.get('address_to')
amount = arguments.get('amount')
node_url = arguments.get('node_url')

remme = Remme(private_key_hex=private_key_from, network_config={
'node_address': str(node_url) + ':8080',
})

result, errors = Account(service=remme).transfer_tokens(address_to=address_to, amount=amount)

if errors is not None:
print_errors(errors=errors)
sys.exit(FAILED_EXIT_FROM_COMMAND_CODE)

print_result(result=result)
36 changes: 36 additions & 0 deletions cli/account/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
ADDRESS_REGEXP,
DOMAIN_NAME_REGEXP,
)
from cli.generic.forms.fields import PrivateKeyField


class GetAccountBalanceForm(Schema):
Expand Down Expand Up @@ -47,3 +48,38 @@ def validate_node_url(self, node_url):

if re.match(pattern=DOMAIN_NAME_REGEXP, string=node_url) is None:
raise ValidationError(f'The following node URL `{node_url}` is invalid.')


class TransferTokensForm(Schema):
"""
Transfer tokens to address form.
"""

private_key_from = PrivateKeyField(required=True)
address_to = fields.String(required=True)
amount = fields.Integer(required=True)
node_url = fields.String(allow_none=True, required=False)

@validates('address_to')
def validate_address(self, address):
"""
Validate account address.
"""
if re.match(pattern=ADDRESS_REGEXP, string=address) is None:
raise ValidationError(f'The following address `{address}` is invalid.')

@validates('node_url')
def validate_node_url(self, node_url):
"""
Validate node URL.

If node URL is localhost, it means client didn't passed any URL, so nothing to validate.
"""
if node_url == 'localhost':
return

if 'http' in node_url or 'https' in node_url:
raise ValidationError(f'Pass the following node URL `{node_url}` without protocol (http, https, etc.).')

if re.match(pattern=DOMAIN_NAME_REGEXP, string=node_url) is None:
raise ValidationError(f'The following node URL `{node_url}` is invalid.')
3 changes: 3 additions & 0 deletions cli/account/help.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
"""
Provide help messages for command line interface's account commands.
"""
ADDRESS_TO_ARGUMENT_HELP_MESSAGE = 'Account address to transfer tokens to.'
AMOUNT_ARGUMENT_HELP_MESSAGE = 'Amount to transfer.'
GET_ACCOUNT_BALANCE_ADDRESS_ARGUMENT_HELP_MESSAGE = 'Account address to get a balance by.'
PRIVATE_KEY_FROM_ARGUMENT_HELP_MESSAGE = 'Account\'s private key to transfer tokens from.'
6 changes: 6 additions & 0 deletions cli/account/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@ async def get_balance(self, address):
Get balance of the account by its address.
"""
pass

def transfer_tokens(self, address_to, amount):
"""
Transfer tokens to address.
"""
pass
16 changes: 16 additions & 0 deletions cli/account/service.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
"""
Provide implementation of the account.
"""
import asyncio

from accessify import implements

from cli.account.interfaces import AccountInterface

loop = asyncio.get_event_loop()


@implements(AccountInterface)
class Account:
Expand All @@ -26,3 +30,15 @@ async def get_balance(self, address):
Get balance of the account by its address.
"""
return await self.service.token.get_balance(address=address)

def transfer_tokens(self, address_to, amount):
"""
Transfer tokens to address.
"""
transaction_response = loop.run_until_complete(
self.service.token.transfer(address_to=address_to, amount=amount),
)

return {
'batch_id': transaction_response.batch_id,
}, None
5 changes: 5 additions & 0 deletions cli/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@
Provide constants for command line interface.
"""
ADDRESS_REGEXP = r'^[0-9a-f]{70}$'
BATCH_ID_REGEXP = r'^[0-9a-f]{128}$'
PRIVATE_KEY_REGEXP = r'^[a-f0-9]{64}$'
DOMAIN_NAME_REGEXP = r'(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]'

PASSED_EXIT_FROM_COMMAND_CODE = 0
FAILED_EXIT_FROM_COMMAND_CODE = -1
INCORRECT_ENTERED_COMMAND_CODE = 2

NODE_URL_ARGUMENT_HELP_MESSAGE = 'Apply the command to the specified node by its URL.'

CLI_CONFIG_FILE_NAME = 'remme-core-cli'

NODE_IP_ADDRESS_FOR_TESTING = '159.89.104.9'
LATEST_RELEASE_NODE_IP_ADDRESS_FOR_TESTING = '165.22.75.163'
PRIVATE_KEY_FOR_TESTING = 'b03e31d2f310305eab249133b53b5fb3270090fc1692c9b022b81c6b9bb6029b'
2 changes: 2 additions & 0 deletions cli/entrypoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import click

from cli.account.cli import account_commands
from cli.node.cli import node_commands


@click.group()
Expand All @@ -17,3 +18,4 @@ def cli():


cli.add_command(account_commands)
cli.add_command(node_commands)
Empty file added cli/generic/__init__.py
Empty file.
Empty file added cli/generic/forms/__init__.py
Empty file.
31 changes: 31 additions & 0 deletions cli/generic/forms/fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""
Provide generic form fields.
"""
import re

from marshmallow import (
ValidationError,
fields,
)

from cli.constants import PRIVATE_KEY_REGEXP


class PrivateKeyField(fields.Field):
"""
Implements validation of the private key.

References:
- https://marshmallow.readthedocs.io/en/3.0/custom_fields.html
"""

def _deserialize(self, value, attr, data, **kwargs):
"""
Validate data (private key) that was passed to field.
"""
private_key = value

if re.match(pattern=PRIVATE_KEY_REGEXP, string=private_key) is None:
raise ValidationError(f'The following private key `{private_key}` is invalid.')

return value
Empty file added cli/node/__init__.py
Empty file.
59 changes: 59 additions & 0 deletions cli/node/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""
Provide implementation of the command line interface's node commands.
"""
import asyncio
import sys

import click
from remme import Remme

from cli.constants import (
FAILED_EXIT_FROM_COMMAND_CODE,
NODE_URL_ARGUMENT_HELP_MESSAGE,
)
from cli.node.forms import GetNodeConfigurationsForm
from cli.node.service import Node
from cli.utils import (
default_node_url,
print_errors,
print_result,
)

loop = asyncio.get_event_loop()


@click.group('node', chain=True)
def node_commands():
"""
Provide commands for working with node.
"""
pass


@click.option('--node-url', type=str, required=False, help=NODE_URL_ARGUMENT_HELP_MESSAGE, default=default_node_url())
@node_commands.command('get-configurations')
def transfer_tokens(node_url):
"""
Get node configurations.
"""
arguments, errors = GetNodeConfigurationsForm().load({
'node_url': node_url,
})

if errors:
print_errors(errors=errors)
sys.exit(FAILED_EXIT_FROM_COMMAND_CODE)

node_url = arguments.get('node_url')

remme = Remme(network_config={
'node_address': str(node_url) + ':8080',
})

result, errors = Node(service=remme).get_configurations()

if errors is not None:
print_errors(errors=errors)
sys.exit(FAILED_EXIT_FROM_COMMAND_CODE)

print_result(result=result)
37 changes: 37 additions & 0 deletions cli/node/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""
Provide forms for command line interface's node commands.
"""
import re

from marshmallow import (
Schema,
ValidationError,
fields,
validates,
)

from cli.constants import DOMAIN_NAME_REGEXP


class GetNodeConfigurationsForm(Schema):
"""
Get node configurations.
"""

node_url = fields.String(allow_none=True, required=False)

@validates('node_url')
def validate_node_url(self, node_url):
"""
Validate node URL.

If node URL is localhost, it means client didn't passed any URL, so nothing to validate.
"""
if node_url == 'localhost':
return

if 'http' in node_url or 'https' in node_url:
raise ValidationError(f'Pass the following node URL `{node_url}` without protocol (http, https, etc.).')

if re.match(pattern=DOMAIN_NAME_REGEXP, string=node_url) is None:
raise ValidationError(f'The following node URL `{node_url}` is invalid.')
Loading