Skip to content

Commit

Permalink
Merged in qt-display-stake (pull request #29)
Browse files Browse the repository at this point in the history
Add coinstakes and masternode reward types to Qt's transaction overview display

Approved-by: Cevap
  • Loading branch information
FornaxA authored and Cevap committed Mar 9, 2020
2 parents 8dc3275 + ae5721b commit c233181
Show file tree
Hide file tree
Showing 15 changed files with 124 additions and 47 deletions.
3 changes: 2 additions & 1 deletion src/coins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,13 @@ void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin&& coin, bool possi

void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight, bool check) {
bool fCoinbase = tx.IsCoinBase();
bool fCoinstake = tx.IsCoinStake();
const uint256& txid = tx.GetHash();
for (size_t i = 0; i < tx.vout.size(); ++i) {
bool overwrite = check ? cache.HaveCoin(COutPoint(txid, i)) : fCoinbase;
// Always set the possible_overwrite flag to AddCoin for coinbase txn, in order to correctly
// deal with the pre-BIP30 occurrences of duplicate coinbase transactions.
cache.AddCoin(COutPoint(txid, i), Coin(tx.vout[i], nHeight, fCoinbase), overwrite);
cache.AddCoin(COutPoint(txid, i), Coin(tx.vout[i], nHeight, fCoinbase, fCoinstake), overwrite);
}
}

Expand Down
20 changes: 14 additions & 6 deletions src/coins.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,31 +34,38 @@ class Coin

//! whether containing transaction was a coinbase
unsigned int fCoinBase : 1;
//! whether containing transaction was a coinstake
unsigned int fCoinStake : 1;

//! at which height this containing transaction was included in the active block chain
uint32_t nHeight : 31;
uint32_t nHeight : 30;

//! construct a Coin from a CTxOut and height/coinbase information.
Coin(CTxOut&& outIn, int nHeightIn, bool fCoinBaseIn) : out(std::move(outIn)), fCoinBase(fCoinBaseIn), nHeight(nHeightIn) {}
Coin(const CTxOut& outIn, int nHeightIn, bool fCoinBaseIn) : out(outIn), fCoinBase(fCoinBaseIn),nHeight(nHeightIn) {}
Coin(CTxOut&& outIn, int nHeightIn, bool fCoinBaseIn, bool fCoinStakeIn) : out(std::move(outIn)), fCoinBase(fCoinBaseIn), fCoinStake(fCoinStakeIn), nHeight(nHeightIn) {}
Coin(const CTxOut& outIn, int nHeightIn, bool fCoinBaseIn, bool fCoinStakeIn) : out(outIn), fCoinBase(fCoinBaseIn), fCoinStake(fCoinStakeIn), nHeight(nHeightIn) {}

void Clear() {
out.SetNull();
fCoinBase = false;
fCoinStake = false;
nHeight = 0;
}

//! empty constructor
Coin() : fCoinBase(false), nHeight(0) { }
Coin() : fCoinBase(false), fCoinStake(false), nHeight(0) { }

bool IsCoinBase() const {
return fCoinBase;
}

bool IsCoinStake() const {
return fCoinStake;
}

template<typename Stream>
void Serialize(Stream &s) const {
assert(!IsSpent());
uint32_t code = nHeight * 2 + fCoinBase;
uint32_t code = nHeight * 4 + fCoinStake * 2 + fCoinBase;
::Serialize(s, VARINT(code));
::Serialize(s, CTxOutCompressor(REF(out)));
}
Expand All @@ -67,7 +74,8 @@ class Coin
void Unserialize(Stream &s) {
uint32_t code = 0;
::Unserialize(s, VARINT(code));
nHeight = code >> 1;
nHeight = code >> 2;
fCoinStake = (code >> 1) & 1;
fCoinBase = code & 1;
::Unserialize(s, REF(CTxOutCompressor(out)));
}
Expand Down
17 changes: 16 additions & 1 deletion src/consensus/tx_verify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "consensus.h"
#include "primitives/transaction.h"
#include "script/interpreter.h"
#include "tokens/groups.h"
#include "validation.h"

// TODO remove the following dependencies
Expand Down Expand Up @@ -228,13 +229,27 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, CValidationState& state, c
const Coin& coin = inputs.AccessCoin(prevout);
assert(!coin.IsSpent());

