2020-10-05 15:03:34 +00:00
|
|
|
import os
|
2023-09-08 00:58:17 +00:00
|
|
|
from typing import List, Union
|
2022-02-09 20:03:49 +00:00
|
|
|
|
|
|
|
import maya
|
2019-09-10 15:34:20 +00:00
|
|
|
from eth_tester.exceptions import TransactionFailed
|
2020-05-25 23:41:42 +00:00
|
|
|
from hexbytes import HexBytes
|
2019-06-19 23:49:11 +00:00
|
|
|
from web3 import Web3
|
2018-09-26 21:39:35 +00:00
|
|
|
|
2023-06-16 09:31:18 +00:00
|
|
|
from nucypher.blockchain.eth.interfaces import (
|
|
|
|
BlockchainInterface,
|
2022-12-06 21:13:32 +00:00
|
|
|
)
|
2022-02-09 20:03:49 +00:00
|
|
|
from nucypher.blockchain.eth.signers.software import Web3Signer
|
2019-03-20 03:38:29 +00:00
|
|
|
from nucypher.blockchain.eth.token import NU
|
2019-06-20 17:13:13 +00:00
|
|
|
from nucypher.crypto.powers import TransactingPower
|
2020-10-19 08:41:33 +00:00
|
|
|
from nucypher.utilities.gas_strategies import EXPECTED_CONFIRMATION_TIME_IN_SECONDS
|
2020-08-08 00:03:39 +00:00
|
|
|
from nucypher.utilities.logging import Logger
|
2020-05-23 00:07:13 +00:00
|
|
|
from tests.constants import (
|
|
|
|
DEVELOPMENT_ETH_AIRDROP_AMOUNT,
|
|
|
|
INSECURE_DEVELOPMENT_PASSWORD,
|
|
|
|
NUMBER_OF_ETH_TEST_ACCOUNTS,
|
2022-02-09 20:03:49 +00:00
|
|
|
NUMBER_OF_STAKING_PROVIDERS_IN_BLOCKCHAIN_TESTS,
|
2020-10-05 20:43:28 +00:00
|
|
|
NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS,
|
2023-06-16 09:31:18 +00:00
|
|
|
TEST_ETH_PROVIDER_URI,
|
2020-05-23 00:07:13 +00:00
|
|
|
)
|
2018-09-13 19:26:25 +00:00
|
|
|
|
|
|
|
|
2021-02-19 00:48:04 +00:00
|
|
|
def token_airdrop(token_agent, amount: NU, transacting_power: TransactingPower, addresses: List[str]):
|
2019-08-21 12:11:39 +00:00
|
|
|
"""Airdrops tokens from creator address to all other addresses!"""
|
2018-09-13 19:26:25 +00:00
|
|
|
|
2021-02-13 07:40:45 +00:00
|
|
|
signer = Web3Signer(token_agent.blockchain.client)
|
2021-02-19 00:48:04 +00:00
|
|
|
signer.unlock_account(account=transacting_power.account, password=INSECURE_DEVELOPMENT_PASSWORD)
|
2021-02-13 07:40:45 +00:00
|
|
|
|
2018-09-13 19:26:25 +00:00
|
|
|
def txs():
|
2021-02-19 00:48:04 +00:00
|
|
|
args = {'from': transacting_power.account, 'gasPrice': token_agent.blockchain.client.gas_price}
|
2018-09-13 19:26:25 +00:00
|
|
|
for address in addresses:
|
2019-06-19 23:49:11 +00:00
|
|
|
contract_function = token_agent.contract.functions.transfer(address, int(amount))
|
2019-07-11 21:03:34 +00:00
|
|
|
_receipt = token_agent.blockchain.send_transaction(contract_function=contract_function,
|
2021-02-19 00:48:04 +00:00
|
|
|
transacting_power=transacting_power,
|
2019-06-19 23:49:11 +00:00
|
|
|
payload=args)
|
|
|
|
yield _receipt
|
2018-09-13 19:26:25 +00:00
|
|
|
|
|
|
|
receipts = list()
|
2019-06-19 23:49:11 +00:00
|
|
|
for receipt in txs(): # One at a time
|
2018-09-13 19:26:25 +00:00
|
|
|
receipts.append(receipt)
|
|
|
|
return receipts
|
|
|
|
|
|
|
|
|
2020-02-27 02:35:02 +00:00
|
|
|
def free_gas_price_strategy(w3, transaction_params=None):
|
2022-09-08 13:46:09 +00:00
|
|
|
return None
|
2020-02-27 02:35:02 +00:00
|
|
|
|
|
|
|
|
2023-04-24 04:45:13 +00:00
|
|
|
class TesterBlockchain(BlockchainInterface):
|
2018-09-13 19:26:25 +00:00
|
|
|
"""
|
|
|
|
Blockchain subclass with additional test utility methods and options.
|
|
|
|
"""
|
|
|
|
|
2020-06-01 03:21:23 +00:00
|
|
|
__test__ = False # prohibit pytest from collecting this object as a test
|
2019-06-19 18:40:18 +00:00
|
|
|
|
2020-06-01 03:21:23 +00:00
|
|
|
# Web3
|
2023-04-24 04:45:13 +00:00
|
|
|
GAS_STRATEGIES = {**BlockchainInterface.GAS_STRATEGIES, 'free': free_gas_price_strategy}
|
2023-06-16 09:31:18 +00:00
|
|
|
ETH_PROVIDER_URI = TEST_ETH_PROVIDER_URI
|
2020-08-26 00:54:07 +00:00
|
|
|
DEFAULT_GAS_STRATEGY = 'free'
|
2020-02-27 02:35:02 +00:00
|
|
|
|
2019-04-17 23:51:08 +00:00
|
|
|
# Reserved addresses
|
2019-03-31 08:56:34 +00:00
|
|
|
_ETHERBASE = 0
|
|
|
|
_ALICE = 1
|
|
|
|
_BOB = 2
|
2022-02-09 20:03:49 +00:00
|
|
|
_FIRST_STAKING_PROVIDER = 5
|
|
|
|
_FIRST_URSULA = _FIRST_STAKING_PROVIDER + NUMBER_OF_STAKING_PROVIDERS_IN_BLOCKCHAIN_TESTS
|
2018-09-13 19:26:25 +00:00
|
|
|
|
2020-06-01 03:21:23 +00:00
|
|
|
# Internal
|
2022-02-09 20:03:49 +00:00
|
|
|
__STAKING_PROVIDERS_RANGE = range(NUMBER_OF_STAKING_PROVIDERS_IN_BLOCKCHAIN_TESTS)
|
|
|
|
__OPERATORS_RANGE = range(NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS)
|
2020-06-01 03:21:23 +00:00
|
|
|
__ACCOUNT_CACHE = list()
|
|
|
|
|
2023-10-02 17:46:29 +00:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
test_accounts: int = NUMBER_OF_ETH_TEST_ACCOUNTS,
|
|
|
|
poa: bool = True,
|
|
|
|
light: bool = False,
|
|
|
|
eth_airdrop: bool = False,
|
|
|
|
*args,
|
|
|
|
**kwargs,
|
|
|
|
):
|
|
|
|
EXPECTED_CONFIRMATION_TIME_IN_SECONDS["free"] = 5 # Just some upper-limit
|
|
|
|
super().__init__(
|
2023-10-03 20:39:46 +00:00
|
|
|
endpoint=self.ETH_PROVIDER_URI,
|
2023-10-02 17:46:29 +00:00
|
|
|
poa=poa,
|
|
|
|
light=light,
|
|
|
|
*args,
|
|
|
|
**kwargs,
|
|
|
|
)
|
2024-01-30 15:08:58 +00:00
|
|
|
|
2019-04-17 23:51:08 +00:00
|
|
|
self.log = Logger("test-blockchain")
|
2023-04-24 04:45:13 +00:00
|
|
|
self.connect()
|
2023-04-28 06:00:54 +00:00
|
|
|
|
|
|
|
# Generate additional ethereum accounts for testing
|
|
|
|
population = test_accounts
|
|
|
|
enough_accounts = len(self.client.accounts) >= population
|
|
|
|
if not enough_accounts:
|
|
|
|
accounts_to_make = population - len(self.client.accounts)
|
|
|
|
self.__generate_insecure_unlocked_accounts(quantity=accounts_to_make)
|
|
|
|
assert test_accounts == len(self.w3.eth.accounts)
|
|
|
|
|
2019-04-22 20:14:18 +00:00
|
|
|
if eth_airdrop is True: # ETH for everyone!
|
2019-04-17 23:51:08 +00:00
|
|
|
self.ether_airdrop(amount=DEVELOPMENT_ETH_AIRDROP_AMOUNT)
|
2018-09-13 19:26:25 +00:00
|
|
|
|
2019-06-17 21:09:42 +00:00
|
|
|
def attach_middleware(self):
|
2022-04-05 04:49:38 +00:00
|
|
|
pass
|
2018-09-13 19:26:25 +00:00
|
|
|
|
2023-04-28 06:00:54 +00:00
|
|
|
def __generate_insecure_unlocked_accounts(self, quantity: int) -> List[str]:
|
|
|
|
|
|
|
|
addresses = list()
|
|
|
|
for _ in range(quantity):
|
|
|
|
address = self.provider.ethereum_tester.add_account('0x' + os.urandom(32).hex())
|
|
|
|
addresses.append(address)
|
|
|
|
self.__ACCOUNT_CACHE.append(address)
|
|
|
|
self.log.info('Generated new insecure account {}'.format(address))
|
|
|
|
return addresses
|
|
|
|
|
2018-09-13 19:26:25 +00:00
|
|
|
def ether_airdrop(self, amount: int) -> List[str]:
|
2019-08-21 12:11:39 +00:00
|
|
|
"""Airdrops ether from creator address to all other addresses!"""
|
2023-04-26 19:59:33 +00:00
|
|
|
coinbase, *addresses = self.client.accounts
|
2018-09-13 19:26:25 +00:00
|
|
|
tx_hashes = list()
|
|
|
|
for address in addresses:
|
2022-04-05 04:49:38 +00:00
|
|
|
tx = {'to': address, 'from': coinbase, 'value': amount, 'gasPrice': self.w3.eth.generate_gas_price()}
|
2022-09-07 16:58:03 +00:00
|
|
|
txhash = self.w3.eth.send_transaction(tx)
|
2018-09-13 19:26:25 +00:00
|
|
|
|
|
|
|
_receipt = self.wait_for_receipt(txhash)
|
|
|
|
tx_hashes.append(txhash)
|
2022-10-03 13:29:22 +00:00
|
|
|
eth_amount = Web3().from_wei(amount, 'ether')
|
2019-04-09 11:50:20 +00:00
|
|
|
self.log.info("Airdropped {} ETH {} -> {}".format(eth_amount, tx['from'], tx['to']))
|
2018-09-13 19:26:25 +00:00
|
|
|
|
|
|
|
return tx_hashes
|
|
|
|
|
2021-02-18 15:29:40 +00:00
|
|
|
def time_travel(self,
|
|
|
|
hours: int = None,
|
2022-02-02 12:41:52 +00:00
|
|
|
seconds: int = None):
|
2018-09-13 19:26:25 +00:00
|
|
|
"""
|
|
|
|
Wait the specified number of wait_hours by comparing
|
|
|
|
block timestamps and mines a single block.
|
|
|
|
"""
|
|
|
|
|
2022-02-02 12:41:52 +00:00
|
|
|
more_than_one_arg = sum(map(bool, (hours, seconds))) > 1
|
2018-09-13 19:26:25 +00:00
|
|
|
if more_than_one_arg:
|
2022-12-08 20:57:47 +00:00
|
|
|
raise ValueError("Specify either hours or seconds, not a combination")
|
2018-09-13 19:26:25 +00:00
|
|
|
|
2022-02-02 12:41:52 +00:00
|
|
|
if hours:
|
2018-09-13 19:26:25 +00:00
|
|
|
duration = hours * (60*60)
|
|
|
|
base = 60 * 60
|
|
|
|
elif seconds:
|
|
|
|
duration = seconds
|
|
|
|
base = 1
|
|
|
|
else:
|
2022-02-02 12:41:52 +00:00
|
|
|
raise ValueError("Specify either hours, or seconds.")
|
2018-09-13 19:26:25 +00:00
|
|
|
|
2022-09-07 16:58:03 +00:00
|
|
|
now = self.w3.eth.get_block('latest').timestamp
|
2018-09-13 19:26:25 +00:00
|
|
|
end_timestamp = ((now+duration)//base) * base
|
|
|
|
|
2022-09-07 16:58:03 +00:00
|
|
|
self.w3.eth.w3.testing.timeTravel(timestamp=end_timestamp)
|
|
|
|
self.w3.eth.w3.testing.mine(1)
|
2019-06-09 03:00:31 +00:00
|
|
|
|
|
|
|
delta = maya.timedelta(seconds=end_timestamp-now)
|
|
|
|
self.log.info(f"Time traveled {delta} "
|
|
|
|
f"| epoch {end_timestamp}")
|
2018-12-15 01:36:48 +00:00
|
|
|
|
2019-03-31 08:56:34 +00:00
|
|
|
@property
|
|
|
|
def etherbase_account(self):
|
2019-06-18 23:59:55 +00:00
|
|
|
return self.client.accounts[self._ETHERBASE]
|
2019-03-31 08:56:34 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def alice_account(self):
|
2019-06-18 23:59:55 +00:00
|
|
|
return self.client.accounts[self._ALICE]
|
2019-03-31 08:56:34 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def bob_account(self):
|
2019-06-18 23:59:55 +00:00
|
|
|
return self.client.accounts[self._BOB]
|
2019-03-31 08:56:34 +00:00
|
|
|
|
|
|
|
def ursula_account(self, index):
|
2022-02-09 20:03:49 +00:00
|
|
|
if index not in self.__OPERATORS_RANGE:
|
2019-04-17 23:51:08 +00:00
|
|
|
raise ValueError(f"Ursula index must be lower than {NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS}")
|
2019-06-18 23:59:55 +00:00
|
|
|
return self.client.accounts[index + self._FIRST_URSULA]
|
2019-03-31 08:56:34 +00:00
|
|
|
|
2022-02-07 18:54:53 +00:00
|
|
|
def stake_provider_account(self, index):
|
2022-02-09 20:03:49 +00:00
|
|
|
if index not in self.__STAKING_PROVIDERS_RANGE:
|
|
|
|
raise ValueError(f"Stake provider index must be lower than {NUMBER_OF_STAKING_PROVIDERS_IN_BLOCKCHAIN_TESTS}")
|
|
|
|
return self.client.accounts[index + self._FIRST_STAKING_PROVIDER]
|
2019-06-08 19:11:39 +00:00
|
|
|
|
2019-03-31 08:56:34 +00:00
|
|
|
@property
|
|
|
|
def ursulas_accounts(self):
|
2022-02-09 20:03:49 +00:00
|
|
|
return list(self.ursula_account(i) for i in self.__OPERATORS_RANGE)
|
2019-06-08 19:11:39 +00:00
|
|
|
|
|
|
|
@property
|
2022-02-07 18:54:53 +00:00
|
|
|
def stake_providers_accounts(self):
|
2022-02-09 20:03:49 +00:00
|
|
|
return list(self.stake_provider_account(i) for i in self.__STAKING_PROVIDERS_RANGE)
|
2019-03-31 08:56:34 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def unassigned_accounts(self):
|
2019-06-08 19:11:39 +00:00
|
|
|
special_accounts = [self.etherbase_account, self.alice_account, self.bob_account]
|
2022-02-07 18:54:53 +00:00
|
|
|
assigned_accounts = set(self.stake_providers_accounts + self.ursulas_accounts + special_accounts)
|
2019-06-18 23:59:55 +00:00
|
|
|
accounts = set(self.client.accounts)
|
2019-04-10 08:27:42 +00:00
|
|
|
return list(accounts.difference(assigned_accounts))
|
2019-06-17 21:09:42 +00:00
|
|
|
|
2020-05-25 23:41:42 +00:00
|
|
|
def wait_for_receipt(self, txhash: Union[bytes, str, HexBytes], timeout: int = None) -> dict:
|
2019-06-17 21:09:42 +00:00
|
|
|
"""Wait for a transaction receipt and return it"""
|
|
|
|
timeout = timeout or self.TIMEOUT
|
2020-05-25 23:41:42 +00:00
|
|
|
result = self.client.wait_for_receipt(transaction_hash=txhash, timeout=timeout)
|
2019-09-10 15:34:20 +00:00
|
|
|
if result.status == 0:
|
|
|
|
raise TransactionFailed()
|
2019-06-17 21:09:42 +00:00
|
|
|
return result
|
|
|
|
|
2020-04-26 02:28:12 +00:00
|
|
|
def get_block_number(self) -> int:
|
2020-06-03 20:51:31 +00:00
|
|
|
return self.client.block_number
|