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

Resuelvo el merge con la posta siendo lo de master

parents 94b53cf9 acb75314
No related branches found
No related tags found
No related merge requests found
Showing
with 861 additions and 104 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
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
File deleted
File deleted
File deleted
File deleted
File deleted
......@@ -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'
}
},
}
File deleted
File deleted
File deleted
File deleted
File deleted
File deleted
......@@ -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)
No preview for this file type
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
......@@ -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')
......@@ -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():
......
......@@ -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)
pyethereum @ d962694b
Subproject commit d962694be03686a8e5c1d7459ae272b70a5c9f77
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