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

Commit

Permalink
Read bulk_payout logs to avoid double payouts
Browse files Browse the repository at this point in the history
  • Loading branch information
alidzm committed Nov 30, 2022
1 parent 1886fe6 commit 27c006e
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 14 deletions.
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ hmt-basemodels = "==0.1.18"
py-solc-x = "*"
sphinx = "*"
web3 = "==5.24.0"
pysha3 = "==1.0.2"

[requires]
python_version = "3.10"
Expand Down
29 changes: 28 additions & 1 deletion Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 32 additions & 0 deletions hmt_escrow/eth_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from web3.providers.auto import load_provider_from_uri
from web3.providers.eth_tester import EthereumTesterProvider
from web3.types import TxReceipt
import sha3

from hmt_escrow.kvstore_abi import abi as kvstore_abi

Expand Down Expand Up @@ -437,3 +438,34 @@ def set_pub_key_at_addr(
}

return handle_transaction(txn_func, *func_args, **txn_info)


def get_entity_topic(contract: str, eventname: str) -> str:
"""
Args:
contract (str): contract name ex.: EscrowFactory.sol:EscrowFactory.
eventname (str): event name to find in abi.
Returns
str: returns keccak_256 hash of event name with input parameters.
"""
contract_interface = get_contract_interface(
"{}/{}".format(CONTRACT_FOLDER, contract)
)
s = ""

for entity in contract_interface["abi"]:
eventName = entity.get("name")
if eventName == eventname:
s += eventName + "("
inputs = entity.get("inputs", [])
inputTypes = []
for input in inputs:
inputTypes.append(input.get("internalType"))
s += ",".join(inputTypes) + ")"

k = sha3.keccak_256()
k.update(s.encode("utf-8"))

return k.hexdigest()
42 changes: 38 additions & 4 deletions hmt_escrow/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from hmt_escrow import utils
from hmt_escrow.eth_bridge import (
get_hmtoken,
get_entity_topic,
get_escrow,
get_factory,
deploy_factory,
Expand All @@ -26,6 +27,7 @@
from hmt_escrow.storage import download, upload, get_public_bucket_url, get_key_from_url

GAS_LIMIT = int(os.getenv("GAS_LIMIT", 4712388))
BULK_TRANSFER_EVENT = get_entity_topic("HMToken.sol:HMToken", "BulkTransfer")

# Explicit env variable that will use s3 for storing results.

Expand Down Expand Up @@ -617,6 +619,7 @@ def bulk_payout(
bool: returns True if paying to ethereum addresses and oracles succeeds.
"""
bulk_paid = False
txn_event = "Bulk payout"
txn_func = self.job_contract.functions.bulkPayOut
txn_info = {
Expand Down Expand Up @@ -646,23 +649,35 @@ def bulk_payout(
func_args = [eth_addrs, hmt_amounts, url, hash_, 1]

try:
handle_transaction_with_retry(txn_func, self.retry, *func_args, **txn_info)
return self._bulk_paid() is True
tx_receipt = handle_transaction_with_retry(
txn_func,
self.retry,
*func_args,
**txn_info,
)
bulk_paid = self._check_transfer_event(tx_receipt)

except Exception as e:
LOG.warn(
f"{txn_event} failed with main credentials: {self.gas_payer}, {self.gas_payer_priv} due to {e}. Using secondary ones..."
)

if bulk_paid:
return bulk_paid

LOG.warn(
f"{txn_event} failed with main credentials: {self.gas_payer}, {self.gas_payer_priv}. Using secondary ones..."
)

raffle_txn_res = self._raffle_txn(
self.multi_credentials, txn_func, func_args, txn_event
)
bulk_paid = raffle_txn_res["txn_succeeded"]
bulk_paid = self._check_transfer_event(raffle_txn_res["tx_receipt"])

if not bulk_paid:
LOG.warning(f"{txn_event} failed with all credentials.")

return bulk_paid is True
return bulk_paid

def abort(self) -> bool:
"""Kills the contract and returns the HMT back to the gas payer.
Expand Down Expand Up @@ -1508,3 +1523,22 @@ def _raffle_txn(self, multi_creds, txn_func, txn_args, txn_event) -> RaffleTxn:
)

return {"txn_succeeded": txn_succeeded, "tx_receipt": tx_receipt}

def _check_transfer_event(self, tx_receipt: Optional[TxReceipt]) -> bool:
"""
Check if transaction receipt has bulkTransfer event, to make sure that transaction was successful.
Args:
tx_receipt (Optional[TxReceipt]): a dict with transaction receipt.
Returns:
bool: returns True if transaction has bulkTransfer event, otherwise returns False.
"""
if not tx_receipt:
return False

for log in tx_receipt.get("logs", {}):
for topic in log["topics"]:
if BULK_TRANSFER_EVENT in topic.hex():
return True
return False
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hmt-escrow",
"version": "0.14.6",
"version": "0.14.7",
"description": "Launch escrow contracts to the HUMAN network",
"main": "truffle.js",
"directories": {
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

setuptools.setup(
name="hmt-escrow",
version="0.14.6",
version="0.14.7",
author="HUMAN Protocol",
description="A python library to launch escrow contracts to the HUMAN network.",
url="https://github.com/humanprotocol/hmt-escrow",
Expand All @@ -18,6 +18,7 @@
"boto3",
"cryptography",
"hmt-basemodels>=0.1.18",
"pysha3==1.0.2",
"web3==5.24.0",
],
)
34 changes: 28 additions & 6 deletions test/hmt_escrow/test_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,8 @@ def test_job_bulk_payout(self):
]
self.assertTrue(self.job.bulk_payout(payouts, {}, self.rep_oracle_pub_key))

def test_job_bulk_payout_with_encryption_option(self):
"""Tests whether final results must be persisted in storage encrypted or plain."""
def test_job_bulk_payout_with_false_encryption_option(self):
"""Test that final results are stored encrypted"""
job = Job(self.credentials, manifest)
self.assertEqual(job.launch(self.rep_oracle_pub_key), True)
self.assertEqual(job.setup(), True)
Expand All @@ -253,10 +253,24 @@ def test_job_bulk_payout_with_encryption_option(self):
encrypt_data=False,
use_public_bucket=False,
)
mock_upload.reset_mock()

# Testing option as: encrypt final results: encrypt_final_results=True
def test_job_bulk_payout_with_true_encryption_option(self):
"""Test that final results are stored uncrypted"""
job = Job(self.credentials, manifest)
self.assertEqual(job.launch(self.rep_oracle_pub_key), True)
self.assertEqual(job.setup(), True)

payouts = [("0x852023fbb19050B8291a335E5A83Ac9701E7B4E6", Decimal("100.0"))]

final_results = {"results": 0}

mock_upload = MagicMock(return_value=("hash", "url"))

# Testing option as: encrypt final results: encrypt_final_results=True
with patch("hmt_escrow.job.upload") as mock_upload:
# Bulk payout with final results as plain (not encrypted)
mock_upload.return_value = ("hash", "url")

job.bulk_payout(
payouts=payouts,
results=final_results,
Expand Down Expand Up @@ -325,15 +339,19 @@ def test_job_bulk_payout_with_full_qualified_url(self):
self.assertEqual(job.setup(), True)

payouts = [("0x852023fbb19050B8291a335E5A83Ac9701E7B4E6", Decimal("100.0"))]

final_results = {"results": 0}

with patch(
"hmt_escrow.job.handle_transaction_with_retry"
) as transaction_retry_mock, patch("hmt_escrow.job.upload") as upload_mock:
) as transaction_retry_mock, patch(
"hmt_escrow.job.upload"
) as upload_mock, patch.object(
Job, "_check_transfer_event"
) as _check_transfer_event_mock:
key = "abcdefg"
hash_ = f"s3{key}"
upload_mock.return_value = hash_, key
_check_transfer_event_mock.return_value = True

# Bulk payout with option to store final results privately
job.bulk_payout(
Expand Down Expand Up @@ -397,6 +415,10 @@ def test_retrieving_encrypted_final_results(self):
self.assertEqual(persisted_final_results, final_results)

# Bulk payout with encryption OFF
job = Job(self.credentials, manifest)
self.assertEqual(job.launch(self.rep_oracle_pub_key), True)
self.assertEqual(job.setup(), True)

job.bulk_payout(
payouts=payouts,
results=final_results,
Expand Down

0 comments on commit 27c006e

Please sign in to comment.