Skip to content

Commit

Permalink
opt-in RBF detection and flagging
Browse files Browse the repository at this point in the history
  • Loading branch information
goatpig committed Feb 28, 2016
1 parent 76ae356 commit 28ee5d2
Show file tree
Hide file tree
Showing 21 changed files with 296 additions and 35 deletions.
4 changes: 3 additions & 1 deletion ArmoryQt.py
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions armorycolors.py
Expand Up @@ -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])
Expand Down
7 changes: 3 additions & 4 deletions armoryengine/Transaction.py
Expand Up @@ -655,7 +655,6 @@ def __init__(self):
self.outputs = UNINITIALIZED
self.lockTime = 0
self.thisHash = UNINITIALIZED
self.optInRBF = False

def serialize(self):
binOut = BinaryPacker()
Expand Down Expand Up @@ -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) )
Expand Down Expand Up @@ -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
Expand Down
17 changes: 14 additions & 3 deletions armorymodels.py
Expand Up @@ -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')

Expand Down Expand Up @@ -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]
Expand All @@ -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])
Expand All @@ -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 )
Expand Down Expand Up @@ -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'
Expand Down
158 changes: 140 additions & 18 deletions cppForSwig/BDM_supportClasses.cpp
Expand Up @@ -606,9 +606,89 @@ map<BinaryData, vector<BinaryData>> ZeroConfContainer::purge(
}
}

//build the set of invalidated zc dbKeys and delete them from db
//get all txhashes for the new blocks
set<BinaryData> 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<BinaryData> 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())
Expand Down Expand Up @@ -677,12 +757,6 @@ map<BinaryData, vector<BinaryData>> ZeroConfContainer::purge(
}

return invalidatedKeys;

/*
// Rewrite the zero-conf pool file
if (hashRmVec.size() > 0)
rewriteZeroConfFile();
*/
}

///////////////////////////////////////////////////////////////////////////////
Expand All @@ -701,7 +775,7 @@ bool ZeroConfContainer::parseNewZC(function<bool(const BinaryData&)> 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;

Expand All @@ -726,9 +800,10 @@ bool ZeroConfContainer::parseNewZC(function<bool(const BinaryData&)> 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<BinaryData, map<BinaryData, TxIOPair> > newTxIO =
ZCisMineBulkFilter(newZCPair.second,
newZCPair.first,
Expand All @@ -740,7 +815,6 @@ bool ZeroConfContainer::parseNewZC(function<bool(const BinaryData&)> filter,
txHashToDBKey_[txHash] = newZCPair.first;
txMap_[newZCPair.first] = newZCPair.second;

keysToWrite.push_back(newZCPair.first);

for (const auto& saTxio : newTxIO)
{
Expand All @@ -758,6 +832,33 @@ bool ZeroConfContainer::parseNewZC(function<bool(const BinaryData&)> 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
Expand All @@ -773,15 +874,15 @@ bool ZeroConfContainer::parseNewZC(function<bool(const BinaryData&)> 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
break;
}

//else search the new ZC container for unseen ZC
map<BinaryData, Tx>::const_iterator newZcIter = newZCMap_.begin();
auto newZcIter = newZCMap_.begin();

while (newZcIter != newZCMap_.begin())
{
Expand All @@ -797,6 +898,8 @@ bool ZeroConfContainer::parseNewZC(function<bool(const BinaryData&)> filter,
nProcessed = 0;
}

lastParsedBlockHash_ = db_->getTopBlockHash();

return zcIsOurs;
}

Expand Down Expand Up @@ -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<tx.getNumTxIn(); iin++)
{
Expand Down Expand Up @@ -898,6 +1003,7 @@ ZeroConfContainer::ZCisMineBulkFilter(const Tx & tx,

txio.setValue(chainedTxOut.getValue());
txio.setTxTime(txtime);
txio.setRBF(isRBF);

BinaryData spentSA = chainedTxOut.getScrAddressStr();
auto& key_txioPair = processedTxIO[spentSA];
Expand Down Expand Up @@ -930,6 +1036,7 @@ ZeroConfContainer::ZCisMineBulkFilter(const Tx & tx,
txio.setTxHashOfInput(txHash);
txio.setValue(stxOut.getValue());
txio.setTxTime(txtime);
txio.setRBF(isRBF);

auto& key_txioPair = processedTxIO[sa];
key_txioPair[opKey] = txio;
Expand All @@ -956,6 +1063,7 @@ ZeroConfContainer::ZCisMineBulkFilter(const Tx & tx,
txio.setTxHashOfOutput(txHash);
txio.setTxTime(txtime);
txio.setUTXO(true);
txio.setRBF(isRBF);

auto& key_txioPair = processedTxIO[scrAddr];

Expand All @@ -965,7 +1073,7 @@ ZeroConfContainer::ZCisMineBulkFilter(const Tx & tx,

// It's still possible this is a multisig addr involving one of our
// existing scrAddrs, even if we aren't explicitly looking for this multisig
if (withSecondOrderMultisig && txout.getScriptType() ==
/* if (withSecondOrderMultisig && txout.getScriptType() ==
TXOUT_SCRIPT_MULTISIG)
{
BinaryRefReader brrmsig(scrAddr);
Expand All @@ -989,7 +1097,7 @@ ZeroConfContainer::ZCisMineBulkFilter(const Tx & tx,
key_txioPair[txio.getDBKeyOfOutput()] = txio;
}
}
}*/
}

// If we got here, it's either non std or not ours
Expand Down Expand Up @@ -1066,9 +1174,18 @@ void ZeroConfContainer::updateZCinDB(const vector<BinaryData>& 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)
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions cppForSwig/BDM_supportClasses.h
Expand Up @@ -276,7 +276,9 @@ class ZeroConfContainer
map<HashString, map<BinaryData, TxIOPair> > txioMap_; //<scrAddr, <dbKeyOfOutput, TxIOPair>>
map<HashString, vector<HashString> > keyToSpentScrAddr_; //<zcKey, vector<ScrAddr>>
set<HashString> txOutsSpentByZC_; //<txOutDbKeys>
set<HashString> allZcTxHashes_;

BinaryData lastParsedBlockHash_;

std::atomic<uint32_t> topId_;
mutex mu_;
Expand Down

0 comments on commit 28ee5d2

Please sign in to comment.