Skip to content
Snippets Groups Projects
Commit e5ef8294 authored by Agustín Sacramento's avatar Agustín Sacramento
Browse files

* Se agrega funcionalidad para reprocesar transacciones pendientes.

 * Se modifica el Verify para poder identificar transacciones por contenido independientemente del ID de la transaccion.
 * Se agregan archivos Docker.
 * Se modifica el nombre del archivo License
parents bc197d5e 0a5599bf
No related branches found
No related merge requests found
File deleted
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)
File deleted
import base64
from json import JSONDecodeError
from app.managers import TimestampManager, EthereumGateway, TimeStamp
from app.utils import Utils, Base64EncodingService
from pymemcache.client import base
from TsaApi.settings import MEMCACHED_HOST, MEMCACHED_PORT, PENDING_TXS_MEMCACHED_KEY
import logging
import json
from sys import stdout
from django.utils.translation import gettext as _, gettext
if stdout.isatty():
log_str = 'console-logger'
else:
log_str = 'logger'
module_logger = logging.getLogger(log_str)
class ReceiptInterpreter:
def interpret(self, ots):
return ots.split('-')
class DefinitiveReceiptGenerator:
encoder = Base64EncodingService()
interpreter = ReceiptInterpreter()
gateway = EthereumGateway()
def generate_definitive_receipt(self, original_file_hash, tx_hash, ots_hash):
tx = self.gateway.transaction(tx_hash)
return self.encoder.encode(Utils.get_permanent_ots(original_file_hash, ots_hash, tx_hash,
tx['blockNumber']).encode('utf-8')).decode('utf-8')
class TransactionInputVerification:
gateway = EthereumGateway()
interpreter = ReceiptInterpreter()
ts = TimeStamp()
tx_hash = ''
def verify(self, original_file_hash, ots):
ots_version, file_hash, ots_hash, tx_hash, block_number = self.interpreter.interpret(ots)
method_name, args = Utils.decode_contract_call(self.ts.abi(), TimestampManager.get_transaction(tx_hash).input)
return args[0].decode('utf-8') == ots_hash and args[1].decode('utf-8') == file_hash
class SmartContractVerification:
timestamp = TimeStamp()
interpreter = ReceiptInterpreter()
gateway = EthereumGateway()
def verify(self, file_hash, ots_hash):
verified = self.timestamp.verify(ots_hash, file_hash)
block = None
if verified:
block_number = self.timestamp.get_block(ots_hash)
block = self.gateway.block(block_number)
return verified, block
class EventVerification:
gateway = EthereumGateway()
class VerifyService:
receipt_generator = DefinitiveReceiptGenerator()
definitive_verificator = SmartContractVerification()
temp_verificator = SmartContractVerification()
interpreter = ReceiptInterpreter()
gateway = EthereumGateway()
permanent_ots_prefix = ''
def verify(self, original_file_hash, rd):
if rd[:2] == self.permanent_ots_prefix:
ots_version, file_hash, ots_hash, tx_hash, block_number = self.interpreter.interpret(rd)
verified, block = self.definitive_verificator.verify(original_file_hash, ots_hash)
if verified:
return {'status': 'success',
'block': block,
'permanent_rd': self.receipt_generator.generate_definitive_receipt(original_file_hash,
tx_hash,
ots_hash)}
else:
return {'status': 'failure'}
else:
ots_version, ots_hash, tx_hash = self.interpreter.interpret(rd)
verified, block = self.temp_verificator.verify(original_file_hash, ots_hash)
tx = self.gateway.transaction(tx_hash)
if verified:
if not self.gateway.transaction_is_canonical(tx):
return {'status': 'pending'}
else:
return {'status': 'success',
'block': block,
'permanent_rd': self.receipt_generator.generate_definitive_receipt(original_file_hash,
tx_hash,
ots_hash)}
else:
try:
if tx and not tx.blockNumber:
return {'status': 'pending'}
except ValueError:
pass
return {'status': 'failure'}
class StatusNotValidException(Exception):
pass
class VerifyServiceTranslation:
verify_service = VerifyService()
def verify(self, original_file_hash, rd):
result = self.verify_service.verify(original_file_hash, rd)
dict_res = {gettext('status'): result['status']}
res_status = 200
if result['status'] == 'success':
dict_res[gettext('permanent_rd')] = result['permanent_rd']
dict_res[gettext('attestation_time')] = str(Utils.datetime_from_timestamp(result['block'].timestamp))
dict_res[gettext('messages')] = gettext('file_uploaded') % (
original_file_hash,
str(result['block'].number),
str(Utils.datetime_from_timestamp(result['block'].timestamp))
)
elif result['status'] == 'failure':
dict_res[gettext('messages')] = gettext('file_not_found')
res_status = 404
elif result['status'] == 'pending':
dict_res[gettext('messages')] = gettext('transaction_pending')
else:
raise StatusNotValidException('Status invalid in verify service result : {}'.format(result['status']))
return dict_res, res_status
class MemcachedStorage:
client = base.Client((MEMCACHED_HOST, MEMCACHED_PORT))
server_tuple = (MEMCACHED_HOST, MEMCACHED_PORT)
key = PENDING_TXS_MEMCACHED_KEY
logger = module_logger
def store(self, pending_txs):
jsoned = json.dumps(pending_txs)
self.client.set(self.key, jsoned)
def retrieve(self):
retrieved = self.client.get(self.key)
try:
decoded = retrieved.decode("utf-8")
interpretado = json.loads(decoded)
except AttributeError as ae:
self.logger.info("Memcached estaba vacío key: {} ; valor : {}".format(str(self.key), retrieved))
interpretado = []
except JSONDecodeError as jsonde:
self.logger.error(
"Se devolvera lista vacia. No se pudo decodear la string: {} {}".format(retrieved, str(jsonde)))
interpretado = []
return interpretado
class CheckCriteria:
storage = MemcachedStorage()
def __init__(self, logger=None):
if logger is not None:
self.logger = logger
def should_transactions_be_resent(self, stuck_txs):
stored_txs = self.storage.retrieve()
txs_dict = {}
for stx in stored_txs:
txs_dict[stx['hash']] = stx
res = False
for tx in stuck_txs:
res = res or tx['hash'] in txs_dict
return res
class PendingTransactionsService:
gateway = EthereumGateway()
criteria = CheckCriteria()
logger = module_logger
def __init__(self, logger=None):
if logger is not None:
self.logger = logger
def resend_transactions(self, txs, new_gas_price, new_gas_limit=None):
self.logger.info("Por evaluar si deberían ser reenviadas las transacciones")
new_txs = []
failed = []
if self.criteria.should_transactions_be_resent(txs): # obs: si es vacío entonces evalúa True
for tx in txs: # si es vacío este ciclo no hace nada
self.logger.info("Reenviando transaccion {}".format(str(tx)))
try:
new_txs.append(self.gateway.resend(tx, new_gas_price, new_gas_limit))
except Exception as e:
failed.append(tx)
self.logger.error('Error :{}'.format(str(e)))
return new_txs, failed
class TransactionUnstucker:
pending_transaction_service = PendingTransactionsService()
gateway = EthereumGateway()
storage = MemcachedStorage()
logger = module_logger
def __init__(self, logger=None):
if logger is not None:
self.logger = logger
self.pending_transaction_service.logger = logger
def unstuck_pending_transactions(self, new_gas_price, new_gas=None):
self.logger.info('Arranca proceso')
stuck_txs = self.gateway.pending_transactions()
self.logger.debug('Cant txs encontradas : {}'.format(len(stuck_txs)))
res, failed = self.pending_transaction_service.resend_transactions(stuck_txs, new_gas_price, new_gas)
self.logger.debug(
'Terminado, cant reenvios con exito: {} ; cant reenvios fallados: {}'.format(len(res), len(failed)))
nuevas_txs = self.gateway.pending_transactions()
self.logger.debug('Cant txs encontradas : {}'.format(len(nuevas_txs)))
self.storage.store(nuevas_txs)
self.logger.info('Termina proceso')
return res, failed
......@@ -7,28 +7,453 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY
You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/
"""
import base64
import json
import time
import unittest
from django.utils.translation import gettext as _
from django.test import TestCase
from django.test import Client
from eth_account.datastructures import AttributeDict
from pymemcache.client import base
from rest_framework import status
from TsaApi.settings import TEST_MEMCACHED_HOST, TEST_MEMCACHED_PORT, TEST_PENDING_TXS_MEMCACHED_KEY, \
PERMANENT_OTS_PREFIX
from app.services import PendingTransactionsService, CheckCriteria, MemcachedStorage, TransactionUnstucker, \
VerifyService
class TimestampTest(TestCase):
def test_verify(self):
class TimestampTest(unittest.TestCase):
def test_full_cycle(self):
c = Client()
response = c.post('/api/tsa/verify/', {
response = c.post('/api/tsa/stamp/', {
"file_hash": "1957db7fe23e4be1740ddeb941ddda7ae0a6b782e536a9e00b5aa82db1e84547"
})
self.assertEqual(response.status_code, status.HTTP_200_OK, 'Stamp Error')
dict_response = json.loads(response.content.decode())
time.sleep(30)
verify_response = c.post('/api/tsa/verify/', {
"file_hash": "1957db7fe23e4be1740ddeb941ddda7ae0a6b782e536a9e00b5aa82db1e84547",
"proof": "NjA2MWQ1ODNkMTM0YTlmZGZlYWYxMWE2MjY1ZDY1OTkwMGM0ZGZjNjM2MDk3MDY4N2VkNDk3NTNhZmE5MGM3YzAxLTB4NWRiYjg4NjNlNDlhN2NmYTY0N2FmYzVkOTFkMWRiMDUxY2YzODZiYWY4NzJkM2ZiYTIzN2JkY2IwOWUwNWMzYQ=="
"rd": dict_response['temporary_rd']
})
self.assertEqual(response.status_code, status.HTTP_200_OK, 'Verify Error')
self.assertEqual(verify_response.status_code, status.HTTP_200_OK, 'Verify Error')
dict_verify = json.loads(verify_response.content.decode())
self.assertEqual(dict_verify[_('status')], _('success'))
def test_stamp(self):
c = Client()
response = c.post('/api/tsa/stamp/', {
"file_hash": "1957db7fe23e4be1740ddeb941ddda7ae0a6b782e536a9e00b5aa82db1e84547"
})
class EthereumMocks:
class HalfResender:
balance = 2.1 * 21000 * 1000 # me da para 2 txs mockeadas
def resend(self, tx, gas_price, gas=None):
if gas is None:
gas = tx['gas']
costo = (gas_price * gas) + tx['value']
if self.balance > costo:
self.balance = self.balance - costo
return tx
raise Exception("Failing test")
class EthereumMock:
def __init__(self):
self.pending_txs = [
AttributeDict({
'blockHash': '0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd',
'blockNumber': None,
'from': '0xa1e4380a3b1f749673e270229993ee55f35663b4',
'gas': 21000,
'gasPrice': 500,
'hash': '0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060',
'input': '0x',
'nonce': 0,
'to': '0x5df9b87991262f6ba471f09758cde1c0fc1de734',
'transactionIndex': 0,
'value': 1000,
}),
AttributeDict({
'blockHash': '0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd',
'blockNumber': 0,
'from': '0xa1e4380a3b1f749673e270229993ee55f35663b4',
'gas': 21000,
'gasPrice': 500,
'hash': '0x6c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060',
'input': '0x',
'nonce': 0,
'to': '0x5df9b87991262f6ba471f09758cde1c0fc1de734',
'transactionIndex': 0,
'value': 1000,
}),
AttributeDict({
'blockHash': '0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd',
'blockNumber': 0,
'from': '0xa1e4380a3b1f749673e270229993ee55f35663b4',
'gas': 21000,
'gasPrice': 500,
'hash': '0x7d515ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060',
'input': '0x',
'nonce': 0,
'to': '0x5df9b87991262f6ba471f09758cde1c0fc1de734',
'transactionIndex': 0,
'value': 1000,
})
]
def resend(self, tx, gas_price, gas):
self.pending_txs.remove(tx)
return tx
def pending_transactions(self):
return self.pending_txs.copy()
class CriteriaMockTrue:
def should_transactions_be_resent(self, txs):
return True
class CriteriaMockFalse:
def should_transactions_be_resent(self, txs):
return False
class StorageMock:
def __init__(self, stored_txs):
self.stored_txs = stored_txs
def store(self, txs):
self.stored_txs = txs
def retrieve(self):
return self.stored_txs
class FailResender:
def resend(self, tx, gas_price, gas):
raise Exception("Failing test")
def generar_valores(self):
self.current_txs = \
[
AttributeDict({
'blockHash': '0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd',
'blockNumber': None,
'from': '0xa1e4380a3b1f749673e270229993ee55f35663b4',
'gas': 21000,
'gasPrice': 500,
'hash': '0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060',
'input': '0x',
'nonce': 0,
'to': '0x5df9b87991262f6ba471f09758cde1c0fc1de734',
'transactionIndex': 0,
'value': 1000,
}),
AttributeDict({
'blockHash': '0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd',
'blockNumber': 0,
'from': '0xa1e4380a3b1f749673e270229993ee55f35663b4',
'gas': 21000,
'gasPrice': 500,
'hash': '0x6c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060',
'input': '0x',
'nonce': 0,
'to': '0x5df9b87991262f6ba471f09758cde1c0fc1de734',
'transactionIndex': 0,
'value': 1000,
}),
AttributeDict({
'blockHash': '0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd',
'blockNumber': 0,
'from': '0xa1e4380a3b1f749673e270229993ee55f35663b4',
'gas': 21000,
'gasPrice': 500,
'hash': '0x7d515ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060',
'input': '0x',
'nonce': 0,
'to': '0x5df9b87991262f6ba471f09758cde1c0fc1de734',
'transactionIndex': 0,
'value': 1000,
})
]
self.stored_txs = \
[
AttributeDict({
'blockHash': '0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd',
'blockNumber': None,
'from': '0xa1e4380a3b1f749673e270229993ee55f35663b4',
'gas': 21000,
'gasPrice': 500,
'hash': '0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060',
'input': '0x',
'nonce': 0,
'to': '0x5df9b87991262f6ba471f09758cde1c0fc1de734',
'transactionIndex': 0,
'value': 31337,
}),
AttributeDict({
'blockHash': '0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd',
'blockNumber': 0,
'from': '0xa1e4380a3b1f749673e270229993ee55f35663b4',
'gas': 21000,
'gasPrice': 500,
'hash': '0x6c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060',
'input': '0x',
'nonce': 0,
'to': '0x5df9b87991262f6ba471f09758cde1c0fc1de734',
'transactionIndex': 0,
'value': 31337,
}),
AttributeDict({
'blockHash': '0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd',
'blockNumber': 0,
'from': '0xa1e4380a3b1f749673e270229993ee55f35663b4',
'gas': 21000,
'gasPrice': 500,
'hash': '0x6c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060',
'input': '0x',
'nonce': 0,
'to': '0x5df9b87991262f6ba471f09758cde1c0fc1de734',
'transactionIndex': 0,
'value': 31337,
})
]
class PendingTransactionTest(unittest.TestCase, EthereumMocks):
def setUp(self):
self.service = PendingTransactionsService()
self.generar_valores()
def test_verify_criteria_affirmative(self):
self.service.criteria = self.CriteriaMockTrue()
self.service.gateway = self.EthereumMock()
mock_txs = self.current_txs
result, failed = self.service.resend_transactions(mock_txs, 1000, )
self.assertEqual(result, mock_txs)
self.assertEqual(failed, [])
def test_verify_criteria_false(self):
self.service.criteria = self.CriteriaMockFalse()
self.service.gateway = self.EthereumMock()
mock_txs = self.current_txs
result, failed = self.service.resend_transactions(mock_txs, 1000, )
self.assertEqual(result, [])
self.assertEqual(failed, [])
def test_resend(self):
criteria = CheckCriteria()
criteria.storage = self.StorageMock(self.stored_txs)
self.service.criteria = criteria
self.service.gateway = self.EthereumMock()
txs = self.current_txs
result, failed = self.service.resend_transactions(txs, 1000, )
self.assertEqual(result, txs)
self.assertEqual(failed, [])
def test_resend_everything_failed(self):
criteria = CheckCriteria()
criteria.storage = self.StorageMock(self.stored_txs)
self.service.criteria = criteria
self.service.gateway = self.FailResender()
txs = self.current_txs
result, failed = self.service.resend_transactions(txs, 1000, )
self.assertEqual(result, [])
self.assertEqual(failed, txs)
def test_resend_some_failed(self):
criteria = CheckCriteria()
criteria.storage = self.StorageMock(self.stored_txs)
self.service.criteria = criteria
self.service.gateway = self.HalfResender()
txs = self.current_txs
result, failed = self.service.resend_transactions(txs, 1000, )
self.assertEqual(result, txs[:2])
self.assertEqual(failed, [txs[-1]])
class MemcachedStorageTest(unittest.TestCase):
def setUp(self):
self.storage = MemcachedStorage()
pass
class MemcachedMock:
dictionary = {}
def __init__(self, some=None): # necesario para el monkey patching que hice en un test
pass
def set(self, key, value, expire_time=0):
self.dictionary[key] = str(value).encode()
return value
def get(self, key):
return self.dictionary[key]
def test_mock_memcached(self):
old_client = self.storage.client
old_mem_client = base.Client # perdon por el monkey patching :c
self.storage.client = self.MemcachedMock()
base.Client = self.MemcachedMock
some_stuff = [123, 345, 78, 9]
self.storage.store(some_stuff)
retrieved = self.storage.retrieve()
self.assertEqual(some_stuff, retrieved)
self.storage.client = old_client
base.Client = old_mem_client
def test_integrated_memcached(self):
m_client = base.Client((TEST_MEMCACHED_HOST, TEST_MEMCACHED_PORT))
m_client.set(TEST_PENDING_TXS_MEMCACHED_KEY, [])
self.storage.client = m_client
self.storage.key = TEST_PENDING_TXS_MEMCACHED_KEY
some_stuff = [1, 2, 3, 4, 54, 5, 765]
self.storage.store(some_stuff)
retrieved = self.storage.retrieve()
self.assertEqual(some_stuff, retrieved)
class TransactionUnstuckerTest(unittest.TestCase, EthereumMocks):
def setUp(self):
self.unstucker = TransactionUnstucker()
self.generar_valores()
pass
def test_unstuck_mock_storage(self):
mock = self.EthereumMock()
service = PendingTransactionsService()
service.criteria.storage = self.StorageMock(mock.pending_transactions())
service.gateway = mock
self.unstucker.gateway = mock
self.unstucker.pending_transaction_service = service
self.unstucker.storage = service.criteria.storage
res, failed = self.unstucker.unstuck_pending_transactions(1000)
self.assertEqual(failed, [])
self.assertEqual(mock.pending_transactions(), [])
# testeo contra transacciones cacheadas que siguen pendientes,
def test_unstuck_integrated_memcached_custom_client(self):
m_client = base.Client((TEST_MEMCACHED_HOST, TEST_MEMCACHED_PORT)) # me hago mi propio cliente
m_client.set(TEST_PENDING_TXS_MEMCACHED_KEY, []) # limpio por las dudas sobre otra key
mock = self.EthereumMock() # mockeo el gateway de ethereum
service = PendingTransactionsService()
service.criteria.storage.client = m_client # setteo mi cliente
service.criteria.storage.key = TEST_PENDING_TXS_MEMCACHED_KEY
service.criteria.storage.store(mock.pending_transactions()) # y le asigno las pendientes mockeadas
service.gateway = mock # asigno los mocks que armé
self.unstucker.gateway = mock
self.unstucker.pending_transaction_service = service
self.unstucker.storage = service.criteria.storage
res, failed = self.unstucker.unstuck_pending_transactions(1000)
self.assertEqual(failed, [])
self.assertEqual(res, self.current_txs)
self.assertEqual(mock.pending_transactions(), [])
# testeo contra nada de transacciones cacheadas, y transacciones pendientes en el momento
def test_unstuck_integrated_memcached_custom_client_nothing_to_do(self):
m_client = base.Client((TEST_MEMCACHED_HOST, TEST_MEMCACHED_PORT))
m_client.set(TEST_PENDING_TXS_MEMCACHED_KEY, [])
mock = self.EthereumMock()
service = PendingTransactionsService()
service.criteria.storage.client = m_client
service.gateway = mock
self.unstucker.gateway = mock
self.unstucker.pending_transaction_service = service
self.unstucker.storage = service.criteria.storage
res, failed = self.unstucker.unstuck_pending_transactions(1000)
self.assertEqual(failed, [])
self.assertEqual(res, []) # como no tenía cache entonces no se hizo nada
class VerificationMocks:
class NotSealedTransactionMock:
def transaction(self, tx_hash):
return AttributeDict(
{'hash': tx_hash, 'blockNumber': None})
class SealedTransactionMock:
def transaction(self, tx_hash):
return AttributeDict(
{'hash': tx_hash, 'blockNumber': 1000})
class TXisCanonicalMock:
def transaction_is_canonical(self, tx):
return True
class TXisNotCanonicalMock:
def transaction_is_canonical(self, tx):
return False
class FalseVerificationMock:
def verify(self, file_hash, ots_hash):
return False, None
class TrueVerificationMock:
def verify(self, file_hash, ots_hash):
return True, AttributeDict({'number': 1000})
class TxCanonicalSealedTx(SealedTransactionMock, TXisCanonicalMock):
pass
class TxNoCanonicalSealedTx(SealedTransactionMock, TXisNotCanonicalMock):
pass
class ReceiptGeneratorMock:
def generate_definitive_receipt(self, original_file_hash, tx_hash, ots_hash):
return "{}{}{}".format(original_file_hash, tx_hash, ots_hash)
class VerifyServiceTest(unittest.TestCase, VerificationMocks):
def setUp(self):
self.service = VerifyService()
self.service.permanent_ots_prefix = PERMANENT_OTS_PREFIX
self.ots = "d6b88be854cb88cf1e161d8bbd912987cff236eb43a3a15fff86370054dd4d3f01"
self.file_hash = 'some_hash'
self.contract_version = '01'
self.temp_rd = "0x-d6b88be854cb88cf1e161d8bbd912987cff236eb43a3a15fff86370054dd4d3f01-0xf495705b5d7d68afdcefe72d9bbacec59b1a968c964ab0b6b13cec6491e5ddec"
self.def_rd = "1x-7b58a42ee27c3699fc169ce43cb4ba598003cba5699f4cd518b91d4177f61f10-0577d1390a5733534c7b6cc45a7ba15d0b5bf37faec9c54f749826af75aa86bb01-0x1a3a5335b44bdb7f62201102e91658586fd1d7845c621503eeff7781c3020b09-2234445"
pass
def test_verify_temporary_rd_not_sealed(self):
self.service.gateway = self.NotSealedTransactionMock()
self.service.temp_verificator = self.FalseVerificationMock()
res = self.service.verify(self.file_hash, self.temp_rd)
self.assertEqual(res['status'], 'pending')
def test_verify_temporary_rd_sealed_not_canonical(self):
self.service.gateway = self.TxNoCanonicalSealedTx()
self.service.temp_verificator = self.TrueVerificationMock()
res = self.service.verify(self.file_hash, self.temp_rd)
self.assertEqual(res['status'], 'pending')
def test_verify_temporary_rd_sealed_canonical(self):
self.service.temp_verificator = self.TrueVerificationMock()
self.service.gateway = self.TxCanonicalSealedTx()
self.service.receipt_generator.gateway = self.SealedTransactionMock()
res = self.service.verify(self.file_hash, self.temp_rd)
self.assertEqual(res['status'], 'success')
rd = res['permanent_rd']
ots_version, file_hash, ots_hash, tx_hash, block_number = base64.b64decode(rd).decode().split('-')
self.assertEqual(ots_version, PERMANENT_OTS_PREFIX)
self.assertEqual(file_hash, self.file_hash)
self.assertEqual(ots_hash, self.ots)
self.assertEqual(res['block'].number, int(block_number))
def test_definitive_rd_correct(self):
self.service.definitive_verificator = self.TrueVerificationMock()
self.service.receipt_generator = self.ReceiptGeneratorMock()
res = self.service.verify(self.file_hash, self.def_rd)
self.assertEqual(res['status'], 'success')
self.assertEqual(response.status_code, status.HTTP_200_OK, 'Stamp Error')
\ No newline at end of file
def test_definitive_rd_incorrect(self):
res = self.service.verify(self.file_hash, self.def_rd)
self.assertEqual(res['status'], 'failure')
......@@ -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
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