From efc9664376ba87c67d284a29b21d4c3336db5055 Mon Sep 17 00:00:00 2001 From: Pau Peris Date: Thu, 28 Dec 2017 22:07:49 +0100 Subject: [PATCH 01/21] BugFix: /usr/env does not exists on Mac OS X. BugFix: env binary does not reside on /usr/ directory but it does on /usr/bin/. --- osxbuild/build-app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osxbuild/build-app.py b/osxbuild/build-app.py index 7a6e832f7..dd612f3eb 100755 --- a/osxbuild/build-app.py +++ b/osxbuild/build-app.py @@ -1,4 +1,4 @@ -#!/usr/env python +#!/usr/bin/env python """Build Armory as a Mac OS X Application.""" import os From f0a8c646197be3d6271c21ff2f8e5c32af3b1b18 Mon Sep 17 00:00:00 2001 From: goatpig Date: Sun, 7 Jan 2018 20:40:56 +0100 Subject: [PATCH 02/21] enforce minimum coin control dialog size --- ui/CoinControlUI.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ui/CoinControlUI.py b/ui/CoinControlUI.py index 72bd656ef..a6b1f9310 100644 --- a/ui/CoinControlUI.py +++ b/ui/CoinControlUI.py @@ -44,6 +44,9 @@ def __init__(self, parent, main, wlt): self.ccTreeModel = CoinControlTreeModel(self, wlt) self.ccView = QTreeView() self.ccView.setModel(self.ccTreeModel) + + self.setMinimumWidth(400) + self.setMinimumHeight(300) self.btnAccept = QPushButton(self.tr("Accept")) self.btnCancel = QPushButton(self.tr("Cancel")) @@ -122,8 +125,9 @@ def reject(self, *args): ############################################################################# def saveGeometrySettings(self): - self.main.writeSetting('ccDlgGeometry', str(self.saveGeometry().toHex())) - self.main.writeSetting('ccDlgAddrCols', saveTableView(self.ccView)) + if self.isVisible() == True: + self.main.writeSetting('ccDlgGeometry', str(self.saveGeometry().toHex())) + self.main.writeSetting('ccDlgAddrCols', saveTableView(self.ccView)) ############################################################################# def exec_(self): From 70eecef046a07b9cb9a4db5ac4eba331b597620b Mon Sep 17 00:00:00 2001 From: goatpig Date: Sun, 7 Jan 2018 21:46:45 +0100 Subject: [PATCH 03/21] fix SegWit fee calculation on MAX value --- cppForSwig/CoinSelection.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/cppForSwig/CoinSelection.cpp b/cppForSwig/CoinSelection.cpp index 54f0a73ac..3b08f2520 100644 --- a/cppForSwig/CoinSelection.cpp +++ b/cppForSwig/CoinSelection.cpp @@ -293,10 +293,17 @@ uint64_t CoinSelection::getFeeForMaxVal( } if (witnessSize != 0) - txSize += 2 + utxoVec_.size(); + { + txSize += 2; + if (coinControlVec.size() == 0) + txSize += utxoVec_.size(); + else + txSize += coinControlVec.size(); + } - float fee = fee_byte * (txSize + witnessSize * 0.75f); - return uint64_t(fee); + uint64_t fee = uint64_t(fee_byte * float(txSize)); + fee += uint64_t(float(witnessSize) * 0.25f * fee_byte); + return fee; } //////////////////////////////////////////////////////////////////////////////// From 44bf4668b4e72bccbaa7947e9ea0d2add5ae31fa Mon Sep 17 00:00:00 2001 From: goatpig Date: Thu, 11 Jan 2018 20:19:28 +0100 Subject: [PATCH 04/21] fix windows executable --- SDM.py | 10 ++++++---- setup.py | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/SDM.py b/SDM.py index 564dd5258..f256913c5 100644 --- a/SDM.py +++ b/SDM.py @@ -93,7 +93,6 @@ def parseLinkList(theData): ################################################################################ -# jgarzik'sjj jsonrpc-bitcoin code -- stupid-easy to talk to bitcoind class SatoshiDaemonManager(object): """ Use an existing implementation of bitcoind @@ -131,11 +130,14 @@ def setSatoshiDir(self, newDir): if 'testnet' in newDir or 'regtest' in newDir: self.satoshiRoot, tail = os.path.split(newDir) - path = os.path.dirname(os.path.abspath(__file__)) + execDir = os.path.dirname(inspect.getsourcefile(SatoshiDaemonManager)) + if execDir.endswith('.zip'): + execDir = os.path.dirname(execDir) + if OS_MACOSX: # OSX separates binaries/start scripts from the Python code. Back up! - path = os.path.join(path, '../../bin/') - self.dbExecutable = os.path.join(path, 'ArmoryDB') + execDir = os.path.join(execDir, '../../bin/') + self.dbExecutable = os.path.join(execDir, 'ArmoryDB') if OS_WINDOWS: self.dbExecutable += ".exe" diff --git a/setup.py b/setup.py index 432c37a84..6ab4fbcbd 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ opts = {"py2exe":{ - "dll_excludes":["MSWSOCK.dll", "IPHLPAPI.dll", "MSWSOCK.dll", "WINNSI.dll", "WTSAPI32.dll"], + "dll_excludes":["MSWSOCK.dll", "IPHLPAPI.dll", "MSWSOCK.dll", "WINNSI.dll", "WTSAPI32.dll", "NSI.dll", "POWRPROF.dll", "PSAPI.dll", "CRYPT32.dll"], "packages":["email"] }} From b64a75daacfeaf3e45227fbf45fa0c41558dddf6 Mon Sep 17 00:00:00 2001 From: goatpig Date: Thu, 11 Jan 2018 21:36:26 +0100 Subject: [PATCH 05/21] do not recompute fragments unnecessarily --- qtdialogs.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/qtdialogs.py b/qtdialogs.py index 0600ccef2..fecbb78d9 100644 --- a/qtdialogs.py +++ b/qtdialogs.py @@ -10388,6 +10388,8 @@ def __init__(self, parent, main, wlt): frmDescr = makeVertFrame([lblDescrTitle, self.lblAboveFrags], \ STYLE_RAISED) + self.fragDisplayLastN = 0 + self.fragDisplayLastM = 0 self.maxM = 5 if not self.main.usermode == USERMODE.Expert else 8 self.maxN = 6 if not self.main.usermode == USERMODE.Expert else 12 @@ -10501,11 +10503,16 @@ def updateComboN(self): ############################################################################# def createFragDisplay(self): - self.recomputeFragData() M = int(str(self.comboM.currentText())) N = int(str(self.comboN.currentText())) + #only recompute fragments if M or N changed + if self.fragDisplayLastN != N or \ + self.fragDisplayLastM != M: + self.recomputeFragData() + self.fragDisplayLastN = N + self.fragDisplayLastM = M lblAboveM = QRichLabel(self.tr('Required Fragments '), hAlign=Qt.AlignHCenter, doWrap=False) lblAboveN = QRichLabel(self.tr('Total Fragments '), hAlign=Qt.AlignHCenter) From 43a7cfe25db66007797a5444b1cadd14fc7083eb Mon Sep 17 00:00:00 2001 From: goatpig Date: Thu, 11 Jan 2018 21:40:01 +0100 Subject: [PATCH 06/21] update changelog --- changelog.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/changelog.txt b/changelog.txt index 6e5556083..ec1d46df3 100644 --- a/changelog.txt +++ b/changelog.txt @@ -30,6 +30,9 @@ v0.96.4 - When in offline mode, the default signer option will now pick the proper signer for SegWit transactions. - Fixed --ram-usage control. --ram-usage=1 will no longer hang the DB during bootstrap scans. - As a result, --ram-usage defaults to 50 no instead of 4. + - Fixed fee calculation when checking "MAX" with SegWit UTXOs + - The Coin control dialog will no longer spawn invisible + - When creating a fragmented backup, fragments will no longer be cycled unecessarily == Minor == - You can now resize the Transaction Info dialog. From eb9d38c4ff82b100af8b8b00d6d75a08c1c39a74 Mon Sep 17 00:00:00 2001 From: Douglas Roark Date: Sat, 30 Dec 2017 00:56:29 -0800 Subject: [PATCH 07/21] More macOS build script fixes - Add support for armoryd. The armoryd daemon requires Twisted. Provide an option that allows the user to compile a version of Armory with Twisted. - Change various names to better reflect the purpose of the variable/function/etc. - Clean up minor logic errors. - Force deletion of .pyc files upon compilation. --- osxbuild/build-app.py | 207 ++++++++++++++++++++++-------------------- 1 file changed, 111 insertions(+), 96 deletions(-) diff --git a/osxbuild/build-app.py b/osxbuild/build-app.py index 7a6e832f7..8ccec8c74 100755 --- a/osxbuild/build-app.py +++ b/osxbuild/build-app.py @@ -33,28 +33,33 @@ sipVer = '4.19.6' # NB: ArmoryMac.pro must also be kept up to date!!! pyQtVer = '4.12.1' # NB: When I'm upgraded, SIP usually has to be upgraded too. -LOGFILE = 'build-app.log.txt' -LOGPATH = path.abspath( path.join(os.getcwd(), LOGFILE)) -ARMORYDIR = '..' -OBJCDIR = path.join(os.getcwd(), 'objc_armory') -WORKDIR = path.join(os.getcwd(), 'workspace') -APPDIR = path.join(WORKDIR, 'Armory.app') # actually make it local -DLDIR = path.join(WORKDIR, 'downloads') -UNPACKDIR = path.join(WORKDIR, 'unpackandbuild') -INSTALLDIR = path.join(WORKDIR, 'install') -PREFIXBASEDIR = path.join(APPDIR, 'Contents/MacOS/py') -PYPREFIX = path.join(APPDIR, 'Contents/Frameworks/Python.framework/Versions/%s' % pyMajorVer) -PREFIXDIR = path.join(PREFIXBASEDIR, 'usr') -PYLIBPREFIX = path.join(PYPREFIX, 'lib') -PYINCPREFIX = path.join(PYPREFIX, 'include/python%s' % pyMajorVer) -PYBINARY = path.join(PYPREFIX, 'Resources/Python.app/Contents/MacOS/Python') -PYSITEPKGS = path.join(PYLIBPREFIX, 'python%s/site-packages' % pyMajorVer) -MAKEFLAGS = '-j4' +# Used only if compiling a version that supports armoryd +twistedVer = '17.9.0' +twistedSubdir = 'a2/37/298f9547606c45d75aa9792369302cc63aa4bbcf7b5f607560180dd099d2' + +# Various paths and build materials related to Armory. +LOGFILE = 'build-app.log.txt' +LOGPATH = path.abspath( path.join(os.getcwd(), LOGFILE)) +OBJCDIR = path.join(os.getcwd(), 'objc_armory') +WORKDIR = path.join(os.getcwd(), 'workspace') +APPBASE = path.join(WORKDIR, 'Armory.app') # actually make it local +WORKDLDIR = path.join(WORKDIR, 'downloads') +WORKUNPACKDIR = path.join(WORKDIR, 'unpackandbuild') +WORKINSTALLDIR = path.join(WORKDIR, 'install') +ARMORYCODEBASE = path.join(APPBASE, 'Contents/MacOS/py') +PYFRAMEBASE = path.join(APPBASE, 'Contents/Frameworks/Python.framework/Versions/%s' % pyMajorVer) +PYLIBPREFIX = path.join(PYFRAMEBASE, 'lib') +PYINCPREFIX = path.join(PYFRAMEBASE, 'include/python%s' % pyMajorVer) +PYBINARY = path.join(PYFRAMEBASE, 'Resources/Python.app/Contents/MacOS/Python') +PYSITEPKGS = path.join(PYLIBPREFIX, 'python%s/site-packages' % pyMajorVer) +MAKEFLAGS = '-j4' # Autotools needs some TLC to make Python happy. -CONFIGFLAGS = '--with-macosx-version-min=%s LIBS=\"-L%s\" PYTHON=\"%s\" PYTHON_LDFLAGS=\"-L%s\" PYTHON_CPPFLAGS=\"-I%s\" PYTHON_EXTRA_LIBS=\"-u _PyMac_Error %s/Python\"' % (minOSXVer, PYLIBPREFIX, PYBINARY, PYLIBPREFIX, PYINCPREFIX, PYPREFIX) +CONFIGFLAGS = '--with-macosx-version-min=%s LIBS=\"-L%s\" PYTHON=\"%s\" PYTHON_LDFLAGS=\"-L%s\" PYTHON_CPPFLAGS=\"-I%s\" PYTHON_EXTRA_LIBS=\"-u _PyMac_Error %s/Python\"' % (minOSXVer, PYLIBPREFIX, PYBINARY, PYLIBPREFIX, PYINCPREFIX, PYFRAMEBASE) -QTBUILTFLAG = path.join(UNPACKDIR, 'qt/qt_install_success.txt') +# Susceptible to build failures. Would fix, but prereqs are going away. Leave +# alone since it'll be gone soon anyway. +QTBUILTFLAG = path.join(WORKUNPACKDIR, 'qt/qt_install_success.txt') # If no arguments specified, then do the minimal amount of work necessary # Assume that only one flag is specified. These should be @@ -62,7 +67,7 @@ parser.add_option('--fromscratch', dest='fromscratch', default=False, action='store_true', help='Remove all prev-downloaded: redownload and rebuild all') parser.add_option('--rebuildall', dest='rebuildall', default=False, action='store_true', help='Remove all prev-built; no redownload, only rebuild') parser.add_option('--compapponly', dest='compapponly', default=False, action='store_true', help='Recompile Armory, not the 3rd party code') -parser.add_option('--cleanupapp', dest='cleanupapp', default=False, action='store_true', help='Delete Python files in the compiled application') +parser.add_option('--armoryd', dest='armoryd', default=False, action='store_true', help='Add files to allow armoryd to run') (CLIOPTS, CLIARGS) = parser.parse_args() ######################################################## @@ -73,7 +78,7 @@ def logprint(s): f.write(s if s.endswith('\n') else s+'\n') # Even if it's already built, we'll always "make install" and then -# set a bunch of environment variables (INSTALLDIR is wiped on every +# set a bunch of environment variables (WORKINSTALLDIR is wiped on every # run of this script, so all "make install" steps need to be re-run). # Variables placed out here to make compile-only option feasible. # Qt5 may require QMAKESPEC to change. @@ -81,10 +86,10 @@ def logprint(s): oldDYLDPath = ':'+os.environ['DYLD_FRAMEWORK_PATH'] except KeyError: oldDYLDPath = '' -qtInstDir = path.join(INSTALLDIR, 'qt') +qtInstDir = path.join(WORKINSTALLDIR, 'qt') qtBinDir = path.join(qtInstDir, 'bin') -qtBuildDir = path.join(UNPACKDIR, 'qt-everywhere-opensource-src-%s' % qtVer) -frmpath = path.join(APPDIR, 'Contents/Frameworks') +qtBuildDir = path.join(WORKUNPACKDIR, 'qt-everywhere-opensource-src-%s' % qtVer) +frmpath = path.join(APPBASE, 'Contents/Frameworks') os.environ['PATH'] = '%s:%s' % (qtBinDir, os.environ['PATH']) os.environ['DYLD_FRAMEWORK_PATH'] = '%s:%s' % (frmpath, oldDYLDPath) os.environ['QTDIR'] = qtInstDir @@ -109,21 +114,24 @@ def main(): os.remove(LOGFILE) if not CLIOPTS.compapponly: - delete_prev_data(CLIOPTS) + delete_prereq_data(CLIOPTS) makedir(WORKDIR) - makedir(DLDIR) - makedir(UNPACKDIR) - makedir(INSTALLDIR) + makedir(WORKDLDIR) + makedir(WORKUNPACKDIR) + makedir(WORKINSTALLDIR) + # Download Armory prerequisites for pkgname, fname, url, ID in distfiles: logprint('\n\n') downloadPkg(pkgname, fname, url, ID) - logprint("\n\nALL DOWNLOADS COMPLETED.\n\n") - if not CLIOPTS.compapponly: + if not os.path.isdir(APPBASE): make_empty_app() + make_resources() + + if not CLIOPTS.compapponly: compile_python() compile_pip() compile_libpng() @@ -131,13 +139,14 @@ def main(): compile_sip() compile_pyqt() compile_psutil() - make_resources() + if CLIOPTS.armoryd: + compile_twisted() compile_armory() compile_objc_library() cleanup_app() # Force Finder to update the Icon - execAndWait("touch " + APPDIR) + execAndWait("touch " + APPBASE) make_targz() # Show the final app size. @@ -253,7 +262,7 @@ def getTarUnpackPath(tarName, inDir=None): return theDir ################################################################################ -def unpack(tarName, fromDir=DLDIR, toDir=UNPACKDIR, overwrite=False): +def unpack(tarName, fromDir=WORKDLDIR, toDir=WORKUNPACKDIR, overwrite=False): """ This is not a versatile function. It expects tar or zip files with a single unpack directory. I will expand this function as necessary if we @@ -302,7 +311,7 @@ def unpack(tarName, fromDir=DLDIR, toDir=UNPACKDIR, overwrite=False): return newStuff[0] if len(newStuff)==1 else newStuff ################################################################################ -def downloadPkg(pkgname, fname, url, ID, toDir=DLDIR): +def downloadPkg(pkgname, fname, url, ID, toDir=WORKDLDIR): myfile = path.join(toDir, fname) doDL = True @@ -374,6 +383,11 @@ def downloadPkg(pkgname, fname, url, ID, toDir=DLDIR): "https://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-%s/PyQt4_gpl_mac-%s.tar.gz" % (pyQtVer, pyQtVer), \ '028f3fc979428687e8e8fd78288b41f1b5735a7c' ] ) +distfiles.append( [ 'Twisted', \ + "Twisted-%s.tar.bz2" % twistedVer, \ + "https://files.pythonhosted.org/packages/%s/Twisted-%s.tar.bz2" % (twistedSubdir, twistedVer), \ + "a218e69ab51b5c6b632043f91aed98bc92083a90" ] ) + # Now repack the information in distfiles tarfilesToDL = {} for d in distfiles: @@ -382,13 +396,13 @@ def downloadPkg(pkgname, fname, url, ID, toDir=DLDIR): ######################################################## def make_empty_app(): 'Make the empty .app bundle structure' - makedir(APPDIR) - makedir(path.join(APPDIR,'Contents')) - makedir(path.join(APPDIR,'Contents/MacOS')) - makedir(path.join(APPDIR,'Contents/MacOS/py')) - makedir(path.join(APPDIR,'Contents/Frameworks')) - makedir(path.join(APPDIR,'Contents/Resources')) - makedir(path.join(APPDIR,'Contents/Dependencies')) + makedir(APPBASE) + makedir(path.join(APPBASE,'Contents')) + makedir(path.join(APPBASE,'Contents/MacOS')) + makedir(path.join(APPBASE,'Contents/MacOS/py')) + makedir(path.join(APPBASE,'Contents/Frameworks')) + makedir(path.join(APPBASE,'Contents/Resources')) + makedir(path.join(APPBASE,'Contents/Dependencies')) ######################################################## def compile_python(): @@ -415,23 +429,23 @@ def compile_python(): with open(modSetupFile, "w") as origFile: origFile.write(setupText) - frameDir = path.join(APPDIR, 'Contents/Frameworks') + frameDir = path.join(APPBASE, 'Contents/Frameworks') execAndWait('./configure CFLAGS=-I%s/include CPPFLAGS=-I%s/include LDFLAGS=\"-L%s/lib -lssl -lcrypto\" --enable-ipv6 --prefix=%s --enable-framework="%s"' % \ - (opensslPath, opensslPath, opensslPath, INSTALLDIR, frameDir), cwd=bldPath) + (opensslPath, opensslPath, opensslPath, WORKINSTALLDIR, frameDir), cwd=bldPath) # make execAndWait('make %s' % MAKEFLAGS, cwd=bldPath) - execAndWait('make install PYTHONAPPSDIR=%s' % INSTALLDIR, cwd=bldPath) + execAndWait('make install PYTHONAPPSDIR=%s' % WORKINSTALLDIR, cwd=bldPath) # Update $PATH var - newPath = path.join(PYPREFIX, 'bin') + newPath = path.join(PYFRAMEBASE, 'bin') os.environ['PATH'] = '%s:%s' % (newPath, os.environ['PATH']) logprint('PATH is now %s' % os.environ['PATH']) ######################################################## def compile_pip(): logprint('Installing pip and setuptools') - pipexe = path.join(PYPREFIX, 'bin/pip') + pipexe = path.join(PYFRAMEBASE, 'bin/pip') if path.exists(pipexe): logprint('Pip already installed') else: @@ -451,7 +465,7 @@ def compile_pip(): def compile_libpng(): logprint('Installing libpng') dylib = 'libpng16.16.dylib' - target = path.join(APPDIR, 'Contents/Dependencies', dylib) + target = path.join(APPBASE, 'Contents/Dependencies', dylib) if path.exists(target): logprint('libpng already installed.') else: @@ -470,13 +484,13 @@ def compile_qt(): # Already cloned to the qtDLDir, then tar it and move the dir to # qtBuildDir. Then we will build inside the qtBuildDir, using qtInstDir # as the prefix. - qtDLDir = path.join(DLDIR, 'qt') - qtBuildDir = path.join(UNPACKDIR, 'qt-everywhere-opensource-src-%s' % qtVer) - qtInstDir = path.join(INSTALLDIR, 'qt') - qtTarFile = path.join(DLDIR, 'qt-everywhere-opensource-src-%s.tar.gz' % qtVer) + qtDLDir = path.join(WORKDLDIR, 'qt') + qtBuildDir = path.join(WORKUNPACKDIR, 'qt-everywhere-opensource-src-%s' % qtVer) + qtInstDir = path.join(WORKINSTALLDIR, 'qt') + qtTarFile = path.join(WORKDLDIR, 'qt-everywhere-opensource-src-%s.tar.gz' % qtVer) # If we did a fresh download, it's already uncompressed in DLDir. Move it - # to where it should be in the UNPACKDIR + # to where it should be in the WORKUNPACKDIR if path.exists(qtDLDir): if path.exists(qtBuildDir): removetree(qtBuildDir) @@ -530,11 +544,11 @@ def install_qt(): qtconf = path.join(qtBinDir, 'qt.conf') execAndWait('make install', cwd=qtBuildDir) - newcwd = path.join(APPDIR, 'Contents/Frameworks') + newcwd = path.join(APPBASE, 'Contents/Frameworks') for mod in ['QtCore', 'QtGui', 'QtNetwork']: src = path.join(qtInstDir, 'lib', mod+'.framework') - dst = path.join(APPDIR, 'Contents/Frameworks', mod+'.framework') + dst = path.join(APPBASE, 'Contents/Frameworks', mod+'.framework') if path.exists(dst): removetree(dst) copytree(src, dst) @@ -553,15 +567,15 @@ def compile_sip(): sipPath = unpack(tarfilesToDL['sip']) command = 'python configure.py' command += ' --destdir="%s"' % PYSITEPKGS - command += ' --bindir="%s/bin"' % PYPREFIX - command += ' --incdir="%s/include"' % PYPREFIX - command += ' --sipdir="%s/share/sip"' % PYPREFIX + command += ' --bindir="%s/bin"' % PYFRAMEBASE + command += ' --incdir="%s/include"' % PYFRAMEBASE + command += ' --sipdir="%s/share/sip"' % PYFRAMEBASE command += ' --deployment-target=%s' % minOSXVer execAndWait(command, cwd=sipPath) execAndWait('make %s' % MAKEFLAGS, cwd=sipPath) # Must run "make install" again even if it was previously built (since - # the APPDIR and INSTALLDIR are wiped every time the script is run) + # the APPBASE and WORKINSTALLDIR are wiped every time the script is run) execAndWait('make install', cwd=sipPath) ######################################################## @@ -573,13 +587,13 @@ def compile_pyqt(): logprint('PyQt4 is already installed.') else: pyqtPath = unpack(tarfilesToDL['pyqt']) - incDir = path.join(PYPREFIX, 'include') + incDir = path.join(PYFRAMEBASE, 'include') execAndWait('python ./configure-ng.py --confirm-license --sip-incdir="%s"' % incDir, cwd=pyqtPath) execAndWait('make %s' % MAKEFLAGS, cwd=pyqtPath) # Need to add pyrcc4 to the PATH execAndWait('make install', cwd=pyqtPath) - pyrccPath = path.join(UNPACKDIR, 'PyQt_mac_gpl-%s/pyrcc' % pyQtVer) + pyrccPath = path.join(WORKUNPACKDIR, 'PyQt_mac_gpl-%s/pyrcc' % pyQtVer) os.environ['PATH'] = '%s:%s' % (pyrccPath, os.environ['PATH']) ######################################################## @@ -593,13 +607,24 @@ def compile_psutil(): psPath = unpack(tarfilesToDL['psutil']) execAndWait(command, cwd=psPath) +######################################################## +def compile_twisted(): + logprint('Installing python-twisted') + + if glob.glob(PYSITEPKGS + '/Twisted*'): + logprint('Twisted already installed') + else: + command = "python -s setup.py --no-user-cfg install --force --verbose" + twpath = unpack(tarfilesToDL['Twisted']) + execAndWait(command, cwd=twpath) + ######################################################## def compile_armory(): logprint('Compiling and installing Armory') # Always compile - even if already in app - armoryAppScript = path.join(APPDIR, 'Contents/MacOS/Armory') - armorydAppScript = path.join(APPDIR, 'Contents/MacOS/armoryd') - armoryDB = path.join(APPDIR, 'Contents/MacOS/ArmoryDB') + armoryAppScript = path.join(APPBASE, 'Contents/MacOS/Armory') + armorydAppScript = path.join(APPBASE, 'Contents/MacOS/armoryd') + armoryDB = path.join(APPBASE, 'Contents/MacOS/ArmoryDB') currentDir = os.getcwd() os.chdir("..") execAndWait('python update_version.py') @@ -607,7 +632,7 @@ def compile_armory(): execAndWait('./autogen.sh', cwd='..') execAndWait('./configure %s' % CONFIGFLAGS, cwd='..') execAndWait('make clean', cwd='..') - execAndWait('make DESTDIR="%s" install %s' % (PREFIXBASEDIR, MAKEFLAGS), cwd='..') + execAndWait('make DESTDIR="%s" install %s' % (ARMORYCODEBASE, MAKEFLAGS), cwd='..') copyfile('Armory-script.sh', armoryAppScript) copyfile('armoryd-script.sh', armorydAppScript) execAndWait('chmod +x "%s"' % armoryAppScript) @@ -636,7 +661,7 @@ def compile_objc_library(): ######################################################## def make_resources(): "Populate the Resources folder." - cont = path.join(APPDIR, 'Contents') + cont = path.join(APPBASE, 'Contents') copyfile('Info.plist', cont) icnsArm = '../img/armory_icon_fullres.icns' @@ -647,15 +672,12 @@ def make_resources(): def cleanup_app(): "Try to remove as much unnecessary junk as possible." print "Removing Python test-suite." - testdir = path.join(PYPREFIX, "lib/python%s/test" % pyMajorVer) + testdir = path.join(PYFRAMEBASE, "lib/python%s/test" % pyMajorVer) if path.exists(testdir): removetree(testdir) - print "Removing .pyo and unneeded .py files." - if CLIOPTS.cleanupapp: - remove_python_files(PYPREFIX, False) - else: - remove_python_files(PYPREFIX) - remove_python_files(PREFIXBASEDIR, False) + + print "Removing .pyo and .pyc files." + remove_python_files(ARMORYCODEBASE) ######################################################## def make_targz(): @@ -685,49 +707,42 @@ def show_app_size(): # following command is fast *and* accurate. Don't touch without good reasons! "Show the size of the app." execAndWait('ls -lR %s | grep -v \'^d\' | awk \'{total += $5} END ' \ - '{print \"Total size of Armory:\", total, \"bytes\"}\'' % APPDIR) + '{print \"Total size of Armory:\", total, \"bytes\"}\'' % APPBASE) sys.stdout.flush() ######################################################## -def remove_python_files(top, removePy=True): - """Remove .pyo files and, if desired, any .py files where the .pyc file exists.""" +def remove_python_files(top): + """Remove .pyo and .pyc files.""" n_pyo = 0 - n_py_rem = 0 - n_py_kept = 0 + n_pyc = 0 + for (dirname, dirs, files) in os.walk(top): for f in files: prename, ext = path.splitext(f) if ext == '.pyo': removefile(path.join(dirname, f)) n_pyo += 1 - elif ext == '.py': - if removePy: - if (f + 'c') in files: - removefile(path.join(dirname, f)) - n_py_rem += 1 - else: - n_py_kept += 1 - else: - if (f + 'c') in files: - removefile(path.join(dirname, (f + 'c'))) - n_py_kept += 1 - logprint("Removes %i .py files (kept %i)." % (n_py_rem, n_py_kept)) + elif ext == '.pyc': + removefile(path.join(dirname, f)) + n_pyc += 1 + + logprint("Removed %i .pyo and %i .pyc files." % (n_pyo, n_pyc)) ######################################################## -def delete_prev_data(opts): +def delete_prereq_data(opts): # If we ran this before, we should have a qt dir here - prevQtDir = path.join(UNPACKDIR, 'qt') + prevQtDir = path.join(WORKUNPACKDIR, 'qt') # Always remove previously-built application files - removetree(APPDIR) - removetree(INSTALLDIR) + removetree(APPBASE) + removetree(WORKINSTALLDIR) # When building from scratch if opts.fromscratch: - removetree(UNPACKDIR) # Clear all unpacked tar files - removetree(DLDIR) # Clear even the downloaded files + removetree(WORKUNPACKDIR) # Clear all unpacked tar files + removetree(WORKDLDIR) # Clear even the downloaded files elif opts.rebuildall: - removetree(UNPACKDIR) + removetree(WORKUNPACKDIR) else: logprint('Using all packages previously downloaded and built') From 8a7f18a4991561b8e17e48225eb27d31ee05331b Mon Sep 17 00:00:00 2001 From: goatpig Date: Sat, 13 Jan 2018 23:56:34 +0100 Subject: [PATCH 08/21] bump version --- armoryengine/ArmoryUtils.py | 2 +- configure.ac | 2 +- cppForSwig/BitcoinP2P.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/armoryengine/ArmoryUtils.py b/armoryengine/ArmoryUtils.py index 0f1ab391d..1dc202d24 100644 --- a/armoryengine/ArmoryUtils.py +++ b/armoryengine/ArmoryUtils.py @@ -68,7 +68,7 @@ LEVELDB_HEADERS = 'leveldb_headers' # Version Numbers -BTCARMORY_VERSION = (0, 96, 3, 991) # (Major, Minor, Bugfix, AutoIncrement) +BTCARMORY_VERSION = (0, 96, 3, 992) # (Major, Minor, Bugfix, AutoIncrement) PYBTCWALLET_VERSION = (1, 35, 0, 0) # (Major, Minor, Bugfix, AutoIncrement) # ARMORY_DONATION_ADDR = '1ArmoryXcfq7TnCSuZa9fQjRYwJ4bkRKfv' diff --git a/configure.ac b/configure.ac index 51516dd53..b89c10f72 100644 --- a/configure.ac +++ b/configure.ac @@ -1,5 +1,5 @@ AC_PREREQ(2.60) -AC_INIT([BitcoinArmory], [0.96.3.991], [moothecowlord@gmail.com]) +AC_INIT([BitcoinArmory], [0.96.3.992], [moothecowlord@gmail.com]) AM_INIT_AUTOMAKE([1.10 subdir-objects foreign -Wall -Werror]) diff --git a/cppForSwig/BitcoinP2P.cpp b/cppForSwig/BitcoinP2P.cpp index e692eead9..5be7f7dfe 100644 --- a/cppForSwig/BitcoinP2P.cpp +++ b/cppForSwig/BitcoinP2P.cpp @@ -936,7 +936,7 @@ void BitcoinP2P::connectLoop(void) version.setVersionHeaderIPv4(70012, services, timestamp, node_addr_, clientsocketaddr); - version.userAgent_ = "Armory:0.96.3.991"; + version.userAgent_ = "Armory:0.96.3.992"; version.startHeight_ = -1; sendMessage(move(version)); From 411b92c0dc7d4be143a8f58f7d526c58349fa3e9 Mon Sep 17 00:00:00 2001 From: "Jeffrey I. Schiller" Date: Thu, 8 Feb 2018 13:51:29 -0500 Subject: [PATCH 09/21] Two quick typo corrections --- ArmoryQt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ArmoryQt.py b/ArmoryQt.py index 9aa899224..e1fdd6179 100755 --- a/ArmoryQt.py +++ b/ArmoryQt.py @@ -3529,7 +3529,7 @@ def clickReceiveCoins(self): if len(self.walletMap)==0: reply = QMessageBox.information(self, self.tr('No Wallets!'), self.tr( 'You have not created any wallets which means there is ' - 'nowhere to store you bitcoins! Would you like to ' + 'nowhere to store your bitcoins! Would you like to ' 'create a wallet now?'), \ QMessageBox.Yes | QMessageBox.No) if reply==QMessageBox.Yes: @@ -4387,7 +4387,7 @@ def GetDashStateText(self, mgmtMode, state): '

' 'Now would be a good time to make paper (or digital) backups of ' 'your wallet(s) if you have not done so already! You are protected ' - 'forever from hard-drive loss, or forgetting you password. ' + 'forever from hard-drive loss, or forgetting your password. ' 'If you do not have a backup, you could lose all of your ' 'Bitcoins forever!', "", len(self.walletMap)) From 5df59339c1e23954fa69d39a1a6a44ae7341c21b Mon Sep 17 00:00:00 2001 From: Douglas Roark Date: Wed, 17 Jan 2018 18:37:10 -0800 Subject: [PATCH 10/21] Add ArmoryDB README Basic info about the binary used to control Armory databases. --- README_ArmoryDB.md | 82 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 README_ArmoryDB.md diff --git a/README_ArmoryDB.md b/README_ArmoryDB.md new file mode 100644 index 000000000..411d490da --- /dev/null +++ b/README_ArmoryDB.md @@ -0,0 +1,82 @@ +# ArmoryDB +ArmoryDB is a binary used by Armory. The code is written in C++11 and is used to control the [LMDB](http://symas.com/mdb/) databases used by Armory. These databases contain information about the wallets/lockboxes in Armory and information about transactions relevant to the wallets. + +ArmoryDB is automatically called whenever Armory starts up; the user needs not intervene. Only advanced users who know what they're doing need any further. + +## ArmoryDB usage +ArmoryDB works by reading the blockchain downloaded by Bitcoin Core and finding any transactions relevant to the wallets loaded into Armory. This means that the entire blockchain must be rescanned whenever a new wallet or lockbox is loaded. Once a wallet/lockbox has been loaded and the blockchain fully scanned for that wallet, ArmoryDB will keep an eye on the blockchain. Any transactions relevant to the addresses controlled by wallets/lockboxes will be resolved. In addition, as Armory builds its own mempool by talking to the Core node, any relevant zero-confirmation transactions will be resolved by ArmoryDB. + +As of v0.96.4, Armory calls ArmoryDB using a particular set of flags. The Armory Python log (`armorylog.txt`) shows attempts at executing ArmoryDB using some default parameters, assuming that `/home/snakamoto` is the user's root directory. + +``` +2018-01-13 13:32:17 (WARNING) -- SDM.py:396 - Spawning DB with command: /home/snakamoto/Armory/ArmoryDB --db-type="DB_FULL" --cookie --satoshi-datadir="/home/snakamoto/.bitcoin/blocks" --datadir="/home/snakamoto/Armory/" --dbdir="/home/snakamoto/Armory/databases" +2018-01-13 13:32:17 (INFO) -- ArmoryUtils.py:679 - Executing popen: ['/home/snakamoto/Armory/ArmoryDB', '--db-type="DB_FULL"', '--cookie', '--satoshi-datadir="/home/snakamoto/.bitcoin/blocks"', '--datadir="/home/snakamoto/Armory/"', '--dbdir="/home/snakamoto/Armory/databases"'] +``` + +The flags are explained below, as seen in the Armory source code. By default, like Armory, ArmoryDB works on the mainnet network. + +* cookie: Create a cookie file holding a random authentication key to allow local clients to make use of elevated commands (e.g., `shutdown`). (Default: False) +* datadir: Path to the Armory data folder. (Default: Same as Armory) +* db-type: Sets the db type. Database type cannot be changed in between Armory runs. Once a database has been built with a certain type, the database will always function according to the initial type; specifying another type will do nothing. Changing the database type requires rebuilding the database. (Default: DB\_FULL) +* dbdir: Path to folder containing the Armory database file directory. If empty, a new database will be created. (Default: Same as Armory) +* satoshi-datadir: Path to blockchain data folder (blkXXXXX.dat files). (Default: Same as Armory) + +The database types are as follows: + +* DB\_BARE: Tracks wallet history only. Smallest DB, as the DB doesn't resolve a wallet's relevant transaction hashes until requested. (In other words, database accesses will be relatively slow.) This was the default database type in Armory v0.94. +* DB\_FULL: Tracks wallet history and resolves all relevant transaction hashes. (In other words, the database can instantly pull up relevant transaction data). ~1GB minimum size for the database. Default database type as of v0.95. +* DB\_SUPER: Tracks the entire blockchain history. Any transaction hash can be instantly resolved into its relevant data. Not fully implemented yet, and the database will be at least ~100GB large. + +There are additional flags. + +* checkchain: A test mode of sorts. It checks all the signatures in the blockchain. (Default: False) +* clear\_mempool: Delete all zero confirmation transactions from the database. (Default: False) +* fcgi-port: Sets the database listening port. The database listens to external connections (e.g., from Armory) via FCGI and can be placed behind an HTTP daemon in order to obtain remote access to ArmoryDB. (Default: 9001 (mainnet) / 19001 (testnet) / 19002 (regtest)) +* listen-all: Listen to all incoming IPs (not just localhost). (Default: False) +* ram-usage: Defines the RAM use during database scan operations. One point averages 128MB of RAM (without accounting for the base amount, ~400MB). Can't be lower than one point. Can be changed in between Armory runs. (Default: 50) +* rebuild: Delete all DB data, and build the database and scan the blockchain data from scratch. +* regtest: Run database against the regression test network. +* rescan: Delete all processed history data and rescan blockchain from the first block. +* rescanSSH: Delete balance and transaction count data, and rescan the data. Much faster than rescan or rebuild. +* satoshirpc-port: Set the P2P port of the Core node to which ArmoryDB will attempt to connect. (Default: Same as Armory) +* testnet: Run database against the testnet network. +* thread-count: Defines how many processing threads can be used during database builds and scans. Can't be lower than one thread. Can be changed in between Armory runs. (Default: The maximum number of available CPU threads.) +* zcthread-count: Defines the maximum number on threads the zero-confirmation (ZC) parser can create for processing incoming transcations from the Core network node. (Default: 100) + +Note that the flags may be added to the Armory root data directory in an ArmoryDB config file (`armorydb.conf`). The file will set the parameters every time ArmoryDB is started. Command line flags, including flags used by Armory, will override config values. (Changing Armory's default values will require recompilation.) An example file that mirrors the default parameters used by Armory can be seen below. Yeah! + +``` +db-type="DB_FULL" +cookie=1 +satoshi-datadir="/home/snakamoto/.bitcoin/blocks"" +datadir="/home/snakamoto/Armory/" +dbdir="/home/snakamoto/Armory/databases" +``` + +As always, check the source code for the most up-to-date information. + +## ArmoryDB connection design +ArmoryDB *must* run alongside the Bitcoin Core node. This is because ArmoryDB does a memory map on the blockchain files. This can only be done if ArmoryDB and the node are running on the same OS and, ideally, on the same storage device. The IP address of the Core node is hardcoded (localhost) and can't be changed without recompiling Armory (and changing the design at your own risk!). Only the node's port can be changed via the `satoshirpc-port` parameter. This design may be changed in the future. + +It is possible for Armory and other clients to talk to ArmoryDB remotely. Possibilities for reaching ArmoryDB include placing ArmoryDB behind an HTTP daemon or logging into the ArmoryDB machine remotely via VPN. Talking to ArmoryDB is done via JSON-encoded packets, as seen in the `armoryd` project. + +## Dependencies +* fcgi - Communication protocol - [Source of goatpig's libfcgi fork](https://github.com/toshic/libfcgi) +* Clang (macOS) - Installed automatically by [Xcode](https://developer.apple.com/xcode/) +* GNU Compiler Collection (Linux) - Install package `g++` +* LMDB - Database engine, modified to suit Armory's use cases - [LMDB page](http://symas.com/mdb/) +* Visual Studio compiler (Windows) - [Visual Studio page](https://www.visualstudio.com/) + +## Troubleshooting +Occasionally, a user may have trouble connecting to the Bitcoin Core node. Often, this is because a version of ArmoryDB from a previous run of Armory didn't shut down properly. If a user is unable to connect to the Core node, the following steps are recommended. + +* Shut down Armory. +* Check the operating system's task manager 30-60 seconds later. +* If ArmoryDB is still running, shut it down manually. +* Restart Armory. + +## License +Distributed under the MIT License. See the [LICENSE file](LICENSE) for more information. + +## Copyright +Copyright (C) 2017-2018 goatpig From f011334db96d0543eac7515c2eaff4abca673ab0 Mon Sep 17 00:00:00 2001 From: goatpig Date: Sun, 18 Mar 2018 12:03:34 +0100 Subject: [PATCH 11/21] fix save file dialog --- ArmoryQt.py | 2 +- qtdialogs.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ArmoryQt.py b/ArmoryQt.py index e1fdd6179..dfac1fc1e 100755 --- a/ArmoryQt.py +++ b/ArmoryQt.py @@ -2240,7 +2240,7 @@ def getFileSave(self, title='Save Wallet File', \ types = ffilter types.append('All files (*)') - typesStr = ';; '.join(types) + typesStr = ';; '.join(str(_type) for _type in types) # Open the native file save dialog and grab the saved file/path unless # we're in OS X, where native dialogs sometimes freeze. Looks like a Qt diff --git a/qtdialogs.py b/qtdialogs.py index fecbb78d9..b858ac78e 100644 --- a/qtdialogs.py +++ b/qtdialogs.py @@ -5092,7 +5092,7 @@ def saveToFile(self): wltID = self.wlt.uniqueIDB58 fn = self.main.getFileSave(title=self.tr('Save Key List'), \ ffilter=[self.tr('Text Files (*.txt)')], \ - defaultFilename=('keylist_%1_.txt' % wltID)) + defaultFilename=('keylist_%s_.txt' % wltID)) if len(fn) > 0: fileobj = open(fn, 'w') fileobj.write(str(self.txtBox.toPlainText())) From 8413afb653e1af18b68bce74c096f9496e226ea8 Mon Sep 17 00:00:00 2001 From: goatpig Date: Wed, 28 Mar 2018 02:43:13 +0200 Subject: [PATCH 12/21] fix wallet recovery gui & size error --- armoryengine/PyBtcWalletRecovery.py | 2 ++ qtdialogs.py | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/armoryengine/PyBtcWalletRecovery.py b/armoryengine/PyBtcWalletRecovery.py index fc356bf2e..215be2b6b 100644 --- a/armoryengine/PyBtcWalletRecovery.py +++ b/armoryengine/PyBtcWalletRecovery.py @@ -71,6 +71,8 @@ def __init__(self): self.UID = '' self.labelName = '' self.WalletPath = '' + + self.pybtcaddrSize = len(PyBtcAddress().serialize()) """ Modes: diff --git a/qtdialogs.py b/qtdialogs.py index b858ac78e..c422ea127 100644 --- a/qtdialogs.py +++ b/qtdialogs.py @@ -13173,7 +13173,8 @@ def ProcessWallet(self, mode=RECOVERMODE.Full): wlt = None wltPath = '' - if isinstance(self.walletList[0], str): + if isinstance(self.walletList[0], str) or \ + isinstance(self.walletList[0], unicode): wltPath = self.walletList[0] else: wlt = self.walletList[0] From e12f2ea8afd274d6018ccf938a8fbce4ea69ffc2 Mon Sep 17 00:00:00 2001 From: goatpig Date: Wed, 28 Mar 2018 02:44:00 +0200 Subject: [PATCH 13/21] display user comments for outgoing addresses in main ledger --- cppForSwig/LedgerEntry.cpp | 44 ++++++++++++++++++++++++++++++++++++++ ui/TxFrames.py | 33 +++++++++++++--------------- 2 files changed, 59 insertions(+), 18 deletions(-) diff --git a/cppForSwig/LedgerEntry.cpp b/cppForSwig/LedgerEntry.cpp index 7ddf95cdb..68e34b586 100644 --- a/cppForSwig/LedgerEntry.cpp +++ b/cppForSwig/LedgerEntry.cpp @@ -297,6 +297,50 @@ void LedgerEntry::computeLedgerMap(map &leMap, usesWitness, isChained); + /* + When signing a tx online, the wallet knows the txhash, therefor it can register all + comments on outgoing addresses under the txhash. + + When the tx is signed offline, there is no guarantee that the txhash will be known + when the offline tx is crafted. Therefor the comments for each outgoing address are + registered under that address only. + + In order for the GUI to be able to resolve outgoing address comments, the ledger entry + needs to carry those for pay out transactions + */ + + if (value < 0) + { + try + { + //grab tx by hash + auto&& payout_tx = db->getFullTxCopy(txioVec.first); + + //get scrAddr for each txout + for (unsigned i=0; i < payout_tx.getNumTxOut(); i++) + { + auto&& txout = payout_tx.getTxOutCopy(i); + scrAddrSet.insert(txout.getScrAddressStr()); + } + } + catch (exception&) + { + LMDBEnv::Transaction zctx; + db->beginDBTransaction(&zctx, ZERO_CONF, LMDB::ReadOnly); + + StoredTx stx; + if (!db->getStoredZcTx(stx, txioVec.first)) + { + LOGWARN << "failed to get tx for ledger parsing"; + } + else + { + for (auto& txout : stx.stxoMap_) + scrAddrSet.insert(txout.second.getScrAddress()); + } + } + } + le.scrAddrSet_ = move(scrAddrSet); leMap[txioVec.first] = le; } diff --git a/ui/TxFrames.py b/ui/TxFrames.py index b0f2443e0..81aeceed8 100644 --- a/ui/TxFrames.py +++ b/ui/TxFrames.py @@ -930,6 +930,8 @@ def unlockWallet(): ustx = self.validateInputsGetUSTX() if ustx: + self.updateUserComments() + if self.createUnsignedTxCallback and self.unsignedCheckbox.isChecked(): self.createUnsignedTxCallback(ustx) else: @@ -1206,16 +1208,6 @@ def createSetMaxButton(self, targWidgetID): ##################################################################### def makeRecipFrame(self, nRecip, is_opreturn=False): - ''' - prevNRecip = len(self.widgetTable) - nRecip = max(nRecip, 1) - inputs = [] - for i in range(nRecip): - if i < prevNRecip and i < nRecip: - inputs.append([]) - for widg in ['QLE_ADDR', 'QLE_AMT', 'QLE_COMM']: - inputs[-1].append(str(self.widgetTable[i][widg].text())) - ''' frmRecip = QFrame() frmRecip.setFrameStyle(QFrame.NoFrame) @@ -1303,13 +1295,6 @@ def createOpReturnWidget(widget_obj): for widget_obj in self.widgetTable: - ''' - if r < nRecip and r < prevNRecip: - self.widgetTable[r]['QLE_ADDR'].setText(inputs[r][0]) - self.widgetTable[r]['QLE_AMT'].setText(inputs[r][1]) - self.widgetTable[r]['QLE_COMM'].setText(inputs[r][2]) - ''' - subfrm = QFrame() subfrm.setFrameStyle(STYLE_RAISED) subLayout = QGridLayout() @@ -1605,7 +1590,19 @@ def findUtxo(utxoList): self.frmSelectedWlt.customUtxoList = utxolist self.frmSelectedWlt.altBalance = balance self.frmSelectedWlt.updateOnRBF(True) - + + ############################################################################# + def updateUserComments(self): + for row in range(len(self.widgetTable)): + widget_obj = self.widgetTable[row] + if 'OP_RETURN' in widget_obj: + continue + + addr_comment = str(self.widgetTable[row]['QLE_COMM'].text()) + addr_str = str(self.widgetTable[row]['QLE_ADDR'].text()) + addr160 = addrStr_to_hash160(addr_str)[1] + + self.wlt.setComment(addr160, addr_comment) ################################################################################ From 2b2eb0e0d9de94de6ede01553c955d5374cb04ac Mon Sep 17 00:00:00 2001 From: goatpig Date: Wed, 28 Mar 2018 20:59:11 +0200 Subject: [PATCH 14/21] fix comments and fee byte display in TxInfo dialog --- armoryengine/PyBtcWallet.py | 5 +++-- qtdialogs.py | 16 +++++++++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/armoryengine/PyBtcWallet.py b/armoryengine/PyBtcWallet.py index 0dde2666a..73b77f4e5 100644 --- a/armoryengine/PyBtcWallet.py +++ b/armoryengine/PyBtcWallet.py @@ -1894,8 +1894,9 @@ def getAddrCommentIfAvail(self, txHash): addrComments = [] for a160 in self.txAddrMap[txHash]: - if self.commentsMap.has_key(a160) and '[[' not in self.commentsMap[a160]: - addrComments.append(self.commentsMap[a160]) + h160 = a160[1:] + if self.commentsMap.has_key(h160) and '[[' not in self.commentsMap[h160]: + addrComments.append(self.commentsMap[h160]) return '; '.join(addrComments) diff --git a/qtdialogs.py b/qtdialogs.py index c422ea127..0b7c89117 100644 --- a/qtdialogs.py +++ b/qtdialogs.py @@ -5145,13 +5145,13 @@ def extractTxInfo(pytx, rcvTime=None): txcpp = TheBDM.bdv().getTxByHash(txHash) if txcpp.isInitialized(): hgt = txcpp.getBlockHeight() + txWeight = txcpp.getTxWeight() if hgt <= TheBDM.getTopBlockHeight(): headref = TheBDM.bdv().blockchain().getHeaderByHeight(hgt) txTime = unixTimeToFormatStr(headref.getTimestamp()) txBlk = headref.getBlockHeight() txIdx = txcpp.getBlockTxIndex() txSize = txcpp.getSize() - txWeight = txcpp.getTxWeight() else: if rcvTime == None: txTime = 'Unknown' @@ -5420,8 +5420,18 @@ def __init__(self, pytx, wlt, parent, main, mode=None, \ lbls.append([]) lbls[-1].append(self.main.createToolTipWidget(self.tr('Comment stored for this transaction in this wallet'))) lbls[-1].append(QLabel(self.tr('User Comment:'))) - if haveWallet and wlt.getComment(txHash): - lbls[-1].append(QRichLabel(wlt.getComment(txHash))) + txhash_bin = hex_to_binary(txHash, endOut=endianness) + comment_tx = '' + if haveWallet: + comment_tx = wlt.getComment(txhash_bin) + if not comment_tx: # and tempPyTx: + comment_tx = wlt.getAddrCommentIfAvail(txhash_bin) + #for txout in tempPyTx.outputs: + # script = script_to_scrAddr(txout.getScript()) + + + if comment_tx: + lbls[-1].append(QRichLabel(comment_tx)) else: lbls[-1].append(QRichLabel(self.tr('[None]'))) From b1fd7193e6ee03655ade0f221f18c881baeeefff Mon Sep 17 00:00:00 2001 From: goatpig Date: Thu, 29 Mar 2018 00:48:38 +0200 Subject: [PATCH 15/21] fix import address resolution in address book --- armorymodels.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/armorymodels.py b/armorymodels.py index d48bc3b63..94163d24e 100644 --- a/armorymodels.py +++ b/armorymodels.py @@ -992,7 +992,19 @@ def data(self, index, role=Qt.DisplayRole): if row>=len(self.addr160List): return QVariant('') addr = self.wlt.addrMap[self.addr160List[row]] - cppaddr = self.wlt.cppWallet.getAddrObjByIndex(addr.chainIndex) + addr_index = addr.chainIndex + + try: + if addr_index == -2: + addr_index = self.wlt.cppWallet.getAssetIndexForAddr(addr.getAddr160()) + index_import = self.wlt.cppWallet.convertToImportIndex(addr_index) + cppaddr = self.wlt.cppWallet.getImportAddrObjByIndex(index_import) + else: + cppaddr = self.wlt.cppWallet.getAddrObjByIndex(addr_index) + except: + LOGERROR('failed to grab address by index %d, original id: %d' % (addr_index, addr.chainIndex)) + return QVariant() + addr160 = cppaddr.getAddrHash() addrB58 = cppaddr.getScrAddr() chainIdx = addr.chainIndex+1 # user must get 1-indexed From 5067489d7df99e3d19d5ff2d8ea40bf8d985cbe1 Mon Sep 17 00:00:00 2001 From: goatpig Date: Thu, 29 Mar 2018 00:50:11 +0200 Subject: [PATCH 16/21] fix message signing issues bar message signing with p2sh addresses for now --- jasvet.py | 6 ++++-- ui/toolsDialogs.py | 13 +++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/jasvet.py b/jasvet.py index a168b3293..e9c099593 100644 --- a/jasvet.py +++ b/jasvet.py @@ -16,7 +16,7 @@ import CppBlockUtils from armoryengine.ArmoryUtils import getVersionString, BTCARMORY_VERSION, \ - ChecksumError + ChecksumError, ADDRBYTE FTVerbose=False @@ -196,8 +196,10 @@ def hash_160(public_key): md.update(hashlib.sha256(public_key).digest()) return md.digest() -def public_key_to_bc_address(public_key, v=0): +def public_key_to_bc_address(public_key, v=ADDRBYTE): h160 = hash_160(public_key) + if isinstance(v, str): + v = ord(v) return hash_160_to_bc_address(h160, v) def inverse_mod( a, m ): diff --git a/ui/toolsDialogs.py b/ui/toolsDialogs.py index f9486416e..593775e3d 100644 --- a/ui/toolsDialogs.py +++ b/ui/toolsDialogs.py @@ -118,6 +118,11 @@ def getPrivateKeyFromAddrInput(self): atype, addr160 = addrStr_to_hash160(str(self.addressLineEdit.text())) if atype==P2SHBYTE: LOGWARN('P2SH address requested') + QMessageBox.critical(self, self.tr('P2SH Address'), \ + self.tr('You are attempting to sign a message with a P2SH address.

