diff --git a/.gitignore b/.gitignore index a724f5f1ed..26521d2e80 100644 --- a/.gitignore +++ b/.gitignore @@ -106,3 +106,5 @@ crashlytics.properties crashlytics-build.properties fabric.properties +# VS Code +.vscode/ diff --git a/docs/conf.py b/docs/conf.py index 3356789691..d93263c1d3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -54,7 +54,7 @@ # General information about the project. project = u'Web3.py' -copyright = u'2017, Piper Merriam, Jason Carver' +copyright = u'2018, Piper Merriam, Jason Carver' __version__ = setup_version # The version info for the project you're documenting, acts as replacement for diff --git a/docs/web3.shh.rst b/docs/web3.shh.rst index 71856ee4c1..9bf8320413 100644 --- a/docs/web3.shh.rst +++ b/docs/web3.shh.rst @@ -19,12 +19,21 @@ The following properties are available on the ``web.shh`` namespace. .. py:attribute:: Shh.version - The version of Whisper protocol used by client + Returns the Whisper version this node offers. .. code-block:: python >>>web3.shh.version - 2 + 6.0 + +.. py:attribute:: Shh.info + + Returns the Whisper statistics for diagnostics. + + .. code-block:: python + + >>>web3.shh.info + {'maxMessageSize': 1024, 'memory': 240, 'messages': 0, 'minPow': 0.2} Methods ------- @@ -32,100 +41,246 @@ Methods The following methods are available on the ``web3.shh`` namespace. -.. py:method:: Shh.post(self, params) +.. py:method:: Shh.post(self, message) - * Delegates to ``shh_post`` RPC method + * Creates a whisper message and injects it into the network for distribution. - * ``params`` cannot be ``None`` and should contain ``topics`` and ``payload`` + * Parameters: + * ``symKeyID``: When using symmetric key encryption, holds the symmetric key ID. + * ``pubKey``: When using asymmetric key encryption, holds the public key. + * ``ttl``: Time-to-live in seconds. + * ``sig (optional)``: ID of the signing key. + * ``topic``: Message topic (four bytes of arbitrary data). + * ``payload``: Payload to be encrypted. + * ``padding (optional)``: Padding (byte array of arbitrary length). + * ``powTime``: Maximal time in seconds to be spent on prrof of work. + * ``powTarget``: Minimal PoW target required for this message. + * ``targetPeer (optional)``: Peer ID (for peer-to-peer message only). - * Returns ``True`` if the message was succesfully sent,otherwise ``False`` + * Returns ``True`` if the message was succesfully sent, otherwise ``False`` .. code-block:: python - >>>web3.shh.post({"topics":[web3.toHex(text="test_topic")],"payload":web3.toHex(text="test_payload")}) + >>>web3.shh.post({'payload': web3.toHex(text="test_payload"), 'pubKey': recipient_public, 'topic': '0x12340000', 'powTarget': 2.5, 'powTime': 2}) True -.. py:method:: Shh.newIdentity(self) +.. py:method:: Shh.newMessageFilter(self, criteria, poll_interval=None) - * Delegates to ``shh_newIdentity`` RPC method + * Create a new filter within the node. This filter can be used to poll for new messages that match the set of criteria. - * Returns ``address`` of newly created identity. + * If a ``poll_interval`` is specified, the client will asynchronously poll for new messages. + + * Parameters: + * ``symKeyID``: When using symmetric key encryption, holds the symmetric key ID. + * ``privateKeyID``: When using asymmetric key encryption, holds the private key ID. + * ``sig``: Public key of the signature. + * ``minPoW``: Minimal PoW requirement for incoming messages. + * ``topics``: Array of possible topics (or partial topics). + * ``allowP2P``: Indicates if this filter allows processing of direct peer-to-peer messages. + + * Returns ``ShhFilter`` which you can either ``watch(callback)`` or request ``get_new_entries()`` .. code-block:: python - >>>web3.shh.newIdentity() - u'0x045ed8042f436e1b546afd16e1f803888b896962484c0154fcc7c5fc43e276972af85f29a995a3beb232a4e9a0648858c0c8c0639d709f5d3230807d084b2d5030' + >>>web3.shh.newMessageFilter({'topic': '0x12340000', 'privateKeyID': recipient_private}) + ShhFilter({'filter_id': 'b37c3106cfb683e8f01b5019342399e0d1d74e9160f69b27625faba7a6738554'}) -.. py:method:: Shh.hasIdentity(self, identity) +.. py:method:: Shh.deleteMessageFilter(self, filter_id) - * Delegates to ``shh_hasIdentity`` RPC method + * Deletes a message filter in the node. - * Returns ``True`` if the client holds the private key for the given identity,otherwise ``False`` + * Returns ``True`` if the filter was sucesfully uninstalled, otherwise ``False`` .. code-block:: python - >>>web3.shh.hasIdentity(u'0x045ed8042f436e1b546afd16e1f803888b896962484c0154fcc7c5fc43e276972af85f29a995a3beb232a4e9a0648858c0c8c0639d709f5d3230807d084b2d5030') + >>>web3.shh.deleteMessageFilter('b37c3106cfb683e8f01b5019342399e0d1d74e9160f69b27625faba7a6738554') True -.. py:method:: Shh.newGroup(self) +.. py:method:: Shh.getMessages(self, filter_id) - * Delegates to ``shh_newGroup`` RPC method + * Retrieve messages that match the filter criteria and are received between the last time this function was called and now. - * Returns ``address`` of newly created group. + * Returns all new messages since the last invocation - .. note:: This method is not implemented yet in ``Geth``. `Open Issue `_ + .. code-block:: python -.. py:method:: Shh.addToGroup(self, identity) + >>>web3.shh.getMessages('b37c3106cfb683e8f01b5019342399e0d1d74e9160f69b27625faba7a6738554') + [{ + 'ttl': 50, + 'timestamp': 1524497850, + 'topic': HexBytes('0x13370000'), + 'payload': HexBytes('0x74657374206d657373616765203a29'), + 'padding': HexBytes('0x50ab643f1b23bc6df1b1532bb6704ad947c2453366754aade3e3597553eeb96119f4f4299834d9989dc4ecc67e6b6470317bb3f7396ace0417fc0d6d2023900d3'), + 'pow': 6.73892030848329, + 'hash': HexBytes('0x7418f8f0989655ed2f4f9b496e6b1d9be51ef9f0f5ad89f6f750b0eee268b02f'), + 'recipientPublicKey': HexBytes('0x047d36c9e45fa82fcd27d35bc7d2fd41a2e41e512feec9e4b90ee4293ab12dc2cfc98250a6f5689b07650f8a5ca3a6e0fa8808cd0ce1a1962f2551354487a8fc79') + }] - * Delegates to ``shh_addToGroup`` RPC Method +.. py:method:: Shh.setMaxMessageSize(self, size) - * Returns ``True`` if the identity was succesfully added to the group,otherwise ``False`` + * Sets the maximal message size allowed by this node. Incoming and outgoing messages with a larger size will be rejected. Whisper message size can never exceed the limit imposed by the underlying P2P protocol (10 Mb). - .. note:: This method is not implemented yet in ``Geth``. `Open Issue `_ + * Returns ``True`` if the filter was sucesfully uninstalled, otherwise ``False`` -.. py:method:: Shh.filter(self, filter_params) + .. code-block:: python - * Delegates to ``shh_newFilter`` RPC Method + >>>web3.shh.setMaxMessageSize(1024) + True + +.. py:method:: Shh.setMinPoW(self, min_pow) - * ``filter_params`` should contain the ``topics`` to subscribe + * Sets the minimal PoW required by this node. - * Returns an instance of ``ShhFilter`` on succesful creation of filter,otherwise raises ``ValueError`` exception + * Returns ``True`` if the filter was sucesfully uninstalled, otherwise ``False`` .. code-block:: python - >>>shh_filter = shh.filter({"topics":[web.toHex(text="topic_to_subscribe")]}) - >>>shh_filter.filter_id - u'0x0' + >>>web3.shh.setMinPoW(0.4) + True -.. py:method:: Shh.uninstallFilter(self, filter_id) +.. py:method:: Shh.markTrustedPeer(self, enode) - * Delegates to ``shh_uninstallFilter`` RPC Method + * Marks specific peer trusted, which will allow it to send historic (expired) messages. - * Returns ``True`` if the filter was sucesfully uninstalled ,otherwise ``False`` + * Returns ``True`` if the filter was sucesfully uninstalled, otherwise ``False`` .. code-block:: python - >>>web3.shh.uninstallFilter("0x2") + >>>web3.shh.markTrustedPeer('enode://d25474361659861e9e651bc728a17e807a3359ca0d344afd544ed0f11a31faecaf4d74b55db53c6670fd624f08d5c79adfc8da5dd4a11b9213db49a3b750845e@52.178.209.125:30379') True -.. py:method:: Shh.getFilterChanges(self, filter_id) +--------------- +Asymmetric Keys +--------------- - * Delegates to ``shh_getFilterChanges`` RPC Method +.. py:method:: Shh.newKeyPair(self) - * Returns list of messages recieved since last poll + * Generates a new cryptographic identity for the client, and injects it into the known identities for message decryption + + * Returns the new key pair's identity .. code-block:: python - >>>web3.shh.getFilterChanges(self,"0x2") - [{u'from': u'0x0', u'to': u'0x0', u'ttl': 50, u'hash': u'0xf84900b57d856a6ab1b41afc9784c31be48e841b9bcfc6accac14d05d7189f2f', u'payload': u'0x746573696e67', u'sent': 1476625149}] + >>>web3.shh.newKeyPair() + '86e658cbc6da63120b79b5eec0c67d5dcfb6865a8f983eff08932477282b77bb' -.. py:method:: Shh.getMessages(self, filter_id) +.. py:method:: Shh.addPrivateKey(self, key) + + * Stores a key pair derived from a private key, and returns its ID. + + * Returns the added key pair's ID + + .. code-block:: python + + >>>web3.shh.addPrivateKey('0x7b8190d96cd061a102e551ee36d08d4f3ca1f56fb0008ef5d70c56271d8c46d0') + '86e658cbc6da63120b79b5eec0c67d5dcfb6865a8f983eff08932477282b77bb' + +.. py:method:: Shh.deleteKeyPair(self, id) + + * Deletes the specifies key if it exists. + + * Returns ``True`` if the key pair was deleted, otherwise ``False`` + + .. code-block:: python + + >>>web3.shh.deleteKeyPair('86e658cbc6da63120b79b5eec0c67d5dcfb6865a8f983eff08932477282b77bb') + True + +.. py:method:: Shh.hasKeyPair(self, id) + + * Checks if the whisper node has a private key of a key pair matching the given ID. + + * Returns ``True`` if the key pair exists, otherwise ``False`` + + .. code-block:: python + + >>>web3.shh.hasKeyPair('86e658cbc6da63120b79b5eec0c67d5dcfb6865a8f983eff08932477282b77bb') + False + +.. py:method:: Shh.getPublicKey(self, id) + + * Returns the public key associated with the key pair. + + .. code-block:: python + + >>>web3.shh.getPublicKey('86e658cbc6da63120b79b5eec0c67d5dcfb6865a8f983eff08932477282b77bb') + '0x041b0777ceb8cf8748fe0bba5e55039d650a03eb0239a909f9ee345bbbad249f2aa236a4b8f41f51bd0a97d87c08e69e67c51f154d634ba51a224195212fc31e4e' - * Delegates to ``shh_getMessages`` RPC Method +.. py:method:: Shh.getPrivateKey(self, id) - * Returns a list of all messages + * Returns the private key associated with the key pair. .. code-block:: python - >>>web3.shh.getMessages("0x2") - [{u'from': u'0x0', u'to': u'0x0', u'ttl': 50, u'hash': u'0x808d74d003d1dcbed546cca29d7a4e839794c226296b613b0fa7a8c670f84146', u'payload': u'0x746573696e67617364', u'sent': 1476625342}, {u'from': u'0x0', u'to': u'0x0', u'ttl': 50, u'hash': u'0x62a2eb9a19968d59d8a85e6dc8d73deb9b4cd40c83d95b796262d6affe6397c6', u'payload': u'0x746573696e67617364617364', u'sent': 1476625369}] + >>>web3.shh.getPrivateKey('86e658cbc6da63120b79b5eec0c67d5dcfb6865a8f983eff08932477282b77bb') + '0x7b8190d96cd061a102e551ee36d08d4f3ca1f56fb0008ef5d70c56271d8c46d0' + +--------------- +Symmetric Keys +--------------- + +.. py:method:: Shh.newSymKey(self) + + * Generates a random symmetric key and stores it under id, which is then returned. Will be used in the future for session key exchange + + * Returns the new key pair's identity + + .. code-block:: python + + >>>web3.shh.newSymKey() + '6c388d63003deb378700c9dad87f67df0247e660647d6ba1d04321bbc2f6ce0c' + +.. py:method:: Shh.addSymKey(self, key) + + * Stores the key, and returns its ID. + + * Returns the new key pair's identity + + .. code-block:: python + + >>>web3.shh.addSymKey('0x58f6556e56a0d41b464a083161377c8a9c2e95156921f954f99ef97d41cebaa2') + '6c388d63003deb378700c9dad87f67df0247e660647d6ba1d04321bbc2f6ce0c' + +.. py:method:: Shh.generateSymKeyFromPassword(self) + + * Generates the key from password, stores it, and returns its ID. + + * Returns the new key pair's identity + + .. code-block:: python + + >>>web3.shh.generateSymKeyFromPassword('shh secret pwd') + '6c388d63003deb378700c9dad87f67df0247e660647d6ba1d04321bbc2f6ce0c' + +.. py:method:: Shh.hasSymKey(self, id) + + * Checks if there is a symmetric key stored with the given ID. + + * Returns ``True`` if the key exists, otherwise ``False`` + + .. code-block:: python + + >>>web3.shh.hasSymKey('6c388d63003deb378700c9dad87f67df0247e660647d6ba1d04321bbc2f6ce0c') + False + +.. py:method:: Shh.getSymKey(self, id) + + * Returns the symmetric key associated with the given ID. + + * Returns the public key associated with the key pair + + .. code-block:: python + + >>>web3.shh.getSymKey('6c388d63003deb378700c9dad87f67df0247e660647d6ba1d04321bbc2f6ce0c') + '0x58f6556e56a0d41b464a083161377c8a9c2e95156921f954f99ef97d41cebaa2' + +.. py:method:: Shh.deleteSymKey(self, id) + + * Deletes the symmetric key associated with the given ID. + + * Returns ``True`` if the key pair was deleted, otherwise ``False`` + + .. code-block:: python + + >>>web3.shh.deleteSymKey('6c388d63003deb378700c9dad87f67df0247e660647d6ba1d04321bbc2f6ce0c') + True diff --git a/tests/core/shh-module/conftest.py b/tests/core/shh-module/conftest.py new file mode 100644 index 0000000000..0a8d795c4b --- /dev/null +++ b/tests/core/shh-module/conftest.py @@ -0,0 +1,10 @@ +import pytest + +from web3.shh import ( + Shh, +) + + +@pytest.fixture(autouse=True) +def include_shh_module(web3): + Shh.attach(web3, "shh") diff --git a/tests/core/shh-module/test_shh_filter.py b/tests/core/shh-module/test_shh_filter.py index 8e9dfb8729..c44a576d2b 100644 --- a/tests/core/shh-module/test_shh_filter.py +++ b/tests/core/shh-module/test_shh_filter.py @@ -1,54 +1,129 @@ import time +from hexbytes import ( + HexBytes, +) + def test_shh_sync_filter(web3, skip_if_testrpc): skip_if_testrpc(web3) - topic = web3.toHex(text="test") - shh_filter = web3.shh.filter({"topics": [topic]}) - payloads = [] - payloads.append(str.encode("payload1")) + sender = web3.shh.newKeyPair() + sender_pub = web3.shh.getPublicKey(sender) + + receiver = web3.shh.newKeyPair() + receiver_pub = web3.shh.getPublicKey(receiver) + + topic = '0x13370000' + payloads = [web3.toHex(text="test message :)"), web3.toHex(text="2nd test message")] + + shh_filter = web3.shh.newMessageFilter({ + 'privateKeyID': receiver, + 'sig': sender_pub, + 'topics': [topic] + }) + web3.shh.post({ - "topics": [topic], - "payload": web3.toHex(text=payloads[-1]), + 'sig': sender, + 'powTarget': 2.5, + 'powTime': 2, + 'payload': payloads[0], + 'pubKey': receiver_pub }) time.sleep(1) - payloads.append(str.encode("payload2")) web3.shh.post({ - "topics": [topic], - "payload": web3.toHex(text=payloads[-1]), + 'sig': sender, + 'powTarget': 2.5, + 'powTime': 2, + 'payload': payloads[1], + 'topic': topic, + 'pubKey': receiver_pub }) time.sleep(1) + received_messages = shh_filter.get_new_entries() - assert len(received_messages) > 1 + assert len(received_messages) == 1 - for message in received_messages: - assert message["payload"] in payloads + message = received_messages[0] + + assert message["payload"] == HexBytes(payloads[1]) + assert message["topic"] == HexBytes(topic) def test_shh_async_filter(web3, skip_if_testrpc): skip_if_testrpc(web3) received_messages = [] - topic = web3.toHex(text="test") - shh_filter = web3.shh.filter({"topics": [topic]}) - shh_filter.watch(received_messages.append) - payloads = [] - payloads.append(str.encode("payload1")) + sender = web3.shh.newKeyPair() + sender_pub = web3.shh.getPublicKey(sender) + + receiver = web3.shh.newKeyPair() + receiver_pub = web3.shh.getPublicKey(receiver) + + topic = '0x13370000' + payloads = [web3.toHex(text="test message :)"), web3.toHex(text="2nd test message")] + + shh_filter = web3.shh.newMessageFilter({ + 'privateKeyID': receiver, + 'sig': sender_pub, + 'topics': [topic] + }, poll_interval=0.5) + watcher = shh_filter.watch(received_messages.extend) + web3.shh.post({ - "topics": [topic], - "payload": web3.toHex(text=payloads[-1]), + 'sig': sender, + 'powTarget': 2.5, + 'powTime': 2, + 'payload': payloads[0], + 'topic': topic, + 'pubKey': receiver_pub }) time.sleep(1) - payloads.append(str.encode("payload2")) web3.shh.post({ - "topics": [topic], - "payload": web3.toHex(text=payloads[-1]), + 'sig': sender, + 'powTarget': 2.5, + 'powTime': 2, + 'payload': payloads[1], + 'pubKey': receiver_pub }) time.sleep(1) - assert len(received_messages) > 1 - for message in received_messages: - assert message["payload"] in payloads + assert len(received_messages) == 1 + + message = received_messages[0] + + assert message["payload"] == HexBytes(payloads[0]) + assert message["topic"] == HexBytes(topic) + + watcher.stop() + + +def test_shh_remove_filter(web3, skip_if_testrpc): + skip_if_testrpc(web3) + + receiver = web3.shh.newKeyPair() + receiver_pub = web3.shh.getPublicKey(receiver) + + payload = web3.toHex(text="test message :)") + shh_filter = web3.shh.newMessageFilter({'privateKeyID': receiver}) + + web3.shh.post({ + 'powTarget': 2.5, + 'powTime': 2, + 'payload': payload, + 'pubKey': receiver_pub + }) + time.sleep(1) + + message = shh_filter.get_new_entries()[0] + assert message["payload"] == HexBytes(payload) + + assert web3.shh.deleteMessageFilter(shh_filter.filter_id) + + try: + web3.shh.getMessages(shh_filter.filter_id) + assert False + except: + assert True diff --git a/tests/core/shh-module/test_shh_has_identity.py b/tests/core/shh-module/test_shh_has_identity.py deleted file mode 100644 index a4e8beed0e..0000000000 --- a/tests/core/shh-module/test_shh_has_identity.py +++ /dev/null @@ -1,6 +0,0 @@ -def test_shh_has_identity(web3, skip_if_testrpc): - skip_if_testrpc(web3) - new_identity = web3.shh.newIdentity() - assert isinstance(new_identity, bytes) - assert len(new_identity) == 60 - assert web3.shh.hasIdentity(new_identity) diff --git a/tests/core/shh-module/test_shh_key_pair.py b/tests/core/shh-module/test_shh_key_pair.py new file mode 100644 index 0000000000..f82b1519f0 --- /dev/null +++ b/tests/core/shh-module/test_shh_key_pair.py @@ -0,0 +1,45 @@ +def test_shh_asymmetric_key_pair(web3, skip_if_testrpc): + skip_if_testrpc(web3) + + # Test generating key + key_id = web3.shh.newKeyPair() + assert web3.shh.hasKeyPair(key_id) + assert len(web3.shh.getPublicKey(key_id)) == 132 + + private_key = web3.shh.getPrivateKey(key_id) + assert len(private_key) == 66 + assert web3.shh.deleteKeyPair(key_id) + + # Test adding a key + assert not web3.shh.hasKeyPair(key_id) + key_id = web3.shh.addPrivateKey(private_key) + assert web3.shh.hasKeyPair(key_id) + assert web3.shh.deleteKeyPair(key_id) + + +def test_shh_symmetric_key_pair(web3, skip_if_testrpc): + skip_if_testrpc(web3) + + # Test generating key + key_id = web3.shh.newSymKey() + assert web3.shh.hasSymKey(key_id) + + key = web3.shh.getSymKey(key_id) + assert len(key) == 66 + assert web3.shh.deleteSymKey(key_id) + + # Test adding a key + assert not web3.shh.hasSymKey(key_id) + key_id = web3.shh.addSymKey(key) + assert web3.shh.hasSymKey(key_id) + assert web3.shh.deleteSymKey(key_id) + + +def test_shh_symmetric_key_pair_from_password(web3, skip_if_testrpc): + skip_if_testrpc(web3) + + key_id = web3.shh.generateSymKeyFromPassword('shh be quiet') + + assert web3.shh.hasSymKey(key_id) + assert len(web3.shh.getSymKey(key_id)) == 66 + assert web3.shh.deleteSymKey(key_id) diff --git a/tests/core/shh-module/test_shh_new_identity.py b/tests/core/shh-module/test_shh_new_identity.py deleted file mode 100644 index 7a2db2f68d..0000000000 --- a/tests/core/shh-module/test_shh_new_identity.py +++ /dev/null @@ -1,5 +0,0 @@ -def test_shh_new_identity(web3, skip_if_testrpc): - skip_if_testrpc(web3) - new_identity = web3.shh.newIdentity() - assert isinstance(new_identity, bytes) - assert len(new_identity) == 60 diff --git a/tests/core/shh-module/test_shh_post.py b/tests/core/shh-module/test_shh_post.py index cb11cd8d97..ff69eb0c63 100644 --- a/tests/core/shh-module/test_shh_post.py +++ b/tests/core/shh-module/test_shh_post.py @@ -1,7 +1,10 @@ def test_shh_post(web3, skip_if_testrpc): skip_if_testrpc(web3) - random_topic = "testing" + receiver_pub = web3.shh.getPublicKey(web3.shh.newKeyPair()) assert web3.shh.post({ - "topics": [web3.toHex(text=random_topic)], + "topic": "0x12345678", + "powTarget": 2.5, + "powTime": 2, "payload": web3.toHex(text="testing shh on web3.py"), + "pubKey": receiver_pub, }) diff --git a/tests/core/shh-module/test_shh_properties.py b/tests/core/shh-module/test_shh_properties.py index 6c5af8e35f..e95af46e9f 100644 --- a/tests/core/shh-module/test_shh_properties.py +++ b/tests/core/shh-module/test_shh_properties.py @@ -1,3 +1,16 @@ def test_shh_version(web3, skip_if_testrpc): skip_if_testrpc(web3) - assert web3.shh.version == 2 + assert web3.shh.version == '6.0' + + +def test_shh_info(web3, skip_if_testrpc): + skip_if_testrpc(web3) + + web3.shh.setMaxMessageSize(1024) + web3.shh.setMinPoW(0.5) + + info = web3.shh.info + + assert len(info) == 4 + assert info["maxMessageSize"] == 1024 + assert info["minPow"] == 0.5 diff --git a/web3/middleware/pythonic.py b/web3/middleware/pythonic.py index 4b1b2e8988..b052cbbb65 100644 --- a/web3/middleware/pythonic.py +++ b/web3/middleware/pythonic.py @@ -109,11 +109,12 @@ def to_hexbytes(num_bytes, val, variable_length=False): WHISPER_LOG_FORMATTERS = { - 'from': to_hexbytes(60), - 'hash': to_hexbytes(32), + 'sig': to_hexbytes(130), + 'topic': to_hexbytes(8), 'payload': HexBytes, - 'to': to_hexbytes(60), - 'topics': apply_formatter_to_array(HexBytes), + 'padding': apply_formatter_if(is_not_null, HexBytes), + 'hash': to_hexbytes(64), + 'recipientPublicKey': apply_formatter_if(is_not_null, to_hexbytes(130)), } @@ -329,11 +330,7 @@ def to_hexbytes(num_bytes, val, variable_length=False): 'personal_newAccount': to_checksum_address, 'personal_sendTransaction': to_hexbytes(32), # SHH - 'shh_getFilterChanges': apply_formatter_to_array(whisper_log_formatter), - 'shh_getMessages': apply_formatter_to_array(whisper_log_formatter), - 'shh_newIdentity': to_hexbytes(60), - 'shh_newGroup': to_hexbytes(60), - 'shh_version': to_integer_if_hex, + 'shh_getFilterMessages': apply_formatter_to_array(whisper_log_formatter), # Transaction Pool 'txpool_content': transaction_pool_content_formatter, 'txpool_inspect': transaction_pool_inspect_formatter, diff --git a/web3/shh.py b/web3/shh.py index 5403e6baaf..52875ab3a9 100644 --- a/web3/shh.py +++ b/web3/shh.py @@ -11,39 +11,69 @@ class Shh(Module): def version(self): return self.web3.manager.request_blocking("shh_version", []) - def post(self, params): - if params and ("topics" in params) and ("payload" in params): - return self.web3.manager.request_blocking("shh_post", [params]) - else: - raise ValueError( - "params cannot be None or does not contain fields 'topic' or " - "'payload'" - ) + @property + def info(self): + return self.web3.manager.request_blocking("shh_info", []) + + def setMaxMessageSize(self, size): + return self.web3.manager.request_blocking("shh_setMaxMessageSize", [size]) + + def setMinPoW(self, min_pow): + return self.web3.manager.request_blocking("shh_setMinPoW", [min_pow]) + + def markTrustedPeer(self, enode): + return self.web3.manager.request_blocking("shh_markTrustedPeer", [enode]) + + def newKeyPair(self): + return self.web3.manager.request_blocking("shh_newKeyPair", []) + + def addPrivateKey(self, key): + return self.web3.manager.request_blocking("shh_addPrivateKey", [key]) + + def deleteKeyPair(self, id): + return self.web3.manager.request_blocking("shh_deleteKeyPair", [id]) - def newIdentity(self): - return self.web3.manager.request_blocking("shh_newIdentity", []) + def hasKeyPair(self, id): + return self.web3.manager.request_blocking("shh_hasKeyPair", [id]) - def hasIdentity(self, identity): - return self.web3.manager.request_blocking("shh_hasIdentity", [identity]) + def getPublicKey(self, id): + return self.web3.manager.request_blocking("shh_getPublicKey", [id]) - def newGroup(self): - return self.web3.manager.request_blocking("shh_newGroup", []) + def getPrivateKey(self, id): + return self.web3.manager.request_blocking("shh_getPrivateKey", [id]) - def addToGroup(self, params): - return self.web3.manager.request_blocking("shh_addToGroup", params) + def newSymKey(self): + return self.web3.manager.request_blocking("shh_newSymKey", []) - def filter(self, filter_params): - if "topics" in filter_params: - filter_id = self.web3.manager.request_blocking("shh_newFilter", [filter_params]) - return ShhFilter(self.web3, filter_id) + def addSymKey(self, key): + return self.web3.manager.request_blocking("shh_addSymKey", [key]) + + def generateSymKeyFromPassword(self, password): + return self.web3.manager.request_blocking("shh_generateSymKeyFromPassword", [password]) + + def hasSymKey(self, id): + return self.web3.manager.request_blocking("shh_hasSymKey", [id]) + + def getSymKey(self, id): + return self.web3.manager.request_blocking("shh_getSymKey", [id]) + + def deleteSymKey(self, id): + return self.web3.manager.request_blocking("shh_deleteSymKey", [id]) + + def post(self, message): + if message and ("payload" in message): + return self.web3.manager.request_blocking("shh_post", [message]) else: - raise ValueError("filter params doesnot contain 'topics' to subsrcibe") + raise ValueError( + "message cannot be None or does not contain field 'payload'" + ) - def uninstallFilter(self, filter_id): - return self.web3.manager.request_blocking("shh_uninstallFilter", [filter_id]) + def newMessageFilter(self, criteria, poll_interval=None): + filter_id = self.web3.manager.request_blocking("shh_newMessageFilter", [criteria]) + return ShhFilter(self.web3, filter_id, poll_interval=poll_interval) - def getMessages(self, filter_id): - return self.web3.manager.request_blocking("shh_getMessages", [filter_id]) + def deleteMessageFilter(self, filter_id): + return self.web3.manager.request_blocking("shh_deleteMessageFilter", [filter_id]) - def getFilterChanges(self, filter_id): - return self.web3.manager.request_blocking("shh_getFilterChanges", [filter_id]) + def getMessages(self, filter_id): + return self.web3.manager.request_blocking("shh_getFilterMessages", [filter_id]) diff --git a/web3/utils/filters.py b/web3/utils/filters.py index 5a562115bb..caccf65072 100644 --- a/web3/utils/filters.py +++ b/web3/utils/filters.py @@ -5,6 +5,9 @@ is_string, ) +from web3.utils.threads import ( + TimerClass, +) from web3.utils.validation import ( validate_address, ) @@ -173,4 +176,28 @@ def is_valid_entry(self, entry): class ShhFilter(Filter): - pass + def __init__(self, *args, **kwargs): + self.poll_interval = kwargs.pop( + 'poll_interval', + self.poll_interval, + ) + super().__init__(*args, **kwargs) + + def get_new_entries(self): + log_entries = self._filter_valid_entries(self.web3.shh.getMessages(self.filter_id)) + return self._format_log_entries(log_entries) + + def get_all_entries(self): + raise NotImplementedError() + + def watch(self, callback): + def callback_wrapper(): + entries = self.get_new_entries() + + if entries: + callback(entries) + + timer = TimerClass(self.poll_interval, callback_wrapper) + timer.daemon = True + timer.start() + return timer diff --git a/web3/utils/threads.py b/web3/utils/threads.py index d97deb8291..1231ff04da 100644 --- a/web3/utils/threads.py +++ b/web3/utils/threads.py @@ -90,6 +90,23 @@ def get(self, timeout=None): raise RuntimeError("Something went wrong. No `_return` property was set") +class TimerClass(threading.Thread): + def __init__(self, interval, callback, *args): + threading.Thread.__init__(self) + self.callback = callback + self.terminate_event = threading.Event() + self.interval = interval + self.args = args + + def run(self): + while not self.terminate_event.is_set(): + self.callback(*self.args) + self.terminate_event.wait(self.interval) + + def stop(self): + self.terminate_event.set() + + def spawn(target, *args, thread_class=ThreadWithReturn, **kwargs): thread = thread_class( target=target,