|
|
|
@ -1,4 +1,5 @@
@@ -1,4 +1,5 @@
|
|
|
|
|
#include "transactiontablemodel.h" |
|
|
|
|
#include "guiutil.h" |
|
|
|
|
#include "main.h" |
|
|
|
|
|
|
|
|
|
#include <QLocale> |
|
|
|
@ -8,66 +9,319 @@
@@ -8,66 +9,319 @@
|
|
|
|
|
const QString TransactionTableModel::Sent = "s"; |
|
|
|
|
const QString TransactionTableModel::Received = "r"; |
|
|
|
|
const QString TransactionTableModel::Generated = "g"; |
|
|
|
|
const QString TransactionTableModel::Other = "o"; |
|
|
|
|
|
|
|
|
|
/* Separate transaction record format from core.
|
|
|
|
|
* When the GUI is going to communicate with the core through the network, |
|
|
|
|
* we'll need our own internal formats anyway. |
|
|
|
|
/* TODO: look up address in address book
|
|
|
|
|
when showing. |
|
|
|
|
Color based on confirmation status. |
|
|
|
|
(fConfirmed ? wxColour(0,0,0) : wxColour(128,128,128)) |
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
class TransactionStatus |
|
|
|
|
{ |
|
|
|
|
public: |
|
|
|
|
TransactionStatus(): |
|
|
|
|
confirmed(false), sortKey(""), maturity(Mature), |
|
|
|
|
matures_in(0), status(Offline), depth(0), open_for(0) |
|
|
|
|
{ } |
|
|
|
|
|
|
|
|
|
enum Maturity |
|
|
|
|
{ |
|
|
|
|
Immature, |
|
|
|
|
Mature, |
|
|
|
|
MaturesIn, |
|
|
|
|
MaturesWarning, /* Will probably not mature because no nodes have confirmed */ |
|
|
|
|
NotAccepted |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
enum Status { |
|
|
|
|
OpenUntilDate, |
|
|
|
|
OpenUntilBlock, |
|
|
|
|
Offline, |
|
|
|
|
Unconfirmed, |
|
|
|
|
HaveConfirmations |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
bool confirmed; |
|
|
|
|
std::string sortKey; |
|
|
|
|
|
|
|
|
|
/* For "Generated" transactions */ |
|
|
|
|
Maturity maturity; |
|
|
|
|
int matures_in; |
|
|
|
|
|
|
|
|
|
/* Reported status */ |
|
|
|
|
Status status; |
|
|
|
|
int64 depth; |
|
|
|
|
int64 open_for; /* Timestamp if status==OpenUntilDate, otherwise number of blocks */ |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
class TransactionRecord |
|
|
|
|
{ |
|
|
|
|
public: |
|
|
|
|
/* Information that never changes for the life of the transaction
|
|
|
|
|
*/ |
|
|
|
|
enum Type |
|
|
|
|
{ |
|
|
|
|
Other, |
|
|
|
|
Generated, |
|
|
|
|
SendToAddress, |
|
|
|
|
SendToIP, |
|
|
|
|
RecvFromAddress, |
|
|
|
|
RecvFromIP, |
|
|
|
|
SendToSelf |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
TransactionRecord(): |
|
|
|
|
hash(), time(0), type(Other), address(""), debit(0), credit(0) |
|
|
|
|
{ |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
TransactionRecord(uint256 hash, int64 time, const TransactionStatus &status): |
|
|
|
|
hash(hash), time(time), type(Other), address(""), debit(0), |
|
|
|
|
credit(0), status(status) |
|
|
|
|
{ |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
TransactionRecord(uint256 hash, int64 time, const TransactionStatus &status, |
|
|
|
|
Type type, const std::string &address, |
|
|
|
|
int64 debit, int64 credit): |
|
|
|
|
hash(hash), time(time), type(type), address(address), debit(debit), credit(credit), |
|
|
|
|
status(status) |
|
|
|
|
{ |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Fixed */ |
|
|
|
|
uint256 hash; |
|
|
|
|
int64 time; |
|
|
|
|
int64 credit; |
|
|
|
|
Type type; |
|
|
|
|
std::string address; |
|
|
|
|
int64 debit; |
|
|
|
|
int64 change; |
|
|
|
|
int64 lockTime; |
|
|
|
|
int64 timeReceived; |
|
|
|
|
bool isCoinBase; |
|
|
|
|
int blockIndex; |
|
|
|
|
|
|
|
|
|
/* Properties that change based on changes in block chain that come in
|
|
|
|
|
over the network. |
|
|
|
|
*/ |
|
|
|
|
bool confirmed; |
|
|
|
|
int depthInMainChain; |
|
|
|
|
bool final; |
|
|
|
|
int requestCount; |
|
|
|
|
int64 credit; |
|
|
|
|
|
|
|
|
|
TransactionRecord(const CWalletTx &tx) |
|
|
|
|
{ |
|
|
|
|
/* Copy immutable properties.
|
|
|
|
|
/* Status: can change with block chain update */ |
|
|
|
|
TransactionStatus status; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/* Return positive answer if transaction should be shown in list.
|
|
|
|
|
*/ |
|
|
|
|
hash = tx.GetHash(); |
|
|
|
|
time = tx.GetTxTime(); |
|
|
|
|
credit = tx.GetCredit(true); |
|
|
|
|
debit = tx.GetDebit(); |
|
|
|
|
change = tx.GetChange(); |
|
|
|
|
isCoinBase = tx.IsCoinBase(); |
|
|
|
|
lockTime = tx.nLockTime; |
|
|
|
|
timeReceived = tx.nTimeReceived; |
|
|
|
|
|
|
|
|
|
/* Find the block the tx is in, store the index
|
|
|
|
|
bool showTransaction(const CWalletTx &wtx) |
|
|
|
|
{ |
|
|
|
|
if (wtx.IsCoinBase()) |
|
|
|
|
{ |
|
|
|
|
// Don't show generated coin until confirmed by at least one block after it
|
|
|
|
|
// so we don't get the user's hopes up until it looks like it's probably accepted.
|
|
|
|
|
//
|
|
|
|
|
// It is not an error when generated blocks are not accepted. By design,
|
|
|
|
|
// some percentage of blocks, like 10% or more, will end up not accepted.
|
|
|
|
|
// This is the normal mechanism by which the network copes with latency.
|
|
|
|
|
//
|
|
|
|
|
// We display regular transactions right away before any confirmation
|
|
|
|
|
// because they can always get into some block eventually. Generated coins
|
|
|
|
|
// are special because if their block is not accepted, they are not valid.
|
|
|
|
|
//
|
|
|
|
|
if (wtx.GetDepthInMainChain() < 2) |
|
|
|
|
{ |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Decompose CWallet transaction to model transaction records.
|
|
|
|
|
*/ |
|
|
|
|
QList<TransactionRecord> decomposeTransaction(const CWalletTx &wtx) |
|
|
|
|
{ |
|
|
|
|
QList<TransactionRecord> parts; |
|
|
|
|
int64 nTime = wtx.nTimeDisplayed = wtx.GetTxTime(); |
|
|
|
|
int64 nCredit = wtx.GetCredit(true); |
|
|
|
|
int64 nDebit = wtx.GetDebit(); |
|
|
|
|
int64 nNet = nCredit - nDebit; |
|
|
|
|
uint256 hash = wtx.GetHash(); |
|
|
|
|
std::map<std::string, std::string> mapValue = wtx.mapValue; |
|
|
|
|
|
|
|
|
|
// Find the block the tx is in
|
|
|
|
|
CBlockIndex* pindex = NULL; |
|
|
|
|
std::map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(tx.hashBlock); |
|
|
|
|
std::map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(wtx.hashBlock); |
|
|
|
|
if (mi != mapBlockIndex.end()) |
|
|
|
|
pindex = (*mi).second; |
|
|
|
|
blockIndex = (pindex ? pindex->nHeight : INT_MAX); |
|
|
|
|
|
|
|
|
|
update(tx); |
|
|
|
|
// Determine transaction status
|
|
|
|
|
TransactionStatus status; |
|
|
|
|
// Sort order, unrecorded transactions sort to the top
|
|
|
|
|
status.sortKey = strprintf("%010d-%01d-%010u", |
|
|
|
|
(pindex ? pindex->nHeight : INT_MAX), |
|
|
|
|
(wtx.IsCoinBase() ? 1 : 0), |
|
|
|
|
wtx.nTimeReceived); |
|
|
|
|
status.confirmed = wtx.IsConfirmed(); |
|
|
|
|
status.depth = wtx.GetDepthInMainChain(); |
|
|
|
|
|
|
|
|
|
if (!wtx.IsFinal()) |
|
|
|
|
{ |
|
|
|
|
if (wtx.nLockTime < 500000000) |
|
|
|
|
{ |
|
|
|
|
status.status = TransactionStatus::OpenUntilBlock; |
|
|
|
|
status.open_for = nBestHeight - wtx.nLockTime; |
|
|
|
|
} else { |
|
|
|
|
status.status = TransactionStatus::OpenUntilDate; |
|
|
|
|
status.open_for = wtx.nLockTime; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0) |
|
|
|
|
{ |
|
|
|
|
status.status = TransactionStatus::Offline; |
|
|
|
|
} else if (status.depth < 6) |
|
|
|
|
{ |
|
|
|
|
status.status = TransactionStatus::Unconfirmed; |
|
|
|
|
} else |
|
|
|
|
{ |
|
|
|
|
status.status = TransactionStatus::HaveConfirmations; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (showTransaction(wtx)) |
|
|
|
|
{ |
|
|
|
|
|
|
|
|
|
if (nNet > 0 || wtx.IsCoinBase()) |
|
|
|
|
{ |
|
|
|
|
//
|
|
|
|
|
// Credit
|
|
|
|
|
//
|
|
|
|
|
TransactionRecord sub(hash, nTime, status); |
|
|
|
|
|
|
|
|
|
sub.credit = nNet; |
|
|
|
|
|
|
|
|
|
void update(const CWalletTx &tx) |
|
|
|
|
if (wtx.IsCoinBase()) |
|
|
|
|
{ |
|
|
|
|
confirmed = tx.IsConfirmed(); |
|
|
|
|
depthInMainChain = tx.GetDepthInMainChain(); |
|
|
|
|
final = tx.IsFinal(); |
|
|
|
|
requestCount = tx.GetRequestCount(); |
|
|
|
|
// Generated
|
|
|
|
|
sub.type = TransactionRecord::Generated; |
|
|
|
|
|
|
|
|
|
if (nCredit == 0) |
|
|
|
|
{ |
|
|
|
|
sub.status.maturity = TransactionStatus::Immature; |
|
|
|
|
|
|
|
|
|
int64 nUnmatured = 0; |
|
|
|
|
BOOST_FOREACH(const CTxOut& txout, wtx.vout) |
|
|
|
|
nUnmatured += txout.GetCredit(); |
|
|
|
|
sub.credit = nUnmatured; |
|
|
|
|
|
|
|
|
|
if (wtx.IsInMainChain()) |
|
|
|
|
{ |
|
|
|
|
sub.status.maturity = TransactionStatus::MaturesIn; |
|
|
|
|
sub.status.matures_in = wtx.GetBlocksToMaturity(); |
|
|
|
|
|
|
|
|
|
// Check if the block was requested by anyone
|
|
|
|
|
if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0) |
|
|
|
|
sub.status.maturity = TransactionStatus::MaturesWarning; |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
sub.status.maturity = TransactionStatus::NotAccepted; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
else if (!mapValue["from"].empty() || !mapValue["message"].empty()) |
|
|
|
|
{ |
|
|
|
|
// Received by IP connection
|
|
|
|
|
sub.type = TransactionRecord::RecvFromIP; |
|
|
|
|
if (!mapValue["from"].empty()) |
|
|
|
|
sub.address = mapValue["from"]; |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
// Received by Bitcoin Address
|
|
|
|
|
sub.type = TransactionRecord::RecvFromAddress; |
|
|
|
|
BOOST_FOREACH(const CTxOut& txout, wtx.vout) |
|
|
|
|
{ |
|
|
|
|
if (txout.IsMine()) |
|
|
|
|
{ |
|
|
|
|
std::vector<unsigned char> vchPubKey; |
|
|
|
|
if (ExtractPubKey(txout.scriptPubKey, true, vchPubKey)) |
|
|
|
|
{ |
|
|
|
|
sub.address = PubKeyToAddress(vchPubKey); |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
parts.append(sub); |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
bool fAllFromMe = true; |
|
|
|
|
BOOST_FOREACH(const CTxIn& txin, wtx.vin) |
|
|
|
|
fAllFromMe = fAllFromMe && txin.IsMine(); |
|
|
|
|
|
|
|
|
|
bool fAllToMe = true; |
|
|
|
|
BOOST_FOREACH(const CTxOut& txout, wtx.vout) |
|
|
|
|
fAllToMe = fAllToMe && txout.IsMine(); |
|
|
|
|
|
|
|
|
|
if (fAllFromMe && fAllToMe) |
|
|
|
|
{ |
|
|
|
|
// Payment to self
|
|
|
|
|
int64 nChange = wtx.GetChange(); |
|
|
|
|
|
|
|
|
|
parts.append(TransactionRecord(hash, nTime, status, TransactionRecord::SendToSelf, "", |
|
|
|
|
-(nDebit - nChange), nCredit - nChange)); |
|
|
|
|
} |
|
|
|
|
else if (fAllFromMe) |
|
|
|
|
{ |
|
|
|
|
//
|
|
|
|
|
// Debit
|
|
|
|
|
//
|
|
|
|
|
int64 nTxFee = nDebit - wtx.GetValueOut(); |
|
|
|
|
|
|
|
|
|
for (int nOut = 0; nOut < wtx.vout.size(); nOut++) |
|
|
|
|
{ |
|
|
|
|
const CTxOut& txout = wtx.vout[nOut]; |
|
|
|
|
TransactionRecord sub(hash, nTime, status); |
|
|
|
|
|
|
|
|
|
if (txout.IsMine()) |
|
|
|
|
{ |
|
|
|
|
// Sent to self
|
|
|
|
|
sub.type = TransactionRecord::SendToSelf; |
|
|
|
|
sub.credit = txout.nValue; |
|
|
|
|
} else if (!mapValue["to"].empty()) |
|
|
|
|
{ |
|
|
|
|
// Sent to IP
|
|
|
|
|
sub.type = TransactionRecord::SendToIP; |
|
|
|
|
sub.address = mapValue["to"]; |
|
|
|
|
} else { |
|
|
|
|
// Sent to Bitcoin Address
|
|
|
|
|
sub.type = TransactionRecord::SendToAddress; |
|
|
|
|
uint160 hash160; |
|
|
|
|
if (ExtractHash160(txout.scriptPubKey, hash160)) |
|
|
|
|
sub.address = Hash160ToAddress(hash160); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
int64 nValue = txout.nValue; |
|
|
|
|
/* Add fee to first output */ |
|
|
|
|
if (nTxFee > 0) |
|
|
|
|
{ |
|
|
|
|
nValue += nTxFee; |
|
|
|
|
nTxFee = 0; |
|
|
|
|
} |
|
|
|
|
sub.debit = nValue; |
|
|
|
|
sub.status.sortKey += strprintf("-%d", nOut); |
|
|
|
|
|
|
|
|
|
parts.append(sub); |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
//
|
|
|
|
|
// Mixed debit transaction, can't break down payees
|
|
|
|
|
//
|
|
|
|
|
bool fAllMine = true; |
|
|
|
|
BOOST_FOREACH(const CTxOut& txout, wtx.vout) |
|
|
|
|
fAllMine = fAllMine && txout.IsMine(); |
|
|
|
|
BOOST_FOREACH(const CTxIn& txin, wtx.vin) |
|
|
|
|
fAllMine = fAllMine && txin.IsMine(); |
|
|
|
|
|
|
|
|
|
parts.append(TransactionRecord(hash, nTime, status, TransactionRecord::Other, "", nNet, 0)); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return parts; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Internal implementation */ |
|
|
|
|
class TransactionTableImpl |
|
|
|
@ -93,7 +347,7 @@ public:
@@ -93,7 +347,7 @@ public:
|
|
|
|
|
/* TODO: Make note of new and removed transactions */ |
|
|
|
|
/* insertedIndices */ |
|
|
|
|
/* removedIndices */ |
|
|
|
|
cachedWallet.append(TransactionRecord(it->second)); |
|
|
|
|
cachedWallet.append(decomposeTransaction(it->second)); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
/* beginInsertRows(QModelIndex(), first, last) */ |
|
|
|
@ -146,14 +400,6 @@ TransactionTableModel::~TransactionTableModel()
@@ -146,14 +400,6 @@ TransactionTableModel::~TransactionTableModel()
|
|
|
|
|
int TransactionTableModel::rowCount(const QModelIndex &parent) const |
|
|
|
|
{ |
|
|
|
|
Q_UNUSED(parent); |
|
|
|
|
/*
|
|
|
|
|
int retval = 0; |
|
|
|
|
CRITICAL_BLOCK(cs_mapWallet) |
|
|
|
|
{ |
|
|
|
|
retval = mapWallet.size(); |
|
|
|
|
} |
|
|
|
|
return retval; |
|
|
|
|
*/ |
|
|
|
|
return impl->size(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -165,32 +411,37 @@ int TransactionTableModel::columnCount(const QModelIndex &parent) const
@@ -165,32 +411,37 @@ int TransactionTableModel::columnCount(const QModelIndex &parent) const
|
|
|
|
|
|
|
|
|
|
QVariant TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const |
|
|
|
|
{ |
|
|
|
|
return QVariant(QString("Test")); |
|
|
|
|
#if 0 |
|
|
|
|
// Status
|
|
|
|
|
if (!wtx.IsFinal()) |
|
|
|
|
{ |
|
|
|
|
if (wtx.nLockTime < 500000000) |
|
|
|
|
return strprintf(_("Open for %d blocks"), nBestHeight - wtx.nLockTime); |
|
|
|
|
else |
|
|
|
|
return strprintf(_("Open until %s"), DateTimeStr(wtx.nLockTime).c_str()); |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
QString status; |
|
|
|
|
switch(wtx->status.status) |
|
|
|
|
{ |
|
|
|
|
int nDepth = wtx.GetDepthInMainChain(); |
|
|
|
|
if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0) |
|
|
|
|
return strprintf(_("%d/offline?"), nDepth); |
|
|
|
|
else if (nDepth < 6) |
|
|
|
|
return strprintf(_("%d/unconfirmed"), nDepth); |
|
|
|
|
else |
|
|
|
|
return strprintf(_("%d confirmations"), nDepth); |
|
|
|
|
case TransactionStatus::OpenUntilBlock: |
|
|
|
|
status = tr("Open for %n block(s)","",wtx->status.open_for); |
|
|
|
|
break; |
|
|
|
|
case TransactionStatus::OpenUntilDate: |
|
|
|
|
status = tr("Open until ") + DateTimeStr(wtx->status.open_for); |
|
|
|
|
break; |
|
|
|
|
case TransactionStatus::Offline: |
|
|
|
|
status = tr("%1/offline").arg(wtx->status.depth); |
|
|
|
|
break; |
|
|
|
|
case TransactionStatus::Unconfirmed: |
|
|
|
|
status = tr("%1/unconfirmed").arg(wtx->status.depth); |
|
|
|
|
break; |
|
|
|
|
case TransactionStatus::HaveConfirmations: |
|
|
|
|
status = tr("%1 confirmations").arg(wtx->status.depth); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
return QVariant(status); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
QVariant TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const |
|
|
|
|
{ |
|
|
|
|
if(wtx->time) |
|
|
|
|
{ |
|
|
|
|
return QVariant(DateTimeStr(wtx->time)); |
|
|
|
|
} else { |
|
|
|
|
return QVariant(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
QVariant TransactionTableModel::formatTxDescription(const TransactionRecord *wtx) const |
|
|
|
@ -200,12 +451,32 @@ QVariant TransactionTableModel::formatTxDescription(const TransactionRecord *wtx
@@ -200,12 +451,32 @@ QVariant TransactionTableModel::formatTxDescription(const TransactionRecord *wtx
|
|
|
|
|
|
|
|
|
|
QVariant TransactionTableModel::formatTxDebit(const TransactionRecord *wtx) const |
|
|
|
|
{ |
|
|
|
|
if(wtx->debit) |
|
|
|
|
{ |
|
|
|
|
QString str = QString::fromStdString(FormatMoney(wtx->debit)); |
|
|
|
|
if(!wtx->status.confirmed) |
|
|
|
|
{ |
|
|
|
|
str = QString("[") + str + QString("]"); |
|
|
|
|
} |
|
|
|
|
return QVariant(str); |
|
|
|
|
} else { |
|
|
|
|
return QVariant(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
QVariant TransactionTableModel::formatTxCredit(const TransactionRecord *wtx) const |
|
|
|
|
{ |
|
|
|
|
if(wtx->credit) |
|
|
|
|
{ |
|
|
|
|
QString str = QString::fromStdString(FormatMoney(wtx->credit)); |
|
|
|
|
if(!wtx->status.confirmed) |
|
|
|
|
{ |
|
|
|
|
str = QString("[") + str + QString("]"); |
|
|
|
|
} |
|
|
|
|
return QVariant(str); |
|
|
|
|
} else { |
|
|
|
|
return QVariant(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
QVariant TransactionTableModel::data(const QModelIndex &index, int role) const |
|
|
|
|