Runnable with both Python3 and Python2, with both PyQt5 and PyQt4 by using Qt.py #2250
|
@ -4,7 +4,11 @@ export APPIMAGE_EXTRACT_AND_RUN=1
|
||||||
BUILDER=appimage-builder-x86_64.AppImage
|
BUILDER=appimage-builder-x86_64.AppImage
|
||||||
RECIPE=packages/AppImage/AppImageBuilder.yml
|
RECIPE=packages/AppImage/AppImageBuilder.yml
|
||||||
|
|
||||||
|
git remote add -f upstream https://github.com/Bitmessage/PyBitmessage.git
|
||||||
|
HEAD="$(git rev-parse HEAD)"
|
||||||
|
UPSTREAM="$(git merge-base --fork-point upstream/v0.6)"
|
||||||
export APP_VERSION=$(git describe --tags | cut -d- -f1,3 | tr -d v)
|
export APP_VERSION=$(git describe --tags | cut -d- -f1,3 | tr -d v)
|
||||||
|
[ $HEAD != $UPSTREAM ] && APP_VERSION="${APP_VERSION}-alpha"
|
||||||
|
|
||||||
function set_sourceline {
|
function set_sourceline {
|
||||||
if [ ${ARCH} == amd64 ]; then
|
if [ ${ARCH} == amd64 ]; then
|
||||||
|
|
33
INSTALL.md
33
INSTALL.md
|
@ -1,12 +1,43 @@
|
||||||
# PyBitmessage Installation Instructions
|
# PyBitmessage Installation Instructions
|
||||||
- Binary (64bit, no separate installation of dependencies required)
|
- Binary (64bit, no separate installation of dependencies required)
|
||||||
- Windows: https://download.bitmessage.org/snapshots/
|
- Windows: https://artifacts.bitmessage.at/winebuild/
|
||||||
- Linux AppImages: https://artifacts.bitmessage.at/appimage/
|
- Linux AppImages: https://artifacts.bitmessage.at/appimage/
|
||||||
- Linux snaps: https://artifacts.bitmessage.at/snap/
|
- Linux snaps: https://artifacts.bitmessage.at/snap/
|
||||||
- Mac (not up to date): https://github.com/Bitmessage/PyBitmessage/releases/tag/v0.6.1
|
- Mac (not up to date): https://github.com/Bitmessage/PyBitmessage/releases/tag/v0.6.1
|
||||||
- Source
|
- Source
|
||||||
`git clone git://github.com/Bitmessage/PyBitmessage.git`
|
`git clone git://github.com/Bitmessage/PyBitmessage.git`
|
||||||
|
|
||||||
|
## Notes on the AppImages
|
||||||
|
|
||||||
|
The [AppImage](https://docs.appimage.org/introduction/index.html)
|
||||||
|
is a bundle, built by the
|
||||||
|
[appimage-builder](https://github.com/AppImageCrafters/appimage-builder) from
|
||||||
|
the Ubuntu Bionic deb files, the sources and `bitmsghash.so`, precompiled for
|
||||||
|
3 architectures, using the `packages/AppImage/AppImageBuilder.yml` recipe.
|
||||||
|
|
||||||
|
When you run the appimage the bundle is loop mounted to a location like
|
||||||
|
`/tmp/.mount_PyBitm97wj4K` with `squashfs-tools`.
|
||||||
|
|
||||||
|
The appimage name has several informational filds:
|
||||||
|
```
|
||||||
|
PyBitmessage-<VERSION>-g<COMMITHASH>[-alpha]-<ARCH>.AppImage
|
||||||
|
```
|
||||||
|
|
||||||
|
E.g. `PyBitmessage-0.6.3.2-ge571ba8a-x86_64.AppImage` is an appimage, built from
|
||||||
|
the `v0.6` for x86_64 and `PyBitmessage-0.6.3.2-g9de2aaf1-alpha-aarch64.AppImage`
|
||||||
|
is one, built from some development branch for arm64.
|
||||||
|
|
||||||
|
You can also build the appimage with local code. For that you need installed
|
||||||
|
docker:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ docker build -t bm-appimage -f .buildbot/appimage/Dockerfile .
|
||||||
|
$ docker run -t --rm -v "$(pwd)"/dist:/out bm-appimage .buildbot/appimage/build.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The appimages should be in the dist dir.
|
||||||
|
|
||||||
|
|
||||||
## Helper Script for building from source
|
## Helper Script for building from source
|
||||||
Go to the directory with PyBitmessage source code and run:
|
Go to the directory with PyBitmessage source code and run:
|
||||||
```
|
```
|
||||||
|
|
|
@ -12,6 +12,7 @@ The PyBitmessage startup script
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import pathmagic
|
import pathmagic
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -156,13 +157,6 @@ class Main(object):
|
||||||
|
|
||||||
set_thread_name("PyBitmessage")
|
set_thread_name("PyBitmessage")
|
||||||
|
|
||||||
state.dandelion_enabled = config.safeGetInt('network', 'dandelion')
|
|
||||||
# dandelion requires outbound connections, without them,
|
|
||||||
# stem objects will get stuck forever
|
|
||||||
if state.dandelion_enabled and not config.safeGetBoolean(
|
|
||||||
'bitmessagesettings', 'sendoutgoingconnections'):
|
|
||||||
state.dandelion_enabled = 0
|
|
||||||
|
|
||||||
if state.testmode or config.safeGetBoolean(
|
if state.testmode or config.safeGetBoolean(
|
||||||
'bitmessagesettings', 'extralowdifficulty'):
|
'bitmessagesettings', 'extralowdifficulty'):
|
||||||
defaults.networkDefaultProofOfWorkNonceTrialsPerByte = int(
|
defaults.networkDefaultProofOfWorkNonceTrialsPerByte = int(
|
||||||
|
|
|
@ -69,6 +69,9 @@ except ImportError:
|
||||||
get_plugins = False
|
get_plugins = False
|
||||||
|
|
||||||
|
|
||||||
|
is_windows = sys.platform.startswith('win')
|
||||||
|
|
||||||
|
|
||||||
# TODO: rewrite
|
# TODO: rewrite
|
||||||
def powQueueSize():
|
def powQueueSize():
|
||||||
"""Returns the size of queues.workerQueue including current unfinished work"""
|
"""Returns the size of queues.workerQueue including current unfinished work"""
|
||||||
|
@ -87,7 +90,7 @@ def openKeysFile():
|
||||||
keysfile = os.path.join(state.appdata, 'keys.dat')
|
keysfile = os.path.join(state.appdata, 'keys.dat')
|
||||||
if 'linux' in sys.platform:
|
if 'linux' in sys.platform:
|
||||||
subprocess.call(["xdg-open", keysfile])
|
subprocess.call(["xdg-open", keysfile])
|
||||||
elif sys.platform.startswith('win'):
|
elif is_windows:
|
||||||
os.startfile(keysfile) # pylint: disable=no-member
|
os.startfile(keysfile) # pylint: disable=no-member
|
||||||
|
|
||||||
|
|
||||||
|
@ -885,7 +888,7 @@ class MyForm(settingsmixin.SMainWindow):
|
||||||
"""
|
"""
|
||||||
startonlogon = config.safeGetBoolean(
|
startonlogon = config.safeGetBoolean(
|
||||||
'bitmessagesettings', 'startonlogon')
|
'bitmessagesettings', 'startonlogon')
|
||||||
if sys.platform.startswith('win'): # Auto-startup for Windows
|
if is_windows: # Auto-startup for Windows
|
||||||
RUN_PATH = "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run"
|
RUN_PATH = "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run"
|
||||||
settings = QtCore.QSettings(
|
settings = QtCore.QSettings(
|
||||||
RUN_PATH, QtCore.QSettings.NativeFormat)
|
RUN_PATH, QtCore.QSettings.NativeFormat)
|
||||||
|
@ -4328,6 +4331,14 @@ class BitmessageQtApplication(QtGui.QApplication):
|
||||||
# Unique identifier for this application
|
# Unique identifier for this application
|
||||||
uuid = '6ec0149b-96e1-4be1-93ab-1465fb3ebf7c'
|
uuid = '6ec0149b-96e1-4be1-93ab-1465fb3ebf7c'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_windowstyle():
|
||||||
|
"""Get window style set in config or default"""
|
||||||
|
return config.safeGet(
|
||||||
|
'bitmessagesettings', 'windowstyle',
|
||||||
|
'Windows' if is_windows else 'GTK+'
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, *argv):
|
def __init__(self, *argv):
|
||||||
super(BitmessageQtApplication, self).__init__(*argv)
|
super(BitmessageQtApplication, self).__init__(*argv)
|
||||||
id = BitmessageQtApplication.uuid
|
id = BitmessageQtApplication.uuid
|
||||||
|
@ -4336,6 +4347,14 @@ class BitmessageQtApplication(QtGui.QApplication):
|
||||||
QtCore.QCoreApplication.setOrganizationDomain("bitmessage.org")
|
QtCore.QCoreApplication.setOrganizationDomain("bitmessage.org")
|
||||||
QtCore.QCoreApplication.setApplicationName("pybitmessageqt")
|
QtCore.QCoreApplication.setApplicationName("pybitmessageqt")
|
||||||
|
|
||||||
|
self.setStyle(self.get_windowstyle())
|
||||||
|
|
||||||
|
font = config.safeGet('bitmessagesettings', 'font')
|
||||||
|
if font:
|
||||||
|
# family, size, weight = font.split(',')
|
||||||
|
family, size = font.split(',')
|
||||||
|
self.setFont(QtGui.QFont(family, int(size)))
|
||||||
|
|
||||||
self.server = None
|
self.server = None
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,8 @@ class Ui_MainWindow(object):
|
||||||
self.tabWidget.setMinimumSize(QtCore.QSize(0, 0))
|
self.tabWidget.setMinimumSize(QtCore.QSize(0, 0))
|
||||||
self.tabWidget.setBaseSize(QtCore.QSize(0, 0))
|
self.tabWidget.setBaseSize(QtCore.QSize(0, 0))
|
||||||
font = QtGui.QFont()
|
font = QtGui.QFont()
|
||||||
font.setPointSize(9)
|
base_size = QtGui.QApplication.instance().font().pointSize()
|
||||||
|
font.setPointSize(int(base_size * 0.75))
|
||||||
self.tabWidget.setFont(font)
|
self.tabWidget.setFont(font)
|
||||||
self.tabWidget.setTabPosition(QtGui.QTabWidget.North)
|
self.tabWidget.setTabPosition(QtGui.QTabWidget.North)
|
||||||
self.tabWidget.setTabShape(QtGui.QTabWidget.Rounded)
|
self.tabWidget.setTabShape(QtGui.QTabWidget.Rounded)
|
||||||
|
|
|
@ -48,14 +48,17 @@ def getSOCKSProxyType(config):
|
||||||
|
|
||||||
class SettingsDialog(QtGui.QDialog):
|
class SettingsDialog(QtGui.QDialog):
|
||||||
"""The "Settings" dialog"""
|
"""The "Settings" dialog"""
|
||||||
|
# pylint: disable=too-many-instance-attributes
|
||||||
def __init__(self, parent=None, firstrun=False):
|
def __init__(self, parent=None, firstrun=False):
|
||||||
super(SettingsDialog, self).__init__(parent)
|
super(SettingsDialog, self).__init__(parent)
|
||||||
widgets.load('settings.ui', self)
|
widgets.load('settings.ui', self)
|
||||||
|
|
||||||
|
self.app = QtGui.QApplication.instance()
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.firstrun = firstrun
|
self.firstrun = firstrun
|
||||||
self.config = config_obj
|
self.config = config_obj
|
||||||
self.net_restart_needed = False
|
self.net_restart_needed = False
|
||||||
|
self.font_setting = None
|
||||||
self.timer = QtCore.QTimer()
|
self.timer = QtCore.QTimer()
|
||||||
|
|
||||||
if self.config.safeGetBoolean('bitmessagesettings', 'dontconnect'):
|
if self.config.safeGetBoolean('bitmessagesettings', 'dontconnect'):
|
||||||
|
@ -92,6 +95,15 @@ class SettingsDialog(QtGui.QDialog):
|
||||||
def adjust_from_config(self, config):
|
def adjust_from_config(self, config):
|
||||||
"""Adjust all widgets state according to config settings"""
|
"""Adjust all widgets state according to config settings"""
|
||||||
# pylint: disable=too-many-branches,too-many-statements
|
# pylint: disable=too-many-branches,too-many-statements
|
||||||
|
|
||||||
|
current_style = self.app.get_windowstyle()
|
||||||
|
for i, sk in enumerate(QtGui.QStyleFactory.keys()):
|
||||||
|
self.comboBoxStyle.addItem(sk)
|
||||||
|
if sk == current_style:
|
||||||
|
self.comboBoxStyle.setCurrentIndex(i)
|
||||||
|
|
||||||
|
self.save_font_setting(self.app.font())
|
||||||
|
|
||||||
if not self.parent.tray.isSystemTrayAvailable():
|
if not self.parent.tray.isSystemTrayAvailable():
|
||||||
self.groupBoxTray.setEnabled(False)
|
self.groupBoxTray.setEnabled(False)
|
||||||
self.groupBoxTray.setTitle(_translate(
|
self.groupBoxTray.setTitle(_translate(
|
||||||
|
@ -144,7 +156,7 @@ class SettingsDialog(QtGui.QDialog):
|
||||||
"MainWindow",
|
"MainWindow",
|
||||||
"Tray notifications not yet supported on your OS."))
|
"Tray notifications not yet supported on your OS."))
|
||||||
|
|
||||||
if 'win' not in sys.platform and not self.parent.desktop:
|
if not sys.platform.startswith('win') and not self.parent.desktop:
|
||||||
self.checkBoxStartOnLogon.setDisabled(True)
|
self.checkBoxStartOnLogon.setDisabled(True)
|
||||||
self.checkBoxStartOnLogon.setText(_translate(
|
self.checkBoxStartOnLogon.setText(_translate(
|
||||||
"MainWindow", "Start-on-login not yet supported on your OS."))
|
"MainWindow", "Start-on-login not yet supported on your OS."))
|
||||||
|
@ -329,6 +341,18 @@ class SettingsDialog(QtGui.QDialog):
|
||||||
if status == 'success':
|
if status == 'success':
|
||||||
self.parent.namecoin = nc
|
self.parent.namecoin = nc
|
||||||
|
|
||||||
|
def save_font_setting(self, font):
|
||||||
|
"""Save user font setting and set the buttonFont text"""
|
||||||
|
font_setting = (font.family(), font.pointSize())
|
||||||
|
self.buttonFont.setText('{} {}'.format(*font_setting))
|
||||||
|
self.font_setting = '{},{}'.format(*font_setting)
|
||||||
|
|
||||||
|
def choose_font(self):
|
||||||
|
"""Show the font selection dialog"""
|
||||||
|
font, valid = QtGui.QFontDialog.getFont()
|
||||||
|
if valid:
|
||||||
|
self.save_font_setting(font)
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
"""A callback for accepted event of buttonBox (OK button pressed)"""
|
"""A callback for accepted event of buttonBox (OK button pressed)"""
|
||||||
# pylint: disable=too-many-branches,too-many-statements
|
# pylint: disable=too-many-branches,too-many-statements
|
||||||
|
@ -355,6 +379,20 @@ class SettingsDialog(QtGui.QDialog):
|
||||||
self.config.set('bitmessagesettings', 'replybelow', str(
|
self.config.set('bitmessagesettings', 'replybelow', str(
|
||||||
self.checkBoxReplyBelow.isChecked()))
|
self.checkBoxReplyBelow.isChecked()))
|
||||||
|
|
||||||
|
window_style = str(self.comboBoxStyle.currentText())
|
||||||
|
if self.app.get_windowstyle() != window_style or self.config.safeGet(
|
||||||
|
'bitmessagesettings', 'font'
|
||||||
|
) != self.font_setting:
|
||||||
|
self.config.set('bitmessagesettings', 'windowstyle', window_style)
|
||||||
|
self.config.set('bitmessagesettings', 'font', self.font_setting)
|
||||||
|
queues.UISignalQueue.put((
|
||||||
|
'updateStatusBar', (
|
||||||
|
_translate(
|
||||||
|
"MainWindow",
|
||||||
|
"You need to restart the application to apply"
|
||||||
|
" the window style or default font."), 1)
|
||||||
|
))
|
||||||
|
|
||||||
lang = str(self.languageComboBox.itemData(
|
lang = str(self.languageComboBox.itemData(
|
||||||
self.languageComboBox.currentIndex()))
|
self.languageComboBox.currentIndex()))
|
||||||
self.config.set('bitmessagesettings', 'userlocale', lang)
|
self.config.set('bitmessagesettings', 'userlocale', lang)
|
||||||
|
|
|
@ -147,6 +147,32 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="9" column="0">
|
||||||
|
<widget class="QGroupBox" name="groupBoxStyle">
|
||||||
|
<property name="title">
|
||||||
|
<string>Custom Style</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QHBoxLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="comboBoxStyle">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>100</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="buttonFont">
|
||||||
|
<property name="text">
|
||||||
|
<string>Font</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item row="9" column="1">
|
<item row="9" column="1">
|
||||||
<widget class="QGroupBox" name="groupBox">
|
<widget class="QGroupBox" name="groupBox">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
|
@ -1202,5 +1228,11 @@
|
||||||
</hint>
|
</hint>
|
||||||
</hints>
|
</hints>
|
||||||
</connection>
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonFont</sender>
|
||||||
|
<signal>clicked()</signal>
|
||||||
|
<receiver>settingsDialog</receiver>
|
||||||
|
<slot>choose_font</slot>
|
||||||
|
</connection>
|
||||||
</connections>
|
</connections>
|
||||||
</ui>
|
</ui>
|
||||||
|
|
|
@ -5,15 +5,20 @@ import sys
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from PyQt4 import QtCore, QtGui
|
from PyQt4 import QtCore, QtGui
|
||||||
|
from six.moves import queue
|
||||||
|
|
||||||
import bitmessageqt
|
import bitmessageqt
|
||||||
import queues
|
from bitmessageqt import _translate, config, queues
|
||||||
from tr import _translate
|
|
||||||
|
|
||||||
|
|
||||||
class TestBase(unittest.TestCase):
|
class TestBase(unittest.TestCase):
|
||||||
"""Base class for bitmessageqt test case"""
|
"""Base class for bitmessageqt test case"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
"""Provide the UI test cases with common settings"""
|
||||||
|
cls.config = config
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.app = (
|
self.app = (
|
||||||
QtGui.QApplication.instance()
|
QtGui.QApplication.instance()
|
||||||
|
@ -24,14 +29,21 @@ class TestBase(unittest.TestCase):
|
||||||
self.window.appIndicatorInit(self.app)
|
self.window.appIndicatorInit(self.app)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
"""Search for exceptions in closures called by timer and fail if any"""
|
||||||
# self.app.deleteLater()
|
# self.app.deleteLater()
|
||||||
|
concerning = []
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
thread, exc = queues.excQueue.get(block=False)
|
thread, exc = queues.excQueue.get(block=False)
|
||||||
except Queue.Empty:
|
except queue.Empty:
|
||||||
return
|
break
|
||||||
if thread == 'tests':
|
if thread == 'tests':
|
||||||
self.fail('Exception in the main thread: %s' % exc)
|
concerning.append(exc)
|
||||||
|
if concerning:
|
||||||
|
self.fail(
|
||||||
|
'Exceptions found in the main thread:\n%s' % '\n'.join((
|
||||||
|
str(e) for e in concerning
|
||||||
|
)))
|
||||||
|
|
||||||
|
|
||||||
class TestMain(unittest.TestCase):
|
class TestMain(unittest.TestCase):
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
|
"""Tests for PyBitmessage settings"""
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from .main import TestBase
|
from PyQt4 import QtCore, QtGui, QtTest
|
||||||
|
|
||||||
from bmconfigparser import config
|
from bmconfigparser import config
|
||||||
from bitmessageqt import settings
|
from bitmessageqt import settings
|
||||||
|
|
||||||
|
from .main import TestBase
|
||||||
|
|
||||||
|
|
||||||
class TestSettings(TestBase):
|
class TestSettings(TestBase):
|
||||||
"""A test case for the "Settings" dialog"""
|
"""A test case for the "Settings" dialog"""
|
||||||
|
@ -14,8 +18,7 @@ class TestSettings(TestBase):
|
||||||
|
|
||||||
def test_udp(self):
|
def test_udp(self):
|
||||||
"""Test the effect of checkBoxUDP"""
|
"""Test the effect of checkBoxUDP"""
|
||||||
udp_setting = config.safeGetBoolean(
|
udp_setting = config.safeGetBoolean('bitmessagesettings', 'udp')
|
||||||
'bitmessagesettings', 'udp')
|
|
||||||
self.assertEqual(udp_setting, self.dialog.checkBoxUDP.isChecked())
|
self.assertEqual(udp_setting, self.dialog.checkBoxUDP.isChecked())
|
||||||
self.dialog.checkBoxUDP.setChecked(not udp_setting)
|
self.dialog.checkBoxUDP.setChecked(not udp_setting)
|
||||||
self.dialog.accept()
|
self.dialog.accept()
|
||||||
|
@ -32,3 +35,44 @@ class TestSettings(TestBase):
|
||||||
else:
|
else:
|
||||||
if not udp_setting:
|
if not udp_setting:
|
||||||
self.fail('No Announcer thread found while udp set to True')
|
self.fail('No Announcer thread found while udp set to True')
|
||||||
|
|
||||||
|
def test_styling(self):
|
||||||
|
"""Test custom windows style and font"""
|
||||||
|
style_setting = config.safeGet('bitmessagesettings', 'windowstyle')
|
||||||
|
font_setting = config.safeGet('bitmessagesettings', 'font')
|
||||||
|
self.assertIs(style_setting, None)
|
||||||
|
self.assertIs(font_setting, None)
|
||||||
|
style_control = self.dialog.comboBoxStyle
|
||||||
|
self.assertEqual(
|
||||||
|
style_control.currentText(), self.app.get_windowstyle())
|
||||||
|
|
||||||
|
def call_font_dialog():
|
||||||
|
"""A function to get the open font dialog and accept it"""
|
||||||
|
font_dialog = QtGui.QApplication.activeModalWidget()
|
||||||
|
self.assertTrue(isinstance(font_dialog, QtGui.QFontDialog))
|
||||||
|
selected_font = font_dialog.currentFont()
|
||||||
|
self.assertEqual(
|
||||||
|
config.safeGet('bitmessagesettings', 'font'), '{},{}'.format(
|
||||||
|
selected_font.family(), selected_font.pointSize()))
|
||||||
|
|
||||||
|
font_dialog.accept()
|
||||||
|
self.dialog.accept()
|
||||||
|
self.assertEqual(
|
||||||
|
config.safeGet('bitmessagesettings', 'windowstyle'),
|
||||||
|
style_control.currentText())
|
||||||
|
|
||||||
|
def click_font_button():
|
||||||
|
"""Use QtTest to click the button"""
|
||||||
|
QtTest.QTest.mouseClick(
|
||||||
|
self.dialog.buttonFont, QtCore.Qt.LeftButton)
|
||||||
|
|
||||||
|
style_count = style_control.count()
|
||||||
|
self.assertGreater(style_count, 1)
|
||||||
|
for i in range(style_count):
|
||||||
|
if i != style_control.currentIndex():
|
||||||
|
style_control.setCurrentIndex(i)
|
||||||
|
break
|
||||||
|
|
||||||
|
QtCore.QTimer.singleShot(30, click_font_button)
|
||||||
|
QtCore.QTimer.singleShot(60, call_font_dialog)
|
||||||
|
time.sleep(2)
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
"""
|
"""
|
||||||
Network subsystem package
|
Network subsystem package
|
||||||
"""
|
"""
|
||||||
|
from .dandelion import Dandelion
|
||||||
from .threads import StoppableThread
|
from .threads import StoppableThread
|
||||||
|
|
||||||
|
dandelion_ins = Dandelion()
|
||||||
|
|
||||||
__all__ = ["StoppableThread"]
|
__all__ = ["StoppableThread"]
|
||||||
|
|
||||||
|
@ -21,6 +22,11 @@ def start(config, state):
|
||||||
from .receivequeuethread import ReceiveQueueThread
|
from .receivequeuethread import ReceiveQueueThread
|
||||||
from .uploadthread import UploadThread
|
from .uploadthread import UploadThread
|
||||||
|
|
||||||
|
# check and set dandelion enabled value at network startup
|
||||||
|
dandelion_ins.init_dandelion_enabled(config)
|
||||||
|
# pass pool instance into dandelion class instance
|
||||||
|
dandelion_ins.init_pool(connectionpool.pool)
|
||||||
|
|
||||||
readKnownNodes()
|
readKnownNodes()
|
||||||
connectionpool.pool.connectToStream(1)
|
connectionpool.pool.connectToStream(1)
|
||||||
for thread in (
|
for thread in (
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
"""
|
"""
|
||||||
Announce addresses as they are received from other hosts
|
Announce addresses as they are received from other hosts
|
||||||
"""
|
"""
|
||||||
|
import random
|
||||||
from six.moves import queue
|
from six.moves import queue
|
||||||
|
|
||||||
# magic imports!
|
# magic imports!
|
||||||
from network import connectionpool
|
from network import connectionpool
|
||||||
from helper_random import randomshuffle
|
|
||||||
from protocol import assembleAddrMessage
|
from protocol import assembleAddrMessage
|
||||||
from queues import addrQueue # FIXME: init with queue
|
from queues import addrQueue # FIXME: init with queue
|
||||||
|
|
||||||
|
@ -29,9 +29,9 @@ class AddrThread(StoppableThread):
|
||||||
if chunk:
|
if chunk:
|
||||||
# Choose peers randomly
|
# Choose peers randomly
|
||||||
connections = connectionpool.pool.establishedConnections()
|
connections = connectionpool.pool.establishedConnections()
|
||||||
randomshuffle(connections)
|
random.shuffle(connections)
|
||||||
for i in connections:
|
for i in connections:
|
||||||
randomshuffle(chunk)
|
random.shuffle(chunk)
|
||||||
filtered = []
|
filtered = []
|
||||||
for stream, peer, seen, destination in chunk:
|
for stream, peer, seen, destination in chunk:
|
||||||
# peer's own address or address received from peer
|
# peer's own address or address received from peer
|
||||||
|
|
|
@ -9,6 +9,7 @@ Basic infrastructure for asynchronous socket service clients and servers.
|
||||||
import os
|
import os
|
||||||
import select
|
import select
|
||||||
import socket
|
import socket
|
||||||
|
import random
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import warnings
|
import warnings
|
||||||
|
@ -20,7 +21,6 @@ from errno import (
|
||||||
from threading import current_thread
|
from threading import current_thread
|
||||||
from six.moves.reprlib import repr
|
from six.moves.reprlib import repr
|
||||||
|
|
||||||
import helper_random
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from errno import WSAEWOULDBLOCK
|
from errno import WSAEWOULDBLOCK
|
||||||
|
@ -234,13 +234,13 @@ def select_poller(timeout=0.0, map=None):
|
||||||
if err.args[0] in (WSAENOTSOCK, ):
|
if err.args[0] in (WSAENOTSOCK, ):
|
||||||
return
|
return
|
||||||
|
|
||||||
for fd in helper_random.randomsample(r, len(r)):
|
for fd in random.sample(r, len(r)):
|
||||||
obj = map.get(fd)
|
obj = map.get(fd)
|
||||||
if obj is None:
|
if obj is None:
|
||||||
continue
|
continue
|
||||||
read(obj)
|
read(obj)
|
||||||
|
|
||||||
for fd in helper_random.randomsample(w, len(w)):
|
for fd in random.sample(w, len(w)):
|
||||||
obj = map.get(fd)
|
obj = map.get(fd)
|
||||||
if obj is None:
|
if obj is None:
|
||||||
continue
|
continue
|
||||||
|
@ -298,7 +298,7 @@ def poll_poller(timeout=0.0, map=None):
|
||||||
except socket.error as err:
|
except socket.error as err:
|
||||||
if err.args[0] in (EBADF, WSAENOTSOCK, EINTR):
|
if err.args[0] in (EBADF, WSAENOTSOCK, EINTR):
|
||||||
return
|
return
|
||||||
for fd, flags in helper_random.randomsample(r, len(r)):
|
for fd, flags in random.sample(r, len(r)):
|
||||||
obj = map.get(fd)
|
obj = map.get(fd)
|
||||||
if obj is None:
|
if obj is None:
|
||||||
continue
|
continue
|
||||||
|
@ -358,7 +358,7 @@ def epoll_poller(timeout=0.0, map=None):
|
||||||
if err.args[0] != EINTR:
|
if err.args[0] != EINTR:
|
||||||
raise
|
raise
|
||||||
r = []
|
r = []
|
||||||
for fd, flags in helper_random.randomsample(r, len(r)):
|
for fd, flags in random.sample(r, len(r)):
|
||||||
obj = map.get(fd)
|
obj = map.get(fd)
|
||||||
if obj is None:
|
if obj is None:
|
||||||
continue
|
continue
|
||||||
|
@ -421,7 +421,7 @@ def kqueue_poller(timeout=0.0, map=None):
|
||||||
|
|
||||||
events = kqueue_poller.pollster.control(updates, selectables, timeout)
|
events = kqueue_poller.pollster.control(updates, selectables, timeout)
|
||||||
if len(events) > 1:
|
if len(events) > 1:
|
||||||
events = helper_random.randomsample(events, len(events))
|
events = random.sample(events, len(events))
|
||||||
|
|
||||||
for event in events:
|
for event in events:
|
||||||
fd = event.ident
|
fd = event.ident
|
||||||
|
|
|
@ -7,7 +7,7 @@ import time
|
||||||
import protocol
|
import protocol
|
||||||
import state
|
import state
|
||||||
import network.connectionpool # use long name to address recursive import
|
import network.connectionpool # use long name to address recursive import
|
||||||
from network import dandelion
|
from network import dandelion_ins
|
||||||
from highlevelcrypto import calculateInventoryHash
|
from highlevelcrypto import calculateInventoryHash
|
||||||
|
|
||||||
logger = logging.getLogger('default')
|
logger = logging.getLogger('default')
|
||||||
|
@ -113,7 +113,7 @@ class BMObject(object): # pylint: disable=too-many-instance-attributes
|
||||||
or advertise it unnecessarily)
|
or advertise it unnecessarily)
|
||||||
"""
|
"""
|
||||||
# if it's a stem duplicate, pretend we don't have it
|
# if it's a stem duplicate, pretend we don't have it
|
||||||
if dandelion.instance.hasHash(self.inventoryHash):
|
if dandelion_ins.hasHash(self.inventoryHash):
|
||||||
return
|
return
|
||||||
if self.inventoryHash in state.Inventory:
|
if self.inventoryHash in state.Inventory:
|
||||||
raise BMObjectAlreadyHaveError()
|
raise BMObjectAlreadyHaveError()
|
||||||
|
|
|
@ -17,7 +17,6 @@ from network import knownnodes
|
||||||
import protocol
|
import protocol
|
||||||
import state
|
import state
|
||||||
import network.connectionpool # use long name to address recursive import
|
import network.connectionpool # use long name to address recursive import
|
||||||
from network import dandelion
|
|
||||||
from bmconfigparser import config
|
from bmconfigparser import config
|
||||||
from queues import invQueue, objectProcessorQueue, portCheckerQueue
|
from queues import invQueue, objectProcessorQueue, portCheckerQueue
|
||||||
from randomtrackingdict import RandomTrackingDict
|
from randomtrackingdict import RandomTrackingDict
|
||||||
|
@ -29,6 +28,7 @@ from network.bmobject import (
|
||||||
)
|
)
|
||||||
from network.proxy import ProxyError
|
from network.proxy import ProxyError
|
||||||
|
|
||||||
|
from network import dandelion_ins
|
||||||
from .node import Node, Peer
|
from .node import Node, Peer
|
||||||
from .objectracker import ObjectTracker, missingObjects
|
from .objectracker import ObjectTracker, missingObjects
|
||||||
|
|
||||||
|
@ -364,14 +364,14 @@ class BMProto(AdvancedDispatcher, ObjectTracker):
|
||||||
raise BMProtoExcessiveDataError()
|
raise BMProtoExcessiveDataError()
|
||||||
|
|
||||||
# ignore dinv if dandelion turned off
|
# ignore dinv if dandelion turned off
|
||||||
if extend_dandelion_stem and not state.dandelion_enabled:
|
if extend_dandelion_stem and not dandelion_ins.enabled:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
for i in items:
|
for i in items:
|
||||||
if i in state.Inventory and not dandelion.instance.hasHash(i):
|
if i in state.Inventory and not dandelion_ins.hasHash(i):
|
||||||
continue
|
continue
|
||||||
if extend_dandelion_stem and not dandelion.instance.hasHash(i):
|
if extend_dandelion_stem and not dandelion_ins.hasHash(i):
|
||||||
dandelion.instance.addHash(i, self)
|
dandelion_ins.addHash(i, self)
|
||||||
self.handleReceivedInventory(i)
|
self.handleReceivedInventory(i)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -437,9 +437,9 @@ class BMProto(AdvancedDispatcher, ObjectTracker):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if self.object.inventoryHash in state.Inventory and dandelion.instance.hasHash(
|
if self.object.inventoryHash in state.Inventory and dandelion_ins.hasHash(
|
||||||
self.object.inventoryHash):
|
self.object.inventoryHash):
|
||||||
dandelion.instance.removeHash(
|
dandelion_ins.removeHash(
|
||||||
self.object.inventoryHash, "cycle detection")
|
self.object.inventoryHash, "cycle detection")
|
||||||
|
|
||||||
if six.PY2:
|
if six.PY2:
|
||||||
|
@ -563,7 +563,7 @@ class BMProto(AdvancedDispatcher, ObjectTracker):
|
||||||
if not self.isOutbound:
|
if not self.isOutbound:
|
||||||
self.append_write_buf(protocol.assembleVersionMessage(
|
self.append_write_buf(protocol.assembleVersionMessage(
|
||||||
self.destination.host, self.destination.port,
|
self.destination.host, self.destination.port,
|
||||||
network.connectionpool.pool.streams, True,
|
network.connectionpool.pool.streams, dandelion_ins.enabled, True,
|
||||||
nodeid=self.nodeid))
|
nodeid=self.nodeid))
|
||||||
logger.debug(
|
logger.debug(
|
||||||
'%(host)s:%(port)i sending version',
|
'%(host)s:%(port)i sending version',
|
||||||
|
|
|
@ -7,9 +7,9 @@ import re
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
import random
|
||||||
|
|
||||||
from network import asyncore_pollchoose as asyncore
|
from network import asyncore_pollchoose as asyncore
|
||||||
import helper_random
|
|
||||||
from network import knownnodes
|
from network import knownnodes
|
||||||
import protocol
|
import protocol
|
||||||
import state
|
import state
|
||||||
|
@ -216,7 +216,7 @@ class BMConnectionPool(object):
|
||||||
connection_base = TCPConnection
|
connection_base = TCPConnection
|
||||||
elif proxy_type == 'SOCKS5':
|
elif proxy_type == 'SOCKS5':
|
||||||
connection_base = Socks5BMConnection
|
connection_base = Socks5BMConnection
|
||||||
hostname = helper_random.randomchoice([
|
hostname = random.choice([ # nosec B311
|
||||||
'quzwelsuziwqgpt2.onion', None
|
'quzwelsuziwqgpt2.onion', None
|
||||||
])
|
])
|
||||||
elif proxy_type == 'SOCKS4a':
|
elif proxy_type == 'SOCKS4a':
|
||||||
|
@ -228,7 +228,7 @@ class BMConnectionPool(object):
|
||||||
|
|
||||||
bootstrapper = bootstrap(connection_base)
|
bootstrapper = bootstrap(connection_base)
|
||||||
if not hostname:
|
if not hostname:
|
||||||
port = helper_random.randomchoice([8080, 8444])
|
port = random.choice([8080, 8444]) # nosec B311
|
||||||
hostname = 'bootstrap%s.bitmessage.org' % port
|
hostname = 'bootstrap%s.bitmessage.org' % port
|
||||||
else:
|
else:
|
||||||
port = 8444
|
port = 8444
|
||||||
|
@ -295,7 +295,7 @@ class BMConnectionPool(object):
|
||||||
state.maximumNumberOfHalfOpenConnections - pending):
|
state.maximumNumberOfHalfOpenConnections - pending):
|
||||||
try:
|
try:
|
||||||
chosen = self.trustedPeer or chooseConnection(
|
chosen = self.trustedPeer or chooseConnection(
|
||||||
helper_random.randomchoice(self.streams))
|
random.choice(self.streams)) # nosec B311
|
||||||
except ValueError:
|
except ValueError:
|
||||||
continue
|
continue
|
||||||
if chosen in self.outboundConnections:
|
if chosen in self.outboundConnections:
|
||||||
|
|
|
@ -9,9 +9,6 @@ from time import time
|
||||||
import six
|
import six
|
||||||
from binascii import hexlify
|
from binascii import hexlify
|
||||||
|
|
||||||
import network.connectionpool # use long name to address recursive import
|
|
||||||
import state
|
|
||||||
from queues import invQueue
|
|
||||||
|
|
||||||
# randomise routes after 600 seconds
|
# randomise routes after 600 seconds
|
||||||
REASSIGN_INTERVAL = 600
|
REASSIGN_INTERVAL = 600
|
||||||
|
@ -39,6 +36,8 @@ class Dandelion: # pylint: disable=old-style-class
|
||||||
# when to rerandomise routes
|
# when to rerandomise routes
|
||||||
self.refresh = time() + REASSIGN_INTERVAL
|
self.refresh = time() + REASSIGN_INTERVAL
|
||||||
self.lock = RLock()
|
self.lock = RLock()
|
||||||
|
self.enabled = None
|
||||||
|
self.pool = None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def poissonTimeout(start=None, average=0):
|
def poissonTimeout(start=None, average=0):
|
||||||
|
@ -49,10 +48,23 @@ class Dandelion: # pylint: disable=old-style-class
|
||||||
average = FLUFF_TRIGGER_MEAN_DELAY
|
average = FLUFF_TRIGGER_MEAN_DELAY
|
||||||
return start + expovariate(1.0 / average) + FLUFF_TRIGGER_FIXED_DELAY
|
return start + expovariate(1.0 / average) + FLUFF_TRIGGER_FIXED_DELAY
|
||||||
|
|
||||||
|
def init_pool(self, pool):
|
||||||
|
"""pass pool instance"""
|
||||||
|
self.pool = pool
|
||||||
|
|
||||||
|
def init_dandelion_enabled(self, config):
|
||||||
|
"""Check if Dandelion is enabled and set value in enabled attribute"""
|
||||||
|
dandelion_enabled = config.safeGetInt('network', 'dandelion')
|
||||||
|
# dandelion requires outbound connections, without them,
|
||||||
|
# stem objects will get stuck forever
|
||||||
|
if not config.safeGetBoolean(
|
||||||
|
'bitmessagesettings', 'sendoutgoingconnections'):
|
||||||
|
dandelion_enabled = 0
|
||||||
|
self.enabled = dandelion_enabled
|
||||||
|
|
||||||
def addHash(self, hashId, source=None, stream=1):
|
def addHash(self, hashId, source=None, stream=1):
|
||||||
"""Add inventory vector to dandelion stem"""
|
"""Add inventory vector to dandelion stem return status of dandelion enabled"""
|
||||||
if not state.dandelion_enabled:
|
assert self.enabled is not None
|
||||||
return
|
|
||||||
with self.lock:
|
with self.lock:
|
||||||
self.hashMap[bytes(hashId)] = Stem(
|
self.hashMap[bytes(hashId)] = Stem(
|
||||||
self.getNodeStem(source),
|
self.getNodeStem(source),
|
||||||
|
@ -92,7 +104,7 @@ class Dandelion: # pylint: disable=old-style-class
|
||||||
"""Child (i.e. next) node for an inventory vector during stem mode"""
|
"""Child (i.e. next) node for an inventory vector during stem mode"""
|
||||||
return self.hashMap[bytes(hashId)].child
|
return self.hashMap[bytes(hashId)].child
|
||||||
|
|
||||||
def maybeAddStem(self, connection):
|
def maybeAddStem(self, connection, invQueue):
|
||||||
"""
|
"""
|
||||||
If we had too few outbound connections, add the current one to the
|
If we had too few outbound connections, add the current one to the
|
||||||
current stem list. Dandelion as designed by the authors should
|
current stem list. Dandelion as designed by the authors should
|
||||||
|
@ -166,7 +178,7 @@ class Dandelion: # pylint: disable=old-style-class
|
||||||
self.nodeMap[node] = self.pickStem(node)
|
self.nodeMap[node] = self.pickStem(node)
|
||||||
return self.nodeMap[node]
|
return self.nodeMap[node]
|
||||||
|
|
||||||
def expire(self):
|
def expire(self, invQueue):
|
||||||
"""Switch expired objects from stem to fluff mode"""
|
"""Switch expired objects from stem to fluff mode"""
|
||||||
with self.lock:
|
with self.lock:
|
||||||
deadline = time()
|
deadline = time()
|
||||||
|
@ -182,19 +194,18 @@ class Dandelion: # pylint: disable=old-style-class
|
||||||
|
|
||||||
def reRandomiseStems(self):
|
def reRandomiseStems(self):
|
||||||
"""Re-shuffle stem mapping (parent <-> child pairs)"""
|
"""Re-shuffle stem mapping (parent <-> child pairs)"""
|
||||||
|
assert self.pool is not None
|
||||||
|
if self.refresh > time():
|
||||||
|
return
|
||||||
|
|
||||||
with self.lock:
|
with self.lock:
|
||||||
try:
|
try:
|
||||||
# random two connections
|
# random two connections
|
||||||
self.stem = sample(
|
self.stem = sample(
|
||||||
sorted(network.connectionpool.BMConnectionPool(
|
sorted(self.pool.outboundConnections.values()), MAX_STEMS)
|
||||||
).outboundConnections.values()), MAX_STEMS)
|
|
||||||
# not enough stems available
|
# not enough stems available
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self.stem = list(network.connectionpool.BMConnectionPool(
|
self.stem = list(self.pool.outboundConnections.values())
|
||||||
).outboundConnections.values())
|
|
||||||
self.nodeMap = {}
|
self.nodeMap = {}
|
||||||
# hashMap stays to cater for pending stems
|
# hashMap stays to cater for pending stems
|
||||||
self.refresh = time() + REASSIGN_INTERVAL
|
self.refresh = time() + REASSIGN_INTERVAL
|
||||||
|
|
||||||
|
|
||||||
instance = Dandelion()
|
|
||||||
|
|
|
@ -2,15 +2,15 @@
|
||||||
`DownloadThread` class definition
|
`DownloadThread` class definition
|
||||||
"""
|
"""
|
||||||
import time
|
import time
|
||||||
|
import random
|
||||||
import state
|
import state
|
||||||
import six
|
import six
|
||||||
import addresses
|
import addresses
|
||||||
import helper_random
|
|
||||||
import protocol
|
import protocol
|
||||||
from network import connectionpool
|
from network import connectionpool
|
||||||
|
from network import dandelion_ins
|
||||||
from .objectracker import missingObjects
|
from .objectracker import missingObjects
|
||||||
from .threads import StoppableThread
|
from .threads import StoppableThread
|
||||||
from network import dandelion
|
|
||||||
|
|
||||||
|
|
||||||
class DownloadThread(StoppableThread):
|
class DownloadThread(StoppableThread):
|
||||||
|
@ -44,7 +44,7 @@ class DownloadThread(StoppableThread):
|
||||||
requested = 0
|
requested = 0
|
||||||
# Choose downloading peers randomly
|
# Choose downloading peers randomly
|
||||||
connections = connectionpool.pool.establishedConnections()
|
connections = connectionpool.pool.establishedConnections()
|
||||||
helper_random.randomshuffle(connections)
|
random.shuffle(connections)
|
||||||
requestChunk = max(int(
|
requestChunk = max(int(
|
||||||
min(self.maxRequestChunk, len(missingObjects))
|
min(self.maxRequestChunk, len(missingObjects))
|
||||||
/ len(connections)), 1) if connections else 1
|
/ len(connections)), 1) if connections else 1
|
||||||
|
@ -61,7 +61,7 @@ class DownloadThread(StoppableThread):
|
||||||
payload = bytearray()
|
payload = bytearray()
|
||||||
chunkCount = 0
|
chunkCount = 0
|
||||||
for chunk in request:
|
for chunk in request:
|
||||||
if chunk in state.Inventory and not dandelion.instance.hasHash(chunk):
|
if chunk in state.Inventory and not dandelion_ins.hasHash(chunk):
|
||||||
try:
|
try:
|
||||||
del i.objectsNewToMe[chunk]
|
del i.objectsNewToMe[chunk]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|
|
@ -9,7 +9,7 @@ import addresses
|
||||||
import protocol
|
import protocol
|
||||||
import state
|
import state
|
||||||
from network import connectionpool
|
from network import connectionpool
|
||||||
from network import dandelion
|
from network import dandelion_ins
|
||||||
from queues import invQueue
|
from queues import invQueue
|
||||||
from .threads import StoppableThread
|
from .threads import StoppableThread
|
||||||
|
|
||||||
|
@ -40,10 +40,10 @@ class InvThread(StoppableThread):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def handleLocallyGenerated(stream, hashId):
|
def handleLocallyGenerated(stream, hashId):
|
||||||
"""Locally generated inventory items require special handling"""
|
"""Locally generated inventory items require special handling"""
|
||||||
dandelion.instance.addHash(hashId, stream=stream)
|
dandelion_ins.addHash(hashId, stream=stream)
|
||||||
for connection in connectionpool.pool.connections():
|
for connection in connectionpool.pool.connections():
|
||||||
if state.dandelion_enabled and connection != \
|
if dandelion_ins.enabled and connection != \
|
||||||
dandelion.instance.objectChildStem(hashId):
|
dandelion_ins.objectChildStem(hashId):
|
||||||
continue
|
continue
|
||||||
connection.objectsNewToThem[hashId] = time()
|
connection.objectsNewToThem[hashId] = time()
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ class InvThread(StoppableThread):
|
||||||
chunk = []
|
chunk = []
|
||||||
while True:
|
while True:
|
||||||
# Dandelion fluff trigger by expiration
|
# Dandelion fluff trigger by expiration
|
||||||
handleExpiredDandelion(dandelion.instance.expire())
|
handleExpiredDandelion(dandelion_ins.expire(invQueue))
|
||||||
try:
|
try:
|
||||||
data = invQueue.get(False)
|
data = invQueue.get(False)
|
||||||
chunk.append((data[0], data[1]))
|
chunk.append((data[0], data[1]))
|
||||||
|
@ -75,10 +75,10 @@ class InvThread(StoppableThread):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
if connection == dandelion.instance.objectChildStem(inv[1]):
|
if connection == dandelion_ins.objectChildStem(inv[1]):
|
||||||
# Fluff trigger by RNG
|
# Fluff trigger by RNG
|
||||||
# auto-ignore if config set to 0, i.e. dandelion is off
|
# auto-ignore if config set to 0, i.e. dandelion is off
|
||||||
if random.randint(1, 100) >= state.dandelion_enabled: # nosec B311
|
if random.randint(1, 100) >= dandelion_ins.enabled: # nosec B311
|
||||||
fluffs.append(inv[1])
|
fluffs.append(inv[1])
|
||||||
# send a dinv only if the stem node supports dandelion
|
# send a dinv only if the stem node supports dandelion
|
||||||
elif connection.services & protocol.NODE_DANDELION > 0:
|
elif connection.services & protocol.NODE_DANDELION > 0:
|
||||||
|
@ -105,7 +105,6 @@ class InvThread(StoppableThread):
|
||||||
for _ in range(len(chunk)):
|
for _ in range(len(chunk)):
|
||||||
invQueue.task_done()
|
invQueue.task_done()
|
||||||
|
|
||||||
if dandelion.instance.refresh < time():
|
dandelion_ins.reRandomiseStems()
|
||||||
dandelion.instance.reRandomiseStems()
|
|
||||||
|
|
||||||
self.stop.wait(1)
|
self.stop.wait(1)
|
||||||
|
|
|
@ -6,7 +6,7 @@ from threading import RLock
|
||||||
import six
|
import six
|
||||||
|
|
||||||
import network.connectionpool # use long name to address recursive import
|
import network.connectionpool # use long name to address recursive import
|
||||||
from network import dandelion
|
from network import dandelion_ins
|
||||||
from randomtrackingdict import RandomTrackingDict
|
from randomtrackingdict import RandomTrackingDict
|
||||||
|
|
||||||
haveBloom = False
|
haveBloom = False
|
||||||
|
@ -111,14 +111,14 @@ class ObjectTracker(object):
|
||||||
del i.objectsNewToMe[hashid]
|
del i.objectsNewToMe[hashid]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
if streamNumber in i.streams and (
|
if streamNumber in i.streams and (
|
||||||
not dandelion.instance.hasHash(hashid)
|
not dandelion_ins.hasHash(hashid)
|
||||||
or dandelion.instance.objectChildStem(hashid) == i):
|
or dandelion_ins.objectChildStem(hashid) == i):
|
||||||
with i.objectsNewToThemLock:
|
with i.objectsNewToThemLock:
|
||||||
i.objectsNewToThem[hashid_bytes] = time.time()
|
i.objectsNewToThem[hashid_bytes] = time.time()
|
||||||
# update stream number,
|
# update stream number,
|
||||||
# which we didn't have when we just received the dinv
|
# which we didn't have when we just received the dinv
|
||||||
# also resets expiration of the stem mode
|
# also resets expiration of the stem mode
|
||||||
dandelion.instance.setHashStream(hashid, streamNumber)
|
dandelion_ins.setHashStream(hashid, streamNumber)
|
||||||
|
|
||||||
if i == self:
|
if i == self:
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -12,14 +12,13 @@ import six
|
||||||
|
|
||||||
# magic imports!
|
# magic imports!
|
||||||
import addresses
|
import addresses
|
||||||
import helper_random
|
|
||||||
import l10n
|
import l10n
|
||||||
import protocol
|
import protocol
|
||||||
import state
|
import state
|
||||||
import network.connectionpool # use long name to address recursive import
|
import network.connectionpool # use long name to address recursive import
|
||||||
from network import dandelion
|
|
||||||
from bmconfigparser import config
|
from bmconfigparser import config
|
||||||
from highlevelcrypto import randomBytes
|
from highlevelcrypto import randomBytes
|
||||||
|
from network import dandelion_ins
|
||||||
from queues import invQueue, receiveDataQueue, UISignalQueue
|
from queues import invQueue, receiveDataQueue, UISignalQueue
|
||||||
from tr import _translate
|
from tr import _translate
|
||||||
|
|
||||||
|
@ -176,7 +175,7 @@ class TCPConnection(BMProto, TLSDispatcher):
|
||||||
knownnodes.increaseRating(self.destination)
|
knownnodes.increaseRating(self.destination)
|
||||||
knownnodes.addKnownNode(
|
knownnodes.addKnownNode(
|
||||||
self.streams, self.destination, time.time())
|
self.streams, self.destination, time.time())
|
||||||
dandelion.instance.maybeAddStem(self)
|
dandelion_ins.maybeAddStem(self, invQueue)
|
||||||
self.sendAddr()
|
self.sendAddr()
|
||||||
self.sendBigInv()
|
self.sendBigInv()
|
||||||
|
|
||||||
|
@ -208,7 +207,7 @@ class TCPConnection(BMProto, TLSDispatcher):
|
||||||
elemCount = min(
|
elemCount = min(
|
||||||
len(filtered),
|
len(filtered),
|
||||||
maxAddrCount / 2 if n else maxAddrCount)
|
maxAddrCount / 2 if n else maxAddrCount)
|
||||||
addrs[s] = helper_random.randomsample(filtered, elemCount)
|
addrs[s] = random.sample(filtered, elemCount)
|
||||||
for substream in addrs:
|
for substream in addrs:
|
||||||
for peer, params in addrs[substream]:
|
for peer, params in addrs[substream]:
|
||||||
templist.append((substream, peer, params["lastseen"]))
|
templist.append((substream, peer, params["lastseen"]))
|
||||||
|
@ -238,7 +237,7 @@ class TCPConnection(BMProto, TLSDispatcher):
|
||||||
with self.objectsNewToThemLock:
|
with self.objectsNewToThemLock:
|
||||||
for objHash in state.Inventory.unexpired_hashes_by_stream(stream):
|
for objHash in state.Inventory.unexpired_hashes_by_stream(stream):
|
||||||
# don't advertise stem objects on bigInv
|
# don't advertise stem objects on bigInv
|
||||||
if dandelion.instance.hasHash(objHash):
|
if dandelion_ins.hasHash(objHash):
|
||||||
continue
|
continue
|
||||||
bigInvList[objHash] = 0
|
bigInvList[objHash] = 0
|
||||||
objectCount = 0
|
objectCount = 0
|
||||||
|
@ -275,7 +274,7 @@ class TCPConnection(BMProto, TLSDispatcher):
|
||||||
self.append_write_buf(
|
self.append_write_buf(
|
||||||
protocol.assembleVersionMessage(
|
protocol.assembleVersionMessage(
|
||||||
self.destination.host, self.destination.port,
|
self.destination.host, self.destination.port,
|
||||||
network.connectionpool.pool.streams,
|
network.connectionpool.pool.streams, dandelion_ins.enabled,
|
||||||
False, nodeid=self.nodeid))
|
False, nodeid=self.nodeid))
|
||||||
self.connectedAt = time.time()
|
self.connectedAt = time.time()
|
||||||
receiveDataQueue.put(self.destination)
|
receiveDataQueue.put(self.destination)
|
||||||
|
@ -300,7 +299,7 @@ class TCPConnection(BMProto, TLSDispatcher):
|
||||||
if host_is_global:
|
if host_is_global:
|
||||||
knownnodes.addKnownNode(
|
knownnodes.addKnownNode(
|
||||||
self.streams, self.destination, time.time())
|
self.streams, self.destination, time.time())
|
||||||
dandelion.instance.maybeRemoveStem(self)
|
dandelion_ins.maybeRemoveStem(self)
|
||||||
else:
|
else:
|
||||||
self.checkTimeOffsetNotification()
|
self.checkTimeOffsetNotification()
|
||||||
if host_is_global:
|
if host_is_global:
|
||||||
|
@ -326,7 +325,7 @@ class Socks5BMConnection(Socks5Connection, TCPConnection):
|
||||||
self.append_write_buf(
|
self.append_write_buf(
|
||||||
protocol.assembleVersionMessage(
|
protocol.assembleVersionMessage(
|
||||||
self.destination.host, self.destination.port,
|
self.destination.host, self.destination.port,
|
||||||
network.connectionpool.pool.streams,
|
network.connectionpool.pool.streams, dandelion_ins.enabled,
|
||||||
False, nodeid=self.nodeid))
|
False, nodeid=self.nodeid))
|
||||||
self.set_state("bm_header", expectBytes=protocol.Header.size)
|
self.set_state("bm_header", expectBytes=protocol.Header.size)
|
||||||
return True
|
return True
|
||||||
|
@ -350,7 +349,7 @@ class Socks4aBMConnection(Socks4aConnection, TCPConnection):
|
||||||
self.append_write_buf(
|
self.append_write_buf(
|
||||||
protocol.assembleVersionMessage(
|
protocol.assembleVersionMessage(
|
||||||
self.destination.host, self.destination.port,
|
self.destination.host, self.destination.port,
|
||||||
network.connectionpool.pool.streams,
|
network.connectionpool.pool.streams, dandelion_ins.enabled,
|
||||||
False, nodeid=self.nodeid))
|
False, nodeid=self.nodeid))
|
||||||
self.set_state("bm_header", expectBytes=protocol.Header.size)
|
self.set_state("bm_header", expectBytes=protocol.Header.size)
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -3,12 +3,12 @@
|
||||||
"""
|
"""
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import helper_random
|
import random
|
||||||
import protocol
|
import protocol
|
||||||
import state
|
import state
|
||||||
from network import connectionpool
|
from network import connectionpool
|
||||||
from network import dandelion
|
|
||||||
from randomtrackingdict import RandomTrackingDict
|
from randomtrackingdict import RandomTrackingDict
|
||||||
|
from network import dandelion_ins
|
||||||
from .threads import StoppableThread
|
from .threads import StoppableThread
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ class UploadThread(StoppableThread):
|
||||||
uploaded = 0
|
uploaded = 0
|
||||||
# Choose uploading peers randomly
|
# Choose uploading peers randomly
|
||||||
connections = connectionpool.pool.establishedConnections()
|
connections = connectionpool.pool.establishedConnections()
|
||||||
helper_random.randomshuffle(connections)
|
random.shuffle(connections)
|
||||||
for i in connections:
|
for i in connections:
|
||||||
now = time.time()
|
now = time.time()
|
||||||
# avoid unnecessary delay
|
# avoid unnecessary delay
|
||||||
|
@ -41,8 +41,8 @@ class UploadThread(StoppableThread):
|
||||||
chunk_count = 0
|
chunk_count = 0
|
||||||
for chunk in request:
|
for chunk in request:
|
||||||
del i.pendingUpload[chunk]
|
del i.pendingUpload[chunk]
|
||||||
if dandelion.instance.hasHash(chunk) and \
|
if dandelion_ins.hasHash(chunk) and \
|
||||||
i != dandelion.instance.objectChildStem(chunk):
|
i != dandelion_ins.objectChildStem(chunk):
|
||||||
i.antiIntersectionDelay()
|
i.antiIntersectionDelay()
|
||||||
self.logger.info(
|
self.logger.info(
|
||||||
'%s asked for a stem object we didn\'t offer to it.',
|
'%s asked for a stem object we didn\'t offer to it.',
|
||||||
|
|
|
@ -339,8 +339,8 @@ def assembleAddrMessage(peerList):
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
|
|
||||||
def assembleVersionMessage(
|
def assembleVersionMessage( # pylint: disable=too-many-arguments
|
||||||
remoteHost, remotePort, participatingStreams, server=False, nodeid=None
|
remoteHost, remotePort, participatingStreams, dandelion_enabled=True, server=False, nodeid=None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Construct the payload of a version message,
|
Construct the payload of a version message,
|
||||||
|
@ -353,7 +353,7 @@ def assembleVersionMessage(
|
||||||
'>q',
|
'>q',
|
||||||
NODE_NETWORK
|
NODE_NETWORK
|
||||||
| (NODE_SSL if haveSSL(server) else 0)
|
| (NODE_SSL if haveSSL(server) else 0)
|
||||||
| (NODE_DANDELION if state.dandelion_enabled else 0)
|
| (NODE_DANDELION if dandelion_enabled else 0)
|
||||||
)
|
)
|
||||||
payload += pack('>q', int(time.time()))
|
payload += pack('>q', int(time.time()))
|
||||||
|
|
||||||
|
@ -377,7 +377,7 @@ def assembleVersionMessage(
|
||||||
'>q',
|
'>q',
|
||||||
NODE_NETWORK
|
NODE_NETWORK
|
||||||
| (NODE_SSL if haveSSL(server) else 0)
|
| (NODE_SSL if haveSSL(server) else 0)
|
||||||
| (NODE_DANDELION if state.dandelion_enabled else 0)
|
| (NODE_DANDELION if dandelion_enabled else 0)
|
||||||
)
|
)
|
||||||
# = 127.0.0.1. This will be ignored by the remote host.
|
# = 127.0.0.1. This will be ignored by the remote host.
|
||||||
# The actual remote connected IP will be used.
|
# The actual remote connected IP will be used.
|
||||||
|
|
|
@ -43,8 +43,6 @@ ownAddresses = {}
|
||||||
|
|
||||||
discoveredPeers = {}
|
discoveredPeers = {}
|
||||||
|
|
||||||
dandelion_enabled = 0
|
|
||||||
|
|
||||||
kivy = False
|
kivy = False
|
||||||
|
|
||||||
kivyapp = None
|
kivyapp = None
|
||||||
|
|
|
@ -322,16 +322,17 @@ class TestCore(unittest.TestCase):
|
||||||
|
|
||||||
def test_version(self):
|
def test_version(self):
|
||||||
"""check encoding/decoding of the version message"""
|
"""check encoding/decoding of the version message"""
|
||||||
|
dandelion_enabled = True
|
||||||
# with single stream
|
# with single stream
|
||||||
msg = protocol.assembleVersionMessage('127.0.0.1', 8444, [1])
|
msg = protocol.assembleVersionMessage('127.0.0.1', 8444, [1], dandelion_enabled)
|
||||||
decoded = self._decode_msg(msg, "IQQiiQlsLv")
|
decoded = self._decode_msg(msg, "IQQiiQlsLv")
|
||||||
peer, _, ua, streams = self._decode_msg(msg, "IQQiiQlsLv")[4:]
|
peer, _, ua, streams = self._decode_msg(msg, "IQQiiQlsLv")[4:]
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
peer, Node(11 if state.dandelion_enabled else 3, '127.0.0.1', 8444))
|
peer, Node(11 if dandelion_enabled else 3, '127.0.0.1', 8444))
|
||||||
self.assertEqual(ua, '/PyBitmessage:' + softwareVersion + '/')
|
self.assertEqual(ua, '/PyBitmessage:' + softwareVersion + '/')
|
||||||
self.assertEqual(streams, [1])
|
self.assertEqual(streams, [1])
|
||||||
# with multiple streams
|
# with multiple streams
|
||||||
msg = protocol.assembleVersionMessage('127.0.0.1', 8444, [1, 2, 3])
|
msg = protocol.assembleVersionMessage('127.0.0.1', 8444, [1, 2, 3], dandelion_enabled)
|
||||||
decoded = self._decode_msg(msg, "IQQiiQlslv")
|
decoded = self._decode_msg(msg, "IQQiiQlslv")
|
||||||
peer, _, ua = decoded[4:7]
|
peer, _, ua = decoded[4:7]
|
||||||
streams = decoded[7:]
|
streams = decoded[7:]
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
from binascii import unhexlify
|
from binascii import unhexlify
|
||||||
|
|
||||||
from six.moves import queue
|
|
||||||
import six
|
import six
|
||||||
|
from six.moves import queue
|
||||||
|
|
||||||
from .partial import TestPartialRun
|
from .partial import TestPartialRun
|
||||||
from .samples import (
|
from .samples import (
|
||||||
|
|
|
@ -5,12 +5,11 @@ Tests using API.
|
||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from binascii import hexlify
|
from binascii import hexlify
|
||||||
from six.moves import xmlrpc_client # nosec
|
|
||||||
import six
|
|
||||||
|
|
||||||
import psutil
|
import psutil
|
||||||
|
import six
|
||||||
|
from six.moves import xmlrpc_client # nosec
|
||||||
|
|
||||||
from .samples import (
|
from .samples import (
|
||||||
sample_deterministic_addr3, sample_deterministic_addr4, sample_seed,
|
sample_deterministic_addr3, sample_deterministic_addr4, sample_seed,
|
||||||
|
@ -181,29 +180,29 @@ class TestAPI(TestAPIProto):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.api.getDeterministicAddress(self._seed, 3, 1),
|
self.api.getDeterministicAddress(self._seed, 3, 1),
|
||||||
sample_deterministic_addr3)
|
sample_deterministic_addr3)
|
||||||
six.assertRegex(self,
|
six.assertRegex(
|
||||||
self.api.getDeterministicAddress(self._seed, 2, 1),
|
self, self.api.getDeterministicAddress(self._seed, 2, 1),
|
||||||
r'^API Error 0002:')
|
r'^API Error 0002:')
|
||||||
|
|
||||||
# This is here until the streams will be implemented
|
# This is here until the streams will be implemented
|
||||||
six.assertRegex(self,
|
six.assertRegex(
|
||||||
self.api.getDeterministicAddress(self._seed, 3, 2),
|
self, self.api.getDeterministicAddress(self._seed, 3, 2),
|
||||||
r'API Error 0003:')
|
r'API Error 0003:')
|
||||||
six.assertRegex(self,
|
six.assertRegex(
|
||||||
self.api.createDeterministicAddresses(self._seed, 1, 4, 2),
|
self, self.api.createDeterministicAddresses(self._seed, 1, 4, 2),
|
||||||
r'API Error 0003:')
|
r'API Error 0003:')
|
||||||
|
|
||||||
six.assertRegex(self,
|
six.assertRegex(
|
||||||
self.api.createDeterministicAddresses('', 1),
|
self, self.api.createDeterministicAddresses('', 1),
|
||||||
r'API Error 0001:')
|
r'API Error 0001:')
|
||||||
six.assertRegex(self,
|
six.assertRegex(
|
||||||
self.api.createDeterministicAddresses(self._seed, 1, 2),
|
self, self.api.createDeterministicAddresses(self._seed, 1, 2),
|
||||||
r'API Error 0002:')
|
r'API Error 0002:')
|
||||||
six.assertRegex(self,
|
six.assertRegex(
|
||||||
self.api.createDeterministicAddresses(self._seed, 0),
|
self, self.api.createDeterministicAddresses(self._seed, 0),
|
||||||
r'API Error 0004:')
|
r'API Error 0004:')
|
||||||
six.assertRegex(self,
|
six.assertRegex(
|
||||||
self.api.createDeterministicAddresses(self._seed, 1000),
|
self, self.api.createDeterministicAddresses(self._seed, 1000),
|
||||||
r'API Error 0005:')
|
r'API Error 0005:')
|
||||||
|
|
||||||
addresses = json.loads(
|
addresses = json.loads(
|
||||||
|
@ -442,7 +441,7 @@ class TestAPI(TestAPIProto):
|
||||||
self.assertEqual(self.api.joinChan(self._seed, addr), 'success')
|
self.assertEqual(self.api.joinChan(self._seed, addr), 'success')
|
||||||
self.assertEqual(self.api.leaveChan(addr), 'success')
|
self.assertEqual(self.api.leaveChan(addr), 'success')
|
||||||
# Joining with wrong address should fail
|
# Joining with wrong address should fail
|
||||||
six.assertRegex(self,
|
six.assertRegex(
|
||||||
self.api.joinChan(self._seed, 'BM-2cWzSnwjJ7yRP3nLEW'),
|
self, self.api.joinChan(self._seed, 'BM-2cWzSnwjJ7yRP3nLEW'),
|
||||||
r'^API Error 0008:'
|
r'^API Error 0008:'
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,6 +6,7 @@ import struct
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from pybitmessage import highlevelcrypto
|
from pybitmessage import highlevelcrypto
|
||||||
|
@ -51,8 +52,8 @@ class TestStorageAbstract(unittest.TestCase):
|
||||||
|
|
||||||
def test_inventory_storage(self):
|
def test_inventory_storage(self):
|
||||||
"""Check inherited abstract methods"""
|
"""Check inherited abstract methods"""
|
||||||
with six.assertRaisesRegex(self,
|
with six.assertRaisesRegex(
|
||||||
TypeError, "^Can't instantiate abstract class.*"
|
self, TypeError, "^Can't instantiate abstract class.*"
|
||||||
"methods __contains__, __delitem__, __getitem__, __iter__,"
|
"methods __contains__, __delitem__, __getitem__, __iter__,"
|
||||||
" __len__, __setitem__"
|
" __len__, __setitem__"
|
||||||
): # pylint: disable=abstract-class-instantiated
|
): # pylint: disable=abstract-class-instantiated
|
||||||
|
|
|
@ -4,6 +4,7 @@ Testing the logger configuration
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from .test_process import TestProcessProto
|
from .test_process import TestProcessProto
|
||||||
|
|
Reference in New Issue
Block a user