mirror of https://github.com/nucypher/nucypher.git
Unit tests for EthereumClient.wait_for_receipt()
Some code changes based on test feedbackpull/2043/head
parent
dfa812e42f
commit
3c71c708c2
|
@ -284,7 +284,7 @@ class EthereumClient:
|
||||||
|
|
||||||
def wait_for_receipt(self,
|
def wait_for_receipt(self,
|
||||||
transaction_hash: str,
|
transaction_hash: str,
|
||||||
timeout: int,
|
timeout: float,
|
||||||
confirmations: int = 0) -> TxReceipt:
|
confirmations: int = 0) -> TxReceipt:
|
||||||
receipt = None
|
receipt = None
|
||||||
if confirmations:
|
if confirmations:
|
||||||
|
@ -298,11 +298,9 @@ class EthereumClient:
|
||||||
receipt = self.block_until_enough_confirmations(transaction_hash=transaction_hash,
|
receipt = self.block_until_enough_confirmations(transaction_hash=transaction_hash,
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
confirmations=confirmations)
|
confirmations=confirmations)
|
||||||
except (self.ChainReorganizationDetected, TimeExhausted):
|
except (self.ChainReorganizationDetected, self.NotEnoughConfirmations, TimeExhausted):
|
||||||
timeout_context.sleep(self.BLOCK_CONFIRMATIONS_POLLING_TIME)
|
timeout_context.sleep(self.BLOCK_CONFIRMATIONS_POLLING_TIME)
|
||||||
continue
|
continue
|
||||||
except self.NotEnoughConfirmations:
|
|
||||||
raise # TODO: What should we do here?
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# If not asking for confirmations, just use web3 and assume the returned receipt is final
|
# If not asking for confirmations, just use web3 and assume the returned receipt is final
|
||||||
|
@ -315,7 +313,7 @@ class EthereumClient:
|
||||||
|
|
||||||
return receipt
|
return receipt
|
||||||
|
|
||||||
def block_until_enough_confirmations(self, transaction_hash: str, timeout: int, confirmations: int) -> dict:
|
def block_until_enough_confirmations(self, transaction_hash: str, timeout: float, confirmations: int) -> dict:
|
||||||
|
|
||||||
receipt = self.w3.eth.waitForTransactionReceipt(transaction_hash=transaction_hash,
|
receipt = self.w3.eth.waitForTransactionReceipt(transaction_hash=transaction_hash,
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
You should have received a copy of the GNU Affero General Public License
|
You should have received a copy of the GNU Affero General Public License
|
||||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
import time
|
||||||
from unittest.mock import PropertyMock
|
from unittest.mock import PropertyMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -30,18 +31,19 @@ def mock_ethereum_client(mocker):
|
||||||
return mock_client
|
return mock_client
|
||||||
|
|
||||||
|
|
||||||
def test_check_transaction_is_on_chain(mocker, mock_ethereum_client):
|
@pytest.fixture()
|
||||||
|
def receipt():
|
||||||
# Mock data
|
|
||||||
block_number_of_my_tx = 42
|
block_number_of_my_tx = 42
|
||||||
my_tx_hash = HexBytes('0xFabadaAcabada')
|
my_tx_hash = HexBytes('0xFabadaAcabada')
|
||||||
|
|
||||||
receipt = {
|
receipt = {
|
||||||
'transactionHash': my_tx_hash,
|
'transactionHash': my_tx_hash,
|
||||||
'blockNumber': block_number_of_my_tx,
|
'blockNumber': block_number_of_my_tx,
|
||||||
'blockHash': HexBytes('0xBebeCafe')
|
'blockHash': HexBytes('0xBebeCafe')
|
||||||
}
|
}
|
||||||
|
return receipt
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_transaction_is_on_chain(mocker, mock_ethereum_client, receipt):
|
||||||
# Mocking Web3 and EthereumClient
|
# Mocking Web3 and EthereumClient
|
||||||
web3_mock = mock_ethereum_client.w3
|
web3_mock = mock_ethereum_client.w3
|
||||||
web3_mock.eth.getTransactionReceipt = mocker.Mock(return_value=receipt)
|
web3_mock.eth.getTransactionReceipt = mocker.Mock(return_value=receipt)
|
||||||
|
@ -54,11 +56,9 @@ def test_check_transaction_is_on_chain(mocker, mock_ethereum_client):
|
||||||
# Test with chain re-organizations:
|
# Test with chain re-organizations:
|
||||||
|
|
||||||
# Let's assume that our TX ends up mined in a different block, and we receive a new receipt
|
# Let's assume that our TX ends up mined in a different block, and we receive a new receipt
|
||||||
new_receipt = {
|
new_receipt = dict(receipt)
|
||||||
'transactionHash': my_tx_hash,
|
new_receipt.update({'blockHash': HexBytes('0xBebeCebada')})
|
||||||
'blockNumber': block_number_of_my_tx,
|
|
||||||
'blockHash': HexBytes('0xBebeCebada')
|
|
||||||
}
|
|
||||||
web3_mock.eth.getTransactionReceipt = mocker.Mock(return_value=new_receipt)
|
web3_mock.eth.getTransactionReceipt = mocker.Mock(return_value=new_receipt)
|
||||||
|
|
||||||
exception = mock_ethereum_client.ChainReorganizationDetected
|
exception = mock_ethereum_client.ChainReorganizationDetected
|
||||||
|
@ -72,28 +72,24 @@ def test_check_transaction_is_on_chain(mocker, mock_ethereum_client):
|
||||||
_ = mock_ethereum_client.check_transaction_is_on_chain(receipt=receipt)
|
_ = mock_ethereum_client.check_transaction_is_on_chain(receipt=receipt)
|
||||||
|
|
||||||
|
|
||||||
def test_block_until_enough_confirmations(mocker, mock_ethereum_client):
|
def test_block_until_enough_confirmations(mocker, mock_ethereum_client, receipt):
|
||||||
|
my_tx_hash = receipt['transactionHash']
|
||||||
# Mock data
|
block_number_of_my_tx = receipt['blockNumber']
|
||||||
block_number_of_my_tx = 42
|
|
||||||
my_tx_hash = HexBytes('0xFabadaAcabada')
|
|
||||||
|
|
||||||
receipt = {
|
|
||||||
'transactionHash': my_tx_hash,
|
|
||||||
'blockNumber': block_number_of_my_tx,
|
|
||||||
'blockHash': HexBytes('0xBebeCafe')
|
|
||||||
}
|
|
||||||
|
|
||||||
# Test that web3's TimeExhausted is propagated:
|
# Test that web3's TimeExhausted is propagated:
|
||||||
web3_mock = mock_ethereum_client.w3
|
web3_mock = mock_ethereum_client.w3
|
||||||
web3_mock.eth.waitForTransactionReceipt = mocker.Mock(side_effect=TimeExhausted)
|
web3_mock.eth.waitForTransactionReceipt = mocker.Mock(side_effect=TimeExhausted)
|
||||||
|
|
||||||
with pytest.raises(TimeExhausted):
|
with pytest.raises(TimeExhausted):
|
||||||
mock_ethereum_client.block_until_enough_confirmations(transaction_hash=my_tx_hash, timeout=1, confirmations=1)
|
mock_ethereum_client.block_until_enough_confirmations(transaction_hash=my_tx_hash,
|
||||||
|
timeout=1,
|
||||||
|
confirmations=1)
|
||||||
|
|
||||||
# Test that NotEnoughConfirmations is raised when there are not enough confirmations.
|
# Test that NotEnoughConfirmations is raised when there are not enough confirmations.
|
||||||
# In this case, we're going to mock eth.blockNumber to be stuck
|
# In this case, we're going to mock eth.blockNumber to be stuck
|
||||||
web3_mock.eth.waitForTransactionReceipt = mocker.Mock(return_value=receipt)
|
web3_mock.eth.waitForTransactionReceipt = mocker.Mock(return_value=receipt)
|
||||||
web3_mock.eth.getTransactionReceipt = mocker.Mock(return_value=receipt)
|
web3_mock.eth.getTransactionReceipt = mocker.Mock(return_value=receipt)
|
||||||
|
|
||||||
type(web3_mock.eth).blockNumber = PropertyMock(return_value=block_number_of_my_tx) # See docs of PropertyMock
|
type(web3_mock.eth).blockNumber = PropertyMock(return_value=block_number_of_my_tx) # See docs of PropertyMock
|
||||||
|
|
||||||
# Additional adjustments to make the test faster
|
# Additional adjustments to make the test faster
|
||||||
|
@ -101,7 +97,9 @@ def test_block_until_enough_confirmations(mocker, mock_ethereum_client):
|
||||||
mock_ethereum_client.BLOCK_CONFIRMATIONS_POLLING_TIME = 0
|
mock_ethereum_client.BLOCK_CONFIRMATIONS_POLLING_TIME = 0
|
||||||
|
|
||||||
with pytest.raises(mock_ethereum_client.NotEnoughConfirmations):
|
with pytest.raises(mock_ethereum_client.NotEnoughConfirmations):
|
||||||
mock_ethereum_client.block_until_enough_confirmations(transaction_hash=my_tx_hash, timeout=1, confirmations=1)
|
mock_ethereum_client.block_until_enough_confirmations(transaction_hash=my_tx_hash,
|
||||||
|
timeout=1,
|
||||||
|
confirmations=1)
|
||||||
|
|
||||||
# Test that block_until_enough_confirmations keeps iterating until the required confirmations are obtained
|
# Test that block_until_enough_confirmations keeps iterating until the required confirmations are obtained
|
||||||
required_confirmations = 3
|
required_confirmations = 3
|
||||||
|
@ -114,3 +112,68 @@ def test_block_until_enough_confirmations(mocker, mock_ethereum_client):
|
||||||
confirmations=required_confirmations)
|
confirmations=required_confirmations)
|
||||||
assert receipt == returned_receipt
|
assert receipt == returned_receipt
|
||||||
assert required_confirmations + 1 == spy_check_transaction.call_count
|
assert required_confirmations + 1 == spy_check_transaction.call_count
|
||||||
|
|
||||||
|
|
||||||
|
def test_wait_for_receipt_no_confirmations(mocker, mock_ethereum_client, receipt):
|
||||||
|
my_tx_hash = receipt['transactionHash']
|
||||||
|
|
||||||
|
# Test that web3's TimeExhausted is propagated:
|
||||||
|
web3_mock = mock_ethereum_client.w3
|
||||||
|
web3_mock.eth.waitForTransactionReceipt = mocker.Mock(side_effect=TimeExhausted)
|
||||||
|
with pytest.raises(TimeExhausted):
|
||||||
|
_ = mock_ethereum_client.wait_for_receipt(transaction_hash=my_tx_hash, timeout=1, confirmations=0)
|
||||||
|
web3_mock.eth.waitForTransactionReceipt.assert_called_once_with(transaction_hash=my_tx_hash,
|
||||||
|
timeout=1,
|
||||||
|
poll_latency=MockEthereumClient.TRANSACTION_POLLING_TIME)
|
||||||
|
|
||||||
|
# Test that when web3's layer returns the receipt, we get that receipt
|
||||||
|
web3_mock.eth.waitForTransactionReceipt = mocker.Mock(return_value=receipt)
|
||||||
|
returned_receipt = mock_ethereum_client.wait_for_receipt(transaction_hash=my_tx_hash, timeout=1, confirmations=0)
|
||||||
|
assert receipt == returned_receipt
|
||||||
|
web3_mock.eth.waitForTransactionReceipt.assert_called_once_with(transaction_hash=my_tx_hash,
|
||||||
|
timeout=1,
|
||||||
|
poll_latency=MockEthereumClient.TRANSACTION_POLLING_TIME)
|
||||||
|
|
||||||
|
|
||||||
|
def test_wait_for_receipt_with_confirmations(mocker, mock_ethereum_client, receipt):
|
||||||
|
my_tx_hash = receipt['transactionHash']
|
||||||
|
|
||||||
|
mock_ethereum_client.COOLING_TIME = 0 # Don't make test unnecessarily slow
|
||||||
|
|
||||||
|
time_spy = mocker.spy(time, 'sleep')
|
||||||
|
# timeout_spy = mocker.spy(Timeout, 'check') # FIXME
|
||||||
|
|
||||||
|
# First, let's make a simple, successful call to check that:
|
||||||
|
# - The same receipt goes through
|
||||||
|
# - The cooling time is respected
|
||||||
|
mock_ethereum_client.block_until_enough_confirmations = mocker.Mock(return_value=receipt)
|
||||||
|
returned_receipt = mock_ethereum_client.wait_for_receipt(transaction_hash=my_tx_hash, timeout=1, confirmations=1)
|
||||||
|
assert receipt == returned_receipt
|
||||||
|
time_spy.assert_called_once_with(mock_ethereum_client.COOLING_TIME)
|
||||||
|
# timeout_spy.assert_not_called() # FIXME
|
||||||
|
|
||||||
|
# Test that wait_for_receipt finishes when a receipt is returned by block_until_enough_confirmations
|
||||||
|
sequence_of_events = (
|
||||||
|
TimeExhausted,
|
||||||
|
mock_ethereum_client.ChainReorganizationDetected(receipt),
|
||||||
|
mock_ethereum_client.NotEnoughConfirmations,
|
||||||
|
receipt
|
||||||
|
)
|
||||||
|
timeout = None
|
||||||
|
|
||||||
|
mock_ethereum_client.BLOCK_CONFIRMATIONS_POLLING_TIME = 0
|
||||||
|
mock_ethereum_client.block_until_enough_confirmations = mocker.Mock(side_effect=sequence_of_events)
|
||||||
|
|
||||||
|
returned_receipt = mock_ethereum_client.wait_for_receipt(transaction_hash=my_tx_hash, timeout=timeout, confirmations=1)
|
||||||
|
assert receipt == returned_receipt
|
||||||
|
# assert timeout_spy.call_count == 3 # FIXME
|
||||||
|
|
||||||
|
# Test that a TransactionTimeout is thrown when no receipt is found during the given time
|
||||||
|
timeout = 0.1
|
||||||
|
sequence_of_events = [TimeExhausted] * 10
|
||||||
|
mock_ethereum_client.BLOCK_CONFIRMATIONS_POLLING_TIME = 0.015
|
||||||
|
mock_ethereum_client.block_until_enough_confirmations = mocker.Mock(side_effect=sequence_of_events)
|
||||||
|
with pytest.raises(mock_ethereum_client.TransactionTimeout):
|
||||||
|
_ = mock_ethereum_client.wait_for_receipt(transaction_hash=my_tx_hash,
|
||||||
|
timeout=timeout,
|
||||||
|
confirmations=1)
|
||||||
|
|
Loading…
Reference in New Issue