Merge branch 'v0.6' of https://github.com/surbhicis/PyBitmessage into UiChanges

This commit is contained in:
surbhi 2020-01-06 15:04:47 +05:30
commit 74036147e5
No known key found for this signature in database
GPG Key ID: 88928762974D3618
18 changed files with 219 additions and 127 deletions

View File

@ -1,5 +1,5 @@
Copyright (c) 2012-2016 Jonathan Warren
Copyright (c) 2012-2019 The Bitmessage Developers
Copyright (c) 2012-2020 The Bitmessage Developers
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2012-2016 Jonathan Warren
Copyright (c) 2012-2019 The Bitmessage Developers
Copyright (c) 2012-2020 The Bitmessage Developers
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in

View File

@ -164,7 +164,7 @@ if (not compiler or prereqs) and OPSYS in PACKAGE_MANAGER:
if not compiler:
compilerToPackages()
prereqToPackages()
if mandatory:
if prereqs and mandatory:
sys.exit(1)
else:
print("All the dependencies satisfied, you can install PyBitmessage")

View File

@ -1,4 +1,3 @@
python_prctl
psutil
pycrypto
stem

View File

@ -2,7 +2,7 @@
# pylint: disable=too-many-statements
# Copyright (c) 2012-2016 Jonathan Warren
# Copyright (c) 2012-2019 The Bitmessage developers
# Copyright (c) 2012-2020 The Bitmessage developers
"""
This is not what you run to run the Bitmessage API. Instead, enable the API

View File

@ -3,7 +3,7 @@
The PyBitmessage startup script
"""
# Copyright (c) 2012-2016 Jonathan Warren
# Copyright (c) 2012-2019 The Bitmessage developers
# Copyright (c) 2012-2020 The Bitmessage developers
# Distributed under the MIT/X11 software license. See the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
@ -31,7 +31,8 @@ import shutdown
from bmconfigparser import BMConfigParser
from debug import logger # this should go before any threads
from helper_startup import (
isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections
isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections,
start_proxyconfig
)
from inventory import Inventory
from knownnodes import readKnownNodes
@ -166,30 +167,9 @@ def signal_handler(signum, frame):
class Main(object):
"""Main PyBitmessage class"""
@staticmethod
def start_proxyconfig(config):
"""Check socksproxytype and start any proxy configuration plugin"""
proxy_type = config.safeGet('bitmessagesettings', 'socksproxytype')
if proxy_type not in ('none', 'SOCKS4a', 'SOCKS5'):
# pylint: disable=relative-import
from plugins.plugin import get_plugin
try:
proxyconfig_start = time.time()
if not get_plugin('proxyconfig', name=proxy_type)(config):
raise TypeError
except TypeError:
logger.error(
'Failed to run proxy config plugin %s',
proxy_type, exc_info=True)
shutdown.doCleanShutdown()
sys.exit(2)
else:
logger.info(
'Started proxy config plugin %s in %s sec',
proxy_type, time.time() - proxyconfig_start)
def start(self): # pylint: disable=too-many-statements, too-many-branches, too-many-locals
def start(self):
"""Start main application"""
# pylint: disable=too-many-statements,too-many-branches,too-many-locals
_fixSocket()
config = BMConfigParser()
@ -346,7 +326,7 @@ class Main(object):
singleAPIThread.start()
# start network components if networking is enabled
if state.enableNetwork:
self.start_proxyconfig(config)
start_proxyconfig()
BMConnectionPool()
asyncoreThread = BMNetworkThread()
asyncoreThread.daemon = True
@ -405,7 +385,7 @@ class Main(object):
self.stop()
elif not state.enableGUI:
from tests import core as test_core # pylint: disable=relative-import
test_core_result = test_core.run(self)
test_core_result = test_core.run()
state.enableGUI = True
self.stop()
test_core.cleanup()

View File

@ -46,7 +46,7 @@
<item alignment="Qt::AlignLeft">
<widget class="QLabel" name="label_2">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Copyright © 2012-2016 Jonathan Warren&lt;br/&gt;Copyright © 2012-2019 The Bitmessage Developers&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Copyright © 2012-2016 Jonathan Warren&lt;br/&gt;Copyright © 2012-2020 The Bitmessage Developers&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignLeft</set>

