Repository: ethereum/web3.py

Issue:
Add function w3.eth.waitForTransactionReceipt(tx_hash)
* Version: 4

### What was wrong?

It's a bit cumbersome to wait for a transaction to be mined. For example, after deploying a transaction, example code typically involves a small while loop, like in #573 

This is a very common need, and should have an easy one-liner.

### How can it be fixed?

Something like:

```py
receipt = w3.eth.waitForTransactionReceipt(tx_hash)
contract = MyContract(receipt['contractAddress'])

# or

with w3.waitFor(lambda: w3.eth.getTransactionReceipt(tx_hash)) as receipt:
    contract = MyContract(receipt['contractAddress'])

```

Very open to alternative API suggestions.

This could potentially let us do cool things eventually, like use pubsub under the hood. Note that pubsub integration would be significantly easier in the first API than the second. But the first API implies adding other new functions that start with `waitFor` at some point in the future.


Discussion:
Hey @carver I've been using this function. Is this different from different from what you were thinking?
https://github.com/ethereum/web3.py/blob/master/web3/utils/transactions.py#L139
@jstoxrocky Yeah, it makes sense to reuse that implementation. But `utils` is presumed to be a private rather than a public API, and has no guarantees about API stability, etc.

So all that's really left for this issue is:

1. Decide on the public API
2. Wire in that public API to that utils implementation
3. Document it
4. Test it
@Carver, I would like to work on this if you can provide some direction. thanks
@scottydelta sure! Let's use that first API. So add `waitForTransactionReceipt` to eth.py which calls through to the `wait_for_transaction_receipt`  method using the web3 attribute in the eth module.

Docs go in the docs folder, and a test to make sure that the method doesn't return until the transaction is mined.

Posting the pull request as a work in progress early on is a good way to make sure that you're in the right track or provide context when asking for help. Please do reach out if you get stuck.
@carver this is somewhat orthogonal but...

What do you think about introducing a `web3.eth.async` module with a coroutine version of this as well?  It'd be nice to start supporting native `asyncio`.  We might need to go as far as implementing new providers as well with coroutine versions of `Provider.make_request` and the corresponding `Manager` methods.  This would also open the door for a `Filter` API with callbacks for logs.
Or maybe web3.async.* that replicates the whole web3 namespace? Seems reasonable, but probably after getting v4 stable.
cc @NIC619 as I think this would be beneficial for the VMC at some point.  Just want to put this in your awareness.
@NIC619 moving async discussion to a new #657 so @scottydelta and I can focus on `waitForTransactionReceipt` here.
This is pretty helpful! Thanks for the work
>@scottydelta sure! Let's use that first API. So add waitForTransactionReceipt to eth.py which calls through to the wait_for_transaction_receipt method using the web3 attribute in the eth module.

@carver, if we follow this approach then we will be making unnecessary loop.

1. User calls `web3.eth.waitForTransactionReceipt`
2. `web3.eth.waitForTransactionReceipt` calls `web3.utils.transactions.wait_for_transaction_receipt` 
3. `web3.utils.transactions.wait_for_transaction_receipt` calls `web3.eth.getTransactionReceipt`

so wont it be easier if `web3.eth.waitForTransactionReceipt` just calls `web3.eth.getTransactionReceipt`? Am I missing something?


Only that we try to avoid keeping the logic at the "edges" where the public API is, so putting the logic in private utility modules and referencing it in the public API is more consistent with our style. I don't see this as being a significant performance hit, relative to the other things happening in the flow.
ok cool, I will add it in the way you suggested, thanks.

Resolving patch:
diff --git a/web3/eth.py b/web3/eth.py
--- a/web3/eth.py
+++ b/web3/eth.py
@@ -43,6 +43,7 @@
     get_buffered_gas_estimate,
     get_required_transaction,
     replace_transaction,
+    wait_for_transaction_receipt,
 )
 
 
