Skip to content

Commit

Permalink
Merge pull request #419 from skalenetwork/hotfix/redis-dropped-tx
Browse files Browse the repository at this point in the history
Hofix redis wallet adapter dropped tx
  • Loading branch information
badrogger authored Jun 9, 2021
2 parents cd7d6ed + f5dea01 commit c7d4f54
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 9 deletions.
29 changes: 20 additions & 9 deletions skale/wallets/redis_wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,19 @@ class RedisAdapterError(Exception):
pass


class RedisAdapterSendError(RedisAdapterError):
class DroppedError(RedisAdapterError):
pass


class RedisAdapterWaitError(RedisAdapterError):
class EmptyStatusError(RedisAdapterError):
pass


class AdapterSendError(RedisAdapterError):
pass


class AdapterWaitError(RedisAdapterError):
pass


Expand Down Expand Up @@ -78,6 +86,7 @@ def _make_raw_id(cls) -> bytes:
unique = binascii.b2a_hex(os.urandom(cls.ID_SIZE // 2))
return prefix + unique

@classmethod
def _make_score(cls, priority: int) -> int:
ts = int(time.time())
return priority * 10 ** len(str(ts)) + ts
Expand Down Expand Up @@ -130,7 +139,7 @@ def sign_and_send(
return self._to_id(raw_id)
except Exception as err:
logger.exception(f'Sending {tx} with redis wallet errored')
raise RedisAdapterSendError(err)
raise AdapterSendError(err)

def get_status(self, tx_id: str) -> str:
return self.get_record(tx_id)['status']
Expand All @@ -151,16 +160,18 @@ def wait(
while time.time() - start_ts < timeout:
try:
status = self.get_status(tx_id)
if status in ('SUCCESS', 'FAILED', 'DROPPED'):
if status == 'DROPPED':
break
if status in ('SUCCESS', 'FAILED'):
r = self.get_record(tx_id)
return get_receipt(self.wallet._web3, r['tx_hash'])
except Exception:
except Exception as err:
logger.exception(f'Waiting for tx {tx_id} errored')
raise RedisAdapterError(f'Waiting for {tx_id} failed')
raise AdapterWaitError(err)

if status is None:
raise RedisAdapterError('Tx status is None')
raise EmptyStatusError('Tx status is None')
if status == 'DROPPED':
raise RedisAdapterWaitError('Tx was dropped after max retries')
raise DroppedError('Tx was dropped after max retries')
else:
raise RedisAdapterWaitError(f'Tx finished with status {status}')
raise AdapterWaitError(f'Tx finished with status {status}')
11 changes: 11 additions & 0 deletions tests/helper.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
""" SKALE test utilities """

from contextlib import contextmanager
from timeit import default_timer as timer

from mock import Mock, MagicMock
from web3 import Web3

Expand Down Expand Up @@ -39,3 +42,11 @@ def init_skale_allocator(
) -> SkaleAllocator:
wallet = Web3Wallet(ETH_PRIVATE_KEY, web3)
return SkaleAllocator(ENDPOINT, TEST_ALLOCATOR_ABI_FILEPATH, wallet)


@contextmanager
def in_time(seconds):
start_ts = timer()
yield
ts_diff = timer() - start_ts
assert ts_diff < seconds, (ts_diff, seconds)
100 changes: 100 additions & 0 deletions tests/wallets/redis_adapter_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
from datetime import datetime

import mock
import pytest
from freezegun import freeze_time

from skale.wallets.redis_wallet import (
AdapterSendError,
AdapterWaitError,
DroppedError,
EmptyStatusError,
RedisWalletAdapter
)

from tests.helper import in_time


@pytest.fixture
def rdp(skale):
return RedisWalletAdapter(mock.Mock(), 'transactions', skale.wallet)


class RedisTestError(Exception):
pass


def test_make_raw_id():
tx_id = RedisWalletAdapter._make_raw_id()
assert tx_id.startswith(b'tx-')
assert len(tx_id) == 19


def test_make_score():
cts = 1623233060
dt = datetime.utcfromtimestamp(cts)
with freeze_time(dt):
score = RedisWalletAdapter._make_score(priority=5)
assert score == 51623233060


def test_make_record():
tx = {
'from': '0x1',
'to': '0x2',
'value': 1,
'gasPrice': 1,
'gas': 22000,
'nonce': 1,
'chainId': 1
}
score = '51623233060'
tx_id, r = RedisWalletAdapter._make_record(tx, score, 2)
assert tx_id.startswith(b'tx-') and len(tx_id) == 19
assert r == b'{"status": "PROPOSED", "score": "51623233060", "multiplier": 2, "tx_hash": null, "from": "0x1", "to": "0x2", "value": 1, "gasPrice": 1, "gas": 22000, "nonce": 1, "chainId": 1}' # noqa


def test_sign_and_send(rdp):
tx = {
'from': '0x1',
'to': '0x2',
'value': 1,
'gasPrice': 1,
'gas': 22000,
'nonce': 1,
'chainId': 1
}
tx_id = rdp.sign_and_send(tx, multiplier=2, priority=5)
assert tx_id.startswith('tx-') and len(tx_id) == 19

rdp.rs.pipeline = mock.Mock(side_effect=RedisTestError('rtest'))
with pytest.raises(AdapterSendError):
tx_id = rdp.sign_and_send(tx, multiplier=2, priority=5)


def test_wait(rdp):
tx_id = 'tx-tttttttttttttttt'
rdp.get_status = mock.Mock(return_value=None)
with in_time(3):
with pytest.raises(EmptyStatusError):
rdp.wait(tx_id, timeout=2)

rdp.get_status = mock.Mock(return_value='DROPPED')
with in_time(2):
with pytest.raises(DroppedError):
rdp.wait(tx_id, timeout=100)

rdp.get_status = mock.Mock(side_effect=RedisTestError('test'))
with in_time(2):
with pytest.raises(AdapterWaitError):
rdp.wait(tx_id, timeout=100)

rdp.get_status = mock.Mock(return_value='SUCCESS')
rdp.get_record = mock.Mock(return_value={'tx_hash': 'test'})
fake_receipt = {'test': 'test'}
with mock.patch(
'skale.wallets.redis_wallet.get_receipt',
return_value=fake_receipt
):
with in_time(2):
assert rdp.wait(tx_id, timeout=100) == fake_receipt

0 comments on commit c7d4f54

Please sign in to comment.