Skip to content
Snippets Groups Projects
Commit 7bd7d15b authored by Agustin Dorda's avatar Agustin Dorda
Browse files

Merge branch 'master' into 'resend-command'

# Conflicts:
#   app/managers.py
#   app/views.py
parents 8ca29eea bd88b3f8
No related branches found
No related tags found
No related merge requests found
Showing with 859 additions and 44 deletions
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
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
ADD . /opt/project
\ No newline at end of file
...@@ -14,11 +14,13 @@ pip install -r requirements.txt ...@@ -14,11 +14,13 @@ pip install -r requirements.txt
``` ```
- Crear un link simbólico al archivo local_settings.py desde el local settings del entorno en que se encuentre el proyecto - Crear un link simbólico al archivo local_settings.py desde el local settings del entorno en que se encuentre el proyecto
- Ejecutar: - Ejecutar:
- sudo apt-get install libssl-dev build-essential automake pkg-config libtool libffi-dev libgmp-dev libyaml-cpp-dev - sudo apt-get install libssl-dev build-essential automake pkg-config libtool libffi-dev libgmp-dev libyaml-cpp-dev memcached
- git clone https://github.com/ethereum/pyethereum/ - git clone https://github.com/ethereum/pyethereum/
- cd pyethereum - cd pyethereum
- python setup.py install - python setup.py install
- Poner en crontab el sig comando reemplazando donde corresponde los directorios
/directorio/del/venv/bin/python /directorio/de/aplicacion/manage.py desatascar_pendientes > /dev/null
### Utilización ### Utilización
- Stamp - Stamp
- Método: POST - Método: POST
......
...@@ -7,7 +7,9 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY ...@@ -7,7 +7,9 @@ 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/ You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/
""" """
APP_ROOT = '/home/python/TsaAPI/' APP_ROOT = '/opt/project/'
LOG_ROOT = APP_ROOT + 'logs/'
STATIC_ROOT = APP_ROOT + 'html/' STATIC_ROOT = APP_ROOT + 'html/'
...@@ -15,11 +17,23 @@ ACCOUNT_ADDRESS = '0x25c185dcaed065bac30a0a1cd0688a7aa02896d7' ...@@ -15,11 +17,23 @@ ACCOUNT_ADDRESS = '0x25c185dcaed065bac30a0a1cd0688a7aa02896d7'
HOST_ADDRESS = 'http://10.23.10.72:8501' HOST_ADDRESS = 'http://10.23.10.72:8501'
#Contracts info # Contracts info
CONTRACTS = { CONTRACTS = {
'01':{ '01': {
'address': '0x88933138A9Ef6474ee4f255A6395210F4f014d6B', 'address': '0x88933138A9Ef6474ee4f255A6395210F4f014d6B',
'abi': [{"constant":True,"inputs":[{"name":"ots","type":"string"}],"name":"getHash","outputs":[{"name":"","type":"string"}],"payable":False,"stateMutability":"view","type":"function"},{"constant":True,"inputs":[{"name":"ots","type":"string"}],"name":"getBlockNumber","outputs":[{"name":"","type":"uint256"}],"payable":False,"stateMutability":"view","type":"function"},{"constant":True,"inputs":[{"name":"ots","type":"string"},{"name":"file_hash","type":"string"}],"name":"verify","outputs":[{"name":"","type":"bool"}],"payable":False,"stateMutability":"view","type":"function"},{"constant":False,"inputs":[{"name":"ots","type":"string"},{"name":"file_hash","type":"string"}],"name":"stamp","outputs":[],"payable":False,"stateMutability":"nonpayable","type":"function"}], 'abi': [{"constant": True, "inputs": [{"name": "ots", "type": "string"}], "name": "getHash",
"outputs": [{"name": "", "type": "string"}], "payable": False, "stateMutability": "view",
"type": "function"},
{"constant": True, "inputs": [{"name": "ots", "type": "string"}], "name": "getBlockNumber",
"outputs": [{"name": "", "type": "uint256"}], "payable": False, "stateMutability": "view",
"type": "function"}, {"constant": True, "inputs": [{"name": "ots", "type": "string"},
{"name": "file_hash", "type": "string"}],
"name": "verify", "outputs": [{"name": "", "type": "bool"}], "payable": False,
"stateMutability": "view", "type": "function"}, {"constant": False, "inputs": [
{"name": "ots", "type": "string"}, {"name": "file_hash", "type": "string"}], "name": "stamp",
"outputs": [], "payable": False,
"stateMutability": "nonpayable",
"type": "function"}],
}, },
} }
...@@ -33,4 +47,21 @@ PERMANENT_OTS_PREFIX = '1x' ...@@ -33,4 +47,21 @@ PERMANENT_OTS_PREFIX = '1x'
GAS = 250000 GAS = 250000
GAS_PRICE = 1 GAS_PRICE = 1
SENTRY_URL = 'http://2ec54beaab3b4459a1e5afadea070f98:92aee3f5d91e4e66996a3e0792985541@172.17.30.21:9000/54' SENTRY_URL = 'http://2ec54beaab3b4459a1e5afadea070f98:92aee3f5d91e4e66996a3e0792985541@172.17.30.21:9000/54'
\ No newline at end of file
MEMCACHED_HOST = '172.17.0.1'
MEMCACHED_PORT = 11211 # TIENE QUE SER INT
PENDING_TXS_MEMCACHED_KEY = 'pending_txs'
TEST_MEMCACHED_HOST = MEMCACHED_HOST
TEST_MEMCACHED_PORT = MEMCACHED_PORT
TEST_PENDING_TXS_MEMCACHED_KEY = 'test_pending_txs'
ELASTIC_APM = {
'SERVICE_NAME': 'TsaApi-dev',
'SERVER_URL': 'http://10.1.100.67:8200',
'DEBUG': True,
}
...@@ -8,6 +8,7 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY ...@@ -8,6 +8,7 @@ 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/ You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/
""" """
APP_ROOT = '/var/www/tsaapi.pp.bfa.local/' APP_ROOT = '/var/www/tsaapi.pp.bfa.local/'
LOG_ROOT = APP_ROOT + 'logs/'
STATIC_ROOT = APP_ROOT + 'html/' STATIC_ROOT = APP_ROOT + 'html/'
STATIC_URL = '/tsa/static/' STATIC_URL = '/tsa/static/'
...@@ -32,4 +33,10 @@ PERMANENT_OTS_PREFIX = '1x' ...@@ -32,4 +33,10 @@ PERMANENT_OTS_PREFIX = '1x'
GAS = 250000 GAS = 250000
GAS_PRICE = 1 GAS_PRICE = 1
SENTRY_URL = 'http://aa30f3ecd3bb4923a7046f197da61e07:47cd9f9ea1e243f6a9f47e2aa40b85c3@10.1.100.118:9000/82' SENTRY_URL = 'http://aa30f3ecd3bb4923a7046f197da61e07:47cd9f9ea1e243f6a9f47e2aa40b85c3@10.1.100.118:9000/82'
\ No newline at end of file
ELASTIC_APM = {
'SERVICE_NAME': 'TsaApi-pp',
'SERVER_URL': 'http://10.20.99.87:8200',
'DEBUG': True,
}
...@@ -41,4 +41,10 @@ PERMANENT_OTS_PREFIX = '1x' ...@@ -41,4 +41,10 @@ PERMANENT_OTS_PREFIX = '1x'
GAS = 250000 GAS = 250000
GAS_PRICE = 100000000 GAS_PRICE = 100000000
SENTRY_URL = 'http://36f4f241a007447d8a4441a13055b110:136cb6d1af854b869d13610603fa7321@10.1.100.118:9000/84' SENTRY_URL = 'http://36f4f241a007447d8a4441a13055b110:136cb6d1af854b869d13610603fa7321@10.1.100.118:9000/84'
\ No newline at end of file
ELASTIC_APM = {
'SERVICE_NAME': 'TsaApi',
'SERVER_URL': 'http://10.20.99.87:8200',
'DEBUG': False,
}
...@@ -8,6 +8,8 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY ...@@ -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/ 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 -*- # -*- coding: utf-8 -*-
import sys
""" """
Django settings for TsaApi project. Django settings for TsaApi project.
...@@ -24,11 +26,9 @@ import os ...@@ -24,11 +26,9 @@ import os
import datetime import datetime
import raven import raven
# Build paths inside the project like this: os.path.join(BASE_DIR, ...) # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production # Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
...@@ -40,7 +40,6 @@ DEBUG = True ...@@ -40,7 +40,6 @@ DEBUG = True
ALLOWED_HOSTS = ['*'] ALLOWED_HOSTS = ['*']
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
...@@ -54,9 +53,12 @@ INSTALLED_APPS = [ ...@@ -54,9 +53,12 @@ INSTALLED_APPS = [
'raven.contrib.django.raven_compat', 'raven.contrib.django.raven_compat',
'corsheaders', 'corsheaders',
'rest_framework_swagger', 'rest_framework_swagger',
'elasticapm.contrib.django',
'app'
] ]
MIDDLEWARE = [ MIDDLEWARE = [
'elasticapm.contrib.django.middleware.TracingMiddleware',
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
...@@ -89,7 +91,6 @@ TEMPLATES = [ ...@@ -89,7 +91,6 @@ TEMPLATES = [
WSGI_APPLICATION = 'TsaApi.wsgi.application' WSGI_APPLICATION = 'TsaApi.wsgi.application'
# Database # Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases # https://docs.djangoproject.com/en/1.11/ref/settings/#databases
...@@ -100,7 +101,6 @@ DATABASES = { ...@@ -100,7 +101,6 @@ DATABASES = {
} }
} }
# Password validation # Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
...@@ -125,6 +125,7 @@ AUTH_PASSWORD_VALIDATORS = [ ...@@ -125,6 +125,7 @@ AUTH_PASSWORD_VALIDATORS = [
LANGUAGE_CODE = 'es' LANGUAGE_CODE = 'es'
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
LANGUAGES = ( LANGUAGES = (
('es', _('Spanish')), ('es', _('Spanish')),
) )
...@@ -141,14 +142,11 @@ USE_L10N = True ...@@ -141,14 +142,11 @@ USE_L10N = True
USE_TZ = True USE_TZ = True
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/ # https://docs.djangoproject.com/en/1.11/howto/static-files/
STATIC_URL = '/static/' STATIC_URL = '/static/'
# Habilitar para permitir la autenticación # Habilitar para permitir la autenticación
REST_FRAMEWORK = { REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': ( 'DEFAULT_PERMISSION_CLASSES': (
...@@ -161,8 +159,27 @@ REST_FRAMEWORK = { ...@@ -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' 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: try:
from TsaApi.local_settings import * from TsaApi.local_settings import *
except ImportError as e: except ImportError as e:
...@@ -170,4 +187,63 @@ except ImportError as e: ...@@ -170,4 +187,63 @@ except ImportError as e:
RAVEN_CONFIG = { RAVEN_CONFIG = {
'dsn': SENTRY_URL, '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': 'ERROR',
'propagate': True,
},
'console-logger': {
'handlers': ['console', 'info_file', 'error_file', 'sentry'],
'progagate': True,
'level': 'DEBUG'
}
},
}
import logging
from django.core.management import BaseCommand
from app.services import TransactionUnstucker, MemcachedStorage
from TsaApi.settings import GAS_PRICE
from TsaApi.settings import PENDING_TXS_MEMCACHED_KEY
class Command(BaseCommand):
unstucker = TransactionUnstucker()
logger = logging.getLogger('cmd')
storage = MemcachedStorage()
def handle(self, *args, **options):
try:
self.storage.store([])
print("Memcached sobre key {} cambiado a {}".format(PENDING_TXS_MEMCACHED_KEY,self.storage.retrieve()))
except Exception as e:
self.logger.error(str(e))
print("Hubo un error {}".format(str(e)))
import logging
from django.core.management import BaseCommand
from app.services import TransactionUnstucker, MemcachedStorage
from TsaApi.settings import GAS_PRICE
class Command(BaseCommand):
unstucker = TransactionUnstucker()
logger = logging.getLogger('cmd')
storage = MemcachedStorage()
def handle(self, *args, **options):
print(self.storage.retrieve())
import logging
from django.core.management import BaseCommand
from app.managers import EthereumGateway
from app.services import TransactionUnstucker
from TsaApi.settings import GAS_PRICE
import logging
class Command(BaseCommand):
unstucker = TransactionUnstucker(logging.getLogger('console-logger'))
def handle(self, *args, **options):
self.unstucker.unstuck_pending_transactions(GAS_PRICE)
...@@ -16,6 +16,8 @@ from TsaApi.settings import HOST_ADDRESS ...@@ -16,6 +16,8 @@ from TsaApi.settings import HOST_ADDRESS
from web3.middleware import geth_poa_middleware from web3.middleware import geth_poa_middleware
import requests import requests
from app.utils import Utils
class TimestampManager(models.Manager): class TimestampManager(models.Manager):
...@@ -43,7 +45,8 @@ class TimestampManager(models.Manager): ...@@ -43,7 +45,8 @@ class TimestampManager(models.Manager):
def get_contract(contract_version): def get_contract(contract_version):
web3 = TimestampManager.get_provider() 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 @staticmethod
def get_block(block_number): def get_block(block_number):
...@@ -82,11 +85,54 @@ class TimestampManager(models.Manager): ...@@ -82,11 +85,54 @@ class TimestampManager(models.Manager):
headers = {'Content-Type': 'application/json', 'accept': 'application/json'} 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']) return len(response.json()['result'])
@staticmethod @staticmethod
def get_last_block_number(): def get_last_block_number():
web3 = TimestampManager.get_provider() web3 = TimestampManager.get_provider()
return web3.eth.getBlock('latest').number return web3.eth.getBlock('latest').number
\ No newline at end of file
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 seald 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)
\ No newline at end of file
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
if stdout.isatty():
log_str = 'console-logger'
else:
log_str = 'logger'
module_logger = logging.getLogger(log_str)
class DefinitiveReceiptGenerator:
encoder = Base64EncodingService()
def generate_definitive_receipt(self, original_file_hash, ots_hash, tx_hash, block):
base64.b64encode(
Utils.get_permanent_ots(original_file_hash, ots_hash, tx_hash,
block.number).encode('utf-8')).decode('utf-8')
class ReceiptInterpreter:
def interpret(self, ots):
return ots.split('-')
class TransactionInputVerification:
gateway = EthereumGateway()
interpreter = ReceiptInterpreter()
ts = TimeStamp()
tx_hash = ''
def verify(self, 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()
def verify(self, ots):
ots_version, file_hash, ots_hash, tx_hash, block_number = self.interpreter.interpret(ots)
self.timestamp.verify(ots_hash, file_hash)
class VerifyService:
receipt_generator = DefinitiveReceiptGenerator()
definitive_verificator = TransactionInputVerification()
gateway = EthereumGateway()
permanent_ots_prefix = ''
def verify(self, original_file_hash, ots):
if ots[:2] == self.permanent_ots_prefix:
if self.definitive_verificator.verify(ots):
block = TimestampManager.get_block(int(block_number))
return {
'status': 'success',
'messages': ('file_uploaded') % (str(original_file_hash), str(block.number),
str(Utils.datetime_from_timestamp(block.timestamp)))}
else:
return {'status': 'failure', 'messages': 'file_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 {'status': 'pending', 'messages': 'transaction_pending'}
else:
block = TimestampManager.get_block(
TimestampManager.get_block_number(contract_version, ots_hash))
return {'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'),
'messages': 'file_uploaded' % (
original_file_hash, str(block.number), str(
Utils.datetime_from_timestamp(block.timestamp)))}
else:
try:
if transaction and not transaction.blockNumber:
return {'status': 'pending', 'messages': 'transaction_pending'}
except ValueError:
pass
return {'status': 'failure', 'messages': 'file_not_found'}
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
...@@ -9,13 +9,17 @@ You should have received a copy of the GNU General Public License along with thi ...@@ -9,13 +9,17 @@ You should have received a copy of the GNU General Public License along with thi
""" """
from django.test import TestCase from django.test import TestCase
from django.test import Client from django.test import Client
from eth_account.datastructures import AttributeDict
from pymemcache.client import base
from rest_framework import status from rest_framework import status
from TsaApi.settings import TEST_MEMCACHED_HOST, TEST_MEMCACHED_PORT, TEST_PENDING_TXS_MEMCACHED_KEY
from app.services import PendingTransactionsService, CheckCriteria, MemcachedStorage, TransactionUnstucker
class TimestampTest(TestCase): class TimestampTest(TestCase):
def test_verify(self): def test_verify(self):
c = Client() c = Client()
response = c.post('/api/tsa/verify/', { response = c.post('/api/tsa/verify/', {
"file_hash": "1957db7fe23e4be1740ddeb941ddda7ae0a6b782e536a9e00b5aa82db1e84547", "file_hash": "1957db7fe23e4be1740ddeb941ddda7ae0a6b782e536a9e00b5aa82db1e84547",
...@@ -25,10 +29,334 @@ class TimestampTest(TestCase): ...@@ -25,10 +29,334 @@ class TimestampTest(TestCase):
self.assertEqual(response.status_code, status.HTTP_200_OK, 'Verify Error') self.assertEqual(response.status_code, status.HTTP_200_OK, 'Verify Error')
def test_stamp(self): def test_stamp(self):
c = Client() c = Client()
response = c.post('/api/tsa/stamp/', { response = c.post('/api/tsa/stamp/', {
"file_hash": "1957db7fe23e4be1740ddeb941ddda7ae0a6b782e536a9e00b5aa82db1e84547" "file_hash": "1957db7fe23e4be1740ddeb941ddda7ae0a6b782e536a9e00b5aa82db1e84547"
}) })
self.assertEqual(response.status_code, status.HTTP_200_OK, 'Stamp Error') self.assertEqual(response.status_code, status.HTTP_200_OK, 'Stamp Error')
\ No newline at end of file
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(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(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(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
...@@ -7,6 +7,7 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY ...@@ -7,6 +7,7 @@ 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/ 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 datetime import datetime
import hashlib import hashlib
import time import time
...@@ -16,7 +17,7 @@ from ethereum.abi import (decode_abi, normalize_name as normalize_abi_method_nam ...@@ -16,7 +17,7 @@ from ethereum.abi import (decode_abi, normalize_name as normalize_abi_method_nam
from ethereum.utils import encode_int, zpad, decode_hex from ethereum.utils import encode_int, zpad, decode_hex
class Utils(): class Utils:
@staticmethod @staticmethod
def sha256_encode(string): def sha256_encode(string):
return hashlib.sha256(string.encode('utf-8')).hexdigest() return hashlib.sha256(string.encode('utf-8')).hexdigest()
...@@ -27,8 +28,10 @@ class Utils(): ...@@ -27,8 +28,10 @@ class Utils():
@staticmethod @staticmethod
def get_ots_hash(file_hash): def get_ots_hash(file_hash):
#El ots se genera con los hashes sha256 de (archivo original+timestamp+dirección de la cuenta) + versión del contrato # El ots se genera con los hashes sha256 de (archivo original+timestamp+dirección de la cuenta) + versión del
return Utils.sha256_encode(str(file_hash + Utils.sha256_encode(str(int(time.time()))) + Utils.sha256_encode(ACCOUNT_ADDRESS))) + CURRENT_CONTRACT_VERSION # contrato
return Utils.sha256_encode(str(file_hash + Utils.sha256_encode(str(int(time.time()))) + Utils.sha256_encode(
ACCOUNT_ADDRESS))) + CURRENT_CONTRACT_VERSION
@staticmethod @staticmethod
def decode_contract_call(contract_abi, call_data): def decode_contract_call(contract_abi, call_data):
...@@ -58,4 +61,9 @@ class Utils(): ...@@ -58,4 +61,9 @@ class Utils():
@staticmethod @staticmethod
def get_temporary_ots(ots_hash, tx_hash): def get_temporary_ots(ots_hash, tx_hash):
return TEMPORARY_OTS_PREFIX + '-' + ots_hash + '-' + tx_hash return TEMPORARY_OTS_PREFIX + '-' + ots_hash + '-' + tx_hash
\ No newline at end of file
class Base64EncodingService:
def encode(self, stuff):
return base64.b64encode(stuff)
\ No newline at end of file
...@@ -16,12 +16,13 @@ from rest_framework.response import Response ...@@ -16,12 +16,13 @@ from rest_framework.response import Response
from web3.exceptions import CannotHandleRequest from web3.exceptions import CannotHandleRequest
from raven.contrib.django.raven_compat.models import client from raven.contrib.django.raven_compat.models import client
from rest_framework.schemas import ManualSchema from rest_framework.schemas import ManualSchema
import coreschema,coreapi import coreschema, coreapi
from app.managers import TimestampManager from app.managers import TimestampManager
from app.utils import Utils from app.utils import Utils
from TsaApi.local_settings import TEMPORARY_OTS_PREFIX, PERMANENT_OTS_PREFIX, CONTRACTS from TsaApi.local_settings import TEMPORARY_OTS_PREFIX, PERMANENT_OTS_PREFIX, CONTRACTS
class Stamp(APIView): class Stamp(APIView):
""" """
[POST] [POST]
...@@ -47,7 +48,6 @@ class Stamp(APIView): ...@@ -47,7 +48,6 @@ class Stamp(APIView):
schema=coreschema.String(), schema=coreschema.String(),
description='El hash del archivo encodeado en sha256', description='El hash del archivo encodeado en sha256',
), ),
]) ])
...@@ -63,18 +63,23 @@ class Stamp(APIView): ...@@ -63,18 +63,23 @@ class Stamp(APIView):
tx_hash = TimestampManager.stamp(ots_hash, file_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 # 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()) 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: 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: 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: except Exception as e:
client.captureException() 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): class Verify(APIView):
...@@ -130,7 +135,8 @@ class Verify(APIView): ...@@ -130,7 +135,8 @@ class Verify(APIView):
ots_version, file_hash, ots_hash, tx_hash, block_number = ots.split('-') 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) 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') == file_hash: if args[0].decode('utf-8') == ots_hash and args[1].decode('utf-8') == file_hash:
...@@ -140,7 +146,8 @@ class Verify(APIView): ...@@ -140,7 +146,8 @@ class Verify(APIView):
_('permanent_rd'): base64.b64encode(Utils.get_permanent_ots(original_file_hash, ots_hash, tx_hash, block.number).encode('utf-8')).decode('utf-8'), _('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)), _('attestation_time'): str(Utils.datetime_from_timestamp(block.timestamp)),
_('messages'): _('file_uploaded') % ( _('messages'): _('file_uploaded') % (
file_hash, str(block.number), str(Utils.datetime_from_timestamp(block.timestamp)))}, file_hash, str(block.number),
str(Utils.datetime_from_timestamp(block.timestamp)))},
status=status.HTTP_200_OK) status=status.HTTP_200_OK)
else: else:
return Response({_('status'): _('failure'), _('messages'): _('file_not_found')}, return Response({_('status'): _('failure'), _('messages'): _('file_not_found')},
...@@ -156,12 +163,15 @@ class Verify(APIView): ...@@ -156,12 +163,15 @@ class Verify(APIView):
if TimestampManager.verify(contract_version, ots_hash, original_file_hash): 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()): if (
TimestampManager.get_last_block_number() - transaction.blockNumber) < Utils.required_block_difference(
TimestampManager.get_signers_count()):
return Response({_('status'): _('pending'), _('messages'): _('transaction_pending')}, return Response({_('status'): _('pending'), _('messages'): _('transaction_pending')},
status=status.HTTP_200_OK) status=status.HTTP_200_OK)
else: else:
block = TimestampManager.get_block(TimestampManager.get_block_number(contract_version, ots_hash)) block = TimestampManager.get_block(
TimestampManager.get_block_number(contract_version, ots_hash))
return Response({_('status'): _('success'), 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'), _('permanent_rd'): base64.b64encode(Utils.get_permanent_ots(original_file_hash, ots_hash, tx_hash, block.number).encode('utf-8')).decode('utf-8'),
...@@ -171,16 +181,21 @@ class Verify(APIView): ...@@ -171,16 +181,21 @@ class Verify(APIView):
else: else:
try: try:
if transaction and not transaction.blockNumber: if transaction and not transaction.blockNumber:
return Response({_('status'): _('pending'), _('messages'): _('transaction_pending')}, status=status.HTTP_200_OK) return Response({_('status'): _('pending'), _('messages'): _('transaction_pending')},
status=status.HTTP_200_OK)
except ValueError: except ValueError:
pass pass
return Response({_('status'): _('failure'), _('messages'): _('file_not_found')},status=status.HTTP_404_NOT_FOUND) return Response({_('status'): _('failure'), _('messages'): _('file_not_found')},
status=status.HTTP_404_NOT_FOUND)
except ValidationError as e: 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: 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: except Exception as e:
client.captureException() client.captureException()
return Response({_('status'): _('failure'), _('messages'): _('operation_failed')}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) return Response({_('status'): _('failure'), _('messages'): _('operation_failed')},
\ No newline at end of file status=status.HTTP_500_INTERNAL_SERVER_ERROR)
\ No newline at end of file
version: '3.1'
services:
memcached:
image: memcached
container_name: memcached
ports:
- "11211:11211"
\ No newline at end of file
asn1crypto==0.24.0
attrdict==2.0.0 attrdict==2.0.0
backcall==0.1.0
certifi==2018.4.16 certifi==2018.4.16
cffi==1.12.0
chardet==3.0.4 chardet==3.0.4
coincurve==11.0.0
coreapi==2.3.3 coreapi==2.3.3
coreschema==0.0.4 coreschema==0.0.4
cytoolz==0.9.0.1 cytoolz==0.9.0.1
decorator==4.3.2
Django==2.1 Django==2.1
django-cors-headers==2.4.0 django-cors-headers==2.4.0
django-rest-swagger==2.2.0 django-rest-swagger==2.2.0
...@@ -15,27 +20,50 @@ eth-hash==0.1.4 ...@@ -15,27 +20,50 @@ eth-hash==0.1.4
eth-keyfile==0.5.1 eth-keyfile==0.5.1
eth-keys==0.2.0b3 eth-keys==0.2.0b3
eth-rlp==0.1.2 eth-rlp==0.1.2
eth-typing==2.0.0
eth-utils==1.0.3 eth-utils==1.0.3
ethereum==2.3.2
future==0.17.1
hexbytes==0.1.0 hexbytes==0.1.0
idna==2.7 idna==2.7
ipython==7.2.0
ipython-genutils==0.2.0
itypes==1.1.0 itypes==1.1.0
jedi==0.13.2
Jinja2==2.10 Jinja2==2.10
lru-dict==1.1.6 lru-dict==1.1.6
MarkupSafe==1.0 MarkupSafe==1.0
openapi-codec==1.3.2 openapi-codec==1.3.2
parsimonious==0.8.0 parsimonious==0.8.0
pkg-resources==0.0.0 parso==0.3.4
pbkdf2==1.3
pexpect==4.6.0
pickleshare==0.7.5
prompt-toolkit==2.0.8
ptyprocess==0.6.0
py-ecc==1.4.7
pycparser==2.19
pycryptodome==3.6.4 pycryptodome==3.6.4
pyethash==0.1.27
Pygments==2.3.1
PyJWT==1.6.4 PyJWT==1.6.4
pymemcache==2.1.1
pysha3==1.0.2
python-bitcoinlib==0.10.1 python-bitcoinlib==0.10.1
pytz==2018.5 pytz==2018.5
PyYAML==4.2b4
raven==6.9.0 raven==6.9.0
repoze.lru==0.7
requests==2.19.1 requests==2.19.1
rlp==1.0.1 rlp==1.0.1
scrypt==0.8.13
simplejson==3.16.0 simplejson==3.16.0
six==1.11.0 six==1.11.0
toolz==0.9.0 toolz==0.9.0
traitlets==4.3.2
uritemplate==3.0.0 uritemplate==3.0.0
urllib3==1.23 urllib3==1.23
wcwidth==0.1.7
web3==4.5.0 web3==4.5.0
websockets==5.0.1 websockets==5.0.1
elastic-apm==3.0.0
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment