Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Try to handle estimateGas predicting that a call will fail #2988

Conversation

LefterisJP
Copy link
Contributor

After the changes introduced by #2623 we again rely on gas estimation for the gas limit to provide to an onchain call which is great since it reduces our overall ETH balance requirements.

But we don't take into account the fact that if estimateGas returns None it's
because the transaction will fail, either due to our account not having enough
funds or because the call will just revert somewhere down the line due to the
contract logic (e.g. calling settle before closing).

With that in mind we can now detect those bad cases a bit earlier and handle
them without sending an onchain transaction. This is what this PR is trying to achieve.

Admittedly all those cases are bad ones. Since all the checks should have stopped us from getting into that part of the code in the first place, unless we expected that sending an onchain transaction will succeed.

@LefterisJP LefterisJP force-pushed the when_estimate_gas_returns_none_call_will_fail branch 3 times, most recently from df0d533 to 8776c8f Compare November 9, 2018 13:33
palango
palango previously approved these changes Nov 9, 2018
Copy link
Contributor

@palango palango left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some nits, but we can fix this next week.

raiden/network/proxies/token_network.py Outdated Show resolved Hide resolved
raiden/network/proxies/token_network.py Outdated Show resolved Hide resolved
raiden/network/proxies/token_network.py Outdated Show resolved Hide resolved
raiden/utils/__init__.py Show resolved Hide resolved
raiden/network/proxies/token_network.py Outdated Show resolved Hide resolved
raiden/network/rpc/smartcontract_proxy.py Outdated Show resolved Hide resolved
@hackaugusto hackaugusto mentioned this pull request Nov 9, 2018
@hackaugusto
Copy link
Contributor

Quick note: I added a comment in another PR which is relate to this one: #2623 (review)

@palango palango dismissed their stale review November 12, 2018 16:27

we need to figure out the waiting thingy

raiden/network/proxies/secret_registry.py Outdated Show resolved Hide resolved
raiden/network/proxies/secret_registry.py Outdated Show resolved Hide resolved
raiden/network/proxies/token.py Outdated Show resolved Hide resolved
raiden/network/proxies/token.py Outdated Show resolved Hide resolved
raiden/network/proxies/token_network.py Outdated Show resolved Hide resolved
raiden/network/proxies/token_network.py Outdated Show resolved Hide resolved
@LefterisJP LefterisJP force-pushed the when_estimate_gas_returns_none_call_will_fail branch from 8776c8f to d400484 Compare January 8, 2019 14:45
@LefterisJP
Copy link
Contributor Author

Rebased on latest master, fixed conflicts. Now going to look at the PR comments and test failures.

