Skip to content
This repository has been archived by the owner on Oct 31, 2023. It is now read-only.

Persistent topups #4345

Merged
merged 10 commits into from
Jul 2, 2019
38 changes: 38 additions & 0 deletions golem/ethereum/incomeskeeper.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,44 @@ class IncomesKeeper:
"""Keeps information about payments received from other nodes
"""

@staticmethod
def received_eth_transfer(
jiivan marked this conversation as resolved.
Show resolved Hide resolved
tx_hash: str,
sender_address: str,
recipient_address: str,
amount: int,
):
model.WalletOperation.create(
tx_hash=tx_hash,
direction=model.WalletOperation.DIRECTION.incoming,
operation_type=model.WalletOperation.TYPE.transfer,
status=model.WalletOperation.STATUS.confirmed,
sender_address=sender_address,
recipient_address=recipient_address,
amount=amount,
currency=model.WalletOperation.CURRENCY.ETH,
gas_cost=0,
)

@staticmethod
def received_gnt_transfer(
tx_hash: str,
sender_address: str,
recipient_address: str,
amount: int,
):
model.WalletOperation.create(
tx_hash=tx_hash,
direction=model.WalletOperation.DIRECTION.incoming,
operation_type=model.WalletOperation.TYPE.transfer,
status=model.WalletOperation.STATUS.confirmed,
sender_address=sender_address,
recipient_address=recipient_address,
amount=amount,
currency=model.WalletOperation.CURRENCY.GNT,
gas_cost=0,
)

@staticmethod
def received_batch_transfer(
tx_hash: str,
Expand Down
44 changes: 41 additions & 3 deletions golem/ethereum/paymentskeeper.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@


class PaymentsDatabase(object):
""" Save and retrieve from database information about payments that this node has to make / made
"""Save and retrieve from database information
about payments that this node has to make / made
"""

@staticmethod
def get_payment_value(subtask_id: str):
""" Return value of a payment that was done to the same node and for the same task as payment for payment_info
"""Returns value of a payment
that was done to the same node and for the same
task as payment for payment_info
"""
return PaymentsDatabase.get_payment_for_subtask(subtask_id)

Expand Down Expand Up @@ -64,12 +67,47 @@ def get_newest_payment(num: Optional[int] = None,


class PaymentsKeeper:
""" Keeps information about payments for tasks that should be processed and send or received. """
"""Keeps information about payments for tasks
that should be processed and send or received.
"""

def __init__(self) -> None:
""" Create new payments keeper instance"""
self.db = PaymentsDatabase()

@staticmethod
def sent_transfer(
jiivan marked this conversation as resolved.
Show resolved Hide resolved
tx_hash: str,
sender_address: str,
recipient_address: str,
amount: int,
currency: model.WalletOperation.CURRENCY,
):
try:
operation = model.WalletOperation.select() \
.where(
model.WalletOperation.tx_hash == tx_hash,
jiivan marked this conversation as resolved.
Show resolved Hide resolved
model.WalletOperation.operation_type # noqa
== model.WalletOperation.TYPE.transfer,
model.WalletOperation.direction # noqa
== model.WalletOperation.DIRECTION.outgoing,
model.WalletOperation.currency == currency
).get()
operation.status = model.WalletOperation.STATUS.confirmed
operation.save()
except model.WalletOperation.DoesNotExist:
jiivan marked this conversation as resolved.
Show resolved Hide resolved
model.WalletOperation.create(
tx_hash=tx_hash,
direction=model.WalletOperation.DIRECTION.outgoing,
operation_type=model.WalletOperation.TYPE.transfer,
status=model.WalletOperation.STATUS.confirmed,
sender_address=sender_address,
recipient_address=recipient_address,
amount=amount,
currency=currency,
gas_cost=0,
)

def get_list_of_all_payments(self, num: Optional[int] = None,
interval: Optional[datetime.timedelta] = None):
# This data is used by UI.
Expand Down
87 changes: 81 additions & 6 deletions golem/ethereum/transactionsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,46 @@ def _subscribe_to_events(self) -> None:
)
)

