From 74cc426cb118c404563156ac5542684e5acc566a Mon Sep 17 00:00:00 2001 From: derekpierre Date: Tue, 26 Mar 2024 16:10:37 -0400 Subject: [PATCH 1/8] Update get_active_staking_providers call for TACoApplicationAgent/TACoChildApplicationAgent to accept an optional duration parameter, with the default value being 0. --- nucypher/blockchain/eth/agents.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/nucypher/blockchain/eth/agents.py b/nucypher/blockchain/eth/agents.py index 8c6b75fff..9e16c8347 100644 --- a/nucypher/blockchain/eth/agents.py +++ b/nucypher/blockchain/eth/agents.py @@ -240,7 +240,7 @@ class StakerSamplingApplicationAgent(EthereumContractAgent): @abstractmethod def _get_active_staking_providers_raw( - self, start_index: int, max_results: int + self, start_index: int, max_results: int, duration: int ) -> Tuple[int, List[bytes]]: raise NotImplementedError @@ -258,12 +258,12 @@ class StakerSamplingApplicationAgent(EthereumContractAgent): @contract_api(CONTRACT_CALL) def get_active_staking_providers( - self, start_index: int, max_results: int + self, start_index: int, max_results: int, duration: int = 0 ) -> Tuple[types.TuNits, Dict[ChecksumAddress, types.TuNits]]: ( total_authorized_tokens, staking_providers_info, - ) = self._get_active_staking_providers_raw(start_index, max_results) + ) = self._get_active_staking_providers_raw(start_index, max_results, duration) staking_providers = self._process_active_staker_info(staking_providers_info) return types.TuNits(total_authorized_tokens), staking_providers @@ -428,7 +428,7 @@ class TACoChildApplicationAgent(StakerSamplingApplicationAgent): @contract_api(CONTRACT_CALL) def _get_active_staking_providers_raw( - self, start_index: int, max_results: int + self, start_index: int, max_results: int, duration: int ) -> Tuple[int, List[bytes]]: get_active_providers_overloaded_function = ( self.contract.get_function_by_signature( @@ -436,7 +436,7 @@ class TACoChildApplicationAgent(StakerSamplingApplicationAgent): ) ) active_staking_providers_info = get_active_providers_overloaded_function( - start_index, max_results, 0 # TODO address via #3458 + start_index, max_results, duration ).call() return active_staking_providers_info @@ -526,11 +526,11 @@ class TACoApplicationAgent(StakerSamplingApplicationAgent): @contract_api(CONTRACT_CALL) def _get_active_staking_providers_raw( - self, start_index: int, max_results: int + self, start_index: int, max_results: int, duration: int ) -> Tuple[int, List[bytes]]: active_staking_providers_info = ( self.contract.functions.getActiveStakingProviders( - start_index, max_results, 0 # TODO address via #3458 + start_index, max_results, duration ).call() ) return active_staking_providers_info From dbbeb233d19142f030bbe8ec0fdbb59f3a733f5b Mon Sep 17 00:00:00 2001 From: derekpierre Date: Tue, 26 Mar 2024 16:12:25 -0400 Subject: [PATCH 2/8] Update tests which should now work given default duration value. --- tests/acceptance/agents/test_sampling_distribution.py | 3 +-- tests/acceptance/agents/test_taco_application_agent.py | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/acceptance/agents/test_sampling_distribution.py b/tests/acceptance/agents/test_sampling_distribution.py index a70fd64cd..de659eacd 100644 --- a/tests/acceptance/agents/test_sampling_distribution.py +++ b/tests/acceptance/agents/test_sampling_distribution.py @@ -16,7 +16,6 @@ from nucypher.crypto.powers import TransactingPower def test_sampling_distribution( testerchain, taco_application_agent, - taco_child_application_agent, # TODO undo and fix as part of #3458 threshold_staking, coordinator_agent, deployer_account, @@ -62,7 +61,7 @@ def test_sampling_distribution( sampled, failed = 0, 0 while sampled < SAMPLES: try: - reservoir = taco_child_application_agent.get_staking_provider_reservoir() + reservoir = taco_application_agent.get_staking_provider_reservoir() addresses = set(reservoir.draw(quantity)) addresses.discard(NULL_ADDRESS) except taco_application_agent.NotEnoughStakingProviders: diff --git a/tests/acceptance/agents/test_taco_application_agent.py b/tests/acceptance/agents/test_taco_application_agent.py index f91da3edc..a335170e4 100644 --- a/tests/acceptance/agents/test_taco_application_agent.py +++ b/tests/acceptance/agents/test_taco_application_agent.py @@ -87,8 +87,6 @@ def test_get_staker_population(taco_application_agent, staking_providers): ) -# TODO #3458 -@pytest.mark.skip("Skip until issue #3458 is addressed") @pytest.mark.usefixtures("staking_providers", "ursulas") def test_sample_staking_providers(taco_application_agent): all_staking_providers = list(taco_application_agent.get_staking_providers()) From 45f05e3134b266f6649c11e04ce3e293d89f8c83 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Tue, 26 Mar 2024 16:43:47 -0400 Subject: [PATCH 3/8] Update other calls to get active providers based on duration. --- nucypher/blockchain/eth/agents.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/nucypher/blockchain/eth/agents.py b/nucypher/blockchain/eth/agents.py index 9e16c8347..7d34dbdef 100644 --- a/nucypher/blockchain/eth/agents.py +++ b/nucypher/blockchain/eth/agents.py @@ -249,10 +249,10 @@ class StakerSamplingApplicationAgent(EthereumContractAgent): raise NotImplementedError def get_all_active_staking_providers( - self, pagination_size: Optional[int] = None + self, pagination_size: Optional[int] = None, duration: int = 0 ) -> Tuple[types.TuNits, Dict[ChecksumAddress, types.TuNits]]: n_tokens, staking_providers = self._get_active_stakers( - pagination_size=pagination_size + pagination_size=pagination_size, duration=duration ) return n_tokens, staking_providers @@ -272,10 +272,11 @@ class StakerSamplingApplicationAgent(EthereumContractAgent): self, without: Iterable[ChecksumAddress] = None, pagination_size: Optional[int] = None, + duration: int = 0, ) -> "StakingProvidersReservoir": # pagination_size = pagination_size or self.get_staking_providers_population() n_tokens, stake_provider_map = self.get_all_active_staking_providers( - pagination_size=pagination_size + pagination_size=pagination_size, duration=duration ) if n_tokens == 0: @@ -309,7 +310,9 @@ class StakerSamplingApplicationAgent(EthereumContractAgent): return staking_providers - def _get_active_stakers(self, pagination_size: Optional[int] = None): + def _get_active_stakers( + self, pagination_size: Optional[int] = None, duration: int = 0 + ): if pagination_size is None: pagination_size = ( self.DEFAULT_PROVIDERS_PAGINATION_SIZE_LIGHT_NODE @@ -332,7 +335,9 @@ class StakerSamplingApplicationAgent(EthereumContractAgent): ( batch_authorized_tokens, batch_staking_providers, - ) = self.get_active_staking_providers(start_index, pagination_size) + ) = self.get_active_staking_providers( + start_index, pagination_size, duration + ) except Exception as e: if "timeout" not in str(e): # exception unrelated to pagination size and timeout From 110633a5180bb6c4757152664888dd91c1deab36 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Thu, 25 Apr 2024 09:30:07 -0400 Subject: [PATCH 4/8] Add duration values to agent sampling tests. --- .../agents/test_taco_application_agent.py | 15 +++++++++++---- .../test_taco_child_application_agent.py | 19 ++++++++++++++----- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/tests/acceptance/agents/test_taco_application_agent.py b/tests/acceptance/agents/test_taco_application_agent.py index a335170e4..1b2743743 100644 --- a/tests/acceptance/agents/test_taco_application_agent.py +++ b/tests/acceptance/agents/test_taco_application_agent.py @@ -88,7 +88,10 @@ def test_get_staker_population(taco_application_agent, staking_providers): @pytest.mark.usefixtures("staking_providers", "ursulas") -def test_sample_staking_providers(taco_application_agent): +@pytest.mark.parametrize( + "duration", [0, 60 * 60 * 24, 60 * 60 * 24 * 182, 60 * 60 * 24 * 365] +) +def test_sample_staking_providers(taco_application_agent, duration): all_staking_providers = list(taco_application_agent.get_staking_providers()) providers_population = taco_application_agent.get_staking_providers_population() @@ -99,13 +102,15 @@ def test_sample_staking_providers(taco_application_agent): providers_population + 1 ) # One more than we have deployed - providers = taco_application_agent.get_staking_provider_reservoir().draw(3) + providers = taco_application_agent.get_staking_provider_reservoir( + duration=duration + ).draw(3) assert len(providers) == 3 # Three... assert len(set(providers)) == 3 # ...unique addresses # Same but with pagination providers = taco_application_agent.get_staking_provider_reservoir( - pagination_size=1 + pagination_size=1, duration=duration ).draw(3) assert len(providers) == 3 assert len(set(providers)) == 3 @@ -114,7 +119,9 @@ def test_sample_staking_providers(taco_application_agent): # repeat for opposite blockchain light setting light = taco_application_agent.blockchain.is_light taco_application_agent.blockchain.is_light = not light - providers = taco_application_agent.get_staking_provider_reservoir().draw(3) + providers = taco_application_agent.get_staking_provider_reservoir( + duration=duration + ).draw(3) assert len(providers) == 3 assert len(set(providers)) == 3 assert len(set(providers).intersection(all_staking_providers)) == 3 diff --git a/tests/acceptance/agents/test_taco_child_application_agent.py b/tests/acceptance/agents/test_taco_child_application_agent.py index 98c259564..015e6bc7c 100644 --- a/tests/acceptance/agents/test_taco_child_application_agent.py +++ b/tests/acceptance/agents/test_taco_child_application_agent.py @@ -101,7 +101,10 @@ def test_get_staker_population(taco_child_application_agent, staking_providers): @pytest.mark.usefixtures("staking_providers", "ursulas") -def test_sample_staking_providers(taco_child_application_agent): +@pytest.mark.parametrize( + "duration", [0, 60 * 60 * 24, 60 * 60 * 24 * 182, 60 * 60 * 24 * 365] +) +def test_sample_staking_providers(taco_child_application_agent, duration): all_staking_providers = list(taco_child_application_agent.get_staking_providers()) providers_population = ( taco_child_application_agent.get_staking_providers_population() @@ -110,18 +113,22 @@ def test_sample_staking_providers(taco_child_application_agent): assert len(all_staking_providers) == providers_population with pytest.raises(taco_child_application_agent.NotEnoughStakingProviders): - taco_child_application_agent.get_staking_provider_reservoir().draw( + taco_child_application_agent.get_staking_provider_reservoir( + duration=duration + ).draw( providers_population + 1 ) # One more than we have deployed - providers = taco_child_application_agent.get_staking_provider_reservoir().draw(3) + providers = taco_child_application_agent.get_staking_provider_reservoir( + duration=duration + ).draw(3) assert len(providers) == 3 # Three... assert len(set(providers)) == 3 # ...unique addresses assert len(set(providers).intersection(all_staking_providers)) == 3 # Same but with pagination providers = taco_child_application_agent.get_staking_provider_reservoir( - pagination_size=1 + pagination_size=1, duration=duration ).draw(3) assert len(providers) == 3 assert len(set(providers)) == 3 @@ -130,7 +137,9 @@ def test_sample_staking_providers(taco_child_application_agent): # repeat for opposite blockchain light setting light = taco_child_application_agent.blockchain.is_light taco_child_application_agent.blockchain.is_light = not light - providers = taco_child_application_agent.get_staking_provider_reservoir().draw(3) + providers = taco_child_application_agent.get_staking_provider_reservoir( + duration=duration + ).draw(3) assert len(providers) == 3 assert len(set(providers)) == 3 assert len(set(providers).intersection(all_staking_providers)) == 3 From f2f7c24bba6c3183561ccde50158c1385cce4996 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Thu, 25 Apr 2024 10:15:56 -0400 Subject: [PATCH 5/8] Allow duration as a parameter to make_staking_provider_reservoir function call. This call is used by Porter. --- nucypher/policy/reservoir.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nucypher/policy/reservoir.py b/nucypher/policy/reservoir.py index fc9f14882..631bdc4b7 100644 --- a/nucypher/policy/reservoir.py +++ b/nucypher/policy/reservoir.py @@ -13,6 +13,7 @@ def make_staking_provider_reservoir( exclude_addresses: Optional[Iterable[ChecksumAddress]] = None, include_addresses: Optional[Iterable[ChecksumAddress]] = None, pagination_size: Optional[int] = None, + duration: Optional[int] = 0, ): """Get a sampler object containing the currently registered staking providers.""" @@ -21,7 +22,9 @@ def make_staking_provider_reservoir( include_addresses = include_addresses or () without_set = set(include_addresses) | set(exclude_addresses or ()) try: - reservoir = application_agent.get_staking_provider_reservoir(without=without_set, pagination_size=pagination_size) + reservoir = application_agent.get_staking_provider_reservoir( + without=without_set, pagination_size=pagination_size, duration=duration + ) except StakerSamplingApplicationAgent.NotEnoughStakingProviders: # TODO: do that in `get_staking_provider_reservoir()`? reservoir = StakingProvidersReservoir({}) From 0f641363c2df7eff4db4262990b7fc8760cee4dd Mon Sep 17 00:00:00 2001 From: derekpierre Date: Thu, 25 Apr 2024 10:50:31 -0400 Subject: [PATCH 6/8] Update StakingProviderInfo tests for TACoApplicationAgent and TACoChildApplicationAgent. --- .../agents/test_taco_application_agent.py | 19 ++++++------ .../test_taco_child_application_agent.py | 30 +++++++++++++++++++ 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/tests/acceptance/agents/test_taco_application_agent.py b/tests/acceptance/agents/test_taco_application_agent.py index 1b2743743..662d016b5 100644 --- a/tests/acceptance/agents/test_taco_application_agent.py +++ b/tests/acceptance/agents/test_taco_application_agent.py @@ -2,7 +2,6 @@ import random import pytest -from nucypher.blockchain.eth.agents import TACoApplicationAgent from nucypher.blockchain.eth.constants import NULL_ADDRESS from nucypher.blockchain.eth.signers.software import Web3Signer from nucypher.crypto.powers import TransactingPower @@ -139,17 +138,19 @@ def test_sample_staking_providers(taco_application_agent, duration): def test_get_staking_provider_info( - testerchain, taco_application_agent, get_random_checksum_address + taco_application_agent, ursulas, get_random_checksum_address ): - staking_provider_account, operator_account, *other = testerchain.unassigned_accounts - info: TACoApplicationAgent.StakingProviderInfo = ( - taco_application_agent.get_staking_provider_info( - staking_provider=staking_provider_account - ) + # existing staker + staking_provider, operator_address = ( + ursulas[0].checksum_address, + ursulas[0].operator_address, + ) + info = taco_application_agent.get_staking_provider_info( + staking_provider=staking_provider ) assert info.operator_start_timestamp > 0 - assert info.operator == operator_account - assert info.operator_confirmed is False + assert info.operator == operator_address + assert info.operator_confirmed is True # non-existent staker info = taco_application_agent.get_staking_provider_info( diff --git a/tests/acceptance/agents/test_taco_child_application_agent.py b/tests/acceptance/agents/test_taco_child_application_agent.py index 015e6bc7c..bd46f971f 100644 --- a/tests/acceptance/agents/test_taco_child_application_agent.py +++ b/tests/acceptance/agents/test_taco_child_application_agent.py @@ -154,3 +154,33 @@ def test_sample_staking_providers(taco_child_application_agent, duration): assert len(set(providers)) == 3 assert len(set(providers).intersection(all_staking_providers)) == 3 assert len(set(providers).intersection(exclude_providers)) == 0 + + +def test_get_staking_provider_info( + taco_child_application_agent, ursulas, get_random_checksum_address +): + # existing staker + staking_provider, operator_address = ( + ursulas[0].checksum_address, + ursulas[0].operator_address, + ) + info = taco_child_application_agent.staking_provider_info( + staking_provider=staking_provider + ) + assert info.operator == operator_address + assert info.authorized > taco_child_application_agent.get_min_authorization() + assert info.operator_confirmed is True + assert info.index == 1 + assert info.deauthorizing == 0 + assert info.end_deauthorization == 0 + + # non-existent staker + info = taco_child_application_agent.staking_provider_info( + get_random_checksum_address() + ) + assert info.operator == NULL_ADDRESS + assert info.authorized == 0 + assert info.operator_confirmed is False + assert info.index == 0 + assert info.deauthorizing == 0 + assert info.end_deauthorization == 0 From 4ac1a12b94fb1fa882182095d1d5c2d923814e0f Mon Sep 17 00:00:00 2001 From: derekpierre Date: Thu, 25 Apr 2024 13:39:14 -0400 Subject: [PATCH 7/8] Fix failing conditions test; add duration parameter for modified internal function call. --- tests/acceptance/conditions/test_conditions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/acceptance/conditions/test_conditions.py b/tests/acceptance/conditions/test_conditions.py index c5ab20da8..2f72a40fe 100644 --- a/tests/acceptance/conditions/test_conditions.py +++ b/tests/acceptance/conditions/test_conditions.py @@ -801,7 +801,7 @@ def test_contract_condition_using_overloaded_function( ( total_staked, providers, - ) = taco_child_application_agent._get_active_staking_providers_raw(0, 10) + ) = taco_child_application_agent._get_active_staking_providers_raw(0, 10, 0) expected_result = [ total_staked, [ From e0e5e8655ad2fe40838c00af9e022000dc4e26f2 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Thu, 25 Apr 2024 13:44:14 -0400 Subject: [PATCH 8/8] Add newsfragment for #3467. --- newsfragments/3467.feature.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 newsfragments/3467.feature.rst diff --git a/newsfragments/3467.feature.rst b/newsfragments/3467.feature.rst new file mode 100644 index 000000000..775d47e71 --- /dev/null +++ b/newsfragments/3467.feature.rst @@ -0,0 +1 @@ +Allow staking providers to be filtered/sampled based on an expectation for staking duration.