mirror of https://github.com/nucypher/nucypher.git
Initial discovery of nonce instability.
parent
775fe32828
commit
ab71b85be2
|
@ -87,15 +87,20 @@ IGNORE_CONTRACT_PREFIXES: Tuple[str, ...] = (
|
|||
|
||||
# RE pattern for matching solidity source compile version specification in devdoc details.
|
||||
VERSION_PATTERN: Pattern = re.compile(r"""
|
||||
^\| # Anchored pipe literal at beginning of version definition
|
||||
(v # Capture version starting from symbol v
|
||||
( # Start outer group
|
||||
\A # Anchor must be first
|
||||
\| # Anchored pipe literal at beginning of version definition
|
||||
( # Start Inner capture group
|
||||
v # Capture version starting from symbol v
|
||||
\d+ # At least one digit of major version
|
||||
\. # Digits splitter
|
||||
\d+ # At least one digit of minor version
|
||||
\. # Digits splitter
|
||||
\d+ # At least one digit of patch
|
||||
) # End of capturing
|
||||
\|$ # Anchored end of version definition |
|
||||
\| # Anchored end of version definition |
|
||||
\Z # Anchor must be the end of the match
|
||||
){1} # Limit to a single match
|
||||
""", re.VERBOSE)
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
"""
|
||||
This file is part of nucypher.
|
||||
|
||||
nucypher is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
nucypher is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import random
|
||||
import time
|
||||
from collections import Counter
|
||||
|
||||
import pytest
|
||||
from eth_utils import ValidationError
|
||||
from pathlib import Path
|
||||
|
||||
from nucypher.blockchain.eth.agents import ContractAgency, NucypherTokenAgent
|
||||
from nucypher.blockchain.eth.interfaces import BlockchainDeployerInterface, BlockchainInterfaceFactory
|
||||
from nucypher.blockchain.eth.registry import InMemoryContractRegistry
|
||||
from nucypher.blockchain.eth.sol.compile import ALLOWED_PATHS
|
||||
from nucypher.crypto.powers import TransactingPower
|
||||
from tests.constants import INSECURE_DEVELOPMENT_PASSWORD
|
||||
from tests.utils.blockchain import TesterBlockchain
|
||||
|
||||
|
||||
llamas = [
|
||||
(1, 0.1),
|
||||
|
||||
(2, None),
|
||||
(2, None),
|
||||
(2, None),
|
||||
(2, None),
|
||||
(2, None),
|
||||
|
||||
(2, 0.01),
|
||||
(2, 0.01),
|
||||
(2, 0.01),
|
||||
(2, 0.01),
|
||||
(2, 0.01),
|
||||
|
||||
(2, 0.2),
|
||||
(2, 0.2),
|
||||
(2, 0.2),
|
||||
(2, 0.2),
|
||||
(2, 0.2),
|
||||
|
||||
(2, 0.3),
|
||||
(2, 0.3),
|
||||
(2, 0.3),
|
||||
(2, 0.3),
|
||||
(2, 0.3),
|
||||
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('tx_count, delay', llamas)
|
||||
def test_rapid_deployment_nonce_uniqueness(mocker, tx_count, delay):
|
||||
|
||||
# Prepare compiler
|
||||
base_dir = Path(__file__).parent / 'contracts' / 'multiversion'
|
||||
v1_dir, v2_dir = base_dir / 'v1', base_dir / 'v2'
|
||||
|
||||
# I am a contract administrator and I an compiling a new updated version of an existing contract...
|
||||
# Represents "Manually hardcoding" a new permitted compile path in compile.py
|
||||
# and new source directory on BlockchainDeployerInterface.SOURCES.
|
||||
ALLOWED_PATHS.append(base_dir)
|
||||
BlockchainDeployerInterface.SOURCES = (v1_dir, v2_dir)
|
||||
|
||||
# Prepare chain
|
||||
BlockchainInterfaceFactory._interfaces.clear()
|
||||
blockchain_interface = BlockchainDeployerInterface(provider_uri='tester://pyevm')
|
||||
blockchain_interface.connect()
|
||||
BlockchainInterfaceFactory.register_interface(interface=blockchain_interface) # Lets this test run in isolation
|
||||
|
||||
origin = blockchain_interface.client.accounts[0]
|
||||
blockchain_interface.transacting_power = TransactingPower(password=INSECURE_DEVELOPMENT_PASSWORD, account=origin)
|
||||
blockchain_interface.transacting_power.activate()
|
||||
|
||||
nonce_spy = mocker.spy(BlockchainDeployerInterface, 'sign_and_broadcast_transaction')
|
||||
|
||||
contract_name = "VersionTest"
|
||||
registry = InMemoryContractRegistry()
|
||||
for i in range(tx_count):
|
||||
try:
|
||||
blockchain_interface.deploy_contract(deployer_address=origin,
|
||||
registry=registry,
|
||||
contract_name=contract_name,
|
||||
contract_version="latest")
|
||||
if delay is not None:
|
||||
time.sleep(delay)
|
||||
except ValidationError:
|
||||
nonces = Counter()
|
||||
for call_args in nonce_spy.call_args_list:
|
||||
transaction_dict = call_args.kwargs['transaction_dict']
|
||||
nonce = transaction_dict['nonce']
|
||||
nonces[nonce] += 1
|
||||
assert len(BlockchainInterfaceFactory._interfaces) == 1
|
||||
assert all(bool(count == 1) for nonce, count in nonces.items()), 'A nonce was reused.'
|
||||
|
||||
|
||||
def test_nonce_stability_with_raw_transactions():
|
||||
BlockchainInterfaceFactory._interfaces.clear()
|
||||
testerchain, registry = TesterBlockchain.bootstrap_network()
|
||||
counter = Counter()
|
||||
for i in range(100):
|
||||
target = random.choice(testerchain.unassigned_accounts)
|
||||
nonce = testerchain.client.w3.eth.getTransactionCount(testerchain.etherbase_account, 'pending')
|
||||
counter[nonce] += 1
|
||||
tx = {
|
||||
'value': 100,
|
||||
'gas': 50_000,
|
||||
'gasPrice': 1,
|
||||
'chainId': testerchain.client.chain_id,
|
||||
# 'data': '0000000000000000000000000000000000000000',
|
||||
'nonce': nonce,
|
||||
'from': testerchain.etherbase_account,
|
||||
'to': target
|
||||
}
|
||||
|
||||
try:
|
||||
testerchain.sign_and_broadcast_transaction(transaction_dict=tx)
|
||||
except ValidationError:
|
||||
assert len(BlockchainInterfaceFactory._interfaces) == 1
|
||||
assert all(bool(count == 1) for nonce, count in counter.items()), 'A nonce was reused.'
|
||||
raise
|
||||
|
||||
|
||||
def test_nonce_stability_with_token_agent():
|
||||
BlockchainInterfaceFactory._interfaces.clear()
|
||||
testerchain, registry = TesterBlockchain.bootstrap_network()
|
||||
token_agent = ContractAgency.get_agent(NucypherTokenAgent, registry=registry)
|
||||
counter = Counter()
|
||||
for i in range(100):
|
||||
target = random.choice(testerchain.unassigned_accounts)
|
||||
contract_function = token_agent.contract.functions.transfer(target, 100)
|
||||
try:
|
||||
testerchain.send_transaction(contract_function=contract_function,
|
||||
sender_address=testerchain.etherbase_account)
|
||||
except ValidationError:
|
||||
assert len(BlockchainInterfaceFactory._interfaces) == 1
|
||||
assert all(bool(count == 1) for nonce, count in counter.items()), 'A nonce was reused.'
|
||||
raise
|
Loading…
Reference in New Issue