@@ -202,6 +203,9 @@ def getTransactionFromBlock(self, block_identifier, transaction_index):
             [block_identifier, transaction_index],
         )
 
+    def waitForTransactionReceipt(self, transaction_hash, timeout=120):
+        return wait_for_transaction_receipt(self.web3, transaction_hash, timeout)
+
     def getTransactionReceipt(self, transaction_hash):
         return self.web3.manager.request_blocking(
             "eth_getTransactionReceipt",
diff --git a/web3/middleware/simulate_unmined_transaction.py b/web3/middleware/simulate_unmined_transaction.py
new file mode 100644
--- /dev/null
+++ b/web3/middleware/simulate_unmined_transaction.py
@@ -0,0 +1,21 @@
+import collections
+import itertools
+
+counter = itertools.count()
+
+INVOCATIONS_BEFORE_RESULT = 5
+
+
+def unmined_receipt_simulator_middleware(make_request, web3):
+    receipt_counters = collections.defaultdict(itertools.count)
+
+    def middleware(method, params):
+        if method == 'eth_getTransactionReceipt':
+            txn_hash = params[0]
+            if next(receipt_counters[txn_hash]) < INVOCATIONS_BEFORE_RESULT:
+                return {'result': None}
+            else:
+                return make_request(method, params)
+        else:
+            return make_request(method, params)
+    return middleware
diff --git a/web3/utils/transactions.py b/web3/utils/transactions.py
--- a/web3/utils/transactions.py
+++ b/web3/utils/transactions.py
@@ -1,5 +1,4 @@
 import math
-import random
 
 from cytoolz import (
     assoc,
@@ -50,13 +49,13 @@ def fill_transaction_defaults(web3, transaction):
     return merge(defaults, transaction)
 
 
-def wait_for_transaction_receipt(web3, txn_hash, timeout=120):
+def wait_for_transaction_receipt(web3, txn_hash, timeout=120, poll_latency=0.1):
     with Timeout(timeout) as _timeout:
         while True:
             txn_receipt = web3.eth.getTransactionReceipt(txn_hash)
             if txn_receipt is not None:
                 break
-            _timeout.sleep(random.random())
+            _timeout.sleep(poll_latency)
     return txn_receipt
 
 


Test patch:
diff --git a/tests/core/eth-module/test_transactions.py b/tests/core/eth-module/test_transactions.py
--- a/tests/core/eth-module/test_transactions.py
+++ b/tests/core/eth-module/test_transactions.py
@@ -3,6 +3,9 @@
 from web3.exceptions import (
     ValidationError,
 )
+from web3.middleware.simulate_unmined_transaction import (
+    unmined_receipt_simulator_middleware,
+)
 
 
 @pytest.mark.parametrize(
@@ -31,7 +34,6 @@ def test_send_transaction_with_valid_chain_id(web3, make_chain_id, expect_succes
         'to': web3.eth.accounts[1],
         'chainId': make_chain_id(web3),
     }
-
     if expect_success:
         # just be happy that we didn't crash
         web3.eth.sendTransaction(transaction)
@@ -40,3 +42,17 @@ def test_send_transaction_with_valid_chain_id(web3, make_chain_id, expect_succes
             web3.eth.sendTransaction(transaction)
 
         assert 'chain ID' in str(exc_info.value)
+
+
+def test_unmined_transaction_wait_for_receipt(web3, extra_accounts):
+    web3.middleware_stack.add(unmined_receipt_simulator_middleware)
+    txn_hash = web3.eth.sendTransaction({
+        'from': web3.eth.coinbase,
+        'to': '0xd3CdA913deB6f67967B99D67aCDFa1712C293601',
+        'value': 123457
+    })
+    assert web3.eth.getTransactionReceipt(txn_hash) is None
+
+    txn_receipt = web3.eth.waitForTransactionReceipt(txn_hash)
+    assert txn_receipt['transactionHash'] == txn_hash
+    assert txn_receipt['blockHash'] is not None