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

Commit

Permalink
Merge pull request #4458 from golemfactory/gnt_withdraw_confirmation
Browse files Browse the repository at this point in the history
Confirm outgoing GNT
  • Loading branch information
jiivan authored Jul 17, 2019
2 parents 2e29c99 + 7964176 commit 736402f
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 106 deletions.
1 change: 1 addition & 0 deletions golem/ethereum/incomeskeeper.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def received_batch_transfer(
e.wallet_operation.amount += received
amount_left -= received
e.wallet_operation.tx_hash = tx_hash
e.wallet_operation.status = model.WalletOperation.STATUS.confirmed
e.wallet_operation.save()

if e.missing_amount == 0:
Expand Down
3 changes: 1 addition & 2 deletions golem/ethereum/paymentprocessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def load_from_db(self):
for sent_payment in model.TaskPayment \
.payments() \
.where(
model.WalletOperation.status == \
model.WalletOperation.status ==
model.WalletOperation.STATUS.sent,
):
if sent_payment.wallet_operation.tx_hash not in sent:
Expand Down Expand Up @@ -178,7 +178,6 @@ def add( # pylint: disable=too-many-arguments
expected_amount=value,
)


self._awaiting.add(payment)
self._gntb_reserved += value

Expand Down
44 changes: 25 additions & 19 deletions golem/ethereum/paymentskeeper.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,25 +75,6 @@ def __init__(self) -> None:
""" Create new payments keeper instance"""
self.db = PaymentsDatabase()

@staticmethod
def confirmed_transfer(
tx_hash: str,
gas_cost: int,
):
try:
operation = model.WalletOperation.select() \
.where(
model.WalletOperation.tx_hash == tx_hash,
).get()
operation.status = model.WalletOperation.STATUS.confirmed
operation.gas_cost = gas_cost
operation.save()
except model.WalletOperation.DoesNotExist:
logger.warning(
"Got confirmation of unknown transfer. tx_hash=%s",
tx_hash,
)

def get_list_of_all_payments(self, num: Optional[int] = None,
interval: Optional[datetime.timedelta] = None):
# This data is used by UI.
Expand Down Expand Up @@ -124,3 +105,28 @@ def get_subtasks_payments(
self,
subtask_ids: Iterable[str]) -> List[model.TaskPayment]:
return self.db.get_subtasks_payments(subtask_ids)

@staticmethod
def confirmed_transfer(
tx_hash: str,
successful: bool,
gas_cost: int,
) -> None:
try:
operation = model.WalletOperation.select() \
.where(
model.WalletOperation.tx_hash == tx_hash,
).get()
except model.WalletOperation.DoesNotExist:
logger.warning(
"Got confirmation of unknown transfer. tx_hash=%s",
tx_hash,
)
return
if not successful:
logger.error("Failed transaction. tx_hash=%s", tx_hash)
operation.on_failed(gas_cost=gas_cost)
operation.save()
return
operation.on_confirmed(gas_cost=gas_cost)
operation.save()
81 changes: 41 additions & 40 deletions golem/ethereum/transactionsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@

if TYPE_CHECKING:
# pylint: disable=unused-import,ungrouped-imports
from golem_sci import events as sci_events
from golem_sci import structs as sci_structs


log = logging.getLogger(__name__)
Expand Down Expand Up @@ -319,30 +319,17 @@ def _subscribe_to_events(self) -> None:
),
)

