diff --git a/ArmoryQt.py b/ArmoryQt.py index 9aa899224..9efab20de 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 @@ -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)) @@ -5345,32 +5345,33 @@ 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: - # 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('') + try: + recipStr = '' + for addr in le.getScrAddrList(): + if pywlt.hasScrAddr(addr): + continue + if len(recipStr)==0: + recipStr = hash160_to_addrStr(addr[1:], addr[0]) + 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))) + 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) - 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 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 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/armoryengine/ArmoryUtils.py b/armoryengine/ArmoryUtils.py index 0f1ab391d..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, 991) # (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/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/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/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 diff --git a/changelog.txt b/changelog.txt index 6e5556083..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). @@ -30,11 +30,24 @@ 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 + - 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 diff --git a/configure.ac b/configure.ac index 50450ebb7..aa7fd58b0 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.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 e692eead9..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.991"; + version.userAgent_ = "Armory:0.96.4"; version.startHeight_ = -1; sendMessage(move(version)); 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; } 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; } //////////////////////////////////////////////////////////////////////////////// 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/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/osxbuild/build-app.py b/osxbuild/build-app.py index 7a6e832f7..193a7bdc4 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 @@ -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') diff --git a/qtdialogs.py b/qtdialogs.py index 0600ccef2..0b7c89117 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())) @@ -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]'))) @@ -10388,6 +10398,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 +10513,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) @@ -13166,7 +13183,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] 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"] }} 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): 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) ################################################################################ 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])))