View File

@ -47,7 +47,7 @@ class AboutDialog(QtGui.QDialog, RetranslateMixin):
try:
self.label_2.setText(
self.label_2.text().replace(
'2019', str(last_commit.get('time').year)
'2020', str(last_commit.get('time').year)
))
except AttributeError:
pass

View File

@ -1,3 +1,4 @@
import ConfigParser
import os
import sys
@ -16,10 +17,24 @@ import tempfile
import widgets
from bmconfigparser import BMConfigParser
from helper_sql import sqlExecute, sqlStoredProcedure
from helper_startup import start_proxyconfig
from network.asyncore_pollchoose import set_rates
from tr import _translate
def getSOCKSProxyType(config):
"""Get user socksproxytype setting from *config*"""
try:
result = ConfigParser.SafeConfigParser.get(
config, 'bitmessagesettings', 'socksproxytype')
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
return
else:
if result.lower() in ('', 'none', 'false'):
result = None
return result
class SettingsDialog(QtGui.QDialog):
"""The "Settings" dialog"""
def __init__(self, parent=None, firstrun=False):
@ -32,6 +47,16 @@ class SettingsDialog(QtGui.QDialog):
self.net_restart_needed = False
self.timer = QtCore.QTimer()
try:
import pkg_resources
except ImportError:
pass
else:
# Append proxy types defined in plugins
for ep in pkg_resources.iter_entry_points(
'bitmessage.proxyconfig'):
self.comboBoxProxyType.addItem(ep.name)
self.lineEditMaxOutboundConnections.setValidator(
QtGui.QIntValidator(0, 8, self.lineEditMaxOutboundConnections))
@ -47,20 +72,33 @@ class SettingsDialog(QtGui.QDialog):
def adjust_from_config(self, config):
"""Adjust all widgets state according to config settings"""
# pylint: disable=too-many-branches,too-many-statements
self.checkBoxStartOnLogon.setChecked(
config.getboolean('bitmessagesettings', 'startonlogon'))
if not self.parent.tray.isSystemTrayAvailable():
self.groupBoxTray.setEnabled(False)
self.groupBoxTray.setTitle(_translate(
"MainWindow", "Tray (not available in your system)"))
for setting in (
'minimizetotray', 'trayonclose', 'startintray'):
config.set('bitmessagesettings', setting, 'false')
else:
self.checkBoxMinimizeToTray.setChecked(
config.getboolean('bitmessagesettings', 'minimizetotray'))
self.checkBoxTrayOnClose.setChecked(
config.safeGetBoolean('bitmessagesettings', 'trayonclose'))
self.checkBoxHideTrayConnectionNotifications.setChecked(
config.getboolean("bitmessagesettings", "hidetrayconnectionnotifications"))
self.checkBoxShowTrayNotifications.setChecked(
config.getboolean('bitmessagesettings', 'showtraynotifications'))
self.checkBoxStartInTray.setChecked(
config.getboolean('bitmessagesettings', 'startintray'))
self.checkBoxHideTrayConnectionNotifications.setChecked(
config.getboolean(
'bitmessagesettings', 'hidetrayconnectionnotifications'))
self.checkBoxShowTrayNotifications.setChecked(
config.getboolean('bitmessagesettings', 'showtraynotifications'))
self.checkBoxStartOnLogon.setChecked(
config.getboolean('bitmessagesettings', 'startonlogon'))
self.checkBoxWillinglySendToMobile.setChecked(
config.safeGetBoolean('bitmessagesettings', 'willinglysendtomobile'))
config.safeGetBoolean(
'bitmessagesettings', 'willinglysendtomobile'))
self.checkBoxUseIdenticons.setChecked(
config.safeGetBoolean('bitmessagesettings', 'useidenticons'))
self.checkBoxReplyBelow.setChecked(
@ -82,10 +120,12 @@ class SettingsDialog(QtGui.QDialog):
"MainWindow", "Start-on-login not yet supported on your OS."))
self.checkBoxMinimizeToTray.setDisabled(True)
self.checkBoxMinimizeToTray.setText(_translate(
"MainWindow", "Minimize-to-tray not yet supported on your OS."))
"MainWindow",
"Minimize-to-tray not yet supported on your OS."))
self.checkBoxShowTrayNotifications.setDisabled(True)
self.checkBoxShowTrayNotifications.setText(_translate(
"MainWindow", "Tray notifications not yet supported on your OS."))
"MainWindow",
"Tray notifications not yet supported on your OS."))
elif 'linux' in sys.platform:
self.checkBoxStartOnLogon.setDisabled(True)
self.checkBoxStartOnLogon.setText(_translate(
@ -102,21 +142,11 @@ class SettingsDialog(QtGui.QDialog):
self.checkBoxOnionOnly.setChecked(
config.safeGetBoolean('bitmessagesettings', 'onionservicesonly'))
proxy_type = config.safeGet(
'bitmessagesettings', 'socksproxytype', 'none')
if proxy_type == 'none':
self.comboBoxProxyType.setCurrentIndex(0)
self.lineEditSocksHostname.setEnabled(False)
self.lineEditSocksPort.setEnabled(False)
self.lineEditSocksUsername.setEnabled(False)
self.lineEditSocksPassword.setEnabled(False)
self.checkBoxAuthentication.setEnabled(False)
self.checkBoxSocksListen.setEnabled(False)
self.checkBoxOnionOnly.setEnabled(False)
elif proxy_type == 'SOCKS4a':
self.comboBoxProxyType.setCurrentIndex(1)
elif proxy_type == 'SOCKS5':
self.comboBoxProxyType.setCurrentIndex(2)
self._proxy_type = getSOCKSProxyType(config)
self.comboBoxProxyType.setCurrentIndex(
0 if not self._proxy_type
else self.comboBoxProxyType.findText(self._proxy_type))
self.comboBoxProxyTypeChanged(self.comboBoxProxyType.currentIndex())
self.lineEditSocksHostname.setText(
config.get('bitmessagesettings', 'sockshostname'))
@ -204,7 +234,7 @@ class SettingsDialog(QtGui.QDialog):
self.checkBoxAuthentication.setEnabled(False)
self.checkBoxSocksListen.setEnabled(False)
self.checkBoxOnionOnly.setEnabled(False)
elif comboBoxIndex in (1, 2):
else:
self.lineEditSocksHostname.setEnabled(True)
self.lineEditSocksPort.setEnabled(True)
self.checkBoxAuthentication.setEnabled(True)
@ -306,27 +336,22 @@ class SettingsDialog(QtGui.QDialog):
upnpThread = upnp.uPnPThread()
upnpThread.start()
proxy_type = self.config.safeGet(
'bitmessagesettings', 'socksproxytype', 'none')
if (
proxy_type == 'none' and
self.comboBoxProxyType.currentText()[0:5] == 'SOCKS' and
shared.statusIconColor != 'red'
):
proxytype_index = self.comboBoxProxyType.currentIndex()
if proxytype_index == 0:
if self._proxy_type and shared.statusIconColor != 'red':
self.net_restart_needed = True
if (
proxy_type[0:5] == 'SOCKS' and
self.comboBoxProxyType.currentText()[0:5] != 'SOCKS'
):
elif self.comboBoxProxyType.currentText() != self._proxy_type:
self.net_restart_needed = True
self.parent.statusbar.clearMessage()
self.config.set(
'bitmessagesettings', 'socksproxytype',
str(self.comboBoxProxyType.currentText())
if self.comboBoxProxyType.currentText()[0:5] == 'SOCKS'
else 'none'
'none' if self.comboBoxProxyType.currentIndex() == 0
else str(self.comboBoxProxyType.currentText())
)
if proxytype_index > 2: # last literal proxytype in ui
start_proxyconfig()
self.config.set('bitmessagesettings', 'socksauthentication', str(
self.checkBoxAuthentication.isChecked()))
self.config.set('bitmessagesettings', 'sockshostname', str(

View File

@ -75,7 +75,7 @@
<string>Minimize to tray</string>
</property>
<property name="checked">
<bool>true</bool>
<bool>false</bool>
</property>
</widget>
</item>
@ -419,12 +419,12 @@
</item>
<item>
<property name="text">
<string>SOCKS4a</string>
<string notr="true">SOCKS4a</string>
</property>
</item>
<item>
<property name="text">
<string>SOCKS5</string>
<string notr="true">SOCKS5</string>
</property>
</item>
</widget>

View File

@ -15,6 +15,7 @@ from openclpow import openclAvailable, openclEnabled
import paths
import proofofwork
from pyelliptic.openssl import OpenSSL
from settings import getSOCKSProxyType
import queues
import network.stats
import state
@ -118,8 +119,7 @@ def createSupportMessage(myapp):
BMConfigParser().safeGet('bitmessagesettings', 'opencl')
) if openclEnabled() else "None"
locale = getTranslationLanguage()
socks = BMConfigParser().safeGet(
'bitmessagesettings', 'socksproxytype', "N/A")
socks = getSOCKSProxyType(BMConfigParser()) or "N/A"
upnp = BMConfigParser().safeGet('bitmessagesettings', 'upnp', "N/A")
connectedhosts = len(network.stats.connectedHostsList())

View File

@ -470,8 +470,8 @@ class singleWorker(StoppableThread):
def sendOnionPeerObj(self, peer=None):
"""Send onionpeer object representing peer"""
if not peer: # find own onionhostname
for peer_ in state.ownAddresses:
if peer_.host.endswith('.onion'):
for peer in state.ownAddresses:
if peer.host.endswith('.onion'):
break
else:
return

View File

@ -2,11 +2,12 @@
Startup operations.
"""
# pylint: disable=too-many-branches,too-many-statements
from __future__ import print_function
import logging
import os
import platform
import sys
import time
from distutils.version import StrictVersion
import defaults
@ -15,6 +16,13 @@ import paths
import state
from bmconfigparser import BMConfigParser
try:
from plugins.plugin import get_plugin
except ImportError:
get_plugin = None
logger = logging.getLogger('default')
# The user may de-select Portable Mode in the settings if they want
# the config files to stay in the application data folder.
@ -31,14 +39,14 @@ def loadConfig():
needToCreateKeysFile = config.safeGet(
'bitmessagesettings', 'settingsversion') is None
if not needToCreateKeysFile:
print(
logger.info(
'Loading config files from directory specified'
' on startup: %s' % state.appdata)
' on startup: %s', state.appdata)
else:
config.read(paths.lookupExeFolder() + 'keys.dat')
try:
config.get('bitmessagesettings', 'settingsversion')
print('Loading config files from same directory as program.')
logger.info('Loading config files from same directory as program.')
needToCreateKeysFile = False
state.appdata = paths.lookupExeFolder()
except:
@ -49,7 +57,8 @@ def loadConfig():
needToCreateKeysFile = config.safeGet(
'bitmessagesettings', 'settingsversion') is None
if not needToCreateKeysFile:
print('Loading existing config files from', state.appdata)
logger.info(
'Loading existing config files from %s', state.appdata)
if needToCreateKeysFile:
@ -104,9 +113,10 @@ def loadConfig():
# Just use the same directory as the program and forget about
# the appdata folder
state.appdata = ''
print('Creating new config files in same directory as program.')
logger.info(
'Creating new config files in same directory as program.')
else:
print('Creating new config files in', state.appdata)
logger.info('Creating new config files in %s', state.appdata)
if not os.path.exists(state.appdata):
os.makedirs(state.appdata)
if not sys.platform.startswith('win'):
@ -256,7 +266,7 @@ def updateConfig():
'bitmessagesettings', 'hidetrayconnectionnotifications', 'false')
if config.safeGetInt('bitmessagesettings', 'maxoutboundconnections') < 1:
config.set('bitmessagesettings', 'maxoutboundconnections', '8')
print('WARNING: your maximum outbound connections must be a number.')
logger.warning('Your maximum outbound connections must be a number.')
# TTL is now user-specifiable. Let's add an option to save
# whatever the user selects.
@ -279,3 +289,26 @@ def isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections():
return False
except Exception:
pass
def start_proxyconfig():
"""Check socksproxytype and start any proxy configuration plugin"""
if not get_plugin:
return
config = BMConfigParser()
proxy_type = config.safeGet('bitmessagesettings', 'socksproxytype')
if proxy_type and proxy_type not in ('none', 'SOCKS4a', 'SOCKS5'):
try:
proxyconfig_start = time.time()
if not get_plugin('proxyconfig', name=proxy_type)(config):
raise TypeError()
except TypeError:
# cannot import shutdown here ):
logger.error(
'Failed to run proxy config plugin %s',
proxy_type, exc_info=True)
os._exit(0) # pylint: disable=protected-access
else:
logger.info(
'Started proxy config plugin %s in %s sec',
proxy_type, time.time() - proxyconfig_start)

View File

@ -146,6 +146,9 @@ class UDPSocket(BMProto): # pylint: disable=too-many-instance-attributes
retval = self.socket.sendto(
self.write_buf, ('<broadcast>', self.port))
except socket.error as e:
logger.error("socket error on sendato: %s", e)
logger.error("socket error on sendto: %s", e)
if e.errno == 101:
self.announcing = False
self.socket.close()
retval = 0
self.slice_write_buf(retval)

View File

@ -0,0 +1,7 @@
"""
Simple plugin system based on setuptools
----------------------------------------
"""
# .. include:: pybitmessage.plugins.plugin.rst

View File

@ -1,19 +1,28 @@
# -*- coding: utf-8 -*-
"""
src/plugins/plugin.py
===================================
Operating with plugins
"""
import logging
import pkg_resources
logger = logging.getLogger('default')
def get_plugins(group, point='', name=None, fallback=None):
"""
Iterate through plugins (`connect_plugin` attribute of entry point)
which name starts with `point` or equals to `name`.
If `fallback` kwarg specified, plugin with that name yield last.
:param str group: plugin group
:param str point: plugin name prefix
:param name: exact plugin name
:param fallback: fallback plugin name
Iterate through plugins (``connect_plugin`` attribute of entry point)
which name starts with ``point`` or equals to ``name``.
If ``fallback`` kwarg specified, plugin with that name yield last.
"""
for ep in pkg_resources.iter_entry_points('bitmessage.' + group):
if name and ep.name == name or ep.name.startswith(point):
if name and ep.name == name or not point or ep.name.startswith(point):
try:
plugin = ep.load().connect_plugin
if ep.name == fallback:
@ -25,6 +34,8 @@ def get_plugins(group, point='', name=None, fallback=None):
ValueError,
pkg_resources.DistributionNotFound,
pkg_resources.UnknownExtra):
logger.debug(
'Problem while loading %s', ep.name, exc_info=True)
continue
try:
yield _fallback
@ -33,6 +44,8 @@ def get_plugins(group, point='', name=None, fallback=None):
def get_plugin(*args, **kwargs):
"""Returns first available plugin `from get_plugins()` if any."""
"""
:return: first available plugin from :func:`get_plugins` if any.
"""
for plugin in get_plugins(*args, **kwargs):
return plugin

View File

@ -1,7 +1,15 @@
# -*- coding: utf-8 -*-
"""
src/plugins/proxyconfig_stem.py
===================================
Configure tor proxy and hidden service with
`stem <https://stem.torproject.org/>`_ depending on *bitmessagesettings*:
* try to start own tor instance on *socksport* if *sockshostname*
is unset or set to localhost;
* if *socksport* is already in use that instance is used only for
hidden service (if *sockslisten* is also set True);
* create ephemeral hidden service v3 if there is already *onionhostname*;
* otherwise use stem's 'BEST' version and save onion keys to the new
section using *onionhostname* as name for future use.
"""
import os
import logging
@ -36,13 +44,20 @@ class DebugLogger(object):
def connect_plugin(config): # pylint: disable=too-many-branches
"""Run stem proxy configurator"""
"""
Run stem proxy configurator
:param config: current configuration instance
:type config: :class:`pybitmessage.bmconfigparser.BMConfigParser`
:return: True if configuration was done successfully
"""
logwrite = DebugLogger()
if config.safeGet('bitmessagesettings', 'sockshostname') not in (
if config.safeGet('bitmessagesettings', 'sockshostname', '') not in (
'localhost', '127.0.0.1', ''
):
# remote proxy is choosen for outbound connections,
# nothing to do here, but need to set socksproxytype to SOCKS5!
config.set('bitmessagesettings', 'socksproxytype', 'SOCKS5')
logwrite(
'sockshostname is set to remote address,'
' aborting stem proxy configuration')
@ -77,6 +92,8 @@ def connect_plugin(config): # pylint: disable=too-many-branches
logwrite('Started tor on port %s' % port)
break
config.setTemp('bitmessagesettings', 'socksproxytype', 'SOCKS5')
if config.safeGetBoolean('bitmessagesettings', 'sockslisten'):
# need a hidden service for inbound connections
try:
@ -95,7 +112,8 @@ def connect_plugin(config): # pylint: disable=too-many-branches
onionkeytype = config.safeGet(onionhostname, 'keytype')
response = controller.create_ephemeral_hidden_service(
config.safeGetInt('bitmessagesettings', 'onionport', 8444),
{config.safeGetInt('bitmessagesettings', 'onionport', 8444):
config.safeGetInt('bitmessagesettings', 'port', 8444)},
key_type=(onionkeytype or 'NEW'),
key_content=(onionkey or onionhostname and 'ED25519-V3' or 'BEST')
)
@ -117,6 +135,5 @@ def connect_plugin(config): # pylint: disable=too-many-branches
config.set(
onionhostname, 'keytype', response.private_key_type)
config.save()
config.set('bitmessagesettings', 'socksproxytype', 'SOCKS5')
return True

View File

@ -15,14 +15,20 @@ import knownnodes
import state
from bmconfigparser import BMConfigParser
from helper_msgcoding import MsgEncode, MsgDecode
from helper_startup import start_proxyconfig
from network import asyncore_pollchoose as asyncore
from network.connectionpool import BMConnectionPool
from network.node import Peer
from network.tcp import Socks4aBMConnection, Socks5BMConnection, TCPConnection
from queues import excQueue
try:
import stem.version as stem_version
except ImportError:
stem_version = None
knownnodes_file = os.path.join(state.appdata, 'knownnodes.dat')
program = None
def pickle_knownnodes():
@ -170,12 +176,29 @@ class TestCore(unittest.TestCase):
self.assertIsInstance(con, connection_base)
self.assertNotEqual(peer.host, '127.0.0.1')
return
else: # pylint: disable=useless-else-on-loop
self.fail(
'Failed to connect during %s sec' % (time.time() - _started))
def test_onionservicesonly(self):
"""test onionservicesonly networking mode"""
def test_bootstrap(self):
"""test bootstrapping"""
self._initiate_bootstrap()
self._check_bootstrap()
@unittest.skipUnless(stem_version, 'No stem, skipping tor dependent test')
def test_bootstrap_tor(self):
"""test bootstrapping with tor"""
self._initiate_bootstrap()
BMConfigParser().set('bitmessagesettings', 'socksproxytype', 'stem')
start_proxyconfig()
self._check_bootstrap()
@unittest.skipUnless(stem_version, 'No stem, skipping tor dependent test')
def test_onionservicesonly(self): # this should start after bootstrap
"""
set onionservicesonly, wait for 3 connections and check them all
are onions
"""
BMConfigParser().set('bitmessagesettings', 'socksproxytype', 'SOCKS5')
BMConfigParser().set('bitmessagesettings', 'onionservicesonly', 'true')
self._initiate_bootstrap()
BMConfigParser().remove_option('bitmessagesettings', 'dontconnect')
@ -184,26 +207,18 @@ class TestCore(unittest.TestCase):
for n, peer in enumerate(BMConnectionPool().outboundConnections):
if n > 2:
return
if not peer.host.endswith('.onion'):
if (
not peer.host.endswith('.onion')
and not peer.host.startswith('bootstrap')
):
self.fail(
'Found non onion hostname %s in outbound connections!'
% peer.host)
self.fail('Failed to connect to at least 3 nodes within 360 sec')
def test_bootstrap(self):
"""test bootstrapping"""
self._initiate_bootstrap()
self._check_bootstrap()
self._initiate_bootstrap()
BMConfigParser().set('bitmessagesettings', 'socksproxytype', 'stem')
program.start_proxyconfig(BMConfigParser())
self._check_bootstrap()
def run(prog):
def run():
"""Starts all tests defined in this module"""
global program # pylint: disable=global-statement
program = prog
loader = unittest.TestLoader()
loader.sortTestMethodsUsing = None
suite = loader.loadTestsFromTestCase(TestCore)