def _outgoing_gnt_transfer_confirmed(
event: 'sci_events.GntTransferEvent',
):
assert self._sci is not None # mypy :(
receipt: TransactionReceipt = self._sci.get_transaction_receipt(
event.tx_hash,
)
gas_price = self._sci.get_transaction_gas_price(
tx_hash=event.tx_hash,
unconfirmed_query = model.WalletOperation.unconfirmed_payments()
for operation in unconfirmed_query.iterator():
log.debug(
'Setting transaction confirmation listener. tx_hash=%s',
operation.tx_hash,
)
# Mined transaction won't return None
assert isinstance(gas_price, int)
self._payments_keeper.confirmed_transfer(
tx_hash=event.tx_hash,
gas_cost=receipt.gas_used * gas_price,
self._sci.on_transaction_confirmed(
tx_hash=operation.tx_hash,
cb=self._on_confirmed,
)

self._sci.subscribe_to_gnt_transfers(
from_address=self._sci.get_eth_address(),
to_address=None,
from_block=from_block,
cb=_outgoing_gnt_transfer_confirmed,
)

if self.deposit_contract_available:
self._sci.subscribe_to_forced_subtask_payments(
None,
Expand Down Expand Up @@ -612,6 +599,23 @@ def get_withdraw_gas_cost(
return self._sci.GAS_WITHDRAW
raise ValueError('Unknown currency {}'.format(currency))

@sci_required()
def _on_confirmed(
self,
receipt: 'sci_structs.TransactionReceipt',
gas_price: Optional[int] = None,
):
if gas_price is None:
assert self._sci is not None # mypy...
gas_price = self._sci.get_transaction_gas_price(receipt.tx_hash)
# Mined transactions won't return None
assert isinstance(gas_price, int)
self._payments_keeper.confirmed_transfer(
tx_hash=receipt.tx_hash,
successful=bool(receipt.status),
gas_cost=gas_price * receipt.gas_used,
)

@sci_required()
def withdraw(
self,
Expand Down Expand Up @@ -663,16 +667,13 @@ def withdraw(
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.confirmed_transfer(
tx_hash=receipt.tx_hash,
gas_cost=receipt.gas_cost * gas_price,
)
self._sci.on_transaction_confirmed(tx_hash, on_eth_receipt)
self._sci.on_transaction_confirmed(
tx_hash,
functools.partial(
self._on_confirmed,
gas_price=gas_price,
),
)
return tx_hash

if currency == 'GNT':
Expand Down Expand Up @@ -703,6 +704,10 @@ def on_receipt(receipt) -> None:
self._gntb_withdrawn -= amount
if not receipt.status:
log.error("Failed GNTB withdrawal: %r", receipt)
self._on_confirmed(
receipt=receipt,
gas_price=gas_price,
)
self._sci.on_transaction_confirmed(tx_hash, on_receipt)
self._gntb_withdrawn += amount
return tx_hash
Expand Down Expand Up @@ -822,14 +827,9 @@ def concent_deposit(
"Deposit failed",
transaction_receipt=receipt,
)

tx_gas_price = self._sci.get_transaction_gas_price(
receipt.tx_hash,
self._on_confirmed(
receipt=receipt,
)
dpayment.gas_cost = receipt.gas_used * tx_gas_price
dpayment.status = \
model.WalletOperation.STATUS.confirmed
dpayment.save()
return dpayment.tx_hash

@gnt_deposit_required()
Expand Down Expand Up @@ -873,8 +873,9 @@ def concent_withdraw(self):
tx_hash = self._sci.withdraw_deposit()
self._concent_withdraw_requested = True

def on_confirmed(_receipt) -> None:
def on_confirmed(receipt) -> None:
self._concent_withdraw_requested = False
self._on_confirmed(receipt=receipt)
self._sci.on_transaction_confirmed(tx_hash, on_confirmed)
log.info("Withdrawing concent deposit, tx: %s", tx_hash)

Expand Down
41 changes: 41 additions & 0 deletions golem/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ class STATUS(msg_dt.StringEnum):
sent = enum.auto()
confirmed = enum.auto()
overdue = enum.auto()
failed = enum.auto()

class DIRECTION(msg_dt.StringEnum):
incoming = enum.auto()
Expand Down Expand Up @@ -304,6 +305,46 @@ def deposit_transfers(cls):
== WalletOperation.TYPE.deposit_transfer,
)

@classmethod
def transfers(cls):
return cls.select() \
.where(
WalletOperation.operation_type
== WalletOperation.TYPE.transfer,
)

@classmethod
def unconfirmed_payments(cls):
return cls.select() \
.where(
cls.status.not_in([
cls.STATUS.confirmed,
cls.STATUS.failed,
]),
cls.tx_hash.is_null(False),
cls.direction ==
cls.DIRECTION.outgoing,
cls.operation_type.in_([
cls.TYPE.transfer,
cls.TYPE.deposit_transfer,
]),
)

def on_confirmed(self, gas_cost: int):
if self.operation_type not in (
self.TYPE.transfer,
self.TYPE.deposit_transfer
):
return
if self.direction != self.DIRECTION.outgoing:
return
self.gas_cost = gas_cost # type: ignore
self.status = self.STATUS.confirmed # type: ignore

def on_failed(self, gas_cost: int):
self.gas_cost = gas_cost # type: ignore
self.status = self.STATUS.failed # type: ignore


class TaskPayment(BaseModel):
wallet_operation = ForeignKeyField(WalletOperation, unique=True)
Expand Down
5 changes: 4 additions & 1 deletion golem/rpc/api/ethereum_.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ def lru_node_factory():
def _inner(node_id):
if node_id is None:
return None
if node_id.startswith('0x'):
node_id = node_id[2:]
node = lru_node(node_id)
if node is None:
node = dt_p2p.Node(key=node_id)
Expand Down Expand Up @@ -61,9 +63,10 @@ def get_incomes_list(self) -> typing.List[typing.Dict[str, typing.Any]]:
lru_node = lru_node_factory()

def item(o):
node_id = o.node if not o.node.startswith('0x') else o.node[2:]
return {
"subtask": common.to_unicode(o.subtask),
"payer": common.to_unicode(o.node),
"payer": common.to_unicode(node_id),
"value": common.to_unicode(o.wallet_operation.amount),
"status": common.to_unicode(o.wallet_operation.status.name),
"transaction": common.to_unicode(o.wallet_operation.tx_hash),
Expand Down
18 changes: 0 additions & 18 deletions tests/golem/ethereum/test_paymentskeeper.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,3 @@ 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_confirmed_transfer(self):
operation = WalletOperationFactory()
operation.save(force_insert=True)
self.payments_keeper.confirmed_transfer(
tx_hash=operation.tx_hash,
gas_cost=1,
)
self.assertEqual(
model.WalletOperation.select().count(),
1,
)
Loading

0 comments on commit 736402f

Please sign in to comment.