Commit 8499f73d authored by Vincent Texier's avatar Vincent Texier

Merge branch '#798' into dev

parents ffb68a11 a883607d
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -3,6 +3,10 @@ import attr
@attr.s(hash=True)
class Source:
TYPE_TRANSACTION = "T"
TYPE_DIVIDEND = "D"
currency = attr.ib(converter=str)
pubkey = attr.ib(converter=str)
identifier = attr.ib(converter=str)
......@@ -10,3 +14,5 @@ class Source:
type = attr.ib(converter=str, validator=lambda i, a, s: s == "T" or s == "D")
amount = attr.ib(converter=int, hash=False)
base = attr.ib(converter=int, hash=False)
conditions = attr.ib(converter=str, hash=False)
used_by = attr.ib(hash=False, default=None)
......@@ -111,18 +111,21 @@ class Transaction:
Transaction entity
:param str currency: the currency of the transaction
:param str pubkey: the pubkey of the issuer
:param str sha_hash: the hash of the transaction
:param int written_block: the number of the block
:param duniterpy.documents.BlockUID blockstamp: the blockstamp of the transaction
:param int timestamp: the timestamp of the transaction
:param str signature: the signature
:param str issuer: the pubkey of the issuer
:param str receiver: the pubkey of the receiver
:param str signatures: the signature
:param tuple issuers: tuple of pubkey of the issuers
:param tuple receivers: tuple of pubkey of the receivers
:param int amount: the amount
:param int amount_base: the amount base
:param str comment: a comment
:param str txid: the transaction id to sort transctions
:param int txid: the transaction id to sort transctions
:param int state: the state of the transaction
:param bool local: is the transaction local
:param str raw: the raw string of the transaction
"""
TO_SEND = 0
......
import attr
import logging
from ..entities import Dividend
from ..entities import Dividend, Source
from .nodes import NodesProcessor
from ..connectors import BmaConnector
from duniterpy.api import bma
......@@ -82,7 +82,7 @@ class DividendsProcessor:
txdoc = Transaction.from_signed_raw(tx.raw)
for input in txdoc.inputs:
if (
input.source == "D"
input.source == Source.TYPE_DIVIDEND
and input.origin_id == connection.pubkey
and input.index not in block_numbers
and input.index > start
......
......@@ -35,6 +35,9 @@ class SourcesProcessor:
except sqlite3.IntegrityError:
self._logger.debug("Source already known: {0}".format(source.identifier))
def get_one(self, **search):
return self._repo.get_one(**search)
def amount(self, currency, pubkey):
"""
Get the amount value of the sources for a given pubkey
......@@ -53,15 +56,27 @@ class SourcesProcessor:
"""
return self._repo.get_all(currency=currency, pubkey=pubkey)
def consume(self, sources):
def consume(self, sources, tx_sha_hash):
"""
Consume sources in db by setting the used_by column to tx hash
:param List(Source) sources: Source instances list
:param str tx_sha_hash: Hash of tx
:param currency:
:param sources:
:return:
"""
for s in sources:
self._repo.drop(s)
self._repo.consume(s, tx_sha_hash)
def restore_all(self, tx_sha_hash):
"""
Restore sources in db by setting the used_by column to null
:param str tx_sha_hash: Hash of tx
:return:
"""
self._repo.restore_all(tx_sha_hash)
def insert(self, source):
try:
......
......@@ -92,9 +92,12 @@ class TransactionsProcessor:
except sqlite3.IntegrityError:
self._repo.update(tx)
def find_by_hash(self, pubkey, sha_hash):
def find_by_pubkey_and_hash(self, pubkey: str, sha_hash: str) -> Transaction:
return self._repo.get_one(pubkey=pubkey, sha_hash=sha_hash)
def find_one_by_hash(self, sha_hash: str) -> Transaction:
return self._repo.get_one(sha_hash=sha_hash)
def awaiting(self, currency):
return self._repo.get_all(currency=currency, state=Transaction.AWAITING)
......
BEGIN TRANSACTION;
ALTER TABLE sources ADD COLUMN conditions VARCHAR(255);
COMMIT;
BEGIN TRANSACTION;
ALTER TABLE sources ADD COLUMN used_by VARCHAR(255) default null;
COMMIT;
......@@ -7,7 +7,7 @@ from .connections import ConnectionsRepo
from .identities import IdentitiesRepo
from .blockchains import BlockchainsRepo
from .certifications import CertificationsRepo
from .transactions import TransactionsRepo, Transaction
from .transactions import TransactionsRepo
from .dividends import DividendsRepo
from .nodes import NodesRepo, Node
from .sources import SourcesRepo
......@@ -89,6 +89,8 @@ class SakiaDatabase:
self.refactor_transactions,
self.drop_incorrect_nodes,
self.insert_last_mass_attribute,
self.add_sources_conditions_property,
self.add_sources_used_by_property,
]
def upgrade_database(self, to=0):
......@@ -211,6 +213,28 @@ class SakiaDatabase:
with self.conn:
self.conn.executescript(sql_file.read())
def add_sources_conditions_property(self):
self._logger.debug("Add sources conditions property")
sql_file = open(
os.path.join(
os.path.dirname(__file__), "006_add_sources_conditions_property.sql"
),
"r",
)
with self.conn:
self.conn.executescript(sql_file.read())
def add_sources_used_by_property(self):
self._logger.debug("Add sources used_by property")
sql_file = open(
os.path.join(
os.path.dirname(__file__), "007_add_sources_used_by_property.sql"
),
"r",
)
with self.conn:
self.conn.executescript(sql_file.read())
def version(self):
with self.conn:
c = self.conn.execute("SELECT * FROM meta WHERE id=1")
......
......@@ -106,7 +106,7 @@ CREATE TABLE IF NOT EXISTS nodes(
PRIMARY KEY (currency, pubkey)
);
-- Cnnections TABLE
-- CONNECTIONS TABLE
CREATE TABLE IF NOT EXISTS connections(
currency VARCHAR(30),
pubkey VARCHAR(50),
......@@ -118,7 +118,7 @@ CREATE TABLE IF NOT EXISTS connections(
PRIMARY KEY (currency, pubkey)
);
-- Cnnections TABLE
-- SOURCES TABLE
CREATE TABLE IF NOT EXISTS sources(
currency VARCHAR(30),
pubkey VARCHAR(50),
......@@ -130,6 +130,7 @@ CREATE TABLE IF NOT EXISTS sources(
PRIMARY KEY (currency, pubkey, identifier, noffset)
);
-- DIVIDENDS TABLE
CREATE TABLE IF NOT EXISTS dividends(
currency VARCHAR(30),
pubkey VARCHAR(50),
......
......@@ -29,8 +29,9 @@ class SourcesRepo:
def get_one(self, **search):
"""
Get an existing source in the database
:param dict search: the criterions of the lookup
Get an existing not consumed source in the database
:param ** search: the criterions of the lookup
:rtype: sakia.data.entities.Source
"""
filters = []
......@@ -39,7 +40,7 @@ class SourcesRepo:
filters.append("{k}=?".format(k=k))
values.append(v)
request = "SELECT * FROM sources WHERE {filters}".format(
request = "SELECT * FROM sources WHERE used_by is NULL AND {filters}".format(
filters=" AND ".join(filters)
)
......@@ -50,9 +51,10 @@ class SourcesRepo:
def get_all(self, **search):
"""
Get all existing source in the database corresponding to the search
:param dict search: the criterions of the lookup
:rtype: sakia.data.entities.Source
Get all existing not consumed source in the database corresponding to the search
:param ** search: the criterions of the lookup
:rtype: [sakia.data.entities.Source]
"""
filters = []
values = []
......@@ -61,7 +63,7 @@ class SourcesRepo:
filters.append("{key} = ?".format(key=k))
values.append(value)
request = "SELECT * FROM sources WHERE {filters}".format(
request = "SELECT * FROM sources WHERE used_by is NULL AND {filters}".format(
filters=" AND ".join(filters)
)
......@@ -101,3 +103,40 @@ class SourcesRepo:
filters=" AND ".join(filters)
)
self._conn.execute(request, tuple(values))
def consume(self, source, tx_hash):
"""
Consume a source by setting the used_by column with the tx hash
:param Source source: Source instance to consume
:param str tx_hash: Hash of tx
:return:
"""
where_fields = attr.astuple(
source, filter=attr.filters.include(*SourcesRepo._primary_keys)
)
fields = (tx_hash,) + where_fields
self._conn.execute(
"""UPDATE sources SET used_by=?
WHERE
currency=? AND
pubkey=? AND
identifier=? AND
noffset=?""",
fields,
)
def restore_all(self, tx_hash):
"""
Restore all sources released by tx_hash setting the used_by column with null
:param Source source: Source instance to consume
:param str tx_hash: Hash of tx
:return:
"""
self._conn.execute(
"""UPDATE sources SET used_by=NULL
WHERE
used_by=?""",
(tx_hash,),
)
......@@ -75,7 +75,7 @@ class TransactionsRepo:
def get_one(self, **search):
"""
Get an existing transaction in the database
:param dict search: the criterions of the lookup
:param ** search: the criterions of the lookup
:rtype: sakia.data.entities.Transaction
"""
filters = []
......
......@@ -55,8 +55,8 @@ class TxHistoryController(QObject):
sources_service,
):
transfer = TransferController.integrate_to_main_view(None, app, connection)
view = TxHistoryView(parent.view, transfer.view)
controller = TransferController.integrate_to_main_view(None, app, connection)
view = TxHistoryView(parent.view, controller.view)
model = TxHistoryModel(
None,
app,
......@@ -66,14 +66,14 @@ class TxHistoryController(QObject):
transactions_service,
sources_service,
)
txhistory = cls(view, model, transfer)
txhistory = cls(view, model, controller)
model.setParent(txhistory)
app.referential_changed.connect(txhistory.refresh_balance)
app.sources_refreshed.connect(txhistory.refresh_balance)
txhistory.view_in_wot.connect(app.view_in_wot)
txhistory.view.spin_page.valueChanged.connect(model.change_page)
transfer.accepted.connect(view.clear)
transfer.rejected.connect(view.clear)
controller.accepted.connect(view.clear)
controller.rejected.connect(view.clear)
return txhistory
def refresh_minimum_maximum(self):
......
......@@ -6,13 +6,16 @@ TX_HISTORY_REQUEST = """
SELECT
transactions.ts,
transactions.pubkey,
total_amount((amount * -1), amountbase) as amount,
total_amount((transactions.amount * -1), transactions.amountbase) as amount,
transactions.comment ,
transactions.sha_hash,
transactions.written_on,
transactions.txid
transactions.txid,
sources.conditions
FROM
transactions
LEFT JOIN sources ON sources.identifier = transactions.sha_hash and
(sources.conditions LIKE "%&&%" OR sources.conditions LIKE "%||%")
WHERE
transactions.currency = ?
and transactions.pubkey = ?
......@@ -24,13 +27,16 @@ UNION ALL
SELECT
transactions.ts,
transactions.pubkey,
total_amount(amount, amountbase) as amount,
total_amount(transactions.amount, transactions.amountbase) as amount,
transactions.comment ,
transactions.sha_hash,
transactions.written_on,
transactions.txid
transactions.txid,
sources.conditions
FROM
transactions
LEFT JOIN sources ON sources.identifier = transactions.sha_hash and
(sources.conditions LIKE "%&&%" OR sources.conditions LIKE "%||%")
WHERE
transactions.currency = ?
and transactions.pubkey = ?
......@@ -42,11 +48,12 @@ UNION ALL
SELECT
dividends.timestamp as ts,
dividends.pubkey ,
total_amount(amount, base) as amount,
total_amount(dividends.amount, dividends.base) as amount,
NULL as comment,
NULL as sha_hash,
dividends.block_number AS written_on,
0 as txid
0 as txid,
NULL
FROM
dividends
WHERE
......
......@@ -17,7 +17,7 @@ from sakia.data.entities import Transaction
from sakia.constants import MAX_CONFIRMATIONS
from sakia.data.processors import BlockchainProcessor
from .sql_adapter import TxHistorySqlAdapter
from sakia.data.repositories import TransactionsRepo, DividendsRepo
from sakia.data.repositories import TransactionsRepo, DividendsRepo, SourcesRepo
from sakia.data.entities.transaction import STOPLINE_HASH
......@@ -39,6 +39,7 @@ class HistoryTableModel(QAbstractTableModel):
"block_number",
"txhash",
"raw_data",
"conditions",
)
columns_to_sql = {
......@@ -81,6 +82,7 @@ class HistoryTableModel(QAbstractTableModel):
self.sql_adapter = TxHistorySqlAdapter(self.app.db.conn)
self.transactions_repo = TransactionsRepo(self.app.db.conn)
self.dividends_repo = DividendsRepo(self.app.db.conn)
self.sources_repo = SourcesRepo(self.app.db.conn)
self.current_page = 0
self.ts_from = ts_from
self.ts_to = ts_to
......@@ -137,6 +139,20 @@ class HistoryTableModel(QAbstractTableModel):
)
def change_transfer(self, transfer):
# capture sources associated with tx
sources = self.sources_repo.get_all(
currency=transfer.currency, identifier=transfer.sha_hash
)
# capture complex conditions
sources_conditions = [
source.conditions
for source in sources
if "%&&%" in source.conditions or "%||%" in source.conditions
]
transfer.conditions = (
sources_conditions[0] if len(sources_conditions) > 0 else None
)
for i, data in enumerate(self.transfers_data):
if (
data[HistoryTableModel.columns_types.index("txhash")]
......@@ -232,6 +248,7 @@ class HistoryTableModel(QAbstractTableModel):
block_number,
transfer.sha_hash,
transfer,
transfer.conditions,
)
def data_sent(self, transfer):
......@@ -267,6 +284,7 @@ class HistoryTableModel(QAbstractTableModel):
block_number,
transfer.sha_hash,
transfer,
transfer.conditions,
)
def data_dividend(self, dividend):
......@@ -299,6 +317,7 @@ class HistoryTableModel(QAbstractTableModel):
block_number,
"",
dividend,
dividend.conditions,
)
def init_transfers(self):
......@@ -312,7 +331,7 @@ class HistoryTableModel(QAbstractTableModel):
pubkey=self.connection.pubkey,
sha_hash=data[4],
)
transfer.conditions = data[7]
if transfer.state != Transaction.DROPPED:
if data[4] == STOPLINE_HASH:
self.transfers_data.append(self.data_stopline(transfer))
......@@ -327,6 +346,7 @@ class HistoryTableModel(QAbstractTableModel):
pubkey=self.connection.pubkey,
block_number=data[5],
)
dividend.conditions = data[7]
self.transfers_data.append(self.data_dividend(dividend))
self.endResetModel()
......@@ -334,7 +354,7 @@ class HistoryTableModel(QAbstractTableModel):
return len(self.transfers_data)
def columnCount(self, parent):
return len(HistoryTableModel.columns_types) - 6
return 4
def sort(self, main_column, order):
self.main_column_id = self.columns_types[main_column]
......@@ -374,7 +394,9 @@ class HistoryTableModel(QAbstractTableModel):
block_data = self.transfers_data[row][
HistoryTableModel.columns_types.index("block_number")
]
conditions_data = self.transfers_data[row][
HistoryTableModel.columns_types.index("conditions")
]
if state_data == Transaction.VALIDATED and block_data:
current_confirmations = (
self.blockchain_processor.current_buid(self.app.currency).number
......@@ -428,6 +450,9 @@ class HistoryTableModel(QAbstractTableModel):
font.setItalic(True)
else:
font.setItalic(False)
# if special lock conditions on a source coming from this transaction...
if conditions_data is not None:
font.setUnderline(True)
return font
if role == Qt.ForegroundRole:
......
......@@ -20,6 +20,10 @@ class PasswordInputView(QWidget, Ui_PasswordInputWidget):
self.button_box.button(QDialogButtonBox.Ok).setEnabled(False)
self.layout().addWidget(self.button_box)
self.button_box.hide()
font = self.edit_secret_key.font()
font.setBold(False)
self.edit_secret_key.setFont(font)
self.edit_password.setFont(font)
def error(self, text):
self.label_info.setText(text)
......
This diff is collapsed.
......@@ -21,6 +21,7 @@ class TransferModel(QObject):
app = attr.ib()
connection = attr.ib(default=None)
resent_transfer = attr.ib(default=None)
current_source = attr.ib(default=None)
_blockchain_processor = attr.ib(default=None)
_sources_processor = attr.ib(default=None)
_connections_processor = attr.ib(default=None)
......@@ -90,15 +91,27 @@ class TransferModel(QObject):
self.connection = connections[index]
async def send_money(
self, recipient, secret_key, password, amount, amount_base, comment
self,
recipient,
secret_key,
password,
amount,
amount_base,
comment,
lock_mode,
source,
):
"""
Send money to given recipient using the account
:param lock_mode:
:param str recipient:
:param str password:
:param str secret_key:
:param int amount:
:param int amount_base:
:param str comment:
:param str password:
:param int lock_mode:
:param Source source:
:return: the result of the send
"""
......@@ -110,6 +123,8 @@ class TransferModel(QObject):
amount,
amount_base,
comment,
lock_mode,
source,
)
for transaction in transactions:
self.app.sources_service.parse_transaction_outputs(
......@@ -117,7 +132,7 @@ class TransferModel(QObject):
)
for conn in self._connections_processor.connections():
if conn.pubkey == recipient:
self.app.sources_service.parse_transaction_inputs(
self.app.sources_service.consume_sources_from_transaction_inputs(
recipient, transaction
)
new_tx = self.app.transactions_service.parse_sent_transaction(
......
This diff is collapsed.
......@@ -18,6 +18,7 @@ class TransferView(QWidget, Ui_TransferMoneyWidget):
WRONG_PASSWORD = 2
NO_RECEIVER = 3
WRONG_RECIPIENT = 4
SOURCE_LOCKED = 5
class RecipientMode(Enum):
PUBKEY = 1
......@@ -45,6 +46,10 @@ class TransferView(QWidget, Ui_TransferMoneyWidget):
False,
QT_TRANSLATE_NOOP("TransferView", "Incorrect receiver address or pubkey"),
),
ButtonBoxState.SOURCE_LOCKED: (
False,
QT_TRANSLATE_NOOP("TransferView", "Source locked"),
),
}
def __init__(
......@@ -60,7 +65,9 @@ class TransferView(QWidget, Ui_TransferMoneyWidget):
super().__init__(parent)
self.setupUi(self)
regexp = QRegExp("^([ a-zA-Z0-9-_:/;*?\[\]\(\)\\\?!^+=@&~#{}|<>%.]{0,255})$")
regexp = QRegExp(
"^([ a-zA-Z0-9-_:/;*?\\[\\]\\(\\)\\\\?!^+=@&~#{}|<>%.]{0,255})$"
)
validator = QRegExpValidator(regexp)
self.edit_message.setValidator(validator)
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -8,7 +8,7 @@ from sakia.data.entities.transaction import (
)
from duniterpy.documents import Transaction as TransactionDoc
from duniterpy.documents import SimpleTransaction, Block
from sakia.data.entities import Dividend
from sakia.data.entities import Dividend, Source
from duniterpy.api import bma
import logging
......@@ -53,7 +53,9 @@ class TransactionsService(QObject):
:param sakia.data.entities.Transaction transaction: The transaction to parse
:return: The list of transfers sent
"""
if not self._transactions_processor.find_by_hash(pubkey, transaction.sha_hash):
if not self._transactions_processor.find_by_pubkey_and_hash(
pubkey, transaction.sha_hash
):
txid = self._transactions_processor.next_txid(transaction.currency, -1)
tx = parse_transaction_doc(