Skip to content
Snippets Groups Projects
Commit 7dab3372 authored by Patricio Kumagae's avatar Patricio Kumagae
Browse files

Nuevo revert

parent 8fa5c054
No related branches found
No related tags found
No related merge requests found
......@@ -14,13 +14,11 @@ pip install -r requirements.txt
```
- Crear un link simbólico al archivo local_settings.py desde el local settings del entorno en que se encuentre el proyecto
- Ejecutar:
- sudo apt-get install libssl-dev build-essential automake pkg-config libtool libffi-dev libgmp-dev libyaml-cpp-dev memcached
- sudo apt-get install libssl-dev build-essential automake pkg-config libtool libffi-dev libgmp-dev libyaml-cpp-dev
- git clone https://github.com/ethereum/pyethereum/
- cd pyethereum
- python setup.py install
- Poner en crontab el sig comando reemplazando donde corresponde los directorios
/directorio/del/venv/bin/python /directorio/de/aplicacion/manage.py desatascar_pendientes > /dev/null
### Utilización
- Stamp
- Método: POST
......
......@@ -7,9 +7,7 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY
You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/
"""
APP_ROOT = '/opt/project/'
LOG_ROOT = APP_ROOT + 'logs/'
APP_ROOT = '/home/python/TsaAPI/'
STATIC_ROOT = APP_ROOT + 'html/'
......@@ -17,23 +15,11 @@ ACCOUNT_ADDRESS = '0x25c185dcaed065bac30a0a1cd0688a7aa02896d7'
HOST_ADDRESS = 'http://10.23.10.72:8501'
# Contracts info
#Contracts info
CONTRACTS = {
'01': {
'01':{
'address': '0x88933138A9Ef6474ee4f255A6395210F4f014d6B',
'abi': [{"constant": True, "inputs": [{"name": "ots", "type": "string"}], "name": "getHash",
"outputs": [{"name": "", "type": "string"}], "payable": False, "stateMutability": "view",
"type": "function"},
{"constant": True, "inputs": [{"name": "ots", "type": "string"}], "name": "getBlockNumber",
"outputs": [{"name": "", "type": "uint256"}], "payable": False, "stateMutability": "view",
"type": "function"}, {"constant": True, "inputs": [{"name": "ots", "type": "string"},
{"name": "file_hash", "type": "string"}],
"name": "verify", "outputs": [{"name": "", "type": "bool"}], "payable": False,
"stateMutability": "view", "type": "function"}, {"constant": False, "inputs": [
{"name": "ots", "type": "string"}, {"name": "file_hash", "type": "string"}], "name": "stamp",
"outputs": [], "payable": False,
"stateMutability": "nonpayable",
"type": "function"}],
'abi': [{"constant":True,"inputs":[{"name":"ots","type":"string"}],"name":"getHash","outputs":[{"name":"","type":"string"}],"payable":False,"stateMutability":"view","type":"function"},{"constant":True,"inputs":[{"name":"ots","type":"string"}],"name":"getBlockNumber","outputs":[{"name":"","type":"uint256"}],"payable":False,"stateMutability":"view","type":"function"},{"constant":True,"inputs":[{"name":"ots","type":"string"},{"name":"file_hash","type":"string"}],"name":"verify","outputs":[{"name":"","type":"bool"}],"payable":False,"stateMutability":"view","type":"function"},{"constant":False,"inputs":[{"name":"ots","type":"string"},{"name":"file_hash","type":"string"}],"name":"stamp","outputs":[],"payable":False,"stateMutability":"nonpayable","type":"function"}],
},
}
......@@ -47,21 +33,4 @@ PERMANENT_OTS_PREFIX = '1x'
GAS = 250000
GAS_PRICE = 1
SENTRY_URL = 'http://2ec54beaab3b4459a1e5afadea070f98:92aee3f5d91e4e66996a3e0792985541@172.17.30.21:9000/54'
MEMCACHED_HOST = '172.17.0.1'
MEMCACHED_PORT = 11211 # TIENE QUE SER INT
PENDING_TXS_MEMCACHED_KEY = 'pending_txs'
TEST_MEMCACHED_HOST = MEMCACHED_HOST
TEST_MEMCACHED_PORT = MEMCACHED_PORT
TEST_PENDING_TXS_MEMCACHED_KEY = 'test_pending_txs'
ELASTIC_APM = {
'SERVICE_NAME': 'TsaApi-dev',
'SERVER_URL': 'http://10.1.100.67:8200',
'DEBUG': True,
}
SENTRY_URL = 'http://2ec54beaab3b4459a1e5afadea070f98:92aee3f5d91e4e66996a3e0792985541@172.17.30.21:9000/54'
\ No newline at end of file
......@@ -8,7 +8,6 @@ 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/
"""
APP_ROOT = '/var/www/tsaapi.pp.bfa.local/'
LOG_ROOT = APP_ROOT + 'logs/'
STATIC_ROOT = APP_ROOT + 'html/'
STATIC_URL = '/tsa/static/'
......@@ -33,10 +32,4 @@ PERMANENT_OTS_PREFIX = '1x'
GAS = 250000
GAS_PRICE = 1
SENTRY_URL = 'http://aa30f3ecd3bb4923a7046f197da61e07:47cd9f9ea1e243f6a9f47e2aa40b85c3@10.1.100.118:9000/82'
ELASTIC_APM = {
'SERVICE_NAME': 'TsaApi-pp',
'SERVER_URL': 'http://10.20.99.87:8200',
'DEBUG': True,
}
SENTRY_URL = 'http://aa30f3ecd3bb4923a7046f197da61e07:47cd9f9ea1e243f6a9f47e2aa40b85c3@10.1.100.118:9000/82'
\ No newline at end of file
......@@ -41,10 +41,4 @@ PERMANENT_OTS_PREFIX = '1x'
GAS = 250000
GAS_PRICE = 100000000
SENTRY_URL = 'http://36f4f241a007447d8a4441a13055b110:136cb6d1af854b869d13610603fa7321@10.1.100.118:9000/84'
ELASTIC_APM = {
'SERVICE_NAME': 'TsaApi',
'SERVER_URL': 'http://10.20.99.87:8200',
'DEBUG': False,
}
SENTRY_URL = 'http://36f4f241a007447d8a4441a13055b110:136cb6d1af854b869d13610603fa7321@10.1.100.118:9000/84'
\ No newline at end of file
......@@ -8,8 +8,6 @@ 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.
......@@ -26,9 +24,11 @@ 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,6 +40,7 @@ DEBUG = True
ALLOWED_HOSTS = ['*']
# Application definition
INSTALLED_APPS = [
......@@ -53,12 +54,9 @@ 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',
......@@ -91,6 +89,7 @@ TEMPLATES = [
WSGI_APPLICATION = 'TsaApi.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
......@@ -101,6 +100,7 @@ DATABASES = {
}
}
# Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
......@@ -125,7 +125,6 @@ AUTH_PASSWORD_VALIDATORS = [
LANGUAGE_CODE = 'es'
from django.utils.translation import ugettext_lazy as _
LANGUAGES = (
('es', _('Spanish')),
)
......@@ -142,11 +141,14 @@ 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': (
......@@ -159,27 +161,8 @@ 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:
......@@ -187,63 +170,4 @@ except ImportError as e:
RAVEN_CONFIG = {
'dsn': SENTRY_URL,
}
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'detailed': {
'format': '%(levelname)s %(asctime)s %(filename)s %(funcName)s %(lineno)d %(message)s',
},
'history': {
'format': '%(asctime)s %(message)s'
}
},
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'stream': sys.stdout,
'formatter': 'detailed'
},
'info_file': {
'level': 'INFO',
'class': 'logging.FileHandler',
'filename': LOG_ROOT + 'info.log',
'formatter': 'history',
},
'error_file': {
'level': 'ERROR',
'class': 'logging.FileHandler',
'filename': LOG_ROOT + 'error.log',
'formatter': 'detailed',
},
'sentry': {
'level': 'ERROR', # To capture more than ERROR, change to WARNING, INFO, etc.
'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler',
}
},
'loggers': {
'django': {
'handlers': ['error_file', 'info_file', 'sentry'],
'level': 'WARNING',
'propagate': True,
},
'raven': {
'level': 'WARNING',
'handlers': ['sentry'],
'propagate': False,
},
'logger': {
'handlers': ['error_file', 'info_file', 'sentry'],
'level': 'ERROR',
'propagate': True,
},
'console-logger': {
'handlers': ['console', 'info_file', 'error_file', 'sentry'],
'progagate': True,
'level': 'DEBUG'
}
},
}
}
\ No newline at end of file
import logging
from django.core.management import BaseCommand
from app.services import TransactionUnstucker, MemcachedStorage
from TsaApi.settings import GAS_PRICE
from TsaApi.settings import PENDING_TXS_MEMCACHED_KEY
class Command(BaseCommand):
unstucker = TransactionUnstucker()
logger = logging.getLogger('cmd')
storage = MemcachedStorage()
def handle(self, *args, **options):
try:
self.storage.store([])
print("Memcached sobre key {} cambiado a {}".format(PENDING_TXS_MEMCACHED_KEY,self.storage.retrieve()))
except Exception as e:
self.logger.error(str(e))
print("Hubo un error {}".format(str(e)))
import logging
from django.core.management import BaseCommand
from app.services import TransactionUnstucker, MemcachedStorage
from TsaApi.settings import GAS_PRICE
class Command(BaseCommand):
unstucker = TransactionUnstucker()
logger = logging.getLogger('cmd')
storage = MemcachedStorage()
def handle(self, *args, **options):
print(self.storage.retrieve())
import logging
from django.core.management import BaseCommand
from app.managers import EthereumGateway
from app.services import TransactionUnstucker
from TsaApi.settings import GAS_PRICE
import logging
class Command(BaseCommand):
unstucker = TransactionUnstucker(logging.getLogger('console-logger'))
def handle(self, *args, **options):
self.unstucker.unstuck_pending_transactions(GAS_PRICE)
......@@ -16,8 +16,6 @@ from TsaApi.settings import HOST_ADDRESS
from web3.middleware import geth_poa_middleware
import requests
from app.utils import Utils
class TimestampManager(models.Manager):
......@@ -45,8 +43,7 @@ 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):
......@@ -85,54 +82,11 @@ 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
class TxAlreadySealedInBlock(Exception):
pass
class EthereumGateway:
def transaction_is_canonical(self, transaction):
last_block_number = TimestampManager.get_last_block_number()
tx_block_number = transaction.blockNumber
required_block_difference = Utils.required_block_difference(TimestampManager.get_signers_count())
return (last_block_number - tx_block_number) < required_block_difference
def transaction(self, tx_hash):
return TimestampManager.get_transaction(tx_hash)
def pending_transactions(self):
w3 = TimestampManager.get_provider()
filt = w3.eth.filter('pending')
tx_hashes = w3.eth.getFilterChanges(filt.filter_id)
# ojo con lo de abajo, es costoso en tiempo, hace len(tx_hashes) conexiones al nodo, si es sobre HTTP es lento
txs = [w3.eth.getTransaction(i.hex()) for i in tx_hashes]
return txs
def block(self, block_number):
return TimestampManager.get_block(block_number)
def resend(self, tx, gas_price=None, gas=None):
w3 = TimestampManager.get_provider()
tx_2 = w3.eth.getTransaction(tx['hash'])
if tx_2['blockNumber'] is not None:
raise TxAlreadySealedInBlock("Tx already seald in block {}".format(str(tx_2['blockNumber'])))
return w3.eth.resend(tx, gas_price, gas)
class TimeStamp:
def abi(self):
return CONTRACTS[CURRENT_CONTRACT_VERSION]['abi']
def verify(self, proof_hash, file_hash):
return TimestampManager.verify(CURRENT_CONTRACT_VERSION, proof_hash, file_hash)
\ No newline at end of file
return web3.eth.getBlock('latest').number
\ No newline at end of file
import base64
from json import JSONDecodeError
from app.managers import TimestampManager, EthereumGateway, TimeStamp
from app.utils import Utils, Base64EncodingService
from pymemcache.client import base
from TsaApi.settings import MEMCACHED_HOST, MEMCACHED_PORT, PENDING_TXS_MEMCACHED_KEY
import logging
import json
from sys import stdout
if stdout.isatty():
log_str = 'console-logger'
else:
log_str = 'logger'
module_logger = logging.getLogger(log_str)
class DefinitiveReceiptGenerator:
encoder = Base64EncodingService()
def generate_definitive_receipt(self, original_file_hash, ots_hash, tx_hash, block):
base64.b64encode(
Utils.get_permanent_ots(original_file_hash, ots_hash, tx_hash,
block.number).encode('utf-8')).decode('utf-8')
class ReceiptInterpreter:
def interpret(self, ots):
return ots.split('-')
class TransactionInputVerification:
gateway = EthereumGateway()
interpreter = ReceiptInterpreter()
ts = TimeStamp()
tx_hash = ''
def verify(self, ots):
ots_version, file_hash, ots_hash, tx_hash, block_number = self.interpreter.interpret(ots)
method_name, args = Utils.decode_contract_call(self.ts.abi(), TimestampManager.get_transaction(tx_hash).input)
return args[0].decode('utf-8') == ots_hash and args[1].decode('utf-8') == file_hash
class SmartContractVerification:
timestamp = TimeStamp()
interpreter = ReceiptInterpreter()
def verify(self, ots):
ots_version, file_hash, ots_hash, tx_hash, block_number = self.interpreter.interpret(ots)
self.timestamp.verify(ots_hash, file_hash)
class VerifyService:
receipt_generator = DefinitiveReceiptGenerator()
definitive_verificator = TransactionInputVerification()
gateway = EthereumGateway()
permanent_ots_prefix = ''
def verify(self, original_file_hash, ots):
if ots[:2] == self.permanent_ots_prefix:
if self.definitive_verificator.verify(ots):
block = TimestampManager.get_block(int(block_number))
return {
'status': 'success',
'messages': ('file_uploaded') % (str(original_file_hash), str(block.number),
str(Utils.datetime_from_timestamp(block.timestamp)))}
else:
return {'status': 'failure', 'messages': 'file_not_found'}
else:
ots_version, ots_hash, tx_hash = ots.split('-')
transaction = TimestampManager.get_transaction(tx_hash)
contract_version = ots_hash[-2:]
if TimestampManager.verify(contract_version, ots_hash, original_file_hash):
if (
TimestampManager.get_last_block_number() - transaction.blockNumber) < Utils.required_block_difference(
TimestampManager.get_signers_count()):
return {'status': 'pending', 'messages': 'transaction_pending'}
else:
block = TimestampManager.get_block(
TimestampManager.get_block_number(contract_version, ots_hash))
return {'status': 'success',
'permanent_rd': base64.b64encode(
Utils.get_permanent_ots(original_file_hash, ots_hash, tx_hash,
block.number).encode('utf-8')).decode('utf-8'),
'messages': 'file_uploaded' % (
original_file_hash, str(block.number), str(
Utils.datetime_from_timestamp(block.timestamp)))}
else:
try:
if transaction and not transaction.blockNumber:
return {'status': 'pending', 'messages': 'transaction_pending'}
except ValueError:
pass
return {'status': 'failure', 'messages': 'file_not_found'}
class MemcachedStorage:
client = base.Client((MEMCACHED_HOST, MEMCACHED_PORT))
server_tuple = (MEMCACHED_HOST, MEMCACHED_PORT)
key = PENDING_TXS_MEMCACHED_KEY
logger = module_logger
def store(self, pending_txs):
jsoned = json.dumps(pending_txs)
self.client.set(self.key, jsoned)
def retrieve(self):
retrieved = self.client.get(self.key)
try:
decoded = retrieved.decode("utf-8")
interpretado = json.loads(decoded)
except AttributeError as ae:
self.logger.info("Memcached estaba vacío key: {} ; valor : {}".format(str(self.key), retrieved))
interpretado = []
except JSONDecodeError as jsonde:
self.logger.error(
"Se devolvera lista vacia. No se pudo decodear la string: {} {}".format(retrieved, str(jsonde)))
interpretado = []
return interpretado
class CheckCriteria:
storage = MemcachedStorage()
def __init__(self, logger=None):
if logger is not None:
self.logger = logger
def should_transactions_be_resent(self, stuck_txs):
stored_txs = self.storage.retrieve()
txs_dict = {}
for stx in stored_txs:
txs_dict[stx['hash']] = stx
res = False
for tx in stuck_txs:
res = res or tx['hash'] in txs_dict
return res
class PendingTransactionsService:
gateway = EthereumGateway()
criteria = CheckCriteria()
logger = module_logger
def __init__(self, logger=None):
if logger is not None:
self.logger = logger
def resend_transactions(self, txs, new_gas_price, new_gas_limit=None):
self.logger.info("Por evaluar si deberían ser reenviadas las transacciones")
new_txs = []
failed = []
if self.criteria.should_transactions_be_resent(txs): # obs: si es vacío entonces evalúa True
for tx in txs: # si es vacío este ciclo no hace nada
self.logger.info("Reenviando transaccion {}".format(str(tx)))
try:
new_txs.append(self.gateway.resend(tx, new_gas_price, new_gas_limit))
except Exception as e:
failed.append(tx)
self.logger.error('Error :{}'.format(str(e)))
return new_txs, failed
class TransactionUnstucker:
pending_transaction_service = PendingTransactionsService()
gateway = EthereumGateway()
storage = MemcachedStorage()
logger = module_logger
def __init__(self, logger=None):
if logger is not None:
self.logger = logger
self.pending_transaction_service.logger = logger
def unstuck_pending_transactions(self, new_gas_price, new_gas=None):
self.logger.info('Arranca proceso')
stuck_txs = self.gateway.pending_transactions()
self.logger.debug('Cant txs encontradas : {}'.format(len(stuck_txs)))
res, failed = self.pending_transaction_service.resend_transactions(stuck_txs, new_gas_price, new_gas)
self.logger.debug(
'Terminado, cant reenvios con exito: {} ; cant reenvios fallados: {}'.format(len(res), len(failed)))
nuevas_txs = self.gateway.pending_transactions()
self.logger.debug('Cant txs encontradas : {}'.format(len(nuevas_txs)))
self.storage.store(nuevas_txs)
self.logger.info('Termina proceso')
return res, failed
......@@ -9,17 +9,13 @@ You should have received a copy of the GNU General Public License along with thi
"""
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
from app.services import PendingTransactionsService, CheckCriteria, MemcachedStorage, TransactionUnstucker
class TimestampTest(TestCase):
def test_verify(self):
c = Client()
response = c.post('/api/tsa/verify/', {
"file_hash": "1957db7fe23e4be1740ddeb941ddda7ae0a6b782e536a9e00b5aa82db1e84547",
......@@ -29,334 +25,10 @@ class TimestampTest(TestCase):
self.assertEqual(response.status_code, status.HTTP_200_OK, 'Verify Error')
def test_stamp(self):
c = Client()
response = c.post('/api/tsa/stamp/', {
"file_hash": "1957db7fe23e4be1740ddeb941ddda7ae0a6b782e536a9e00b5aa82db1e84547"
})
self.assertEqual(response.status_code, status.HTTP_200_OK, 'Stamp Error')
class EthereumMocks():
class HalfResender:
balance = 2.1 * 21000 * 1000 # me da para 2 txs mockeadas
def resend(self, tx, gas_price, gas=None):
if gas is None:
gas = tx['gas']
costo = (gas_price * gas) + tx['value']
if self.balance > costo:
self.balance = self.balance - costo
return tx
raise Exception("Failing test")
class EthereumMock:
def __init__(self):
self.pending_txs = [
AttributeDict({
'blockHash': '0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd',
'blockNumber': None,
'from': '0xa1e4380a3b1f749673e270229993ee55f35663b4',
'gas': 21000,
'gasPrice': 500,
'hash': '0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060',
'input': '0x',
'nonce': 0,
'to': '0x5df9b87991262f6ba471f09758cde1c0fc1de734',
'transactionIndex': 0,
'value': 1000,
}),
AttributeDict({
'blockHash': '0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd',
'blockNumber': 0,
'from': '0xa1e4380a3b1f749673e270229993ee55f35663b4',
'gas': 21000,
'gasPrice': 500,
'hash': '0x6c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060',
'input': '0x',
'nonce': 0,
'to': '0x5df9b87991262f6ba471f09758cde1c0fc1de734',
'transactionIndex': 0,
'value': 1000,
}),
AttributeDict({
'blockHash': '0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd',
'blockNumber': 0,
'from': '0xa1e4380a3b1f749673e270229993ee55f35663b4',
'gas': 21000,
'gasPrice': 500,
'hash': '0x7d515ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060',
'input': '0x',
'nonce': 0,
'to': '0x5df9b87991262f6ba471f09758cde1c0fc1de734',
'transactionIndex': 0,
'value': 1000,
})
]
def resend(self, tx, gas_price, gas):
self.pending_txs.remove(tx)
return tx
def pending_transactions(self):
return self.pending_txs.copy()
class CriteriaMockTrue:
def should_transactions_be_resent(self, txs):
return True
class CriteriaMockFalse:
def should_transactions_be_resent(self, txs):
return False
class StorageMock:
def __init__(self, stored_txs):
self.stored_txs = stored_txs
def store(self, txs):
self.stored_txs = txs
def retrieve(self):
return self.stored_txs
class FailResender:
def resend(self, tx, gas_price, gas):
raise Exception("Failing test")
def generar_valores(self):
self.current_txs = \
[
AttributeDict({
'blockHash': '0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd',
'blockNumber': None,
'from': '0xa1e4380a3b1f749673e270229993ee55f35663b4',
'gas': 21000,
'gasPrice': 500,
'hash': '0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060',
'input': '0x',
'nonce': 0,
'to': '0x5df9b87991262f6ba471f09758cde1c0fc1de734',
'transactionIndex': 0,
'value': 1000,
}),
AttributeDict({
'blockHash': '0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd',
'blockNumber': 0,
'from': '0xa1e4380a3b1f749673e270229993ee55f35663b4',
'gas': 21000,
'gasPrice': 500,
'hash': '0x6c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060',
'input': '0x',
'nonce': 0,
'to': '0x5df9b87991262f6ba471f09758cde1c0fc1de734',
'transactionIndex': 0,
'value': 1000,
}),
AttributeDict({
'blockHash': '0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd',
'blockNumber': 0,
'from': '0xa1e4380a3b1f749673e270229993ee55f35663b4',
'gas': 21000,
'gasPrice': 500,
'hash': '0x7d515ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060',
'input': '0x',
'nonce': 0,
'to': '0x5df9b87991262f6ba471f09758cde1c0fc1de734',
'transactionIndex': 0,
'value': 1000,
})
]
self.stored_txs = \
[
AttributeDict({
'blockHash': '0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd',
'blockNumber': None,
'from': '0xa1e4380a3b1f749673e270229993ee55f35663b4',
'gas': 21000,
'gasPrice': 500,
'hash': '0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060',
'input': '0x',
'nonce': 0,
'to': '0x5df9b87991262f6ba471f09758cde1c0fc1de734',
'transactionIndex': 0,
'value': 31337,
}),
AttributeDict({
'blockHash': '0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd',
'blockNumber': 0,
'from': '0xa1e4380a3b1f749673e270229993ee55f35663b4',
'gas': 21000,
'gasPrice': 500,
'hash': '0x6c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060',
'input': '0x',
'nonce': 0,
'to': '0x5df9b87991262f6ba471f09758cde1c0fc1de734',
'transactionIndex': 0,
'value': 31337,
}),
AttributeDict({
'blockHash': '0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd',
'blockNumber': 0,
'from': '0xa1e4380a3b1f749673e270229993ee55f35663b4',
'gas': 21000,
'gasPrice': 500,
'hash': '0x6c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060',
'input': '0x',
'nonce': 0,
'to': '0x5df9b87991262f6ba471f09758cde1c0fc1de734',
'transactionIndex': 0,
'value': 31337,
})
]
class PendingTransactionTest(TestCase, EthereumMocks):
def setUp(self):
self.service = PendingTransactionsService()
self.generar_valores()
def test_verify_criteria_affirmative(self):
self.service.criteria = self.CriteriaMockTrue()
self.service.gateway = self.EthereumMock()
mock_txs = self.current_txs
result, failed = self.service.resend_transactions(mock_txs, 1000, )
self.assertEqual(result, mock_txs)
self.assertEqual(failed, [])
def test_verify_criteria_false(self):
self.service.criteria = self.CriteriaMockFalse()
self.service.gateway = self.EthereumMock()
mock_txs = self.current_txs
result, failed = self.service.resend_transactions(mock_txs, 1000, )
self.assertEqual(result, [])
self.assertEqual(failed, [])
def test_resend(self):
criteria = CheckCriteria()
criteria.storage = self.StorageMock(self.stored_txs)
self.service.criteria = criteria
self.service.gateway = self.EthereumMock()
txs = self.current_txs
result, failed = self.service.resend_transactions(txs, 1000, )
self.assertEqual(result, txs)
self.assertEqual(failed, [])
def test_resend_everything_failed(self):
criteria = CheckCriteria()
criteria.storage = self.StorageMock(self.stored_txs)
self.service.criteria = criteria
self.service.gateway = self.FailResender()
txs = self.current_txs
result, failed = self.service.resend_transactions(txs, 1000, )
self.assertEqual(result, [])
self.assertEqual(failed, txs)
def test_resend_some_failed(self):
criteria = CheckCriteria()
criteria.storage = self.StorageMock(self.stored_txs)
self.service.criteria = criteria
self.service.gateway = self.HalfResender()
txs = self.current_txs
result, failed = self.service.resend_transactions(txs, 1000, )
self.assertEqual(result, txs[:2])
self.assertEqual(failed, [txs[-1]])
class MemcachedStorageTest(TestCase):
def setUp(self):
self.storage = MemcachedStorage()
pass
class MemcachedMock:
dictionary = {}
def __init__(self, some=None): # necesario para el monkey patching que hice en un test
pass
def set(self, key, value, expire_time=0):
self.dictionary[key] = str(value).encode()
return value
def get(self, key):
return self.dictionary[key]
def test_mock_memcached(self):
old_client = self.storage.client
old_mem_client = base.Client # perdon por el monkey patching :c
self.storage.client = self.MemcachedMock()
base.Client = self.MemcachedMock
some_stuff = [123, 345, 78, 9]
self.storage.store(some_stuff)
retrieved = self.storage.retrieve()
self.assertEqual(some_stuff, retrieved)
self.storage.client = old_client
base.Client = old_mem_client
def test_integrated_memcached(self):
m_client = base.Client((TEST_MEMCACHED_HOST, TEST_MEMCACHED_PORT))
m_client.set(TEST_PENDING_TXS_MEMCACHED_KEY, [])
self.storage.client = m_client
self.storage.key = TEST_PENDING_TXS_MEMCACHED_KEY
some_stuff = [1, 2, 3, 4, 54, 5, 765]
self.storage.store(some_stuff)
retrieved = self.storage.retrieve()
self.assertEqual(some_stuff, retrieved)
class TransactionUnstuckerTest(TestCase, EthereumMocks):
def setUp(self):
self.unstucker = TransactionUnstucker()
self.generar_valores()
pass
def test_unstuck_mock_storage(self):
mock = self.EthereumMock()
service = PendingTransactionsService()
service.criteria.storage = self.StorageMock(mock.pending_transactions())
service.gateway = mock
self.unstucker.gateway = mock
self.unstucker.pending_transaction_service = service
self.unstucker.storage = service.criteria.storage
res, failed = self.unstucker.unstuck_pending_transactions(1000)
self.assertEqual(failed, [])
self.assertEqual(mock.pending_transactions(), [])
# testeo contra transacciones cacheadas que siguen pendientes,
def test_unstuck_integrated_memcached_custom_client(self):
m_client = base.Client((TEST_MEMCACHED_HOST, TEST_MEMCACHED_PORT)) # me hago mi propio cliente
m_client.set(TEST_PENDING_TXS_MEMCACHED_KEY, []) # limpio por las dudas sobre otra key
mock = self.EthereumMock() # mockeo el gateway de ethereum
service = PendingTransactionsService()
service.criteria.storage.client = m_client # setteo mi cliente
service.criteria.storage.key = TEST_PENDING_TXS_MEMCACHED_KEY
service.criteria.storage.store(mock.pending_transactions()) # y le asigno las pendientes mockeadas
service.gateway = mock # asigno los mocks que armé
self.unstucker.gateway = mock
self.unstucker.pending_transaction_service = service
self.unstucker.storage = service.criteria.storage
res, failed = self.unstucker.unstuck_pending_transactions(1000)
self.assertEqual(failed, [])
self.assertEqual(res, self.current_txs)
self.assertEqual(mock.pending_transactions(), [])
# testeo contra nada de transacciones cacheadas, y transacciones pendientes en el momento
def test_unstuck_integrated_memcached_custom_client_nothing_to_do(self):
m_client = base.Client((TEST_MEMCACHED_HOST, TEST_MEMCACHED_PORT))
m_client.set(TEST_PENDING_TXS_MEMCACHED_KEY, [])
mock = self.EthereumMock()
service = PendingTransactionsService()
service.criteria.storage.client = m_client
service.gateway = mock
self.unstucker.gateway = mock
self.unstucker.pending_transaction_service = service
self.unstucker.storage = service.criteria.storage
res, failed = self.unstucker.unstuck_pending_transactions(1000)
self.assertEqual(failed, [])
self.assertEqual(res, []) # como no tenía cache entonces no se hizo nada
self.assertEqual(response.status_code, status.HTTP_200_OK, 'Stamp Error')
\ No newline at end of file
......@@ -7,7 +7,6 @@ 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 datetime
import hashlib
import time
......@@ -17,7 +16,7 @@ from ethereum.abi import (decode_abi, normalize_name as normalize_abi_method_nam
from ethereum.utils import encode_int, zpad, decode_hex
class Utils:
class Utils():
@staticmethod
def sha256_encode(string):
return hashlib.sha256(string.encode('utf-8')).hexdigest()
......@@ -28,10 +27,8 @@ class Utils:
@staticmethod
def get_ots_hash(file_hash):
# El ots se genera con los hashes sha256 de (archivo original+timestamp+dirección de la cuenta) + versión del
# contrato
return Utils.sha256_encode(str(file_hash + Utils.sha256_encode(str(int(time.time()))) + Utils.sha256_encode(
ACCOUNT_ADDRESS))) + CURRENT_CONTRACT_VERSION
#El ots se genera con los hashes sha256 de (archivo original+timestamp+dirección de la cuenta) + versión del contrato
return Utils.sha256_encode(str(file_hash + Utils.sha256_encode(str(int(time.time()))) + Utils.sha256_encode(ACCOUNT_ADDRESS))) + CURRENT_CONTRACT_VERSION
@staticmethod
def decode_contract_call(contract_abi, call_data):
......@@ -61,9 +58,4 @@ class Utils:
@staticmethod
def get_temporary_ots(ots_hash, tx_hash):
return TEMPORARY_OTS_PREFIX + '-' + ots_hash + '-' + tx_hash
class Base64EncodingService:
def encode(self, stuff):
return base64.b64encode(stuff)
\ No newline at end of file
return TEMPORARY_OTS_PREFIX + '-' + ots_hash + '-' + tx_hash
\ No newline at end of file
......@@ -16,13 +16,12 @@ 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
import coreschema,coreapi
from app.managers import TimestampManager
from app.utils import Utils
from TsaApi.local_settings import TEMPORARY_OTS_PREFIX, PERMANENT_OTS_PREFIX, CONTRACTS
class Stamp(APIView):
"""
[POST]
......@@ -48,6 +47,7 @@ class Stamp(APIView):
schema=coreschema.String(),
description='El hash del archivo encodeado en sha256',
),
])
......@@ -63,23 +63,18 @@ class Stamp(APIView):
tx_hash = TimestampManager.stamp(ots_hash, file_hash)
# Al OTS se le agrega la transacción para poder verificar luego si está pendiente de subida
#Al OTS se le agrega la transacción para poder verificar luego si está pendiente de subida
ots = Utils.get_temporary_ots(ots_hash, tx_hash.hex())
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):
......@@ -135,10 +130,9 @@ class Verify(APIView):
ots_version, file_hash, ots_hash, tx_hash, block_number = ots.split('-')
method_name, args = Utils.decode_contract_call(CONTRACTS['01']['abi'],
TimestampManager.get_transaction(tx_hash).input)
method_name, args = Utils.decode_contract_call(CONTRACTS['01']['abi'], TimestampManager.get_transaction(tx_hash).input)
if args[0].decode('utf-8') == ots_hash and args[1].decode('utf-8') == original_file_hash:
if args[0].decode('utf-8') == ots_hash and args[1].decode('utf-8') == file_hash:
block = TimestampManager.get_block(int(block_number))
......@@ -146,8 +140,7 @@ class Verify(APIView):
_('permanent_rd'): base64.b64encode(Utils.get_permanent_ots(original_file_hash, ots_hash, tx_hash, block.number).encode('utf-8')).decode('utf-8'),
_('attestation_time'): str(Utils.datetime_from_timestamp(block.timestamp)),
_('messages'): _('file_uploaded') % (
file_hash, str(block.number),
str(Utils.datetime_from_timestamp(block.timestamp)))},
file_hash, str(block.number), str(Utils.datetime_from_timestamp(block.timestamp)))},
status=status.HTTP_200_OK)
else:
return Response({_('status'): _('failure'), _('messages'): _('file_not_found')},
......@@ -163,15 +156,12 @@ class Verify(APIView):
if TimestampManager.verify(contract_version, ots_hash, original_file_hash):
if (
TimestampManager.get_last_block_number() - transaction.blockNumber) < Utils.required_block_difference(
TimestampManager.get_signers_count()):
if (TimestampManager.get_last_block_number() - transaction.blockNumber) < Utils.required_block_difference(TimestampManager.get_signers_count()):
return Response({_('status'): _('pending'), _('messages'): _('transaction_pending')},
status=status.HTTP_200_OK)
else:
block = TimestampManager.get_block(
TimestampManager.get_block_number(contract_version, ots_hash))
block = TimestampManager.get_block(TimestampManager.get_block_number(contract_version, ots_hash))
return Response({_('status'): _('success'),
_('permanent_rd'): base64.b64encode(Utils.get_permanent_ots(original_file_hash, ots_hash, tx_hash, block.number).encode('utf-8')).decode('utf-8'),
......@@ -181,21 +171,16 @@ class Verify(APIView):
else:
try:
if transaction and not transaction.blockNumber:
return Response({_('status'): _('pending'), _('messages'): _('transaction_pending')},
status=status.HTTP_200_OK)
return Response({_('status'): _('pending'), _('messages'): _('transaction_pending')}, status=status.HTTP_200_OK)
except ValueError:
pass
return Response({_('status'): _('failure'), _('messages'): _('file_not_found')},
status=status.HTTP_404_NOT_FOUND)
return Response({_('status'): _('failure'), _('messages'): _('file_not_found')},status=status.HTTP_404_NOT_FOUND)
except ValidationError as e:
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)
\ No newline at end of file
return Response({_('status'): _('failure'), _('messages'): _('operation_failed')}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
\ No newline at end of file
asn1crypto==0.24.0
attrdict==2.0.0
backcall==0.1.0
certifi==2018.4.16
cffi==1.12.0
chardet==3.0.4
coincurve==11.0.0
coreapi==2.3.3
coreschema==0.0.4
cytoolz==0.9.0.1
decorator==4.3.2
Django==2.1
django-cors-headers==2.4.0
django-rest-swagger==2.2.0
......@@ -20,50 +15,27 @@ eth-hash==0.1.4
eth-keyfile==0.5.1
eth-keys==0.2.0b3
eth-rlp==0.1.2
eth-typing==2.0.0
eth-utils==1.0.3
ethereum==2.3.2
future==0.17.1
hexbytes==0.1.0
idna==2.7
ipython==7.2.0
ipython-genutils==0.2.0
itypes==1.1.0
jedi==0.13.2
Jinja2==2.10
lru-dict==1.1.6
MarkupSafe==1.0
openapi-codec==1.3.2
parsimonious==0.8.0
parso==0.3.4
pbkdf2==1.3
pexpect==4.6.0
pickleshare==0.7.5
prompt-toolkit==2.0.8
ptyprocess==0.6.0
py-ecc==1.4.7
pycparser==2.19
pkg-resources==0.0.0
pycryptodome==3.6.4
pyethash==0.1.27
Pygments==2.3.1
PyJWT==1.6.4
pymemcache==2.1.1
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.13
simplejson==3.16.0
six==1.11.0
toolz==0.9.0
traitlets==4.3.2
uritemplate==3.0.0
urllib3==1.23
wcwidth==0.1.7
web3==4.5.0
websockets==5.0.1
elastic-apm==3.0.0
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment