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

Lykke #1

Open
wants to merge 33 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
a9cdd05
continuation error in get balances
Apr 19, 2018
5ce9c6e
add balance table
Apr 19, 2018
667a4bf
Signed-off-by: Stefan Schiessl <stefan.schiessl@blockchainprojectsbv.…
Apr 19, 2018
6769a44
refactored address_from_operation handling
Apr 20, 2018
7888a19
fixed chain:identifier regex bug
Apr 20, 2018
0c87bc9
fxied tests for balance / memo change
Apr 20, 2018
ab2d622
incident_id written on broadcast (reply no longer possible)
Apr 24, 2018
d7e854d
hardened blockchain monitor and introduced smart retry
Apr 25, 2018
e02c237
bugfixes in intialization of monitor
Apr 25, 2018
1dd5a12
bug in hash function (machine specific deviations)
Apr 26, 2018
eadd847
update readme
Apr 29, 2018
c553b96
stop_block none error
Apr 29, 2018
22927da
all unexpected exceptions trigger retry of monitor
Apr 29, 2018
7815b4d
switched hashing function, also ensured utf-8 encoding
Apr 30, 2018
7430e84
added more logs
May 2, 2018
dbaa092
update status table usage
May 9, 2018
04f08bb
update for concurrent handling in azure
May 17, 2018
5fda8b4
add httphandler
May 23, 2018
40bfa5c
add blockchain time in the past issueIndicator
Jun 18, 2018
1cd6f4a
fix int overflow bug in jsonschema
Jun 21, 2018
3ab76fd
add logs.file to deactivate file logging
Jul 20, 2018
da4d66f
log_format misplaced
Jul 20, 2018
c164a07
emit of httphandler is now non-blocking
Jul 23, 2018
b7246d5
Merge branch 'master' of github.com:blockchainbv/bexi
Jul 23, 2018
f77ef1a
add log blocking config
Jul 23, 2018
c992bee
add log blocking config
Jul 23, 2018
f12d24b
user input might contain invalid cahracters (tabs)
Jul 23, 2018
6cbf2aa
Merge branch 'develop'
Jul 23, 2018
90f506f
continuation must be string
Jul 24, 2018
a37fadf
add OperationStorageBadRequestException to skip operation insertion
Jul 24, 2018
67f1a11
Merge branch 'develop'
Jul 24, 2018
4fb962f
be precise in exceptiona nd include num_retries as default
Jul 24, 2018
723dd05
Merge branch 'develop'
Jul 24, 2018
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
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
include *.md
include *.txt
include bexi/*.yaml
include VERSION
12 changes: 3 additions & 9 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,25 +70,19 @@ The active key is what you normally see as the private key, since it allows
to move funds, whereas the memo key only allows reading the memo message of
transfers.

Then initiate the blockchain monitor

.. code-block:: bash

$ python3 cli.py blockchain_monitor

Start the blockchain monitor service (isalive wsgi response for blockchain monitor)
Start the blockchain monitor service (isalive wsgi response with the actual monitor as coroutine, needs chain connection)

.. code-block:: bash

$ python3 cli.py blockchain_monitor_service

Start the sign service
Start the sign service (create wallets, sign transactions, offline)

.. code-block:: bash

$ python3 cli.py sign_service

and the manage service
and the manage service (buil and broadcast transactions, manage balances, history, ..., needs chain connection)

.. code-block:: bash

Expand Down
1 change: 1 addition & 0 deletions VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.1
92 changes: 75 additions & 17 deletions bexi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import os
import yaml
import logging
from logging.handlers import TimedRotatingFileHandler
from logging.handlers import TimedRotatingFileHandler, HTTPHandler
from copy import deepcopy
import io
import urllib.request
import collections
import json
import threading


__VERSION__ = "0.1"
def get_version():
with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", 'VERSION')) as version_file:
return version_file.read().strip()


__VERSION__ = get_version()


class Config(dict):
Expand Down Expand Up @@ -179,34 +185,86 @@ def _nested_update(d, u):
return d


class LykkeHttpHandler(HTTPHandler):

def mapLogRecord(self, record):
from .wsgi import flask_setup

record_dict = record.__dict__
record_dict["appName"] = Config.get("wsgi", "name")
record_dict["appVersion"] = __VERSION__
record_dict["envInfo"] = flask_setup.get_env_info()
record_dict["logLevel"] = record_dict["levelname"]
record_dict["component"] = record_dict["name"]
record_dict["process"] = record_dict["processName"]
record_dict["context"] = None

if record_dict.get("exc_info", None) is not None:
record_dict["callStack"] = record_dict["exc_text"]
record_dict["exceptionType"] = record_dict["exc_info"][0].__name__

return record_dict

def update_blocking(self):
self.blocking = Config.get("logs", "http", "blocking", True)

def emit(self, record):
if self.blocking:
super(LykkeHttpHandler, self).emit(record)
else:
def super_emit():
super(LykkeHttpHandler, self).emit(record)

thread = threading.Thread(target=super_emit)
thread.daemon = True
thread.start()


def set_global_logger(existing_loggers=None):
use_handlers = []

# setup logging
# ... log to file system
log_folder = os.path.join(Config.get("dump_folder", default="dump"), "logs")
log_level = logging.getLevelName(Config.get("logs", "level", default="INFO"))

os.makedirs(log_folder, exist_ok=True)
log_format = ('%(asctime)s %(levelname) -10s: %(message)s')
trfh = TimedRotatingFileHandler(
os.path.join(log_folder, "bexi.log"),
"midnight",
1
)
trfh.suffix = "%Y-%m-%d"
trfh.setFormatter(logging.Formatter(log_format))
trfh.setLevel(log_level)

if Config.get("logs", "file", True):
# ... log to file system
log_folder = os.path.join(Config.get("dump_folder", default="dump"), "logs")
os.makedirs(log_folder, exist_ok=True)
trfh = TimedRotatingFileHandler(
os.path.join(log_folder, "bexi.log"),
"midnight",
1
)
trfh.suffix = "%Y-%m-%d"
trfh.setFormatter(logging.Formatter(log_format))
trfh.setLevel(log_level)

use_handlers.append(trfh)

# ... and to console
sh = logging.StreamHandler()
sh.setFormatter(logging.Formatter(log_format))
sh.setLevel(log_level)

use_handlers.append(sh)

if not Config.get("logs", "http", {}) == {}:
# ... and http logger
lhh = LykkeHttpHandler(
host=Config.get("logs", "http", "host"),
url=Config.get("logs", "http", "url"),
method="POST",
secure=Config.get("logs", "http", "secure")
)
lhh.setLevel(log_level)
lhh.update_blocking()
use_handlers.append(lhh)

# global config (e.g. for werkzeug)
logging.basicConfig(level=log_level,
format=log_format,
handlers=[trfh, sh])

use_handlers = [trfh, sh]
handlers=use_handlers)

if existing_loggers is not None:
if not type(existing_loggers) == list:
Expand Down
134 changes: 107 additions & 27 deletions bexi/addresses.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def split_unique_address(address):
return always


def get_from_address_from_operation(operation):
def get_from_address(operation):
""" returns the from address of the given operation.
if the from address is the exchange account,
the address contains the customer_id, otherwise empty string
Expand All @@ -34,13 +34,14 @@ def get_from_address_from_operation(operation):
:param operation: operation formatted for operation storage
:type operation: dict
"""
if utils.is_exchange_account(operation["from"]):
return get_address_from_operation(operation)
if is_internal(operation["from"], operation["to"]):
# internal transfer
return create_unique_address(operation["from"], operation["customer_id"])
else:
return ensure_account_name(operation["from"]) + DELIMITER + ""
return create_unique_address(operation["from"], "")


def get_to_address_from_operation(operation):
def get_to_address(operation):
""" returns the to address of the given operation.
if the to address is the exchange account,
the address contains the customer_id, otherwise empty string
Expand All @@ -49,29 +50,81 @@ def get_to_address_from_operation(operation):
:param operation: operation formatted for operation storage
:type operation: dict
"""
if utils.is_exchange_account(operation["to"]) and not utils.is_exchange_account(operation["from"]):
return get_address_from_operation(operation)
if not is_internal(operation["from"], operation["to"]):
# no internal transfer
return create_unique_address(operation["to"], operation["customer_id"])
else:
return ensure_account_name(operation["to"]) + DELIMITER + ""
return create_unique_address(operation["to"], "")


def get_address_from_operation(operation):
""" assumes that the operation is either from or to an exchange account.
the address of this operation is then returned as
<exchange_account_id>DELIMITER<customer_id>
def get_tracking_address(operation):
"""
Get the tracking address of this operation, either from or to an exchange account.
Decision depends on internal transfer, deposit, withdraw operation

:param operation: operation formatted for operation storage
:type operation: dict

:returns address as defined in `func`:create_unique_address
"""
if utils.is_exchange_account(operation["from"]) and utils.is_exchange_account(operation["to"]):
return ensure_account_name(operation["from"]) + DELIMITER + operation["customer_id"]
elif utils.is_exchange_account(operation["from"]):
return ensure_account_name(operation["from"]) + DELIMITER + operation["customer_id"]
elif utils.is_exchange_account(operation["to"]):
return ensure_account_name(operation["to"]) + DELIMITER + operation["customer_id"]
if is_internal(operation["from"], operation["to"]):
# internal transfer
return create_unique_address(operation["from"], operation["customer_id"])
elif is_withdraw(operation["from"], operation["to"]):
# withdraw
return create_unique_address(operation["to"], operation["customer_id"])
elif is_deposit(operation["from"], operation["to"]):
# deposit
return create_unique_address(operation["to"], operation["customer_id"])
raise Exception("No operaton concerning this exchange")


def decide_tracking_address(from_address, to_address):
"""
Given two addresses it decides which is the tracking address for the underlying operation.
Creates and splits the address to use common functionality buried in both methods.
Decision depends on internal transfer, deposit, withdraw operation

:param from_address: from address
:type from_address: str or split address
:param to_address: to address
:type to_address: str or split address3

:returns split address as defined in `func`:split_unique_address
"""
if type(from_address) == str:
from_address = split_unique_address(from_address)
if type(to_address) == str:
to_address = split_unique_address(to_address)
if is_internal(from_address, to_address):
# internal transfer
return split_unique_address(create_unique_address(from_address["account_id"], from_address["customer_id"]))
elif is_withdraw(from_address, to_address):
# withdraw
return split_unique_address(create_unique_address(to_address["account_id"], to_address["customer_id"]))
elif is_deposit(from_address, to_address):
# deposit
return split_unique_address(create_unique_address(to_address["account_id"], to_address["customer_id"]))
raise Exception("No operaton concerning this exchange")


def ensure_address_format(address):
"""
Ensures that the address has the correct format name:uuid

:param address: address to be checked
:type address: address

:returns properly formatted address
"""
if type(address) == str:
if not address.startswith("1.2."):
return address
address = split_unique_address(address)
assert type(address) == dict
return create_unique_address(address["account_id"], address["customer_id"])


@requires_blockchain
def _account_name_to_id(account_name, bitshares_instance):
return Account(account_name, bitshares_instance=bitshares_instance)["id"]
Expand Down Expand Up @@ -115,27 +168,54 @@ def create_unique_address(account_id_or_name, randomizer=uuid.uuid4):
"""
account_id_or_name = ensure_account_name(account_id_or_name)
if type(randomizer) == str:
return account_id_or_name + DELIMITER + randomizer
if randomizer == "":
return account_id_or_name
else:
return account_id_or_name + DELIMITER + randomizer
return account_id_or_name + DELIMITER + str(randomizer())


def create_memo(address, incident_id):
""" Create plain text memo for an address/incident pair.
The memo will contain <customer_id>DELIMITER<incident_id>,
this is done to have full transparency on the blockchain
def is_withdraw(from_address, to_address):
if type(from_address) == dict:
from_address = from_address["account_id"]
if type(to_address) == dict:
to_address = to_address["account_id"]
return utils.is_exchange_account(from_address) and not utils.is_exchange_account(to_address)


def is_deposit(from_address, to_address):
if type(from_address) == dict:
from_address = from_address["account_id"]
if type(to_address) == dict:
to_address = to_address["account_id"]
return not utils.is_exchange_account(from_address) and utils.is_exchange_account(to_address)


def is_internal(from_address, to_address):
if type(from_address) == dict:
from_address = from_address["account_id"]
if type(to_address) == dict:
to_address = to_address["account_id"]
return utils.is_exchange_account(from_address) and utils.is_exchange_account(to_address)


def create_memo(from_address, to_address, incident_id):
""" Create plain text memo for a transfer as defined by arguments.
Depending on the case (internal transfer, deposit, withdraw operation),
the memo will contain [<customer_id>[DELIMITER<incident_id>]].

:param address: address in the format <account_id>DELIMITER<customer_id>
:type address: str
:param incident_id: unique incident id
:type incident_id: str
"""
address = split_unique_address(address)
address = decide_tracking_address(from_address, to_address)

memo = ""

if address["customer_id"]:
memo = memo + address["customer_id"]
if incident_id:
if incident_id and not is_withdraw(from_address, to_address):
if memo != "":
memo = memo + DELIMITER + incident_id
else:
Expand All @@ -154,9 +234,9 @@ def split_memo(memo):
raise ValueError()

splitted = memo.split(DELIMITER)
always = {"customer_id": splitted[0]}
always = {"customer_id": splitted[0].strip()}
if len(splitted) == 2:
always["incident_id"] = splitted[1]
always["incident_id"] = splitted[1].strip()
else:
always["incident_id"] = None
return always
Loading