// If prev is coinbase, check that it's matured
// If prev is coinbase, coinstake or group authority confirguration, check that it's matured
if (coin.IsCoinStake() && nSpendHeight - coin.nHeight < (nSpendHeight <= 100 ? (int)10 : Params().nCoinbaseMaturity)) {
return state.Invalid(false,
REJECT_INVALID, "bad-txns-premature-spend-of-coinstake",
strprintf("tried to spend coinstake at depth %d", nSpendHeight - coin.nHeight));
}

if (coin.IsCoinBase() && nSpendHeight - coin.nHeight < (nSpendHeight <= 100 ? (int)10 : Params().nCoinbaseMaturity)) {
return state.Invalid(false,
REJECT_INVALID, "bad-txns-premature-spend-of-coinbase",
strprintf("tried to spend coinbase at depth %d", nSpendHeight - coin.nHeight));
}

if (IsOutputGroupedAuthority(coin.out)) {
if (nSpendHeight - coin.nHeight < Params().nOpGroupNewRequiredConfirmations) {
return state.Invalid(
error("CheckInputs() : tried to use a token authority before it reached maturity (%d confirmations)", Params().nOpGroupNewRequiredConfirmations),
REJECT_INVALID, "bad-txns-premature-use-of-token-authority");
}
}

// Check for negative or overflow input values
nValueIn += coin.out.nValue;
if (!MoneyRange(coin.out.nValue) || !MoneyRange(nValueIn)) {
Expand Down
10 changes: 5 additions & 5 deletions src/qt/transactiondesc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,11 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
//
// From
//
if (wtx.IsCoinBase())
if (wtx.IsCoinBase() || wtx.IsCoinStake())
{
strHTML += "<b>" + tr("Source") + ":</b> " + tr("Generated") + "<br>";
}
else if (wtx.mapValue.count("from") && !wtx.mapValue["from"].empty())
if (wtx.mapValue.count("from") && !wtx.mapValue["from"].empty())
{
// Online transaction
strHTML += "<b>" + tr("From") + ":</b> " + GUIUtil::HtmlEscape(wtx.mapValue["from"]) + "<br>";
Expand Down Expand Up @@ -143,7 +143,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
//
// Amount
//
if (wtx.IsCoinBase() && nCredit == 0)
if ((wtx.IsCoinBase() || wtx.IsCoinStake()) && nCredit == 0)
{
//
// Coinbase
Expand Down Expand Up @@ -280,9 +280,9 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
}
}

if (wtx.IsCoinBase())
if (wtx.IsCoinBase() || wtx.IsCoinStake())
{
quint32 numBlocksToMaturity = Consensus::Params().nCoinbaseMaturity + 1;
int numBlocksToMaturity = Consensus::Params().nCoinbaseMaturity;
strHTML += "<br>" + tr("Generated coins must mature %1 blocks before they can be spent. When you generated this block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, its state will change to \"not accepted\" and it won't be spendable. This may occasionally happen if another node generates a block within a few seconds of yours.").arg(QString::number(numBlocksToMaturity)) + "<br>";
}

Expand Down
11 changes: 11 additions & 0 deletions src/qt/transactiontablemodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,11 @@ QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
case TransactionRecord::PrivateSend:
return tr("PrivateSend");

case TransactionRecord::MNReward:
return tr("Masternode Reward");
case TransactionRecord::StakeMint:
return tr("ION Stake");

default:
return QString();
}
Expand All @@ -443,6 +448,8 @@ QVariant TransactionTableModel::txAddressDecoration(const TransactionRecord *wtx
switch(wtx->type)
{
case TransactionRecord::Generated:
case TransactionRecord::StakeMint:
case TransactionRecord::MNReward:
return QIcon(":/icons/tx_mined");
case TransactionRecord::RecvWithPrivateSend:
case TransactionRecord::RecvWithAddress:
Expand Down Expand Up @@ -473,6 +480,8 @@ QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, b
case TransactionRecord::RecvWithPrivateSend:
case TransactionRecord::SendToAddress:
case TransactionRecord::Generated:
case TransactionRecord::StakeMint:
case TransactionRecord::MNReward:
case TransactionRecord::PrivateSend:
return formatAddressLabel(wtx->strAddress, QString::fromStdString(wtx->status.label), tooltip) + watchAddress;
case TransactionRecord::SendToOther:
Expand All @@ -491,6 +500,8 @@ QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const
case TransactionRecord::RecvWithAddress:
case TransactionRecord::SendToAddress:
case TransactionRecord::Generated:
case TransactionRecord::StakeMint:
case TransactionRecord::MNReward:
case TransactionRecord::PrivateSend:
case TransactionRecord::RecvWithPrivateSend:
{
Expand Down
2 changes: 2 additions & 0 deletions src/qt/transactionview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa
typeWidget->addItem(tr("PrivateSend Collateral Payment"), TransactionFilterProxy::TYPE(TransactionRecord::PrivateSendCollateralPayment));
typeWidget->addItem(tr("To yourself"), TransactionFilterProxy::TYPE(TransactionRecord::SendToSelf));
typeWidget->addItem(tr("Mined"), TransactionFilterProxy::TYPE(TransactionRecord::Generated));
typeWidget->addItem(tr("Minted"), TransactionFilterProxy::TYPE(TransactionRecord::StakeMint));
typeWidget->addItem(tr("Masternode Reward"), TransactionFilterProxy::TYPE(TransactionRecord::MNReward));
typeWidget->addItem(tr("Other"), TransactionFilterProxy::TYPE(TransactionRecord::Other));
typeWidget->setCurrentIndex(settings.value("transactionType").toInt());

Expand Down
7 changes: 4 additions & 3 deletions src/rpc/blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1069,7 +1069,7 @@ static void ApplyStats(CCoinsStats &stats, CHashWriter& ss, const uint256& hash,
{
assert(!outputs.empty());
ss << hash;
ss << VARINT(outputs.begin()->second.nHeight * 2 + outputs.begin()->second.fCoinBase);
ss << VARINT(outputs.begin()->second.nHeight * 4 + outputs.begin()->second.fCoinStake * 2 + outputs.begin()->second.fCoinBase);
stats.nTransactions++;
for (const auto output : outputs) {
ss << VARINT(output.first + 1);
Expand Down Expand Up @@ -1288,6 +1288,7 @@ UniValue gettxout(const JSONRPCRequest& request)
ScriptPubKeyToUniv(coin.out.scriptPubKey, o, true);
ret.push_back(Pair("scriptPubKey", o));
ret.push_back(Pair("coinbase", (bool)coin.fCoinBase));
ret.push_back(Pair("coinstake", (bool)coin.fCoinStake));

return ret;
}
Expand Down Expand Up @@ -1861,8 +1862,8 @@ static inline bool SetHasKeys(const std::set<T>& set, const Tk& key, const Args&
return (set.count(key) != 0) || SetHasKeys(set, args...);
}

// outpoint (needed for the utxo index) + nHeight + fCoinBase
static constexpr size_t PER_UTXO_OVERHEAD = sizeof(COutPoint) + sizeof(uint32_t) + sizeof(bool);
// outpoint (needed for the utxo index) + nHeight + fCoinBase + fCoinStake
static constexpr size_t PER_UTXO_OVERHEAD = sizeof(COutPoint) + sizeof(uint32_t) + sizeof(bool) + sizeof(bool);

static UniValue getblockstats(const JSONRPCRequest& request)
{
Expand Down
43 changes: 22 additions & 21 deletions src/test/coins_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ bool operator==(const Coin &a, const Coin &b) {
// Empty Coin objects are always equal.
if (a.IsSpent() && b.IsSpent()) return true;
return a.fCoinBase == b.fCoinBase &&
a.fCoinStake == b.fCoinStake &&
a.nHeight == b.nHeight &&
a.out == b.out;
}
Expand Down Expand Up @@ -372,7 +373,7 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
// Update the expected result to know about the new output coins
assert(tx.vout.size() == 1);
const COutPoint outpoint(tx.GetHash(), 0);
result[outpoint] = Coin(tx.vout[0], height, CTransaction(tx).IsCoinBase());
result[outpoint] = Coin(tx.vout[0], height, CTransaction(tx).IsCoinBase(), CTransaction(tx).IsCoinStake());

// Call UpdateCoins on the top cache
CTxUndo undo;
Expand Down Expand Up @@ -707,7 +708,7 @@ BOOST_AUTO_TEST_CASE(ccoins_spend)
CheckSpendCoins(VALUE1, VALUE2, ABSENT, DIRTY|FRESH, NO_ENTRY );
}

void CheckAddCoinBase(CAmount base_value, CAmount cache_value, CAmount modify_value, CAmount expected_value, char cache_flags, char expected_flags, bool coinbase)
void CheckAddCoinBase(CAmount base_value, CAmount cache_value, CAmount modify_value, CAmount expected_value, char cache_flags, char expected_flags, bool coinbase, bool coinstake)
{
SingleEntryCacheTest test(base_value, cache_value, cache_flags);

Expand All @@ -716,7 +717,7 @@ void CheckAddCoinBase(CAmount base_value, CAmount cache_value, CAmount modify_va
try {
CTxOut output;
output.nValue = modify_value;
test.cache.AddCoin(OUTPOINT, Coin(std::move(output), 1, coinbase), coinbase);
test.cache.AddCoin(OUTPOINT, Coin(std::move(output), 1, coinbase, coinstake), coinbase);
test.cache.SelfTest();
GetCoinsMapEntry(test.cache.map(), result_value, result_flags);
} catch (std::logic_error& e) {
Expand Down Expand Up @@ -750,24 +751,24 @@ BOOST_AUTO_TEST_CASE(ccoins_add)
* Cache Write Result Cache Result potential_overwrite
* Value Value Value Flags Flags
*/
CheckAddCoin(ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY|FRESH, false);
CheckAddCoin(ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY , true );
CheckAddCoin(PRUNED, VALUE3, VALUE3, 0 , DIRTY|FRESH, false);
CheckAddCoin(PRUNED, VALUE3, VALUE3, 0 , DIRTY , true );
CheckAddCoin(PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH, false);
CheckAddCoin(PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH, true );
CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY , DIRTY , false);
CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY , DIRTY , true );
CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, false);
CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, true );
CheckAddCoin(VALUE2, VALUE3, FAIL , 0 , NO_ENTRY , false);
CheckAddCoin(VALUE2, VALUE3, VALUE3, 0 , DIRTY , true );
CheckAddCoin(VALUE2, VALUE3, FAIL , FRESH , NO_ENTRY , false);
CheckAddCoin(VALUE2, VALUE3, VALUE3, FRESH , DIRTY|FRESH, true );
CheckAddCoin(VALUE2, VALUE3, FAIL , DIRTY , NO_ENTRY , false);
CheckAddCoin(VALUE2, VALUE3, VALUE3, DIRTY , DIRTY , true );
CheckAddCoin(VALUE2, VALUE3, FAIL , DIRTY|FRESH, NO_ENTRY , false);
CheckAddCoin(VALUE2, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, true );
CheckAddCoin(ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY|FRESH, false, false);
CheckAddCoin(ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY , true , false);
CheckAddCoin(PRUNED, VALUE3, VALUE3, 0 , DIRTY|FRESH, false, false);
CheckAddCoin(PRUNED, VALUE3, VALUE3, 0 , DIRTY , true , false);
CheckAddCoin(PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH, false, false);
CheckAddCoin(PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH, true , false);
CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY , DIRTY , false, false);
CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY , DIRTY , true , false);
CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, false, false);
CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, true , false);
CheckAddCoin(VALUE2, VALUE3, FAIL , 0 , NO_ENTRY , false, false);
CheckAddCoin(VALUE2, VALUE3, VALUE3, 0 , DIRTY , true , false);
CheckAddCoin(VALUE2, VALUE3, FAIL , FRESH , NO_ENTRY , false, false);
CheckAddCoin(VALUE2, VALUE3, VALUE3, FRESH , DIRTY|FRESH, true , false);
CheckAddCoin(VALUE2, VALUE3, FAIL , DIRTY , NO_ENTRY , false, false);
CheckAddCoin(VALUE2, VALUE3, VALUE3, DIRTY , DIRTY , true , false);
CheckAddCoin(VALUE2, VALUE3, FAIL , DIRTY|FRESH, NO_ENTRY , false, false);
CheckAddCoin(VALUE2, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, true , false);
}

