diff --git a/Tribler/Core/APIImplementation/LaunchManyCore.py b/Tribler/Core/APIImplementation/LaunchManyCore.py index 532d5bf89de..a8aefcda02d 100644 --- a/Tribler/Core/APIImplementation/LaunchManyCore.py +++ b/Tribler/Core/APIImplementation/LaunchManyCore.py @@ -878,6 +878,11 @@ def early_shutdown(self): else: self._logger.info("lmc: Dispersy failed to shutdown in %.2f seconds", diff) + if self.tunnel_community and self.triblerchain_community: + # We unload these overlays manually since the triblerchain has to be unloaded after the tunnel overlay. + yield self.ipv8.unload_overlay(self.tunnel_community) + yield self.ipv8.unload_overlay(self.triblerchain_community) + if self.ipv8: yield self.ipv8.stop(stop_reactor=False) self.ipv8.endpoint.close() diff --git a/Tribler/Core/Modules/restapi/market/matchmakers_endpoint.py b/Tribler/Core/Modules/restapi/market/matchmakers_endpoint.py new file mode 100644 index 00000000000..10560988d4c --- /dev/null +++ b/Tribler/Core/Modules/restapi/market/matchmakers_endpoint.py @@ -0,0 +1,35 @@ +from Tribler.Core.Modules.restapi.market import BaseMarketEndpoint +from Tribler.Core.Utilities.json_util import dumps + + +class MatchmakersEndpoint(BaseMarketEndpoint): + """ + This class handles requests regarding your known matchmakers in the market community. + """ + + def render_GET(self, request): + """ + .. http:get:: /market/matchmakers + + A GET request to this endpoint will return all known matchmakers. + + **Example request**: + + .. sourcecode:: none + + curl -X GET http://localhost:8085/market/matchmakers + + **Example response**: + + .. sourcecode:: javascript + + { + "matchmakers": [{ + "ip": "131.249.48.3", + "port": 7008 + }] + } + """ + matchmakers = self.session.lm.market_community.matchmakers + matchmakers_json = [{"ip": mm.address[0], "port": mm.address[1]} for mm in matchmakers] + return dumps({"matchmakers": matchmakers_json}) diff --git a/Tribler/Core/Modules/restapi/market_endpoint.py b/Tribler/Core/Modules/restapi/market_endpoint.py index c0e7641c35a..fe00e00a635 100644 --- a/Tribler/Core/Modules/restapi/market_endpoint.py +++ b/Tribler/Core/Modules/restapi/market_endpoint.py @@ -1,3 +1,4 @@ +from Tribler.Core.Modules.restapi.market.matchmakers_endpoint import MatchmakersEndpoint from twisted.web import resource from Tribler.Core.Modules.restapi.market.asks_bids_endpoint import AsksEndpoint, BidsEndpoint @@ -15,6 +16,6 @@ def __init__(self, session): self.session = session child_handler_dict = {"asks": AsksEndpoint, "bids": BidsEndpoint, "transactions": TransactionsEndpoint, - "orders": OrdersEndpoint} + "orders": OrdersEndpoint, "matchmakers": MatchmakersEndpoint} for path, child_cls in child_handler_dict.iteritems(): self.putChild(path, child_cls(self.session)) diff --git a/Tribler/Core/Modules/restapi/wallets_endpoint.py b/Tribler/Core/Modules/restapi/wallets_endpoint.py index 4d99096d547..a5ae7b0ccfe 100644 --- a/Tribler/Core/Modules/restapi/wallets_endpoint.py +++ b/Tribler/Core/Modules/restapi/wallets_endpoint.py @@ -232,15 +232,15 @@ def __init__(self, session, identifier): def render_POST(self, request): """ - .. http:get:: /wallets/(string:wallet identifier)/transfer + .. http:post:: /wallets/(string:wallet identifier)/transfer - A GET request to this endpoint will return past transactions of a specific wallet. + A POST request to this endpoint will transfer some units from a wallet to another address. **Example request**: .. sourcecode:: none - curl -X GET http://localhost:8085/wallets/BTC/transfer + curl -X POST http://localhost:8085/wallets/BTC/transfer --data "amount=0.3&destination=mpC1DDgSP4PKc5HxJzQ5w9q6CGLBEQuLsN" **Example response**: diff --git a/Tribler/Test/Community/Market/Wallet/test_btc_wallet.py b/Tribler/Test/Community/Market/Wallet/test_btc_wallet.py index 5e5ae5f5b26..915c226fd59 100644 --- a/Tribler/Test/Community/Market/Wallet/test_btc_wallet.py +++ b/Tribler/Test/Community/Market/Wallet/test_btc_wallet.py @@ -1,3 +1,4 @@ +from jsonrpclib import ProtocolError from twisted.internet.defer import inlineCallbacks, succeed, Deferred from Tribler.Test.Core.base_test import MockObject @@ -143,8 +144,43 @@ def test_get_transactions(self): wallet = BitcoinWallet(self.session_base_dir) mock_daemon = MockObject() mock_server = MockObject() - transactions = [{'value': -1, 'txid': 'a', 'timestamp': 1}, {'value': 1, 'txid': 'a', 'timestamp': 1}] + transactions = [{ + 'value': -1, + 'txid': 'a', + 'timestamp': 1, + 'input_addresses': ['a', 'b'], + 'output_addresses': ['c', 'd'], + 'confirmations': 3 + }, { + 'value': 1, + 'txid': 'b', + 'timestamp': False, # In Electrum, this means that the transaction has not been confirmed yet + 'input_addresses': ['a', 'b'], + 'output_addresses': ['c', 'd'], + 'confirmations': 0 + }] mock_server.run_cmdline = lambda _: transactions mock_daemon.get_server = lambda _: mock_server wallet.get_daemon = lambda: mock_daemon return wallet.get_transactions() + + @deferred(timeout=10) + def test_get_transactions_error(self): + """ + Test whether no transactions are returned when there's a protocol in the JSON RPC protocol + """ + wallet = BitcoinWallet(self.session_base_dir) + mock_daemon = MockObject() + mock_server = MockObject() + + def failing_run_cmdline(*_): + raise ProtocolError() + + mock_server.run_cmdline = failing_run_cmdline + mock_daemon.get_server = lambda _: mock_server + wallet.get_daemon = lambda: mock_daemon + + def verify_transactions(transactions): + self.assertFalse(transactions) + + return wallet.get_transactions().addCallback(verify_transactions) diff --git a/Tribler/Test/Community/Market/Wallet/test_trustchain_wallet.py b/Tribler/Test/Community/Market/Wallet/test_trustchain_wallet.py index 2dc393080bc..98e366d943a 100644 --- a/Tribler/Test/Community/Market/Wallet/test_trustchain_wallet.py +++ b/Tribler/Test/Community/Market/Wallet/test_trustchain_wallet.py @@ -14,6 +14,7 @@ def setUp(self): self.initialize(TrustChainCommunity, 2) self.tc_wallet = TrustchainWallet(self.nodes[0].overlay) self.tc_wallet.MONITOR_DELAY = 0.01 + self.tc_wallet.check_negative_balance = True def create_node(self): return MockIPv8(u"curve25519", TrustChainCommunity, working_directory=u":memory:") diff --git a/Tribler/Test/Community/Market/test_community.py b/Tribler/Test/Community/Market/test_community.py index afab6e57e94..0631cfbba99 100644 --- a/Tribler/Test/Community/Market/test_community.py +++ b/Tribler/Test/Community/Market/test_community.py @@ -184,13 +184,38 @@ def test_failing_payment(self): self.nodes[node_nr].overlay.wallets['DUM2'].transfer = lambda *_: fail(RuntimeError("oops")) yield self.nodes[0].overlay.create_ask(1, 'DUM1', 1, 'DUM2', 3600) - yield self.deliver_messages() yield self.nodes[1].overlay.create_bid(1, 'DUM1', 1, 'DUM2', 3600) + yield self.deliver_messages() self.assertEqual(self.nodes[0].overlay.transaction_manager.find_all()[0].status, "error") self.assertEqual(self.nodes[1].overlay.transaction_manager.find_all()[0].status, "error") + @twisted_wrapper + def test_proposed_trade_timeout(self): + """ + Test whether we unreserve the quantity if a proposed trade timeouts + """ + yield self.introduce_nodes() + + self.nodes[0].overlay.decode_map[chr(10)] = lambda *_: None + + ask_order = yield self.nodes[0].overlay.create_ask(1, 'DUM1', 1, 'DUM2', 3600) + bid_order = yield self.nodes[1].overlay.create_bid(1, 'DUM1', 1, 'DUM2', 3600) + + yield self.deliver_messages(timeout=.5) + + outstanding = self.nodes[1].overlay.get_outstanding_proposals(bid_order.order_id, ask_order.order_id) + self.assertTrue(outstanding) + outstanding[0][1].on_timeout() + + yield self.deliver_messages(timeout=.5) + + ask_tick_entry = self.nodes[2].overlay.order_book.get_tick(ask_order.order_id) + bid_tick_entry = self.nodes[2].overlay.order_book.get_tick(bid_order.order_id) + self.assertEqual(float(bid_tick_entry.reserved_for_matching), 0) + self.assertEqual(float(ask_tick_entry.reserved_for_matching), 0) + class TestMarketCommunityTwoNodes(TestMarketCommunityBase): __testing__ = True @@ -220,3 +245,36 @@ def test_e2e_trade(self): balance2 = yield self.nodes[1].overlay.wallets['DUM2'].get_balance() self.assertEqual(balance1['available'], 999) self.assertEqual(balance2['available'], 1001) + + @twisted_wrapper + def test_partial_trade(self): + """ + Test a partial trade + """ + yield self.introduce_nodes() + + yield self.nodes[0].overlay.create_ask(1, 'DUM1', 2, 'DUM2', 3600) + bid_order = yield self.nodes[1].overlay.create_bid(1, 'DUM1', 10, 'DUM2', 3600) + + yield self.deliver_messages(timeout=.5) + + # Verify that the trade has been made + self.assertEqual(len(self.nodes[0].overlay.transaction_manager.find_all()), 1) + self.assertEqual(len(self.nodes[1].overlay.transaction_manager.find_all()), 1) + + # There should be no reserved quantity for the bid tick + for node_nr in [0, 1]: + bid_tick_entry = self.nodes[node_nr].overlay.order_book.get_tick(bid_order.order_id) + self.assertEqual(float(bid_tick_entry.reserved_for_matching), 0) + + yield self.nodes[0].overlay.create_ask(1, 'DUM1', 8, 'DUM2', 3600) + + yield self.deliver_messages(timeout=.5) + + # Verify that the trade has been made + self.assertEqual(len(self.nodes[0].overlay.transaction_manager.find_all()), 2) + self.assertEqual(len(self.nodes[1].overlay.transaction_manager.find_all()), 2) + + for node_nr in [0, 1]: + self.assertEqual(len(self.nodes[node_nr].overlay.order_book.asks), 0) + self.assertEqual(len(self.nodes[node_nr].overlay.order_book.bids), 0) diff --git a/Tribler/Test/Community/Market/test_orderbook.py b/Tribler/Test/Community/Market/test_orderbook.py index 85400ba375c..1859d52a0a1 100644 --- a/Tribler/Test/Community/Market/test_orderbook.py +++ b/Tribler/Test/Community/Market/test_orderbook.py @@ -187,13 +187,15 @@ def test_update_ticks(self): "trader_id": str(self.ask.order_id.trader_id), "order_number": int(self.ask.order_id.order_number), "quantity": 3, - "quantity_type": self.ask.quantity.wallet_id + "quantity_type": self.ask.quantity.wallet_id, + "traded_quantity": 3 } bid_dict = { "trader_id": str(self.bid.order_id.trader_id), "order_number": int(self.bid.order_id.order_number), "quantity": 3, - "quantity_type": self.bid.quantity.wallet_id + "quantity_type": self.bid.quantity.wallet_id, + "traded_quantity": 3 } self.order_book.get_tick(self.ask.order_id).reserve_for_matching(Quantity(3, self.ask.quantity.wallet_id)) diff --git a/Tribler/Test/Community/Market/test_trade.py b/Tribler/Test/Community/Market/test_trade.py index 9bfcc619887..6c380f8d252 100644 --- a/Tribler/Test/Community/Market/test_trade.py +++ b/Tribler/Test/Community/Market/test_trade.py @@ -22,7 +22,6 @@ def test_to_network(self): # Test for to network self.assertEquals(NotImplemented, self.trade.to_network()) - class ProposedTradeTestSuite(unittest.TestCase): """Proposed trade test cases.""" @@ -61,6 +60,19 @@ def test_from_network(self): self.assertEquals(Quantity(30, 'MC'), data.quantity) self.assertEquals(Timestamp(1462224447.117), data.timestamp) + def test_has_acceptable_price(self): + """ + Test the acceptable price method + """ + self.assertTrue(self.proposed_trade.has_acceptable_price(True, Price(63000, 'BTC'))) + self.assertFalse(self.proposed_trade.has_acceptable_price(False, Price(63000, 'BTC'))) + self.assertTrue(self.proposed_trade.has_acceptable_price(False, Price(64000, 'BTC'))) + self.assertFalse(self.proposed_trade.has_acceptable_price(True, Price(64000, 'BTC'))) + + # Test a price close to the proposed price + self.assertTrue(self.proposed_trade.has_acceptable_price(False, Price(63400.0 - 1e-07, 'BTC'))) + self.assertTrue(self.proposed_trade.has_acceptable_price(True, Price(63400.0 - 1e-07, 'BTC'))) + class DeclinedTradeTestSuite(unittest.TestCase): """Declined trade test cases.""" diff --git a/Tribler/Test/Core/Modules/RestApi/test_market_endpoint.py b/Tribler/Test/Core/Modules/RestApi/test_market_endpoint.py index 38b0e64d0a8..ee37740f380 100644 --- a/Tribler/Test/Core/Modules/RestApi/test_market_endpoint.py +++ b/Tribler/Test/Core/Modules/RestApi/test_market_endpoint.py @@ -261,3 +261,16 @@ def on_response(response): self.should_check_equality = False return self.do_request('market/orders/1/cancel', request_type='POST', expected_code=200)\ .addCallback(on_response) + + @deferred(timeout=10) + def test_get_matchmakers(self): + """ + Test the request to fetch known matchmakers + """ + def on_response(response): + json_response = json.loads(response) + self.assertGreaterEqual(len(json_response['matchmakers']), 1) + + self.session.lm.market_community.matchmakers.add(self.session.lm.market_community.my_peer) + self.should_check_equality = False + return self.do_request('market/matchmakers', expected_code=200).addCallback(on_response) diff --git a/Tribler/community/market/community.py b/Tribler/community/market/community.py index 23857c5b2d3..8458e3a0ec0 100644 --- a/Tribler/community/market/community.py +++ b/Tribler/community/market/community.py @@ -67,7 +67,7 @@ def on_timeout(self): if self.match_id: # Inform the matchmaker about the failed trade match_message = self.community.incoming_match_messages[self.match_id] - self.community.send_decline_match_message(self.match_id, match_message.payload.matchmaker_trader_id, + self.community.send_decline_match_message(self.match_id, match_message.matchmaker_trader_id, DeclineMatchReason.OTHER) @@ -114,7 +114,7 @@ def __init__(self, *args, **kwargs): self.use_local_address = False self.matching_enabled = True self.message_repository = MemoryMessageRepository(self.mid) - self.use_incremental_payments = True + self.use_incremental_payments = False self.matchmakers = set() self.pending_matchmaker_deferreds = [] self.request_cache = RequestCache() @@ -529,6 +529,7 @@ def received_half_block(self, source_address, data): TransactionNumber(block.transaction["tx"]["transaction_number"])) transaction = self.transaction_manager.find_by_id(transaction_id) if transaction and self.market_database.get_linked(block): + self.notify_transaction_complete(transaction.to_dictionary(), mine=True) self.send_transaction_completed(transaction, block) self.process_market_block(block) @@ -798,8 +799,8 @@ def received_match(self, source_address, data): auth, _, payload = self._ez_unpack_auth(MatchPayload, data) peer = Peer(auth.public_key_bin, source_address) - self.logger.debug("We received a match message for order %s.%s", - TraderId(self.mid), payload.recipient_order_number) + self.logger.debug("We received a match message for order %s.%s (matched quantity: %s)", + TraderId(self.mid), payload.recipient_order_number, payload.match_quantity) # We got a match, check whether we can respond to this match self.update_ip(payload.matchmaker_trader_id, source_address) @@ -1032,10 +1033,10 @@ def received_proposed_trade(self, _, data): declined_trade = Trade.decline(self.message_repository.next_identity(), Timestamp.now(), proposed_trade, decline_reason) self.logger.debug("Declined trade made with id: %s for proposed trade with id: %s " - "(valid? %s, available quantity of order: %s, reserved: %s, traded: %s)", + "(valid? %s, available quantity of order: %s, reserved: %s, traded: %s), reason: %s", str(declined_trade.message_id), str(proposed_trade.message_id), order.is_valid(), order.available_quantity, order.reserved_quantity, - order.traded_quantity) + order.traded_quantity, decline_reason) self.send_declined_trade(declined_trade) else: self.logger.debug("Proposed trade received with id: %s for order with id: %s", @@ -1356,7 +1357,7 @@ def on_payment_error(failure): When a payment fails, log the error and still send a payment message to inform the other party that the payment has failed. """ - self.logger.error("Payment of %f to %s failed: %s", transfer_amount, + self.logger.error("Payment of %s to %s failed: %s", transfer_amount, str(transaction.partner_incoming_address), failure.value) self.send_payment_message(PaymentId(''), transaction, payment_tup, False) @@ -1449,7 +1450,7 @@ def on_tx_done_signed(block): """ We received the signed block from the counterparty, wrap everything up """ - self.notify_transaction_complete(transaction) + self.notify_transaction_complete(transaction.to_dictionary(), mine=True) self.send_transaction_completed(transaction, block) def build_tx_done_block(other_order_dict): @@ -1481,14 +1482,16 @@ def abort_transaction(self, transaction): """ self.logger.error("Aborting transaction %s", transaction.transaction_id) order = self.order_manager.order_repository.find_by_id(transaction.order_id) - order.release_quantity_for_tick(transaction.partner_order_id, - transaction.total_quantity - transaction.transferred_quantity) - self.order_manager.order_repository.update(order) + if (transaction.total_quantity - transaction.transferred_quantity) > \ + Quantity(0, transaction.total_quantity.wallet_id): + order.release_quantity_for_tick(transaction.partner_order_id, + transaction.total_quantity - transaction.transferred_quantity) + self.order_manager.order_repository.update(order) - def notify_transaction_complete(self, transaction): + def notify_transaction_complete(self, tx_dict, mine=False): if self.tribler_session: self.tribler_session.notifier.notify(NTFY_MARKET_ON_TRANSACTION_COMPLETE, NTFY_UPDATE, None, - transaction.to_dictionary()) + {"tx": tx_dict, "mine": mine}) def send_transaction_completed(self, transaction, block): """ @@ -1535,6 +1538,8 @@ def on_transaction_completed_bc_message(self, block1, _): if not self.is_matchmaker: return + self.notify_transaction_complete(tx_dict["tx"]) + # Update ticks in order book, release the reserved quantity quantity = Quantity(tx_dict["tx"]["quantity"], tx_dict["tx"]["quantity_type"]) self.order_book.update_ticks(tx_dict["ask"], tx_dict["bid"], quantity, unreserve=False) diff --git a/Tribler/community/market/core/matching_engine.py b/Tribler/community/market/core/matching_engine.py index 146dffb2a01..8c17bf4793f 100644 --- a/Tribler/community/market/core/matching_engine.py +++ b/Tribler/community/market/core/matching_engine.py @@ -237,8 +237,8 @@ def _search_for_quantity_in_price_level(self, order_id, tick_entry, quantity_to_ def _search_for_quantity_in_price_level_total(self, tick_entry, quantity_to_trade): trading_quantity = quantity_to_trade - self._logger.debug("Match with the id (%s) was found: price %i, quantity %i", - str(tick_entry.order_id), float(tick_entry.price), int(trading_quantity)) + self._logger.debug("Match with the id (%s) was found: price %f, quantity %f", + str(tick_entry.order_id), float(tick_entry.price), float(trading_quantity)) return [(self.get_unique_match_id(), tick_entry, trading_quantity)] @@ -250,8 +250,8 @@ def _search_for_quantity_in_price_level_partial(self, order_id, tick_entry, quan tick_entry.is_blocked_for_matching(order_id): quantity_to_trade -= matched_quantity - self._logger.debug("Match with the id (%s) was found: price %i, quantity %i", - str(tick_entry.order_id), float(tick_entry.price), int(matched_quantity)) + self._logger.debug("Match with the id (%s) was found: price %f, quantity %f", + str(tick_entry.order_id), float(tick_entry.price), float(matched_quantity)) matching_ticks = [(self.get_unique_match_id(), tick_entry, matched_quantity)] diff --git a/Tribler/community/market/core/orderbook.py b/Tribler/community/market/core/orderbook.py index 5c41e62c2e4..cc5085979f2 100644 --- a/Tribler/community/market/core/orderbook.py +++ b/Tribler/community/market/core/orderbook.py @@ -117,9 +117,9 @@ def update_ticks(self, ask_order_dict, bid_order_dict, traded_quantity, unreserv str(ask_order_id), str(bid_order_id), str(traded_quantity)) # Update ask tick - new_ask_quantity = Quantity(ask_order_dict["quantity"] - float(traded_quantity), + new_ask_quantity = Quantity(ask_order_dict["quantity"] - ask_order_dict["traded_quantity"], ask_order_dict["quantity_type"]) - if self.tick_exists(ask_order_id) and new_ask_quantity < self.get_tick(ask_order_id).quantity: + if self.tick_exists(ask_order_id) and new_ask_quantity <= self.get_tick(ask_order_id).quantity: tick = self.get_tick(ask_order_id) tick.quantity = new_ask_quantity if unreserve: @@ -133,9 +133,9 @@ def update_ticks(self, ask_order_dict, bid_order_dict, traded_quantity, unreserv self.insert_ask(ask) # Update bid tick - new_bid_quantity = Quantity(bid_order_dict["quantity"] - float(traded_quantity), + new_bid_quantity = Quantity(bid_order_dict["quantity"] - bid_order_dict["traded_quantity"], bid_order_dict["quantity_type"]) - if self.tick_exists(bid_order_id) and new_bid_quantity < self.get_tick(bid_order_id).quantity: + if self.tick_exists(bid_order_id) and new_bid_quantity <= self.get_tick(bid_order_id).quantity: tick = self.get_tick(bid_order_id) tick.quantity = new_bid_quantity if unreserve: diff --git a/Tribler/community/market/core/side.py b/Tribler/community/market/core/side.py index d1d208429b0..19f5e3db3b6 100644 --- a/Tribler/community/market/core/side.py +++ b/Tribler/community/market/core/side.py @@ -117,11 +117,12 @@ def remove_tick(self, order_id): assert isinstance(order_id, OrderId), type(order_id) tick = self.get_tick(order_id) - tick.cancel_all_pending_tasks() - tick.price_level().remove_tick(tick) - if len(tick.price_level()) == 0: # Last tick for that price - self._remove_price_level(tick.price, tick.quantity.wallet_id) - del self._tick_map[order_id] + if tick: + tick.cancel_all_pending_tasks() + tick.price_level().remove_tick(tick) + if len(tick.price_level()) == 0: # Last tick for that price + self._remove_price_level(tick.price, tick.quantity.wallet_id) + del self._tick_map[order_id] def get_price_level_list(self, price_wallet_id, quantity_wallet_id): """ diff --git a/Tribler/community/market/core/trade.py b/Tribler/community/market/core/trade.py index 8bec7f73739..4d9807bac7c 100644 --- a/Tribler/community/market/core/trade.py +++ b/Tribler/community/market/core/trade.py @@ -221,7 +221,13 @@ def has_acceptable_price(self, is_ask, order_price): Return whether this trade proposal has an acceptable price. :rtype: bool """ - return (is_ask and self.price >= order_price) or (not is_ask and self.price <= order_price) + def isclose(price_a, price_b): + price_a = float(price_a) + price_b = float(price_b) + return abs(price_a - price_b) <= 1e-06 + + return (is_ask and (self.price >= order_price or isclose(self.price, order_price))) or \ + (not is_ask and (self.price <= order_price or isclose(self.price, order_price))) def to_network(self): """ diff --git a/Tribler/community/market/wallet/btc_wallet.py b/Tribler/community/market/wallet/btc_wallet.py index de46d8c46fc..82f4b22906c 100644 --- a/Tribler/community/market/wallet/btc_wallet.py +++ b/Tribler/community/market/wallet/btc_wallet.py @@ -5,6 +5,7 @@ import imp import keyring from Tribler.Core.Utilities.install_dir import get_base_path +from jsonrpclib import ProtocolError from twisted.internet.defer import Deferred, succeed, fail, inlineCallbacks from twisted.internet.task import LoopingCall @@ -88,7 +89,7 @@ def start_daemon(self): if not fd: return - self.daemon = self.get_daemon().Daemon(config, fd) + self.daemon = self.get_daemon().Daemon(config, fd, is_gui=False) self.daemon.start() def open_wallet(self): @@ -150,12 +151,23 @@ def get_balance(self): """ Return the balance of the wallet. """ - divider = 100000000 if self.created: - confirmed, unconfirmed, unmatured = self.wallet.get_balance() + options = {'nolnet': False, 'password': None, 'verbose': False, 'cmd': 'getbalance', + 'wallet_path': self.wallet_file, 'testnet': self.testnet, 'segwit': False, + 'cwd': self.wallet_dir, + 'portable': False} + config = SimpleConfig(options) + + server = self.get_daemon().get_server(config) + result = server.run_cmdline(options) + + confirmed = float(result['confirmed']) + unconfirmed = float(result['unconfirmed']) if 'unconfirmed' in result else 0 + unconfirmed += (float(result['unmatured']) if 'unmatured' in result else 0) + return succeed({ - "available": float(confirmed) / divider, - "pending": float(unconfirmed + unmatured) / divider, + "available": confirmed, + "pending": unconfirmed, "currency": 'BTC' }) else: @@ -226,17 +238,17 @@ def get_transactions(self): config = SimpleConfig(options) server = self.get_daemon().get_server(config) - result = server.run_cmdline(options) + try: + result = server.run_cmdline(options) + except ProtocolError: + self._logger.error("Unable to fetch transactions from BTC wallet!") + return succeed([]) transactions = [] for transaction in result: outgoing = transaction['value'] < 0 - if outgoing: - from_address = self.get_address() - to_address = '' - else: - from_address = '' - to_address = self.get_address() + from_address = ','.join(transaction['input_addresses']) + to_address = ','.join(transaction['output_addresses']) transactions.append({ 'id': transaction['txid'], @@ -247,7 +259,7 @@ def get_transactions(self): 'fee_amount': 0.0, 'currency': 'BTC', 'timestamp': str(transaction['timestamp']), - 'description': '' + 'description': 'Confirmations: %d' % transaction['confirmations'] }) return succeed(transactions) diff --git a/Tribler/community/market/wallet/tc_wallet.py b/Tribler/community/market/wallet/tc_wallet.py index 6c044ed5c4b..c80e13080fe 100644 --- a/Tribler/community/market/wallet/tc_wallet.py +++ b/Tribler/community/market/wallet/tc_wallet.py @@ -20,7 +20,7 @@ def __init__(self, tc_community): self.tc_community = tc_community self.created = True - self.check_negative_balance = True + self.check_negative_balance = False self.transaction_history = [] def get_name(self): diff --git a/Tribler/community/triblertunnel/dispatcher.py b/Tribler/community/triblertunnel/dispatcher.py index c6dc12d1a3c..633b165f252 100644 --- a/Tribler/community/triblertunnel/dispatcher.py +++ b/Tribler/community/triblertunnel/dispatcher.py @@ -46,8 +46,6 @@ def on_incoming_from_tunnel(self, community, circuit, origin, data, force=False) socks5_data = conversion.encode_udp_packet( 0, 0, conversion.ADDRESS_TYPE_IPV4, origin[0], origin[1], data) return session._udp_socket.sendDatagram(socks5_data) - else: - self._logger.error("UDP socket for socks server not available!") return False diff --git a/Tribler/pyipv8 b/Tribler/pyipv8 index b336cc086cc..bcb9cb61ef3 160000 --- a/Tribler/pyipv8 +++ b/Tribler/pyipv8 @@ -1 +1 @@ -Subproject commit b336cc086cceff20e9596ae4c8012f55027d42af +Subproject commit bcb9cb61ef38e36c48ced676b90daeba6c3ac4d9 diff --git a/TriblerGUI/qt_resources/mainwindow.ui b/TriblerGUI/qt_resources/mainwindow.ui index 36a27c05d6d..014b5ee509a 100644 --- a/TriblerGUI/qt_resources/mainwindow.ui +++ b/TriblerGUI/qt_resources/mainwindow.ui @@ -7,7 +7,7 @@ 0 0 875 - 768 + 777 @@ -7982,8 +7982,8 @@ QTabBar::tab:selected { 0 0 - 673 - 341 + 131 + 260 @@ -11196,7 +11196,7 @@ QTabBar::tab:selected { } - 0 + 1 @@ -11273,8 +11273,11 @@ QTabBar::tab:selected { + + QAbstractItemView::NoSelection + - 8 + 0 diff --git a/TriblerGUI/tribler_window.py b/TriblerGUI/tribler_window.py index e27d96ac4b2..703ec5e3f8a 100644 --- a/TriblerGUI/tribler_window.py +++ b/TriblerGUI/tribler_window.py @@ -467,8 +467,8 @@ def load_token_balance(self): def received_token_balance(self, statistics): statistics = statistics["statistics"] if 'latest_block' in statistics: - balance = statistics["latest_block"]["transaction"]["total_up"] - \ - statistics["latest_block"]["transaction"]["total_down"] + balance = (statistics["latest_block"]["transaction"]["total_up"] - \ + statistics["latest_block"]["transaction"]["total_down"]) / 1024 / 1024 self.token_balance_label.setText("%d" % balance) else: self.token_balance_label.setText("0") @@ -736,6 +736,7 @@ def show_force_shutdown(): if self.tray_icon: self.tray_icon.deleteLater() self.show_loading_screen() + self.hide_status_bar() self.loading_text_label.setText("Shutting down...") self.shutdown_timer = QTimer() diff --git a/TriblerGUI/utilities.py b/TriblerGUI/utilities.py index 49a06d1d839..9f3cfa92fc0 100644 --- a/TriblerGUI/utilities.py +++ b/TriblerGUI/utilities.py @@ -48,7 +48,7 @@ def timestamp_to_time(timestamp): diff = today - discovered if diff.days > 0 or today.day != discovered.day: - return discovered.strftime('%d-%m-%Y') + return discovered.strftime('%d-%m-%Y %H:%M') return discovered.strftime('Today %H:%M') diff --git a/TriblerGUI/widgets/marketpage.py b/TriblerGUI/widgets/marketpage.py index af4035b2486..5ff8a82e3f5 100644 --- a/TriblerGUI/widgets/marketpage.py +++ b/TriblerGUI/widgets/marketpage.py @@ -213,17 +213,22 @@ def on_bid(self, bid): self.update_filter_bids_list() def on_transaction_complete(self, transaction): - main_text = "Transaction with price %f %s and quantity %f %s completed." \ - % (transaction["price"], transaction["price_type"], - transaction["quantity"], transaction["quantity_type"]) - self.window().tray_icon.showMessage("Transaction completed", main_text) - self.window().hide_status_bar() + if transaction["mine"]: + transaction = transaction["tx"] + main_text = "Transaction with price %f %s and quantity %f %s completed." \ + % (transaction["price"], transaction["price_type"], + transaction["quantity"], transaction["quantity_type"]) + self.window().tray_icon.showMessage("Transaction completed", main_text) + self.window().hide_status_bar() - # Reload wallets - self.load_wallets() + # Reload wallets + self.load_wallets() - # Reload transactions - self.window().market_transactions_page.load_transactions() + # Reload transactions + self.window().market_transactions_page.load_transactions() + else: + self.load_asks() + self.load_bids() def on_iom_input_required(self, event_dict): self.dialog = IomInputDialog(self.window().stackedWidget, event_dict['bank_name'], event_dict['input']) diff --git a/TriblerGUI/widgets/marketwalletspage.py b/TriblerGUI/widgets/marketwalletspage.py index 4c2d254b7aa..c90133edd73 100644 --- a/TriblerGUI/widgets/marketwalletspage.py +++ b/TriblerGUI/widgets/marketwalletspage.py @@ -11,7 +11,7 @@ from TriblerGUI.dialogs.confirmationdialog import ConfirmationDialog from TriblerGUI.tribler_action_menu import TriblerActionMenu from TriblerGUI.tribler_request_manager import TriblerRequestManager -from TriblerGUI.utilities import get_image_path +from TriblerGUI.utilities import get_image_path, timestamp_to_time class MarketWalletsPage(QWidget): @@ -103,6 +103,7 @@ def initialize_wallet_info(self, wallet_id, pressed_button): button.setChecked(False) self.active_wallet = wallet_id + self.window().wallet_info_tabs.setCurrentIndex(0) self.window().wallet_address_label.setText(self.wallets[wallet_id]['address']) # Create a QR code of the wallet address @@ -126,11 +127,11 @@ def initialize_wallet_info(self, wallet_id, pressed_button): self.window().wallet_address_qr_label.setText("QR Code functionality not available!") def load_transactions(self, wallet_id): + self.window().wallet_transactions_list.clear() self.request_mgr = TriblerRequestManager() self.request_mgr.perform_request("wallets/%s/transactions" % wallet_id, self.on_transactions) def on_transactions(self, transactions): - self.window().wallet_transactions_list.clear() for transaction in transactions["transactions"]: item = QTreeWidgetItem(self.window().wallet_transactions_list) item.setText(0, "Sent" if transaction["outgoing"] else "Received") @@ -139,7 +140,8 @@ def on_transactions(self, transactions): item.setText(3, "%g %s" % (transaction["amount"], transaction["currency"])) item.setText(4, "%g %s" % (transaction["fee_amount"], transaction["currency"])) item.setText(5, transaction["id"]) - item.setText(6, transaction["timestamp"]) + timestamp = timestamp_to_time(float(transaction["timestamp"])) if transaction["timestamp"] != "False" else "-" + item.setText(6, timestamp) self.window().wallet_transactions_list.addTopLevelItem(item) def on_add_wallet_clicked(self): diff --git a/electrum b/electrum index d062792493d..c26cc844f5d 160000 --- a/electrum +++ b/electrum @@ -1 +1 @@ -Subproject commit d062792493dad38c92b0ef3326ec2176517a39ad +Subproject commit c26cc844f5dd3a91452dd84568dcacfec23679e2 diff --git a/systemd/tribler.service b/systemd/tribler.service new file mode 100644 index 00000000000..08f422e2fe7 --- /dev/null +++ b/systemd/tribler.service @@ -0,0 +1,21 @@ +[Unit] +Description="Tribler runner" +After=network.target + +[Service] +ProtectSystem=yes +PrivateTmp=true +Type=simple +User=tribler +Group=tribler +Restart=always +Environment=HOME=/var/lib/tribler/%I +Environment=EXTRA_TRIBLER_ARGS=-- +Environment=PYTHONPATH=/opt/tribler + +WorkingDirectory=/opt/tribler +ExecStartPre=/bin/mkdir -p ${HOME} +ExecStart=/usr/bin/twistd --nodaemon --pidfile= tribler ${EXTRA_TRIBLER_ARGS} + +[Install] +WantedBy=multi-user.target