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