From c2aeb344bc327a6c1571536be8d8ce0c0f13f730 Mon Sep 17 00:00:00 2001 From: Jonathan Zernik Date: Thu, 23 Dec 2021 08:33:40 -0800 Subject: [PATCH] Implement node settings (#1884) * Implement node settings class * Fix price policy for zero price * Remove old comment * Remove old comments * Remove old comments --- squeaknode/node/node_settings.py | 64 ++++++++++++++++++++++++++++ squeaknode/node/price_policy.py | 24 ++++------- squeaknode/node/squeak_controller.py | 25 +++-------- squeaknode/node/squeak_node.py | 6 +++ tests/node/test_node_settings.py | 58 +++++++++++++++++++++++++ tests/node/test_price_policy.py | 33 +++----------- tests/node/test_squeak_controller.py | 10 +++++ 7 files changed, 160 insertions(+), 60 deletions(-) create mode 100644 squeaknode/node/node_settings.py create mode 100644 tests/node/test_node_settings.py diff --git a/squeaknode/node/node_settings.py b/squeaknode/node/node_settings.py new file mode 100644 index 00000000..d3d32757 --- /dev/null +++ b/squeaknode/node/node_settings.py @@ -0,0 +1,64 @@ +# MIT License +# +# Copyright (c) 2020 Jonathan Zernik +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +import logging +from typing import Optional + +from squeaknode.core.user_config import UserConfig + + +logger = logging.getLogger(__name__) + +NODE_SETTINGS_USERNAME = "default" + + +class NodeSettings: + + def __init__(self, squeak_db): + self.squeak_db = squeak_db + self.username = NODE_SETTINGS_USERNAME + + def insert_user_config(self) -> Optional[str]: + user_config = UserConfig(username=self.username) + return self.squeak_db.insert_config(user_config) + + def set_sell_price_msat(self, sell_price_msat: int) -> None: + self.insert_user_config() + if sell_price_msat < 0: + raise Exception("Sell price cannot be negative.") + self.squeak_db.set_config_sell_price_msat( + username=self.username, + sell_price_msat=sell_price_msat, + ) + + def clear_sell_price_msat(self) -> None: + self.insert_user_config() + self.squeak_db.clear_config_sell_price_msat( + username=self.username, + ) + + def get_sell_price_msat(self) -> Optional[int]: + user_config = self.squeak_db.get_config( + username=self.username, + ) + if user_config is None: + return None + return user_config.sell_price_msat diff --git a/squeaknode/node/price_policy.py b/squeaknode/node/price_policy.py index 1e88be52..935ee056 100644 --- a/squeaknode/node/price_policy.py +++ b/squeaknode/node/price_policy.py @@ -27,8 +27,8 @@ from squeak.core import CSqueak from squeaknode.config.config import SqueaknodeConfig from squeaknode.core.peer_address import PeerAddress from squeaknode.core.squeak_peer import SqueakPeer -from squeaknode.core.user_config import UserConfig from squeaknode.db.squeak_db import SqueakDb +from squeaknode.node.node_settings import NodeSettings logger = logging.getLogger(__name__) @@ -36,9 +36,10 @@ logger = logging.getLogger(__name__) class PricePolicy: - def __init__(self, squeak_db: SqueakDb, config: SqueaknodeConfig): + def __init__(self, squeak_db: SqueakDb, config: SqueaknodeConfig, node_settings: NodeSettings): self.squeak_db = squeak_db self.config = config + self.node_settings = node_settings def get_price(self, squeak: CSqueak, peer_address: PeerAddress) -> int: """Get the price to sell this squeak to this peer. @@ -48,11 +49,10 @@ class PricePolicy: peer = self.get_peer(peer_address) if peer is not None and peer.share_for_free: return 0 - # Return sell price from settings if configured - sell_price = self.get_sell_price_msat() - if sell_price is not None: - return sell_price - return self.get_default_price() + sell_price_msat = self.get_sell_price_msat() + if sell_price_msat is None: + return self.get_default_price() + return sell_price_msat def get_peer(self, peer_address: PeerAddress) -> Optional[SqueakPeer]: return self.squeak_db.get_peer_by_address(peer_address) @@ -60,13 +60,5 @@ class PricePolicy: def get_default_price(self) -> int: return self.config.node.price_msat - def get_user_config(self) -> Optional[UserConfig]: - return self.squeak_db.get_config( - username=self.config.webadmin.username, - ) - def get_sell_price_msat(self) -> Optional[int]: - user_config = self.get_user_config() - if user_config is None: - return None - return user_config.sell_price_msat + return self.node_settings.get_sell_price_msat() diff --git a/squeaknode/node/squeak_controller.py b/squeaknode/node/squeak_controller.py index e8ec8de9..006fe594 100644 --- a/squeaknode/node/squeak_controller.py +++ b/squeaknode/node/squeak_controller.py @@ -87,6 +87,7 @@ class SqueakController: network_manager, download_manager, tweet_forwarder, + node_settings, config, ): self.squeak_db = squeak_db @@ -100,6 +101,7 @@ class SqueakController: self.twitter_stream_change_listener = EventListener() self.active_download_manager = download_manager self.tweet_forwarder = tweet_forwarder + self.node_settings = node_settings self.config = config def save_squeak(self, squeak: CSqueak) -> Optional[bytes]: @@ -253,7 +255,8 @@ class SqueakController: return sent_offer def get_price_for_squeak(self, squeak: CSqueak, peer_address: PeerAddress) -> int: - price_policy = PricePolicy(self.squeak_db, self.config) + price_policy = PricePolicy( + self.squeak_db, self.config, self.node_settings) return price_policy.get_price(squeak, peer_address) def create_signing_profile(self, profile_name: str) -> int: @@ -868,27 +871,13 @@ class SqueakController: return self.squeak_db.insert_config(user_config) def set_sell_price_msat(self, sell_price_msat: int) -> None: - self.insert_user_config() - if sell_price_msat < 0: - raise Exception("Sell price cannot be negative.") - self.squeak_db.set_config_sell_price_msat( - username=self.config.webadmin.username, - sell_price_msat=sell_price_msat, - ) + self.node_settings.set_sell_price_msat(sell_price_msat) def clear_sell_price_msat(self) -> None: - self.insert_user_config() - self.squeak_db.clear_config_sell_price_msat( - username=self.config.webadmin.username, - ) + self.node_settings.clear_sell_price_msat() def get_sell_price_msat(self) -> Optional[int]: - user_config = self.squeak_db.get_config( - username=self.config.webadmin.username, - ) - if user_config is None: - return None - return user_config.sell_price_msat + return self.node_settings.get_sell_price_msat() def get_default_sell_price_msat(self) -> int: return self.config.node.price_msat diff --git a/squeaknode/node/squeak_node.py b/squeaknode/node/squeak_node.py index 60e2a82f..bcead937 100644 --- a/squeaknode/node/squeak_node.py +++ b/squeaknode/node/squeak_node.py @@ -36,6 +36,7 @@ from squeaknode.db.squeak_db import SqueakDb from squeaknode.lightning.lnd_lightning_client import LNDLightningClient from squeaknode.network.network_manager import NetworkManager from squeaknode.node.active_download_manager import ActiveDownloadManager +from squeaknode.node.node_settings import NodeSettings from squeaknode.node.payment_processor import PaymentProcessor from squeaknode.node.peer_connection_worker import PeerConnectionWorker from squeaknode.node.peer_subscription_update_worker import PeerSubscriptionUpdateWorker @@ -61,6 +62,7 @@ class SqueakNode: def _initialize(self): self.initialize_network() self.initialize_db() + self.initialize_node_settings() self.initialize_lightning_client() self.initialize_bitcoin_client() self.initialize_bitcoin_block_subscription_client() @@ -132,6 +134,9 @@ class SqueakNode: self.squeak_db = SqueakDb(engine) self.squeak_db.init_with_retries() + def initialize_node_settings(self): + self.node_settings = NodeSettings(self.squeak_db) + def initialize_lightning_client(self): # load the lightning client self.lightning_client = LNDLightningClient( @@ -188,6 +193,7 @@ class SqueakNode: self.network_manager, self.download_manager, self.twitter_forwarder, + self.node_settings, self.config, ) diff --git a/tests/node/test_node_settings.py b/tests/node/test_node_settings.py new file mode 100644 index 00000000..9693cca0 --- /dev/null +++ b/tests/node/test_node_settings.py @@ -0,0 +1,58 @@ +# MIT License +# +# Copyright (c) 2020 Jonathan Zernik +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +import pytest +from sqlalchemy import create_engine + +from squeaknode.db.squeak_db import SqueakDb +from squeaknode.node.node_settings import NodeSettings + + +@pytest.fixture +def db_engine(): + yield create_engine('sqlite://') + + +@pytest.fixture +def squeak_db(db_engine): + db = SqueakDb(db_engine) + db.init() + yield db + + +@pytest.fixture +def node_settings( + squeak_db, +): + return NodeSettings(squeak_db) + + +def test_default_set_sell_price(node_settings): + retrieved_sell_price_msat = node_settings.get_sell_price_msat() + + assert retrieved_sell_price_msat is None + + +def test_set_sell_price(node_settings): + node_settings.set_sell_price_msat(666) + retrieved_sell_price_msat = node_settings.get_sell_price_msat() + + assert retrieved_sell_price_msat == 666 diff --git a/tests/node/test_price_policy.py b/tests/node/test_price_policy.py index a94af5b3..90e059b1 100644 --- a/tests/node/test_price_policy.py +++ b/tests/node/test_price_policy.py @@ -27,54 +27,35 @@ from squeaknode.node.price_policy import PricePolicy @pytest.fixture() def price_policy(): - yield PricePolicy(None, None) + yield PricePolicy(None, None, None) def test_get_price(price_policy, squeak, peer_address, user_config): with mock.patch.object(price_policy, 'get_peer', autospec=True) as mock_get_peer, \ - mock.patch.object(price_policy, 'get_user_config', autospec=True) as mock_get_user_config, \ - mock.patch.object(price_policy, 'get_default_price', autospec=True) as mock_get_default_price: + mock.patch.object(price_policy, 'get_sell_price_msat', autospec=True) as mock_get_sell_price_msat: mock_get_peer.return_value = None - mock_get_user_config.return_value = user_config - mock_get_default_price.return_value = 555 + mock_get_sell_price_msat.return_value = 555 assert price_policy.get_price(squeak, peer_address) == 555 def test_get_price_profile_share_free_peer(price_policy, squeak, peer_address, peer, user_config): with mock.patch.object(price_policy, 'get_peer', autospec=True) as mock_get_peer, \ - mock.patch.object(price_policy, 'get_user_config', autospec=True) as mock_get_user_config, \ - mock.patch.object(price_policy, 'get_default_price', autospec=True) as mock_get_default_price: + mock.patch.object(price_policy, 'get_sell_price_msat', autospec=True) as mock_get_sell_price_msat: mock_get_peer.return_value = peer._replace( share_for_free=True, ) - mock_get_user_config.return_value = user_config - mock_get_default_price.return_value = 555 + mock_get_sell_price_msat.return_value = 555 assert price_policy.get_price(squeak, peer_address) == 0 def test_get_price_profile_no_share_free_peer(price_policy, squeak, peer_address, peer, user_config): with mock.patch.object(price_policy, 'get_peer', autospec=True) as mock_get_peer, \ - mock.patch.object(price_policy, 'get_user_config', autospec=True) as mock_get_user_config, \ - mock.patch.object(price_policy, 'get_default_price', autospec=True) as mock_get_default_price: + mock.patch.object(price_policy, 'get_sell_price_msat', autospec=True) as mock_get_sell_price_msat: mock_get_peer.return_value = peer._replace( share_for_free=False, ) - mock_get_user_config.return_value = user_config - mock_get_default_price.return_value = 555 + mock_get_sell_price_msat.return_value = 555 assert price_policy.get_price(squeak, peer_address) == 555 - - -def test_get_price_sell_price_set(price_policy, squeak, peer_address, user_config): - with mock.patch.object(price_policy, 'get_peer', autospec=True) as mock_get_peer, \ - mock.patch.object(price_policy, 'get_user_config', autospec=True) as mock_get_user_config, \ - mock.patch.object(price_policy, 'get_default_price', autospec=True) as mock_get_default_price: - mock_get_peer.return_value = None - mock_get_user_config.return_value = user_config._replace( - sell_price_msat=7777, - ) - mock_get_default_price.return_value = 555 - - assert price_policy.get_price(squeak, peer_address) == 7777 diff --git a/tests/node/test_squeak_controller.py b/tests/node/test_squeak_controller.py index 55fea987..b2f2a250 100644 --- a/tests/node/test_squeak_controller.py +++ b/tests/node/test_squeak_controller.py @@ -31,6 +31,7 @@ from squeaknode.core.squeak_peer import SqueakPeer from squeaknode.db.squeak_db import SqueakDb from squeaknode.network.network_manager import NetworkManager from squeaknode.node.active_download_manager import ActiveDownloadManager +from squeaknode.node.node_settings import NodeSettings from squeaknode.node.payment_processor import PaymentProcessor from squeaknode.node.squeak_controller import SqueakController from squeaknode.twitter.twitter_forwarder import TwitterForwarder @@ -58,6 +59,11 @@ def squeak_db(): return mock.Mock(spec=SqueakDb) +@pytest.fixture +def node_settings(): + return mock.Mock(spec=NodeSettings) + + @pytest.fixture def network_manager(): return mock.Mock(spec=NetworkManager) @@ -119,6 +125,7 @@ def squeak_controller( network_manager, download_manager, twitter_forwarder, + node_settings, config, ): return SqueakController( @@ -128,6 +135,7 @@ def squeak_controller( network_manager, download_manager, twitter_forwarder, + node_settings, config, ) @@ -140,6 +148,7 @@ def regtest_squeak_controller( network_manager, download_manager, twitter_forwarder, + node_settings, regtest_config, ): return SqueakController( @@ -149,6 +158,7 @@ def regtest_squeak_controller( network_manager, download_manager, twitter_forwarder, + node_settings, regtest_config, )