diff --git a/Dockerfile b/Dockerfile index 50438ad815a0560502618fd6dc30adeb9ebaa018..7b49a021dc948be39e2146174b2b306ddaf1b84f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,13 @@ -FROM python:3.5 -RUN apt-get update && apt-get install -y --no-install-recommends apt-utils -RUN apt-get update && apt-get -y upgrade && apt-get install -y apt-transport-https software-properties-common libsasl2-dev python-dev libldap2-dev libssl-dev memcached +FROM python:3.5.2 +RUN apt-get update && apt-get -y upgrade && apt-get install -y --no-install-recommends apt-utils && apt-get install -y apt-transport-https software-properties-common libsasl2-dev python-dev libldap2-dev libssl-dev memcached RUN apt-get install -y gettext RUN git clone https://github.com/ethereum/pyethereum/ /opt/pyethereum -WORKDIR /opt/pyethereum -RUN python setup.py install -RUN mkdir -p /opt/project WORKDIR /opt/project ADD requirements.txt /opt/project RUN pip install -U pip RUN pip install -r requirements.txt +WORKDIR /opt/pyethereum +RUN python setup.py install +RUN mkdir -p /opt/project +WORKDIR /opt/project ADD . /opt/project \ No newline at end of file diff --git a/TsaApi/__pycache__/__init__.cpython-35.pyc b/TsaApi/__pycache__/__init__.cpython-35.pyc deleted file mode 100644 index 0867da956487e6a335bfb989fe0874b2d9f426d3..0000000000000000000000000000000000000000 Binary files a/TsaApi/__pycache__/__init__.cpython-35.pyc and /dev/null differ diff --git a/TsaApi/__pycache__/local_settings.cpython-35.pyc b/TsaApi/__pycache__/local_settings.cpython-35.pyc deleted file mode 100644 index 28354c72868e8eb0125421bd92f3bf444edce855..0000000000000000000000000000000000000000 Binary files a/TsaApi/__pycache__/local_settings.cpython-35.pyc and /dev/null differ diff --git a/TsaApi/__pycache__/settings.cpython-35.pyc b/TsaApi/__pycache__/settings.cpython-35.pyc deleted file mode 100644 index 668ba9be9382f726c5b2730593daa0d9612b45a3..0000000000000000000000000000000000000000 Binary files a/TsaApi/__pycache__/settings.cpython-35.pyc and /dev/null differ diff --git a/TsaApi/__pycache__/urls.cpython-35.pyc b/TsaApi/__pycache__/urls.cpython-35.pyc deleted file mode 100644 index 6ce68b54ef09b2ae4314920357e91db955db5c29..0000000000000000000000000000000000000000 Binary files a/TsaApi/__pycache__/urls.cpython-35.pyc and /dev/null differ diff --git a/TsaApi/__pycache__/wsgi.cpython-35.pyc b/TsaApi/__pycache__/wsgi.cpython-35.pyc deleted file mode 100644 index 2188d4539e91a971edc54ec9a54a32df922a194f..0000000000000000000000000000000000000000 Binary files a/TsaApi/__pycache__/wsgi.cpython-35.pyc and /dev/null differ diff --git a/TsaApi/settings.py b/TsaApi/settings.py index 305c8c6226ea64a32436791f47b52fab0d714ccf..1ad48e3fa4f20e614c78ac9f80a4808d854f79ca 100644 --- a/TsaApi/settings.py +++ b/TsaApi/settings.py @@ -8,6 +8,8 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/ """ # -*- coding: utf-8 -*- +import sys + """ Django settings for TsaApi project. @@ -24,11 +26,9 @@ import os import datetime import raven - # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ @@ -40,7 +40,6 @@ DEBUG = True ALLOWED_HOSTS = ['*'] - # Application definition INSTALLED_APPS = [ @@ -54,9 +53,12 @@ INSTALLED_APPS = [ 'raven.contrib.django.raven_compat', 'corsheaders', 'rest_framework_swagger', + 'elasticapm.contrib.django', + 'app' ] MIDDLEWARE = [ + 'elasticapm.contrib.django.middleware.TracingMiddleware', 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', @@ -89,7 +91,6 @@ TEMPLATES = [ WSGI_APPLICATION = 'TsaApi.wsgi.application' - # Database # https://docs.djangoproject.com/en/1.11/ref/settings/#databases @@ -100,7 +101,6 @@ DATABASES = { } } - # Password validation # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators @@ -125,6 +125,7 @@ AUTH_PASSWORD_VALIDATORS = [ LANGUAGE_CODE = 'es' from django.utils.translation import ugettext_lazy as _ + LANGUAGES = ( ('es', _('Spanish')), ) @@ -141,14 +142,11 @@ USE_L10N = True USE_TZ = True - # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.11/howto/static-files/ STATIC_URL = '/static/' - - # Habilitar para permitir la autenticación REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': ( @@ -161,8 +159,27 @@ REST_FRAMEWORK = { ), } +APP_ROOT = '/var/www/tsaapi.prod.bfa.local/' +LOG_ROOT = APP_ROOT + 'logs/' + SENTRY_URL = 'http://2ec54beaab3b4459a1e5afadea070f98:92aee3f5d91e4e66996a3e0792985541@172.17.30.21:9000/54' +MEMCACHED_HOST = '' +MEMCACHED_PORT = 0 # OJO, TIENE QUE SER INT, sino REVIENTA MEMCACHED + +PENDING_TXS_MEMCACHED_KEY = 'pending_txs' + +TEST_MEMCACHED_HOST = '' +TEST_MEMCACHED_PORT = '' + +TEST_PENDING_TXS_MEMCACHED_KEY = 'test_pending_txs' + +ELASTIC_APM = { + 'SERVICE_NAME': 'TsaApi', + 'SERVER_URL': 'http://10.1.100.67:8200', + 'DEBUG': False, +} + try: from TsaApi.local_settings import * except ImportError as e: @@ -170,4 +187,63 @@ except ImportError as e: RAVEN_CONFIG = { 'dsn': SENTRY_URL, -} \ No newline at end of file +} + +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'formatters': { + 'detailed': { + 'format': '%(levelname)s %(asctime)s %(filename)s %(funcName)s %(lineno)d %(message)s', + }, + 'history': { + 'format': '%(asctime)s %(message)s' + } + }, + 'handlers': { + 'console': { + 'level': 'DEBUG', + 'class': 'logging.StreamHandler', + 'stream': sys.stdout, + 'formatter': 'detailed' + }, + 'info_file': { + 'level': 'INFO', + 'class': 'logging.FileHandler', + 'filename': LOG_ROOT + 'info.log', + 'formatter': 'history', + }, + 'error_file': { + 'level': 'ERROR', + 'class': 'logging.FileHandler', + 'filename': LOG_ROOT + 'error.log', + 'formatter': 'detailed', + }, + 'sentry': { + 'level': 'ERROR', # To capture more than ERROR, change to WARNING, INFO, etc. + 'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler', + } + }, + 'loggers': { + 'django': { + 'handlers': ['error_file', 'info_file', 'sentry'], + 'level': 'WARNING', + 'propagate': True, + }, + 'raven': { + 'level': 'WARNING', + 'handlers': ['sentry'], + 'propagate': False, + }, + 'logger': { + 'handlers': ['error_file', 'info_file', 'sentry'], + 'level': 'INFO', + 'propagate': True, + }, + 'console-logger': { + 'handlers': ['console', 'info_file', 'error_file', 'sentry'], + 'progagate': True, + 'level': 'DEBUG' + } + }, +} diff --git a/app/__pycache__/__init__.cpython-35.pyc b/app/__pycache__/__init__.cpython-35.pyc deleted file mode 100644 index 24d1a5b248593470699ea6ca599e2ad3ab41f35e..0000000000000000000000000000000000000000 Binary files a/app/__pycache__/__init__.cpython-35.pyc and /dev/null differ diff --git a/app/__pycache__/managers.cpython-35.pyc b/app/__pycache__/managers.cpython-35.pyc deleted file mode 100644 index 84414991941a0fd259b67fa171434bb6480e741d..0000000000000000000000000000000000000000 Binary files a/app/__pycache__/managers.cpython-35.pyc and /dev/null differ diff --git a/app/__pycache__/tests.cpython-35.pyc b/app/__pycache__/tests.cpython-35.pyc deleted file mode 100644 index 141c512ef8dc016ba00a152829e2128983e736d9..0000000000000000000000000000000000000000 Binary files a/app/__pycache__/tests.cpython-35.pyc and /dev/null differ diff --git a/app/__pycache__/urls.cpython-35.pyc b/app/__pycache__/urls.cpython-35.pyc deleted file mode 100644 index 602e748e6acf4f15fbeb391bb9b5166a1e478622..0000000000000000000000000000000000000000 Binary files a/app/__pycache__/urls.cpython-35.pyc and /dev/null differ diff --git a/app/__pycache__/utils.cpython-35.pyc b/app/__pycache__/utils.cpython-35.pyc deleted file mode 100644 index 767ebaa7167744a271dfa368637b20ddaf7d18de..0000000000000000000000000000000000000000 Binary files a/app/__pycache__/utils.cpython-35.pyc and /dev/null differ diff --git a/app/__pycache__/views.cpython-35.pyc b/app/__pycache__/views.cpython-35.pyc deleted file mode 100644 index 23d4e3818c6a522e24813642e31c0aa94e39d530..0000000000000000000000000000000000000000 Binary files a/app/__pycache__/views.cpython-35.pyc and /dev/null differ diff --git a/app/managers.py b/app/managers.py index be93c2cd4d5abbfb029393c641fa2ad9eee8242a..11f17ef8b030c32ad842fbe00fe6f91dc74074bd 100644 --- a/app/managers.py +++ b/app/managers.py @@ -16,6 +16,8 @@ from TsaApi.settings import HOST_ADDRESS from web3.middleware import geth_poa_middleware import requests +from app.utils import Utils + class TimestampManager(models.Manager): @@ -43,7 +45,8 @@ class TimestampManager(models.Manager): def get_contract(contract_version): web3 = TimestampManager.get_provider() - return web3.eth.contract(abi=CONTRACTS[contract_version]['abi'], address=Web3.toChecksumAddress(CONTRACTS[contract_version]['address'])) + return web3.eth.contract(abi=CONTRACTS[contract_version]['abi'], + address=Web3.toChecksumAddress(CONTRACTS[contract_version]['address'])) @staticmethod def get_block(block_number): @@ -61,11 +64,11 @@ class TimestampManager(models.Manager): def stamp(proof_hash, file_hash): contract = TimestampManager.get_current_contract() - - web3 = TimestampManager.get_provider() - return contract.functions.stamp(proof_hash, file_hash).transact({'from': Web3.toChecksumAddress(ACCOUNT_ADDRESS), 'gas': GAS, 'gasPrice': web3.eth.gasPrice}) + web3 = TimestampManager.get_provider() + return contract.functions.stamp(proof_hash, file_hash).transact( + {'from': Web3.toChecksumAddress(ACCOUNT_ADDRESS), 'gas': GAS, 'gasPrice': web3.eth.gasPrice}) @staticmethod def verify(contract_version, proof_hash, file_hash): @@ -82,11 +85,57 @@ class TimestampManager(models.Manager): headers = {'Content-Type': 'application/json', 'accept': 'application/json'} - response = requests.post(HOST_ADDRESS, data='{"jsonrpc":"2.0","method":"clique_getSigners","params":[],"id":1}', headers=headers) + response = requests.post(HOST_ADDRESS, data='{"jsonrpc":"2.0","method":"clique_getSigners","params":[],"id":1}', + headers=headers) return len(response.json()['result']) @staticmethod def get_last_block_number(): web3 = TimestampManager.get_provider() - return web3.eth.getBlock('latest').number \ No newline at end of file + return web3.eth.getBlock('latest').number + + +class TxAlreadySealedInBlock(Exception): + pass + + +class EthereumGateway: + def transaction_is_canonical(self, transaction): + last_block_number = TimestampManager.get_last_block_number() + tx_block_number = transaction.blockNumber + required_block_difference = Utils.required_block_difference(TimestampManager.get_signers_count()) + return (last_block_number - tx_block_number) > required_block_difference + + def transaction(self, tx_hash): + return TimestampManager.get_transaction(tx_hash) + + def pending_transactions(self): + w3 = TimestampManager.get_provider() + filt = w3.eth.filter('pending') + tx_hashes = w3.eth.getFilterChanges(filt.filter_id) + # ojo con lo de abajo, es costoso en tiempo, hace len(tx_hashes) conexiones al nodo, si es sobre HTTP es lento + txs = [w3.eth.getTransaction(i.hex()) for i in tx_hashes] + return txs + + def block(self, block_number): + return TimestampManager.get_block(block_number) + + def resend(self, tx, gas_price=None, gas=None): + w3 = TimestampManager.get_provider() + tx_2 = w3.eth.getTransaction(tx['hash']) + if tx_2['blockNumber'] is not None: + raise TxAlreadySealedInBlock("Tx already sealed in block {}".format(str(tx_2['blockNumber']))) + return w3.eth.resend(tx, gas_price, gas) + + +class TimeStamp: + + def abi(self): + return CONTRACTS[CURRENT_CONTRACT_VERSION]['abi'] + + def verify(self, proof_hash, file_hash): + return TimestampManager.verify(CURRENT_CONTRACT_VERSION, proof_hash, file_hash) + + def get_block(self, proof_hash): + return TimestampManager.get_block_number(CURRENT_CONTRACT_VERSION, proof_hash) diff --git a/app/migrations/__pycache__/__init__.cpython-35.pyc b/app/migrations/__pycache__/__init__.cpython-35.pyc index 607aff09d96f8a1e3491a03b221cf63b89707c10..2387a6ec660cdb84289934a600da42e18277676c 100644 Binary files a/app/migrations/__pycache__/__init__.cpython-35.pyc and b/app/migrations/__pycache__/__init__.cpython-35.pyc differ diff --git a/app/services.py b/app/services.py new file mode 100644 index 0000000000000000000000000000000000000000..ab51a9e37a7fee79c778f049321d9de6f2097e0b --- /dev/null +++ b/app/services.py @@ -0,0 +1,235 @@ +import base64 +from json import JSONDecodeError + +from app.managers import TimestampManager, EthereumGateway, TimeStamp +from app.utils import Utils, Base64EncodingService +from pymemcache.client import base +from TsaApi.settings import MEMCACHED_HOST, MEMCACHED_PORT, PENDING_TXS_MEMCACHED_KEY +import logging +import json + +from sys import stdout +from django.utils.translation import gettext as _, gettext + +if stdout.isatty(): + log_str = 'console-logger' +else: + log_str = 'logger' + +module_logger = logging.getLogger(log_str) + + +class ReceiptInterpreter: + + def interpret(self, ots): + return ots.split('-') + + +class DefinitiveReceiptGenerator: + encoder = Base64EncodingService() + interpreter = ReceiptInterpreter() + gateway = EthereumGateway() + + def generate_definitive_receipt(self, original_file_hash, tx_hash, ots_hash): + tx = self.gateway.transaction(tx_hash) + return self.encoder.encode(Utils.get_permanent_ots(original_file_hash, ots_hash, tx_hash, + tx['blockNumber']).encode('utf-8')).decode('utf-8') + + +class TransactionInputVerification: + gateway = EthereumGateway() + interpreter = ReceiptInterpreter() + ts = TimeStamp() + tx_hash = '' + + def verify(self, original_file_hash, ots): + ots_version, file_hash, ots_hash, tx_hash, block_number = self.interpreter.interpret(ots) + method_name, args = Utils.decode_contract_call(self.ts.abi(), TimestampManager.get_transaction(tx_hash).input) + return args[0].decode('utf-8') == ots_hash and args[1].decode('utf-8') == file_hash + + +class SmartContractVerification: + timestamp = TimeStamp() + interpreter = ReceiptInterpreter() + gateway = EthereumGateway() + + def verify(self, file_hash, ots_hash): + verified = self.timestamp.verify(ots_hash, file_hash) + block = None + if verified: + block_number = self.timestamp.get_block(ots_hash) + block = self.gateway.block(block_number) + return verified, block + + +class EventVerification: + gateway = EthereumGateway() + + +class VerifyService: + receipt_generator = DefinitiveReceiptGenerator() + definitive_verificator = SmartContractVerification() + temp_verificator = SmartContractVerification() + interpreter = ReceiptInterpreter() + gateway = EthereumGateway() + permanent_ots_prefix = '' + + def verify(self, original_file_hash, rd): + if rd[:2] == self.permanent_ots_prefix: + ots_version, file_hash, ots_hash, tx_hash, block_number = self.interpreter.interpret(rd) + verified, block = self.definitive_verificator.verify(original_file_hash, ots_hash) + if verified: + return {'status': 'success', + 'block': block, + 'permanent_rd': self.receipt_generator.generate_definitive_receipt(original_file_hash, + tx_hash, + ots_hash)} + else: + return {'status': 'failure'} + + else: + ots_version, ots_hash, tx_hash = self.interpreter.interpret(rd) + verified, block = self.temp_verificator.verify(original_file_hash, ots_hash) + tx = self.gateway.transaction(tx_hash) + if verified: + if not self.gateway.transaction_is_canonical(tx): + return {'status': 'pending'} + else: + return {'status': 'success', + 'block': block, + 'permanent_rd': self.receipt_generator.generate_definitive_receipt(original_file_hash, + tx_hash, + ots_hash)} + else: + try: + if tx and not tx.blockNumber: + return {'status': 'pending'} + except ValueError: + pass + + return {'status': 'failure'} + + +class StatusNotValidException(Exception): + pass + + +class VerifyServiceTranslation: + verify_service = VerifyService() + + def verify(self, original_file_hash, rd): + result = self.verify_service.verify(original_file_hash, rd) + dict_res = {gettext('status'): result['status']} + res_status = 200 + if result['status'] == 'success': + dict_res[gettext('permanent_rd')] = result['permanent_rd'] + dict_res[gettext('attestation_time')] = str(Utils.datetime_from_timestamp(result['block'].timestamp)) + dict_res[gettext('messages')] = gettext('file_uploaded') % ( + original_file_hash, + str(result['block'].number), + str(Utils.datetime_from_timestamp(result['block'].timestamp)) + ) + elif result['status'] == 'failure': + dict_res[gettext('messages')] = gettext('file_not_found') + res_status = 404 + elif result['status'] == 'pending': + dict_res[gettext('messages')] = gettext('transaction_pending') + else: + raise StatusNotValidException('Status invalid in verify service result : {}'.format(result['status'])) + + return dict_res, res_status + + +class MemcachedStorage: + client = base.Client((MEMCACHED_HOST, MEMCACHED_PORT)) + server_tuple = (MEMCACHED_HOST, MEMCACHED_PORT) + key = PENDING_TXS_MEMCACHED_KEY + logger = module_logger + + def store(self, pending_txs): + jsoned = json.dumps(pending_txs) + self.client.set(self.key, jsoned) + + def retrieve(self): + retrieved = self.client.get(self.key) + try: + decoded = retrieved.decode("utf-8") + interpretado = json.loads(decoded) + except AttributeError as ae: + self.logger.info("Memcached estaba vacÃo key: {} ; valor : {}".format(str(self.key), retrieved)) + interpretado = [] + except JSONDecodeError as jsonde: + self.logger.error( + "Se devolvera lista vacia. No se pudo decodear la string: {} {}".format(retrieved, str(jsonde))) + interpretado = [] + return interpretado + + +class CheckCriteria: + storage = MemcachedStorage() + + def __init__(self, logger=None): + if logger is not None: + self.logger = logger + + def should_transactions_be_resent(self, stuck_txs): + stored_txs = self.storage.retrieve() + txs_dict = {} + for stx in stored_txs: + txs_dict[stx['hash']] = stx + + res = False + for tx in stuck_txs: + res = res or tx['hash'] in txs_dict + + return res + + +class PendingTransactionsService: + gateway = EthereumGateway() + criteria = CheckCriteria() + logger = module_logger + + def __init__(self, logger=None): + if logger is not None: + self.logger = logger + + def resend_transactions(self, txs, new_gas_price, new_gas_limit=None): + self.logger.info("Por evaluar si deberÃan ser reenviadas las transacciones") + new_txs = [] + failed = [] + if self.criteria.should_transactions_be_resent(txs): # obs: si es vacÃo entonces evalúa True + for tx in txs: # si es vacÃo este ciclo no hace nada + self.logger.info("Reenviando transaccion {}".format(str(tx))) + try: + new_txs.append(self.gateway.resend(tx, new_gas_price, new_gas_limit)) + except Exception as e: + failed.append(tx) + self.logger.error('Error :{}'.format(str(e))) + + return new_txs, failed + + +class TransactionUnstucker: + pending_transaction_service = PendingTransactionsService() + gateway = EthereumGateway() + storage = MemcachedStorage() + logger = module_logger + + def __init__(self, logger=None): + if logger is not None: + self.logger = logger + self.pending_transaction_service.logger = logger + + def unstuck_pending_transactions(self, new_gas_price, new_gas=None): + self.logger.info('Arranca proceso') + stuck_txs = self.gateway.pending_transactions() + self.logger.debug('Cant txs encontradas : {}'.format(len(stuck_txs))) + res, failed = self.pending_transaction_service.resend_transactions(stuck_txs, new_gas_price, new_gas) + self.logger.debug( + 'Terminado, cant reenvios con exito: {} ; cant reenvios fallados: {}'.format(len(res), len(failed))) + nuevas_txs = self.gateway.pending_transactions() + self.logger.debug('Cant txs encontradas : {}'.format(len(nuevas_txs))) + self.storage.store(nuevas_txs) + self.logger.info('Termina proceso') + return res, failed diff --git a/app/tests.py b/app/tests.py index 56271e47a8e679931e5ee162199b0f2dae9761bd..08b37f0e66a755ebfbe319bbb9a1dc33aa0727f5 100644 --- a/app/tests.py +++ b/app/tests.py @@ -7,28 +7,453 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/ """ +import base64 +import json +import time +import unittest + +from django.utils.translation import gettext as _ from django.test import TestCase from django.test import Client +from eth_account.datastructures import AttributeDict +from pymemcache.client import base from rest_framework import status +from TsaApi.settings import TEST_MEMCACHED_HOST, TEST_MEMCACHED_PORT, TEST_PENDING_TXS_MEMCACHED_KEY, \ + PERMANENT_OTS_PREFIX +from app.services import PendingTransactionsService, CheckCriteria, MemcachedStorage, TransactionUnstucker, \ + VerifyService -class TimestampTest(TestCase): - def test_verify(self): +class TimestampTest(unittest.TestCase): + def test_full_cycle(self): c = Client() - response = c.post('/api/tsa/verify/', { + response = c.post('/api/tsa/stamp/', { + "file_hash": "1957db7fe23e4be1740ddeb941ddda7ae0a6b782e536a9e00b5aa82db1e84547" + }) + + self.assertEqual(response.status_code, status.HTTP_200_OK, 'Stamp Error') + dict_response = json.loads(response.content.decode()) + time.sleep(30) + + verify_response = c.post('/api/tsa/verify/', { "file_hash": "1957db7fe23e4be1740ddeb941ddda7ae0a6b782e536a9e00b5aa82db1e84547", - "proof": "NjA2MWQ1ODNkMTM0YTlmZGZlYWYxMWE2MjY1ZDY1OTkwMGM0ZGZjNjM2MDk3MDY4N2VkNDk3NTNhZmE5MGM3YzAxLTB4NWRiYjg4NjNlNDlhN2NmYTY0N2FmYzVkOTFkMWRiMDUxY2YzODZiYWY4NzJkM2ZiYTIzN2JkY2IwOWUwNWMzYQ==" + "rd": dict_response['temporary_rd'] }) - self.assertEqual(response.status_code, status.HTTP_200_OK, 'Verify Error') + self.assertEqual(verify_response.status_code, status.HTTP_200_OK, 'Verify Error') + dict_verify = json.loads(verify_response.content.decode()) + self.assertEqual(dict_verify[_('status')], _('success')) - def test_stamp(self): - c = Client() - response = c.post('/api/tsa/stamp/', { - "file_hash": "1957db7fe23e4be1740ddeb941ddda7ae0a6b782e536a9e00b5aa82db1e84547" - }) +class EthereumMocks: + class HalfResender: + balance = 2.1 * 21000 * 1000 # me da para 2 txs mockeadas + + def resend(self, tx, gas_price, gas=None): + if gas is None: + gas = tx['gas'] + costo = (gas_price * gas) + tx['value'] + if self.balance > costo: + self.balance = self.balance - costo + return tx + raise Exception("Failing test") + + class EthereumMock: + + def __init__(self): + self.pending_txs = [ + AttributeDict({ + 'blockHash': '0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd', + 'blockNumber': None, + 'from': '0xa1e4380a3b1f749673e270229993ee55f35663b4', + 'gas': 21000, + 'gasPrice': 500, + 'hash': '0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060', + 'input': '0x', + 'nonce': 0, + 'to': '0x5df9b87991262f6ba471f09758cde1c0fc1de734', + 'transactionIndex': 0, + 'value': 1000, + }), + AttributeDict({ + 'blockHash': '0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd', + 'blockNumber': 0, + 'from': '0xa1e4380a3b1f749673e270229993ee55f35663b4', + 'gas': 21000, + 'gasPrice': 500, + 'hash': '0x6c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060', + 'input': '0x', + 'nonce': 0, + 'to': '0x5df9b87991262f6ba471f09758cde1c0fc1de734', + 'transactionIndex': 0, + 'value': 1000, + }), + AttributeDict({ + 'blockHash': '0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd', + 'blockNumber': 0, + 'from': '0xa1e4380a3b1f749673e270229993ee55f35663b4', + 'gas': 21000, + 'gasPrice': 500, + 'hash': '0x7d515ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060', + 'input': '0x', + 'nonce': 0, + 'to': '0x5df9b87991262f6ba471f09758cde1c0fc1de734', + 'transactionIndex': 0, + 'value': 1000, + }) + ] + + def resend(self, tx, gas_price, gas): + self.pending_txs.remove(tx) + return tx + + def pending_transactions(self): + return self.pending_txs.copy() + + class CriteriaMockTrue: + def should_transactions_be_resent(self, txs): + return True + + class CriteriaMockFalse: + def should_transactions_be_resent(self, txs): + return False + + class StorageMock: + def __init__(self, stored_txs): + self.stored_txs = stored_txs + + def store(self, txs): + self.stored_txs = txs + + def retrieve(self): + return self.stored_txs + + class FailResender: + def resend(self, tx, gas_price, gas): + raise Exception("Failing test") + + def generar_valores(self): + self.current_txs = \ + [ + AttributeDict({ + 'blockHash': '0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd', + 'blockNumber': None, + 'from': '0xa1e4380a3b1f749673e270229993ee55f35663b4', + 'gas': 21000, + 'gasPrice': 500, + 'hash': '0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060', + 'input': '0x', + 'nonce': 0, + 'to': '0x5df9b87991262f6ba471f09758cde1c0fc1de734', + 'transactionIndex': 0, + 'value': 1000, + }), + AttributeDict({ + 'blockHash': '0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd', + 'blockNumber': 0, + 'from': '0xa1e4380a3b1f749673e270229993ee55f35663b4', + 'gas': 21000, + 'gasPrice': 500, + 'hash': '0x6c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060', + 'input': '0x', + 'nonce': 0, + 'to': '0x5df9b87991262f6ba471f09758cde1c0fc1de734', + 'transactionIndex': 0, + 'value': 1000, + }), + AttributeDict({ + 'blockHash': '0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd', + 'blockNumber': 0, + 'from': '0xa1e4380a3b1f749673e270229993ee55f35663b4', + 'gas': 21000, + 'gasPrice': 500, + 'hash': '0x7d515ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060', + 'input': '0x', + 'nonce': 0, + 'to': '0x5df9b87991262f6ba471f09758cde1c0fc1de734', + 'transactionIndex': 0, + 'value': 1000, + }) + ] + self.stored_txs = \ + [ + AttributeDict({ + 'blockHash': '0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd', + 'blockNumber': None, + 'from': '0xa1e4380a3b1f749673e270229993ee55f35663b4', + 'gas': 21000, + 'gasPrice': 500, + 'hash': '0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060', + 'input': '0x', + 'nonce': 0, + 'to': '0x5df9b87991262f6ba471f09758cde1c0fc1de734', + 'transactionIndex': 0, + 'value': 31337, + }), + AttributeDict({ + 'blockHash': '0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd', + 'blockNumber': 0, + 'from': '0xa1e4380a3b1f749673e270229993ee55f35663b4', + 'gas': 21000, + 'gasPrice': 500, + 'hash': '0x6c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060', + 'input': '0x', + 'nonce': 0, + 'to': '0x5df9b87991262f6ba471f09758cde1c0fc1de734', + 'transactionIndex': 0, + 'value': 31337, + }), + AttributeDict({ + 'blockHash': '0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd', + 'blockNumber': 0, + 'from': '0xa1e4380a3b1f749673e270229993ee55f35663b4', + 'gas': 21000, + 'gasPrice': 500, + 'hash': '0x6c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060', + 'input': '0x', + 'nonce': 0, + 'to': '0x5df9b87991262f6ba471f09758cde1c0fc1de734', + 'transactionIndex': 0, + 'value': 31337, + }) + ] + + +class PendingTransactionTest(unittest.TestCase, EthereumMocks): + + def setUp(self): + self.service = PendingTransactionsService() + self.generar_valores() + + def test_verify_criteria_affirmative(self): + self.service.criteria = self.CriteriaMockTrue() + self.service.gateway = self.EthereumMock() + mock_txs = self.current_txs + result, failed = self.service.resend_transactions(mock_txs, 1000, ) + self.assertEqual(result, mock_txs) + self.assertEqual(failed, []) + + def test_verify_criteria_false(self): + self.service.criteria = self.CriteriaMockFalse() + self.service.gateway = self.EthereumMock() + mock_txs = self.current_txs + result, failed = self.service.resend_transactions(mock_txs, 1000, ) + self.assertEqual(result, []) + self.assertEqual(failed, []) + + def test_resend(self): + criteria = CheckCriteria() + criteria.storage = self.StorageMock(self.stored_txs) + self.service.criteria = criteria + self.service.gateway = self.EthereumMock() + txs = self.current_txs + result, failed = self.service.resend_transactions(txs, 1000, ) + self.assertEqual(result, txs) + self.assertEqual(failed, []) + + def test_resend_everything_failed(self): + criteria = CheckCriteria() + criteria.storage = self.StorageMock(self.stored_txs) + self.service.criteria = criteria + self.service.gateway = self.FailResender() + txs = self.current_txs + result, failed = self.service.resend_transactions(txs, 1000, ) + self.assertEqual(result, []) + self.assertEqual(failed, txs) + + def test_resend_some_failed(self): + criteria = CheckCriteria() + criteria.storage = self.StorageMock(self.stored_txs) + self.service.criteria = criteria + self.service.gateway = self.HalfResender() + txs = self.current_txs + result, failed = self.service.resend_transactions(txs, 1000, ) + self.assertEqual(result, txs[:2]) + self.assertEqual(failed, [txs[-1]]) + + +class MemcachedStorageTest(unittest.TestCase): + + def setUp(self): + self.storage = MemcachedStorage() + pass + + class MemcachedMock: + dictionary = {} + + def __init__(self, some=None): # necesario para el monkey patching que hice en un test + pass + + def set(self, key, value, expire_time=0): + self.dictionary[key] = str(value).encode() + return value + + def get(self, key): + return self.dictionary[key] + + def test_mock_memcached(self): + old_client = self.storage.client + old_mem_client = base.Client # perdon por el monkey patching :c + self.storage.client = self.MemcachedMock() + base.Client = self.MemcachedMock + some_stuff = [123, 345, 78, 9] + self.storage.store(some_stuff) + retrieved = self.storage.retrieve() + self.assertEqual(some_stuff, retrieved) + self.storage.client = old_client + base.Client = old_mem_client + + def test_integrated_memcached(self): + m_client = base.Client((TEST_MEMCACHED_HOST, TEST_MEMCACHED_PORT)) + m_client.set(TEST_PENDING_TXS_MEMCACHED_KEY, []) + self.storage.client = m_client + self.storage.key = TEST_PENDING_TXS_MEMCACHED_KEY + some_stuff = [1, 2, 3, 4, 54, 5, 765] + self.storage.store(some_stuff) + retrieved = self.storage.retrieve() + self.assertEqual(some_stuff, retrieved) + + +class TransactionUnstuckerTest(unittest.TestCase, EthereumMocks): + + def setUp(self): + self.unstucker = TransactionUnstucker() + self.generar_valores() + pass + + def test_unstuck_mock_storage(self): + mock = self.EthereumMock() + service = PendingTransactionsService() + service.criteria.storage = self.StorageMock(mock.pending_transactions()) + service.gateway = mock + self.unstucker.gateway = mock + self.unstucker.pending_transaction_service = service + self.unstucker.storage = service.criteria.storage + res, failed = self.unstucker.unstuck_pending_transactions(1000) + self.assertEqual(failed, []) + self.assertEqual(mock.pending_transactions(), []) + + # testeo contra transacciones cacheadas que siguen pendientes, + def test_unstuck_integrated_memcached_custom_client(self): + m_client = base.Client((TEST_MEMCACHED_HOST, TEST_MEMCACHED_PORT)) # me hago mi propio cliente + m_client.set(TEST_PENDING_TXS_MEMCACHED_KEY, []) # limpio por las dudas sobre otra key + mock = self.EthereumMock() # mockeo el gateway de ethereum + service = PendingTransactionsService() + service.criteria.storage.client = m_client # setteo mi cliente + service.criteria.storage.key = TEST_PENDING_TXS_MEMCACHED_KEY + service.criteria.storage.store(mock.pending_transactions()) # y le asigno las pendientes mockeadas + + service.gateway = mock # asigno los mocks que armé + self.unstucker.gateway = mock + self.unstucker.pending_transaction_service = service + self.unstucker.storage = service.criteria.storage + + res, failed = self.unstucker.unstuck_pending_transactions(1000) + self.assertEqual(failed, []) + self.assertEqual(res, self.current_txs) + self.assertEqual(mock.pending_transactions(), []) + + # testeo contra nada de transacciones cacheadas, y transacciones pendientes en el momento + def test_unstuck_integrated_memcached_custom_client_nothing_to_do(self): + m_client = base.Client((TEST_MEMCACHED_HOST, TEST_MEMCACHED_PORT)) + m_client.set(TEST_PENDING_TXS_MEMCACHED_KEY, []) + mock = self.EthereumMock() + service = PendingTransactionsService() + service.criteria.storage.client = m_client + service.gateway = mock + self.unstucker.gateway = mock + self.unstucker.pending_transaction_service = service + self.unstucker.storage = service.criteria.storage + + res, failed = self.unstucker.unstuck_pending_transactions(1000) + self.assertEqual(failed, []) + self.assertEqual(res, []) # como no tenÃa cache entonces no se hizo nada + + +class VerificationMocks: + class NotSealedTransactionMock: + def transaction(self, tx_hash): + return AttributeDict( + {'hash': tx_hash, 'blockNumber': None}) + + class SealedTransactionMock: + def transaction(self, tx_hash): + return AttributeDict( + {'hash': tx_hash, 'blockNumber': 1000}) + + class TXisCanonicalMock: + def transaction_is_canonical(self, tx): + return True + + class TXisNotCanonicalMock: + def transaction_is_canonical(self, tx): + return False + + class FalseVerificationMock: + def verify(self, file_hash, ots_hash): + return False, None + + class TrueVerificationMock: + def verify(self, file_hash, ots_hash): + return True, AttributeDict({'number': 1000}) + + class TxCanonicalSealedTx(SealedTransactionMock, TXisCanonicalMock): + pass + + class TxNoCanonicalSealedTx(SealedTransactionMock, TXisNotCanonicalMock): + pass + + class ReceiptGeneratorMock: + def generate_definitive_receipt(self, original_file_hash, tx_hash, ots_hash): + return "{}{}{}".format(original_file_hash, tx_hash, ots_hash) + + +class VerifyServiceTest(unittest.TestCase, VerificationMocks): + + def setUp(self): + self.service = VerifyService() + self.service.permanent_ots_prefix = PERMANENT_OTS_PREFIX + self.ots = "d6b88be854cb88cf1e161d8bbd912987cff236eb43a3a15fff86370054dd4d3f01" + self.file_hash = 'some_hash' + self.contract_version = '01' + self.temp_rd = "0x-d6b88be854cb88cf1e161d8bbd912987cff236eb43a3a15fff86370054dd4d3f01-0xf495705b5d7d68afdcefe72d9bbacec59b1a968c964ab0b6b13cec6491e5ddec" + self.def_rd = "1x-7b58a42ee27c3699fc169ce43cb4ba598003cba5699f4cd518b91d4177f61f10-0577d1390a5733534c7b6cc45a7ba15d0b5bf37faec9c54f749826af75aa86bb01-0x1a3a5335b44bdb7f62201102e91658586fd1d7845c621503eeff7781c3020b09-2234445" + pass + + def test_verify_temporary_rd_not_sealed(self): + self.service.gateway = self.NotSealedTransactionMock() + self.service.temp_verificator = self.FalseVerificationMock() + res = self.service.verify(self.file_hash, self.temp_rd) + self.assertEqual(res['status'], 'pending') + + def test_verify_temporary_rd_sealed_not_canonical(self): + self.service.gateway = self.TxNoCanonicalSealedTx() + self.service.temp_verificator = self.TrueVerificationMock() + res = self.service.verify(self.file_hash, self.temp_rd) + self.assertEqual(res['status'], 'pending') + + def test_verify_temporary_rd_sealed_canonical(self): + self.service.temp_verificator = self.TrueVerificationMock() + self.service.gateway = self.TxCanonicalSealedTx() + self.service.receipt_generator.gateway = self.SealedTransactionMock() + res = self.service.verify(self.file_hash, self.temp_rd) + + self.assertEqual(res['status'], 'success') + rd = res['permanent_rd'] + ots_version, file_hash, ots_hash, tx_hash, block_number = base64.b64decode(rd).decode().split('-') + self.assertEqual(ots_version, PERMANENT_OTS_PREFIX) + self.assertEqual(file_hash, self.file_hash) + self.assertEqual(ots_hash, self.ots) + self.assertEqual(res['block'].number, int(block_number)) + + def test_definitive_rd_correct(self): + self.service.definitive_verificator = self.TrueVerificationMock() + self.service.receipt_generator = self.ReceiptGeneratorMock() + res = self.service.verify(self.file_hash, self.def_rd) + self.assertEqual(res['status'], 'success') - self.assertEqual(response.status_code, status.HTTP_200_OK, 'Stamp Error') \ No newline at end of file + def test_definitive_rd_incorrect(self): + res = self.service.verify(self.file_hash, self.def_rd) + self.assertEqual(res['status'], 'failure') diff --git a/app/utils.py b/app/utils.py index d23d68fe2c988cead773aa2af0c2814a47ec4493..d7e2b233fa5822086b7ef5574305ce7e5f273a48 100644 --- a/app/utils.py +++ b/app/utils.py @@ -12,8 +12,6 @@ import hashlib import time import math from TsaApi.settings import ACCOUNT_ADDRESS, CURRENT_CONTRACT_VERSION, PERMANENT_OTS_PREFIX, TEMPORARY_OTS_PREFIX -from ethereum.abi import (decode_abi, normalize_name as normalize_abi_method_name, method_id as get_abi_method_id) -from ethereum.utils import encode_int, zpad, decode_hex class Utils(): diff --git a/app/views.py b/app/views.py index 41ef165c5da3cc5ab86ab611c7a8f65cb70018ad..2642b5be2446c92b319d7590066f9629c5c372ca 100644 --- a/app/views.py +++ b/app/views.py @@ -8,19 +8,26 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/ """ import base64 +import logging + +import coreapi +import coreschema from django.core.exceptions import ValidationError from django.utils.translation import gettext as _ +from raven.contrib.django.raven_compat.models import client from rest_framework import status -from rest_framework.views import APIView from rest_framework.response import Response -from web3.exceptions import CannotHandleRequest -from raven.contrib.django.raven_compat.models import client from rest_framework.schemas import ManualSchema -import coreschema,coreapi +from rest_framework.views import APIView +from web3.exceptions import CannotHandleRequest +from TsaApi.local_settings import PERMANENT_OTS_PREFIX from app.managers import TimestampManager +from app.services import VerifyServiceTranslation from app.utils import Utils -from TsaApi.local_settings import TEMPORARY_OTS_PREFIX, PERMANENT_OTS_PREFIX, CONTRACTS + +logger = logging.getLogger('logger') + class Stamp(APIView): """ @@ -47,7 +54,6 @@ class Stamp(APIView): schema=coreschema.String(), description='El hash del archivo encodeado en sha256', - ), ]) @@ -61,20 +67,26 @@ class Stamp(APIView): ots_hash = Utils.get_ots_hash(file_hash) + logger.info("Haciendo stamp para hash: {} ots: {} ".format(file_hash, ots_hash)) tx_hash = TimestampManager.stamp(ots_hash, file_hash) - - #Al OTS se le agrega la transacción para poder verificar luego si está pendiente de subida + logger.info("Stamp para hash: {} ots: {} terminado, tx_hash : {}".format(file_hash, ots_hash, tx_hash.hex())) + # Al OTS se le agrega la transacción para poder verificar luego si está pendiente de subida ots = Utils.get_temporary_ots(ots_hash, tx_hash.hex()) - return Response({_('status'): _('success'), _('temporary_rd'): base64.b64encode(ots.encode('utf-8')).decode('utf-8')}, status=status.HTTP_200_OK) + return Response( + {_('status'): _('success'), _('temporary_rd'): base64.b64encode(ots.encode('utf-8')).decode('utf-8')}, + status=status.HTTP_200_OK) except ValidationError as e: - return Response({_('status'): _('failure'), _('messages'): _('parameter_missing') % e.message}, status=status.HTTP_400_BAD_REQUEST) + return Response({_('status'): _('failure'), _('messages'): _('parameter_missing') % e.message}, + status=status.HTTP_400_BAD_REQUEST) except CannotHandleRequest: - return Response({_('status'): _('failure'), _('messages'): _('could_not_connect')}, status=status.HTTP_503_SERVICE_UNAVAILABLE) + return Response({_('status'): _('failure'), _('messages'): _('could_not_connect')}, + status=status.HTTP_503_SERVICE_UNAVAILABLE) except Exception as e: client.captureException() - return Response({_('status'): _('failure'), _('messages'): _('operation_failed')}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return Response({_('status'): _('failure'), _('messages'): _('operation_failed')}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) class Verify(APIView): @@ -112,6 +124,8 @@ class Verify(APIView): ) ]) + verify_service = VerifyServiceTranslation() + def post(self, request): try: @@ -122,65 +136,26 @@ class Verify(APIView): raise ValidationError('rd') original_file_hash = request.data.get('file_hash') - base64_ots = request.data.get('rd') - - ots = base64.b64decode(base64_ots).decode('utf-8') - - if ots[:2] == PERMANENT_OTS_PREFIX: + base64_rd = request.data.get('rd') - ots_version, file_hash, ots_hash, tx_hash, block_number = ots.split('-') - - method_name, args = Utils.decode_contract_call(CONTRACTS['01']['abi'], TimestampManager.get_transaction(tx_hash).input) - - if args[0].decode('utf-8') == ots_hash and args[1].decode('utf-8') == original_file_hash: - - block = TimestampManager.get_block(int(block_number)) - - return Response({_('status'): _('success'), - _('permanent_rd'): base64.b64encode(Utils.get_permanent_ots(original_file_hash, ots_hash, tx_hash, block.number).encode('utf-8')).decode('utf-8'), - _('attestation_time'): str(Utils.datetime_from_timestamp(block.timestamp)), - _('messages'): _('file_uploaded') % ( - file_hash, str(block.number), str(Utils.datetime_from_timestamp(block.timestamp)))}, - status=status.HTTP_200_OK) - else: - return Response({_('status'): _('failure'), _('messages'): _('file_not_found')}, - status=status.HTTP_404_NOT_FOUND) - - else: - - ots_version, ots_hash, tx_hash = ots.split('-') - - transaction = TimestampManager.get_transaction(tx_hash) - - contract_version = ots_hash[-2:] - - if TimestampManager.verify(contract_version, ots_hash, original_file_hash): - - if (TimestampManager.get_last_block_number() - transaction.blockNumber) < Utils.required_block_difference(TimestampManager.get_signers_count()): - return Response({_('status'): _('pending'), _('messages'): _('transaction_pending')}, - status=status.HTTP_200_OK) - else: - - block = TimestampManager.get_block(TimestampManager.get_block_number(contract_version, ots_hash)) - - return Response({_('status'): _('success'), - _('permanent_rd'): base64.b64encode(Utils.get_permanent_ots(original_file_hash, ots_hash, tx_hash, block.number).encode('utf-8')).decode('utf-8'), - _('attestation_time'): str(Utils.datetime_from_timestamp(block.timestamp)), - _('messages'): _('file_uploaded') % (original_file_hash, str(block.number), str( - Utils.datetime_from_timestamp(block.timestamp)))}, status=status.HTTP_200_OK) - else: - try: - if transaction and not transaction.blockNumber: - return Response({_('status'): _('pending'), _('messages'): _('transaction_pending')}, status=status.HTTP_200_OK) - except ValueError: - pass - - return Response({_('status'): _('failure'), _('messages'): _('file_not_found')},status=status.HTTP_404_NOT_FOUND) + rd = base64.b64decode(base64_rd).decode('utf-8') + # importante la asignación del prefijo de ots permanente + self.verify_service.verify_service.permanent_ots_prefix = PERMANENT_OTS_PREFIX + logger.info("Iniciando verificacion para hash {} rd {} baserd {}".format(original_file_hash, rd, base64_rd)) + result, res_status = self.verify_service.verify(original_file_hash, rd) + logger.info( + "Verificacion terminada para hash {} rd {}, resultado {}".format(original_file_hash, rd, str(result))) + return Response(result, status=res_status) except ValidationError as e: - return Response({_('status'): _('failure'), _('messages'): _('parameter_missing') % e.message}, status=status.HTTP_400_BAD_REQUEST) - except CannotHandleRequest: - return Response({_('status'): _('failure'), _('messages'): _('could_not_connect')}, status=status.HTTP_503_SERVICE_UNAVAILABLE) + return Response({_('status'): _('failure'), _('messages'): _('parameter_missing') % e.message}, + status=status.HTTP_400_BAD_REQUEST) + except CannotHandleRequest as e: + logger.critical('Servicio a nivel Geth no disponible!! error: {}'.format(str(e))) + return Response({_('status'): _('failure'), _('messages'): _('could_not_connect')}, + status=status.HTTP_503_SERVICE_UNAVAILABLE) except Exception as e: client.captureException() - return Response({_('status'): _('failure'), _('messages'): _('operation_failed')}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) \ No newline at end of file + logger.error('Error interno en la verificacion : {}'.format(str(e))) + return Response({_('status'): _('failure'), _('messages'): _('operation_failed')}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) diff --git a/pyethereum b/pyethereum deleted file mode 160000 index d962694be03686a8e5c1d7459ae272b70a5c9f77..0000000000000000000000000000000000000000 --- a/pyethereum +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d962694be03686a8e5c1d7459ae272b70a5c9f77 diff --git a/requirements.txt b/requirements.txt index 409c0b57d2b1ac957849526b39094948017a1d09..e7e919e6c5f1ea1ae27a23d4c7d124c58f98abfe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,10 @@ -asn1crypto==0.24.0 +ipython==7.2.0 +ipython-genutils==0.2.0 +pymemcache==2.1.1 +elastic-apm==3.0.0 attrdict==2.0.0 certifi==2018.4.16 -cffi==1.11.5 chardet==3.0.4 -coincurve==9.0.0 coreapi==2.3.3 coreschema==0.0.4 cytoolz==0.9.0.1 @@ -19,8 +20,6 @@ eth-keyfile==0.5.1 eth-keys==0.2.0b3 eth-rlp==0.1.2 eth-utils==1.0.3 -ethereum==2.3.2 -future==0.16.0 hexbytes==0.1.0 idna==2.7 itypes==1.1.0 @@ -29,27 +28,17 @@ lru-dict==1.1.6 MarkupSafe==1.0 openapi-codec==1.3.2 parsimonious==0.8.0 -pbkdf2==1.3 -pkg-resources==0.0.0 -py-ecc==1.4.3 -pycparser==2.18 pycryptodome==3.6.4 -pyethash==0.1.27 PyJWT==1.6.4 -pysha3==1.0.2 python-bitcoinlib==0.10.1 pytz==2018.5 -PyYAML==4.2b4 raven==6.9.0 -repoze.lru==0.7 requests==2.19.1 rlp==1.0.1 -scrypt==0.8.6 simplejson==3.16.0 six==1.11.0 toolz==0.9.0 uritemplate==3.0.0 urllib3==1.23 web3==4.5.0 -websockets==5.0.1 - +websockets==5.0.1 \ No newline at end of file