self._sci.subscribe_to_direct_incoming_eth_transfers(
address=self._sci.get_eth_address(),
from_block=from_block,
cb=lambda event: ik.received_eth_transfer(
tx_hash=event.tx_hash,
sender_address=event.from_address,
recipient_address=event.to_address,
amount=event.amount,
),
)

self._sci.subscribe_to_gnt_transfers(
from_address=None,
to_address=self._sci.get_eth_address(),
from_blokc=from_block,
cb=lambda event: ik.received_gnt_transfer(
tx_hash=event.tx_hash,
sender_address=event.from_address,
recipient_address=event.to_address,
amount=event.amount,
),
)
# Overcome mypy limitations
gnt: model.WalletOperation.CURRENCY
gnt = model.WalletOperation.CURRENCY.GNT # type: ignore
# EO mypy limitations

self._sci.subscribe_to_gnt_transfers(
from_address=self._sci.get_eth_address(),
to_address=None,
from_block=from_block,
cb=lambda event: self._payments_keeper.sent_transfer(
tx_hash=event.tx_hash,
sender_address=event.from_address,
recipient_address=event.to_address,
amount=event.amount,
currency=gnt,
),
)

if self.deposit_contract_available:
self._sci.subscribe_to_forced_subtask_payments(
None,
Expand Down Expand Up @@ -578,9 +618,11 @@ def withdraw(
currency,
destination,
)

if gas_price is None:
gas_price = self.gas_price

if currency == 'ETH':
if gas_price is None:
gas_price = self.gas_price
gas_eth = self.get_withdraw_gas_cost(amount, destination, currency)\
* gas_price
if amount > self.get_available_eth():
Expand All @@ -589,12 +631,36 @@ def withdraw(
available=self.get_available_eth(),
currency=currency,
)
# TODO Create WalletOperation #4172
return self._sci.transfer_eth(
tx_hash = self._sci.transfer_eth(
destination,
amount - gas_eth,
gas_price,
)
model.WalletOperation.create(
tx_hash=tx_hash,
direction=model.WalletOperation.DIRECTION.outgoing,
operation_type=model.WalletOperation.TYPE.transfer,
status=model.WalletOperation.STATUS.sent,
sender_address=self._sci.get_eth_address(),
recipient_address=destination,
amount=amount,
currency=model.WalletOperation.CURRENCY.ETH,
gas_cost=gas_eth,
)

def on_eth_receipt(receipt):
if not receipt.status:
log.error("Failed ETH withdrawal: %r", receipt)
return
self._payments_keeper.sent_transfer(
tx_hash=receipt.tx_hash,
sender_address=receipt.from_address,
recipient_address=receipt.to_address,
amount=receipt.amount,
currency=model.WalletOperation.CURRENCY.ETH
)
self._sci.on_transaction_confirmed(tx_hash, on_eth_receipt)
return tx_hash

if currency == 'GNT':
if amount > self.get_available_gnt():
Expand All @@ -603,18 +669,27 @@ def withdraw(
available=self.get_available_gnt(),
currency=currency,
)
# TODO Create WalletOperation #4172
tx_hash = self._sci.convert_gntb_to_gnt(
destination,
amount,
gas_price,
)
model.WalletOperation.create(
tx_hash=tx_hash,
direction=model.WalletOperation.DIRECTION.outgoing,
operation_type=model.WalletOperation.TYPE.transfer,
status=model.WalletOperation.STATUS.sent,
jiivan marked this conversation as resolved.
Show resolved Hide resolved
sender_address=self._sci.get_eth_address(),
recipient_address=destination,
amount=amount,
currency=model.WalletOperation.CURRENCY.GNT,
gas_cost=gas_price * self._sci.GAS_GNT_TRANSFER,
jiivan marked this conversation as resolved.
Show resolved Hide resolved
)

def on_receipt(receipt) -> None:
self._gntb_withdrawn -= amount
if not receipt.status:
log.error("Failed GNTB withdrawal: %r", receipt)
# TODO Update WalletOperation #4172
self._sci.on_transaction_confirmed(tx_hash, on_receipt)
self._gntb_withdrawn += amount
return tx_hash
Expand Down
24 changes: 24 additions & 0 deletions tests/golem/ethereum/test_incomeskeeper.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,3 +289,27 @@ def test_update_overdue_incomes_already_marked_as_overdue(self):
income.wallet_operation.refresh().status,
model.WalletOperation.STATUS.overdue,
)

def test_received_eth_transfer(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those two tests do not test different things the way they are implemented right now.
So either remove one of them or introduce some more assertions.

self.incomes_keeper.received_eth_transfer(
tx_hash=f"0x{'0'*64}",
sender_address=random_eth_address(),
recipient_address=random_eth_address(),
amount=1,
)
self.assertEqual(
model.WalletOperation.select().count(),
1,
)

def test_received_gnt_transfer(self):
self.incomes_keeper.received_gnt_transfer(
tx_hash=f"0x{'0'*64}",
sender_address=random_eth_address(),
recipient_address=random_eth_address(),
amount=1,
)
self.assertEqual(
model.WalletOperation.select().count(),
1,
)
24 changes: 24 additions & 0 deletions tests/golem/ethereum/test_paymentskeeper.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
from golem_messages.factories.helpers import (
random_eth_address,
)

from golem import model
from golem.ethereum.paymentskeeper import PaymentsDatabase
from golem.ethereum.paymentskeeper import PaymentsKeeper
from golem.tools.testwithdatabase import TestWithDatabase
from tests.factories.model import TaskPayment as TaskPaymentFactory

Expand Down Expand Up @@ -42,3 +47,22 @@ def test_subtasks_payments(self):

payments = pd.get_subtasks_payments(['id1', 'id4', 'id2'])
assert self._get_ids(payments) == ['id1', 'id2']


class TestPaymentsKeeper(TestWithDatabase):
def setUp(self):
super().setUp()
self.payments_keeper = PaymentsKeeper()

def test_sent_transfer(self):
jiivan marked this conversation as resolved.
Show resolved Hide resolved
self.payments_keeper.sent_transfer(
tx_hash=f"0x{'0'*64}",
sender_address=random_eth_address(),
recipient_address=random_eth_address(),
amount=1,
currency=model.WalletOperation.CURRENCY.GNT,
)
self.assertEqual(
model.WalletOperation.select().count(),
1,
)
7 changes: 3 additions & 4 deletions tests/golem/ethereum/test_transactionsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def setUp(self):
self.sci.get_gntb_balance.return_value = 0
self.sci.GAS_PER_PAYMENT = 20000
self.sci.get_deposit_locked_until.return_value = 0
self.sci.GAS_GNT_TRANSFER = 2
self.ets = self._make_ets()

def _make_ets(
Expand Down Expand Up @@ -209,7 +210,6 @@ def test_convert_gnt(self):
self.sci.get_eth_balance.return_value = denoms.ether
self.sci.get_current_gas_price.return_value = 0
self.sci.GAS_OPEN_GATE = 10
self.sci.GAS_GNT_TRANSFER = 2
self.sci.GAS_TRANSFER_FROM_GATE = 5
self.ets._refresh_balances()

Expand Down Expand Up @@ -240,7 +240,6 @@ def test_topup_while_convert(self):
self.sci.get_gnt_balance.return_value = amount1
self.sci.get_eth_balance.return_value = denoms.ether
self.sci.get_current_gas_price.return_value = 0
self.sci.GAS_GNT_TRANSFER = 2
self.sci.GAS_TRANSFER_FROM_GATE = 5
self.ets._refresh_balances()

Expand Down Expand Up @@ -356,8 +355,8 @@ def setUp(self):
self.sci.estimate_transfer_eth_gas.return_value = self.gas_cost
self.dest = '0x' + 40 * 'd'

self.eth_tx = '0xee'
self.gntb_tx = '0xfad'
self.eth_tx = f'0x{"e"*64}'
self.gntb_tx = f'0x{"f"*64}'
self.sci.transfer_eth.return_value = self.eth_tx
self.sci.convert_gntb_to_gnt.return_value = self.gntb_tx

Expand Down