From a0bf7ae6e3dbbf9ce0b39dbbc370477729fce3ba Mon Sep 17 00:00:00 2001 From: Mike Casale <46603283+mikewcasale@users.noreply.github.com> Date: Tue, 30 Jan 2024 10:19:14 -0800 Subject: [PATCH 1/8] bugfix for excessive main loop runtime --- fastlane_bot/events/async_event_update_utils.py | 1 + .../NBTest_908_run_async_update_with_retries.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/fastlane_bot/events/async_event_update_utils.py b/fastlane_bot/events/async_event_update_utils.py index cf66f2005..a5520f6e1 100644 --- a/fastlane_bot/events/async_event_update_utils.py +++ b/fastlane_bot/events/async_event_update_utils.py @@ -572,6 +572,7 @@ def run_async_update_with_retries(mgr, current_block, logging_path, retry_interv while failed_async_calls < max_retries: try: async_update_pools_from_contracts(mgr, current_block, logging_path) + mgr.pools_to_add_from_contracts = [] return # Successful execution except AyncUpdateRetryException as e: failed_async_calls += 1 diff --git a/resources/NBTest/NBTest_908_run_async_update_with_retries.py b/resources/NBTest/NBTest_908_run_async_update_with_retries.py index 4ef7a2ee9..ceb0169b7 100644 --- a/resources/NBTest/NBTest_908_run_async_update_with_retries.py +++ b/resources/NBTest/NBTest_908_run_async_update_with_retries.py @@ -93,6 +93,21 @@ def test_successful_execution_first_try(mock_get_new_pool_data, mocker): mgr.cfg.logger.error.assert_not_called() +@patch("fastlane_bot.events.async_event_update_utils.get_new_pool_data") +def test_pools_to_add_from_contracts_is_cleared_upon_success(mock_get_new_pool_data, mocker): + mgr = setup_mock_mgr() + mocker.patch("time.sleep") # To avoid actual sleep calls + current_block = max( + int(pool[2]["blockNumber"]) + for pool in mgr.pools_to_add_from_contracts + if "blockNumber" in pool[2] + ) + mock_get_new_pool_data.return_value = mgr.pool_data + run_async_update_with_retries(mgr, current_block=current_block, logging_path="") + + assert mgr.pools_to_add_from_contracts == [], "mgr.pools_to_add_from_contracts is not reset to [] upon success" + + @patch("fastlane_bot.events.async_event_update_utils.async_update_pools_from_contracts") def test_successful_execution_after_retries( mock_async_update_pools_from_contracts, mocker From 1c295001eb4041c402d21f837ea0d02c1c5a2c4c Mon Sep 17 00:00:00 2001 From: Mike Casale <46603283+mikewcasale@users.noreply.github.com> Date: Tue, 30 Jan 2024 11:56:10 -0800 Subject: [PATCH 2/8] reworking bugfix per @barakman suggestions --- .../events/async_event_update_utils.py | 43 ------------ fastlane_bot/events/managers/manager.py | 66 ++++++++++++------- main.py | 30 ++++++++- 3 files changed, 70 insertions(+), 69 deletions(-) diff --git a/fastlane_bot/events/async_event_update_utils.py b/fastlane_bot/events/async_event_update_utils.py index a5520f6e1..8b5a1e04e 100644 --- a/fastlane_bot/events/async_event_update_utils.py +++ b/fastlane_bot/events/async_event_update_utils.py @@ -544,46 +544,3 @@ def async_update_pools_from_contracts(mgr: Any, current_block: int, logging_path mgr.cfg.logger.info( f"Async Updating pools from contracts took {(time.time() - start_time):0.4f} seconds" ) - - -def update_remaining_pools(mgr): - remaining_pools = [] - all_events = [pool[2] for pool in mgr.pools_to_add_from_contracts] - for event in all_events: - addr = mgr.web3.to_checksum_address(event["address"]) - ex_name = mgr.exchange_name_from_event(event) - if not ex_name: - mgr.cfg.logger.warning("[run_async_update_with_retries] ex_name not found from event") - continue - - key, key_value = mgr.get_key_and_value(event, addr, ex_name) - pool_info = mgr.get_pool_info(key, key_value, ex_name) - - if not pool_info: - remaining_pools.append((addr, ex_name, event, key, key_value)) - - shuffle(remaining_pools) # shuffle to avoid repeated immediate failure of the same pool - return remaining_pools - - -def run_async_update_with_retries(mgr, current_block, logging_path, retry_interval=1, max_retries=5): - failed_async_calls = 0 - - while failed_async_calls < max_retries: - try: - async_update_pools_from_contracts(mgr, current_block, logging_path) - mgr.pools_to_add_from_contracts = [] - return # Successful execution - except AyncUpdateRetryException as e: - failed_async_calls += 1 - mgr.pools_to_add_from_contracts = update_remaining_pools(mgr) - mgr.cfg.logger.error(f"Attempt {failed_async_calls} failed: {e}") - - # Handling failure after retries - mgr.cfg.logger.error( - f"[main run.py] async_update_pools_from_contracts failed after {len(mgr.pools_to_add_from_contracts)} attempts. List of failed pools: {mgr.pools_to_add_from_contracts}" - ) - mgr.pools_to_add_from_contracts = [] - raise Exception("[main run.py] async_update_pools_from_contracts failed after maximum retries.") - - diff --git a/fastlane_bot/events/managers/manager.py b/fastlane_bot/events/managers/manager.py index 8a6460b72..b11d94f04 100644 --- a/fastlane_bot/events/managers/manager.py +++ b/fastlane_bot/events/managers/manager.py @@ -87,7 +87,7 @@ def handle_pair_created(self, event: Dict[str, Any]): self.fee_pairs.update(fee_pairs) def update_from_pool_info( - self, pool_info: Optional[Dict[str, Any]] = None, current_block: int = None + self, pool_info: Optional[Dict[str, Any]] = None, current_block: int = None ) -> Dict[str, Any]: """ Update the pool info. @@ -101,8 +101,8 @@ def update_from_pool_info( """ if "last_updated_block" in pool_info: if ( - type(pool_info["last_updated_block"]) == int - and pool_info["last_updated_block"] == current_block + type(pool_info["last_updated_block"]) == int + and pool_info["last_updated_block"] == current_block ): return pool_info else: @@ -150,11 +150,11 @@ def update_from_pool_info( return pool_info def update_from_contract( - self, - address: str = None, - contract: Optional[Contract] = None, - pool_info: Optional[Dict[str, Any]] = None, - block_number: int = None, + self, + address: str = None, + contract: Optional[Contract] = None, + pool_info: Optional[Dict[str, Any]] = None, + block_number: int = None, ) -> Dict[str, Any]: """ Update the state from the contract (instead of events). @@ -212,13 +212,13 @@ def update_from_contract( return pool_info def update( - self, - event: Dict[str, Any] = None, - address: str = None, - token_address: bool = False, - pool_info: Dict[str, Any] = None, - contract: Contract = None, - block_number: int = None, + self, + event: Dict[str, Any] = None, + address: str = None, + token_address: bool = False, + pool_info: Dict[str, Any] = None, + contract: Contract = None, + block_number: int = None, ) -> None: """ Update the state. @@ -286,8 +286,8 @@ def update( time.sleep(random.random()) def handle_pair_trading_fee_updated( - self, - event: Dict[str, Any] = None, + self, + event: Dict[str, Any] = None, ): """ Handle the pair trading fee updated event by updating the fee pairs and pool info for the given pair. @@ -305,20 +305,20 @@ def handle_pair_trading_fee_updated( for idx, pool in enumerate(self.pool_data): if ( - pool["tkn0_address"] == tkn0_address - and pool["tkn1_address"] == tkn1_address - and pool["exchange_name"] == "carbon_v1" + pool["tkn0_address"] == tkn0_address + and pool["tkn1_address"] == tkn1_address + and pool["exchange_name"] == "carbon_v1" ): self._handle_pair_trading_fee_updated(fee, pool, idx) elif ( - pool["tkn0_address"] == tkn1_address - and pool["tkn1_address"] == tkn0_address - and pool["exchange_name"] == "carbon_v1" + pool["tkn0_address"] == tkn1_address + and pool["tkn1_address"] == tkn0_address + and pool["exchange_name"] == "carbon_v1" ): self._handle_pair_trading_fee_updated(fee, pool, idx) def _handle_pair_trading_fee_updated( - self, fee: int, pool: Dict[str, Any], idx: int + self, fee: int, pool: Dict[str, Any], idx: int ): """ Handle the pair trading fee updated event by updating the fee pairs and pool info for the given pair. @@ -360,3 +360,21 @@ def handle_trading_fee_updated(self): ] pool["fee_float"] = pool["fee"] / 1e6 pool["descr"] = self.pool_descr_from_info(pool) + + def update_remaining_pools(self): + remaining_pools = [] + all_events = [pool[2] for pool in self.pools_to_add_from_contracts] + for event in all_events: + addr = self.web3.to_checksum_address(event["address"]) + ex_name = self.exchange_name_from_event(event) + if not ex_name: + self.cfg.logger.warning("[run_async_update_with_retries] ex_name not found from event") + continue + + key, key_value = self.get_key_and_value(event, addr, ex_name) + pool_info = self.get_pool_info(key, key_value, ex_name) + + if not pool_info: + remaining_pools.append((addr, ex_name, event, key, key_value)) + + return remaining_pools diff --git a/main.py b/main.py index 43fbb076b..515ade738 100644 --- a/main.py +++ b/main.py @@ -5,7 +5,9 @@ (c) Copyright Bprotocol foundation 2023. Licensed under MIT """ -from fastlane_bot.events.exceptions import ReadOnlyException +from random import shuffle + +from fastlane_bot.events.exceptions import ReadOnlyException, AyncUpdateRetryException from fastlane_bot.events.version_utils import check_version_requirements from fastlane_bot.tools.cpc import T @@ -26,7 +28,7 @@ async_handle_initial_iteration, ) from fastlane_bot.events.async_event_update_utils import ( - async_update_pools_from_contracts, run_async_update_with_retries, + async_update_pools_from_contracts, ) from fastlane_bot.events.managers.manager import Manager from fastlane_bot.events.multicall_utils import multicall_every_iteration @@ -673,6 +675,7 @@ def run( current_block=current_block, logging_path=logging_path, ) + mgr.pools_to_add_from_contracts = [] # Increment the loop index loop_idx += 1 @@ -854,6 +857,29 @@ def run( break +def run_async_update_with_retries(mgr, current_block, logging_path, max_retries=5): + failed_async_calls = 0 + + while failed_async_calls < max_retries: + try: + async_update_pools_from_contracts(mgr, current_block, logging_path) + return # Successful execution + except AyncUpdateRetryException as e: + failed_async_calls += 1 + remaining_pools = mgr.update_remaining_pools() + shuffle(remaining_pools) # shuffle to avoid repeated immediate failure of the same pool + mgr.pools_to_add_from_contracts = remaining_pools + mgr.cfg.logger.error(f"Attempt {failed_async_calls} failed: {e}") + + # Handling failure after retries + mgr.cfg.logger.error( + f"[main run.py] async_update_pools_from_contracts failed after " + f"{len(mgr.pools_to_add_from_contracts)} attempts. List of failed pools: {mgr.pools_to_add_from_contracts}" + ) + + raise AyncUpdateRetryException("[main.py] async_update_pools_from_contracts failed after maximum retries.") + + if __name__ == "__main__": main() From dc6150754c988344a63800ffa11bf6d78232b580 Mon Sep 17 00:00:00 2001 From: Mike Casale <46603283+mikewcasale@users.noreply.github.com> Date: Tue, 30 Jan 2024 13:22:37 -0800 Subject: [PATCH 3/8] Update async_event_update_utils.py --- fastlane_bot/events/async_event_update_utils.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/fastlane_bot/events/async_event_update_utils.py b/fastlane_bot/events/async_event_update_utils.py index 8b5a1e04e..84fa45f0f 100644 --- a/fastlane_bot/events/async_event_update_utils.py +++ b/fastlane_bot/events/async_event_update_utils.py @@ -2,7 +2,6 @@ import os import time from glob import glob -from random import shuffle from typing import Any, List, Dict, Tuple, Type, Callable import nest_asyncio @@ -14,7 +13,6 @@ from fastlane_bot.data.abi import ERC20_ABI from fastlane_bot.events.async_utils import get_contract_chunks -from fastlane_bot.events.exceptions import AyncUpdateRetryException from fastlane_bot.events.utils import update_pools_from_events nest_asyncio.apply() From a0267f89efef53575d37e6dfd2b3b775be3ec619 Mon Sep 17 00:00:00 2001 From: Mike Casale <46603283+mikewcasale@users.noreply.github.com> Date: Tue, 30 Jan 2024 13:23:52 -0800 Subject: [PATCH 4/8] Update manager.py --- fastlane_bot/events/managers/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane_bot/events/managers/manager.py b/fastlane_bot/events/managers/manager.py index b11d94f04..249f2b9d2 100644 --- a/fastlane_bot/events/managers/manager.py +++ b/fastlane_bot/events/managers/manager.py @@ -377,4 +377,4 @@ def update_remaining_pools(self): if not pool_info: remaining_pools.append((addr, ex_name, event, key, key_value)) - return remaining_pools + self.pools_to_add_from_contracts = remaining_pools From c83be3bf8ea26f580ff74b4047b255b1faf0ae2c Mon Sep 17 00:00:00 2001 From: Mike Casale <46603283+mikewcasale@users.noreply.github.com> Date: Tue, 30 Jan 2024 13:25:11 -0800 Subject: [PATCH 5/8] adding shuffle to manager.py --- fastlane_bot/events/managers/manager.py | 1 + 1 file changed, 1 insertion(+) diff --git a/fastlane_bot/events/managers/manager.py b/fastlane_bot/events/managers/manager.py index 249f2b9d2..0f1b2bdaa 100644 --- a/fastlane_bot/events/managers/manager.py +++ b/fastlane_bot/events/managers/manager.py @@ -377,4 +377,5 @@ def update_remaining_pools(self): if not pool_info: remaining_pools.append((addr, ex_name, event, key, key_value)) + random.shuffle(remaining_pools) self.pools_to_add_from_contracts = remaining_pools From fe5c1329b133c8263f82b2552d266cee518f323b Mon Sep 17 00:00:00 2001 From: Mike Casale <46603283+mikewcasale@users.noreply.github.com> Date: Tue, 30 Jan 2024 13:26:14 -0800 Subject: [PATCH 6/8] removing logic now handled in manager.py --- main.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/main.py b/main.py index 515ade738..8992fff4b 100644 --- a/main.py +++ b/main.py @@ -5,7 +5,6 @@ (c) Copyright Bprotocol foundation 2023. Licensed under MIT """ -from random import shuffle from fastlane_bot.events.exceptions import ReadOnlyException, AyncUpdateRetryException from fastlane_bot.events.version_utils import check_version_requirements @@ -866,9 +865,7 @@ def run_async_update_with_retries(mgr, current_block, logging_path, max_retries= return # Successful execution except AyncUpdateRetryException as e: failed_async_calls += 1 - remaining_pools = mgr.update_remaining_pools() - shuffle(remaining_pools) # shuffle to avoid repeated immediate failure of the same pool - mgr.pools_to_add_from_contracts = remaining_pools + mgr.update_remaining_pools() mgr.cfg.logger.error(f"Attempt {failed_async_calls} failed: {e}") # Handling failure after retries From 106d513d53eb5f65a601af08aea51b586600f261 Mon Sep 17 00:00:00 2001 From: Mike Casale <46603283+mikewcasale@users.noreply.github.com> Date: Tue, 30 Jan 2024 14:31:43 -0800 Subject: [PATCH 7/8] Update main.py --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 8992fff4b..8201b85d6 100644 --- a/main.py +++ b/main.py @@ -865,8 +865,8 @@ def run_async_update_with_retries(mgr, current_block, logging_path, max_retries= return # Successful execution except AyncUpdateRetryException as e: failed_async_calls += 1 - mgr.update_remaining_pools() mgr.cfg.logger.error(f"Attempt {failed_async_calls} failed: {e}") + mgr.update_remaining_pools() # Handling failure after retries mgr.cfg.logger.error( From 147a7d1876e9c6ad5c12d13ab18ed83fb7579b14 Mon Sep 17 00:00:00 2001 From: Mike Casale <46603283+mikewcasale@users.noreply.github.com> Date: Tue, 30 Jan 2024 14:35:25 -0800 Subject: [PATCH 8/8] typo correction --- NBTest_068_exceptions.py | 10 +++++----- fastlane_bot/events/exceptions.py | 2 +- main.py | 6 +++--- resources/NBTest/NBTest_068_exceptions.py | 10 +++++----- .../NBTest/NBTest_908_run_async_update_with_retries.py | 6 +++--- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/NBTest_068_exceptions.py b/NBTest_068_exceptions.py index 6c728f302..923fc9f7b 100644 --- a/NBTest_068_exceptions.py +++ b/NBTest_068_exceptions.py @@ -1,7 +1,7 @@ # Happy path test with various realistic test values import pytest -from fastlane_bot.events.exceptions import AyncUpdateRetryException +from fastlane_bot.events.exceptions import AsyncUpdateRetryException @pytest.mark.parametrize( @@ -14,7 +14,7 @@ ) def test_aync_update_retry_exception_with_message(message, id): # Act - exception = AyncUpdateRetryException(message) + exception = AsyncUpdateRetryException(message) # Assert assert str(exception) == message, f"Test case {id} failed: The exception message does not match the expected message." @@ -28,7 +28,7 @@ def test_aync_update_retry_exception_with_message(message, id): ) def test_aync_update_retry_exception_with_empty_message(message, id): # Act - exception = AyncUpdateRetryException(message) + exception = AsyncUpdateRetryException(message) # Assert assert str(exception) == message, f"Test case {id} failed: The exception message should be empty." @@ -44,5 +44,5 @@ def test_aync_update_retry_exception_with_empty_message(message, id): ) def test_aync_update_retry_exception_raises(message, id): # Act & Assert - with pytest.raises(AyncUpdateRetryException, match=message): - raise AyncUpdateRetryException(message) + with pytest.raises(AsyncUpdateRetryException, match=message): + raise AsyncUpdateRetryException(message) diff --git a/fastlane_bot/events/exceptions.py b/fastlane_bot/events/exceptions.py index 7e9e2e8b2..0bbb1da3b 100644 --- a/fastlane_bot/events/exceptions.py +++ b/fastlane_bot/events/exceptions.py @@ -7,7 +7,7 @@ def __str__(self): f"create this file.") -class AyncUpdateRetryException(Exception): +class AsyncUpdateRetryException(Exception): """ Exception raised when async_update_pools_from_contracts fails and needs to be retried. """ diff --git a/main.py b/main.py index 8201b85d6..01fd7e9d0 100644 --- a/main.py +++ b/main.py @@ -6,7 +6,7 @@ Licensed under MIT """ -from fastlane_bot.events.exceptions import ReadOnlyException, AyncUpdateRetryException +from fastlane_bot.events.exceptions import ReadOnlyException, AsyncUpdateRetryException from fastlane_bot.events.version_utils import check_version_requirements from fastlane_bot.tools.cpc import T @@ -863,7 +863,7 @@ def run_async_update_with_retries(mgr, current_block, logging_path, max_retries= try: async_update_pools_from_contracts(mgr, current_block, logging_path) return # Successful execution - except AyncUpdateRetryException as e: + except AsyncUpdateRetryException as e: failed_async_calls += 1 mgr.cfg.logger.error(f"Attempt {failed_async_calls} failed: {e}") mgr.update_remaining_pools() @@ -874,7 +874,7 @@ def run_async_update_with_retries(mgr, current_block, logging_path, max_retries= f"{len(mgr.pools_to_add_from_contracts)} attempts. List of failed pools: {mgr.pools_to_add_from_contracts}" ) - raise AyncUpdateRetryException("[main.py] async_update_pools_from_contracts failed after maximum retries.") + raise AsyncUpdateRetryException("[main.py] async_update_pools_from_contracts failed after maximum retries.") if __name__ == "__main__": diff --git a/resources/NBTest/NBTest_068_exceptions.py b/resources/NBTest/NBTest_068_exceptions.py index 403014659..85b5c8906 100644 --- a/resources/NBTest/NBTest_068_exceptions.py +++ b/resources/NBTest/NBTest_068_exceptions.py @@ -1,6 +1,6 @@ import pytest -from fastlane_bot.events.exceptions import AyncUpdateRetryException +from fastlane_bot.events.exceptions import AsyncUpdateRetryException @pytest.mark.parametrize( @@ -13,7 +13,7 @@ ) def test_aync_update_retry_exception_with_message(message, id): # Act - exception = AyncUpdateRetryException(message) + exception = AsyncUpdateRetryException(message) # Assert assert str(exception) == message, f"Test case {id} failed: The exception message does not match the expected message." @@ -27,7 +27,7 @@ def test_aync_update_retry_exception_with_message(message, id): ) def test_aync_update_retry_exception_with_empty_message(message, id): # Act - exception = AyncUpdateRetryException(message) + exception = AsyncUpdateRetryException(message) # Assert assert str(exception) == message, f"Test case {id} failed: The exception message should be empty." @@ -43,5 +43,5 @@ def test_aync_update_retry_exception_with_empty_message(message, id): ) def test_aync_update_retry_exception_raises(message, id): # Act & Assert - with pytest.raises(AyncUpdateRetryException, match=message): - raise AyncUpdateRetryException(message) + with pytest.raises(AsyncUpdateRetryException, match=message): + raise AsyncUpdateRetryException(message) diff --git a/resources/NBTest/NBTest_908_run_async_update_with_retries.py b/resources/NBTest/NBTest_908_run_async_update_with_retries.py index ceb0169b7..046b8913b 100644 --- a/resources/NBTest/NBTest_908_run_async_update_with_retries.py +++ b/resources/NBTest/NBTest_908_run_async_update_with_retries.py @@ -8,7 +8,7 @@ update_remaining_pools, run_async_update_with_retries, ) # replace with your actual module name -from fastlane_bot.events.exceptions import AyncUpdateRetryException +from fastlane_bot.events.exceptions import AsyncUpdateRetryException @pytest.fixture @@ -115,7 +115,7 @@ def test_successful_execution_after_retries( mgr = setup_mock_mgr() mgr.get_key_and_value.side_effect = ("key1", "value1"), ("key2", "value2") mock_async_update_pools_from_contracts.side_effect = [ - AyncUpdateRetryException, + AsyncUpdateRetryException, None, ] mocker.patch("time.sleep") # To avoid actual sleep calls @@ -130,7 +130,7 @@ def test_failure_after_max_retries( mock_async_update_pools_from_contracts, mock_get_new_pool_data, mocker ): mgr = setup_mock_mgr() - mock_async_update_pools_from_contracts.side_effect = AyncUpdateRetryException + mock_async_update_pools_from_contracts.side_effect = AsyncUpdateRetryException mocker.patch("time.sleep") # To avoid actual sleep calls current_block = max( int(pool[2]["blockNumber"])