2022-12-08 20:58:38 +00:00
|
|
|
import os
|
|
|
|
|
2023-05-31 22:30:49 +00:00
|
|
|
import pytest
|
2019-08-31 03:38:38 +00:00
|
|
|
import pytest_twisted
|
2022-12-08 20:58:38 +00:00
|
|
|
from hexbytes import HexBytes
|
2019-08-31 03:38:38 +00:00
|
|
|
from twisted.internet import threads
|
2022-12-08 20:58:38 +00:00
|
|
|
from twisted.internet.task import Clock
|
|
|
|
from web3.middleware.simulate_unmined_transaction import (
|
|
|
|
INVOCATIONS_BEFORE_RESULT,
|
|
|
|
unmined_receipt_simulator_middleware,
|
|
|
|
)
|
2019-08-31 03:38:38 +00:00
|
|
|
|
2022-02-02 19:49:34 +00:00
|
|
|
from nucypher.blockchain.eth.actors import Operator
|
|
|
|
from nucypher.blockchain.eth.agents import ContractAgency, PREApplicationAgent
|
|
|
|
from nucypher.blockchain.eth.constants import NULL_ADDRESS
|
|
|
|
from nucypher.blockchain.eth.signers.software import Web3Signer
|
2023-05-31 22:30:49 +00:00
|
|
|
from nucypher.blockchain.eth.trackers.pre import WorkTracker, WorkTrackerBase
|
2022-02-02 19:49:34 +00:00
|
|
|
from nucypher.crypto.powers import TransactingPower
|
2020-12-07 12:00:20 +00:00
|
|
|
from nucypher.utilities.logging import Logger
|
2023-06-16 09:31:18 +00:00
|
|
|
from tests.constants import TEST_ETH_PROVIDER_URI
|
2022-12-08 20:58:38 +00:00
|
|
|
from tests.utils.ursula import select_test_port, start_pytest_ursula_services
|
2019-08-31 03:38:38 +00:00
|
|
|
|
2022-02-02 19:34:05 +00:00
|
|
|
logger = Logger("test-operator")
|
2020-12-07 12:00:20 +00:00
|
|
|
|
|
|
|
def log(message):
|
|
|
|
logger.debug(message)
|
|
|
|
print(message)
|
|
|
|
|
2019-08-31 03:38:38 +00:00
|
|
|
|
2023-05-31 22:30:49 +00:00
|
|
|
@pytest.mark.usefixtures("test_registry_source_manager")
|
2022-11-14 16:04:13 +00:00
|
|
|
def test_ursula_operator_confirmation(
|
2022-11-30 11:46:51 +00:00
|
|
|
ursula_test_config,
|
2022-11-14 16:04:13 +00:00
|
|
|
testerchain,
|
|
|
|
threshold_staking,
|
|
|
|
application_economics,
|
|
|
|
test_registry,
|
|
|
|
):
|
|
|
|
application_agent = ContractAgency.get_agent(
|
2023-06-16 09:31:18 +00:00
|
|
|
PREApplicationAgent,
|
|
|
|
registry=test_registry,
|
2023-06-16 10:28:46 +00:00
|
|
|
provider_uri=TEST_ETH_PROVIDER_URI,
|
2022-11-14 16:04:13 +00:00
|
|
|
)
|
2023-04-27 15:50:50 +00:00
|
|
|
|
|
|
|
staking_provider = testerchain.stake_provider_account(0)
|
|
|
|
operator_address = testerchain.ursula_account(0)
|
2022-02-02 19:49:34 +00:00
|
|
|
min_authorization = application_economics.min_authorization
|
|
|
|
|
|
|
|
# make an staking_providers and some stakes
|
|
|
|
tx = threshold_staking.functions.setRoles(staking_provider).transact()
|
|
|
|
testerchain.wait_for_receipt(tx)
|
2022-11-14 16:04:13 +00:00
|
|
|
tx = threshold_staking.functions.setStakes(
|
|
|
|
staking_provider, min_authorization, 0, 0
|
|
|
|
).transact()
|
2022-02-02 19:49:34 +00:00
|
|
|
testerchain.wait_for_receipt(tx)
|
|
|
|
|
|
|
|
# make an ursula.
|
2022-12-08 20:58:38 +00:00
|
|
|
ursula = ursula_test_config.produce(
|
2023-04-27 15:50:50 +00:00
|
|
|
operator_address=operator_address, rest_port=select_test_port()
|
2022-11-14 16:04:13 +00:00
|
|
|
)
|
2022-02-02 19:49:34 +00:00
|
|
|
|
|
|
|
# it's not confirmed
|
2022-12-08 20:58:38 +00:00
|
|
|
assert ursula.is_confirmed is False
|
2022-02-02 19:49:34 +00:00
|
|
|
|
|
|
|
# it has no staking provider
|
2022-12-08 20:58:38 +00:00
|
|
|
assert ursula.get_staking_provider_address() == NULL_ADDRESS
|
2022-02-02 19:49:34 +00:00
|
|
|
|
|
|
|
# now lets visit stake.nucypher.network and bond this operator
|
2022-11-14 16:04:13 +00:00
|
|
|
tpower = TransactingPower(
|
|
|
|
account=staking_provider, signer=Web3Signer(testerchain.client)
|
|
|
|
)
|
|
|
|
application_agent.bond_operator(
|
|
|
|
staking_provider=staking_provider,
|
|
|
|
operator=operator_address,
|
|
|
|
transacting_power=tpower,
|
|
|
|
)
|
2022-02-02 19:49:34 +00:00
|
|
|
|
|
|
|
# now the worker has a staking provider
|
2022-12-08 20:58:38 +00:00
|
|
|
assert ursula.get_staking_provider_address() == staking_provider
|
2022-02-02 19:49:34 +00:00
|
|
|
# but it still isn't confirmed
|
2022-12-08 20:58:38 +00:00
|
|
|
assert ursula.is_confirmed is False
|
2022-02-02 19:49:34 +00:00
|
|
|
|
2022-12-09 16:29:41 +00:00
|
|
|
# let's confirm it. It will probably do this automatically in real life...
|
2022-12-08 20:58:38 +00:00
|
|
|
tx = ursula.confirm_address()
|
2022-02-02 19:49:34 +00:00
|
|
|
testerchain.wait_for_receipt(tx)
|
|
|
|
|
2022-12-08 20:58:38 +00:00
|
|
|
assert ursula.is_confirmed is True
|
2022-02-02 19:49:34 +00:00
|
|
|
|
|
|
|
|
|
|
|
@pytest_twisted.inlineCallbacks
|
2022-11-14 16:04:13 +00:00
|
|
|
def test_ursula_operator_confirmation_autopilot(
|
|
|
|
mocker,
|
2022-11-30 11:46:51 +00:00
|
|
|
ursula_test_config,
|
2022-11-14 16:04:13 +00:00
|
|
|
testerchain,
|
|
|
|
threshold_staking,
|
|
|
|
application_economics,
|
|
|
|
test_registry,
|
|
|
|
):
|
|
|
|
application_agent = ContractAgency.get_agent(
|
2023-06-16 09:31:18 +00:00
|
|
|
PREApplicationAgent,
|
|
|
|
registry=test_registry,
|
2023-06-16 10:28:46 +00:00
|
|
|
provider_uri=TEST_ETH_PROVIDER_URI,
|
2022-11-14 16:04:13 +00:00
|
|
|
)
|
2023-04-27 15:50:50 +00:00
|
|
|
staking_provider2 = testerchain.stake_provider_account(1)
|
|
|
|
operator2 = testerchain.ursula_account(1)
|
2022-02-02 19:49:34 +00:00
|
|
|
min_authorization = application_economics.min_authorization
|
|
|
|
|
2022-12-08 20:58:38 +00:00
|
|
|
confirmation_spy = mocker.spy(Operator, "confirm_address")
|
|
|
|
replacement_confirmation_spy = mocker.spy(
|
|
|
|
WorkTrackerBase, "_WorkTrackerBase__fire_replacement_commitment"
|
|
|
|
)
|
2022-02-02 19:49:34 +00:00
|
|
|
|
|
|
|
# make an staking_providers and some stakes
|
|
|
|
tx = threshold_staking.functions.setRoles(staking_provider2).transact()
|
|
|
|
testerchain.wait_for_receipt(tx)
|
2022-11-14 16:04:13 +00:00
|
|
|
tx = threshold_staking.functions.setStakes(
|
|
|
|
staking_provider2, min_authorization, 0, 0
|
|
|
|
).transact()
|
2022-02-02 19:49:34 +00:00
|
|
|
testerchain.wait_for_receipt(tx)
|
|
|
|
|
|
|
|
# now lets bond this worker
|
2022-11-14 16:04:13 +00:00
|
|
|
tpower = TransactingPower(
|
|
|
|
account=staking_provider2, signer=Web3Signer(testerchain.client)
|
|
|
|
)
|
|
|
|
application_agent.bond_operator(
|
|
|
|
staking_provider=staking_provider2, operator=operator2, transacting_power=tpower
|
|
|
|
)
|
2022-02-02 19:49:34 +00:00
|
|
|
|
|
|
|
# Make the Operator
|
2022-12-08 20:58:38 +00:00
|
|
|
ursula = ursula_test_config.produce(
|
|
|
|
operator_address=operator2, rest_port=select_test_port()
|
|
|
|
)
|
2022-11-14 16:04:13 +00:00
|
|
|
|
|
|
|
ursula.run(
|
|
|
|
preflight=False,
|
|
|
|
discovery=False,
|
|
|
|
start_reactor=False,
|
|
|
|
worker=True,
|
|
|
|
eager=True,
|
|
|
|
block_until_ready=True,
|
|
|
|
) # "start" services
|
2022-02-02 19:49:34 +00:00
|
|
|
|
|
|
|
def start():
|
|
|
|
log("Starting Operator for auto confirm address simulation")
|
|
|
|
start_pytest_ursula_services(ursula=ursula)
|
|
|
|
|
|
|
|
def verify_confirmed(_):
|
2022-12-08 20:58:38 +00:00
|
|
|
# Verify that confirmation made on-chain automatically
|
|
|
|
expected_confirmations = 1
|
|
|
|
log(f"Verifying worker made {expected_confirmations} commitment so far")
|
|
|
|
assert confirmation_spy.call_count == expected_confirmations
|
|
|
|
assert replacement_confirmation_spy.call_count == 0 # no replacement txs needed
|
2022-02-02 19:49:34 +00:00
|
|
|
assert application_agent.is_operator_confirmed(operator2)
|
|
|
|
|
|
|
|
# Behavioural Test, like a screenplay made of legos
|
|
|
|
|
|
|
|
# Ursula confirms on startup
|
|
|
|
d = threads.deferToThread(start)
|
|
|
|
d.addCallback(verify_confirmed)
|
|
|
|
|
|
|
|
yield d
|
2022-12-08 20:58:38 +00:00
|
|
|
|
|
|
|
|
|
|
|
@pytest_twisted.inlineCallbacks
|
|
|
|
def test_work_tracker(
|
|
|
|
mocker,
|
|
|
|
ursula_test_config,
|
|
|
|
testerchain,
|
|
|
|
threshold_staking,
|
|
|
|
application_economics,
|
|
|
|
test_registry,
|
|
|
|
):
|
|
|
|
application_agent = ContractAgency.get_agent(
|
2023-06-16 09:31:18 +00:00
|
|
|
PREApplicationAgent,
|
|
|
|
registry=test_registry,
|
2023-06-16 10:28:46 +00:00
|
|
|
provider_uri=TEST_ETH_PROVIDER_URI,
|
2022-12-08 20:58:38 +00:00
|
|
|
)
|
2023-04-27 15:50:50 +00:00
|
|
|
|
|
|
|
staking_provider3 = testerchain.stake_provider_account(2)
|
|
|
|
operator3 = testerchain.ursula_account(2)
|
2022-12-08 20:58:38 +00:00
|
|
|
min_authorization = application_economics.min_authorization
|
|
|
|
|
|
|
|
# Mock confirm_operator transaction
|
|
|
|
def mock_confirm_operator_tx_hash(*args, **kwargs):
|
|
|
|
# rando txHash
|
|
|
|
return HexBytes(os.urandom(32))
|
|
|
|
|
2022-12-09 16:29:41 +00:00
|
|
|
# Mock return that operator is not confirmed
|
|
|
|
mocker.patch.object(application_agent, "is_operator_confirmed", return_value=False)
|
|
|
|
mocker.patch.object(
|
|
|
|
application_agent,
|
|
|
|
"confirm_operator_address",
|
|
|
|
side_effect=mock_confirm_operator_tx_hash,
|
2022-12-08 20:58:38 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
# deterministic wait for replacement transaction to be mined
|
|
|
|
mocker.patch.object(
|
|
|
|
WorkTrackerBase,
|
|
|
|
"max_confirmation_time",
|
|
|
|
return_value=WorkTracker.INTERVAL_FLOOR,
|
|
|
|
)
|
|
|
|
|
|
|
|
# Spies
|
|
|
|
confirmation_spy = mocker.spy(Operator, "confirm_address")
|
|
|
|
replacement_confirmation_spy = mocker.spy(
|
|
|
|
WorkTrackerBase, "_WorkTrackerBase__fire_replacement_commitment"
|
|
|
|
)
|
|
|
|
|
|
|
|
# Control time
|
|
|
|
clock = Clock()
|
|
|
|
WorkTrackerBase.CLOCK = clock
|
|
|
|
|
|
|
|
# make an staking_providers and some stakes
|
|
|
|
tx = threshold_staking.functions.setRoles(staking_provider3).transact()
|
|
|
|
testerchain.wait_for_receipt(tx)
|
|
|
|
tx = threshold_staking.functions.setStakes(
|
|
|
|
staking_provider3, min_authorization, 0, 0
|
|
|
|
).transact()
|
|
|
|
testerchain.wait_for_receipt(tx)
|
|
|
|
|
|
|
|
# now lets bond this worker
|
|
|
|
tpower = TransactingPower(
|
|
|
|
account=staking_provider3, signer=Web3Signer(testerchain.client)
|
|
|
|
)
|
|
|
|
application_agent.bond_operator(
|
|
|
|
staking_provider=staking_provider3, operator=operator3, transacting_power=tpower
|
|
|
|
)
|
|
|
|
|
|
|
|
# Make the Operator
|
|
|
|
ursula = ursula_test_config.produce(
|
|
|
|
operator_address=operator3, rest_port=select_test_port()
|
|
|
|
)
|
|
|
|
|
|
|
|
def start(_):
|
|
|
|
log("Starting Worker services")
|
|
|
|
start_pytest_ursula_services(ursula=ursula)
|
|
|
|
|
|
|
|
def advance_clock_interval(_):
|
|
|
|
# note: for the test replacement tx confirmation time is <= next run
|
|
|
|
log("Advance clock interval to next run")
|
|
|
|
next_tracker_run = ursula.work_tracker._tracking_task.interval + 1
|
|
|
|
# replacement tx time uses block number so advance chain as well
|
|
|
|
testerchain.time_travel(next_tracker_run)
|
|
|
|
clock.advance(next_tracker_run)
|
|
|
|
|
|
|
|
def simulate_unmined_transactions():
|
|
|
|
log("Starting unmined transaction simulation")
|
|
|
|
testerchain.client.add_middleware(unmined_receipt_simulator_middleware)
|
|
|
|
|
|
|
|
def check_pending_confirmation(_):
|
|
|
|
log("Worker is currently tracking an unmined transaction")
|
|
|
|
assert len(ursula.work_tracker.pending) == 1 # only ever tracks one tx
|
|
|
|
|
|
|
|
def verify_confirm_operator_calls(_):
|
|
|
|
log("Verifying worker calls to confirm_operator")
|
|
|
|
# one less replacement tx than total count (initial + replacements)
|
|
|
|
assert (
|
|
|
|
confirmation_spy.call_count == replacement_confirmation_spy.call_count + 1
|
|
|
|
)
|
|
|
|
|
|
|
|
def verify_replacement_confirm_operator_call(_):
|
|
|
|
log("Verifying worker has issued replaced confirmation transaction")
|
|
|
|
# one less replacement tx than total count
|
|
|
|
assert replacement_confirmation_spy.call_count == (
|
|
|
|
confirmation_spy.call_count - 1
|
|
|
|
)
|
|
|
|
|
|
|
|
def verify_confirmed(_):
|
|
|
|
# Verify that commitment made on-chain automatically
|
|
|
|
log("Verifying operator is confirmed")
|
|
|
|
assert application_agent.is_operator_confirmed(operator3)
|
|
|
|
|
|
|
|
def verify_not_yet_confirmed(_):
|
|
|
|
# Verify that commitment made on-chain automatically
|
|
|
|
log("Verifying operator is not confirmed")
|
|
|
|
assert not application_agent.is_operator_confirmed(operator3)
|
|
|
|
|
|
|
|
# Behavioural Test, like a screenplay made of legos
|
|
|
|
# Simulate unmined transactions
|
|
|
|
d = threads.deferToThread(simulate_unmined_transactions)
|
|
|
|
|
|
|
|
# Run ursula and start services
|
|
|
|
ursula.run(
|
|
|
|
preflight=False,
|
|
|
|
discovery=False,
|
|
|
|
start_reactor=False,
|
|
|
|
worker=True,
|
|
|
|
eager=True,
|
|
|
|
block_until_ready=True,
|
|
|
|
)
|
|
|
|
d.addCallback(start)
|
|
|
|
|
|
|
|
# there is an attempt to confirm operator on Ursula start
|
|
|
|
d.addCallback(verify_confirm_operator_calls)
|
|
|
|
|
|
|
|
# Ensure not yet confirmed; technically it is, but we mock that it isn't
|
|
|
|
d.addCallback(verify_not_yet_confirmed)
|
|
|
|
|
|
|
|
# Ursula's confirm_operator transaction remains unmined and gets stuck
|
|
|
|
for i in range(INVOCATIONS_BEFORE_RESULT - 1):
|
|
|
|
d.addCallback(advance_clock_interval)
|
|
|
|
d.addCallback(verify_confirm_operator_calls)
|
|
|
|
|
|
|
|
d.addCallback(verify_replacement_confirm_operator_call)
|
|
|
|
|
|
|
|
d.addCallback(verify_not_yet_confirmed)
|
|
|
|
d.addCallback(check_pending_confirmation)
|
|
|
|
|
|
|
|
# Ursula recovers from this situation
|
|
|
|
d.addCallback(advance_clock_interval)
|
|
|
|
|
|
|
|
d.addCallback(verify_confirm_operator_calls)
|
|
|
|
d.addCallback(verify_replacement_confirm_operator_call)
|
|
|
|
|
2022-12-09 16:29:41 +00:00
|
|
|
yield d
|
|
|
|
|
2022-12-08 20:58:38 +00:00
|
|
|
# allow operator to be considered confirmed
|
2022-12-09 16:29:41 +00:00
|
|
|
mocker.patch.object(application_agent, "is_operator_confirmed", return_value=True)
|
2022-12-08 20:58:38 +00:00
|
|
|
d.addCallback(verify_confirmed)
|
|
|
|
|
|
|
|
yield d
|
|
|
|
|
|
|
|
# initial call + 5 replacements until is_operator_confirmed is mocked to True
|
|
|
|
# (no more afterwards)
|
|
|
|
assert confirmation_spy.call_count == 6
|
2022-12-09 14:38:18 +00:00
|
|
|
assert replacement_confirmation_spy.call_count == 5
|
2022-12-08 20:58:38 +00:00
|
|
|
|
|
|
|
# now that operator is confirmed there should be no more confirm_operator calls
|
|
|
|
for i in range(3):
|
|
|
|
d.addCallback(advance_clock_interval)
|
|
|
|
|
|
|
|
yield d
|
|
|
|
assert confirmation_spy.call_count == 6 # no change in number
|
2022-12-09 14:38:18 +00:00
|
|
|
assert replacement_confirmation_spy.call_count == 5
|