This feature is not supported at the moment'), \ + QMessageBox.Ok) + return + walletId = self.main.getWalletForAddr160(addr160) wallet = self.main.walletMap[walletId] if wallet.useEncryption and wallet.isLocked: @@ -128,7 +133,7 @@ def getPrivateKeyFromAddrInput(self): self.tr('Cannot import private keys without unlocking wallet!'), \ QMessageBox.Ok) return - return wallet.addrMap[addr160].binPrivKey32_Plain.toBinStr() + return wallet.getAddrObjectForHash(addr160).binPrivKey32_Plain.toBinStr() def bareSignMessage(self): messageText = str(self.messageTextEdit.toPlainText()) @@ -236,13 +241,13 @@ def displayVerifiedBox(self, addrB58, messageString): # in the Message Signing/Verification dialog msg = '
'.join([line[:60]+ '...'*(len(line)>60) for line in msg.split('
')][:12]) MsgBoxCustom(MSGBOX.Good, self.tr('Verified!'), str(self.tr( - '%`' + '%1`' '
' '
' - '%1' + '%2' '
' '

' - 'Please make sure that the address above (%2...) matches the ' + 'Please make sure that the address above (%3...) matches the ' 'exact address you were expecting. A valid signature is meaningless ' 'unless it is made ' 'from a recognized address!').arg(ownerStr, msg, addrB58[:10]))) From 6ac513c54f00bed139e18ab82dff85eb807315f9 Mon Sep 17 00:00:00 2001 From: goatpig Date: Fri, 30 Mar 2018 18:26:17 +0200 Subject: [PATCH 17/21] put system tray notification on tx broadcast in try block --- ArmoryQt.py | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/ArmoryQt.py b/ArmoryQt.py index dfac1fc1e..d1a6998ea 100755 --- a/ArmoryQt.py +++ b/ArmoryQt.py @@ -5350,23 +5350,26 @@ def doTheSystemTrayThing(self): dispLines.append(self.tr('Amount: %1 BTC').arg(totalStr)) dispLines.append(self.tr('From: %2').arg(wltName)) elif le.getValue() < 0: - # Also display the address of where they went - txref = TheBDM.bdv().getTxByHash(le.getTxHash()) - nOut = txref.getNumTxOut() - recipStr = '' - for i in range(0, nOut): - script = txref.getTxOutCopy(i).getScript() - if pywlt.hasScrAddr(script_to_scrAddr(script)): - continue - if len(recipStr)==0: - recipStr = self.getDisplayStringForScript(script, 45)['String'] - else: - recipStr = self.tr('') - - title = self.tr('Bitcoins Sent!') - dispLines.append(unicode(self.tr('Amount: %1 BTC').arg(totalStr))) - dispLines.append(unicode(self.tr('From: %1').arg(wltName ))) - dispLines.append(unicode(self.tr('To: %1').arg(recipStr))) + try: + # Also display the address of where they went + txref = TheBDM.bdv().getTxByHash(le.getTxHash()) + nOut = txref.getNumTxOut() + recipStr = '' + for i in range(0, nOut): + script = txref.getTxOutCopy(i).getScript() + if pywlt.hasScrAddr(script_to_scrAddr(script)): + continue + if len(recipStr)==0: + recipStr = self.getDisplayStringForScript(script, 45)['String'] + else: + recipStr = self.tr('') + + title = self.tr('Bitcoins Sent!') + dispLines.append(unicode(self.tr('Amount: %1 BTC').arg(totalStr))) + dispLines.append(unicode(self.tr('From: %1').arg(wltName ))) + dispLines.append(unicode(self.tr('To: %1').arg(recipStr))) + except Exception as e: + LOGERROR('tx broadcast systray display failed with error: %s' % e) self.showTrayMsg(title, dispLines.join("\n"), \ QSystemTrayIcon.Information, 10000) From d669e948ee835c295335a9dc718a975de3d8869f Mon Sep 17 00:00:00 2001 From: Douglas Roark Date: Fri, 30 Mar 2018 11:31:43 -0700 Subject: [PATCH 18/21] TRIVIAL: Make logs slightly friendlier to read When ArmoryDB catches up with the node's blockchain, the percentage isn't expressed as a fraction of 100. Fix that. --- cppForSwig/BlockDataManagerConfig.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cppForSwig/BlockDataManagerConfig.cpp b/cppForSwig/BlockDataManagerConfig.cpp index 94623d1ce..31ae089f8 100644 --- a/cppForSwig/BlockDataManagerConfig.cpp +++ b/cppForSwig/BlockDataManagerConfig.cpp @@ -892,7 +892,7 @@ bool NodeChainState::processState( if (pct_int != prev_pct_int_) { - LOGINFO << "waiting on node sync: " << pct_ << "%"; + LOGINFO << "waiting on node sync: " << float(pct_ * 100.0) << "%"; prev_pct_int_ = pct_int; } From f256bc1e364afe0d7cdf515bed34413057f4924c Mon Sep 17 00:00:00 2001 From: goatpig Date: Fri, 30 Mar 2018 19:03:05 +0200 Subject: [PATCH 19/21] update changelog.txt --- changelog.txt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index ec1d46df3..3133655f3 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,4 +1,4 @@ -v0.96.4 +v0.96.4, released March 30th 2018 == Added == - Updated fee estimate query from node to improve estimateSmartFee call introduced in Bitcoin Core 0.15. - The block confirmation target slider now spans from 2 to 100 blocks (previously 2 to 6). @@ -33,11 +33,21 @@ v0.96.4 - Fixed fee calculation when checking "MAX" with SegWit UTXOs - The Coin control dialog will no longer spawn invisible - When creating a fragmented backup, fragments will no longer be cycled unecessarily + - Fixed imported address rendering in the Address Book dialog + - The Transaction Info dialog will now display address comments in the comment field if there is no comment + attached to the transaction itself + - The Transaction Info dialog will now properly display fee/byte fee for unconfirmed transactions + - The main transaction ledger will now display comments attached to outgoing addresses for each relevant transaction + - Fixed selecting an arbitrary wallet file in the Recovery Tool dialog. + +== Removed == + - You cannot sign messages with P2SH addresses. This functionality never existed in Armory to begin with, as it + did not produce single sig P2SH addresses prior to 0.96. It will be introduced in 0.97 == Minor == - You can now resize the Transaction Info dialog. -v0.96.3 released September 21st 2017 +v0.96.3, released September 21st 2017 == Vulnerability Fix == - Fragmented backups were using a faulty implementation of Shamir's Secret Sharing (SSS). One of the requirement of SSS security parameters is that the coefficients of the curve are chozen randomly. The implementation From fb77d4f9c4959f85688e953d5517c31cb9c77ab8 Mon Sep 17 00:00:00 2001 From: goatpig Date: Fri, 30 Mar 2018 20:44:46 +0200 Subject: [PATCH 20/21] bump version --- armoryengine/ArmoryUtils.py | 2 +- configure.ac | 2 +- cppForSwig/BitcoinP2P.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/armoryengine/ArmoryUtils.py b/armoryengine/ArmoryUtils.py index 1dc202d24..d2989998b 100644 --- a/armoryengine/ArmoryUtils.py +++ b/armoryengine/ArmoryUtils.py @@ -68,7 +68,7 @@ LEVELDB_HEADERS = 'leveldb_headers' # Version Numbers -BTCARMORY_VERSION = (0, 96, 3, 992) # (Major, Minor, Bugfix, AutoIncrement) +BTCARMORY_VERSION = (0, 96, 4, 0) # (Major, Minor, Bugfix, AutoIncrement) PYBTCWALLET_VERSION = (1, 35, 0, 0) # (Major, Minor, Bugfix, AutoIncrement) # ARMORY_DONATION_ADDR = '1ArmoryXcfq7TnCSuZa9fQjRYwJ4bkRKfv' diff --git a/configure.ac b/configure.ac index b89c10f72..7e1a59c60 100644 --- a/configure.ac +++ b/configure.ac @@ -1,5 +1,5 @@ AC_PREREQ(2.60) -AC_INIT([BitcoinArmory], [0.96.3.992], [moothecowlord@gmail.com]) +AC_INIT([BitcoinArmory], [0.96.4], [moothecowlord@gmail.com]) AM_INIT_AUTOMAKE([1.10 subdir-objects foreign -Wall -Werror]) diff --git a/cppForSwig/BitcoinP2P.cpp b/cppForSwig/BitcoinP2P.cpp index 5be7f7dfe..bd37c9d8f 100644 --- a/cppForSwig/BitcoinP2P.cpp +++ b/cppForSwig/BitcoinP2P.cpp @@ -936,7 +936,7 @@ void BitcoinP2P::connectLoop(void) version.setVersionHeaderIPv4(70012, services, timestamp, node_addr_, clientsocketaddr); - version.userAgent_ = "Armory:0.96.3.992"; + version.userAgent_ = "Armory:0.96.4"; version.startHeight_ = -1; sendMessage(move(version)); From fee1f91a3137ef1056e15cc606a186b0e508f84c Mon Sep 17 00:00:00 2001 From: goatpig Date: Sat, 31 Mar 2018 22:43:01 +0200 Subject: [PATCH 21/21] fix new tx systray notification --- ArmoryQt.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/ArmoryQt.py b/ArmoryQt.py index d1a6998ea..9efab20de 100755 --- a/ArmoryQt.py +++ b/ArmoryQt.py @@ -5345,22 +5345,19 @@ def doTheSystemTrayThing(self): # If coins were either received or sent from the loaded wlt/lbox dispLines = QStringList() totalStr = coin2strNZS(abs(le.getValue())) + title = None if le.getValue() > 0: title = self.tr('Bitcoins Received!') dispLines.append(self.tr('Amount: %1 BTC').arg(totalStr)) dispLines.append(self.tr('From: %2').arg(wltName)) elif le.getValue() < 0: try: - # Also display the address of where they went - txref = TheBDM.bdv().getTxByHash(le.getTxHash()) - nOut = txref.getNumTxOut() recipStr = '' - for i in range(0, nOut): - script = txref.getTxOutCopy(i).getScript() - if pywlt.hasScrAddr(script_to_scrAddr(script)): + for addr in le.getScrAddrList(): + if pywlt.hasScrAddr(addr): continue if len(recipStr)==0: - recipStr = self.getDisplayStringForScript(script, 45)['String'] + recipStr = hash160_to_addrStr(addr[1:], addr[0]) else: recipStr = self.tr('') @@ -5371,9 +5368,10 @@ def doTheSystemTrayThing(self): except Exception as e: LOGERROR('tx broadcast systray display failed with error: %s' % e) - self.showTrayMsg(title, dispLines.join("\n"), \ - QSystemTrayIcon.Information, 10000) - LOGINFO(title + '\n' + dispLines.join("\n")) + if title: + self.showTrayMsg(title, dispLines.join("\n"), \ + QSystemTrayIcon.Information, 10000) + LOGINFO(title + '\n' + dispLines.join("\n")) # Wait for 5 seconds before processing the next queue object. self.notifyBlockedUntil = RightNow() + 5