Initial discovery of nonce instability.

pull/2439/head
Kieran R. Prasch 2020-05-28 17:14:45 -07:00 committed by vzotova
parent 775fe32828
commit ab71b85be2
2 changed files with 159 additions and 3 deletions

View File

@ -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)

View File

@ -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