def blockchain_ursulas(testerchain, stakers, ursula_decentralized_test_config): if MOCK_KNOWN_URSULAS_CACHE: # TODO: Is this a safe assumption / test behaviour? # raise RuntimeError("Ursulas cache was unclear at fixture loading time. Did you use one of the ursula maker functions without cleaning up?") MOCK_KNOWN_URSULAS_CACHE.clear() _ursulas = make_decentralized_ursulas( ursula_config=ursula_decentralized_test_config, stakers_addresses=testerchain.stakers_accounts, workers_addresses=testerchain.ursulas_accounts) for u in _ursulas: u.synchronous_query_timeout = .01 # We expect to never have to wait for content that is actually on-chain during tests. testerchain.time_travel(periods=1) # Bootstrap the network for ursula_to_teach in _ursulas: for ursula_to_learn_about in _ursulas: # FIXME #2588: FleetSensor should not own fully-functional Ursulas. # It only needs to see whatever public info we can normally get via REST. # Also sharing mutable Ursulas like that can lead to unpredictable results. ursula_to_teach.remember_node(ursula_to_learn_about) _ports_to_remove = [ursula.rest_interface.port for ursula in _ursulas] yield _ursulas for port in _ports_to_remove: del MOCK_KNOWN_URSULAS_CACHE[port] for u in _ursulas: u.stop() u._finalize() # Pytest will hold on to this object, need to clear it manually. # See https://github.com/pytest-dev/pytest/issues/5642 _ursulas.clear()
def test_staker_manages_winding_down(testerchain, test_registry, staker, token_economics, ursula_decentralized_test_config): # Get worker ursula = make_decentralized_ursulas( ursula_config=ursula_decentralized_test_config, stakers_addresses=[staker.checksum_address], workers_addresses=[staker.worker_address], registry=test_registry).pop() # Enable winding down testerchain.time_travel(periods=1) base_duration = token_economics.minimum_locked_periods + 4 receipt = staker.enable_winding_down() assert receipt['status'] == 1 assert staker.locked_tokens(base_duration) != 0 assert staker.locked_tokens(base_duration + 1) == 0 ursula.commit_to_next_period() assert staker.locked_tokens(base_duration) != 0 assert staker.locked_tokens(base_duration + 1) == 0 # Disable winding down testerchain.time_travel(periods=1) receipt = staker.disable_winding_down() assert receipt['status'] == 1 assert staker.locked_tokens(base_duration - 1) != 0 assert staker.locked_tokens(base_duration) == 0 ursula.commit_to_next_period() assert staker.locked_tokens(base_duration - 1) != 0 assert staker.locked_tokens(base_duration) == 0
def test_staker_manages_winding_down(testerchain, test_registry, staker, token_economics, ursula_decentralized_test_config): # Get worker ursula = make_decentralized_ursulas(ursula_config=ursula_decentralized_test_config, stakers_addresses=[staker.checksum_address], workers_addresses=[staker.worker_address], registry=test_registry).pop() # Unlock transacting_power = ursula._crypto_power.power_ups(TransactingPower) transacting_power.activate(password=INSECURE_DEVELOPMENT_PASSWORD) # Enable winding down testerchain.time_travel(periods=1) base_duration = token_economics.minimum_locked_periods + 4 receipt = staker.enable_winding_down() assert receipt['status'] == 1 assert staker.locked_tokens(base_duration) != 0 assert staker.locked_tokens(base_duration + 1) == 0 ursula.commit_to_next_period() assert staker.locked_tokens(base_duration) != 0 assert staker.locked_tokens(base_duration + 1) == 0 # Disable winding down testerchain.time_travel(periods=1) receipt = staker.disable_winding_down() assert receipt['status'] == 1 assert staker.locked_tokens(base_duration - 1) != 0 assert staker.locked_tokens(base_duration) == 0 ursula.commit_to_next_period() assert staker.locked_tokens(base_duration - 1) != 0 assert staker.locked_tokens(base_duration) == 0
def test_worker_auto_commitments(mocker, testerchain, test_registry, staker, agency, token_economics, mock_transacting_power_activation, ursula_decentralized_test_config): mock_transacting_power_activation(account=staker.checksum_address, password=INSECURE_DEVELOPMENT_PASSWORD) staker.initialize_stake( amount=NU(token_economics.minimum_allowed_locked, 'NuNit'), lock_periods=int(token_economics.minimum_locked_periods)) # Get an unused address and create a new worker worker_address = testerchain.unassigned_accounts[-1] # Control time clock = Clock() WorkTracker.CLOCK = clock # Bond the Worker and Staker staker.bond_worker(worker_address=worker_address) commit_spy = mocker.spy(Worker, 'commit_to_next_period') # Make the Worker ursula = make_decentralized_ursulas( ursula_config=ursula_decentralized_test_config, stakers_addresses=[staker.checksum_address], workers_addresses=[worker_address], commit_to_next_period=False, registry=test_registry).pop() commit_spy.assert_not_called() initial_period = staker.staking_agent.get_current_period() def start(): # Start running the worker start_pytest_ursula_services(ursula=ursula) ursula.work_tracker.start(act_now=True) def time_travel(_): testerchain.time_travel(periods=1) clock.advance(WorkTracker.REFRESH_RATE + 1) def verify(_): # Verify that periods were committed on-chain automatically last_committed_period = staker.staking_agent.get_last_committed_period( staker_address=staker.checksum_address) current_period = staker.staking_agent.get_current_period() assert (last_committed_period - current_period) == 1 assert commit_spy.call_count == current_period - initial_period + 1 # Run the callbacks d = threads.deferToThread(start) d.addCallback(verify) for i in range(5): d.addCallback(time_travel) d.addCallback(verify) yield d
def test_staker_collects_staking_reward(testerchain, test_registry, staker, blockchain_ursulas, agency, token_economics, ursula_decentralized_test_config): token_agent = ContractAgency.get_agent(NucypherTokenAgent, registry=test_registry) tpower = TransactingPower(account=testerchain.etherbase_account, signer=Web3Signer(testerchain.client)) # Give more tokens to staker token_airdrop(token_agent=token_agent, transacting_power=tpower, addresses=[staker.checksum_address], amount=DEVELOPMENT_TOKEN_AIRDROP_AMOUNT) staker.initialize_stake( amount=NU(token_economics.minimum_allowed_locked, 'NuNit'), # Lock the minimum amount of tokens lock_periods=int(token_economics.minimum_locked_periods) ) # ... for the fewest number of periods # Get an unused address for a new worker worker_address = testerchain.unassigned_accounts[-1] staker.bond_worker(worker_address=worker_address) # Create this worker and bond it with the staker ursula = make_decentralized_ursulas( ursula_config=ursula_decentralized_test_config, stakers_addresses=[staker.checksum_address], workers_addresses=[worker_address], registry=test_registry, commit_now=False).pop() # ...mint few tokens... for _ in range(2): ursula.commit_to_next_period() testerchain.time_travel(periods=1) # Check mintable periods assert staker.mintable_periods() == 1 ursula.commit_to_next_period() # ...wait more... assert staker.mintable_periods() == 0 testerchain.time_travel(periods=2) assert staker.mintable_periods() == 2 # Capture the current token balance of the staker initial_balance = staker.token_balance assert token_agent.get_balance(staker.checksum_address) == initial_balance # Profit! staked = staker.non_withdrawable_stake() owned = staker.owned_tokens() staker.collect_staking_reward() assert staker.owned_tokens() == staked final_balance = staker.token_balance assert final_balance == initial_balance + owned - staked
def blockchain_ursulas(testerchain, stakers, ursula_decentralized_test_config): if MOCK_KNOWN_URSULAS_CACHE: raise RuntimeError("Ursulas cache was unclear at fixture loading time. Did you use one of the ursula maker functions without cleaning up?") _ursulas = make_decentralized_ursulas(ursula_config=ursula_decentralized_test_config, stakers_addresses=testerchain.stakers_accounts, workers_addresses=testerchain.ursulas_accounts, commit_to_next_period=True) for u in _ursulas: u.synchronous_query_timeout = .01 # We expect to never have to wait for content that is actually on-chain during tests. testerchain.time_travel(periods=1) # Bootstrap the network for ursula_to_teach in _ursulas: for ursula_to_learn_about in _ursulas: ursula_to_teach.remember_node(ursula_to_learn_about) _ports_to_remove = [ursula.rest_interface.port for ursula in _ursulas] yield _ursulas for port in _ports_to_remove: del MOCK_KNOWN_URSULAS_CACHE[port] for u in _ursulas: u.stop() u._finalize() # Pytest will hold on to this object, need to clear it manually. # See https://github.com/pytest-dev/pytest/issues/5642 _ursulas.clear()
def test_staker_collects_staking_reward(testerchain, test_registry, staker, blockchain_ursulas, agency, token_economics, mock_transacting_power_activation, ursula_decentralized_test_config): token_agent, staking_agent, policy_agent = agency mock_transacting_power_activation(account=staker.checksum_address, password=INSECURE_DEVELOPMENT_PASSWORD) staker.initialize_stake( amount=NU(token_economics.minimum_allowed_locked, 'NuNit'), # Lock the minimum amount of tokens lock_periods=int(token_economics.minimum_locked_periods) ) # ... for the fewest number of periods # Get an unused address for a new worker worker_address = testerchain.unassigned_accounts[-1] staker.bond_worker(worker_address=worker_address) # Create this worker and bond it with the staker ursula = make_decentralized_ursulas( ursula_config=ursula_decentralized_test_config, stakers_addresses=[staker.checksum_address], workers_addresses=[worker_address], commit_to_next_period=False, registry=test_registry).pop() # ...mint few tokens... for _ in range(2): ursula.transacting_power.activate( password=INSECURE_DEVELOPMENT_PASSWORD) ursula.commit_to_next_period() testerchain.time_travel(periods=1) # Check mintable periods assert staker.mintable_periods() == 1 ursula.transacting_power.activate(password=INSECURE_DEVELOPMENT_PASSWORD) ursula.commit_to_next_period() # ...wait more... assert staker.mintable_periods() == 0 testerchain.time_travel(periods=2) assert staker.mintable_periods() == 2 mock_transacting_power_activation(account=staker.checksum_address, password=INSECURE_DEVELOPMENT_PASSWORD) # Capture the current token balance of the staker initial_balance = staker.token_balance assert token_agent.get_balance(staker.checksum_address) == initial_balance # Profit! staked = staker.non_withdrawable_stake() owned = staker.owned_tokens() staker.collect_staking_reward() assert staker.owned_tokens() == staked final_balance = staker.token_balance assert final_balance == initial_balance + owned - staked
def test_stakers_bond_to_ursulas(testerchain, test_registry, stakers, ursula_decentralized_test_config): ursulas = make_decentralized_ursulas(ursula_config=ursula_decentralized_test_config, stakers_addresses=testerchain.stakers_accounts, workers_addresses=testerchain.ursulas_accounts, commit_now=False) assert len(ursulas) == len(stakers) for ursula in ursulas: ursula.validate_worker(registry=test_registry) assert ursula.verified_worker
def test_stakers_bond_to_ursulas(testerchain, test_registry, staking_providers, ursula_decentralized_test_config): ursulas = make_decentralized_ursulas( ursula_config=ursula_decentralized_test_config, staking_provider_addresses=testerchain.stake_providers_accounts, operator_addresses=testerchain.ursulas_accounts) assert len(ursulas) == len(staking_providers) for ursula in ursulas: ursula.validate_operator(registry=test_registry) assert ursula.verified_operator
def blockchain_ursulas(testerchain, stakers, ursula_decentralized_test_config): _ursulas = make_decentralized_ursulas(ursula_config=ursula_decentralized_test_config, stakers_addresses=testerchain.stakers_accounts, workers_addresses=testerchain.ursulas_accounts, commit_to_next_period=True) for u in _ursulas: u.synchronous_query_timeout = .01 # We expect to never have to wait for content that is actually on-chain during tests. testerchain.time_travel(periods=1) # Bootstrap the network for ursula_to_teach in _ursulas: for ursula_to_learn_about in _ursulas: ursula_to_teach.remember_node(ursula_to_learn_about) yield _ursulas
def test_worker_auto_commitments(mocker, testerchain, test_registry, staker, agency, token_economics, mock_transacting_power_activation, ursula_decentralized_test_config): mock_transacting_power_activation(account=staker.checksum_address, password=INSECURE_DEVELOPMENT_PASSWORD) staker.initialize_stake(amount=NU(token_economics.minimum_allowed_locked, 'NuNit'), lock_periods=int(token_economics.minimum_locked_periods)) # Get an unused address and create a new worker worker_address = testerchain.unassigned_accounts[-1] # Control time clock = Clock() WorkTracker.CLOCK = clock # Bond the Worker and Staker staker.bond_worker(worker_address=worker_address) commit_spy = mocker.spy(Worker, 'commit_to_next_period') replacement_spy = mocker.spy(WorkTracker, '_WorkTracker__fire_replacement_commitment') # Make the Worker ursula = make_decentralized_ursulas(ursula_config=ursula_decentralized_test_config, stakers_addresses=[staker.checksum_address], workers_addresses=[worker_address], commit_to_next_period=False, registry=test_registry).pop() initial_period = staker.staking_agent.get_current_period() def start(): print("Starting Worker for auto-commitment simulation") start_pytest_ursula_services(ursula=ursula) def advance_one_period(_): print('Advancing one period') testerchain.time_travel(periods=1) clock.advance(WorkTracker.INTERVAL_CEIL + 1) def pending_commitments(_): print('Starting unmined transaction simulation') testerchain.client.add_middleware(unmined_receipt_simulator_middleware) def advance_one_cycle(_): print('Advancing one tracking iteration') clock.advance(ursula.work_tracker._tracking_task.interval + 1) def advance_until_replacement_indicated(_): print("Advancing until replacement is indicated") testerchain.time_travel(periods=1) clock.advance(WorkTracker.INTERVAL_CEIL + 1) mocker.patch.object(WorkTracker, 'max_confirmation_time', return_value=1.0) clock.advance(ursula.work_tracker.max_confirmation_time() + 1) def verify_unmined_commitment(_): print('Verifying worker has unmined commitment transaction') assert len(ursula.work_tracker.pending) == 1 current_period = staker.staking_agent.get_current_period() assert commit_spy.call_count == current_period - initial_period + 1 def verify_replacement_commitment(_): print('Verifying worker has replaced commitment transaction') assert len(ursula.work_tracker.pending) == 1 assert replacement_spy.call_count > 0 def verify_confirmed(_): print('Verifying worker made a commitments') # Verify that periods were committed on-chain automatically last_committed_period = staker.staking_agent.get_last_committed_period(staker_address=staker.checksum_address) current_period = staker.staking_agent.get_current_period() assert (last_committed_period - current_period) == 1 assert commit_spy.call_count == current_period - initial_period + 1 assert replacement_spy.call_count == 0 # Behavioural Test, like a screenplay made of legos # Ursula commits on startup d = threads.deferToThread(start) d.addCallback(verify_confirmed) # Ursula commits for 3 periods with no problem for i in range(3): d.addCallback(advance_one_period) d.addCallback(verify_confirmed) # Introduce unmined transactions d.addCallback(pending_commitments) # Ursula's commitment transaction gets stuck for i in range(4): d.addCallback(advance_one_cycle) d.addCallback(verify_unmined_commitment) # Ursula recovers from this situation d.addCallback(advance_one_cycle) d.addCallback(verify_confirmed) # but it happens again, resulting in a replacement transaction d.addCallback(advance_until_replacement_indicated) d.addCallback(advance_one_cycle) d.addCallback(verify_replacement_commitment) yield d
def test_work_tracker(mocker, testerchain, test_registry, staker, agency, application_economics, ursula_decentralized_test_config): staker.initialize_stake( amount=NU(application_economics.min_authorization, 'NuNit'), lock_periods=int(application_economics.min_operator_seconds)) # Get an unused address and create a new worker worker_address = testerchain.unassigned_accounts[-1] # Control time clock = Clock() ClassicPREWorkTracker.CLOCK = clock # Bond the Worker and Staker staker.bond_worker(worker_address=worker_address) commit_spy = mocker.spy(Worker, 'commit_to_next_period') replacement_spy = mocker.spy( ClassicPREWorkTracker, '_ClassicPREWorkTracker__fire_replacement_commitment') # Make the Worker ursula = make_decentralized_ursulas( ursula_config=ursula_decentralized_test_config, staking_provider_addresses=[staker.checksum_address], operator_addresses=[worker_address], registry=test_registry).pop() ursula.run(preflight=False, discovery=False, start_reactor=False, worker=True, eager=True, block_until_ready=False) # "start" services initial_period = staker.staking_agent.get_current_period() def start(): log("Starting Worker for auto-commitment simulation") start_pytest_ursula_services(ursula=ursula) def advance_one_period(_): log('Advancing one period') testerchain.time_travel(periods=1) clock.advance(ClassicPREWorkTracker.INTERVAL_CEIL + 1) def check_pending_commitments(number_of_commitments): def _check_pending_commitments(_): log(f'Checking we have {number_of_commitments} pending commitments' ) assert number_of_commitments == len(ursula.work_tracker.pending) return _check_pending_commitments def pending_commitments(_): log('Starting unmined transaction simulation') testerchain.client.add_middleware(unmined_receipt_simulator_middleware) def advance_one_cycle(_): log('Advancing one tracking iteration') clock.advance(ursula.work_tracker._tracking_task.interval + 1) def advance_until_replacement_indicated(_): last_committed_period = staker.staking_agent.get_last_committed_period( staker_address=staker.checksum_address) log("Advancing until replacement is indicated") testerchain.time_travel(periods=1) clock.advance(ClassicPREWorkTracker.INTERVAL_CEIL + 1) mocker.patch.object(ClassicPREWorkTracker, 'max_confirmation_time', return_value=1.0) mock_last_committed_period = mocker.PropertyMock( return_value=last_committed_period) mocker.patch.object(Worker, 'last_committed_period', new_callable=mock_last_committed_period) clock.advance(ursula.work_tracker.max_confirmation_time() + 1) def verify_unmined_commitment(_): log('Verifying worker has unmined commitment transaction') # FIXME: The test doesn't model accurately an unmined TX, but an unconfirmed receipt, # so the tracker does not have pending TXs. If we want to model pending TXs we need to actually # prevent them from being mined. # # assert len(ursula.work_tracker.pending) == 1 current_period = staker.staking_agent.get_current_period() assert commit_spy.call_count == current_period - initial_period + 1 def verify_replacement_commitment(_): log('Verifying worker has replaced commitment transaction') assert replacement_spy.call_count > 0 def verify_confirmed(_): # Verify that periods were committed on-chain automatically last_committed_period = staker.staking_agent.get_last_committed_period( staker_address=staker.checksum_address) current_period = staker.staking_agent.get_current_period() expected_commitments = current_period - initial_period + 1 log(f'Verifying worker made {expected_commitments} commitments so far') assert (last_committed_period - current_period) == 1 assert commit_spy.call_count == expected_commitments assert replacement_spy.call_count == 0 # Behavioural Test, like a screenplay made of legos # Ursula commits on startup d = threads.deferToThread(start) d.addCallback(verify_confirmed) d.addCallback(advance_one_period) d.addCallback(check_pending_commitments(1)) d.addCallback(advance_one_cycle) d.addCallback(check_pending_commitments(0)) # Ursula commits for 3 periods with no problem for i in range(3): d.addCallback(advance_one_period) d.addCallback(verify_confirmed) d.addCallback(check_pending_commitments(1)) # Introduce unmined transactions d.addCallback(advance_one_period) d.addCallback(pending_commitments) # Ursula's commitment transaction gets stuck for i in range(4): d.addCallback(advance_one_cycle) d.addCallback(verify_unmined_commitment) # Ursula recovers from this situation d.addCallback(advance_one_cycle) d.addCallback(verify_confirmed) d.addCallback(advance_one_cycle) d.addCallback(check_pending_commitments(0)) # but it happens again, resulting in a replacement transaction d.addCallback(advance_until_replacement_indicated) d.addCallback(advance_one_cycle) d.addCallback(check_pending_commitments(1)) d.addCallback(advance_one_cycle) d.addCallback(verify_replacement_commitment) yield d