repsond to RFCs in PR #3437:

- removes stale newsfrag
- do not return Deferred from SimpleTask.start and SimpleTask.stop
- removes eager atxm startup with ursula startup.
- remove unused token agent methods
- add spaces after periods in select logs
- more...
pull/3475/head
KPrasch 2024-02-15 15:18:50 +01:00 committed by derekpierre
parent b1fef5d6b1
commit edbead6791
No known key found for this signature in database
11 changed files with 32 additions and 263 deletions

View File

@ -1 +0,0 @@
Introduces automated protocol transaction retries

View File

@ -1,6 +1,7 @@
import json
import random
import time
import traceback
from collections import defaultdict
from decimal import Decimal
from typing import DefaultDict, Dict, List, Optional, Set, Union
@ -25,7 +26,6 @@ from nucypher_core.ferveo import (
Transcript,
Validator,
)
from twisted.internet import reactor
from web3 import HTTPProvider, Web3
from web3.types import TxReceipt
@ -436,23 +436,13 @@ class Operator(BaseActor):
to be handled by the EventActuator.
"""
if self.checksum_address not in participants:
# ERROR: This is an abnormal state since this method
# is designed to be invoked only when this node
# is an on-chain participant in the Coordinator.StartRitual event.
#
# This is a *nearly* a critical error. It's possible that the
# log it as an error and return None to avoid crashing upstack
# async tasks and drawing unnecessary amounts of attention to the issue.
message = (
f"{self.checksum_address}|{self.wallet_address} "
f"is not a member of ritual {ritual_id}"
)
try:
raise RuntimeError(message)
finally:
# log and stop reactor; this will crash the application.
self.log.critical(message)
return reactor.stop()
stack_trace = traceback.format_stack()
self.log.critical(f"{message}\n{stack_trace}")
return
# check phase 1 contract state
if not self._is_phase_1_action_required(ritual_id=ritual_id):

View File

@ -141,69 +141,6 @@ class NucypherTokenAgent(EthereumContractAgent):
balance: int = self.contract.functions.balanceOf(address).call()
return types.NuNits(balance)
@contract_api(CONTRACT_CALL)
def get_allowance(
self, owner: ChecksumAddress, spender: ChecksumAddress
) -> types.NuNits:
"""Check the amount of tokens that an owner allowed to a spender"""
allowance: int = self.contract.functions.allowance(owner, spender).call()
return types.NuNits(allowance)
@contract_api(TRANSACTION)
def increase_allowance(
self,
transacting_power: TransactingPower,
spender_address: ChecksumAddress,
increase: types.NuNits,
) -> TxReceipt:
"""Increase the allowance of a spender address funded by a sender address"""
contract_function: ContractFunction = self.contract.functions.increaseAllowance(
spender_address, increase
)
receipt: TxReceipt = self.blockchain.send_transaction(
contract_function=contract_function, transacting_power=transacting_power
)
return receipt
@contract_api(TRANSACTION)
def decrease_allowance(
self,
transacting_power: TransactingPower,
spender_address: ChecksumAddress,
decrease: types.NuNits,
) -> TxReceipt:
"""Decrease the allowance of a spender address funded by a sender address"""
contract_function: ContractFunction = self.contract.functions.decreaseAllowance(
spender_address, decrease
)
receipt: TxReceipt = self.blockchain.send_transaction(
contract_function=contract_function, transacting_power=transacting_power
)
return receipt
@contract_api(TRANSACTION)
def approve_transfer(
self,
amount: types.NuNits,
spender_address: ChecksumAddress,
transacting_power: TransactingPower,
) -> TxReceipt:
"""Approve the spender address to transfer an amount of tokens on behalf of the sender address"""
self._validate_zero_allowance(amount, spender_address, transacting_power)
payload: TxParams = {
"gas": Wei(500_000)
} # TODO #842: gas needed for use with geth! <<<< Is this still open?
contract_function: ContractFunction = self.contract.functions.approve(
spender_address, amount
)
receipt: TxReceipt = self.blockchain.send_transaction(
contract_function=contract_function,
payload=payload,
transacting_power=transacting_power,
)
return receipt
@contract_api(TRANSACTION)
def transfer(
self,
@ -220,41 +157,6 @@ class NucypherTokenAgent(EthereumContractAgent):
)
return receipt
@contract_api(TRANSACTION)
def approve_and_call(
self,
amount: types.NuNits,
target_address: ChecksumAddress,
transacting_power: TransactingPower,
call_data: bytes = b"",
gas_limit: Optional[Wei] = None,
) -> TxReceipt:
self._validate_zero_allowance(amount, target_address, transacting_power)
payload = None
if gas_limit: # TODO: Gas management - #842
payload = {"gas": gas_limit}
approve_and_call: ContractFunction = self.contract.functions.approveAndCall(
target_address, amount, call_data
)
approve_and_call_receipt: TxReceipt = self.blockchain.send_transaction(
contract_function=approve_and_call,
transacting_power=transacting_power,
payload=payload,
)
return approve_and_call_receipt
def _validate_zero_allowance(self, amount, target_address, transacting_power):
if amount == 0:
return
current_allowance = self.get_allowance(
owner=transacting_power.account, spender=target_address
)
if current_allowance != 0:
raise self.RequirementError(
f"Token allowance for spender {target_address} must be 0"
)
class SubscriptionManagerAgent(EthereumContractAgent):
contract_name: str = SUBSCRIPTION_MANAGER_CONTRACT_NAME
@ -829,12 +731,12 @@ class CoordinatorAgent(EthereumContractAgent):
contract_function: ContractFunction = self.contract.functions.postTranscript(
ritualId=ritual_id, transcript=bytes(transcript)
)
atx = self.blockchain.send_async_transaction(
async_tx = self.blockchain.send_async_transaction(
contract_function=contract_function,
transacting_power=transacting_power,
info={"ritual_id": ritual_id, "phase": PHASE1},
)
return atx
return async_tx
@contract_api(TRANSACTION)
def post_aggregation(
@ -851,13 +753,13 @@ class CoordinatorAgent(EthereumContractAgent):
dkgPublicKey=Ferveo.G1Point.from_dkg_public_key(public_key),
decryptionRequestStaticKey=bytes(participant_public_key),
)
atx = self.blockchain.send_async_transaction(
async_tx = self.blockchain.send_async_transaction(
contract_function=contract_function,
gas_estimation_multiplier=1.4,
transacting_power=transacting_power,
info={"ritual_id": ritual_id, "phase": PHASE2},
)
return atx
return async_tx
@contract_api(CONTRACT_CALL)
def get_ritual_initiation_cost(

View File

@ -133,8 +133,8 @@ class BlockchainInterface:
else:
cost = transaction_fee + self.payload.get("value", 0)
message = (
f'{self.name} from {self.payload["from"][:8]} - {self.base_message}.'
f"Calculated cost is {prettify_eth_amount(cost)},"
f'{self.name} from {self.payload["from"][:8]} - {self.base_message}. '
f"Calculated cost is {prettify_eth_amount(cost)}, "
f"but sender only has {prettify_eth_amount(self.get_balance())}."
)
return message
@ -656,7 +656,7 @@ class BlockchainInterface:
tx = self.client.get_transaction(txhash)
if tx["gas"] == receipt["gasUsed"]:
raise self.InterfaceError(
f"Transaction consumed 100% of transaction gas."
f"Transaction consumed 100% of transaction gas. "
f"Full receipt: \n {pprint.pformat(receipt, indent=2)}"
)

View File

@ -55,10 +55,10 @@ class EventScannerTask(SimpleTask):
self.scanner = scanner
super().__init__()
def run(self):
def run(self) -> None:
self.scanner()
def handle_errors(self, *args, **kwargs):
def handle_errors(self, *args, **kwargs) -> None:
self.log.warn(
"Error during ritual event scanning: {}".format(args[0].getTraceback())
)
@ -209,13 +209,13 @@ class ActiveRitualTracker:
# if non-zero block found - return the block before
return expected_start_block.number - 1 if expected_start_block.number > 0 else 0
def start(self):
def start(self) -> None:
"""Start the event scanner task."""
return self.task.start()
self.task.start()
def stop(self):
def stop(self) -> None:
"""Stop the event scanner task."""
return self.task.stop()
self.task.stop()
def _action_required(self, ritual_event: AttributeDict) -> bool:
"""Check if an action is required for a given ritual event."""

View File

@ -9,11 +9,11 @@ class PrometheusMetricsTracker(SimpleTask):
self.metrics_collectors = collectors
super().__init__(interval=interval)
def run(self):
def run(self) -> None:
for collector in self.metrics_collectors:
collector.collect()
def handle_errors(self, *args, **kwargs):
def handle_errors(self, *args, **kwargs) -> None:
self.log.warn(
"Error during prometheus metrics collection: {}".format(
args[0].getTraceback()

View File

@ -950,12 +950,11 @@ class Ursula(Teacher, Character, Operator):
preflight: bool = True,
block_until_ready: bool = True,
eager: bool = False,
transaction_tracking: bool = True,
) -> None:
"""Schedule and start select ursula services, then optionally start the reactor."""
BlockchainInterfaceFactory.get_or_create_interface(endpoint=self.eth_endpoint)
polygon = BlockchainInterfaceFactory.get_or_create_interface(
BlockchainInterfaceFactory.get_or_create_interface(
endpoint=self.polygon_endpoint
)
@ -974,13 +973,6 @@ class Ursula(Teacher, Character, Operator):
if emitter:
emitter.message(f"✓ P2P Networking ({self.domain})", color="green")
if transaction_tracking:
# Uncomment to enable tracking for both chains.
# mainnet.tracker.start(now=False)
polygon.tx_machine.start(now=False)
if emitter:
emitter.message("✓ Transaction Autopilot", color="green")
if ritual_tracking:
self.ritual_tracker.start()
if emitter:

View File

@ -1,7 +1,6 @@
from abc import ABC, abstractmethod
from twisted.internet import reactor
from twisted.internet.defer import Deferred
from twisted.internet.task import LoopingCall
from twisted.python.failure import Failure
@ -25,25 +24,24 @@ class SimpleTask(ABC):
"""Determine whether the task is already running."""
return self._task.running
def start(self, now: bool = False) -> Deferred:
def start(self, now: bool = False) -> None:
"""Start task."""
if not self.running:
d = self._task.start(interval=self.interval, now=now)
d.addErrback(self.handle_errors)
return d
def stop(self):
def stop(self) -> None:
"""Stop task."""
if self.running:
self._task.stop()
@abstractmethod
def run(self):
def run(self) -> None:
"""Task method that should be periodically run."""
raise NotImplementedError
@abstractmethod
def handle_errors(self, *args, **kwargs):
def handle_errors(self, *args, **kwargs) -> None:
"""Error callback for error handling during execution."""
raise NotImplementedError

View File

@ -129,9 +129,9 @@ def test_post_transcript(agent, transcripts, transacting_powers, testerchain, cl
yield clock.advance(testerchain.tx_machine._task.interval)
testerchain.tx_machine.stop()
for i, atx in enumerate(txs):
for i, async_tx in enumerate(txs):
post_transcript_events = (
agent.contract.events.TranscriptPosted().process_receipt(atx.receipt)
agent.contract.events.TranscriptPosted().process_receipt(async_tx.receipt)
)
# assert len(post_transcript_events) == 1
event = post_transcript_events[0]
@ -181,10 +181,10 @@ def test_post_aggregation(
yield clock.advance(testerchain.tx_machine._task.interval)
testerchain.tx_machine.stop()
for i, atx in enumerate(txs):
for i, async_tx in enumerate(txs):
participant_public_keys[cohort[i]] = participant_public_key
post_aggregation_events = (
agent.contract.events.AggregationPosted().process_receipt(atx.receipt)
agent.contract.events.AggregationPosted().process_receipt(async_tx.receipt)
)
assert len(post_aggregation_events) == 1
event = post_aggregation_events[0]

View File

@ -1,116 +0,0 @@
import pytest
from eth_tester.exceptions import TransactionFailed
from nucypher.blockchain.eth.agents import ContractAgency, NucypherTokenAgent
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.crypto.powers import TransactingPower
from tests.constants import TEST_ETH_PROVIDER_URI
@pytest.fixture(scope='module')
def agent(testerchain, test_registry) -> NucypherTokenAgent:
token_agent = ContractAgency.get_agent(
NucypherTokenAgent,
registry=test_registry,
blockchain_endpoint=TEST_ETH_PROVIDER_URI,
)
return token_agent
@pytest.mark.skip()
def test_token_properties(agent):
testerchain = agent.blockchain
# Internal
assert 'NuCypher' == agent.contract.functions.name().call()
assert 18 == agent.contract.functions.decimals().call()
assert 'NU' == agent.contract.functions.symbol().call()
# Cannot transfer any ETH to token contract
with pytest.raises((TransactionFailed, ValueError)):
origin = testerchain.client.coinbase
payload = {'from': origin, 'to': agent.contract_address, 'value': 1}
tx = testerchain.client.send_transaction(payload)
testerchain.wait_for_receipt(tx)
assert len(agent.contract_address) == 42
assert agent.contract.address == agent.contract_address
assert agent.contract_name == NucypherTokenAgent.contract_name
@pytest.mark.skip()
def test_get_balance(agent):
testerchain = agent.blockchain
deployer, someone, *everybody_else = testerchain.client.accounts
balance = agent.get_balance(address=someone)
assert balance == 0
balance = agent.get_balance(address=deployer)
assert balance > 0
@pytest.mark.skip()
def test_approve_transfer(agent, taco_application_agent):
testerchain = agent.blockchain
deployer, someone, *everybody_else = testerchain.client.accounts
tpower = TransactingPower(account=someone, signer=Web3Signer(testerchain.client))
# Approve
receipt = agent.approve_transfer(
amount=taco_application_agent.get_min_authorization(),
spender_address=agent.contract_address,
transacting_power=tpower,
)
assert receipt['status'] == 1, "Transaction Rejected"
assert receipt['logs'][0]['address'] == agent.contract_address
@pytest.mark.skip()
def test_transfer(agent, taco_application_agent):
testerchain = agent.blockchain
origin, someone, *everybody_else = testerchain.client.accounts
tpower = TransactingPower(account=origin, signer=Web3Signer(testerchain.client))
old_balance = agent.get_balance(someone)
receipt = agent.transfer(
amount=taco_application_agent.get_min_authorization(),
target_address=someone,
transacting_power=tpower,
)
assert receipt['status'] == 1, "Transaction Rejected"
assert receipt['logs'][0]['address'] == agent.contract_address
new_balance = agent.get_balance(someone)
assert new_balance == old_balance + taco_application_agent.get_min_authorization()
@pytest.mark.skip()
def test_approve_and_call(agent, taco_application_agent, deploy_contract):
testerchain = agent.blockchain
deployer, someone, *everybody_else = testerchain.client.accounts
mock_target, _ = deploy_contract('ReceiveApprovalMethodMock')
# Approve and call
tpower = TransactingPower(account=someone, signer=Web3Signer(testerchain.client))
call_data = b"Good morning, that's a nice tnetennba."
receipt = agent.approve_and_call(
amount=taco_application_agent.get_min_authorization(),
target_address=mock_target.address,
transacting_power=tpower,
call_data=call_data,
)
assert receipt['status'] == 1, "Transaction Rejected"
assert receipt['logs'][0]['address'] == agent.contract_address
assert mock_target.functions.extraData().call() == call_data
assert mock_target.functions.sender().call() == someone
assert (
mock_target.functions.value().call()
== taco_application_agent.get_min_authorization()
)

View File

@ -20,6 +20,7 @@ def test_operator_never_bonded(mocker, get_random_checksum_address):
tracker = OperatorBondedTracker(ursula=ursula)
try:
threads.deferToThread(tracker.start)
with pytest.raises(OperatorBondedTracker.OperatorNoLongerBonded):
d = threads.deferToThread(tracker.run)
yield d
@ -43,6 +44,9 @@ def test_operator_bonded_but_becomes_unbonded(mocker, get_random_checksum_addres
tracker = OperatorBondedTracker(ursula=ursula)
try:
threads.deferToThread(tracker.start)
# bonded
for i in range(1, 10):
d = threads.deferToThread(tracker.run)
yield d