-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #114 from pipermerriam/piper/implement-delegated-s…
…igning-manager Delegated and PrivateKey signing managers
- Loading branch information
Showing
32 changed files
with
1,021 additions
and
110 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,7 @@ Contents | |
overview | ||
filters | ||
contracts | ||
managers | ||
web3.main | ||
web3.eth | ||
web3.db | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
Managers | ||
======== | ||
|
||
.. py:module:: web3.providers.manager | ||
.. py:currentmodule:: web3.providers.manager | ||
Managers control the flow of RPC requests that get passed to and from whatever | ||
provider is in use. | ||
|
||
|
||
RequestManager | ||
-------------- | ||
|
||
.. py:class:: RequestManager(provider) | ||
This is the default manager that web3 will use. | ||
|
||
|
||
|
||
Delegated Signing Manager | ||
------------------------- | ||
|
||
|
||
.. py:class:: DelegatedSigningManager(wrapped_manager, signing_manager) | ||
This manager incercepts any attempt to send a transaction and instead | ||
routes it through the ``eth_sendRawTransaction`` method, using the | ||
``signing_manager`` to sign the transactions, and the ``wrapped_manager`` | ||
for the actual sending of the transaction. | ||
|
||
Any calls to the following RPC methods will incercepted: | ||
|
||
* ``eth_sendTransaction`` | ||
* ``personal_sendTransaction`` | ||
* ``personal_signAndSendTransaction`` | ||
|
||
The ``signing_manager`` is only used for the signing of transactions via | ||
the ``eth_sign`` RPC method. Any account which you wish to send from needs | ||
to be unlocked on whatever node this manager is connected to. | ||
|
||
The ``wrapped_manager`` is used for all other RPC methods. | ||
|
||
This manager is useful for using a public Ethreum node such as Infura for | ||
your RPC interactions while keeping your private keys held on a local node | ||
that does not need to be connected or synced with the network. | ||
|
||
|
||
.. code-block:: python | ||
# setup RPC provider connected to infura. | ||
>>> web3 = Web3(Web3.RPCProvider(host='mainnet.infura.io', path='your-infura-access-key')) | ||
# create second manager connected to local node (which must be unlocked) | ||
>>> signature_manager = web3.RequestManager(IPCProvider()) | ||
# Setup the signing manager. | ||
>>> delegated_manager = Web3.DelegatedSigningManager(web3._requestManager, signature_manager) | ||
>>> web3.setManager(delegated_manager) | ||
>>> web3.eth.sendTransaction({ | ||
... 'from': '0x...' | ||
... ... | ||
... }) | ||
In this example the transaction will be signed using the locally unlocked IPC | ||
node and then the public Infura RPC node is used relay the pre-signed | ||
transaction to the network using the ``eth_sendRawTransaction`` method. | ||
|
||
|
||
Private Key Signing Manager | ||
--------------------------- | ||
|
||
.. py:class:: PrivateKeySigningManager(wrapped_manager, keys={}) | ||
This manager is similar to the ``DelegatedSigningManager`` except that | ||
rather than delegating to a node to do the signing, it holds the private | ||
keys and does the signing itself. | ||
|
||
The optional ``keys`` constructor should be a mapping between ethereum | ||
address and private key encoded as bytes. | ||
|
||
.. py:method:: PrivateKeySigningManager.register_private_key(key) | ||
This method registers a private key with the manager which will allow | ||
sending from the derived address. | ||
|
||
|
||
.. code-block:: python | ||
>>> web3 = Web3(Web3.RPCProvider(host='mainnet.infura.io', path='your-infura-access-key')) | ||
>>> pk_manager = Web3.PrivateKeySigningManager(web3._requestManager) | ||
>>> pk_manager.register_private_key(b'the-private-key-as-bytes') | ||
>>> web3.setManager(pk_manager) | ||
>>> web3.eth.sendTransaction({ | ||
... 'from': '0x...' # the public address for the registered private key. | ||
... ... | ||
... }) | ||
In this example, the transaction will be signed using the private key it was | ||
given, after which it will be sent using the ``eth_sendRawTransaction`` through | ||
the connected Infura RPC node. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import pytest | ||
|
||
from web3.providers.manager import ( | ||
DelegatedSigningManager, | ||
) | ||
from web3.utils.currency import denoms | ||
|
||
from flaky import flaky | ||
|
||
|
||
@pytest.fixture() | ||
def web3_signer(web3_rpc_empty, wait_for_block): | ||
wait_for_block(web3_rpc_empty) | ||
web3_rpc_empty.miner.stop() | ||
return web3_rpc_empty | ||
|
||
|
||
@pytest.fixture() | ||
def web3_sender(web3_signer, web3_ipc_empty, wait_for_transaction, wait_for_block): | ||
wait_for_block(web3_ipc_empty) | ||
|
||
signer_cb = web3_signer.eth.coinbase | ||
sender_cb = web3_ipc_empty.eth.coinbase | ||
|
||
assert signer_cb != sender_cb | ||
|
||
# fund the account | ||
fund_txn_hash = web3_ipc_empty.eth.sendTransaction({ | ||
'from': sender_cb, | ||
'to': signer_cb, | ||
'value': 10 * denoms.ether, | ||
}) | ||
wait_for_transaction(web3_ipc_empty, fund_txn_hash) | ||
|
||
with pytest.raises(ValueError): | ||
web3_ipc_empty.eth.sendTransaction({ | ||
'from': signer_cb, | ||
'to': sender_cb, | ||
'value': 0, | ||
}) | ||
|
||
web3_ipc_empty._requestManager = DelegatedSigningManager( | ||
wrapped_manager=web3_ipc_empty._requestManager, | ||
signature_manager=web3_signer._requestManager, | ||
) | ||
|
||
return web3_ipc_empty | ||
|
||
|
||
@flaky(max_runs=3) | ||
def test_delegated_signing_manager(web3_sender, | ||
web3_signer, | ||
wait_for_transaction): | ||
sender_cb = web3_sender.eth.coinbase | ||
signer_cb = web3_signer.eth.coinbase | ||
|
||
assert sender_cb in web3_sender.eth.accounts | ||
assert signer_cb not in web3_sender.eth.accounts | ||
|
||
txn_hash = web3_sender.eth.sendTransaction({ | ||
'from': signer_cb, | ||
'to': sender_cb, | ||
'value': 12345, | ||
}) | ||
txn_receipt = wait_for_transaction(web3_sender, txn_hash) | ||
txn = web3_sender.eth.getTransaction(txn_hash) | ||
|
||
assert txn['from'] == signer_cb | ||
assert txn['to'] == sender_cb | ||
assert txn['value'] == 12345 | ||
|
||
|
||
@flaky(max_runs=3) | ||
def test_delegated_signing_manager_tracks_nonces_correctly(web3_sender, | ||
web3_signer, | ||
wait_for_transaction): | ||
sender_cb = web3_sender.eth.coinbase | ||
signer_cb = web3_signer.eth.coinbase | ||
|
||
assert sender_cb in web3_sender.eth.accounts | ||
assert signer_cb not in web3_sender.eth.accounts | ||
|
||
num_to_send = web3_sender.eth.getBlock('latest')['gasLimit'] // 21000 + 10 | ||
|
||
all_txn_hashes = [] | ||
for i in range(num_to_send): | ||
all_txn_hashes.append(web3_sender.eth.sendTransaction({ | ||
'from': signer_cb, | ||
'to': sender_cb, | ||
'value': i, | ||
})) | ||
|
||
all_txn_receipts = [ | ||
wait_for_transaction(web3_sender, txn_hash) | ||
for txn_hash in all_txn_hashes | ||
] | ||
all_txns = [ | ||
web3_sender.eth.getTransaction(txn_hash) | ||
for txn_hash in all_txn_hashes | ||
] | ||
|
||
assert all([ | ||
idx == txn['nonce'] | ||
for idx, txn in enumerate(all_txns) | ||
]) | ||
block_numbers = { | ||
txn_receipt['blockNumber'] for txn_receipt in all_txn_receipts | ||
} | ||
assert len(block_numbers) > 1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import pytest | ||
|
||
from web3.providers.manager import ( | ||
PrivateKeySigningManager, | ||
) | ||
from web3.utils.address import to_address | ||
from web3.utils.currency import denoms | ||
|
||
|
||
@pytest.fixture() | ||
def account_private_key(): | ||
from eth_tester_client.utils import mk_random_privkey | ||
return mk_random_privkey() | ||
|
||
|
||
@pytest.fixture() | ||
def account_public_key(account_private_key): | ||
from ethereum.utils import privtoaddr | ||
from eth_tester_client.utils import encode_address | ||
return to_address(encode_address(privtoaddr(account_private_key))) | ||
|
||
|
||
@pytest.fixture() | ||
def web3_pk_signer(web3_ipc_persistent, | ||
account_public_key, | ||
account_private_key, | ||
wait_for_block, | ||
wait_for_transaction): | ||
pk_signing_manager = PrivateKeySigningManager(web3_ipc_persistent._requestManager) | ||
pk_signing_manager.register_private_key(account_private_key) | ||
assert account_public_key in pk_signing_manager.keys | ||
|
||
wait_for_block(web3_ipc_persistent) | ||
|
||
fund_txn_hash = web3_ipc_persistent.eth.sendTransaction({ | ||
'from': web3_ipc_persistent.eth.coinbase, | ||
'to': account_public_key, | ||
'value': 10 * denoms.ether, | ||
}) | ||
wait_for_transaction(web3_ipc_persistent, fund_txn_hash) | ||
|
||
with pytest.raises(ValueError): | ||
web3_ipc_persistent.eth.sendTransaction({ | ||
'from': account_public_key, | ||
'to': web3_ipc_persistent.eth.coinbase, | ||
'value': 1, | ||
}) | ||
|
||
web3_ipc_persistent._requestManager = pk_signing_manager | ||
return web3_ipc_persistent | ||
|
||
|
||
def test_private_key_signing_manager(web3_pk_signer, account_public_key, wait_for_transaction): | ||
assert account_public_key not in web3_pk_signer.eth.accounts | ||
txn_hash = web3_pk_signer.eth.sendTransaction({ | ||
'from': account_public_key, | ||
'to': web3_pk_signer.eth.coinbase, | ||
'value': 12345, | ||
}) | ||
txn_receipt = wait_for_transaction(web3_pk_signer, txn_hash) | ||
txn = web3_pk_signer.eth.getTransaction(txn_hash) | ||
|
||
assert txn['from'] == account_public_key |
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.