void CheckWriteCoins(CAmount parent_value, CAmount child_value, CAmount expected_value, char parent_flags, char child_flags, char expected_flags)
Expand Down
32 changes: 30 additions & 2 deletions src/transactionrecord.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,35 @@ std::vector<TransactionRecord> TransactionRecord::decomposeTransaction(const CWa
uint256 hash = wtx.GetHash();
std::map<std::string, std::string> mapValue = wtx.mapValue;

if (nNet > 0 || wtx.IsCoinBase())
if (wtx.tx->IsCoinStake()) {
TransactionRecord sub(hash, nTime);
CTxDestination address;
if (!ExtractDestination(wtx.tx->vout[1].scriptPubKey, address))
return parts;

if (isminetype mine = wallet->IsMine(wtx.tx->vout[1])) {
// ION stake reward
sub.involvesWatchAddress = mine & ISMINE_WATCH_ONLY;
sub.type = TransactionRecord::StakeMint;
sub.strAddress = CBitcoinAddress(address).ToString();
sub.credit = nNet;
} else {
//Masternode reward
CTxDestination destMN;
int nIndexMN = wtx.tx->vout.size() - 1;
if (ExtractDestination(wtx.tx->vout[nIndexMN].scriptPubKey, destMN) && IsMine(*wallet, destMN)) {
isminetype mine = wallet->IsMine(wtx.tx->vout[nIndexMN]);
sub.involvesWatchAddress = mine & ISMINE_WATCH_ONLY;
sub.type = TransactionRecord::MNReward;
sub.strAddress = CBitcoinAddress(destMN).ToString();
sub.credit = wtx.tx->vout[nIndexMN].nValue;
}
}

sub.address.SetString(sub.strAddress);
sub.txDest = sub.address.Get();
parts.push_back(sub);
} else if (nNet > 0 || wtx.IsCoinBase())
{
//
// Credit
Expand Down Expand Up @@ -300,7 +328,7 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx, int chainLockHeight)
}
}
// For generated transactions, determine maturity
else if(type == TransactionRecord::Generated)
else if(type == TransactionRecord::Generated || type == TransactionRecord::StakeMint || type == TransactionRecord::MNReward)
{
if (wtx.GetBlocksToMaturity() > 0)
{
Expand Down
6 changes: 4 additions & 2 deletions src/txdb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,7 @@ class CCoins
public:
//! whether transaction is a coinbase
bool fCoinBase;
bool fCoinStake;

//! unspent transaction outputs; spent outputs are .IsNull(); spent outputs at the end of the array are dropped
std::vector<CTxOut> vout;
Expand All @@ -479,7 +480,7 @@ class CCoins
int nHeight;

//! empty constructor
CCoins() : fCoinBase(false), vout(0), nHeight(0) { }
CCoins() : fCoinBase(false), fCoinStake(false), vout(0), nHeight(0) { }

template<typename Stream>
void Unserialize(Stream &s) {
Expand All @@ -489,6 +490,7 @@ class CCoins
::Unserialize(s, VARINT(nVersionDummy));
// header code
::Unserialize(s, VARINT(nCode));
fCoinStake = (nCode >> 1) & 1;
fCoinBase = nCode & 1;
std::vector<bool> vAvail(2, false);
vAvail[0] = (nCode & 2) != 0;
Expand Down Expand Up @@ -561,7 +563,7 @@ bool CCoinsViewDB::Upgrade() {
COutPoint outpoint(key.second, 0);
for (size_t i = 0; i < old_coins.vout.size(); ++i) {
if (!old_coins.vout[i].IsNull() && !old_coins.vout[i].scriptPubKey.IsUnspendable()) {
Coin newcoin(std::move(old_coins.vout[i]), old_coins.nHeight, old_coins.fCoinBase);
Coin newcoin(std::move(old_coins.vout[i]), old_coins.nHeight, old_coins.fCoinBase, old_coins.fCoinStake);
outpoint.n = i;
CoinEntry entry(&outpoint);
batch.Write(entry, newcoin);
Expand Down
2 changes: 1 addition & 1 deletion src/txmempool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1382,7 +1382,7 @@ bool CCoinsViewMemPool::GetCoin(const COutPoint &outpoint, Coin &coin) const {
CTransactionRef ptx = mempool.get(outpoint.hash);
if (ptx) {
if (outpoint.n < ptx->vout.size()) {
coin = Coin(ptx->vout[outpoint.n], MEMPOOL_HEIGHT, false);
coin = Coin(ptx->vout[outpoint.n], MEMPOOL_HEIGHT, false, false);
return true;
} else {
return false;
Expand Down
Loading

0 comments on commit c233181

Please sign in to comment.