Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix coinbase transaction amount returned by filtertransactions #971

Merged
merged 2 commits into from
Apr 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ UNITE_TESTS += \
test/validation_block_tests.cpp \
test/waiter_tests.cpp \
wallet/test/rpcvalidator_tests.cpp \
wallet/test/rpcwalletext_tests.cpp \
wallet/test/accounting_tests.cpp \
wallet/test/wallet_tests.cpp \
wallet/test/crypto_tests.cpp
Expand Down
115 changes: 47 additions & 68 deletions src/wallet/rpcwalletext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -484,14 +484,13 @@ static void TxWithOutputsToJSON(const CWalletTx &wtx, CWallet *const pwallet,
UniValue entry(UniValue::VOBJ);

// GetAmounts variables
std::list<COutputEntry> listReceived;
std::list<COutputEntry> listSent;
std::list<COutputEntry> listStaked;
std::list<COutputEntry> list_received;
std::list<COutputEntry> list_sent;
CAmount fee;
CAmount amount = 0;
std::string strSentAccount;
std::string sent_account;

wtx.GetAmounts(listReceived, listSent, fee, strSentAccount, ISMINE_ALL);
wtx.GetAmounts(list_received, list_sent, fee, sent_account, ISMINE_ALL);

if (wtx.IsFromMe(ISMINE_WATCH_ONLY) && !(watchonly & ISMINE_WATCH_ONLY)) {
return;
Expand All @@ -502,87 +501,67 @@ static void TxWithOutputsToJSON(const CWalletTx &wtx, CWallet *const pwallet,

UniValue outputs(UniValue::VARR);
// common to every type of transaction
if (!strSentAccount.empty()) {
entry.pushKV("account", strSentAccount);
if (!sent_account.empty()) {
entry.pushKV("account", sent_account);
}
WalletTxToJSON(wtx, entry);

if (!listStaked.empty() || !listSent.empty()) {
if (!list_sent.empty()) {
entry.pushKV("abandoned", wtx.isAbandoned());
}

// UNIT-E: TODO: get staked transactions
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the reason why this branch was removed, see #779 (comment).

if (!listStaked.empty()) {
if (!wtx.IsInMainChain()) {
entry.pushKV("category", "orphaned_stake");
} else {
entry.pushKV("category", "stake");
}
for (const auto &s : listStaked) {
std::set<int> receive_outputs;
for (const auto &r : list_received) {
receive_outputs.insert(r.vout);
}

// sent
if (!list_sent.empty()) {
entry.pushKV("fee", ValueFromAmount(-fee));
for (const auto &s : list_sent) {
UniValue output(UniValue::VOBJ);
if (!OutputToJSON(output, s, pwallet, wtx, watchonly)) {
return;
}

output.pushKV("amount", ValueFromAmount(s.amount));
outputs.push_back(output);
}

amount += -fee;
} else {
std::set<int> receivedOutputs;
for (const auto &r : listReceived) {
receivedOutputs.insert(r.vout);
}

// sent
if (!listSent.empty()) {
entry.pushKV("fee", ValueFromAmount(-fee));
for (const auto &s : listSent) {
UniValue output(UniValue::VOBJ);
if (!OutputToJSON(output, s, pwallet, wtx, watchonly)) {
return;
}
amount -= s.amount;
if (receivedOutputs.count(s.vout) == 0) {
output.pushKV("amount", ValueFromAmount(-s.amount));
outputs.push_back(output);
}
amount -= s.amount;
if (receive_outputs.count(s.vout) == 0) {
output.pushKV("amount", ValueFromAmount(-s.amount));
outputs.push_back(output);
}
}
}

// received
for (const auto &r : listReceived) {
UniValue output(UniValue::VOBJ);
if (!OutputToJSON(output, r, pwallet, wtx, watchonly)) {
return;
}
// received
for (const auto &r : list_received) {
UniValue output(UniValue::VOBJ);
if (!OutputToJSON(output, r, pwallet, wtx, watchonly)) {
return;
}

output.pushKV("amount", ValueFromAmount(r.amount));
amount += r.amount;
output.pushKV("amount", ValueFromAmount(r.amount));
amount += r.amount;

outputs.push_back(output);
}
outputs.push_back(output);
}

if (wtx.IsCoinBase()) {
if (!wtx.IsInMainChain()) {
entry.pushKV("category", "orphan");
} else if (wtx.GetBlocksToRewardMaturity() > 0) {
entry.pushKV("category", "immature");
} else {
entry.pushKV("category", "coinbase");
}
} else if (!fee) {
entry.pushKV("category", "receive");
} else if (amount == 0) {
if (listSent.empty()) {
entry.pushKV("fee", ValueFromAmount(-fee));
}
entry.pushKV("category", "internal_transfer");
if (wtx.IsCoinBase()) {
if (!wtx.IsInMainChain()) {
entry.pushKV("category", "orphan");
} else if (wtx.GetBlocksToRewardMaturity() > 0) {
entry.pushKV("category", "immature");
cmihai marked this conversation as resolved.
Show resolved Hide resolved
} else {
entry.pushKV("category", "send");
entry.pushKV("category", "coinbase");
}
};
} else if (!fee) {
entry.pushKV("category", "receive");
} else if (amount == 0) {
if (list_sent.empty()) {
entry.pushKV("fee", ValueFromAmount(-fee));
}
entry.pushKV("category", "internal_transfer");
} else {
entry.pushKV("category", "send");
}

entry.pushKV("outputs", outputs);
entry.pushKV("amount", ValueFromAmount(amount));
Expand Down
49 changes: 49 additions & 0 deletions src/wallet/test/rpcwalletext_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) 2019 The Unit-e developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.


#include <rpc/server.h>
#include <script/standard.h>
#include <test/rpc_test_utils.h>
#include <validation.h>
#include <wallet/rpcwalletext.h>
#include <wallet/test/wallet_test_fixture.h>

#include <boost/test/unit_test.hpp>


// Retrieve details about a transaction with specified number of confirmations
static UniValue FindByConfirmations(int confirmations) {
const UniValue transactions = CallRPC("filtertransactions {\"count\":0}");

const auto &values = transactions.getValues();
const auto it = std::find_if(values.begin(), values.end(), [&confirmations](const UniValue &x) {
return find_value(x, "confirmations").get_int64() == confirmations;
});

return *it;
}


BOOST_AUTO_TEST_SUITE(rpcwalletext_tests)

BOOST_FIXTURE_TEST_CASE(genesis_block_coinbase, TestChain100Setup) {
const UniValue genesis_coinbase = FindByConfirmations(COINBASE_MATURITY + 1);

BOOST_CHECK_EQUAL(find_value(genesis_coinbase, "category").get_str(), "coinbase");

// The returned amount should equal the amount credited to us
BOOST_CHECK_EQUAL(find_value(genesis_coinbase, "amount").get_real(), 10000);
}

BOOST_FIXTURE_TEST_CASE(regular_coinbase, TestChain100Setup) {
const UniValue regular_coinbase = FindByConfirmations(1);

BOOST_CHECK_EQUAL(find_value(regular_coinbase, "category").get_str(), "immature");

// The amount should equal the block reward
BOOST_CHECK_EQUAL(find_value(regular_coinbase, "amount").get_real(), 3.75);
}

BOOST_AUTO_TEST_SUITE_END()
2 changes: 2 additions & 0 deletions src/wallet/test/wallet_test_fixture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <validation.h>
#include <wallet/db.h>
#include <wallet/rpcvalidator.h>
#include <wallet/rpcwalletext.h>
#include <consensus/merkle.h>

WalletTestingSetup::WalletTestingSetup(const std::string& chainName)
Expand All @@ -34,6 +35,7 @@ WalletTestingSetup::WalletTestingSetup(std::function<void(Settings&)> f, const s

RegisterWalletRPCCommands(tableRPC);
RegisterValidatorRPCCommands(tableRPC);
RegisterWalletextRPCCommands(tableRPC);
}

WalletTestingSetup::~WalletTestingSetup()
Expand Down
20 changes: 18 additions & 2 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1685,7 +1685,9 @@ void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived,

// Compute fee:
CAmount nDebit = GetDebit(filter);
if (nDebit > 0) { // debit>0 means we signed/sent this transaction
if (nDebit > 0 && !IsCoinBase()) {
// debit>0 means we signed/sent this transaction
// Coinbase transactions cannot have fees
CAmount nValueOut = tx->GetValueOut();
nFee = nDebit - nValueOut;
}
Expand All @@ -1699,7 +1701,7 @@ void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived,
// 2) the output is to us (received)
if (nDebit > 0) {
// Don't report 'change' txouts
if (pwallet->IsChange(txout)) {
if (!IsCoinBase() && pwallet->IsChange(txout)) {
continue;
}
} else if (!(fIsMine & filter)) {
Expand All @@ -1717,6 +1719,20 @@ void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived,

COutputEntry output = {address, txout.nValue, (int)i};

if (IsCoinBase()) {
// TODO UNIT-E: implement a better way to distinguish reward outputs
// For example, current approach does not work in case of stake splitting
if (i == tx->vout.size() - 1) {
// We consider the last output (and only it) to be a non-reward output
if (nDebit > 0 && !(fIsMine & filter)) {
listSent.push_back(output);
}
} else if (fIsMine & filter) {
listReceived.push_back(output);
}
continue;
}

// If we are debited by the transaction, add the output as a "sent" entry
if (nDebit > 0) {
listSent.push_back(output);
Expand Down
4 changes: 2 additions & 2 deletions test/functional/proposer_balance.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def test_empty_blocks_balance(self, created_money, nodes):
coinstake_tx_id = block_info['tx'][0]
coinstake_tx_info = nodes[node_idx].gettransaction(coinstake_tx_id)

created_money += coinstake_tx_info['details'][0]['fee']
created_money += coinstake_tx_info['details'][0]['amount']
assert_equal(
created_money,
nodes[node_idx].gettxoutsetinfo()['total_amount']
Expand Down Expand Up @@ -97,7 +97,7 @@ def test_transaction_blocks_balance(self, created_money, nodes):
]

coinstake_tx_info = transactions[0]
created_money += coinstake_tx_info['details'][0]['fee']
created_money += coinstake_tx_info['details'][0]['amount']

# We want to subtract the fees because are not created money
for tx in transactions[1:]:
Expand Down
15 changes: 14 additions & 1 deletion test/functional/rpc_filtertransactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

from test_framework.test_framework import UnitETestFramework
from test_framework.test_framework import (
UnitETestFramework,
PROPOSER_REWARD,
)
from test_framework.util import *
from test_framework.address import *

Expand Down Expand Up @@ -95,6 +98,16 @@ def test_output_format(self):
)
assert result_tx is None

# coinbase
sync_mempools(self.nodes)
self.nodes[0].generatetoaddress(1, self.nodes[0].getnewaddress()) # Clear node1's mempool
sync_blocks(self.nodes)

address = self.nodes[1].getnewaddress()
self.nodes[1].generatetoaddress(1, address)
tx = self.nodes[1].filtertransactions({"search": address})[0]
assert_equal(tx['amount'], PROPOSER_REWARD)

def test_count_option(self):
# count: -1 => JSONRPCException
assert_raises_rpc_error(-8, "Invalid count", self.nodes[0].filtertransactions, {"count": -1})
Expand Down