diff --git a/requirements.txt b/requirements.txt index 01e9d692..839f0cbd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ protobuf==3.17.3 PySocks==1.7.1 python-bitcoinlib==0.11.0 pyzmq==22.3.0 -requests==2.26.0 +requests[socks]==2.26.0 SQLAlchemy==1.4.25 squeaklib==0.11.0 typed-config==0.2.5 diff --git a/squeaknode/client/network_controller.py b/squeaknode/client/network_controller.py index a0871b2f..0d354f92 100644 --- a/squeaknode/client/network_controller.py +++ b/squeaknode/client/network_controller.py @@ -20,6 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import logging +from typing import Optional from squeak.core.keys import SqueakPublicKey @@ -35,8 +36,15 @@ DOWNLOAD_TIMEOUT_S = 10 class NetworkController: - def __init__(self, squeak_store: SqueakStore): + def __init__( + self, + squeak_store: SqueakStore, + proxy_host: Optional[str], + proxy_port: Optional[int], + ): self.squeak_store = squeak_store + self.proxy_host = proxy_host + self.proxy_port = proxy_port def download_timeline_async( self, @@ -50,6 +58,8 @@ class NetworkController: downloader = RangeDownloader( peer, self.squeak_store, + self.proxy_host, + self.proxy_port, min_block, max_block, followed_public_keys, @@ -64,6 +74,8 @@ class NetworkController: downloader = RangeDownloader( peer, self.squeak_store, + self.proxy_host, + self.proxy_port, min_block, max_block, [pubkey], @@ -76,6 +88,8 @@ class NetworkController: downloader = SingleDownloader( peer, self.squeak_store, + self.proxy_host, + self.proxy_port, squeak_hash, ) downloader.download_async() diff --git a/squeaknode/client/peer_client.py b/squeaknode/client/peer_client.py index 8657bc87..6e4a65c0 100644 --- a/squeaknode/client/peer_client.py +++ b/squeaknode/client/peer_client.py @@ -28,6 +28,7 @@ from squeak.core import CSqueak from squeak.core.keys import SqueakPublicKey from squeaknode.core.offer import Offer +from squeaknode.core.peer_address import Network from squeaknode.core.squeak_peer import SqueakPeer logger = logging.getLogger(__name__) @@ -38,9 +39,23 @@ REQUEST_TIMEOUT_S = 10 class PeerClient: - def __init__(self, peer: SqueakPeer): + def __init__( + self, + peer: SqueakPeer, + proxy_host: Optional[str], + proxy_port: Optional[int], + ): self.peer = peer + self.proxy_host = proxy_host + self.proxy_port = proxy_port self.base_url = f"http://{peer.address.host}:{peer.address.port}" + self.proxies = {} + if peer.address.network == Network.TORV3 and \ + proxy_host is not None and \ + proxy_port is not None: + self.proxies = { + "http": f'socks5://{proxy_host}:{proxy_port}', + } def lookup( self, @@ -61,6 +76,7 @@ class PeerClient: r = requests.get( # type: ignore url, params=payload, # type: ignore + proxies=self.proxies, timeout=REQUEST_TIMEOUT_S, ) squeak_hashes_str = r.json() @@ -72,7 +88,11 @@ class PeerClient: def get_squeak(self, squeak_hash: bytes) -> Optional[CSqueak]: squeak_hash_str = squeak_hash.hex() url = f"{self.base_url}/squeak/{squeak_hash_str}" - r = requests.get(url, timeout=REQUEST_TIMEOUT_S) + r = requests.get( + url, + proxies=self.proxies, + timeout=REQUEST_TIMEOUT_S, + ) if r.status_code != requests.codes.ok: return None squeak_bytes = r.content @@ -81,7 +101,11 @@ class PeerClient: def get_secret_key(self, squeak_hash: bytes) -> Optional[bytes]: squeak_hash_str = squeak_hash.hex() url = f"{self.base_url}/secretkey/{squeak_hash_str}" - r = requests.get(url, timeout=REQUEST_TIMEOUT_S) + r = requests.get( + url, + proxies=self.proxies, + timeout=REQUEST_TIMEOUT_S, + ) if r.status_code != requests.codes.ok: return None secret_key = r.content @@ -90,7 +114,11 @@ class PeerClient: def get_offer(self, squeak_hash: bytes) -> Optional[Offer]: squeak_hash_str = squeak_hash.hex() url = f"{self.base_url}/offer/{squeak_hash_str}" - r = requests.get(url, timeout=REQUEST_TIMEOUT_S) + r = requests.get( + url, + proxies=self.proxies, + timeout=REQUEST_TIMEOUT_S, + ) if r.status_code != requests.codes.ok: return None offer_json = r.json() diff --git a/squeaknode/client/peer_downloader.py b/squeaknode/client/peer_downloader.py index b976993a..7e874c6b 100644 --- a/squeaknode/client/peer_downloader.py +++ b/squeaknode/client/peer_downloader.py @@ -24,6 +24,7 @@ import threading from abc import ABC from abc import abstractmethod from typing import List +from typing import Optional from squeak.core import CSqueak from squeak.core.keys import SqueakPublicKey @@ -41,9 +42,17 @@ DOWNLOAD_TIMEOUT_S = 10 class PeerDownloader(ABC): - def __init__(self, peer: SqueakPeer, squeak_store: SqueakStore): + def __init__( + self, + peer: SqueakPeer, + squeak_store: SqueakStore, + proxy_host: Optional[str], + proxy_port: Optional[int], + ): self.peer = peer - self.client = PeerClient(peer) + self.proxy_host = proxy_host + self.proxy_port = proxy_port + self.client = PeerClient(peer, proxy_host, proxy_port) self.squeak_store = squeak_store @abstractmethod @@ -114,11 +123,13 @@ class RangeDownloader(PeerDownloader): self, peer: SqueakPeer, squeak_store: SqueakStore, + proxy_host: Optional[str], + proxy_port: Optional[int], min_block: int, max_block: int, pubkeys: List[SqueakPublicKey], ): - super().__init__(peer, squeak_store) + super().__init__(peer, squeak_store, proxy_host, proxy_port) self.min_block = min_block self.max_block = max_block self.pubkeys = pubkeys @@ -142,9 +153,11 @@ class SingleDownloader(PeerDownloader): self, peer: SqueakPeer, squeak_store: SqueakStore, + proxy_host: Optional[str], + proxy_port: Optional[int], squeak_hash: bytes, ): - super().__init__(peer, squeak_store) + super().__init__(peer, squeak_store, proxy_host, proxy_port) self.squeak_hash = squeak_hash def get_hashes(self) -> List[bytes]: diff --git a/squeaknode/node/squeak_controller.py b/squeaknode/node/squeak_controller.py index 0e72775b..04c75b02 100644 --- a/squeaknode/node/squeak_controller.py +++ b/squeaknode/node/squeak_controller.py @@ -28,7 +28,6 @@ from squeak.core import CSqueak from squeak.core.keys import SqueakPrivateKey from squeak.core.keys import SqueakPublicKey -from squeaknode.client.network_controller import NetworkController from squeaknode.core.download_result import DownloadResult from squeaknode.core.lightning_address import LightningAddressHostPort from squeaknode.core.offer import Offer @@ -63,6 +62,7 @@ class SqueakController: squeak_core: SqueakCore, payment_processor, tweet_forwarder, + network_controller, node_settings, config, default_port, @@ -71,6 +71,7 @@ class SqueakController: self.squeak_core = squeak_core self.payment_processor = payment_processor self.tweet_forwarder = tweet_forwarder + self.network_controller = network_controller self.node_settings = node_settings self.config = config self.default_port = default_port @@ -296,8 +297,7 @@ class SqueakController: return self.squeak_store.get_squeak_entry(squeak_hash) def download_single_squeak(self, squeak_hash: bytes) -> DownloadResult: - network_controller = NetworkController(self.squeak_store) - network_controller.download_single_squeak(squeak_hash) + self.network_controller.download_single_squeak(squeak_hash) return DownloadResult(1, 1, 0, 9999) def get_timeline_squeak_entries( @@ -307,8 +307,7 @@ class SqueakController: ) -> List[SqueakEntry]: # TODO: remove this temporary hack, after converting this to websockets. # logger.info('Start downloading timeline...') - # network_controller = NetworkController(self.squeak_store) - # network_controller.download_timeline() + # self.network_controller.download_timeline() # logger.info('Finished downloading timeline.') return self.squeak_store.get_timeline_squeak_entries(limit, last_entry) @@ -341,8 +340,7 @@ class SqueakController: ) -> List[SqueakEntry]: # TODO: remove this temporary hack, after converting this to websockets. logger.info('Start downloading pubkey squeaks...') - network_controller = NetworkController(self.squeak_store) - network_controller.download_pubkey_squeaks_async(public_key) + self.network_controller.download_pubkey_squeaks_async(public_key) logger.info('Finished downloading pubkey squeaks.') return self.squeak_store.get_squeak_entries_for_public_key( public_key, diff --git a/squeaknode/node/squeak_download_worker.py b/squeaknode/node/squeak_download_worker.py index aa35b2a9..a2a04a8d 100644 --- a/squeaknode/node/squeak_download_worker.py +++ b/squeaknode/node/squeak_download_worker.py @@ -32,15 +32,14 @@ class SqueakDownloadWorker(PeriodicWorker): def __init__( self, squeak_store: SqueakStore, + network_controller: NetworkController, download_timeline_interval_s: int, interest_block_interval: int, ): self.squeak_store = squeak_store self.download_timeline_interval_s = download_timeline_interval_s self.interest_block_interval = interest_block_interval - self.network_controller = NetworkController( - self.squeak_store, - ) + self.network_controller = network_controller def work_fn(self): self.network_controller.download_timeline_async( diff --git a/squeaknode/node/squeak_node.py b/squeaknode/node/squeak_node.py index 25fb0268..22fa4c66 100644 --- a/squeaknode/node/squeak_node.py +++ b/squeaknode/node/squeak_node.py @@ -28,6 +28,7 @@ from squeaknode.admin.squeak_admin_server_handler import SqueakAdminServerHandle from squeaknode.admin.squeak_admin_server_servicer import SqueakAdminServerServicer from squeaknode.admin.webapp.app import SqueakAdminWebServer from squeaknode.bitcoin.bitcoin_core_client import BitcoinCoreClient +from squeaknode.client.network_controller import NetworkController from squeaknode.config.config import SqueaknodeConfig from squeaknode.core.squeak_core import SqueakCore from squeaknode.db.db_engine import get_connection_string @@ -63,6 +64,7 @@ class SqueakNode: self.create_squeak_store() self.create_payment_processor() self.create_twitter_forwarder() + self.create_network_controller() self.create_squeak_controller() self.create_peer_handler() @@ -164,12 +166,20 @@ class SqueakNode: self.config.twitter.forward_tweets_retry_s, ) + def create_network_controller(self): + self.network_controller = NetworkController( + self.squeak_store, + self.config.tor.proxy_ip, + self.config.tor.proxy_port, + ) + def create_squeak_controller(self): self.squeak_controller = SqueakController( self.squeak_store, self.squeak_core, self.payment_processor, self.twitter_forwarder, + self.network_controller, self.node_settings, self.config, squeak.params.params.DEFAULT_PORT, @@ -228,6 +238,7 @@ class SqueakNode: def create_squeak_download_worker(self): self.squeak_download_worker = SqueakDownloadWorker( self.squeak_store, + self.network_controller, self.config.node.peer_download_interval_s, self.config.node.interest_block_interval, ) diff --git a/tests/node/test_squeak_controller.py b/tests/node/test_squeak_controller.py index 2698aeb8..23ac5985 100644 --- a/tests/node/test_squeak_controller.py +++ b/tests/node/test_squeak_controller.py @@ -22,6 +22,7 @@ import mock import pytest +from squeaknode.client.network_controller import NetworkController from squeaknode.config.config import SqueaknodeConfig from squeaknode.core.lightning_address import LightningAddressHostPort from squeaknode.core.peer_address import Network @@ -108,12 +109,18 @@ def twitter_forwarder(): return mock.Mock(spec=TwitterForwarder) +@pytest.fixture +def network_controller(): + return mock.Mock(spec=NetworkController) + + @pytest.fixture def squeak_controller( squeak_store, squeak_core, payment_processor, twitter_forwarder, + network_controller, node_settings, config, default_peer_port, @@ -123,6 +130,7 @@ def squeak_controller( squeak_core, payment_processor, twitter_forwarder, + network_controller, node_settings, config, default_peer_port, @@ -135,6 +143,7 @@ def regtest_squeak_controller( squeak_core, payment_processor, twitter_forwarder, + network_controller, node_settings, regtest_config, default_peer_port, @@ -144,6 +153,7 @@ def regtest_squeak_controller( squeak_core, payment_processor, twitter_forwarder, + network_controller, node_settings, regtest_config, default_peer_port,