From 28ee5d20f3aea19012e1d7604e3d5bfbbe8a949c Mon Sep 17 00:00:00 2001 From: goatpig Date: Sat, 27 Feb 2016 21:43:30 -0500 Subject: [PATCH] opt-in RBF detection and flagging --- ArmoryQt.py | 4 +- armorycolors.py | 2 + armoryengine/Transaction.py | 7 +- armorymodels.py | 17 ++- cppForSwig/BDM_supportClasses.cpp | 158 +++++++++++++++++++++--- cppForSwig/BDM_supportClasses.h | 2 + cppForSwig/BlockDataViewer.cpp | 11 ++ cppForSwig/BlockDataViewer.h | 2 + cppForSwig/BlockObj.cpp | 24 +++- cppForSwig/BlockObj.h | 9 ++ cppForSwig/BlockchainScanner.cpp | 12 ++ cppForSwig/LedgerEntry.cpp | 10 +- cppForSwig/StoredBlockObj.cpp | 42 +++++++ cppForSwig/StoredBlockObj.h | 2 + cppForSwig/cryptopp/cryptopp.vcxproj | 2 +- cppForSwig/gtest/CppBlockUtilsTests.cpp | 1 + cppForSwig/lmdb_wrapper.cpp | 12 +- cppForSwig/lmdb_wrapper.h | 5 + cppForSwig/txio.cpp | 1 + cppForSwig/txio.h | 3 + qtdialogs.py | 5 +- 21 files changed, 296 insertions(+), 35 deletions(-) diff --git a/ArmoryQt.py b/ArmoryQt.py index 5f48a2dd5..5394f762e 100755 --- a/ArmoryQt.py +++ b/ArmoryQt.py @@ -353,7 +353,7 @@ def updateProgress(val): self.ledgerView.hideColumn(LEDGERCOLS.TxHash) self.ledgerView.hideColumn(LEDGERCOLS.isCoinbase) self.ledgerView.hideColumn(LEDGERCOLS.toSelf) - self.ledgerView.hideColumn(LEDGERCOLS.DoubleSpend) + self.ledgerView.hideColumn(LEDGERCOLS.optInRBF) # Another table and model, for lockboxes @@ -3348,6 +3348,8 @@ def convertLedgerToTable(self, ledger, showSentToSelfAmt=True, wltIDIn=None): row.append(wltName) # Comment + if le.isOptInRBF() == True: + dispComment = "***MEMPOOL REPLACEABLE*** " + dispComment row.append(dispComment) # Amount diff --git a/armorycolors.py b/armorycolors.py index 452cf71f4..bc1f2795e 100644 --- a/armorycolors.py +++ b/armorycolors.py @@ -125,6 +125,8 @@ class ArbitraryStruct: pass Colors.TblWltMine = tweakColor(Colors.Background, '*', [0.95, 0.95, 1.3 ]) Colors.TblWltOffline = tweakColor(Colors.Background, '*', [0.85, 0.85, 1.35]) +Colors.optInRBF = tweakColor(Colors.Background, '*', [1.00, 0.10, 0.10]) + if(Colors.isDarkBkgd): Colors.LBtnNormalBG = Colors.Background Colors.LBtnHoverBG = tweakColor(Colors.Background, '+', [ +25, +25, 0]) diff --git a/armoryengine/Transaction.py b/armoryengine/Transaction.py index 68907a77d..aadaa36fa 100644 --- a/armoryengine/Transaction.py +++ b/armoryengine/Transaction.py @@ -655,7 +655,6 @@ def __init__(self): self.outputs = UNINITIALIZED self.lockTime = 0 self.thisHash = UNINITIALIZED - self.optInRBF = False def serialize(self): binOut = BinaryPacker() @@ -683,8 +682,7 @@ def unserialize(self, toUnpack): for i in xrange(numInputs): txin = PyTxIn().unserialize(txData); self.inputs.append( txin ) - if txin.intSeq < (2**32 - 1) - 1: - self.optInRBF = True + numOutputs = txData.get(VAR_INT) for i in xrange(numOutputs): self.outputs.append( PyTxOut().unserialize(txData) ) @@ -790,7 +788,8 @@ def pprintHex(self, nIndent=0): print binary_to_hex(bu.get(BINARY_CHUNK,scriptSz)) print binary_to_hex(bu.get(BINARY_CHUNK, 4)) - + def isRBF(self): + return TheBDM.bdv().isRBF(self.getHash()) # Use to identify status of individual sigs on an UnsignedTxINPUT diff --git a/armorymodels.py b/armorymodels.py index e2b9a95d2..51f995d18 100644 --- a/armorymodels.py +++ b/armorymodels.py @@ -27,7 +27,8 @@ WLTVIEWCOLS = enum('Visible', 'ID', 'Name', 'Secure', 'Bal') LEDGERCOLS = enum('NumConf', 'UnixTime', 'DateStr', 'TxDir', 'WltName', 'Comment', \ - 'Amount', 'isOther', 'WltID', 'TxHash', 'isCoinbase', 'toSelf', 'DoubleSpend') + 'Amount', 'isOther', 'WltID', 'TxHash', 'isCoinbase', 'toSelf', \ + 'optInRBF') ADDRESSCOLS = enum('ChainIdx', 'Address', 'Comment', 'NumTx', 'Balance') ADDRBOOKCOLS = enum('Address', 'WltID', 'NumSent', 'Comment') @@ -330,6 +331,10 @@ def data(self, index, role=Qt.DisplayRole): nConf = rowData[LEDGERCOLS.NumConf] wltID = rowData[LEDGERCOLS.WltID] wlt = self.main.walletMap.get(wltID) + optInRBF = rowData[LEDGERCOLS.optInRBF] + + if optInRBF == True: + abc = '' if wlt: wtype = determineWalletType(self.main.walletMap[wltID], self.main)[0] @@ -339,7 +344,7 @@ def data(self, index, role=Qt.DisplayRole): #LEDGERCOLS = enum( 'NumConf', 'UnixTime','DateStr', 'TxDir', # 'WltName', 'Comment', 'Amount', 'isOther', # 'WltID', 'TxHash', 'isCoinbase', 'toSelf', - # 'DoubleSpend') + # 'optInRBF') if role==Qt.DisplayRole: return QVariant(rowData[col]) @@ -353,7 +358,9 @@ def data(self, index, role=Qt.DisplayRole): elif role==Qt.DecorationRole: pass elif role==Qt.BackgroundColorRole: - if wtype==WLTTYPES.WatchOnly: + if optInRBF is True: + return QVariant( Colors.optInRBF ) + elif wtype==WLTTYPES.WatchOnly: return QVariant( Colors.TblWltOther ) elif wtype==WLTTYPES.Offline: return QVariant( Colors.TblWltOffline ) @@ -394,6 +401,10 @@ def data(self, index, role=Qt.DisplayRole): 'Bitcoin mining. These transactions take\n' '120 confirmations (approximately one day)\n' 'before they are available to be spent.') + elif optInRBF: + tooltipStr = ('This is a mempool replaceable transaction.' + ' Do not consider you have been sent these coins until' + ' this transaction has at least 1 confirmation.') else: tooltipStr = '%d/6 confirmations'%rowData[COL.NumConf] tooltipStr += ( '\n\nFor small transactions, 2 or 3\n' diff --git a/cppForSwig/BDM_supportClasses.cpp b/cppForSwig/BDM_supportClasses.cpp index ade01ee39..0d019e6aa 100644 --- a/cppForSwig/BDM_supportClasses.cpp +++ b/cppForSwig/BDM_supportClasses.cpp @@ -606,9 +606,89 @@ map> ZeroConfContainer::purge( } } - //build the set of invalidated zc dbKeys and delete them from db + //get all txhashes for the new blocks + set minedHashes; + auto bcPtr = db_->blockchain(); + try + { + const BlockHeader* lastKnownHeader = + &bcPtr->getHeaderByHash(lastParsedBlockHash_); + + while (!lastKnownHeader->isMainBranch()) + { + //trace back to the branch point + auto& bhash = lastKnownHeader->getPrevHash(); + lastKnownHeader = &bcPtr->getHeaderByHash(bhash); + } + + //get the next header + auto height = lastKnownHeader->getBlockHeight() + 1; + lastKnownHeader = &bcPtr->getHeaderByHeight(height); + + while (lastKnownHeader != nullptr) + { + //grab block + StoredHeader sbh; + db_->getStoredHeader(sbh, + lastKnownHeader->getBlockHeight(), + lastKnownHeader->getDuplicateID(), + false); + + //build up hash set + for (auto& stx : sbh.stxMap_) + minedHashes.insert(stx.second.thisHash_); + + //next block + auto& bhash = lastKnownHeader->getNextHash(); + lastKnownHeader = &bcPtr->getHeaderByHash(bhash); + } + } + catch (...) + { + } + vector keysToWrite, keysToDelete; + //compare minedHashes to allZCTxHashes_, mark keys for deletion + for (auto& minedHash : minedHashes) + { + auto iter = allZcTxHashes_.find(minedHash); + if (iter != allZcTxHashes_.end()) + { + keysToDelete.push_back(*iter); + allZcTxHashes_.erase(iter); + } + } + + for (auto& txiomap : txioMap) + { + for (auto& txio : txiomap.second) + { + if (!txio.second.isRBF()) + { + auto iter = allZcTxHashes_.find(txio.second.getTxHashOfOutput()); + if (iter != allZcTxHashes_.end()) + txio.second.setRBF(true); //flag all ZC of ZC as replaceable + } + + if (txio.second.isRBF()) + { + auto iter = txMap.find(txio.second.getTxHashOfOutput()); + if (iter != txMap.end()) + { + iter->second.setRBF(true); + continue; + } + + iter = txMap.find(txio.second.getTxHashOfInput()); + if (iter != txMap.end()) + iter->second.setRBF(true); + } + } + } + + + //build the set of invalidated zc dbKeys and delete them from db for (auto& tx : txMap_) { if (txMap.find(tx.first) == txMap.end()) @@ -677,12 +757,6 @@ map> ZeroConfContainer::purge( } return invalidatedKeys; - - /* - // Rewrite the zero-conf pool file - if (hashRmVec.size() > 0) - rewriteZeroConfFile(); - */ } /////////////////////////////////////////////////////////////////////////////// @@ -701,7 +775,7 @@ bool ZeroConfContainer::parseNewZC(function filter, without deleting any new ZC that may have been added during the process. Note: there is no concurency interference with purging the container - (for reorgs and new blocks), as they methods called by the BDM main thread. + (for reorgs and new blocks), as both methods are called by the BDM main thread. ***/ uint32_t nProcessed = 0; @@ -726,9 +800,10 @@ bool ZeroConfContainer::parseNewZC(function filter, if (txHashToDBKey_.find(txHash) != txHashToDBKey_.end()) continue; //already have this ZC - //LOGWARN << "new ZC transcation: " << txHash.toHexStr(); - { + allZcTxHashes_.insert(txHash); + keysToWrite.push_back(newZCPair.first); + map > newTxIO = ZCisMineBulkFilter(newZCPair.second, newZCPair.first, @@ -740,7 +815,6 @@ bool ZeroConfContainer::parseNewZC(function filter, txHashToDBKey_[txHash] = newZCPair.first; txMap_[newZCPair.first] = newZCPair.second; - keysToWrite.push_back(newZCPair.first); for (const auto& saTxio : newTxIO) { @@ -758,6 +832,33 @@ bool ZeroConfContainer::parseNewZC(function filter, } } + for (auto& txiomap : txioMap_) + { + for (auto& txio : txiomap.second) + { + if (!txio.second.isRBF()) + { + auto iter = allZcTxHashes_.find(txio.second.getTxHashOfOutput()); + if (iter != allZcTxHashes_.end()) + txio.second.setRBF(true); //flag all ZC of ZC as replaceable + } + + if (txio.second.isRBF()) + { + auto iter = txMap_.find(txio.second.getTxHashOfOutput()); + if (iter != txMap_.end()) + { + iter->second.setRBF(true); + continue; + } + + iter = txMap_.find(txio.second.getTxHashOfInput()); + if (iter != txMap_.end()) + iter->second.setRBF(true); + } + } + } + if (updateDb) { //write ZC in the new thread to guaranty we can get a RW tx @@ -773,7 +874,7 @@ bool ZeroConfContainer::parseNewZC(function filter, //check if newZCMap_ doesnt have new Txn if (nProcessed >= newZCMap_.size()) { - //clear map and release lock + //clear map newZCMap_.clear(); //break out of the loop @@ -781,7 +882,7 @@ bool ZeroConfContainer::parseNewZC(function filter, } //else search the new ZC container for unseen ZC - map::const_iterator newZcIter = newZCMap_.begin(); + auto newZcIter = newZCMap_.begin(); while (newZcIter != newZCMap_.begin()) { @@ -797,6 +898,8 @@ bool ZeroConfContainer::parseNewZC(function filter, nProcessed = 0; } + lastParsedBlockHash_ = db_->getTopBlockHash(); + return zcIsOurs; } @@ -871,6 +974,8 @@ ZeroConfContainer::ZCisMineBulkFilter(const Tx & tx, return processedTxIO; } + bool isRBF = tx.isRBF(); + uint8_t const * txStartPtr = tx.getPtr(); for (uint32_t iin = 0; iin& keysToWrite, for (auto& key : keysToWrite) { - StoredTx zcTx; - zcTx.createFromTx(txMap_[key], true, true); - db_->putStoredZC(zcTx, key); + auto iter = txMap_.find(key); + if (iter != txMap_.end()) + { + StoredTx zcTx; + zcTx.createFromTx(txMap_[key], true, true); + db_->putStoredZC(zcTx, key); + } + else + { + //if the key is not to be found in the txMap_, this is a ZC txhash + db_->putValue(ZERO_CONF, key, BinaryData()); + } } for (auto& key : keysToDelete) @@ -1146,6 +1263,11 @@ void ZeroConfContainer::loadZeroConfMempool( //TxOut, ignore it continue; } + else if (zcKey.getSize() == 32) + { + //tx hash + allZcTxHashes_.insert(zcKey); + } else { //shouldn't hit this diff --git a/cppForSwig/BDM_supportClasses.h b/cppForSwig/BDM_supportClasses.h index 0bdeebdf0..75cedf269 100644 --- a/cppForSwig/BDM_supportClasses.h +++ b/cppForSwig/BDM_supportClasses.h @@ -276,7 +276,9 @@ class ZeroConfContainer map > txioMap_; //> map > keyToSpentScrAddr_; //> set txOutsSpentByZC_; // + set allZcTxHashes_; + BinaryData lastParsedBlockHash_; std::atomic topId_; mutex mu_; diff --git a/cppForSwig/BlockDataViewer.cpp b/cppForSwig/BlockDataViewer.cpp index 7dafd6cd5..f4c4c3a7e 100644 --- a/cppForSwig/BlockDataViewer.cpp +++ b/cppForSwig/BlockDataViewer.cpp @@ -734,6 +734,17 @@ Tx BlockDataViewer::getSpenderTxForTxOut(uint32_t height, uint32_t txindex, return txref.attached(db_).getTxCopy(); } +//////////////////////////////////////////////////////////////////////////////// +bool BlockDataViewer::isRBF(const BinaryData& txHash) const +{ + auto&& zctx = zeroConfCont_.getTxByHash(txHash); + if (!zctx.isInitialized()) + return false; + + return zctx.isRBF(); +} + + //////////////////////////////////////////////////////////////////////////////// //// WalletGroup //////////////////////////////////////////////////////////////////////////////// diff --git a/cppForSwig/BlockDataViewer.h b/cppForSwig/BlockDataViewer.h index 011d194b6..c7b28f56a 100644 --- a/cppForSwig/BlockDataViewer.h +++ b/cppForSwig/BlockDataViewer.h @@ -245,6 +245,8 @@ class BlockDataViewer bool getZCflag(void) const { return rescanZC_.load(memory_order_acquire); } + bool isRBF(const BinaryData& txHash) const; + public: //refresh notifications diff --git a/cppForSwig/BlockObj.cpp b/cppForSwig/BlockObj.cpp index 6fbc70e00..f5fe5e687 100644 --- a/cppForSwig/BlockObj.cpp +++ b/cppForSwig/BlockObj.cpp @@ -576,7 +576,6 @@ void Tx::unserialize(uint8_t const * ptr, size_t size) lockTime_ = READ_UINT32_LE(ptr + offsetsTxOut_[numTxOut]); isInitialized_ = true; - //headerPtr_ = NULL; } @@ -625,7 +624,10 @@ TxIn Tx::getTxInCopy(int i) const assert(isInitialized()); uint32_t txinSize = offsetsTxIn_[i+1] - offsetsTxIn_[i]; TxIn out; - out.unserialize_checked(dataCopy_.getPtr()+offsetsTxIn_[i], dataCopy_.getSize()-offsetsTxIn_[i], txinSize, txRefObj_, i); + out.unserialize_checked( + dataCopy_.getPtr()+offsetsTxIn_[i], + dataCopy_.getSize()-offsetsTxIn_[i], + txinSize, txRefObj_, i); if(txRefObj_.isInitialized()) { @@ -653,6 +655,24 @@ TxOut Tx::getTxOutCopy(int i) const return out; } +///////////////////////////////////////////////////////////////////////////// +bool Tx::isRBF() const +{ + if (isRBF_ != UINT32_MAX) + return isRBF_ == 1; + + for (unsigned i = 0; i < offsetsTxIn_.size() - 1; i++) + { + uint32_t sequenceOffset = offsetsTxIn_[i + 1] - 4; + uint32_t* sequencePtr = (uint32_t*)(dataCopy_.getPtr() + sequenceOffset); + + if (*sequencePtr < 0xFFFFFFFF - 1) + return true; + } + + return false; +} + ///////////////////////////////////////////////////////////////////////////// void Tx::pprint(ostream & os, int nIndent, bool pBigendian) { diff --git a/cppForSwig/BlockObj.h b/cppForSwig/BlockObj.h index c5519b9f5..5a07358c4 100644 --- a/cppForSwig/BlockObj.h +++ b/cppForSwig/BlockObj.h @@ -577,6 +577,13 @@ class Tx uint8_t getDuplicateID(void) { return txRefObj_.getDuplicateID(); } uint16_t getBlockTxIndex(void) { return txRefObj_.getBlockTxIndex(); } + bool isRBF(void) const; + void setRBF(bool isTrue) + { + isRBF_ = 0; + if (isTrue) isRBF_ = 1; + } + ///////////////////////////////////////////////////////////////////////////// void pprint(ostream & os=cout, int nIndent=0, bool pBigendian=true); void pprintAlot(ostream & os=cout); @@ -611,6 +618,8 @@ class Tx TxRef txRefObj_; uint32_t txTime_; + + unsigned isRBF_ = UINT32_MAX; }; diff --git a/cppForSwig/BlockchainScanner.cpp b/cppForSwig/BlockchainScanner.cpp index a61db059d..1212d1b8a 100644 --- a/cppForSwig/BlockchainScanner.cpp +++ b/cppForSwig/BlockchainScanner.cpp @@ -755,6 +755,12 @@ void BlockchainScanner::updateSSH() auto sshKey = subsshkey.getSliceRef(1, subsshkey.getSize() - 5); sshPtr = &sshMap_[sshKey]; + if (!scrAddrFilter_->hasScrAddress(sshKey)) + { + LOGWARN << "invalid scrAddr in SUBSSH db"; + continue; + } + //get what's already in the db db_->getStoredScriptHistorySummary(*sshPtr, sshKey); if (sshPtr->isInitialized()) @@ -1095,6 +1101,12 @@ void BlockchainScanner::undo(Blockchain::ReorganizationState& reorgState) //write it all up for (auto& ssh : sshMap) { + if (!scrAddrFilter_->hasScrAddress(ssh.second.uniqueKey_)) + { + LOGWARN << "invalid scrAddr during undo"; + continue; + } + BinaryWriter bw; ssh.second.serializeDBValue(bw, ARMORY_DB_BARE, DB_PRUNE_NONE); db_->putValue(SSH, diff --git a/cppForSwig/LedgerEntry.cpp b/cppForSwig/LedgerEntry.cpp index 2d5ff272c..27b15ae5b 100644 --- a/cppForSwig/LedgerEntry.cpp +++ b/cppForSwig/LedgerEntry.cpp @@ -181,7 +181,7 @@ void LedgerEntry::computeLedgerMap(map &leMap, for (const auto& txio : txioMap) { - auto txOutDBKey = txio.second.getDBKeyOfOutput().getSliceCopy(0, 6); + auto&& txOutDBKey = txio.second.getDBKeyOfOutput().getSliceCopy(0, 6); auto& txioVec = TxnTxIOMap[txOutDBKey]; txioVec.push_back(&txio.second); @@ -206,6 +206,8 @@ void LedgerEntry::computeLedgerMap(map &leMap, uint16_t txIndex; set scrAddrSet; + + bool isRBF = false; //grab iterator auto txioIter = txioVec.second.cbegin(); @@ -241,6 +243,9 @@ void LedgerEntry::computeLedgerMap(map &leMap, while (txioIter != txioVec.second.cend()) { + if (blockNum == UINT32_MAX && (*txioIter)->isRBF()) + isRBF = true; + if ((*txioIter)->getDBKeyOfOutput().startsWith(txioVec.first)) { isCoinbase |= (*txioIter)->isFromCoinbase(); @@ -288,7 +293,8 @@ void LedgerEntry::computeLedgerMap(map &leMap, txTime, isCoinbase, isSentToSelf, - isChangeBack); + isChangeBack, + isRBF); le.scrAddrSet_ = move(scrAddrSet); leMap[txioVec.first] = le; diff --git a/cppForSwig/StoredBlockObj.cpp b/cppForSwig/StoredBlockObj.cpp index 8e67c0c8e..ca5151cb3 100644 --- a/cppForSwig/StoredBlockObj.cpp +++ b/cppForSwig/StoredBlockObj.cpp @@ -233,6 +233,48 @@ void DBBlock::setHeaderData(BinaryData const & header80B) BtcUtils::getHash256(header80B, thisHash_); } +///////////////////////////////////////////////////////////////////////////// +void StoredHeader::unserializeSimple(BinaryRefReader brr) +{ + uint32_t height = blockHeight_; + uint8_t dupid = duplicateID_; + + vector allTxHashes; + BlockHeader bh(brr); + uint32_t nTx = (uint32_t)brr.get_var_int(); + + createFromBlockHeader(bh); + numTx_ = nTx; + + blockHeight_ = height; + duplicateID_ = dupid; + + numBytes_ = HEADER_SIZE + BtcUtils::calcVarIntSize(numTx_); + if (dataCopy_.getSize() != HEADER_SIZE) + { + LOGERR << "Unserializing header did not produce 80-byte object!"; + return; + } + + if (numBytes_ > brr.getSize()) + { + LOGERR << "Anticipated size of block header is more than what we have"; + throw BlockDeserializingException(); + } + + BtcUtils::getHash256(dataCopy_, thisHash_); + + for (uint32_t tx = 0; tx < nTx; tx++) + { + // gather tx hashes + Tx thisTx(brr); + + StoredTx stx; + stx.thisHash_ = thisTx.getThisHash(); + stxMap_[tx] = move(stx); + } +} + ///////////////////////////////////////////////////////////////////////////// void StoredHeader::unserializeFullBlock(BinaryRefReader brr, bool doFrag, diff --git a/cppForSwig/StoredBlockObj.h b/cppForSwig/StoredBlockObj.h index e76d49b5b..2edce73e3 100644 --- a/cppForSwig/StoredBlockObj.h +++ b/cppForSwig/StoredBlockObj.h @@ -523,6 +523,8 @@ class StoredHeader : public DBBlock bool doFrag = true, bool withPrefix8 = false); + void unserializeSimple(BinaryRefReader); + bool serializeFullBlock(BinaryWriter & bw) const; void setKeyData(uint32_t hgt, uint8_t dupID = UINT8_MAX); diff --git a/cppForSwig/cryptopp/cryptopp.vcxproj b/cppForSwig/cryptopp/cryptopp.vcxproj index cb9069fc2..619c74373 100644 --- a/cppForSwig/cryptopp/cryptopp.vcxproj +++ b/cppForSwig/cryptopp/cryptopp.vcxproj @@ -130,7 +130,7 @@ Disabled NDEBUG;%(PreprocessorDefinitions) cryptopp - MultiThreadedDebug + MultiThreaded UninitializedLocalUsageCheck diff --git a/cppForSwig/gtest/CppBlockUtilsTests.cpp b/cppForSwig/gtest/CppBlockUtilsTests.cpp index 269711fb1..f0cc77535 100644 --- a/cppForSwig/gtest/CppBlockUtilsTests.cpp +++ b/cppForSwig/gtest/CppBlockUtilsTests.cpp @@ -10011,3 +10011,4 @@ GTEST_API_ int main(int argc, char **argv) //TODO: add test to merge new addresses on reorg //TODO: add test for SSH rescan +//TODO: add test to detect RBF and RBF inheritence diff --git a/cppForSwig/lmdb_wrapper.cpp b/cppForSwig/lmdb_wrapper.cpp index 3dcfd414d..22ad3373b 100644 --- a/cppForSwig/lmdb_wrapper.cpp +++ b/cppForSwig/lmdb_wrapper.cpp @@ -539,6 +539,11 @@ BinaryData LMDBBlockDatabase::getTopBlockHash(DB_SELECT db) return sdbi.topScannedBlkHash_; } +//////////////////////////////////////////////////////////////////////////////// +BinaryData LMDBBlockDatabase::getTopBlockHash() const +{ + return blockchainPtr_->top().getThisHash(); +} //////////////////////////////////////////////////////////////////////////////// uint32_t LMDBBlockDatabase::getTopBlockHeight(DB_SELECT db) @@ -999,7 +1004,7 @@ void LMDBBlockDatabase::putStoredScriptHistorySummary(StoredScriptHistory & ssh) void LMDBBlockDatabase::putStoredSubHistory(StoredSubHistory & subssh) { - DB_SELECT db = SSH; + DB_SELECT db = SUBSSH; if (subssh.txioMap_.size() > 0) putValue(db, subssh.getDBKey(), @@ -1971,7 +1976,10 @@ bool LMDBBlockDatabase::getStoredHeader( BinaryRefReader brr(dataPtr + bh.getOffset(), bh.getBlockSize()); - sbh.unserializeFullBlock(brr, false, false); + if (withTx) + sbh.unserializeFullBlock(brr, false, false); + else + sbh.unserializeSimple(brr); } catch (...) { diff --git a/cppForSwig/lmdb_wrapper.h b/cppForSwig/lmdb_wrapper.h index 24005e0f3..2a9e3e44d 100644 --- a/cppForSwig/lmdb_wrapper.h +++ b/cppForSwig/lmdb_wrapper.h @@ -291,6 +291,8 @@ class LMDBBlockDatabase // Get latest block info BinaryData getTopBlockHash(DB_SELECT db); uint32_t getTopBlockHeight(DB_SELECT db); + BinaryData getTopBlockHash() const; + ///////////////////////////////////////////////////////////////////////////// LDBIter getIterator(DB_SELECT db) const @@ -621,6 +623,9 @@ class LMDBBlockDatabase StoredDBInfo openDB(DB_SELECT); void resetSSHdb(void); + const Blockchain* blockchain(void) const { return blockchainPtr_; } + + public: mutable map > dbEnv_; diff --git a/cppForSwig/txio.cpp b/cppForSwig/txio.cpp index 8e761c2e4..4b55a571f 100644 --- a/cppForSwig/txio.cpp +++ b/cppForSwig/txio.cpp @@ -328,6 +328,7 @@ TxIOPair& TxIOPair::operator=(const TxIOPair &rhs) this->txtime_ = rhs.txtime_; this->isUTXO_ = rhs.isUTXO_; + this->isRBF_ = rhs.isRBF_; return *this; } diff --git a/cppForSwig/txio.h b/cppForSwig/txio.h index aba797461..d0980c270 100644 --- a/cppForSwig/txio.h +++ b/cppForSwig/txio.h @@ -58,6 +58,8 @@ class TxIOPair void setFromCoinbase(bool isTrue = true) { isFromCoinbase_ = isTrue; } bool isMultisig(void) const { return isMultisig_; } void setMultisig(bool isTrue = true) { isMultisig_ = isTrue; } + bool isRBF(void) const { return isRBF_; } + void setRBF(bool isTrue) { isRBF_ = isTrue; } BinaryData getDBKeyOfOutput(void) const { @@ -150,6 +152,7 @@ class TxIOPair bool isTxOutFromSelf_ = false; bool isFromCoinbase_; bool isMultisig_; + bool isRBF_ = false; //mainly for ZC ledgers. Could replace the need for a blockchain //object to build scrAddrObj ledgers. diff --git a/qtdialogs.py b/qtdialogs.py index adc6e3520..cb0e19f9c 100644 --- a/qtdialogs.py +++ b/qtdialogs.py @@ -5934,14 +5934,15 @@ def __init__(self, pytx, wlt, parent, main, mode=None, \ lbls[-1].append(QLabel('Confirmations:')) lbls[-1].append(QRichLabel(str(nConf))) - if self.pytx.optInRBF and nConf == 0: + isRBF = self.pytx.isRBF() + if isRBF: lbls.append([]) lbls[-1].append(self.main.createToolTipWidget( 'This transaction can be replaced by another transaction that' 'spends the same inputs if the replacement transaction has' 'a higher fee.')) lbls[-1].append(QLabel('Mempool Replaceable: ')) - lbls[-1].append(QRichLabel(str(self.pytx.optInRBF))) + lbls[-1].append(QRichLabel(str(isRBF)))