Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: add error handling for contract revert error #381

Merged
merged 1 commit into from
Sep 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ def __init__(self, code_msg: str):
self.message = message
super().__init__(message)

def __repr__(self):
return f"<ContractRevertError(code={self.code}, message={self.message})>"


class AuthorizationError(Exception):
pass
Expand Down
5 changes: 4 additions & 1 deletion app/model/blockchain/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@
IbetSecurityTokenCancelTransfer
)
from app.model.blockchain import IbetExchangeInterface
from app.exceptions import SendTransactionError, ContractRevertError
from app.exceptions import (
SendTransactionError,
ContractRevertError
)
from app import log
from app.utils.contract_utils import ContractUtils
from app.utils.web3_utils import Web3Wrapper
Expand Down
9 changes: 7 additions & 2 deletions batch/processor_batch_issue_redeem.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,10 @@
IbetShareRedeem
)
from app.utils.e2ee_utils import E2EEUtils
from app.exceptions import SendTransactionError

from app.exceptions import (
SendTransactionError,
ContractRevertError
)
import batch_log

process_name = "PROCESSOR-Batch-Issue-Redeem"
Expand Down Expand Up @@ -159,6 +161,9 @@ def process(self):
)
LOG.debug(f"Transaction sent successfully: {tx_hash}")
batch_data.status = 1
except ContractRevertError as e:
LOG.warning(f"Transaction reverted: upload_id=<{batch_data.upload_id}> error_code:<{e.code}> error_msg:<{e.message}>")
batch_data.status = 2
except SendTransactionError:
LOG.warning(f"Failed to send transaction: {tx_hash}")
batch_data.status = 2
Expand Down
10 changes: 7 additions & 3 deletions batch/processor_rotate_e2e_messaging_rsa_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@
from app.utils.web3_utils import Web3Wrapper
from app.exceptions import (
SendTransactionError,
ServiceUnavailableError
ServiceUnavailableError,
ContractRevertError
)
import batch_log

Expand Down Expand Up @@ -122,9 +123,9 @@ def __auto_generate_rsa_key(self, db_session: Session, base_time: int, e2e_messa
raw_keyfile_json=e2e_messaging_account.keyfile,
password=eoa_password.encode("utf-8")
)
except Exception as err:
except Exception:
LOG.exception(f"Could not get the EOA private key: "
f"account_address={e2e_messaging_account.account_address}", err)
f"account_address={e2e_messaging_account.account_address}")
return
try:
tx_hash, _ = E2EMessaging.set_public_key(
Expand All @@ -134,6 +135,9 @@ def __auto_generate_rsa_key(self, db_session: Session, base_time: int, e2e_messa
tx_from=e2e_messaging_account.account_address,
private_key=private_key
)
except ContractRevertError as e:
LOG.warning(f"Transaction reverted: account_address=<{e2e_messaging_account.account_address}> error_code:<{e.code}> error_msg:<{e.message}>")
return
except SendTransactionError:
LOG.warning(f"Failed to send transaction: account_address={e2e_messaging_account.account_address}")
return
Expand Down
21 changes: 18 additions & 3 deletions batch/processor_scheduled_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@
)
from app.exceptions import (
SendTransactionError,
ServiceUnavailableError
ServiceUnavailableError,
ContractRevertError
)
import batch_log

Expand Down Expand Up @@ -155,8 +156,8 @@ def __process(self, db_session: Session, events_list: List[ScheduledEvents]):
raw_keyfile_json=keyfile_json,
password=decrypt_password.encode("utf-8")
)
except Exception as err:
LOG.exception(f"Could not get the private key of the issuer of id:{_event.id}", err)
except Exception:
LOG.exception(f"Could not get the private key of the issuer of id:{_event.id}")
self.__sink_on_finish_event_process(
db_session=db_session,
record_id=_event.id,
Expand Down Expand Up @@ -197,6 +198,20 @@ def __process(self, db_session: Session, events_list: List[ScheduledEvents]):
record_id=_event.id,
status=1
)
except ContractRevertError as e:
LOG.warning(f"Transaction reverted: id=<{_event.id}> error_code:<{e.code}> error_msg:<{e.message}>")
self.__sink_on_finish_event_process(
db_session=db_session,
record_id=_event.id,
status=2
)
self.__sink_on_error_notification(
db_session=db_session,
issuer_address=_event.issuer_address,
code=2,
scheduled_event_id=_event.event_id,
token_type=_event.token_type
)
except SendTransactionError:
LOG.warning(f"Failed to send transaction: id=<{_event.id}>")
self.__sink_on_finish_event_process(
Expand Down
139 changes: 137 additions & 2 deletions tests/test_batch_processor_batch_issue_redeem.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@
"""
import logging
import uuid
from sqlalchemy.orm import Session
from typing import List
from unittest.mock import patch, ANY

import pytest

from eth_keyfile import decode_keyfile_json

from app.exceptions import SendTransactionError
from app.exceptions import SendTransactionError, ContractRevertError
from app.model.db import (
Account,
BatchIssueRedeemUpload,
Expand Down Expand Up @@ -702,4 +703,138 @@ def test_error_3(self, processor, db, caplog):
"upload_id": upload_id,
"error_data_id": ANY
}
assert len(_notification.metainfo["error_data_id"]) == 2
assert len(_notification.metainfo["error_data_id"]) == 2

# <Error_4>
# ContractRevertError
def test_error_4(self, processor: Processor, db: Session, caplog: pytest.LogCaptureFixture):
# Test settings
issuer_account = config_eth_account("user1")
issuer_address = issuer_account["address"]
issuer_keyfile = issuer_account["keyfile_json"]
issuer_eoa_password = E2EEUtils.encrypt("password")

token_address = "test_token_address"

target_account = config_eth_account("user2")
target_address = target_account["address"]
target_amount = 10

# Prepare data
_account = Account()
_account.issuer_address = issuer_address
_account.keyfile = issuer_keyfile
_account.eoa_password = issuer_eoa_password
_account.rsa_status = 3
db.add(_account)

upload_1_id = str(uuid.uuid4())

_upload_1 = BatchIssueRedeemUpload()
_upload_1.upload_id = upload_1_id
_upload_1.issuer_address = issuer_address
_upload_1.token_type = TokenType.IBET_STRAIGHT_BOND.value
_upload_1.token_address = token_address
_upload_1.category = BatchIssueRedeemProcessingCategory.ISSUE.value
_upload_1.processed = 0
db.add(_upload_1)

_upload_data_1 = BatchIssueRedeem()
_upload_data_1.upload_id = upload_1_id
_upload_data_1.account_address = target_address
_upload_data_1.amount = target_amount
_upload_data_1.status = 0
db.add(_upload_data_1)

upload_2_id = str(uuid.uuid4())

_upload_2 = BatchIssueRedeemUpload()
_upload_2.upload_id = upload_2_id
_upload_2.issuer_address = issuer_address
_upload_2.token_type = TokenType.IBET_SHARE.value
_upload_2.token_address = token_address
_upload_2.category = BatchIssueRedeemProcessingCategory.ISSUE.value
_upload_2.processed = 0
db.add(_upload_2)

_upload_data_2 = BatchIssueRedeem()
_upload_data_2.upload_id = upload_2_id
_upload_data_2.account_address = target_address
_upload_data_2.amount = target_amount
_upload_data_2.status = 0
db.add(_upload_data_2)

db.commit()

# mock
with (
patch(
target="app.model.blockchain.token.IbetStraightBondContract.additional_issue",
side_effect=ContractRevertError("999999")
),
patch(
target="app.model.blockchain.token.IbetShareContract.additional_issue",
side_effect=ContractRevertError("999999")
)
):
processor.process()

# Assertion: DB
_upload_1_after: BatchIssueRedeemUpload = db.query(BatchIssueRedeemUpload). \
filter(BatchIssueRedeemUpload.upload_id == upload_1_id). \
first()
assert _upload_1_after.processed == True

_upload_1_data_after: List[BatchIssueRedeem] = db.query(BatchIssueRedeem). \
filter(BatchIssueRedeem.upload_id == upload_1_id). \
all()
assert len(_upload_1_data_after) == 1
assert _upload_1_data_after[0].status == 2

_upload_2_after: BatchIssueRedeemUpload = db.query(BatchIssueRedeemUpload). \
filter(BatchIssueRedeemUpload.upload_id == upload_2_id). \
first()
assert _upload_2_after.processed == True

_upload_2_data_after: List[BatchIssueRedeem] = db.query(BatchIssueRedeem). \
filter(BatchIssueRedeem.upload_id == upload_2_id). \
all()
assert len(_upload_2_data_after) == 1
assert _upload_2_data_after[0].status == 2

# Assertion: Log
assert caplog.record_tuples.count((
LOG.name,
logging.WARNING,
f"Transaction reverted: upload_id=<{_upload_1_after.upload_id}> error_code:<999999> error_msg:<>"
)) == 1
assert caplog.record_tuples.count((
LOG.name,
logging.WARNING,
f"Transaction reverted: upload_id=<{_upload_2_after.upload_id}> error_code:<999999> error_msg:<>"
)) == 1

_notification_list = db.query(Notification).all()
assert _notification_list[0].notice_id is not None
assert _notification_list[0].issuer_address == issuer_address
assert _notification_list[0].priority == 1
assert _notification_list[0].type == NotificationType.BATCH_ISSUE_REDEEM_PROCESSED
assert _notification_list[0].code == 3 # Failed
assert _notification_list[0].metainfo == {
"category": BatchIssueRedeemProcessingCategory.ISSUE.value,
"upload_id": upload_1_id,
"error_data_id": ANY
}
assert len(_notification_list[0].metainfo["error_data_id"]) == 1

assert _notification_list[1].notice_id is not None
assert _notification_list[1].issuer_address == issuer_address
assert _notification_list[1].priority == 1
assert _notification_list[1].type == NotificationType.BATCH_ISSUE_REDEEM_PROCESSED
assert _notification_list[1].code == 3 # Failed
assert _notification_list[1].metainfo == {
"category": BatchIssueRedeemProcessingCategory.ISSUE.value,
"upload_id": upload_2_id,
"error_data_id": ANY
}
assert len(_notification_list[1].metainfo["error_data_id"]) == 1
76 changes: 73 additions & 3 deletions tests/test_batch_processor_rotate_e2e_messaging_rsa_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

SPDX-License-Identifier: Apache-2.0
"""
import logging
import pytest
from unittest import mock
from unittest.mock import (
Expand All @@ -30,6 +31,7 @@
)

from eth_keyfile import decode_keyfile_json
from sqlalchemy.orm import Session

from app.model.blockchain import E2EMessaging
from app.model.db import (
Expand All @@ -38,16 +40,22 @@
)
from app.utils.contract_utils import ContractUtils
from app.utils.e2ee_utils import E2EEUtils
from app.exceptions import SendTransactionError
from app.exceptions import SendTransactionError, ContractRevertError
import batch.processor_rotate_e2e_messaging_rsa_key as processor_rotate_e2e_messaging_rsa_key
from batch.processor_rotate_e2e_messaging_rsa_key import Processor
from batch.processor_rotate_e2e_messaging_rsa_key import Processor, LOG
from tests.account_config import config_eth_account


@pytest.fixture(scope="function")
def processor(db, e2e_messaging_contract):
processor_rotate_e2e_messaging_rsa_key.E2E_MESSAGING_CONTRACT_ADDRESS = e2e_messaging_contract.address
return Processor()
log = logging.getLogger("background")
default_log_level = LOG.level
log.setLevel(logging.DEBUG)
log.propagate = True
yield Processor()
log.propagate = False
log.setLevel(default_log_level)


class TestProcessor:
Expand Down Expand Up @@ -426,3 +434,65 @@ def test_error_2(self, processor, db, e2e_messaging_contract):

_rsa_key_list = db.query(E2EMessagingAccountRsaKey).order_by(E2EMessagingAccountRsaKey.block_timestamp).all()
assert len(_rsa_key_list) == 1

# <Error_3>
# ContractRevertError
def test_error_3(self, processor: Processor, db: Session, e2e_messaging_contract, caplog: pytest.LogCaptureFixture):
user_1 = config_eth_account("user1")
user_address_1 = user_1["address"]
user_keyfile_1 = user_1["keyfile_json"]
user_private_key_1 = decode_keyfile_json(
raw_keyfile_json=user_1["keyfile_json"],
password="password".encode("utf-8")
)

# Prepare data : E2EMessagingAccount
_account = E2EMessagingAccount()
_account.account_address = user_address_1
_account.keyfile = user_keyfile_1
_account.eoa_password = E2EEUtils.encrypt("password")
_account.rsa_key_generate_interval = 1
_account.rsa_generation = 2
db.add(_account)

datetime_now = datetime.utcnow()

# Prepare data : E2EMessagingAccountRsaKey
_rsa_key = E2EMessagingAccountRsaKey()
_rsa_key.transaction_hash = "tx_3"
_rsa_key.account_address = user_address_1
_rsa_key.rsa_private_key = "rsa_private_key_1_3"
_rsa_key.rsa_public_key = "rsa_public_key_1_3"
_rsa_key.rsa_passphrase = E2EEUtils.encrypt("latest_passphrase_1")
_rsa_key.block_timestamp = datetime_now + timedelta(hours=-1, seconds=-1)
db.add(_rsa_key)
time.sleep(1)

db.commit()

# mock
mock_E2EMessaging_set_public_key = mock.patch(
target="app.model.blockchain.e2e_messaging.E2EMessaging.set_public_key",
side_effect=ContractRevertError("999999")
)

# Run target process
with mock_E2EMessaging_set_public_key:
processor.process()

# Assertion
E2EMessaging.set_public_key.assert_called_with(
contract_address=e2e_messaging_contract.address,
public_key=ANY,
key_type="RSA4096",
tx_from=user_address_1,
private_key=user_private_key_1
)

_rsa_key_list = db.query(E2EMessagingAccountRsaKey).order_by(E2EMessagingAccountRsaKey.block_timestamp).all()
assert len(_rsa_key_list) == 1
assert caplog.record_tuples.count((
LOG.name,
logging.WARNING,
f"Transaction reverted: account_address=<{user_address_1}> error_code:<999999> error_msg:<>"
)) == 1
Loading