...
 
Commits (106)
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/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
......@@ -14,11 +14,13 @@ pip install -r requirements.txt
```
- Crear un link simbólico al archivo local_settings.py desde el local settings del entorno en que se encuentre el proyecto
- Ejecutar:
- sudo apt-get install libssl-dev build-essential automake pkg-config libtool libffi-dev libgmp-dev libyaml-cpp-dev
- sudo apt-get install libssl-dev build-essential automake pkg-config libtool libffi-dev libgmp-dev libyaml-cpp-dev memcached
- git clone https://github.com/ethereum/pyethereum/
- 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
......
......@@ -15,11 +15,23 @@ 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"}],
},
}
......@@ -33,4 +45,21 @@ PERMANENT_OTS_PREFIX = '1x'
GAS = 250000
GAS_PRICE = 1
SENTRY_URL = 'http://2ec54beaab3b4459a1e5afadea070f98:92aee3f5d91e4e66996a3e0792985541@172.17.30.21:9000/54'
\ No newline at end of file
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,
}
......@@ -8,6 +8,7 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY
You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/
"""
APP_ROOT = '/var/www/tsaapi.pp.bfa.local/'
LOG_ROOT = APP_ROOT + 'logs/'
STATIC_ROOT = APP_ROOT + 'html/'
STATIC_URL = '/tsa/static/'
......@@ -15,11 +16,47 @@ ACCOUNT_ADDRESS = '0xA9cAc6C2EF4909A05eF24A12Ecadf9E541B5995F' # cuenta con bal
HOST_ADDRESS = 'http://10.23.10.73:54450' # Nodo de la BFA de Test
#Contracts info
# Contracts info
CONTRACTS = {
'01':{
'01': {
'address': '0xD5110AB5cE1be270d5B8c5FB7A25c60765994ca8',
'abi': [ { "constant": False, "inputs": [], "name": "selfDestroy", "outputs": [], "payable": False, "stateMutability": "nonpayable", "type": "function" }, { "constant": False, "inputs": [ { "name": "ots", "type": "string" }, { "name": "file_hash", "type": "string" } ], "name": "stamp", "outputs": [], "payable": False, "stateMutability": "nonpayable", "type": "function" }, { "anonymous": False, "inputs": [ { "indexed": True, "name": "from", "type": "address" }, { "indexed": True, "name": "hash", "type": "string" }, { "indexed": True, "name": "ots", "type": "string" } ], "name": "Stamped", "type": "event" }, { "anonymous": False, "inputs": [ { "indexed": True, "name": "from", "type": "address" }, { "indexed": True, "name": "contract_address", "type": "address" } ], "name": "Deploy", "type": "event" }, { "anonymous": False, "inputs": [ { "indexed": True, "name": "from", "type": "address" }, { "indexed": True, "name": "contract_address", "type": "address" } ], "name": "SelfDestroy", "type": "event" }, { "inputs": [], "payable": False, "stateMutability": "nonpayable", "type": "constructor" }, { "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": "getHash", "outputs": [ { "name": "", "type": "string" } ], "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" } ],
'abi': [{"constant": False, "inputs": [], "name": "selfDestroy", "outputs": [], "payable": False,
"stateMutability": "nonpayable", "type": "function"}, {"constant": False,
"inputs": [{"name": "ots", "type": "string"},
{"name": "file_hash",
"type": "string"}], "name": "stamp",
"outputs": [], "payable": False,
"stateMutability": "nonpayable",
"type": "function"}, {"anonymous": False,
"inputs": [
{"indexed": True,
"name": "from",
"type": "address"},
{"indexed": True,
"name": "hash",
"type": "string"},
{"indexed": True,
"name": "ots",
"type": "string"}],
"name": "Stamped",
"type": "event"},
{"anonymous": False, "inputs": [{"indexed": True, "name": "from", "type": "address"},
{"indexed": True, "name": "contract_address", "type": "address"}],
"name": "Deploy", "type": "event"}, {"anonymous": False,
"inputs": [{"indexed": True, "name": "from", "type": "address"},
{"indexed": True, "name": "contract_address",
"type": "address"}], "name": "SelfDestroy",
"type": "event"},
{"inputs": [], "payable": False, "stateMutability": "nonpayable", "type": "constructor"},
{"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": "getHash",
"outputs": [{"name": "", "type": "string"}], "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"}],
},
}
CURRENT_CONTRACT_VERSION = '01'
......@@ -32,4 +69,10 @@ PERMANENT_OTS_PREFIX = '1x'
GAS = 250000
GAS_PRICE = 1
SENTRY_URL = 'http://aa30f3ecd3bb4923a7046f197da61e07:47cd9f9ea1e243f6a9f47e2aa40b85c3@10.1.100.118:9000/82'
\ No newline at end of file
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,
}
......@@ -7,18 +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/
"""
# APP_ROOT = '/home/python/TsaAPI/'
APP_ROOT = '/var/www/tsaapi.prod.bfa.local/'
# STATIC_ROOT = APP_ROOT + 'html/'
STATIC_URL = '/tsa/static/'
# ACCOUNT_ADDRESS = '0x21ea59FC5cE54a827E20BC9b736FeeD8F9C880Ff'
# HOST_ADDRESS = 'http://10.23.10.71:8501'
HOST_ADDRESS = 'http://127.0.0.1:8545'
ACCOUNT_ADDRESS = '0xac952ae0716e8aded07dda498f182f4983af682a'
......@@ -39,6 +27,6 @@ TEMPORARY_OTS_PREFIX = '0x'
PERMANENT_OTS_PREFIX = '1x'
GAS = 250000
GAS_PRICE = 100000000
GAS_PRICE = 100000000000
SENTRY_URL = 'http://36f4f241a007447d8a4441a13055b110:136cb6d1af854b869d13610603fa7321@10.1.100.118:9000/84'
\ No newline at end of file
......@@ -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,13 +159,93 @@ REST_FRAMEWORK = {
),
}
APP_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + '/'
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:
pass
LOG_ROOT = APP_ROOT + 'logs/'
STATIC_ROOT = APP_ROOT + 'html/'
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'
}
},
}
import logging
from django.core.management import BaseCommand
from app.services import TransactionUnstucker, MemcachedStorage
from TsaApi.settings import GAS_PRICE
from TsaApi.settings import PENDING_TXS_MEMCACHED_KEY
class Command(BaseCommand):
unstucker = TransactionUnstucker()
logger = logging.getLogger('cmd')
storage = MemcachedStorage()
def handle(self, *args, **options):
try:
self.storage.store([])
print("Memcached sobre key {} cambiado a {}".format(PENDING_TXS_MEMCACHED_KEY,self.storage.retrieve()))
except Exception as e:
self.logger.error(str(e))
print("Hubo un error {}".format(str(e)))
import logging
from django.core.management import BaseCommand
from app.services import TransactionUnstucker, MemcachedStorage
from TsaApi.settings import GAS_PRICE
class Command(BaseCommand):
unstucker = TransactionUnstucker()
logger = logging.getLogger('cmd')
storage = MemcachedStorage()
def handle(self, *args, **options):
print(self.storage.retrieve())
import logging
from django.core.management import BaseCommand
from app.managers import EthereumGateway
from app.services import TransactionUnstucker
from TsaApi.settings import GAS_PRICE
import logging
class Command(BaseCommand):
unstucker = TransactionUnstucker(logging.getLogger('console-logger'))
def handle(self, *args, **options):
self.unstucker.unstuck_pending_transactions(GAS_PRICE)
......@@ -16,6 +16,8 @@ from TsaApi.settings import HOST_ADDRESS
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):
......@@ -64,8 +67,8 @@ class TimestampManager(models.Manager):
web3 = TimestampManager.get_provider()
return contract.functions.stamp(proof_hash, file_hash).transact({'from': Web3.toChecksumAddress(ACCOUNT_ADDRESS), 'gas': GAS, 'gasPrice': web3.eth.gasPrice})
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)
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
This diff is collapsed.
......@@ -7,16 +7,15 @@ 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
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():
class Utils:
@staticmethod
def sha256_encode(string):
return hashlib.sha256(string.encode('utf-8')).hexdigest()
......@@ -27,7 +26,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
# 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
......@@ -58,4 +58,9 @@ class Utils():
@staticmethod
def get_temporary_ots(ots_hash, tx_hash):
return TEMPORARY_OTS_PREFIX + '-' + ots_hash + '-' + tx_hash
\ No newline at end of file
return TEMPORARY_OTS_PREFIX + '-' + ots_hash + '-' + tx_hash
class Base64EncodingService:
def encode(self, stuff):
return base64.b64encode(stuff)
......@@ -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)
version: '3.1'
services:
memcached:
image: memcached
container_name: memcached
ports:
- "11211:11211"
\ No newline at end of file
pyethereum @ d962694b
Subproject commit d962694be03686a8e5c1d7459ae272b70a5c9f77
asn1crypto==0.24.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
......@@ -16,6 +19,8 @@ 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
......@@ -24,14 +29,21 @@ lru-dict==1.1.6
MarkupSafe==1.0
openapi-codec==1.3.2
parsimonious==0.8.0
pkg-resources==0.0.0
pbkdf2==1.3
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
......@@ -39,3 +51,7 @@ uritemplate==3.0.0
urllib3==1.23
web3==4.5.0
websockets==5.0.1
elastic-apm==3.0.0
ipython==7.2.0
ipython-genutils==0.2.0
pymemcache==2.1.1
\ No newline at end of file