Skip to content
Snippets Groups Projects
Commit e9275a9e authored by inso's avatar inso
Browse files

Starting to implement new tx API

parent eba7d956
No related branches found
No related tags found
No related merge requests found
...@@ -122,8 +122,8 @@ class API(object): ...@@ -122,8 +122,8 @@ class API(object):
request.setHeader(QNetworkRequest.ContentTypeHeader, request.setHeader(QNetworkRequest.ContentTypeHeader,
"application/x-www-form-urlencoded") "application/x-www-form-urlencoded")
reply = self.conn_handler.network_manager.post(request, reply = self.conn_handler.network_manager.post(request,
post_data.toString(QUrl.FullyEncoded)) post_data.toString(QUrl.FullyEncoded).encode('utf-8'))
logging.debug(url.toString(QUrl.FullyEncoded))
return reply return reply
from . import network, blockchain, tx, wot from . import network, blockchain, tx, wot
...@@ -35,6 +35,27 @@ class Process(Tx): ...@@ -35,6 +35,27 @@ class Process(Tx):
return self.requests_post('/process', **kwargs) return self.requests_post('/process', **kwargs)
class History(Tx):
"""Get transaction sources."""
null_value = {
"currency": "",
"pubkey": "",
"history": {
"sent": [],
"received": []
}
}
def __init__(self, conn_handler, pubkey, module='tx'):
super(Tx, self).__init__(conn_handler, module)
self.pubkey = pubkey
def __get__(self, **kwargs):
assert self.pubkey is not None
return self.requests_get('/history/%s' % self.pubkey, **kwargs)
class Sources(Tx): class Sources(Tx):
"""Get transaction sources.""" """Get transaction sources."""
......
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Authors:
# Caner Candan <caner@candan.fr>, http://caner.candan.fr
#
from .. import History, logging
logger = logging.getLogger("ucoin/tx")
class Blocks(History):
def __init__(self, conn_handler, pubkey, from_, to_, module='blocks'):
super(History, self).__init__(conn_handler, pubkey, module)
self.from_ = from_
self.to_ = to_
null_value = {
"currency": "",
"pubkey": "",
"history": {
"sent": [],
"received": []
}
}
def __get__(self, **kwargs):
return self.requests_get('/blocks/{0}/{1}'.format(self.from_, self.to_) **kwargs)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Authors:
# Caner Candan <caner@candan.fr>, http://caner.candan.fr
#
from .. import API, logging
logger = logging.getLogger("ucoin/ud")
class Ud(API):
def __init__(self, conn_handler, module='ud'):
super(Ud, self).__init__(conn_handler, module)
class History(Ud):
"""Get UD history."""
null_value = {
"currency": "",
"pubkey": "",
"history": {
"history": []
}
}
def __init__(self, conn_handler, pubkey, module='ud'):
super(Ud, self).__init__(conn_handler, module)
self.pubkey = pubkey
def __get__(self, **kwargs):
assert self.pubkey is not None
return self.requests_get('/history/%s' % self.pubkey, **kwargs)
...@@ -4,11 +4,13 @@ Created on 31 janv. 2015 ...@@ -4,11 +4,13 @@ Created on 31 janv. 2015
@author: inso @author: inso
''' '''
import logging import logging
from ucoinpy.api import bma
from ucoinpy.documents.transaction import Transaction from ucoinpy.documents.transaction import Transaction
from .net.api import bma as qtbma
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject
from PyQt5.QtNetwork import QNetworkReply
import hashlib
class Transfer(QObject):
class Transfer(object):
''' '''
A transfer is the lifecycle of a transaction. A transfer is the lifecycle of a transaction.
TO_SEND means the transaction wasn't sent yet TO_SEND means the transaction wasn't sent yet
...@@ -25,7 +27,10 @@ class Transfer(object): ...@@ -25,7 +27,10 @@ class Transfer(object):
REFUSED = 3 REFUSED = 3
DROPPED = 5 DROPPED = 5
def __init__(self, txdoc, state, metadata): transfer_broadcasted = pyqtSignal()
broadcast_error = pyqtSignal(str, str)
def __init__(self, hash, state, metadata):
''' '''
The constructor of a transfer. The constructor of a transfer.
Check for metadata keys which must be present : Check for metadata keys which must be present :
...@@ -50,7 +55,7 @@ class Transfer(object): ...@@ -50,7 +55,7 @@ class Transfer(object):
assert('receiver_uid' in metadata) assert('receiver_uid' in metadata)
assert('txid' in metadata) assert('txid' in metadata)
self.txdoc = txdoc self.hash = hash
self.state = state self.state = state
self._metadata = metadata self._metadata = metadata
...@@ -62,22 +67,18 @@ class Transfer(object): ...@@ -62,22 +67,18 @@ class Transfer(object):
return cls(None, Transfer.TO_SEND, metadata) return cls(None, Transfer.TO_SEND, metadata)
@classmethod @classmethod
def create_validated(cls, txdoc, metadata): def create_validated(cls, hash, metadata):
''' '''
Create a new transfer in a "VALIDATED" state. Create a new transfer in a "VALIDATED" state.
''' '''
return cls(txdoc, Transfer.VALIDATED, metadata) return cls(hash, Transfer.VALIDATED, metadata)
@classmethod @classmethod
def load(cls, data): def load(cls, data):
''' '''
Create a new transfer from a dict in json format. Create a new transfer from a dict in json format.
''' '''
if data['state'] is Transfer.TO_SEND: return cls(data['hash'], data['state'], data['metadata'])
txdoc = None
else:
txdoc = Transaction.from_signed_raw(data['txdoc'])
return cls(txdoc, data['state'], data['metadata'])
@property @property
def metadata(self): def metadata(self):
...@@ -90,11 +91,7 @@ class Transfer(object): ...@@ -90,11 +91,7 @@ class Transfer(object):
''' '''
:return: The transfer as a dict in json format :return: The transfer as a dict in json format
''' '''
if self.txdoc: return {'hash': self.hash,
txraw = self.txdoc.signed_raw()
else:
txraw = None
return {'txdoc': txraw,
'state': self.state, 'state': self.state,
'metadata': self._metadata} 'metadata': self._metadata}
...@@ -107,19 +104,33 @@ class Transfer(object): ...@@ -107,19 +104,33 @@ class Transfer(object):
:param txdoc: A transaction ucoinpy object :param txdoc: A transaction ucoinpy object
:param community: The community target of the transaction :param community: The community target of the transaction
''' '''
try: replies = community.bma_access.broadcast(qtbma.tx.Process,
self.txdoc = txdoc
community.broadcast(bma.tx.Process,
post_args={'transaction': self.txdoc.signed_raw()}) post_args={'transaction': self.txdoc.signed_raw()})
for r in replies:
r.finished.connect(lambda reply=r: self.__handle_transfers_reply(replies, reply))
self.state = Transfer.AWAITING self.state = Transfer.AWAITING
except ValueError as e: self.hash = hashlib.sha1(txdoc.signed_raw().encode("ascii")).hexdigest().upper()
if '400' in str(e):
self.state = Transfer.REFUSED
raise
finally:
self._metadata['block'] = community.current_blockid()['number'] self._metadata['block'] = community.current_blockid()['number']
self._metadata['time'] = community.get_block().mediantime self._metadata['time'] = community.get_block().mediantime
def __handle_transfers_reply(self, replies, reply):
strdata = bytes(reply.readAll()).decode('utf-8')
logging.debug("Received reply : {0} : {1}".format(reply.error(), strdata))
if reply.error() == QNetworkReply.NoError:
self.transfer_broadcasted.emit()
for r in replies:
try:
r.disconnect()
except TypeError as e:
if "disconnect()" in str(e):
logging.debug("Could not disconnect a reply")
else:
for r in replies:
if not r.isFinished() or r.error() == QNetworkReply.NoError:
return
self.broadcast_error.emit(r.error(), strdata)
def check_registered(self, tx, block, time): def check_registered(self, tx, block, time):
''' '''
Check if the transfer was registered in a block. Check if the transfer was registered in a block.
...@@ -154,14 +165,14 @@ class Transfer(object): ...@@ -154,14 +165,14 @@ class Transfer(object):
class Received(Transfer): class Received(Transfer):
def __init__(self, txdoc, metadata): def __init__(self, hash, metadata):
''' '''
A transfer were the receiver is the local user. A transfer were the receiver is the local user.
:param txdoc: The transaction document of the received transfer :param txdoc: The transaction document of the received transfer
:param metadata: The metadata of the transfer :param metadata: The metadata of the transfer
''' '''
super().__init__(txdoc, Transfer.VALIDATED, metadata) super().__init__(hash, Transfer.VALIDATED, metadata)
@classmethod @classmethod
def load(cls, data): def load(cls, data):
...@@ -170,5 +181,4 @@ class Received(Transfer): ...@@ -170,5 +181,4 @@ class Received(Transfer):
:param data: The transfer as a dict in json format :param data: The transfer as a dict in json format
''' '''
txdoc = Transaction.from_signed_raw(data['txdoc']) return cls(data['hash'], data['metadata'])
return cls(txdoc, data['metadata'])
...@@ -66,42 +66,43 @@ class Cache(): ...@@ -66,42 +66,43 @@ class Cache():
def transfers(self): def transfers(self):
return [t for t in self._transfers if t.state != Transfer.DROPPED] return [t for t in self._transfers if t.state != Transfer.DROPPED]
def _parse_transaction(self, community, tx, block_number, def _parse_transaction(self, community, txdata, received_list, txid):
mediantime, received_list, txid): receivers = [o.pubkey for o in txdata['outputs']
#logging.debug(tx.signed_raw()) if o.pubkey != txdata['issuers'][0]]
receivers = [o.pubkey for o in tx.outputs
if o.pubkey != tx.issuers[0]]
if len(receivers) == 0: block_number = txdata['block']
receivers = [tx.issuers[0]] mediantime = txdata['time']
try:
issuer_uid = Person.lookup(tx.issuers[0], community).uid
except LookupFailureError:
issuer_uid = ""
try: if len(receivers) == 0:
receiver_uid = Person.lookup(receivers[0], community).uid receivers = [txdata['issuers'][0]]
except LookupFailureError: #
receiver_uid = "" # try:
# issuer_uid = IdentitiesRegistry.lookup(txdata['issuers'][0], community).uid
# except LookupFailureError:
# issuer_uid = ""
#
# try:
# receiver_uid = IdentitiesRegistry.lookup(receivers[0], community).uid
# except LookupFailureError:
# receiver_uid = ""
metadata = {'block': block_number, metadata = {'block': block_number,
'time': mediantime, 'time': mediantime,
'comment': tx.comment, 'comment': txdata['comment'],
'issuer': tx.issuers[0], 'issuer': txdata['issuers'][0],
'issuer_uid': issuer_uid, 'issuer_uid': "",#issuer_uid,
'receiver': receivers[0], 'receiver': receivers[0],
'receiver_uid': receiver_uid, 'receiver_uid': "",#receiver_uid,
'txid': txid} 'txid': txid}
in_issuers = len([i for i in tx.issuers in_issuers = len([i for i in txdata['issuers']
if i == self.wallet.pubkey]) > 0 if i == self.wallet.pubkey]) > 0
in_outputs = len([o for o in tx.outputs in_outputs = len([o for o in txdata['outputs']
if o.pubkey == self.wallet.pubkey]) > 0 if o.pubkey == self.wallet.pubkey]) > 0
# If the wallet pubkey is in the issuers we sent this transaction # If the wallet pubkey is in the issuers we sent this transaction
if in_issuers: if in_issuers:
outputs = [o for o in tx.outputs outputs = [o for o in txdata['outputs']
if o.pubkey != self.wallet.pubkey] if o.pubkey != self.wallet.pubkey]
amount = 0 amount = 0
for o in outputs: for o in outputs:
...@@ -111,49 +112,23 @@ class Cache(): ...@@ -111,49 +112,23 @@ class Cache():
awaiting = [t for t in self._transfers awaiting = [t for t in self._transfers
if t.state == Transfer.AWAITING] if t.state == Transfer.AWAITING]
# We check if the transaction correspond to one we sent # We check if the transaction correspond to one we sent
if tx.signed_raw() not in [t.txdoc.signed_raw() for t in awaiting]: if txdata['hash'] not in [t['hash'] for t in awaiting]:
transfer = Transfer.create_validated(tx, transfer = Transfer.create_validated(txdata,
metadata.copy()) metadata.copy())
self._transfers.append(transfer) self._transfers.append(transfer)
# If we are not in the issuers, # If we are not in the issuers,
# maybe it we are in the recipients of this transaction # maybe it we are in the recipients of this transaction
elif in_outputs: elif in_outputs:
outputs = [o for o in tx.outputs outputs = [o for o in txdata.outputs
if o.pubkey == self.wallet.pubkey] if o.pubkey == self.wallet.pubkey]
amount = 0 amount = 0
for o in outputs: for o in outputs:
amount += o.amount amount += o.amount
metadata['amount'] = amount metadata['amount'] = amount
received = Received(tx, metadata.copy()) received = Received(txdata, metadata.copy())
received_list.append(received) received_list.append(received)
self._transfers.append(received) self._transfers.append(received)
def _parse_block(self, community, block_number, received_list):
block = community.request(bma.blockchain.Block,
req_args={'number': block_number})
signed_raw = "{0}{1}\n".format(block['raw'],
block['signature'])
try:
block_doc = Block.from_signed_raw(signed_raw)
except:
logging.debug("Error in {0}".format(block_number))
raise
for (txid, tx) in enumerate(block_doc.transactions):
self._parse_transaction(community, tx, block_number,
block_doc.mediantime, received_list,
txid)
logging.debug("Received {0} transactions".format(len(received_list)))
awaiting = [t for t in self._transfers
if t.state == Transfer.AWAITING]
# After we checked all transactions, we check if
# sent transactions still waiting for validation
# have to be considered refused
for transfer in awaiting:
transfer.check_registered(tx, block_number,
block_doc.mediantime)
def refresh(self, community, received_list): def refresh(self, community, received_list):
current_block = 0 current_block = 0
try: try:
...@@ -163,22 +138,14 @@ class Cache(): ...@@ -163,22 +138,14 @@ class Cache():
# Lets look if transactions took too long to be validated # Lets look if transactions took too long to be validated
awaiting = [t for t in self._transfers awaiting = [t for t in self._transfers
if t.state == Transfer.AWAITING] if t.state == Transfer.AWAITING]
with_tx = community.request(bma.blockchain.TX) tx_history = community.bma_access.request(qtbma.tx.history.Blocks,
req_args={'pubkey': self.wallet.pubkey,
'from_':self.latest_block,
'to_': self.current_block})
# We parse only blocks with transactions # We parse only blocks with transactions
parsed_blocks = reversed(range(self.latest_block + 1, for (txid, txdata) in enumerate(tx_history['history']['received'] + tx_history['history']['sent']):
current_block + 1)) self._parse_transaction(community, txdata, received_list, txid)
logging.debug("Refresh from {0} to {1}".format(self.latest_block + 1,
current_block + 1))
parsed_blocks = [n for n in parsed_blocks
if n in with_tx['result']['blocks']]
logging.debug(parsed_blocks)
self.wallet.refresh_progressed.emit(self.latest_block, current_block)
for block_number in parsed_blocks:
self._parse_block(community, block_number, received_list)
self.wallet.refresh_progressed.emit(current_block - block_number,
current_block - self.latest_block)
if current_block > self.latest_block: if current_block > self.latest_block:
self.available_sources = self.wallet.sources(community) self.available_sources = self.wallet.sources(community)
...@@ -397,12 +364,12 @@ class Wallet(QObject): ...@@ -397,12 +364,12 @@ class Wallet(QObject):
logging.debug("Sender pubkey:{0}".format(key.pubkey)) logging.debug("Sender pubkey:{0}".format(key.pubkey))
try: try:
issuer_uid = Person.lookup(key.pubkey, community).uid issuer_uid = self.identities_registry.lookup(key.pubkey, community).uid
except LookupFailureError: except LookupFailureError:
issuer_uid = "" issuer_uid = ""
try: try:
receiver_uid = Person.lookup(recipient, community).uid receiver_uid = self.identities_registry.lookup(recipient, community).uid
except LookupFailureError: except LookupFailureError:
receiver_uid = "" receiver_uid = ""
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment