Skip to content
This repository has been archived by the owner on Dec 15, 2021. It is now read-only.

Commit

Permalink
IS-1218: Eventlog for fixing invalid expired unstakes (#514)
Browse files Browse the repository at this point in the history
  • Loading branch information
goldworm-icon authored Sep 3, 2020
1 parent c7d11e6 commit 1a68f3a
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 18 deletions.
2 changes: 2 additions & 0 deletions iconservice/icon_service_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from typing import TYPE_CHECKING, List, Optional, Tuple, Dict, Union, Any

from iconcommons.logger import Logger

from iconservice.rollback import check_backup_exists
from iconservice.rollback.backup_cleaner import BackupCleaner
from iconservice.rollback.backup_manager import BackupManager
Expand Down Expand Up @@ -2112,6 +2113,7 @@ def _run_unstake_patcher(self, context: 'IconScoreContext'):
try:
path: Optional[str] = self._conf.get(
ConfigKey.INVALID_EXPIRED_UNSTAKES_PATH, None)
Logger.info(tag="UNSTAKE", msg=f"path: {path}")

patcher = UnstakePatcher.from_path(path)
patcher.run(context)
Expand Down
51 changes: 35 additions & 16 deletions iconservice/icx/unstake_patcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
from typing import TYPE_CHECKING, List, Dict, Any

from iconcommons.logger import Logger

from .storage import AccountPartFlag
from ..base.address import Address
from ..base.address import Address, SYSTEM_SCORE_ADDRESS
from ..iconscore.icon_score_event_log import EventLogEmitter
from ..icx.coin_part import CoinPartFlag

if TYPE_CHECKING:
Expand Down Expand Up @@ -125,22 +127,27 @@ def run(self, context: 'IconScoreContext'):
storage = context.storage.icx

for target in self._targets:
address = target.address
coin_part = storage.get_part(context, AccountPartFlag.COIN, address)
stake_part = storage.get_part(context, AccountPartFlag.STAKE, address)

result: Result = self._check_removable(coin_part, stake_part, target)
if result == Result.FALSE:
self._add_failure_item(target)
else:
if result == Result.REMOVABLE_V0:
stake_part = self._remove_invalid_expired_unstakes_v0(stake_part, target)
try:
address = target.address
coin_part = storage.get_part(context, AccountPartFlag.COIN, address)
stake_part = storage.get_part(context, AccountPartFlag.STAKE, address)

result: Result = self._check_removable(coin_part, stake_part, target)
if result == Result.FALSE:
self._add_failure_item(target)
else:
stake_part = self._remove_invalid_expired_unstakes_v1(stake_part, target)

assert stake_part.is_dirty()
storage.put_stake_part(context, address, stake_part)
self._add_success_item(target)
if result == Result.REMOVABLE_V0:
stake_part = self._remove_invalid_expired_unstakes_v0(stake_part, target)
else:
stake_part = self._remove_invalid_expired_unstakes_v1(stake_part, target)

assert stake_part.is_dirty()
storage.put_stake_part(context, address, stake_part)
self._emit_event_log(context, target)
self._add_success_item(target)
except BaseException as e:
# Although some unexpected errors happen, keep going
Logger.exception(tag=TAG, msg=str(e))

Logger.info(tag=TAG, msg="UnstakePatcher.run() end")

Expand Down Expand Up @@ -257,6 +264,18 @@ def _add_failure_item(self, target: Target):
self._failure_targets.append(target)
self._failure_unstake += target.total_unstake

@classmethod
def _emit_event_log(cls, context: 'IconScoreContext', target: Target):
for unstake in target.unstakes:
EventLogEmitter.emit_event_log(
context=context,
event_signature="InvalidUnstakeFixed(Address,int,int)",
score_address=SYSTEM_SCORE_ADDRESS,
arguments=[target.address, unstake.amount, unstake.block_height],
indexed_args_count=1,
fee_charge=False
)

def write_result(self, path: str):
Logger.info(tag=TAG, msg=f"UnstakePatcher.write_result() start: {path}")

Expand Down
35 changes: 33 additions & 2 deletions tests/unit_test/icx/test_unstake_patcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@
import json
import os
import random
from typing import Dict, Any
from typing import Dict, Any, List

import pytest

from iconservice.base.address import Address, AddressPrefix
from iconservice.icx.stake_part import StakePart
from iconservice.icx.unstake_patcher import UnstakePatcher, INVALID_EXPIRED_UNSTAKES_FILENAME
from iconservice.icx.unstake_patcher import (
INVALID_EXPIRED_UNSTAKES_FILENAME,
Target,
UnstakePatcher,
)


def get_invalid_expired_unstakes_path() -> str:
Expand All @@ -26,6 +31,32 @@ def unstake_patcher() -> UnstakePatcher:
return UnstakePatcher.from_path(path)


class TestTarget:
def test_from_dict(self):
address = Address(AddressPrefix.EOA, os.urandom(20))
count = random.randint(1, 5)
unstakes: List[List[int]] = []

for _ in range(count):
amount = random.randint(1, 1000)
block_height = random.randint(10000, 99999)
unstakes.append([amount, block_height])

data = {
"address": str(address),
"unstakes": unstakes
}
target = Target.from_dict(data)

assert target.address == address
assert target.total_unstake == sum(unstake[0] for unstake in unstakes)
assert len(target.unstakes) == count

for i, unstake in enumerate(target.unstakes):
assert unstake.amount == unstakes[i][0]
assert unstake.block_height == unstakes[i][1]


class TestUnstakePatcher:
def test_init(self, unstake_patcher):
path = get_invalid_expired_unstakes_path()
Expand Down

0 comments on commit 1a68f3a

Please sign in to comment.