From 959169ed1c91290ea4d0caee6cc00d4b30bab3a7 Mon Sep 17 00:00:00 2001 From: drew2a Date: Fri, 5 Jan 2024 15:34:23 +0100 Subject: [PATCH] Remove `BandwidthCommunity` --- .github/workflows/scripttest.yml | 4 +- .../config/tribler_config.spec | 3 - .../{bandwidth_crawler => crawler}/README.md | 4 +- .../run_crawler.py} | 29 +- scripts/exit_node/run_exit_node.py | 2 - src/run_tribler_headless.py | 1 - .../bandwidth_accounting/__init__.py | 0 .../bandwidth_accounting_component.py | 55 ---- .../community/__init__.py | 0 .../bandwidth_accounting_community.py | 206 ------------- .../bandwidth_accounting/community/cache.py | 42 --- .../bandwidth_accounting/community/payload.py | 35 --- .../bandwidth_accounting/db/__init__.py | 0 .../bandwidth_accounting/db/database.py | 191 ------------- .../bandwidth_accounting/db/history.py | 17 -- .../bandwidth_accounting/db/misc.py | 12 - .../bandwidth_accounting/db/transaction.py | 174 ----------- .../bandwidth_accounting/restapi/__init__.py | 0 .../restapi/bandwidth_endpoint.py | 64 ----- .../bandwidth_accounting/settings.py | 9 - .../bandwidth_accounting/tests/__init__.py | 0 .../test_bandwidth_accounting_component.py | 16 -- .../tests/test_bandwidth_endpoint.py | 79 ----- .../tests/test_community.py | 156 ---------- .../tests/test_database.py | 168 ----------- .../tests/test_transaction.py | 41 --- .../core/components/payout/__init__.py | 0 .../components/payout/payout_component.py | 38 --- .../core/components/payout/payout_manager.py | 68 ----- .../core/components/payout/tests/__init__.py | 0 .../payout/tests/test_payout_component.py | 15 - .../payout/tests/test_payout_manager.py | 97 ------- .../rest/tests/test_statistics_endpoint.py | 15 +- .../components/restapi/restapi_component.py | 4 - .../restapi/tests/test_restapi_component.py | 3 +- .../components/tunnel/community/caches.py | 14 +- .../components/tunnel/community/payload.py | 59 ---- .../tunnel/community/tunnel_community.py | 230 +-------------- .../core/components/tunnel/settings.py | 3 +- .../tests/test_triblertunnel_community.py | 270 +----------------- .../components/tunnel/tunnel_component.py | 6 - src/tribler/core/config/tribler_config.py | 2 - src/tribler/core/start_core.py | 9 +- .../data/upgrade_databases/bandwidth_v8.db | Bin 438272 -> 0 bytes .../core/upgrade/tests/test_upgrader.py | 34 ++- src/tribler/core/upgrade/upgrade.py | 45 +-- src/tribler/gui/debug_window.py | 20 -- src/tribler/gui/qt_resources/debugwindow.ui | 52 +--- src/tribler/gui/tests/test_gui.py | 6 - 49 files changed, 92 insertions(+), 2206 deletions(-) rename scripts/{bandwidth_crawler => crawler}/README.md (73%) rename scripts/{bandwidth_crawler/run_bandwidth_crawler.py => crawler/run_crawler.py} (80%) delete mode 100644 src/tribler/core/components/bandwidth_accounting/__init__.py delete mode 100644 src/tribler/core/components/bandwidth_accounting/bandwidth_accounting_component.py delete mode 100644 src/tribler/core/components/bandwidth_accounting/community/__init__.py delete mode 100644 src/tribler/core/components/bandwidth_accounting/community/bandwidth_accounting_community.py delete mode 100644 src/tribler/core/components/bandwidth_accounting/community/cache.py delete mode 100644 src/tribler/core/components/bandwidth_accounting/community/payload.py delete mode 100644 src/tribler/core/components/bandwidth_accounting/db/__init__.py delete mode 100644 src/tribler/core/components/bandwidth_accounting/db/database.py delete mode 100644 src/tribler/core/components/bandwidth_accounting/db/history.py delete mode 100644 src/tribler/core/components/bandwidth_accounting/db/misc.py delete mode 100644 src/tribler/core/components/bandwidth_accounting/db/transaction.py delete mode 100644 src/tribler/core/components/bandwidth_accounting/restapi/__init__.py delete mode 100644 src/tribler/core/components/bandwidth_accounting/restapi/bandwidth_endpoint.py delete mode 100644 src/tribler/core/components/bandwidth_accounting/settings.py delete mode 100644 src/tribler/core/components/bandwidth_accounting/tests/__init__.py delete mode 100644 src/tribler/core/components/bandwidth_accounting/tests/test_bandwidth_accounting_component.py delete mode 100644 src/tribler/core/components/bandwidth_accounting/tests/test_bandwidth_endpoint.py delete mode 100644 src/tribler/core/components/bandwidth_accounting/tests/test_community.py delete mode 100644 src/tribler/core/components/bandwidth_accounting/tests/test_database.py delete mode 100644 src/tribler/core/components/bandwidth_accounting/tests/test_transaction.py delete mode 100644 src/tribler/core/components/payout/__init__.py delete mode 100644 src/tribler/core/components/payout/payout_component.py delete mode 100644 src/tribler/core/components/payout/payout_manager.py delete mode 100644 src/tribler/core/components/payout/tests/__init__.py delete mode 100644 src/tribler/core/components/payout/tests/test_payout_component.py delete mode 100644 src/tribler/core/components/payout/tests/test_payout_manager.py delete mode 100644 src/tribler/core/tests/tools/data/upgrade_databases/bandwidth_v8.db diff --git a/.github/workflows/scripttest.yml b/.github/workflows/scripttest.yml index 9da846f4a46..68eec17c475 100644 --- a/.github/workflows/scripttest.yml +++ b/.github/workflows/scripttest.yml @@ -42,10 +42,10 @@ jobs: with: path: ./src - - name: run_bandwidth_crawler.py + - name: run_crawler.py uses: ./.github/actions/timeout with: - command: python ./scripts/bandwidth_crawler/run_bandwidth_crawler.py --fragile + command: python ./scripts/crawler/run_crawler.py --fragile duration: ${{inputs.duration}} - name: run_exit_node.py diff --git a/scripts/application_tester/tribler_apptester/config/tribler_config.spec b/scripts/application_tester/tribler_apptester/config/tribler_config.spec index b9c4bc41530..32d418adea6 100644 --- a/scripts/application_tester/tribler_apptester/config/tribler_config.spec +++ b/scripts/application_tester/tribler_apptester/config/tribler_config.spec @@ -22,9 +22,6 @@ ec_keypair_filename = string(default='ec_multichain.pem') testnet_keypair_filename = string(default='ec_trustchain_testnet.pem') testnet = boolean(default=False) -[bandwidth_accounting] -testnet = boolean(default=False) - [bootstrap] enabled = boolean(default=True) max_download_rate = integer(min=1, default=1000000) diff --git a/scripts/bandwidth_crawler/README.md b/scripts/crawler/README.md similarity index 73% rename from scripts/bandwidth_crawler/README.md rename to scripts/crawler/README.md index 60e572d85da..658140ffde6 100644 --- a/scripts/bandwidth_crawler/README.md +++ b/scripts/crawler/README.md @@ -1,4 +1,4 @@ -# Bandwidth Crawler +# Crawler ## Requirements @@ -8,5 +8,5 @@ ## Execution ```shell -python3 run_bandwidth_crawler.py +python3 run_crawler.py ``` \ No newline at end of file diff --git a/scripts/bandwidth_crawler/run_bandwidth_crawler.py b/scripts/crawler/run_crawler.py similarity index 80% rename from scripts/bandwidth_crawler/run_bandwidth_crawler.py rename to scripts/crawler/run_crawler.py index 4561e3f8360..2dcac089d84 100644 --- a/scripts/bandwidth_crawler/run_bandwidth_crawler.py +++ b/scripts/crawler/run_crawler.py @@ -1,5 +1,7 @@ """ -This executable script starts a Tribler instance and joins the BandwidthAccountingCommunity. +This is an example of a Tribler crawler. It was originally written by @devos50 +to crawl the BandwidthCommunity's data and was later adapted to serve +as just an example of a generic Tribler crawler. """ import argparse import logging @@ -8,7 +10,6 @@ from asyncio import ensure_future, get_event_loop from pathlib import Path -from tribler.core.components.bandwidth_accounting.bandwidth_accounting_component import BandwidthAccountingComponent from tribler.core.components.ipv8.ipv8_component import Ipv8Component from tribler.core.components.key.key_component import KeyComponent from tribler.core.components.session import Session @@ -16,6 +17,19 @@ from tribler.core.utilities.utilities import make_async_loop_fragile +async def crawler_session(session_config: TriblerConfig): + session = Session( + config=session_config, + components=[ + KeyComponent(), + Ipv8Component(), + # Put Your Component Here + ]) + signal.signal(signal.SIGTERM, lambda signum, stack: session.shutdown_event.set) + async with session: + await session.shutdown_event.wait() + + class PortAction(argparse.Action): def __call__(self, _, namespace, value, option_string=None): if not 0 < value < 2 ** 16: @@ -23,14 +37,6 @@ def __call__(self, _, namespace, value, option_string=None): setattr(namespace, self.dest, value) -async def crawler_session(session_config: TriblerConfig): - session = Session(session_config, - [KeyComponent(), Ipv8Component(), BandwidthAccountingComponent(crawler_mode=True)]) - signal.signal(signal.SIGTERM, lambda signum, stack: session.shutdown_event.set) - async with session: - await session.shutdown_event.wait() - - if __name__ == "__main__": parser = argparse.ArgumentParser(description=('Start a crawler in the bandwidth accounting community')) parser.add_argument('--statedir', '-s', default='bw_crawler', type=str, help='Use an alternate statedir') @@ -45,13 +51,10 @@ async def crawler_session(session_config: TriblerConfig): config = TriblerConfig.load(state_dir=state_dir) config.tunnel_community.enabled = False - config.libtorrent.enabled = False config.bootstrap.enabled = False - config.chant.enabled = False config.torrent_checking.enabled = False config.api.http_enabled = True config.api.http_port = args.restapi - config.bandwidth_accounting.outgoing_query_interval = 5 loop = get_event_loop() if args.fragile: diff --git a/scripts/exit_node/run_exit_node.py b/scripts/exit_node/run_exit_node.py index c7c220337ce..8150aeb51f2 100644 --- a/scripts/exit_node/run_exit_node.py +++ b/scripts/exit_node/run_exit_node.py @@ -15,7 +15,6 @@ from ipv8.util import run_forever from tribler.core import notifications -from tribler.core.components.bandwidth_accounting.bandwidth_accounting_component import BandwidthAccountingComponent from tribler.core.components.ipv8.ipv8_component import Ipv8Component from tribler.core.components.key.key_component import KeyComponent from tribler.core.components.restapi.restapi_component import RESTComponent @@ -33,7 +32,6 @@ def components_gen(): yield KeyComponent() yield RESTComponent() yield Ipv8Component() - yield BandwidthAccountingComponent() yield TunnelsComponent() diff --git a/src/run_tribler_headless.py b/src/run_tribler_headless.py index 3f07fdce012..37932a1a73d 100644 --- a/src/run_tribler_headless.py +++ b/src/run_tribler_headless.py @@ -114,7 +114,6 @@ async def signal_handler(sig): if options.testnet: config.tunnel_community.testnet = True config.chant.testnet = True - config.bandwidth_accounting.testnet = True self.session = Session(config, components=list(components_gen(config))) try: diff --git a/src/tribler/core/components/bandwidth_accounting/__init__.py b/src/tribler/core/components/bandwidth_accounting/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/tribler/core/components/bandwidth_accounting/bandwidth_accounting_component.py b/src/tribler/core/components/bandwidth_accounting/bandwidth_accounting_component.py deleted file mode 100644 index 9686af225e9..00000000000 --- a/src/tribler/core/components/bandwidth_accounting/bandwidth_accounting_component.py +++ /dev/null @@ -1,55 +0,0 @@ -from tribler.core.components.bandwidth_accounting.community.bandwidth_accounting_community import ( - BandwidthAccountingCommunity, - BandwidthAccountingTestnetCommunity, -) -from tribler.core.components.bandwidth_accounting.db.database import BandwidthDatabase -from tribler.core.components.component import Component -from tribler.core.components.ipv8.ipv8_component import Ipv8Component -from tribler.core.utilities.simpledefs import STATEDIR_DB_DIR - - -class BandwidthAccountingComponent(Component): - community: BandwidthAccountingCommunity = None - _ipv8_component: Ipv8Component = None - database: BandwidthDatabase = None - - def __init__(self, crawler_mode=False): - super().__init__() - self.crawler_mode = crawler_mode - - async def run(self): - await super().run() - self._ipv8_component = await self.require_component(Ipv8Component) - - config = self.session.config - if config.general.testnet or config.bandwidth_accounting.testnet: - bandwidth_cls = BandwidthAccountingTestnetCommunity - else: - bandwidth_cls = BandwidthAccountingCommunity - - if self.crawler_mode: - store_all_transactions = True - unlimited_peers = True - else: - store_all_transactions = False - unlimited_peers = False - - db_name = "bandwidth_gui_test.db" if config.gui_test_mode else f"{bandwidth_cls.DB_NAME}.db" - database_path = config.state_dir / STATEDIR_DB_DIR / db_name - self.database = BandwidthDatabase(database_path, self._ipv8_component.peer.public_key.key_to_bin(), - store_all_transactions=store_all_transactions) - - kwargs = {"max_peers": -1} if unlimited_peers else {} - self.community = bandwidth_cls(self._ipv8_component.peer, - self._ipv8_component.ipv8.endpoint, - self._ipv8_component.ipv8.network, - settings=config.bandwidth_accounting, - database=self.database, - **kwargs) - - self._ipv8_component.initialise_community_by_default(self.community) - - async def shutdown(self): - await super().shutdown() - if self._ipv8_component and self.community: - await self._ipv8_component.unload_community(self.community) diff --git a/src/tribler/core/components/bandwidth_accounting/community/__init__.py b/src/tribler/core/components/bandwidth_accounting/community/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/tribler/core/components/bandwidth_accounting/community/bandwidth_accounting_community.py b/src/tribler/core/components/bandwidth_accounting/community/bandwidth_accounting_community.py deleted file mode 100644 index 4c3dd4df1a7..00000000000 --- a/src/tribler/core/components/bandwidth_accounting/community/bandwidth_accounting_community.py +++ /dev/null @@ -1,206 +0,0 @@ -from __future__ import annotations - -from asyncio import Future -from binascii import unhexlify -from random import Random -from typing import Dict - -from ipv8.peer import Peer -from ipv8.requestcache import RequestCache -from ipv8.types import Address - -from tribler.core.components.bandwidth_accounting.community.cache import BandwidthTransactionSignCache -from tribler.core.components.bandwidth_accounting.community.payload import ( - BandwidthTransactionPayload, - BandwidthTransactionQueryPayload, -) -from tribler.core.components.bandwidth_accounting.db.database import BandwidthDatabase -from tribler.core.components.bandwidth_accounting.db.transaction import BandwidthTransactionData, EMPTY_SIGNATURE -from tribler.core.components.ipv8.tribler_community import TriblerCommunity -from tribler.core.utilities.unicode import hexlify - - -class BandwidthAccountingCommunity(TriblerCommunity): - """ - Community around bandwidth accounting and payouts. - """ - community_id = unhexlify('79b25f2867739261780faefede8f25038de9975d') - DB_NAME = 'bandwidth' - version = b'\x02' - - def __init__(self, *args, **kwargs) -> None: - """ - Initialize the community. - :param persistence: The database that stores transactions, will be created if not provided. - :param database_path: The path at which the database will be created. Defaults to the current working directory. - """ - self.database: BandwidthDatabase = kwargs.pop('database', None) - self.random = Random() - - super().__init__(*args, **kwargs) - - self.request_cache = RequestCache() - self.my_pk = self.my_peer.public_key.key_to_bin() - - self.add_message_handler(BandwidthTransactionPayload, self.received_transaction) - self.add_message_handler(BandwidthTransactionQueryPayload, self.received_query) - - self.register_task("query_peers", self.query_random_peer, interval=self.settings.outgoing_query_interval) - - self.logger.info("Started bandwidth accounting community with public key %s", hexlify(self.my_pk)) - - def construct_signed_transaction(self, peer: Peer, amount: int) -> BandwidthTransactionData: - """ - Construct a new signed bandwidth transaction. - :param peer: The counterparty of the transaction. - :param amount: The amount of bytes to payout. - :return A signed BandwidthTransaction. - """ - peer_pk = peer.public_key.key_to_bin() - latest_tx = self.database.get_latest_transaction(self.my_pk, peer_pk) - total_amount = latest_tx.amount + amount if latest_tx else amount - next_seq_num = latest_tx.sequence_number + 1 if latest_tx else 1 - tx = BandwidthTransactionData(next_seq_num, self.my_pk, peer_pk, EMPTY_SIGNATURE, EMPTY_SIGNATURE, total_amount) - tx.sign(self.my_peer.key, as_a=True) - return tx - - def do_payout(self, peer: Peer, amount: int) -> Future: - """ - Conduct a payout with a given amount of bytes to a peer. - :param peer: The counterparty of the payout. - :param amount: The amount of bytes to payout. - :return A Future that fires when the counterparty has acknowledged the payout. - """ - tx = self.construct_signed_transaction(peer, amount) - self.database.BandwidthTransaction.insert(tx) - cache = self.request_cache.add(BandwidthTransactionSignCache(self, tx)) - self.send_transaction(tx, peer.address, cache.number) - - return cache.future - - def send_transaction(self, transaction: BandwidthTransactionData, address: Address, request_id: int) -> None: - """ - Send a provided transaction to another party. - :param transaction: The BandwidthTransaction to send to the other party. - :param peer: The IP address and port of the peer. - :param request_id: The identifier of the message, is usually provided by a request cache. - """ - payload = BandwidthTransactionPayload.from_transaction(transaction, request_id) - packet = self._ez_pack(self._prefix, 1, [payload], False) - self.endpoint.send(address, packet) - - def received_transaction(self, source_address: Address, data: bytes) -> None: - """ - Callback when we receive a transaction from another peer. - :param source_address: The network address of the peer that has sent us the transaction. - :param data: The serialized, raw data in the packet. - """ - payload = self._ez_unpack_noauth(BandwidthTransactionPayload, data, global_time=False) - tx = BandwidthTransactionData.from_payload(payload) - - if not tx.is_valid(): - self.logger.info("Transaction %s not valid, ignoring it", tx) - return - - if payload.public_key_a == self.my_pk or payload.public_key_b == self.my_pk: - # This transaction involves this peer. - latest_tx = self.database.get_latest_transaction(tx.public_key_a, tx.public_key_b) - if payload.public_key_b == self.my_peer.public_key.key_to_bin(): - from_peer = Peer(payload.public_key_a, source_address) - if latest_tx: - # Check if the amount in the received transaction is higher than the amount of the latest one - # in the database. - if payload.amount > latest_tx.amount: - # Sign it, store it, and send it back - tx.sign(self.my_peer.key, as_a=False) - self.database.BandwidthTransaction.insert(tx) - self.send_transaction(tx, from_peer.address, payload.request_id) - else: - self.logger.info("Received older bandwidth transaction from peer %s:%d - " - "sending back the latest one", *from_peer.address) - self.send_transaction(latest_tx, from_peer.address, payload.request_id) - else: - # This transaction is the first one with party A. Sign it, store it, and send it back. - tx.sign(self.my_peer.key, as_a=False) - self.database.BandwidthTransaction.insert(tx) - from_peer = Peer(payload.public_key_a, source_address) - self.send_transaction(tx, from_peer.address, payload.request_id) - elif payload.public_key_a == self.my_peer.public_key.key_to_bin(): - # It seems that we initiated this transaction. Check if we are waiting for it. - cache = self.request_cache.get("bandwidth-tx-sign", payload.request_id) - if not cache: - self.logger.info("Received bandwidth transaction %s without associated cache entry, ignoring it", - tx) - return - - if not latest_tx or (latest_tx and latest_tx.amount >= tx.amount): - self.database.BandwidthTransaction.insert(tx) - - cache.future.set_result(tx) - else: - # This transaction involves two unknown peers. We can add it to our database. - self.database.BandwidthTransaction.insert(tx) - - def query_random_peer(self) -> None: - """ - Query a random peer neighbouring peer and ask their bandwidth transactions. - """ - peers = list(self.network.verified_peers) - if peers: - random_peer = self.random.choice(peers) - self.query_transactions(random_peer) - - def query_transactions(self, peer: Peer) -> None: - """ - Query the transactions of a specific peer and ask for their bandwidth transactions. - :param peer: The peer to send the query to. - """ - self.logger.info("Querying the transactions of peer %s:%d", *peer.address) - payload = BandwidthTransactionQueryPayload() - packet = self._ez_pack(self._prefix, 2, [payload], False) - self.endpoint.send(peer.address, packet) - - def received_query(self, source_address: Address, data: bytes) -> None: - """ - We received a query from another peer. - :param source_address: The network address of the peer that has sent us the query. - :param data: The serialized, raw data in the packet. - """ - my_txs = self.database.get_my_latest_transactions(limit=self.settings.max_tx_returned_in_query) - self.logger.debug("Sending %d bandwidth transaction(s) to peer %s:%d", len(my_txs), *source_address) - for tx in my_txs: - self.send_transaction(tx, source_address, 0) - - def get_statistics(self) -> Dict: - """ - Return a dictionary with bandwidth statistics, including the total amount of bytes given and taken, and the - number of unique peers you helped/that helped you. - :return: A dictionary with statistics. - """ - my_pk = self.my_peer.public_key.key_to_bin() - return { - "id": hexlify(my_pk), - "total_given": self.database.get_total_given(my_pk), - "total_taken": self.database.get_total_taken(my_pk), - "num_peers_helped": self.database.get_num_peers_helped(my_pk), - "num_peers_helped_by": self.database.get_num_peers_helped_by(my_pk) - } - - async def unload(self) -> None: - """ - Unload this community by shutting down the request cache and database. - """ - self.logger.info("Unloading the bandwidth accounting community.") - - await self.request_cache.shutdown() - self.database.shutdown() - - await super().unload() - - -class BandwidthAccountingTestnetCommunity(BandwidthAccountingCommunity): - """ - This community defines the testnet for bandwidth accounting. - """ - DB_NAME = 'bandwidth_testnet' - community_id = unhexlify('e7de42f46f9ef225f4a5fc32ed0a0ce9a8ea4af6') diff --git a/src/tribler/core/components/bandwidth_accounting/community/cache.py b/src/tribler/core/components/bandwidth_accounting/community/cache.py deleted file mode 100644 index c58682b11b7..00000000000 --- a/src/tribler/core/components/bandwidth_accounting/community/cache.py +++ /dev/null @@ -1,42 +0,0 @@ -from __future__ import annotations - -from asyncio import Future -from typing import TYPE_CHECKING - -from ipv8.requestcache import RandomNumberCache - -from tribler.core.components.bandwidth_accounting.db.transaction import BandwidthTransactionData - -if TYPE_CHECKING: - from tribler.core.components.bandwidth_accounting.community.bandwidth_accounting_community import \ - BandwidthAccountingCommunity - - -class BandwidthTransactionSignCache(RandomNumberCache): - """ - This cache keeps track of pending bandwidth transaction signature requests. - """ - - def __init__(self, community: BandwidthAccountingCommunity, transaction: BandwidthTransactionData) -> None: - """ - Initialize the cache. - :param community: The bandwidth community associated with this cache. - :param transaction: The transaction that will be signed by the counterparty. - """ - super().__init__(community.request_cache, "bandwidth-tx-sign") - self.transaction = transaction - self.future = Future() - self.register_future(self.future) - - @property - def timeout_delay(self) -> float: - """ - :return The timeout of this sign cache, defaults to one hour. - """ - return 3600.0 - - def on_timeout(self) -> None: - """ - Invoked when the cache times out. - """ - self._logger.info("Sign request for transaction %s timed out!", self.transaction) diff --git a/src/tribler/core/components/bandwidth_accounting/community/payload.py b/src/tribler/core/components/bandwidth_accounting/community/payload.py deleted file mode 100644 index 9dcb024facb..00000000000 --- a/src/tribler/core/components/bandwidth_accounting/community/payload.py +++ /dev/null @@ -1,35 +0,0 @@ -from __future__ import annotations - -from ipv8.messaging.lazy_payload import VariablePayload, vp_compile - - -@vp_compile -class BandwidthTransactionPayload(VariablePayload): - """ - Payload for a message containing a bandwidth transaction. - """ - msg_id = 1 - format_list = ['I', '74s', '74s', '64s', '64s', 'Q', 'Q', 'I'] - names = ["sequence_number", "public_key_a", "public_key_b", "signature_a", "signature_b", "amount", - "timestamp", "request_id"] - - @classmethod - def from_transaction(cls, transaction, request_id: int) -> BandwidthTransactionPayload: - return BandwidthTransactionPayload( - transaction.sequence_number, - transaction.public_key_a, - transaction.public_key_b, - transaction.signature_a, - transaction.signature_b, - transaction.amount, - transaction.timestamp, - request_id - ) - - -@vp_compile -class BandwidthTransactionQueryPayload(VariablePayload): - """ - (empty) payload for an outgoing query to fetch transactions by the counterparty. - """ - msg_id = 2 diff --git a/src/tribler/core/components/bandwidth_accounting/db/__init__.py b/src/tribler/core/components/bandwidth_accounting/db/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/tribler/core/components/bandwidth_accounting/db/database.py b/src/tribler/core/components/bandwidth_accounting/db/database.py deleted file mode 100644 index 2863285b717..00000000000 --- a/src/tribler/core/components/bandwidth_accounting/db/database.py +++ /dev/null @@ -1,191 +0,0 @@ -from pathlib import Path -from typing import List, Optional, Union - -from pony.orm import count, db_session, desc, select - -from tribler.core.components.bandwidth_accounting.db import history, misc, transaction as db_transaction -from tribler.core.components.bandwidth_accounting.db.transaction import BandwidthTransactionData -from tribler.core.utilities.db_corruption_handling.base import handle_db_if_corrupted -from tribler.core.utilities.pony_utils import TrackedDatabase -from tribler.core.utilities.utilities import MEMORY_DB - - -class BandwidthDatabase: - """ - Simple database that stores bandwidth transactions in Tribler as a work graph. - """ - CURRENT_DB_VERSION = 9 - MAX_HISTORY_ITEMS = 100 # The maximum number of history items to store. - - def __init__(self, db_path: Union[Path, type(MEMORY_DB)], my_pub_key: bytes, - store_all_transactions: bool = False) -> None: - """ - Sets up the persistence layer ready for use. - :param db_path: The full path of the database. - :param my_pub_key: The public key of the user operating the database. - :param store_all_transactions: Whether we store all pairwise transactions in the database. This is disabled by - default and used for data collection purposes. - """ - self.my_pub_key = my_pub_key - self.store_all_transactions = store_all_transactions - - self.database = TrackedDatabase() - - # This attribute is internally called by Pony on startup, though pylint cannot detect it - # with the static analysis. - # pylint: disable=unused-variable - - @self.database.on_connect - def sqlite_sync_pragmas(_, connection): - cursor = connection.cursor() - cursor.execute("PRAGMA journal_mode = WAL") - cursor.execute("PRAGMA synchronous = 1") - cursor.execute("PRAGMA temp_store = 2") - # pylint: enable=unused-variable - - self.MiscData = misc.define_binding(self.database) - self.BandwidthTransaction = db_transaction.define_binding(self) - self.BandwidthHistory = history.define_binding(self) - - if db_path is MEMORY_DB: - create_db = True - db_path_string = ":memory:" - else: - # We need to handle the database corruption case before determining the state of the create_db flag. - handle_db_if_corrupted(db_path) - create_db = not db_path.is_file() - db_path_string = str(db_path) - - self.database.bind(provider='sqlite', filename=db_path_string, create_db=create_db, timeout=120.0) - self.database.generate_mapping(create_tables=create_db) - - if create_db: - with db_session: - self.MiscData(name="db_version", value=str(self.CURRENT_DB_VERSION)) - - def has_transaction(self, transaction: BandwidthTransactionData) -> bool: - """ - Return whether a transaction is persisted to the database. - :param transaction: The transaction to check. - :return: A boolean value, indicating whether we have the transaction in the database or not. - """ - return self.BandwidthTransaction.exists(public_key_a=transaction.public_key_a, - public_key_b=transaction.public_key_b, - sequence_number=transaction.sequence_number) - - @db_session - def get_my_latest_transactions(self, limit: Optional[int] = None) -> List[BandwidthTransactionData]: - """ - Return all latest transactions involving you. - :param limit: An optional integer, to limit the number of results returned. Pass None to get all results. - :return A list containing all latest transactions involving you. - """ - results = [] - db_txs = select(tx for tx in self.BandwidthTransaction - if tx.public_key_a == self.my_pub_key or tx.public_key_b == self.my_pub_key) \ - .limit(limit) - for db_tx in db_txs: - results.append(BandwidthTransactionData.from_db(db_tx)) - return results - - @db_session - def get_latest_transaction(self, public_key_a: bytes, public_key_b: bytes) -> BandwidthTransactionData: - """ - Return the latest transaction between two parties, or None if no such transaction exists. - :param public_key_a: The public key of the party transferring the bandwidth. - :param public_key_b: The public key of the party receiving the bandwidth. - :return The latest transaction between the two specified parties, or None if no such transaction exists. - """ - db_tx = select(tx for tx in self.BandwidthTransaction - if public_key_a == tx.public_key_a and public_key_b == tx.public_key_b) \ - .order_by(lambda tx: desc(tx.sequence_number)) \ - .first() - - return BandwidthTransactionData.from_db(db_tx) if db_tx else None - - @db_session - def get_latest_transactions(self, public_key: bytes, limit: Optional[int] = 100) -> List[BandwidthTransactionData]: - """ - Return the latest transactions of a given public key, or an empty list if no transactions exist. - :param public_key: The public key of the party transferring the bandwidth. - :param limit: The number of transactions to return. (Default: 100) - :return The latest transactions of the specified public key, or an empty list if no transactions exist. - """ - db_txs = select(tx for tx in self.BandwidthTransaction - if public_key in (tx.public_key_a, tx.public_key_b)) \ - .limit(limit) - return [BandwidthTransactionData.from_db(db_txn) for db_txn in db_txs] - - @db_session - def get_total_taken(self, public_key: bytes) -> int: - """ - Return the total amount of bandwidth taken by a given party. - :param public_key: The public key of the peer of which we want to determine the total taken. - :return The total amount of bandwidth taken by the specified peer, in bytes. - """ - return select(transaction.amount for transaction in self.BandwidthTransaction - if transaction.public_key_a == public_key).sum() - - @db_session - def get_total_given(self, public_key: bytes) -> int: - """ - Return the total amount of bandwidth given by a given party. - :param public_key: The public key of the peer of which we want to determine the total given. - :return The total amount of bandwidth given by the specified peer, in bytes. - """ - return select(transaction.amount for transaction in self.BandwidthTransaction - if transaction.public_key_b == public_key).sum() - - @db_session - def get_balance(self, public_key: bytes) -> int: - """ - Return the bandwidth balance (total given - total taken) of a specific peer. - :param public_key: The public key of the peer of which we want to determine the balance. - :return The bandwidth balance the specified peer, in bytes. - """ - return self.get_total_given(public_key) - self.get_total_taken(public_key) - - def get_my_balance(self) -> int: - """ - Return your bandwidth balance, which is the total amount given minus the total amount taken. - :return Your bandwidth balance, in bytes. - """ - return self.get_balance(self.my_pub_key) - - @db_session - def get_num_peers_helped(self, public_key: bytes) -> int: - """ - Return the number of unique peers that a peer with the provided public key has helped. - :param public_key: The public key of the peer of which we want to determine this number. - :return The unique number of peers helped by the specified peer. - """ - result = list(select(count(g.public_key_b) for g in self.BandwidthTransaction if g.public_key_a == public_key)) - return result[0] - - @db_session - def get_num_peers_helped_by(self, public_key: bytes) -> int: - """ - Return the number of unique peers that a peer with the provided public key has been helped by. - :param public_key: The public key of the peer of which we want to determine this number. - :return The unique number of peers that helped the specified peer. - """ - result = list(select(count(g.public_key_a) for g in self.BandwidthTransaction if g.public_key_b == public_key)) - return result[0] - - @db_session - def get_history(self) -> List: - """ - Get the history of your bandwidth balance as an ordered list. - :return A list. Each item in this list contains a timestamp and a balance. - """ - history = [] - for history_item in self.BandwidthHistory.select().order_by(self.BandwidthHistory.timestamp): - history.append({"timestamp": history_item.timestamp, "balance": history_item.balance}) - - return history - - def shutdown(self) -> None: - """ - Shutdown the database. - """ - self.database.disconnect() diff --git a/src/tribler/core/components/bandwidth_accounting/db/history.py b/src/tribler/core/components/bandwidth_accounting/db/history.py deleted file mode 100644 index 7c299bf51bd..00000000000 --- a/src/tribler/core/components/bandwidth_accounting/db/history.py +++ /dev/null @@ -1,17 +0,0 @@ -from pony.orm import PrimaryKey, Required - - -def define_binding(bandwidth_database): - db = bandwidth_database.database - - class BandwidthHistory(db.Entity): - """ - This ORM class represents a mutation of ones bandwidth balance. - We store the last 100 mutations in ones bandwidth balance. - """ - - rowid = PrimaryKey(int, auto=True) - timestamp = Required(int, size=64) - balance = Required(int, size=64) - - return BandwidthHistory diff --git a/src/tribler/core/components/bandwidth_accounting/db/misc.py b/src/tribler/core/components/bandwidth_accounting/db/misc.py deleted file mode 100644 index 1adf5f4c50d..00000000000 --- a/src/tribler/core/components/bandwidth_accounting/db/misc.py +++ /dev/null @@ -1,12 +0,0 @@ -from pony.orm import Optional, PrimaryKey - - -def define_binding(db): - class MiscData(db.Entity): - """ - This binding is used to store all kinds of values, like DB version, counters, etc. - """ - name = PrimaryKey(str) - value = Optional(str) - - return MiscData diff --git a/src/tribler/core/components/bandwidth_accounting/db/transaction.py b/src/tribler/core/components/bandwidth_accounting/db/transaction.py deleted file mode 100644 index 8db476b320b..00000000000 --- a/src/tribler/core/components/bandwidth_accounting/db/transaction.py +++ /dev/null @@ -1,174 +0,0 @@ -""" -This file defines the required data structures for the bandwidth accounting mechanism. -Note that we define two different class types of BandwidthTransaction: one for the object that resides outside the -Pony database and another one that represents the class inside Pony. -We make this separation to workaround the fact that Pony does not support objects that are created outside a database -context. -""" -from __future__ import annotations - -import time -from dataclasses import dataclass, field -from typing import Dict - -from ipv8.keyvault.crypto import default_eccrypto -from ipv8.keyvault.keys import Key -from ipv8.messaging.serialization import default_serializer -from pony.orm import PrimaryKey, Required, db_session - -from tribler.core.components.bandwidth_accounting.community.payload import BandwidthTransactionPayload - -EMPTY_SIGNATURE = b'0' * 64 - - -@dataclass -class BandwidthTransactionData: - """ - This class defines a data class for a bandwidth transaction. - """ - sequence_number: int - public_key_a: bytes - public_key_b: bytes - signature_a: bytes - signature_b: bytes - amount: int - timestamp: int = field(default_factory=lambda: int(round(time.time() * 1000))) - - def pack(self, signature_a=True, signature_b=True) -> bytes: - """ - Encode this block for transport. - :param signature_a: False to pack EMPTY_SIG in the location of signature A, true to pack the signature A field. - :param signature_b: False to pack EMPTY_SIG in the location of signature B, true to pack the signature B field. - :return: bytes object the data was packed into. - """ - args = [self.sequence_number, self.public_key_a, self.public_key_b, - self.signature_a if signature_a else EMPTY_SIGNATURE, - self.signature_b if signature_b else EMPTY_SIGNATURE, self.amount, self.timestamp] - return default_serializer.pack_serializable(BandwidthTransactionPayload(*args, 0)) - - def sign(self, key: Key, as_a: bool) -> None: - """ - Signs this block with the given key - :param key: The key to sign this block with. - :param as_a: Whether we are signing this block as party A or B. - """ - if as_a: - # Party A is the first to sign the transaction - self.signature_a = default_eccrypto.create_signature( - key, self.pack(signature_a=False, signature_b=False)) - else: - # Party B is the first to sign the transaction - self.signature_b = default_eccrypto.create_signature( - key, self.pack(signature_b=False)) - - def is_valid(self) -> bool: - """ - Validate the signatures in the transaction. - return: True if the transaction is valid, False otherwise - """ - if self.signature_a != EMPTY_SIGNATURE: - # Verify signature A - pck = self.pack(signature_a=False, signature_b=False) - valid_signature = default_eccrypto.is_valid_signature( - default_eccrypto.key_from_public_bin(self.public_key_a), pck, self.signature_a) - if not valid_signature: - return False - - if self.signature_b != EMPTY_SIGNATURE: - # Verify signature B - pck = self.pack(signature_b=False) - valid_signature = default_eccrypto.is_valid_signature( - default_eccrypto.key_from_public_bin(self.public_key_b), pck, self.signature_b) - if not valid_signature: - return False - - if self.sequence_number < 1: - return False - - return True - - @classmethod - def from_payload(cls, payload: BandwidthTransactionPayload): - """ - Create a block according to a given payload. - This method can be used when receiving a block from the network. - :param payload: The payload to convert to a transaction. - :return A BandwidthTransaction, constructed from the provided payload. - """ - return cls(payload.sequence_number, payload.public_key_a, payload.public_key_b, - payload.signature_a, payload.signature_b, payload.amount, payload.timestamp) - - @classmethod - def from_db(cls, db_obj) -> BandwidthTransactionData: - """ - Return a BandwidthTransaction object from a database object. - :param db_obj: The BandwidthTransaction object to convert. - :return A BandwidthTransaction object, based on the database object. - """ - return BandwidthTransactionData(db_obj.sequence_number, db_obj.public_key_a, db_obj.public_key_b, - db_obj.signature_a, db_obj.signature_b, db_obj.amount, db_obj.timestamp) - - def get_db_kwargs(self) -> Dict: - """ - Return the database arguments for easy insertion in a Pony database. - :return A dictionary with keyword arguments for database insertion. - """ - return { - "sequence_number": self.sequence_number, - "public_key_a": self.public_key_a, - "public_key_b": self.public_key_b, - "signature_a": self.signature_a, - "signature_b": self.signature_b, - "amount": self.amount, - "timestamp": self.timestamp - } - - -def define_binding(bandwidth_database): - db = bandwidth_database.database - - class BandwidthTransaction(db.Entity): - """ - This class describes a bandwidth transaction that resides in the database. - """ - sequence_number = Required(int) - public_key_a = Required(bytes, index=True) - public_key_b = Required(bytes, index=True) - signature_a = Required(bytes) - signature_b = Required(bytes) - amount = Required(int, size=64) - timestamp = Required(int, size=64) - PrimaryKey(sequence_number, public_key_a, public_key_b) - - @classmethod - @db_session(optimistic=False) - def insert(cls, transaction: BandwidthTransaction) -> None: - """ - Insert a BandwidthTransaction object in the database. - Remove the last transaction with that specific counterparty while doing so. - :param transaction: The transaction to insert in the database. - """ - if not bandwidth_database.store_all_transactions: - # Make sure to only store the latest pairwise transaction. - for tx in cls.select( - lambda c: c.public_key_a == transaction.public_key_a and - c.public_key_b == transaction.public_key_b): - tx.delete() - db.commit() - cls(**transaction.get_db_kwargs()) - elif not bandwidth_database.has_transaction(transaction): - # We store all transactions and it does not exist yet - insert it. - cls(**transaction.get_db_kwargs()) - - if transaction.public_key_a == bandwidth_database.my_pub_key or \ - transaction.public_key_b == bandwidth_database.my_pub_key: - # Update the balance history - timestamp = int(round(time.time() * 1000)) - db.BandwidthHistory(timestamp=timestamp, balance=bandwidth_database.get_my_balance()) - num_entries = db.BandwidthHistory.select().count() - if num_entries > bandwidth_database.MAX_HISTORY_ITEMS: - # Delete the entry with the lowest timestamp - entry = list(db.BandwidthHistory.select().order_by(db.BandwidthHistory.timestamp))[0] - entry.delete() - - return BandwidthTransaction diff --git a/src/tribler/core/components/bandwidth_accounting/restapi/__init__.py b/src/tribler/core/components/bandwidth_accounting/restapi/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/tribler/core/components/bandwidth_accounting/restapi/bandwidth_endpoint.py b/src/tribler/core/components/bandwidth_accounting/restapi/bandwidth_endpoint.py deleted file mode 100644 index 025312b2893..00000000000 --- a/src/tribler/core/components/bandwidth_accounting/restapi/bandwidth_endpoint.py +++ /dev/null @@ -1,64 +0,0 @@ -from aiohttp import web -from aiohttp_apispec import docs -from ipv8.REST.schema import schema -from marshmallow.fields import Integer, String - -from tribler.core.components.bandwidth_accounting.community.bandwidth_accounting_community import ( - BandwidthAccountingCommunity, -) -from tribler.core.components.restapi.rest.rest_endpoint import RESTEndpoint, RESTResponse -from tribler.core.utilities.utilities import froze_it - - -@froze_it -class BandwidthEndpoint(RESTEndpoint): - """ - This endpoint is responsible for handing requests for bandwidth accounting data. - """ - path = '/bandwidth' - - def __init__(self, bandwidth_community: BandwidthAccountingCommunity): - super().__init__() - self.bandwidth_community = bandwidth_community - - def setup_routes(self) -> None: - self.app.add_routes([web.get('/statistics', self.get_statistics)]) - self.app.add_routes([web.get('/history', self.get_history)]) - - @docs( - tags=["Bandwidth"], - summary="Return statistics about the bandwidth community.", - responses={ - 200: { - "schema": schema(BandwidthStatisticsResponse={ - 'statistics': schema(BandwidthStatistics={ - 'id': String, - 'num_peers_helped': Integer, - 'num_peers_helped_by': Integer, - 'total_taken': Integer, - 'total_given': Integer - }) - }) - } - } - ) - async def get_statistics(self, request) -> RESTResponse: - return RESTResponse({'statistics': self.bandwidth_community.get_statistics()}) - - @docs( - tags=["Bandwidth"], - summary="Return a list of the balance history.", - responses={ - 200: { - "schema": schema(BandwidthHistoryResponse={ - "history": [schema(BandwidthHistoryItem={ - "timestamp": Integer, - "balance": Integer - }) - ] - }) - } - } - ) - async def get_history(self, request) -> RESTResponse: - return RESTResponse({'history': self.bandwidth_community.database.get_history()}) diff --git a/src/tribler/core/components/bandwidth_accounting/settings.py b/src/tribler/core/components/bandwidth_accounting/settings.py deleted file mode 100644 index 3df107daf12..00000000000 --- a/src/tribler/core/components/bandwidth_accounting/settings.py +++ /dev/null @@ -1,9 +0,0 @@ -from pydantic import Field - -from tribler.core.config.tribler_config_section import TriblerConfigSection - - -class BandwidthAccountingSettings(TriblerConfigSection): - testnet: bool = Field(default=False, env='BANDWIDTH_TESTNET') - outgoing_query_interval: int = 30 # The interval at which we send out queries to other peers, in seconds. - max_tx_returned_in_query: int = 10 # The maximum number of bandwidth transactions to return in response to a query. diff --git a/src/tribler/core/components/bandwidth_accounting/tests/__init__.py b/src/tribler/core/components/bandwidth_accounting/tests/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/tribler/core/components/bandwidth_accounting/tests/test_bandwidth_accounting_component.py b/src/tribler/core/components/bandwidth_accounting/tests/test_bandwidth_accounting_component.py deleted file mode 100644 index 5d9dd822e28..00000000000 --- a/src/tribler/core/components/bandwidth_accounting/tests/test_bandwidth_accounting_component.py +++ /dev/null @@ -1,16 +0,0 @@ -from tribler.core.components.bandwidth_accounting.bandwidth_accounting_component import BandwidthAccountingComponent -from tribler.core.components.ipv8.ipv8_component import Ipv8Component -from tribler.core.components.key.key_component import KeyComponent -from tribler.core.components.session import Session - - -# pylint: disable=protected-access - - -async def test_bandwidth_accounting_component(tribler_config): - components = [KeyComponent(), Ipv8Component(), BandwidthAccountingComponent()] - async with Session(tribler_config, components) as session: - comp = session.get_instance(BandwidthAccountingComponent) - assert comp.started_event.is_set() and not comp.failed - assert comp.community - assert comp._ipv8_component diff --git a/src/tribler/core/components/bandwidth_accounting/tests/test_bandwidth_endpoint.py b/src/tribler/core/components/bandwidth_accounting/tests/test_bandwidth_endpoint.py deleted file mode 100644 index d1b9078509e..00000000000 --- a/src/tribler/core/components/bandwidth_accounting/tests/test_bandwidth_endpoint.py +++ /dev/null @@ -1,79 +0,0 @@ -import pytest -from ipv8.keyvault.crypto import default_eccrypto -from ipv8.peer import Peer - -from tribler.core.components.bandwidth_accounting.community.bandwidth_accounting_community import ( - BandwidthAccountingCommunity, -) -from tribler.core.components.bandwidth_accounting.db.database import BandwidthDatabase -from tribler.core.components.bandwidth_accounting.db.transaction import BandwidthTransactionData, EMPTY_SIGNATURE -from tribler.core.components.bandwidth_accounting.restapi.bandwidth_endpoint import BandwidthEndpoint -from tribler.core.components.bandwidth_accounting.settings import BandwidthAccountingSettings -from tribler.core.components.ipv8.adapters_tests import TriblerMockIPv8 -from tribler.core.components.restapi.rest.base_api_test import do_request -from tribler.core.utilities.unicode import hexlify - - -# pylint: disable=redefined-outer-name -@pytest.fixture -def peer(): - return Peer(default_eccrypto.generate_key("curve25519"), address=("1.2.3.4", 5)) - - -@pytest.fixture -def bandwidth_database(tmp_path, peer): - return BandwidthDatabase(db_path=tmp_path / "bandwidth.db", my_pub_key=peer.public_key.key_to_bin()) - - -@pytest.fixture -async def bw_community(bandwidth_database, peer): - ipv8 = TriblerMockIPv8(peer, BandwidthAccountingCommunity, - database=bandwidth_database, - settings=BandwidthAccountingSettings()) - community = ipv8.get_overlay(BandwidthAccountingCommunity) - yield community - await ipv8.stop() - - -@pytest.fixture -async def bw_endpoint(bw_community): - endpoint = BandwidthEndpoint(bw_community) - endpoint.setup_routes() - return endpoint - - -async def test_get_statistics(bw_endpoint, bw_community, aiohttp_client): - """ - Testing whether the API returns the correct statistics - """ - bw_endpoint.bandwidth_community = bw_community - my_pk = bw_community.database.my_pub_key - tx1 = BandwidthTransactionData(1, b"a", my_pk, EMPTY_SIGNATURE, EMPTY_SIGNATURE, 3000) - tx2 = BandwidthTransactionData(1, my_pk, b"a", EMPTY_SIGNATURE, EMPTY_SIGNATURE, 2000) - bw_community.database.BandwidthTransaction.insert(tx1) - bw_community.database.BandwidthTransaction.insert(tx2) - - response_dict = await do_request(await aiohttp_client(bw_endpoint.app), 'statistics', expected_code=200) - assert "statistics" in response_dict - stats = response_dict["statistics"] - assert stats["id"] == hexlify(my_pk) - assert stats["total_given"] == 3000 - assert stats["total_taken"] == 2000 - assert stats["num_peers_helped"] == 1 - assert stats["num_peers_helped_by"] == 1 - - -async def test_get_history(bw_endpoint, bw_community, aiohttp_client): - """ - Testing whether the API returns the correct bandwidth balance history - """ - bw_endpoint.bandwidth_community = bw_community - my_pk = bw_community.my_peer.public_key.key_to_bin() - tx1 = BandwidthTransactionData(1, b"a", my_pk, EMPTY_SIGNATURE, EMPTY_SIGNATURE, 3000) - tx2 = BandwidthTransactionData(1, my_pk, b"a", EMPTY_SIGNATURE, EMPTY_SIGNATURE, 2000) - bw_community.database.BandwidthTransaction.insert(tx1) - bw_community.database.BandwidthTransaction.insert(tx2) - - response_dict = await do_request(await aiohttp_client(bw_endpoint.app), 'history', expected_code=200) - assert "history" in response_dict - assert len(response_dict["history"]) == 2 diff --git a/src/tribler/core/components/bandwidth_accounting/tests/test_community.py b/src/tribler/core/components/bandwidth_accounting/tests/test_community.py deleted file mode 100644 index d5560318c04..00000000000 --- a/src/tribler/core/components/bandwidth_accounting/tests/test_community.py +++ /dev/null @@ -1,156 +0,0 @@ -import unittest.mock - -from ipv8.keyvault.crypto import default_eccrypto -from ipv8.peer import Peer - -from tribler.core.components.bandwidth_accounting.community.bandwidth_accounting_community import ( - BandwidthAccountingCommunity, -) -from tribler.core.components.bandwidth_accounting.community.cache import BandwidthTransactionSignCache -from tribler.core.components.bandwidth_accounting.db.database import BandwidthDatabase -from tribler.core.components.bandwidth_accounting.db.transaction import BandwidthTransactionData, EMPTY_SIGNATURE -from tribler.core.components.bandwidth_accounting.settings import BandwidthAccountingSettings -from tribler.core.components.ipv8.adapters_tests import TriblerMockIPv8, TriblerTestBase -from tribler.core.utilities.utilities import MEMORY_DB - -ID1, ID2, ID3 = range(3) - - -class TestBandwidthAccountingCommunity(TriblerTestBase): - - def setUp(self): - super().setUp() - self.initialize(BandwidthAccountingCommunity, 2) - - def create_node(self, *args, **kwargs): - peer = Peer(default_eccrypto.generate_key("curve25519"), address=("1.2.3.4", 5)) - db = BandwidthDatabase(db_path=MEMORY_DB, my_pub_key=peer.public_key.key_to_bin()) - ipv8 = TriblerMockIPv8(peer, BandwidthAccountingCommunity, database=db, - settings=BandwidthAccountingSettings()) - return ipv8 - - def database(self, i): - return self.overlay(i).database - - def add_cache(self, i, cache): - return self.overlay(i).request_cache.add(cache) - - async def test_single_transaction(self): - """ - Test a simple transaction between two parties. - """ - await self.overlay(ID1).do_payout(self.peer(ID2), 1024) - - assert self.database(ID1).get_total_taken(self.key_bin(ID1)) == 1024 - assert self.database(ID2).get_total_taken(self.key_bin(ID1)) == 1024 - - async def test_multiple_transactions(self): - """ - Test multiple, subsequent transactions between two parties. - """ - await self.overlay(ID1).do_payout(self.peer(ID2), 500) - await self.overlay(ID1).do_payout(self.peer(ID2), 1500) - - assert self.database(ID1).get_total_taken(self.key_bin(ID1)) == 2000 - assert self.database(ID2).get_total_taken(self.key_bin(ID1)) == 2000 - - async def test_bilateral_transaction(self): - """ - Test creating a transaction from A to B and one from B to A. - """ - await self.overlay(ID1).do_payout(self.peer(ID2), 500) - await self.overlay(ID2).do_payout(self.peer(ID1), 1500) - - assert self.database(ID1).get_total_taken(self.key_bin(ID1)) == 500 - assert self.database(ID2).get_total_taken(self.key_bin(ID1)) == 500 - assert self.database(ID1).get_total_taken(self.key_bin(ID2)) == 1500 - assert self.database(ID2).get_total_taken(self.key_bin(ID2)) == 1500 - - async def test_bilateral_transaction_timestamps(self): - """ - Test whether the timestamps are different for transactions created at different times. - - We do not depend on chance and ensure that `time.time()` is different between calls. - """ - with unittest.mock.patch('time.time') as fake_time: - fake_time.return_value = 10.0 - tx1 = await self.overlay(ID1).do_payout(self.peer(ID2), 500) - - with unittest.mock.patch('time.time') as fake_time: - fake_time.return_value = 11.0 - tx2 = await self.overlay(ID1).do_payout(self.peer(ID2), 500) - - assert tx1.timestamp != tx2.timestamp - - async def test_invalid_transaction(self): - """ - Test sending a transaction with an invalid signature to the counterparty, which should be ignored. - """ - tx = self.overlay(ID1).construct_signed_transaction(self.peer(ID2), 300) - tx.signature_a = b"invalid" - self.database(ID1).BandwidthTransaction.insert(tx) - cache = self.add_cache(ID1, BandwidthTransactionSignCache(self.overlay(ID1), tx)) - self.overlay(ID1).send_transaction(tx, self.address(ID2), cache.number) - - await self.deliver_messages() - - assert self.database(ID2).get_total_taken(self.key_bin(ID1)) == 0 - - async def test_ignore_unknown_transaction(self): - """ - Test whether we are ignoring a transaction that is not in our cache. - """ - tx = BandwidthTransactionData(ID2, self.key_bin(ID1), self.key_bin(ID2), EMPTY_SIGNATURE, EMPTY_SIGNATURE, 1000) - tx.sign(self.private_key(ID1), as_a=True) - self.overlay(ID1).send_transaction(tx, self.address(ID2), 1234) - - await self.deliver_messages() - - assert not self.database(ID1).get_latest_transaction(self.key_bin(ID1), self.key_bin(ID2)) - - async def test_concurrent_transaction_out_of_order(self): - """ - Test creating multiple transactions, while the other party is offline and receives messages out of order. - """ - tx1 = BandwidthTransactionData(1, self.key_bin(ID1), self.key_bin(ID2), EMPTY_SIGNATURE, EMPTY_SIGNATURE, 1000) - tx2 = BandwidthTransactionData(2, self.key_bin(ID1), self.key_bin(ID2), EMPTY_SIGNATURE, EMPTY_SIGNATURE, 2000) - - # Send them in reverse order - cache = self.add_cache(ID1, BandwidthTransactionSignCache(self.overlay(ID1), tx1)) - self.overlay(ID1).send_transaction(tx2, self.address(ID2), cache.number) - await self.deliver_messages() - - # This one should be ignored by node 1 - cache = self.add_cache(ID1, BandwidthTransactionSignCache(self.overlay(ID1), tx1)) - self.overlay(ID1).send_transaction(tx1, self.address(ID2), cache.number) - await self.deliver_messages() - - # Both parties should have the transaction with amount 2000 in their database - assert self.database(ID1).get_total_taken(self.key_bin(ID1)) == 2000 - assert self.database(ID2).get_total_taken(self.key_bin(ID1)) == 2000 - - async def test_querying_peer(self): - """ - Test whether node C can query node B to get the transaction between A and B. - """ - await self.overlay(ID1).do_payout(self.peer(ID2), 500) - - self.add_node_to_experiment(self.create_node()) - self.overlay(ID3).query_transactions(self.peer(ID2)) - - await self.deliver_messages() - - assert self.database(ID3).get_total_taken(self.key_bin(ID1)) == 500 - - async def test_query_random_peer(self): - """ - Test whether node C can query node B to get the transaction between A and B. - """ - await self.overlay(ID1).do_payout(self.peer(ID2), 500) - - self.add_node_to_experiment(self.create_node()) - self.overlay(ID3).query_random_peer() - - await self.deliver_messages() - - assert self.database(ID3).get_total_taken(self.key_bin(ID1)) == 500 diff --git a/src/tribler/core/components/bandwidth_accounting/tests/test_database.py b/src/tribler/core/components/bandwidth_accounting/tests/test_database.py deleted file mode 100644 index 5026b58f5a5..00000000000 --- a/src/tribler/core/components/bandwidth_accounting/tests/test_database.py +++ /dev/null @@ -1,168 +0,0 @@ -import random - -import pytest -from ipv8.keyvault.crypto import default_eccrypto -from pony.orm import db_session - -from tribler.core.components.bandwidth_accounting.db.database import BandwidthDatabase -from tribler.core.components.bandwidth_accounting.db.transaction import BandwidthTransactionData, EMPTY_SIGNATURE -from tribler.core.utilities.utilities import MEMORY_DB - - -@pytest.fixture -def my_key(): - return default_eccrypto.generate_key('curve25519') - - -@pytest.fixture -def bandwidth_db(tmpdir, my_key): - db = BandwidthDatabase(MEMORY_DB, my_key.pub().key_to_bin()) - yield db - db.shutdown() - - -@db_session -def test_add_transaction(bandwidth_db): - tx1 = BandwidthTransactionData(1, b"a", b"b", EMPTY_SIGNATURE, EMPTY_SIGNATURE, 3000) - bandwidth_db.BandwidthTransaction.insert(tx1) - assert bandwidth_db.has_transaction(tx1) - tx2 = BandwidthTransactionData(2, b"a", b"b", EMPTY_SIGNATURE, EMPTY_SIGNATURE, 4000) - bandwidth_db.BandwidthTransaction.insert(tx2) - - latest_tx = bandwidth_db.get_latest_transaction(b"a", b"b") - assert latest_tx - assert latest_tx.amount == 4000 - - # Test storing all transactions - bandwidth_db.store_all_transactions = True - tx3 = BandwidthTransactionData(3, b"a", b"b", EMPTY_SIGNATURE, EMPTY_SIGNATURE, 4000) - bandwidth_db.BandwidthTransaction.insert(tx3) - assert len(list(bandwidth_db.BandwidthTransaction.select())) == 2 - assert bandwidth_db.has_transaction(tx2) - assert bandwidth_db.has_transaction(tx3) - - # Test whether adding a transaction again does not result in an error - bandwidth_db.BandwidthTransaction.insert(tx2) - - -@db_session -def test_get_my_latest_transactions(bandwidth_db): - assert not bandwidth_db.get_my_latest_transactions() - - tx1 = BandwidthTransactionData(1, b"a", bandwidth_db.my_pub_key, EMPTY_SIGNATURE, EMPTY_SIGNATURE, 3000) - bandwidth_db.BandwidthTransaction.insert(tx1) - tx2 = BandwidthTransactionData(1, bandwidth_db.my_pub_key, b"c", EMPTY_SIGNATURE, EMPTY_SIGNATURE, 3000) - bandwidth_db.BandwidthTransaction.insert(tx2) - tx3 = BandwidthTransactionData(1, b"c", b"d", EMPTY_SIGNATURE, EMPTY_SIGNATURE, 3000) - bandwidth_db.BandwidthTransaction.insert(tx3) - - assert len(bandwidth_db.get_my_latest_transactions()) == 2 - assert len(bandwidth_db.get_my_latest_transactions(limit=1)) == 1 - - -@db_session -def test_get_latest_transaction(bandwidth_db): - assert not bandwidth_db.get_latest_transaction(b"a", b"b") - tx1 = BandwidthTransactionData(1, b"a", b"b", EMPTY_SIGNATURE, EMPTY_SIGNATURE, 3000) - bandwidth_db.BandwidthTransaction.insert(tx1) - - tx2 = bandwidth_db.get_latest_transaction(b"a", b"b") - assert tx1 == tx2 - assert tx2.amount == 3000 - - -@db_session -def test_get_latest_transactions(bandwidth_db): - pub_key_a = b"a" - pub_keys_rest = [b"b", b"c", b"d", b"e", b"f"] - - assert not bandwidth_db.get_latest_transactions(pub_key_a) - - for pub_key in pub_keys_rest: - seq_number = random.randint(1, 100) - amount = random.randint(1, 1000) - tx = BandwidthTransactionData(seq_number, pub_key_a, pub_key, EMPTY_SIGNATURE, EMPTY_SIGNATURE, amount) - bandwidth_db.BandwidthTransaction.insert(tx) - - txs = bandwidth_db.get_latest_transactions(pub_key_a) - assert len(txs) == len(pub_keys_rest) - - -@db_session -def test_store_large_transaction(bandwidth_db): - large_tx = BandwidthTransactionData(1, b"a", b"b", EMPTY_SIGNATURE, EMPTY_SIGNATURE, 1024 * 1024 * 1024 * 3) - bandwidth_db.BandwidthTransaction.insert(large_tx) - - latest_tx = bandwidth_db.get_latest_transaction(b"a", b"b") - assert latest_tx - - -async def test_totals(bandwidth_db): - with db_session: - tx1 = BandwidthTransactionData(1, b"a", b"b", EMPTY_SIGNATURE, EMPTY_SIGNATURE, 3000) - bandwidth_db.BandwidthTransaction.insert(tx1) - - assert bandwidth_db.get_total_taken(b"a") == 3000 - assert bandwidth_db.get_total_given(b"a") == 0 - - tx2 = BandwidthTransactionData(1, b"b", b"a", EMPTY_SIGNATURE, EMPTY_SIGNATURE, 4000) - bandwidth_db.BandwidthTransaction.insert(tx2) - - assert bandwidth_db.get_total_taken(b"a") == 3000 - assert bandwidth_db.get_total_given(b"a") == 4000 - assert bandwidth_db.get_balance(b"a") == 1000 - assert bandwidth_db.get_balance(b"b") == -1000 - - -@db_session -def test_peers_helped(bandwidth_db): - assert bandwidth_db.get_num_peers_helped(b"a") == 0 - tx1 = BandwidthTransactionData(1, b"a", b"b", EMPTY_SIGNATURE, EMPTY_SIGNATURE, 3000) - bandwidth_db.BandwidthTransaction.insert(tx1) - assert bandwidth_db.get_num_peers_helped(b"a") == 1 - tx2 = BandwidthTransactionData(2, b"a", b"c", EMPTY_SIGNATURE, EMPTY_SIGNATURE, 3000) - bandwidth_db.BandwidthTransaction.insert(tx2) - assert bandwidth_db.get_num_peers_helped(b"a") == 2 - tx3 = BandwidthTransactionData(1, b"b", b"c", EMPTY_SIGNATURE, EMPTY_SIGNATURE, 3000) - bandwidth_db.BandwidthTransaction.insert(tx3) - assert bandwidth_db.get_num_peers_helped(b"a") == 2 - - -@db_session -def test_peers_helped_by(bandwidth_db): - assert bandwidth_db.get_num_peers_helped_by(b"a") == 0 - tx1 = BandwidthTransactionData(1, b"b", b"a", EMPTY_SIGNATURE, EMPTY_SIGNATURE, 3000) - bandwidth_db.BandwidthTransaction.insert(tx1) - assert bandwidth_db.get_num_peers_helped_by(b"a") == 1 - tx2 = BandwidthTransactionData(2, b"c", b"a", EMPTY_SIGNATURE, EMPTY_SIGNATURE, 3000) - bandwidth_db.BandwidthTransaction.insert(tx2) - assert bandwidth_db.get_num_peers_helped_by(b"a") == 2 - tx3 = BandwidthTransactionData(1, b"c", b"b", EMPTY_SIGNATURE, EMPTY_SIGNATURE, 3000) - bandwidth_db.BandwidthTransaction.insert(tx3) - assert bandwidth_db.get_num_peers_helped_by(b"a") == 2 - - -@db_session -def test_history(bandwidth_db): - assert not bandwidth_db.get_history() - tx1 = BandwidthTransactionData(1, bandwidth_db.my_pub_key, b"a", EMPTY_SIGNATURE, EMPTY_SIGNATURE, 3000) - bandwidth_db.BandwidthTransaction.insert(tx1) - - history = bandwidth_db.get_history() - assert len(history) == 1 - assert history[0]["balance"] == -3000 - - tx2 = BandwidthTransactionData(1, b"a", bandwidth_db.my_pub_key, EMPTY_SIGNATURE, EMPTY_SIGNATURE, 4000) - bandwidth_db.BandwidthTransaction.insert(tx2) - - history = bandwidth_db.get_history() - assert len(history) == 2 - assert history[1]["balance"] == 1000 - - # Test whether the history is pruned correctly - bandwidth_db.MAX_HISTORY_ITEMS = 2 - tx3 = BandwidthTransactionData(1, b"a", bandwidth_db.my_pub_key, EMPTY_SIGNATURE, EMPTY_SIGNATURE, 2000) - bandwidth_db.BandwidthTransaction.insert(tx3) - - history = bandwidth_db.get_history() - assert len(history) == 2 diff --git a/src/tribler/core/components/bandwidth_accounting/tests/test_transaction.py b/src/tribler/core/components/bandwidth_accounting/tests/test_transaction.py deleted file mode 100644 index 43af80b07db..00000000000 --- a/src/tribler/core/components/bandwidth_accounting/tests/test_transaction.py +++ /dev/null @@ -1,41 +0,0 @@ -from ipv8.keyvault.crypto import default_eccrypto - -from tribler.core.components.bandwidth_accounting.db.transaction import BandwidthTransactionData, EMPTY_SIGNATURE - - -def test_sign_transaction(): - key1 = default_eccrypto.generate_key('curve25519') - key2 = default_eccrypto.generate_key('curve25519') - tx = BandwidthTransactionData(1, key1.pub().key_to_bin(), key2.pub().key_to_bin(), - EMPTY_SIGNATURE, EMPTY_SIGNATURE, 3000) - - tx.sign(key1, as_a=True) - assert tx.is_valid() - assert tx.signature_a != EMPTY_SIGNATURE - assert tx.signature_b == EMPTY_SIGNATURE - - tx.sign(key2, as_a=False) - assert tx.is_valid() - assert tx.signature_a != EMPTY_SIGNATURE - assert tx.signature_b != EMPTY_SIGNATURE - - -def test_is_valid(): - key1 = default_eccrypto.generate_key('curve25519') - key2 = default_eccrypto.generate_key('curve25519') - tx = BandwidthTransactionData(1, key1.pub().key_to_bin(), key2.pub().key_to_bin(), - EMPTY_SIGNATURE, EMPTY_SIGNATURE, 3000) - - assert tx.is_valid() # No signatures have been computed so far - - tx.signature_a = b'a' * 32 - assert not tx.is_valid() - - tx.signature_a = EMPTY_SIGNATURE - tx.signature_b = b'a' * 32 - assert not tx.is_valid() - - tx.signature_a = EMPTY_SIGNATURE - tx.signature_b = EMPTY_SIGNATURE - tx.sequence_number = -1 - assert not tx.is_valid() diff --git a/src/tribler/core/components/payout/__init__.py b/src/tribler/core/components/payout/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/tribler/core/components/payout/payout_component.py b/src/tribler/core/components/payout/payout_component.py deleted file mode 100644 index 4a6ceee29f6..00000000000 --- a/src/tribler/core/components/payout/payout_component.py +++ /dev/null @@ -1,38 +0,0 @@ -from tribler.core import notifications -from tribler.core.components.bandwidth_accounting.bandwidth_accounting_component import BandwidthAccountingComponent -from tribler.core.components.component import Component -from tribler.core.components.ipv8.ipv8_component import Ipv8Component -from tribler.core.components.payout.payout_manager import PayoutManager -from tribler.core.components.reporter.reporter_component import ReporterComponent - -INFINITE = -1 - - -class PayoutComponent(Component): - payout_manager: PayoutManager = None - - async def run(self): - await super().run() - - config = self.session.config - assert not config.gui_test_mode - - await self.get_component(ReporterComponent) - - ipv8_component = await self.require_component(Ipv8Component) - bandwidth_accounting_component = await self.require_component(BandwidthAccountingComponent) - - self.payout_manager = PayoutManager(bandwidth_accounting_component.community, - ipv8_component.dht_discovery_community) - - self.session.notifier.add_observer(notifications.peer_disconnected, self.payout_manager.on_peer_disconnected) - self.session.notifier.add_observer(notifications.tribler_torrent_peer_update, self.payout_manager.update_peer) - - async def shutdown(self): - await super().shutdown() - if self.payout_manager: - notifier = self.session.notifier - notifier.remove_observer(notifications.peer_disconnected, self.payout_manager.on_peer_disconnected) - notifier.remove_observer(notifications.tribler_torrent_peer_update, self.payout_manager.update_peer) - - await self.payout_manager.shutdown() diff --git a/src/tribler/core/components/payout/payout_manager.py b/src/tribler/core/components/payout/payout_manager.py deleted file mode 100644 index 44cb32ba3e7..00000000000 --- a/src/tribler/core/components/payout/payout_manager.py +++ /dev/null @@ -1,68 +0,0 @@ -import logging - -from ipv8.taskmanager import TaskManager, task - -from tribler.core.utilities.unicode import hexlify - - -class PayoutManager(TaskManager): - """ - This manager is responsible for keeping track of known Tribler peers and doing (zero-hop) payouts. - """ - - def __init__(self, bandwidth_community, dht): - super().__init__() - self.logger = logging.getLogger(self.__class__.__name__) - self.bandwidth_community = bandwidth_community - self.dht = dht - self.tribler_peers = {} - - def on_peer_disconnected(self, peer_id: bytes): - # do_payout is not specified directly, as PyCharm does not understand its type correctly due to a task decorator - self.do_payout(peer_id) - - @task - async def do_payout(self, peer_id: bytes): - """ - Perform a payout to a given mid. First, determine the outstanding balance. Then resolve the node in the DHT. - """ - if peer_id not in self.tribler_peers: - return None - - total_bytes = sum(self.tribler_peers[peer_id].values()) - - self.logger.info("Doing direct payout to %s (%d bytes)", hexlify(peer_id), total_bytes) - try: - nodes = await self.dht.connect_peer(peer_id) - except Exception as e: - self.logger.warning("Error while doing DHT lookup for payouts, error %s", e) - return None - - self.logger.debug("Received %d nodes for DHT lookup", len(nodes)) - if not nodes: - return None - - try: - await self.bandwidth_community.do_payout(nodes[0], total_bytes) - except Exception as e: - self.logger.error("Error while doing bandwidth payout, error %s", e) - return None - - # Remove the outstanding bytes; otherwise we will payout again - self.tribler_peers.pop(peer_id, None) - return nodes[0] - - def update_peer(self, peer_id: bytes, infohash: bytes, balance: int): - """ - Update a peer with a specific mid for a specific infohash. - """ - self.logger.debug("Updating peer with mid %s and ih %s (balance: %d)", hexlify(peer_id), - hexlify(infohash), balance) - - if peer_id not in self.tribler_peers: - self.tribler_peers[peer_id] = {} - - self.tribler_peers[peer_id][infohash] = balance - - async def shutdown(self): - await self.shutdown_task_manager() diff --git a/src/tribler/core/components/payout/tests/__init__.py b/src/tribler/core/components/payout/tests/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/tribler/core/components/payout/tests/test_payout_component.py b/src/tribler/core/components/payout/tests/test_payout_component.py deleted file mode 100644 index 4786a6355dc..00000000000 --- a/src/tribler/core/components/payout/tests/test_payout_component.py +++ /dev/null @@ -1,15 +0,0 @@ -# pylint: disable=protected-access - -from tribler.core.components.bandwidth_accounting.bandwidth_accounting_component import BandwidthAccountingComponent -from tribler.core.components.ipv8.ipv8_component import Ipv8Component -from tribler.core.components.key.key_component import KeyComponent -from tribler.core.components.payout.payout_component import PayoutComponent -from tribler.core.components.session import Session - - -async def test_payout_component(tribler_config): - components = [BandwidthAccountingComponent(), KeyComponent(), Ipv8Component(), PayoutComponent()] - async with Session(tribler_config, components) as session: - comp = session.get_instance(PayoutComponent) - assert comp.started_event.is_set() and not comp.failed - assert comp.payout_manager diff --git a/src/tribler/core/components/payout/tests/test_payout_manager.py b/src/tribler/core/components/payout/tests/test_payout_manager.py deleted file mode 100644 index aa587128a2f..00000000000 --- a/src/tribler/core/components/payout/tests/test_payout_manager.py +++ /dev/null @@ -1,97 +0,0 @@ -from unittest.mock import Mock - -import pytest -from ipv8.util import succeed - -from tribler.core.components.payout.payout_manager import PayoutManager - - -@pytest.fixture -async def payout_manager(): - fake_bw_community = Mock() - - fake_response_peer = Mock() - fake_response_peer.public_key = Mock() - fake_response_peer.public_key.key_to_bin = lambda: b'a' * 64 - fake_dht = Mock() - fake_dht.connect_peer = lambda *_: succeed([fake_response_peer]) - - payout_manager = PayoutManager(fake_bw_community, fake_dht) - yield payout_manager - await payout_manager.shutdown() - - -async def test_do_payout(payout_manager): - """ - Test doing a payout - """ - res = await payout_manager.do_payout(b'a') # Does not exist - assert not res - payout_manager.update_peer(b'b', b'c', 10 * 1024 * 1024) - payout_manager.update_peer(b'b', b'd', 1337) - - def mocked_do_payout(*_, **__): - return succeed(None) - - payout_manager.bandwidth_community.do_payout = mocked_do_payout - res = await payout_manager.do_payout(b'b') - assert res - - -async def test_do_payout_dht_error(payout_manager): - """ - Test whether we are not doing a payout when the DHT lookup fails - """ - - def err_connect_peer(_): - raise RuntimeError("test") - - payout_manager.update_peer(b'a', b'b', 10 * 1024 * 1024) - payout_manager.dht.connect_peer = err_connect_peer - res = await payout_manager.do_payout(b'a') - assert not res - - -async def test_do_payout_no_dht_peers(payout_manager): - """ - Test whether we are not doing a payout when there are no peers returned by the DHT - """ - - def connect_peer(_): - return succeed([]) - - payout_manager.update_peer(b'a', b'b', 10 * 1024 * 1024) - payout_manager.dht.connect_peer = connect_peer - res = await payout_manager.do_payout(b'a') - assert not res - - -async def test_do_payout_error(payout_manager): - """ - Test whether we are not doing a payout when the payout fails - """ - - def connect_peer(_): - return succeed([b"abc"]) - - def do_payout(*_): - raise RuntimeError("test") - - payout_manager.update_peer(b'a', b'b', 10 * 1024 * 1024) - payout_manager.dht.connect_peer = connect_peer - payout_manager.bandwidth_community.do_payout = do_payout - res = await payout_manager.do_payout(b'a') - assert not res - - -def test_update_peer(payout_manager): - """ - Test the updating of a specific peer - """ - payout_manager.update_peer(b'a', b'b', 1337) - assert b'a' in payout_manager.tribler_peers - assert b'b' in payout_manager.tribler_peers[b'a'] - assert payout_manager.tribler_peers[b'a'][b'b'] == 1337 - - payout_manager.update_peer(b'a', b'b', 1338) - assert payout_manager.tribler_peers[b'a'][b'b'] == 1338 diff --git a/src/tribler/core/components/restapi/rest/tests/test_statistics_endpoint.py b/src/tribler/core/components/restapi/rest/tests/test_statistics_endpoint.py index a61be19e762..49fa0379c2f 100644 --- a/src/tribler/core/components/restapi/rest/tests/test_statistics_endpoint.py +++ b/src/tribler/core/components/restapi/rest/tests/test_statistics_endpoint.py @@ -1,13 +1,12 @@ -from unittest.mock import Mock +from unittest.mock import MagicMock, Mock import pytest -from tribler.core.components.bandwidth_accounting.community.bandwidth_accounting_community \ - import BandwidthAccountingCommunity -from tribler.core.components.bandwidth_accounting.settings import BandwidthAccountingSettings from tribler.core.components.ipv8.adapters_tests import TriblerMockIPv8 from tribler.core.components.restapi.rest.base_api_test import do_request from tribler.core.components.restapi.rest.statistics_endpoint import StatisticsEndpoint +from tribler.core.components.tunnel.community.tunnel_community import TriblerTunnelCommunity +from tribler.core.components.tunnel.settings import TunnelCommunitySettings # pylint: disable=redefined-outer-name @@ -15,8 +14,12 @@ @pytest.fixture async def endpoint(metadata_store): - ipv8 = TriblerMockIPv8("low", BandwidthAccountingCommunity, database=Mock(), - settings=BandwidthAccountingSettings()) + ipv8 = TriblerMockIPv8("curve25519", + TriblerTunnelCommunity, + settings={"max_circuits": 1}, + config=TunnelCommunitySettings(), + socks_servers=MagicMock(), + dlmgr=Mock()) ipv8.overlays = [ipv8.overlay] ipv8.endpoint.bytes_up = 100 ipv8.endpoint.bytes_down = 20 diff --git a/src/tribler/core/components/restapi/restapi_component.py b/src/tribler/core/components/restapi/restapi_component.py index a43ff2bdf1e..165a586536a 100644 --- a/src/tribler/core/components/restapi/restapi_component.py +++ b/src/tribler/core/components/restapi/restapi_component.py @@ -2,8 +2,6 @@ from typing import Type from ipv8.REST.root_endpoint import RootEndpoint as IPV8RootEndpoint -from tribler.core.components.bandwidth_accounting.bandwidth_accounting_component import BandwidthAccountingComponent -from tribler.core.components.bandwidth_accounting.restapi.bandwidth_endpoint import BandwidthEndpoint from tribler.core.components.component import Component from tribler.core.components.content_discovery.content_discovery_component import ContentDiscoveryComponent from tribler.core.components.content_discovery.restapi.search_endpoint import SearchEndpoint @@ -72,7 +70,6 @@ async def run(self): ipv8_component = await self.maybe_component(Ipv8Component) libtorrent_component = await self.maybe_component(LibtorrentComponent) resource_monitor_component = await self.maybe_component(ResourceMonitorComponent) - bandwidth_accounting_component = await self.maybe_component(BandwidthAccountingComponent) content_discovery_component = await self.maybe_component(ContentDiscoveryComponent) knowledge_component = await self.maybe_component(KnowledgeComponent) tunnel_component = await self.maybe_component(TunnelsComponent) @@ -93,7 +90,6 @@ async def run(self): self.maybe_add(DebugEndpoint, config.state_dir, log_dir, tunnel_community=tunnel_community, resource_monitor=resource_monitor_component.resource_monitor, core_exception_handler=self._core_exception_handler) - self.maybe_add(BandwidthEndpoint, bandwidth_accounting_component.community) self.maybe_add(DownloadsEndpoint, libtorrent_component.download_manager, metadata_store=db_component.mds, tunnel_community=tunnel_community) self.maybe_add(CreateTorrentEndpoint, libtorrent_component.download_manager) diff --git a/src/tribler/core/components/restapi/tests/test_restapi_component.py b/src/tribler/core/components/restapi/tests/test_restapi_component.py index 21991bdcc94..7e02178c4b5 100644 --- a/src/tribler/core/components/restapi/tests/test_restapi_component.py +++ b/src/tribler/core/components/restapi/tests/test_restapi_component.py @@ -2,7 +2,6 @@ import pytest -from tribler.core.components.bandwidth_accounting.bandwidth_accounting_component import BandwidthAccountingComponent from tribler.core.components.database.database_component import DatabaseComponent from tribler.core.components.exceptions import NoneComponent from tribler.core.components.ipv8.ipv8_component import Ipv8Component @@ -20,7 +19,7 @@ # pylint: disable=protected-access, not-callable, redefined-outer-name async def test_rest_component(tribler_config): components = [KeyComponent(), RESTComponent(), Ipv8Component(), LibtorrentComponent(), ResourceMonitorComponent(), - BandwidthAccountingComponent(), KnowledgeComponent(), SocksServersComponent(), DatabaseComponent()] + KnowledgeComponent(), SocksServersComponent(), DatabaseComponent()] async with Session(tribler_config, components) as session: # Test REST component starts normally comp = session.get_instance(RESTComponent) diff --git a/src/tribler/core/components/tunnel/community/caches.py b/src/tribler/core/components/tunnel/community/caches.py index 49aa437cec2..569f9d14589 100644 --- a/src/tribler/core/components/tunnel/community/caches.py +++ b/src/tribler/core/components/tunnel/community/caches.py @@ -1,18 +1,6 @@ from asyncio import Future -from ipv8.requestcache import NumberCache, RandomNumberCache - - -class BalanceRequestCache(NumberCache): - - def __init__(self, community, circuit_id, balance_future): - super().__init__(community.request_cache, "balance-request", circuit_id) - self.circuit_id = circuit_id - self.balance_future = balance_future - self.register_future(self.balance_future) - - def on_timeout(self): - pass +from ipv8.requestcache import RandomNumberCache class HTTPRequestCache(RandomNumberCache): diff --git a/src/tribler/core/components/tunnel/community/payload.py b/src/tribler/core/components/tunnel/community/payload.py index 774be62ac8d..df97f89742b 100644 --- a/src/tribler/core/components/tunnel/community/payload.py +++ b/src/tribler/core/components/tunnel/community/payload.py @@ -3,65 +3,6 @@ from ipv8.messaging.lazy_payload import VariablePayload, vp_compile -@vp_compile -class BandwidthTransactionPayload(VariablePayload): - """ - Payload for a message containing a bandwidth transaction. - """ - msg_id = 30 - format_list = ['I', '74s', '74s', '64s', '64s', 'Q', 'Q', 'I', 'I'] - names = ["sequence_number", "public_key_a", "public_key_b", "signature_a", "signature_b", "amount", "timestamp", - "circuit_id", "base_amount"] - - @classmethod - def from_transaction(cls, transaction, circuit_id: int, base_amount: int): - """ - Create a transaction from the provided payload. - :param transaction: The BandwidthTransaction to convert to a payload. - :param circuit_id: The circuit identifier to include in the payload. - :param base_amount: The base amount of bandwidth to payout. - """ - return BandwidthTransactionPayload( - transaction.sequence_number, - transaction.public_key_a, - transaction.public_key_b, - transaction.signature_a, - transaction.signature_b, - transaction.amount, - transaction.timestamp, - circuit_id, - base_amount - ) - - -@vp_compile -class BalanceResponsePayload(VariablePayload): - """ - Payload that contains the bandwidth balance of a specific peer. - """ - msg_id = 31 - format_list = ["I", "q"] - names = ["circuit_id", "balance"] - - -class RelayBalanceResponsePayload(BalanceResponsePayload): - msg_id = 32 - - -@vp_compile -class BalanceRequestPayload(VariablePayload): - msg_id = 33 - format_list = ['I', 'H'] - names = ['circuit_id', 'identifier'] - - -@vp_compile -class RelayBalanceRequestPayload(VariablePayload): - msg_id = 34 - format_list = ['I'] - names = ['circuit_id'] - - @vp_compile class HTTPRequestPayload(VariablePayload): msg_id = 28 diff --git a/src/tribler/core/components/tunnel/community/tunnel_community.py b/src/tribler/core/components/tunnel/community/tunnel_community.py index 37316b62f70..bb2d387690d 100644 --- a/src/tribler/core/components/tunnel/community/tunnel_community.py +++ b/src/tribler/core/components/tunnel/community/tunnel_community.py @@ -1,52 +1,36 @@ import hashlib import math -import sys import time -from asyncio import Future, TimeoutError as AsyncTimeoutError, open_connection +from asyncio import TimeoutError as AsyncTimeoutError, open_connection from binascii import unhexlify from collections import Counter from distutils.version import LooseVersion from typing import Callable, List, Optional import async_timeout -from ipv8.messaging.anonymization.caches import CreateRequestCache from ipv8.messaging.anonymization.community import unpack_cell from ipv8.messaging.anonymization.hidden_services import HiddenTunnelCommunity -from ipv8.messaging.anonymization.payload import EstablishIntroPayload, NO_CRYPTO_PACKETS +from ipv8.messaging.anonymization.payload import EstablishIntroPayload from ipv8.messaging.anonymization.tunnel import ( - CIRCUIT_STATE_CLOSING, CIRCUIT_STATE_READY, - CIRCUIT_TYPE_DATA, CIRCUIT_TYPE_IP_SEEDER, - CIRCUIT_TYPE_RP_DOWNLOADER, CIRCUIT_TYPE_RP_SEEDER, - EXIT_NODE, PEER_FLAG_EXIT_BT, PEER_FLAG_EXIT_IPV8, - RelayRoute, ) -from ipv8.peer import Peer from ipv8.peerdiscovery.network import Network from ipv8.taskmanager import task -from ipv8.types import Address from ipv8.util import succeed -from pony.orm import OrmError from tribler.core import notifications -from tribler.core.components.bandwidth_accounting.db.transaction import BandwidthTransactionData from tribler.core.components.ipv8.tribler_community import args_kwargs_to_community_settings from tribler.core.components.socks_servers.socks5.server import Socks5Server -from tribler.core.components.tunnel.community.caches import BalanceRequestCache, HTTPRequestCache +from tribler.core.components.tunnel.community.caches import HTTPRequestCache from tribler.core.components.tunnel.community.discovery import GoldenRatioStrategy from tribler.core.components.tunnel.community.dispatcher import TunnelDispatcher from tribler.core.components.tunnel.community.payload import ( - BalanceRequestPayload, - BalanceResponsePayload, - BandwidthTransactionPayload, HTTPRequestPayload, HTTPResponsePayload, - RelayBalanceRequestPayload, - RelayBalanceResponsePayload, ) from tribler.core.utilities.bencodecheck import is_bencoded from tribler.core.utilities.path_util import Path @@ -65,18 +49,16 @@ class TriblerTunnelCommunity(HiddenTunnelCommunity): """ This community is built upon the anonymous messaging layer in IPv8. - It adds support for libtorrent anonymous downloads and bandwidth token payout when closing circuits. + It adds support for libtorrent anonymous downloads. """ community_id = unhexlify('a3591a6bd89bbaca0974062a1287afcfbc6fd6bb') def __init__(self, *args, **kwargs): - self.bandwidth_community = kwargs.pop('bandwidth_community', None) self.exitnode_cache: Optional[Path] = kwargs.pop('exitnode_cache', None) self.config = kwargs.pop('config', None) self.notifier = kwargs.pop('notifier', None) self.download_manager = kwargs.pop('dlmgr', None) self.socks_servers: List[Socks5Server] = kwargs.pop('socks_servers', []) - num_competing_slots = self.config.competing_slots num_random_slots = self.config.random_slots super().__init__(args_kwargs_to_community_settings(self.settings_class, args, kwargs)) @@ -90,7 +72,6 @@ def __init__(self, *args, **kwargs): self.bittorrent_peers = {} self.dispatcher = TunnelDispatcher(self) self.download_states = {} - self.competing_slots = [(0, None)] * num_competing_slots # 1st tuple item = token balance, 2nd = circuit id self.random_slots = [None] * num_random_slots # This callback is invoked with a tuple (time, balance) when we reject a circuit self.reject_callback: Optional[Callable] = None @@ -101,17 +82,9 @@ def __init__(self, *args, **kwargs): for server in self.socks_servers: server.output_stream = self.dispatcher - self.add_message_handler(BandwidthTransactionPayload, self.on_payout) - - self.add_cell_handler(BalanceRequestPayload, self.on_balance_request_cell) - self.add_cell_handler(RelayBalanceRequestPayload, self.on_relay_balance_request_cell) - self.add_cell_handler(BalanceResponsePayload, self.on_balance_response_cell) - self.add_cell_handler(RelayBalanceResponsePayload, self.on_relay_balance_response_cell) self.add_cell_handler(HTTPRequestPayload, self.on_http_request) self.add_cell_handler(HTTPResponsePayload, self.on_http_response) - NO_CRYPTO_PACKETS.extend([BalanceRequestPayload.msg_id, BalanceResponsePayload.msg_id]) - if self.exitnode_cache is not None: self.restore_exitnodes_from_disk() if self.download_manager is not None: @@ -163,130 +136,23 @@ def restore_exitnodes_from_disk(self): else: self.logger.warning('Could not retrieve backup exitnode cache, file does not exist!') - def on_token_balance(self, circuit_id, balance): - """ - We received the token balance of a circuit initiator. Check whether we can allocate a slot to this user. - """ - if not self.request_cache.has("balance-request", circuit_id): - self.logger.warning("Received token balance without associated request cache!") - return - - cache = self.request_cache.pop("balance-request", circuit_id) - - lowest_balance = sys.maxsize - lowest_index = -1 - for ind, tup in enumerate(self.competing_slots): - if not tup[1]: - # The slot is empty, take it - self.competing_slots[ind] = (balance, circuit_id) - cache.balance_future.set_result(True) - return - - if tup[0] < lowest_balance: - lowest_balance = tup[0] - lowest_index = ind - - if balance > lowest_balance: - # We kick this user out - old_circuit_id = self.competing_slots[lowest_index][1] - self.logger.info("Kicked out circuit %s (balance: %s) in favor of %s (balance: %s)", - old_circuit_id, lowest_balance, circuit_id, balance) - self.competing_slots[lowest_index] = (balance, circuit_id) - - self.remove_relay(old_circuit_id, destroy=DESTROY_REASON_BALANCE) - self.remove_exit_socket(old_circuit_id, destroy=DESTROY_REASON_BALANCE) - - cache.balance_future.set_result(True) - else: - # We can't compete with the balances in the existing slots - if self.reject_callback: - self.reject_callback(time.time(), balance) - cache.balance_future.set_result(False) - def should_join_circuit(self, create_payload, previous_node_address): """ Check whether we should join a circuit. Returns a future that fires with a boolean. """ - if self.settings.max_joined_circuits <= len(self.relay_from_to) + len(self.exit_sockets): - self.logger.warning("too many relays (%d)", (len(self.relay_from_to) + len(self.exit_sockets))) + joined_circuits = len(self.relay_from_to) + len(self.exit_sockets) + if self.settings.max_joined_circuits <= joined_circuits: + self.logger.warning("too many relays (%d)", joined_circuits) return succeed(False) circuit_id = create_payload.circuit_id - if self.request_cache.has('balance-request', circuit_id): - self.logger.warning("balance request already in progress for circuit %d", circuit_id) - return succeed(False) # Check whether we have a random open slot, if so, allocate this to this request. for index, slot in enumerate(self.random_slots): if not slot: self.random_slots[index] = circuit_id return succeed(True) - - # No random slots but this user might be allocated a competing slot. - # Next, we request the token balance of the circuit initiator. - self.logger.info("Requesting balance of circuit initiator!") - balance_future = Future() - self.request_cache.add(BalanceRequestCache(self, circuit_id, balance_future)) - - # Temporarily add these values, otherwise we are unable to communicate with the previous hop. - self.directions[circuit_id] = EXIT_NODE - shared_secret, _, _ = self.crypto.generate_diffie_shared_secret(create_payload.key) - self.relay_session_keys[circuit_id] = self.crypto.generate_session_keys(shared_secret) - - self.send_cell(Peer(create_payload.node_public_key, previous_node_address), - BalanceRequestPayload(circuit_id, create_payload.identifier)) - - self.directions.pop(circuit_id, None) - self.relay_session_keys.pop(circuit_id, None) - - return balance_future - - @unpack_cell(BalanceRequestPayload) - def on_balance_request_cell(self, _, payload, __): - if self.request_cache.has("create", payload.identifier): - request = self.request_cache.get("create", payload.identifier) - forwarding_relay = RelayRoute(request.from_circuit_id, request.peer) - self.send_cell(forwarding_relay.peer, RelayBalanceRequestPayload(forwarding_relay.circuit_id)) - elif self.request_cache.has("retry", payload.circuit_id): - self.on_balance_request(payload) - else: - self.logger.warning("Circuit creation cache for id %s not found!", payload.circuit_id) - - @unpack_cell(RelayBalanceRequestPayload) - def on_relay_balance_request_cell(self, source_address, payload, _): - self.on_balance_request(payload) - - def on_balance_request(self, payload): - """ - We received a balance request from a relay or exit node. Respond with the latest block in our chain. - """ - if not self.bandwidth_community: - self.logger.warning("Bandwidth community is not available, unable to send a balance response!") - return - - # Get the latest block - - # Get the current balance and send it back - balance = self.bandwidth_community.database.get_balance(self.my_peer.public_key.key_to_bin()) - - # We either send the response directly or relay the response to the last verified hop - circuit = self.circuits[payload.circuit_id] - if not circuit.hops: - self.send_cell(circuit.peer, BalanceResponsePayload(circuit.circuit_id, balance)) - else: - self.send_cell(circuit.peer, RelayBalanceResponsePayload(circuit.circuit_id, balance)) - - @unpack_cell(BalanceResponsePayload) - def on_balance_response_cell(self, source_address, payload, _): - self.on_token_balance(payload.circuit_id, payload.balance) - - @unpack_cell(RelayBalanceResponsePayload) - def on_relay_balance_response_cell(self, source_address, payload, _): - # At this point, we don't have the circuit ID of the follow-up hop. We have to iterate over the items in the - # request cache and find the link to the next hop. - for cache in self.request_cache._identifiers.values(): - if isinstance(cache, CreateRequestCache) and cache.from_circuit_id == payload.circuit_id: - self.send_cell(cache.to_peer, BalanceResponsePayload(cache.to_circuit_id, payload.balance)) + return succeed(False) def readd_bittorrent_peers(self): for torrent, peers in list(self.bittorrent_peers.items()): @@ -311,66 +177,6 @@ def update_torrent(self, peers, download): if self.find_circuits(): self.readd_bittorrent_peers() - def do_payout(self, peer: Peer, circuit_id: int, amount: int, base_amount: int) -> None: - """ - Perform a payout to a specific peer. - :param peer: The peer to perform the payout to, usually the next node in the circuit. - :param circuit_id: The circuit id of the payout, used by the subsequent node. - :param amount: The amount to put in the transaction, multiplier of base_amount. - :param base_amount: The base amount for the payout. - """ - self.logger.info("Sending payout of %d (base: %d) to %s (cid: %s)", amount, base_amount, peer, circuit_id) - - tx = self.bandwidth_community.construct_signed_transaction(peer, amount) - try: - self.bandwidth_community.database.BandwidthTransaction.insert(tx) - except OrmError as e: - self.logger.exception(e) - return - - payload = BandwidthTransactionPayload.from_transaction(tx, circuit_id, base_amount) - packet = self._ez_pack(self._prefix, 30, [payload], False) - self.send_packet(peer, packet) - - def on_payout(self, source_address: Address, data: bytes) -> None: - """ - We received a payout from another peer. - :param source_address: The address of the peer that sent us this payout. - :param data: The serialized, raw data. - """ - if not self.bandwidth_community: - self.logger.warning("Got payout while not having a bandwidth community running!") - return - - payload = self._ez_unpack_noauth(BandwidthTransactionPayload, data, global_time=False) - tx = BandwidthTransactionData.from_payload(payload) - - if not tx.is_valid(): - self.logger.info("Received invalid bandwidth transaction in tunnel community - ignoring it") - return - - from_peer = Peer(payload.public_key_a, source_address) - my_pk = self.my_peer.public_key.key_to_bin() - latest_tx = self.bandwidth_community.database.get_latest_transaction( - self.my_peer.public_key.key_to_bin(), from_peer.public_key.key_to_bin()) - if payload.circuit_id != 0 and tx.public_key_b == my_pk and (not latest_tx or latest_tx.amount < tx.amount): - # Sign it and send it back - tx.sign(self.my_peer.key, as_a=False) - self.bandwidth_community.database.BandwidthTransaction.insert(tx) - - response_payload = BandwidthTransactionPayload.from_transaction(tx, 0, payload.base_amount) - packet = self._ez_pack(self._prefix, 30, [response_payload], False) - self.send_packet(from_peer, packet) - elif payload.circuit_id == 0 and tx.public_key_a == my_pk: - if not latest_tx or (latest_tx and latest_tx.amount >= tx.amount): - self.bandwidth_community.database.BandwidthTransaction.insert(tx) - - # Send the next payout - if payload.circuit_id in self.relay_from_to and tx.amount > payload.base_amount: - relay = self.relay_from_to[payload.circuit_id] - self._logger.info("Sending next payout to peer %s", relay.peer) - self.do_payout(relay.peer, relay.circuit_id, payload.base_amount * 2, payload.base_amount) - def clean_from_slots(self, circuit_id): """ Clean a specific circuit from the allocated slots. @@ -379,10 +185,6 @@ def clean_from_slots(self, circuit_id): if slot == circuit_id: self.random_slots[ind] = None - for ind, tup in enumerate(self.competing_slots): - if tup[1] == circuit_id: - self.competing_slots[ind] = (0, None) - def remove_circuit(self, circuit_id, additional_info='', remove_now=False, destroy=False): if circuit_id not in self.circuits: self.logger.warning("Circuit %d not found when trying to remove it", circuit_id) @@ -394,22 +196,6 @@ def remove_circuit(self, circuit_id, additional_info='', remove_now=False, destr if self.notifier: self.notifier[notifications.circuit_removed](circuit, additional_info) - # Ignore circuits that are closing so we do not payout again if we receive a destroy message. - if circuit.state != CIRCUIT_STATE_CLOSING and self.bandwidth_community: - - # We should perform a payout of the removed circuit. - if circuit.ctype == CIRCUIT_TYPE_RP_DOWNLOADER: - # We remove an e2e circuit as downloader. We pay the subsequent nodes in the downloader part of the e2e - # circuit. In addition, we pay for one hop seeder anonymity since we don't know the circuit length at - # the seeder side. - self.do_payout(circuit.peer, circuit_id, circuit.bytes_down * ((circuit.goal_hops * 2) + 1), - circuit.bytes_down) - - if circuit.ctype == CIRCUIT_TYPE_DATA: - # We remove a regular data circuit as downloader. Pay the relay nodes and the exit nodes. - self.do_payout(circuit.peer, circuit_id, circuit.bytes_down * (circuit.goal_hops * 2 - 1), - circuit.bytes_down) - affected_peers = self.dispatcher.circuit_dead(circuit) # Make sure the circuit is marked as closing, otherwise we may end up reusing it diff --git a/src/tribler/core/components/tunnel/settings.py b/src/tribler/core/components/tunnel/settings.py index 92ee8b86610..bda267fbd88 100644 --- a/src/tribler/core/components/tunnel/settings.py +++ b/src/tribler/core/components/tunnel/settings.py @@ -6,8 +6,7 @@ class TunnelCommunitySettings(TriblerConfigSection): enabled: bool = True exitnode_enabled: bool = False - random_slots: int = 5 - competing_slots: int = 15 + random_slots: int = 20 testnet: bool = Field(default=False, env='TUNNEL_TESTNET') min_circuits: int = 3 max_circuits: int = 10 diff --git a/src/tribler/core/components/tunnel/tests/test_triblertunnel_community.py b/src/tribler/core/components/tunnel/tests/test_triblertunnel_community.py index d9df2cd1517..ffd905103fc 100644 --- a/src/tribler/core/components/tunnel/tests/test_triblertunnel_community.py +++ b/src/tribler/core/components/tunnel/tests/test_triblertunnel_community.py @@ -1,13 +1,12 @@ from __future__ import annotations import os -from asyncio import Future, TimeoutError as AsyncTimeoutError, sleep, wait_for +from asyncio import TimeoutError as AsyncTimeoutError, wait_for from collections import defaultdict from random import random from unittest.mock import Mock import pytest - from ipv8.keyvault.public.libnaclkey import LibNaCLPK from ipv8.messaging.anonymization.payload import EstablishIntroPayload from ipv8.messaging.anonymization.tunnel import ( @@ -24,13 +23,7 @@ from ipv8.test.mocking.exit_socket import MockTunnelExitSocket from ipv8.util import succeed -from tribler.core.components.bandwidth_accounting.community.bandwidth_accounting_community import ( - BandwidthAccountingCommunity, -) -from tribler.core.components.bandwidth_accounting.db.database import BandwidthDatabase -from tribler.core.components.bandwidth_accounting.settings import BandwidthAccountingSettings from tribler.core.components.ipv8.adapters_tests import TriblerMockIPv8 -from tribler.core.components.tunnel.community.payload import BandwidthTransactionPayload from tribler.core.components.tunnel.community.tunnel_community import PEER_FLAG_EXIT_HTTP, TriblerTunnelCommunity from tribler.core.components.tunnel.settings import TunnelCommunitySettings from tribler.core.tests.tools.base_test import MockObject @@ -38,7 +31,6 @@ from tribler.core.utilities.network_utils import NetworkUtils from tribler.core.utilities.path_util import Path from tribler.core.utilities.simpledefs import DownloadStatus -from tribler.core.utilities.utilities import MEMORY_DB @pytest.mark.usefixtures("tmp_path") @@ -54,8 +46,6 @@ def setUp(self): async def tearDown(self): test_community.global_dht_services = defaultdict(list) # Reset the global_dht_services variable - for node in self.nodes: - await node.overlay.bandwidth_community.unload() await super().tearDown() def create_node(self, *args, **kwargs): @@ -67,12 +57,6 @@ def create_node(self, *args, **kwargs): ) mock_ipv8.overlay.settings.max_circuits = 1 - db = BandwidthDatabase(db_path=MEMORY_DB, my_pub_key=mock_ipv8.my_peer.public_key.key_to_bin()) - - # Load the bandwidth accounting community - mock_ipv8.overlay.bandwidth_community = BandwidthAccountingCommunity( - mock_ipv8.my_peer, mock_ipv8.endpoint, mock_ipv8.network, - settings=BandwidthAccountingSettings(), database=db) mock_ipv8.overlay.dht_provider = MockDHTProvider(Peer(mock_ipv8.overlay.my_peer.key, mock_ipv8.overlay.my_estimated_wan)) @@ -311,64 +295,6 @@ def test_update_torrent(self): self.nodes[0].overlay.bittorrent_peers[mock_download] = {('4.4.4.4', 4)} self.nodes[0].overlay.update_torrent(peers, mock_download) - async def test_payouts(self): - """ - Test whether nodes are correctly paid after transferring data - """ - self.add_node_to_experiment(self.create_node()) - self.add_node_to_experiment(self.create_node()) - - # Make sure that every node has some initial transactions. This will help us to detect bugs in the - # relay payout logic, e.g. https://github.com/Tribler/tribler/issues/5789. - for node in self.nodes: - for other_node in self.nodes: - if node == other_node: - continue - - await node.overlay.bandwidth_community.do_payout(other_node.my_peer, 100 * 1024 * 1024) - - # Build a tunnel - self.nodes[2].overlay.settings.peer_flags.add(PEER_FLAG_EXIT_BT) - await self.introduce_nodes() - self.nodes[0].overlay.build_tunnels(2) - await self.deliver_messages(timeout=.5) - - self.assertEqual(self.nodes[0].overlay.tunnels_ready(2), 1.0) - - # Destroy the circuit - for circuit_id, circuit in list(self.nodes[0].overlay.circuits.items()): - circuit.bytes_down = 250 * 1024 * 1024 - await self.nodes[0].overlay.remove_circuit(circuit_id, destroy=1) - await self.deliver_messages() - - # Verify whether the downloader (node 0) correctly paid the relay and exit nodes. - self.assertTrue(self.nodes[0].overlay.bandwidth_community.database.get_my_balance() < 0) - self.assertTrue(self.nodes[1].overlay.bandwidth_community.database.get_my_balance() > 0) - self.assertTrue(self.nodes[2].overlay.bandwidth_community.database.get_my_balance() > 0) - - balances = [] - for node_nr in [0, 1, 2]: - balances.append(self.nodes[node_nr].overlay.bandwidth_community.database.get_my_balance()) - - balances.sort() - self.assertEqual(balances[0], -750 * 1024 * 1024) - self.assertEqual(balances[1], 250 * 1024 * 1024) - self.assertEqual(balances[2], 500 * 1024 * 1024) - - async def test_invalid_payout(self): - """ - Test whether an invalid payout to another peer is ignored - """ - self.add_node_to_experiment(self.create_node()) - - tx = self.nodes[0].overlay.bandwidth_community.construct_signed_transaction(self.nodes[1].my_peer, 1024 * 1024) - tx.signature_a = b"a" * 32 - payload = BandwidthTransactionPayload.from_transaction(tx, 0, 1024) - packet = self.nodes[0].overlay._ez_pack(self.nodes[0].overlay._prefix, 30, [payload], False) - self.nodes[0].overlay.send_packet(self.nodes[1].my_peer, packet) - - assert not self.nodes[1].overlay.bandwidth_community.database.get_my_balance() - async def test_circuit_reject_too_many(self): """ Test whether a circuit is rejected by an exit node if it already joined the max number of circuits @@ -382,155 +308,6 @@ async def test_circuit_reject_too_many(self): self.assertEqual(self.nodes[0].overlay.tunnels_ready(1), 0.0) - async def test_payouts_e2e(self): - """ - Check if payouts work for an e2e-linked circuit - """ - self.add_node_to_experiment(self.create_node()) - self.add_node_to_experiment(self.create_node()) - - service = b'0' * 20 - - self.nodes[0].overlay.join_swarm(service, 1, seeding=False) - - await self.introduce_nodes() - await self.create_intro(2, service) - await self.assign_exit_node(0) - - await self.nodes[0].overlay.do_peer_discovery() - - await self.deliver_messages(timeout=0.5) - - # Destroy the e2e-circuit - removed_circuits = [] - for circuit_id, circuit in self.nodes[0].overlay.circuits.items(): - if circuit.ctype == CIRCUIT_TYPE_RP_DOWNLOADER: - circuit.bytes_down = 250 * 1024 * 1024 - self.nodes[0].overlay.remove_circuit(circuit_id, destroy=1) - removed_circuits.append(circuit_id) - - await sleep(0.5) - - # Verify whether the downloader (node 0) correctly paid the subsequent nodes. - self.assertTrue(self.nodes[0].overlay.bandwidth_community.database.get_my_balance() < 0) - self.assertTrue(self.nodes[1].overlay.bandwidth_community.database.get_my_balance() >= 0) - self.assertTrue(self.nodes[2].overlay.bandwidth_community.database.get_my_balance() > 0) - - # Ensure balances remain unchanged after calling remove_circuit a second time - balances = [self.nodes[i].overlay.bandwidth_community.database.get_my_balance() for i in range(3)] - for circuit_id in removed_circuits: - self.nodes[0].overlay.remove_circuit(circuit_id, destroy=1) - for i in range(3): - self.assertEqual(self.nodes[i].overlay.bandwidth_community.database.get_my_balance(), balances[i]) - - async def test_decline_competing_slot(self): - """ - Test whether a circuit is not created when a node does not have enough balance for a competing slot - """ - self.add_node_to_experiment(self.create_node()) - self.nodes[1].overlay.settings.peer_flags.add(PEER_FLAG_EXIT_BT) - await self.introduce_nodes() - self.nodes[1].overlay.random_slots = [] - self.nodes[1].overlay.competing_slots = [(1000, 1234)] - self.nodes[0].overlay.build_tunnels(1) - await self.deliver_messages() - - # Assert whether we didn't create the circuit - self.assertEqual(self.nodes[0].overlay.tunnels_ready(1), 0.0) - - async def test_win_competing_slot(self): - """ - Test whether a circuit is created when a node has enough balance for a competing slot - """ - self.add_node_to_experiment(self.create_node()) - self.nodes[1].overlay.settings.peer_flags.add(PEER_FLAG_EXIT_BT) - await self.introduce_nodes() - self.nodes[1].overlay.random_slots = [] - self.nodes[1].overlay.competing_slots = [(-1000, 1234)] - self.nodes[0].overlay.build_tunnels(1) - await self.deliver_messages() - - # Assert whether we didn't create the circuit - self.assertEqual(self.nodes[0].overlay.tunnels_ready(1), 1.0) - - async def test_empty_competing_slot(self): - """ - Test whether a circuit is created when a node takes an empty competing slot - """ - self.add_node_to_experiment(self.create_node()) - self.nodes[1].overlay.settings.peer_flags.add(PEER_FLAG_EXIT_BT) - await self.introduce_nodes() - self.nodes[1].overlay.random_slots = [] - self.nodes[1].overlay.competing_slots = [(0, None)] - self.nodes[0].overlay.build_tunnels(1) - await self.deliver_messages() - - # Assert whether we did create the circuit - self.assertEqual(self.nodes[0].overlay.tunnels_ready(1), 1.0) - - async def test_win_competing_slot_exit(self): - """ - Test whether a two-hop circuit is created when a node has enough balance for a competing slot at the exit - """ - self.add_node_to_experiment(self.create_node()) - self.add_node_to_experiment(self.create_node()) - self.nodes[2].overlay.settings.peer_flags.add(PEER_FLAG_EXIT_BT) - await self.introduce_nodes() - self.nodes[2].overlay.random_slots = [] - self.nodes[2].overlay.competing_slots = [(-1000, 1234)] - self.nodes[0].overlay.build_tunnels(2) - await self.deliver_messages() - - # Assert whether we did create the circuit - self.assertEqual(self.nodes[0].overlay.tunnels_ready(2), 1.0) - - async def test_win_competing_slot_relay(self): - """ - Test whether a two-hop circuit is created when a node has enough balance for a competing slot - """ - self.add_node_to_experiment(self.create_node()) - self.add_node_to_experiment(self.create_node()) - self.nodes[2].overlay.settings.peer_flags.add(PEER_FLAG_EXIT_BT) - await self.introduce_nodes() - self.nodes[1].overlay.random_slots = [] - self.nodes[1].overlay.competing_slots = [(-1000, 1234)] - self.nodes[0].overlay.build_tunnels(2) - await self.deliver_messages() - - # Assert whether we did create the circuit - self.assertEqual(self.nodes[0].overlay.tunnels_ready(2), 1.0) - - async def test_payout_on_competition_kick(self): - """ - Test whether a payout is initiated when an existing node is kicked out from a competing slot - """ - self.add_node_to_experiment(self.create_node()) - self.add_node_to_experiment(self.create_node()) - self.nodes[2].overlay.settings.peer_flags.add(PEER_FLAG_EXIT_BT) - await self.introduce_nodes() - - # Make sure that there's a token disbalance between node 0 and 1 - await self.nodes[0].overlay.bandwidth_community.do_payout(self.nodes[1].my_peer, 1024 * 1024) - - self.nodes[2].overlay.random_slots = [] - self.nodes[2].overlay.competing_slots = [(0, None)] - self.nodes[0].overlay.build_tunnels(1) - await self.deliver_messages() - - # Let some artificial data flow over the circuit - list(self.nodes[0].overlay.circuits.values())[0].bytes_down = 250 * 1024 * 1024 - - self.assertEqual(self.nodes[0].overlay.tunnels_ready(1), 1.0) - self.assertTrue(self.nodes[2].overlay.exit_sockets) - - self.nodes[1].overlay.build_tunnels(1) - await self.deliver_messages() - self.assertTrue(self.nodes[2].overlay.exit_sockets) - self.assertEqual(self.nodes[1].overlay.tunnels_ready(1), 1.0) - - # Check whether the exit node has been paid - self.assertGreaterEqual(self.nodes[2].overlay.bandwidth_community.database.get_my_balance(), 250 * 1024 * 1024) - async def test_intro_point_slot(self): """ Test whether a introduction point occupies a slot @@ -550,36 +327,6 @@ async def test_intro_point_slot(self): await self.deliver_messages() self.assertFalse(exit_socket.circuit_id in self.nodes[1].overlay.random_slots) - async def test_reject_callback(self): - """ - Test whether the rejection callback is correctly invoked when a circuit request is rejected - """ - reject_future = Future() - self.add_node_to_experiment(self.create_node()) - self.nodes[1].overlay.settings.peer_flags.add(PEER_FLAG_EXIT_BT) - await self.introduce_nodes() - - # Make sure that there's a token disbalance between node 0 and 1 - await self.nodes[0].overlay.bandwidth_community.do_payout(self.nodes[1].my_peer, 1024 * 1024) - - def on_reject(_, balance): - self.assertEqual(balance, -1024 * 1024) - reject_future.set_result(None) - - self.nodes[1].overlay.reject_callback = on_reject - - # Initialize the slots - self.nodes[1].overlay.random_slots = [] - self.nodes[1].overlay.competing_slots = [(100000000, 12345)] - - self.nodes[0].overlay.build_tunnels(1) - await self.deliver_messages() - - self.assertEqual(self.nodes[0].overlay.tunnels_ready(1), 0.0) - - # Node 0 should be rejected and the reject callback should be invoked by node 1 - await reject_future - async def test_perform_http_request(self): """ Test whether we can make a http request through a circuit @@ -664,7 +411,7 @@ async def test_perform_http_request_failed(self): def test_cache_exitnodes_to_disk(self): """ Test whether we can cache exit nodes to disk """ - self.overlay(0).candidates = {Peer(LibNaCLPK(b'\x00'*64), ("0.1.2.3", 1029)): {PEER_FLAG_EXIT_BT}} + self.overlay(0).candidates = {Peer(LibNaCLPK(b'\x00' * 64), ("0.1.2.3", 1029)): {PEER_FLAG_EXIT_BT}} self.overlay(0).exitnode_cache = self.tmp_path / 'exitnode_cache.dat' self.overlay(0).cache_exitnodes_to_disk() @@ -672,8 +419,19 @@ def test_cache_exitnodes_to_disk(self): def test_cache_exitnodes_to_disk_os_error(self): """ Test whether we can handle an OSError when caching exit nodes to disk and raise no errors """ - self.overlay(0).candidates = {Peer(LibNaCLPK(b'\x00'*64), ("0.1.2.3", 1029)): {PEER_FLAG_EXIT_BT}} + self.overlay(0).candidates = {Peer(LibNaCLPK(b'\x00' * 64), ("0.1.2.3", 1029)): {PEER_FLAG_EXIT_BT}} self.overlay(0).exitnode_cache = Mock(write_bytes=Mock(side_effect=FileNotFoundError)) self.overlay(0).cache_exitnodes_to_disk() assert self.overlay(0).exitnode_cache.write_bytes.called + + async def test_should_join_circuit(self): + """ Test whether we can join a circuit""" + community: TriblerTunnelCommunity = self.overlay(0) + assert await community.should_join_circuit(create_payload=Mock(), previous_node_address=Mock()) + + async def test_should_join_circuit_no_slots(self): + """ Test whether we can not join a circuit when we have no slots""" + community: TriblerTunnelCommunity = self.overlay(0) + community.random_slots = [] + assert not await community.should_join_circuit(create_payload=Mock(), previous_node_address=Mock()) diff --git a/src/tribler/core/components/tunnel/tunnel_component.py b/src/tribler/core/components/tunnel/tunnel_component.py index ae89702fb95..10fce6db51e 100644 --- a/src/tribler/core/components/tunnel/tunnel_component.py +++ b/src/tribler/core/components/tunnel/tunnel_component.py @@ -1,7 +1,6 @@ from ipv8.dht.provider import DHTCommunityProvider from ipv8.messaging.anonymization.community import TunnelSettings -from tribler.core.components.bandwidth_accounting.bandwidth_accounting_component import BandwidthAccountingComponent from tribler.core.components.component import Component from tribler.core.components.ipv8.ipv8_component import INFINITE, Ipv8Component from tribler.core.components.libtorrent.libtorrent_component import LibtorrentComponent @@ -24,9 +23,6 @@ async def run(self): self._ipv8_component = await self.require_component(Ipv8Component) dht_discovery_community = self._ipv8_component.dht_discovery_community - bandwidth_component = await self.get_component(BandwidthAccountingComponent) - bandwidth_community = bandwidth_component.community if bandwidth_component else None - download_component = await self.get_component(LibtorrentComponent) download_manager = download_component.download_manager if download_component else None @@ -46,7 +42,6 @@ async def run(self): provider = DHTCommunityProvider(dht_discovery_community, config.ipv8.port) if dht_discovery_community else None exitnode_cache = config.state_dir / "exitnode_cache.dat" - # TODO: decouple bandwidth community and dlmgr to initiate later self.community = tunnel_cls(self._ipv8_component.peer, self._ipv8_component.ipv8.endpoint, self._ipv8_component.ipv8.network, @@ -54,7 +49,6 @@ async def run(self): config=config.tunnel_community, notifier=self.session.notifier, dlmgr=download_manager, - bandwidth_community=bandwidth_community, dht_provider=provider, exitnode_cache=exitnode_cache, settings=settings) diff --git a/src/tribler/core/config/tribler_config.py b/src/tribler/core/config/tribler_config.py index ccee0b92a30..64df1ee6efa 100644 --- a/src/tribler/core/config/tribler_config.py +++ b/src/tribler/core/config/tribler_config.py @@ -9,7 +9,6 @@ from configobj import ParseError from pydantic import BaseSettings, Extra, PrivateAttr, validate_model -from tribler.core.components.bandwidth_accounting.settings import BandwidthAccountingSettings from tribler.core.components.ipv8.settings import ( BootstrapSettings, DHTSettings, @@ -40,7 +39,6 @@ class Config: general: GeneralSettings = GeneralSettings() tunnel_community: TunnelCommunitySettings = TunnelCommunitySettings() - bandwidth_accounting: BandwidthAccountingSettings = BandwidthAccountingSettings() bootstrap: BootstrapSettings = BootstrapSettings() ipv8: Ipv8Settings = Ipv8Settings() discovery_community: DiscoveryCommunitySettings = DiscoveryCommunitySettings() diff --git a/src/tribler/core/start_core.py b/src/tribler/core/start_core.py index 23502f43d0f..cad77af6818 100644 --- a/src/tribler/core/start_core.py +++ b/src/tribler/core/start_core.py @@ -3,7 +3,6 @@ import logging.config import os import signal -import sys from pathlib import Path from typing import List, Optional @@ -12,8 +11,8 @@ check_and_enable_code_tracing, set_process_priority, ) -from tribler.core.components.bandwidth_accounting.bandwidth_accounting_component import BandwidthAccountingComponent from tribler.core.components.component import Component +from tribler.core.components.content_discovery.content_discovery_component import ContentDiscoveryComponent from tribler.core.components.database.database_component import DatabaseComponent from tribler.core.components.gui_process_watcher.gui_process_watcher import GuiProcessWatcher from tribler.core.components.gui_process_watcher.gui_process_watcher_component import GuiProcessWatcherComponent @@ -21,8 +20,6 @@ from tribler.core.components.key.key_component import KeyComponent from tribler.core.components.knowledge.knowledge_component import KnowledgeComponent from tribler.core.components.libtorrent.libtorrent_component import LibtorrentComponent -from tribler.core.components.payout.payout_component import PayoutComponent -from tribler.core.components.content_discovery.content_discovery_component import ContentDiscoveryComponent from tribler.core.components.reporter.exception_handler import default_core_exception_handler from tribler.core.components.reporter.reporter_component import ReporterComponent from tribler.core.components.resource_monitor.resource_monitor_component import ResourceMonitorComponent @@ -65,8 +62,6 @@ def components_gen(config: TriblerConfig): if config.libtorrent.enabled: yield LibtorrentComponent() - if config.ipv8.enabled: - yield BandwidthAccountingComponent() if config.resource_monitor.enabled: yield ResourceMonitorComponent() @@ -84,8 +79,6 @@ def components_gen(config: TriblerConfig): if config.ipv8.enabled and config.tunnel_community.enabled: yield TunnelsComponent() - if config.ipv8.enabled: - yield PayoutComponent() yield WatchFolderComponent() if config.general.version_checker_enabled: yield VersionCheckComponent() diff --git a/src/tribler/core/tests/tools/data/upgrade_databases/bandwidth_v8.db b/src/tribler/core/tests/tools/data/upgrade_databases/bandwidth_v8.db deleted file mode 100644 index b7138b418a79acd8d6e86e8fc94f04c7ea4969f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 438272 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⁡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 diff --git a/src/tribler/core/upgrade/tests/test_upgrader.py b/src/tribler/core/upgrade/tests/test_upgrader.py index eea1831b546..7e86ded7eb7 100644 --- a/src/tribler/core/upgrade/tests/test_upgrader.py +++ b/src/tribler/core/upgrade/tests/test_upgrader.py @@ -7,9 +7,8 @@ import pytest from ipv8.keyvault.private.libnaclkey import LibNaCLSK -from pony.orm import db_session, select +from pony.orm import db_session -from tribler.core.components.bandwidth_accounting.db.database import BandwidthDatabase from tribler.core.components.database.db.orm_bindings.torrent_metadata import CHANNEL_DIR_NAME_LENGTH from tribler.core.components.database.db.store import CURRENT_DB_VERSION, MetadataStore from tribler.core.tests.tools.common import TESTS_DATA_DIR @@ -19,6 +18,7 @@ cleanup_noncompliant_channel_torrents from tribler.core.utilities.configparser import CallbackConfigParser from tribler.core.utilities.db_corruption_handling.base import DatabaseIsCorrupted +from tribler.core.utilities.simpledefs import STATEDIR_DB_DIR from tribler.core.utilities.utilities import random_infohash @@ -326,19 +326,6 @@ def test_calc_progress(): assert calc_progress(1000, 100) == pytest.approx(99.158472, abs=EPSILON) -def test_upgrade_bw_accounting_db_8to9(upgrader, state_dir, trustchain_keypair): - bandwidth_path = state_dir / 'sqlite/bandwidth.db' - _copy('bandwidth_v8.db', bandwidth_path) - - upgrader.upgrade_bw_accounting_db_8to9() - db = BandwidthDatabase(bandwidth_path, trustchain_keypair.key.pk) - with db_session: - assert not list(select(tx for tx in db.BandwidthTransaction)) - assert not list(select(item for item in db.BandwidthHistory)) - assert int(db.MiscData.get(name="db_version").value) == 9 - db.shutdown() - - def test_remove_old_logs(upgrader: TriblerUpgrader, state_dir: Path, tmp_path): """Ensure that the `remove_old_logs` function removes only logs""" @@ -403,3 +390,20 @@ def patched_unlink(self, *_, **__): assert left == [side_effect_log_file] assert not normal_log_file.exists() assert side_effect_log_file.exists() + + +def test_remove_bandwidth_db(upgrader: TriblerUpgrader, state_dir: Path): + """ Ensure that the `remove_bandwidth_db` function removes only bandwidth db files""" + db_path = Path(state_dir / STATEDIR_DB_DIR) + + (db_path / 'bandwidth.db').touch() + (db_path / 'bandwidth.db-shm').touch() + (db_path / 'bandwidth.db-wal').touch() + (db_path / 'knowledge.db').touch() + + assert len(list(db_path.glob('*'))) == 4 + assert len(list(db_path.glob('bandwidth*'))) == 3 + + upgrader.remove_bandwidth_db() + + assert len(list(db_path.glob('bandwidth*'))) == 0 diff --git a/src/tribler/core/upgrade/upgrade.py b/src/tribler/core/upgrade/upgrade.py index 3d7be8a921f..6f588e99306 100644 --- a/src/tribler/core/upgrade/upgrade.py +++ b/src/tribler/core/upgrade/upgrade.py @@ -3,14 +3,14 @@ import shutil import time from configparser import MissingSectionHeaderError, ParsingError +from contextlib import suppress from functools import wraps from types import SimpleNamespace from typing import List, Optional, Tuple from ipv8.keyvault.private.libnaclkey import LibNaCLSK -from pony.orm import db_session, delete +from pony.orm import db_session -from tribler.core.components.bandwidth_accounting.db.database import BandwidthDatabase from tribler.core.components.database.db.orm_bindings.torrent_metadata import CHANNEL_DIR_NAME_LENGTH from tribler.core.components.database.db.store import ( CURRENT_DB_VERSION, MetadataStore, @@ -130,7 +130,6 @@ def run(self): self.upgrade_pony_db_8to10() self.upgrade_pony_db_10to11() convert_config_to_tribler76(self.state_dir) - self.upgrade_bw_accounting_db_8to9() self.upgrade_pony_db_11to12() self.upgrade_pony_db_12to13() self.upgrade_pony_db_13to14() @@ -138,6 +137,7 @@ def run(self): self.remove_old_logs() self.upgrade_pony_db_14to15() self.upgrade_knowledge_to_tribler_db() + self.remove_bandwidth_db() migration_chain = TriblerDatabaseMigrationChain(self.state_dir) migration_chain.execute() @@ -268,35 +268,6 @@ def upgrade_pony_db_10to11(self): self.do_upgrade_pony_db_10to11(mds) mds.shutdown() - @catch_db_is_corrupted_exception - def upgrade_bw_accounting_db_8to9(self): - """ - Upgrade the database with bandwidth accounting information from 8 to 9. - Specifically, this upgrade wipes all transactions and addresses an issue where payouts with the wrong amount - were made. Also see https://github.com/Tribler/tribler/issues/5789. - """ - self._logger.info('Upgrade bandwidth accounting DB 8 to 9') - to_version = 9 - - database_path = self.state_dir / STATEDIR_DB_DIR / 'bandwidth.db' - if not database_path.exists() or get_db_version(database_path, BandwidthDatabase.CURRENT_DB_VERSION) > 8: - # No need to update if the database does not exist or is already updated - return # pragma: no cover - - self._logger.info('bw8->9') - db = BandwidthDatabase(database_path, self.primary_key.key.pk) - - # Wipe all transactions and bandwidth history - with db_session: - delete(tx for tx in db.BandwidthTransaction) - delete(item for item in db.BandwidthHistory) - - # Update db version - db_version = db.MiscData.get(name="db_version") - db_version.value = str(to_version) - - db.shutdown() - def column_exists_in_table(self, db, table, column): pragma = f'SELECT COUNT(*) FROM pragma_table_info("{table}") WHERE name="{column}"' result = list(db.execute(pragma)) @@ -488,3 +459,13 @@ def upgrade_knowledge_to_tribler_db(self): self._logger.info('Upgrade knowledge to tribler.db') migration = MigrationKnowledgeToTriblerDB(self.state_dir) migration.run() + + def remove_bandwidth_db(self): + self._logger.info('Removing bandwidth database') + + db_path = Path(self.state_dir / STATEDIR_DB_DIR) + + for file_path in db_path.glob('bandwidth*'): + self._logger.info(f'Removing {file_path}') + with suppress(OSError): + file_path.unlink(missing_ok=True) diff --git a/src/tribler/gui/debug_window.py b/src/tribler/gui/debug_window.py index c6544dc77ce..e15b9292802 100644 --- a/src/tribler/gui/debug_window.py +++ b/src/tribler/gui/debug_window.py @@ -6,7 +6,6 @@ import sys from binascii import unhexlify from time import localtime, strftime, time -from typing import Dict import libtorrent import psutil @@ -179,8 +178,6 @@ def tab_changed(self, index): self.load_general_tab() elif index == 1: self.load_requests_tab() - elif index == 2: - self.run_with_timer(self.load_bandwidth_accounting_tab) elif index == 3: self.ipv8_tab_changed(self.window().ipv8_tab_widget.currentIndex()) elif index == 4: @@ -324,23 +321,6 @@ def load_requests_tab(self): item.setText(2, f"{strftime('%H:%M:%S', localtime(timestamp))}") self.window().requests_tree_widget.addTopLevelItem(item) - def load_bandwidth_accounting_tab(self) -> None: - """ - Initiate a request to the Tribler core to fetch statistics on bandwidth accounting. - """ - request_manager.get("bandwidth/statistics", self.on_bandwidth_statistics) - - def on_bandwidth_statistics(self, data: Dict) -> None: - """ - We received bandwidth statistics from the core. - :param data: The bandwidth statistics, in JSON format. - """ - if not data: - return - self.window().bandwidth_tree_widget.clear() - for key, value in data["statistics"].items(): - self.create_and_add_widget_item(key, value, self.window().bandwidth_tree_widget) - def load_ipv8_general_tab(self): request_manager.get("statistics/ipv8", self.on_ipv8_general_stats) diff --git a/src/tribler/gui/qt_resources/debugwindow.ui b/src/tribler/gui/qt_resources/debugwindow.ui index 9c0866ac45a..eb13b7ce3ab 100644 --- a/src/tribler/gui/qt_resources/debugwindow.ui +++ b/src/tribler/gui/qt_resources/debugwindow.ui @@ -112,49 +112,7 @@ - - - Bandwidth Accounting - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 2 - - - 200 - - - - Key - - - - - Value - - - - - - - + IPv8 @@ -413,7 +371,7 @@ - + Tunnels @@ -747,7 +705,7 @@ - + DHT @@ -862,7 +820,7 @@ - + Events @@ -920,7 +878,7 @@ - + System diff --git a/src/tribler/gui/tests/test_gui.py b/src/tribler/gui/tests/test_gui.py index 350c25bc03a..d08457514da 100644 --- a/src/tribler/gui/tests/test_gui.py +++ b/src/tribler/gui/tests/test_gui.py @@ -315,8 +315,6 @@ def test_settings(window): screenshot(window, name="settings_general") QTest.mouseClick(window.settings_connection_button, Qt.LeftButton) screenshot(window, name="settings_connection") - QTest.mouseClick(window.settings_bandwidth_button, Qt.LeftButton) - screenshot(window, name="settings_bandwidth") QTest.mouseClick(window.settings_seeding_button, Qt.LeftButton) screenshot(window, name="settings_seeding") QTest.mouseClick(window.settings_anonymity_button, Qt.LeftButton) @@ -497,10 +495,6 @@ def test_debug_pane(window): wait_for_list_populated(window.debug_window.requests_tree_widget) screenshot(window.debug_window, name="debug_panel_requests_tab") - window.debug_window.debug_tab_widget.setCurrentIndex(2) - wait_for_list_populated(window.debug_window.bandwidth_tree_widget) - screenshot(window.debug_window, name="debug_panel_bandwidth_tab") - window.debug_window.debug_tab_widget.setCurrentIndex(3) wait_for_list_populated(window.debug_window.ipv8_general_tree_widget) screenshot(window.debug_window, name="debug_panel_ipv8_tab")