@LefterisJP LefterisJP force-pushed the when_estimate_gas_returns_none_call_will_fail branch 3 times, most recently from 493b847 to 13007e9 Compare January 16, 2019 21:24
LefterisJP added a commit to LefterisJP/raiden that referenced this pull request Jan 16, 2019
As discussed in Github [here](raiden-network#2988 (comment))
we are now using the following order for gas estimation and call() in
the smart contract proxy transactions:

```python
nonblockchain_preconditions()
blockchain_preconditions(consistent_block)  # this will also function as a check of the logic that lead us to start the call in the first place
gas = gas_estimate(pending_block)
if gas is not None:
  txhash = send_transaction()
  result = poll(txhash)
if gas is None or result is Failure:
    transaction_mined = gas is not None
    known_race, msg = check_blockchain_conditions(mined_block if transaction_mined else pending_block)
    if known_race:
        raise RaidenRecoverableError('{} {}'.format('Transaction Failed' if transaction_mined else 'Transaction will fail', msg))
    else:
        raise RaidenUnrecoverableError("Bad error -- we don't know why")
```
@LefterisJP
Copy link
Contributor Author

This PR is first steps towards a model such as: #3268

@LefterisJP LefterisJP force-pushed the when_estimate_gas_returns_none_call_will_fail branch from 13007e9 to fc25fa5 Compare January 16, 2019 23:15
LefterisJP added a commit to LefterisJP/raiden that referenced this pull request Jan 16, 2019
As discussed in Github [here](raiden-network#2988 (comment))
we are now using the following order for gas estimation and call() in
the smart contract proxy transactions:

```python
nonblockchain_preconditions()
blockchain_preconditions(consistent_block)  # this will also function as a check of the logic that lead us to start the call in the first place
gas = gas_estimate(pending_block)
if gas is not None:
  txhash = send_transaction()
  result = poll(txhash)
if gas is None or result is Failure:
    transaction_mined = gas is not None
    known_race, msg = check_blockchain_conditions(mined_block if transaction_mined else pending_block)
    if known_race:
        raise RaidenRecoverableError('{} {}'.format('Transaction Failed' if transaction_mined else 'Transaction will fail', msg))
    else:
        raise RaidenUnrecoverableError("Bad error -- we don't know why")
```
@LefterisJP LefterisJP force-pushed the when_estimate_gas_returns_none_call_will_fail branch 2 times, most recently from 86c595d to 59bf2bd Compare January 16, 2019 23:57
@LefterisJP
Copy link
Contributor Author

LefterisJP commented Jan 17, 2019

Note that with this approach of calling estimateGas() first, if estimateGas() fails it may be due to not having enough ETH to pay for gas too. In the past this would have raised an InsufficientFunds exception.

This is no longer the case. We can't detect this anymore and we now just return an UnrecoverableError. The gas reserve warnings should not let us get there so we still should be fine with this approach.

As an alternative to allow detecting this we could still do something like below for each proxy transaction where estimateGas fails:

gas_limit = estimate_gas()
...
if not gas_limit:
    if balanceOf(self.node.address) < GAS_REQUIRED_FOR_TX * gas_price:
        raise InsufficientFunds()

I can't see any problem with having the above added so I will probably add it in the morning.
EDIT: Added it with this commit.

LefterisJP added a commit to LefterisJP/raiden that referenced this pull request Jan 22, 2019
As discussed in Github [here](raiden-network#2988 (comment))
we are now using the following order for gas estimation and call() in
the smart contract proxy transactions:

```python
nonblockchain_preconditions()
blockchain_preconditions(consistent_block)  # this will also function as a check of the logic that lead us to start the call in the first place
gas = gas_estimate(pending_block)
if gas is not None:
  txhash = send_transaction()
  result = poll(txhash)
if gas is None or result is Failure:
    transaction_mined = gas is not None
    known_race, msg = check_blockchain_conditions(mined_block if transaction_mined else pending_block)
    if known_race:
        raise RaidenRecoverableError('{} {}'.format('Transaction Failed' if transaction_mined else 'Transaction will fail', msg))
    else:
        raise RaidenUnrecoverableError("Bad error -- we don't know why")
```
@LefterisJP
Copy link
Contributor Author

I just want to note that these changes to the proxy did not make it more readable, it's getting too cumbersome IMO

Agreed, but the purpose of this PR is not to increase readability but optimize in the case of estimateGas failing so that we don't change the transaction.

I hope to continue with #3268 which should help with code readability and organization for the proxies.

@LefterisJP LefterisJP force-pushed the when_estimate_gas_returns_none_call_will_fail branch from e88c5ef to 5e255b0 Compare January 22, 2019 23:33
@codecov-io
Copy link

codecov-io commented Jan 23, 2019

Codecov Report

❗ No coverage uploaded for pull request base (master@92da405). Click here to learn what that means.
The diff coverage is 73.45%.

Impacted file tree graph

@@            Coverage Diff            @@
##             master    #2988   +/-   ##
=========================================
  Coverage          ?   74.75%           
=========================================
  Files             ?       94           
  Lines             ?    12338           
  Branches          ?     1729           
=========================================
  Hits              ?     9223           
  Misses            ?     2474           
  Partials          ?      641
Impacted Files Coverage Δ
raiden/network/blockchain_service.py 75.22% <100%> (ø)
raiden/utils/__init__.py 74.28% <100%> (ø)
raiden/network/proxies/payment_channel.py 84.72% <100%> (ø)
raiden/network/proxies/token.py 67.64% <46.87%> (ø)
raiden/network/proxies/secret_registry.py 82.5% <62.5%> (ø)
raiden/network/rpc/client.py 53.03% <67.3%> (ø)
raiden/network/proxies/token_network.py 76.89% <76.49%> (ø)
raiden/network/proxies/token_network_registry.py 82.14% <83.33%> (ø)
raiden/network/rpc/smartcontract_proxy.py 71.55% <87.5%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 92da405...9eaa6c3. Read the comment docs.

LefterisJP and others added 16 commits January 23, 2019 21:26
After the changes introduced by
raiden-network#2623 we again rely on gas
estimation for the gas limit to provide to an onchain call which is great since
it reduces our overall ETH balance requirements.

But we don't take into account the fact that if estimateGas returns None it's
because the transaction will fail, either due to our account not having enough
funds or because the call will just revert somewhere down the line due to the
contract logic (e.g. calling settle before closing).

With that in mind we can now detect those bad cases a bit earlier and handle
them without sending an onchain transaction. This is what this PR is trying to achieve.
- Better logging messages
- kwargs
- Swap mistaken errors messages on why approve call failed
This is temporary and should be reverted in favour of the web3.py
implementation as soon as they include it in a release.
As discussed in Github [here](raiden-network#2988 (comment))
we are now using the following order for gas estimation and call() in
the smart contract proxy transactions:

```python
nonblockchain_preconditions()
blockchain_preconditions(consistent_block)  # this will also function as a check of the logic that lead us to start the call in the first place
gas = gas_estimate(pending_block)
if gas is not None:
  txhash = send_transaction()
  result = poll(txhash)
if gas is None or result is Failure:
    transaction_mined = gas is not None
    known_race, msg = check_blockchain_conditions(mined_block if transaction_mined else pending_block)
    if known_race:
        raise RaidenRecoverableError('{} {}'.format('Transaction Failed' if transaction_mined else 'Transaction will fail', msg))
    else:
        raise RaidenUnrecoverableError("Bad error -- we don't know why")
```
If estimate gas fails, then introduce an extra check which will
determine if the failure came due to our address not having sufficient
ETH to pay for gas
@LefterisJP LefterisJP force-pushed the when_estimate_gas_returns_none_call_will_fail branch from 73a6c99 to 9eaa6c3 Compare January 23, 2019 20:27
@LefterisJP
Copy link
Contributor Author

Rebased on master to avoid the secret revealed flakiness.

@hackaugusto hackaugusto merged commit 2610897 into raiden-network:master Jan 23, 2019
hackaugusto pushed a commit that referenced this pull request Jan 23, 2019
As discussed in Github [here](#2988 (comment))
we are now using the following order for gas estimation and call() in
the smart contract proxy transactions:

```python
nonblockchain_preconditions()
blockchain_preconditions(consistent_block)  # this will also function as a check of the logic that lead us to start the call in the first place
gas = gas_estimate(pending_block)
if gas is not None:
  txhash = send_transaction()
  result = poll(txhash)
if gas is None or result is Failure:
    transaction_mined = gas is not None
    known_race, msg = check_blockchain_conditions(mined_block if transaction_mined else pending_block)
    if known_race:
        raise RaidenRecoverableError('{} {}'.format('Transaction Failed' if transaction_mined else 'Transaction will fail', msg))
    else:
        raise RaidenUnrecoverableError("Bad error -- we don't know why")
```
hackaugusto pushed a commit to hackaugusto/raiden that referenced this pull request Jan 25, 2019
As discussed in Github [here](raiden-network#2988 (comment))
we are now using the following order for gas estimation and call() in
the smart contract proxy transactions:

```python
nonblockchain_preconditions()
blockchain_preconditions(consistent_block)  # this will also function as a check of the logic that lead us to start the call in the first place
gas = gas_estimate(pending_block)
if gas is not None:
  txhash = send_transaction()
  result = poll(txhash)
if gas is None or result is Failure:
    transaction_mined = gas is not None
    known_race, msg = check_blockchain_conditions(mined_block if transaction_mined else pending_block)
    if known_race:
        raise RaidenRecoverableError('{} {}'.format('Transaction Failed' if transaction_mined else 'Transaction will fail', msg))
    else:
        raise RaidenUnrecoverableError("Bad error -- we don't know why")
```
hackaugusto pushed a commit to hackaugusto/raiden that referenced this pull request Jan 25, 2019
As discussed in Github [here](raiden-network#2988 (comment))
we are now using the following order for gas estimation and call() in
the smart contract proxy transactions:

```python
nonblockchain_preconditions()
blockchain_preconditions(consistent_block)  # this will also function as a check of the logic that lead us to start the call in the first place
gas = gas_estimate(pending_block)
if gas is not None:
  txhash = send_transaction()
  result = poll(txhash)
if gas is None or result is Failure:
    transaction_mined = gas is not None
    known_race, msg = check_blockchain_conditions(mined_block if transaction_mined else pending_block)
    if known_race:
        raise RaidenRecoverableError('{} {}'.format('Transaction Failed' if transaction_mined else 'Transaction will fail', msg))
    else:
        raise RaidenUnrecoverableError("Bad error -- we don't know why")
```
hackaugusto pushed a commit to hackaugusto/raiden that referenced this pull request Jan 25, 2019
As discussed in Github [here](raiden-network#2988 (comment))
we are now using the following order for gas estimation and call() in
the smart contract proxy transactions:

```python
nonblockchain_preconditions()
blockchain_preconditions(consistent_block)  # this will also function as a check of the logic that lead us to start the call in the first place
gas = gas_estimate(pending_block)
if gas is not None:
  txhash = send_transaction()
  result = poll(txhash)
if gas is None or result is Failure:
    transaction_mined = gas is not None
    known_race, msg = check_blockchain_conditions(mined_block if transaction_mined else pending_block)
    if known_race:
        raise RaidenRecoverableError('{} {}'.format('Transaction Failed' if transaction_mined else 'Transaction will fail', msg))
    else:
        raise RaidenUnrecoverableError("Bad error -- we don't know why")
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants