Skip to content

Commit

Permalink
hdwallet: Fix filtertransactions by type.
Browse files Browse the repository at this point in the history
  • Loading branch information
tecnovert committed Jul 2, 2019
1 parent bbba621 commit 1e8db96
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 42 deletions.
2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ define(_CLIENT_VERSION_MAJOR, 0)
define(_CLIENT_VERSION_MINOR, 18)
define(_CLIENT_VERSION_REVISION, 0)
define(_CLIENT_VERSION_PARTICL, 11)
define(_CLIENT_VERSION_BUILD, 1)
define(_CLIENT_VERSION_BUILD, 2)
define(_CLIENT_VERSION_RC, 0)
define(_CLIENT_VERSION_IS_RELEASE, true)
define(_COPYRIGHT_YEAR, 2019)
Expand Down
1 change: 1 addition & 0 deletions doc/release-notes-particl.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- Added smsgsetwallet RPC command to switch the active SMSG wallet without disabling SMSG.
- Unloading the active SMSG wallet will leave SMSG enabled.
- Fixed DOS vulnerability.
- Fixed rpc cmd filtertransactions filtering by type.


0.18.0.10
Expand Down
102 changes: 66 additions & 36 deletions src/wallet/rpchdwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2605,7 +2605,8 @@ static void ParseOutputs(
CWalletTx &wtx,
const CHDWallet *pwallet,
const isminefilter &watchonly,
std::string &search,
const std::string &search,
const std::string &category_filter,
bool fWithReward,
bool fBech32,
bool hide_zero_coinstakes,
Expand Down Expand Up @@ -2773,8 +2774,11 @@ static void ParseOutputs(
nInput += pwallet->GetOutputValue(vin.prevout, true);
}
entry.pushKV("reward", ValueFromAmount(nOutput - nInput));
};
}

if (category_filter != "all" && category_filter != entry["category"].get_str()) {
return;
}
if (search != "") {
// search in addresses
if (std::any_of(addresses.begin(), addresses.end(), [search](std::string addr) {
Expand Down Expand Up @@ -2810,7 +2814,9 @@ static void ParseRecords(
const CTransactionRecord &rtx,
CHDWallet *const pwallet,
const isminefilter &watchonly_filter,
std::string search
const std::string &search,
const std::string &category_filter,
int type
) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
{
std::vector<std::string> addresses, amounts;
Expand Down Expand Up @@ -2843,6 +2849,7 @@ static void ParseRecords(
}
PushTime(entry, "time", rtx.nTimeReceived);

int nStd = 0, nBlind = 0, nAnon = 0;
size_t nLockedOutputs = 0;
for (auto &record : rtx.vout) {
UniValue output(UniValue::VOBJ);
Expand Down Expand Up @@ -2909,7 +2916,13 @@ static void ParseRecords(
addresses.push_back(addr.ToString());
}

push(output, "type",
switch (record.nType) {
case OUTPUT_STANDARD: ++nStd; break;
case OUTPUT_CT: ++nBlind; break;
case OUTPUT_RINGCT: ++nAnon; break;
default: ++nStd = 0;
}
output.__pushKV("type",
record.nType == OUTPUT_STANDARD ? "standard"
: record.nType == OUTPUT_CT ? "blind"
: record.nType == OUTPUT_RINGCT ? "anon"
Expand All @@ -2930,19 +2943,43 @@ static void ParseRecords(
outputs.push_back(output);
}

if (type > 0) {
if (type == OUTPUT_STANDARD && !nStd) {
return;
}
if (type == OUTPUT_CT && !nBlind && !(rtx.nFlags & ORF_BLIND_IN)) {
return;
}
if (type == OUTPUT_RINGCT && !nAnon && !(rtx.nFlags & ORF_ANON_IN)) {
return;
}
}

if (nFrom > 0) {
push(entry, "abandoned", rtx.IsAbandoned());
push(entry, "fee", ValueFromAmount(-rtx.nFee));
}

std::string category;
if (nOwned && nFrom) {
push(entry, "category", "internal_transfer");
category = "internal_transfer";
} else if (nOwned && !nFrom) {
push(entry, "category", "receive");
category = "receive";
} else if (nFrom) {
push(entry, "category", "send");
category = "send";
} else {
push(entry, "category", "unknown");
category = "unknown";
}
if (category_filter != "all" && category_filter != category) {
return;
}
entry.__pushKV("category", category);

if (rtx.nFlags & ORF_ANON_IN) {
entry.__pushKV("type_in", "anon");
} else
if (rtx.nFlags & ORF_BLIND_IN) {
entry.__pushKV("type_in", "blind");
}

if (nLockedOutputs) {
Expand Down Expand Up @@ -2984,15 +3021,15 @@ static void ParseRecords(
return addr.find(search) != std::string::npos;
})) {
entries.push_back(entry);
return ;
return;
}
// search in amounts
// character DOT '.' is not searched for: search "123" will find 1.23 and 12.3
if (std::any_of(amounts.begin(), amounts.end(), [search](std::string amount) {
return amount.find(search) != std::string::npos;
})) {
entries.push_back(entry);
return ;
return;
}
} else {
entries.push_back(entry);
Expand Down Expand Up @@ -3226,6 +3263,7 @@ static UniValue filtertransactions(const JSONRPCRequest &request)
// transaction processing
const CHDWallet::TxItems &txOrdered = pwallet->wtxOrdered;
CWallet::TxItems::const_reverse_iterator tit = txOrdered.rbegin();
if (type == "all" || type == "standard")
while (tit != txOrdered.rend()) {
CWalletTx *const pwtx = tit->second;
int64_t txTime = pwtx->GetTxTime();
Expand All @@ -3238,6 +3276,7 @@ static UniValue filtertransactions(const JSONRPCRequest &request)
pwallet,
watchonly,
search,
category,
fWithReward,
fBech32,
hide_zero_coinstakes,
Expand All @@ -3246,6 +3285,10 @@ static UniValue filtertransactions(const JSONRPCRequest &request)
tit++;
}

int type_i = type == "standard" ? OUTPUT_STANDARD :
type == "blind" ? OUTPUT_CT :
type == "anon" ? OUTPUT_RINGCT :
0;
// records processing
const RtxOrdered_t &rtxOrdered = pwallet->rtxOrdered;
RtxOrdered_t::const_reverse_iterator rit = rtxOrdered.rbegin();
Expand All @@ -3262,7 +3305,9 @@ static UniValue filtertransactions(const JSONRPCRequest &request)
rtx,
pwallet,
watchonly,
search
search,
category,
type_i
);
rit++;
}
Expand Down Expand Up @@ -3299,32 +3344,17 @@ static UniValue filtertransactions(const JSONRPCRequest &request)
}
// for every value while count is positive
for (unsigned int i = 0; i < values.size() && count != 0; i++) {
// if value's category is relevant
if (values[i]["category"].get_str() == category || category == "all") {
// if value's type is not relevant
if (values[i]["type"].getType() == 0) {
// value's type is undefined
if (!(type == "all" || type == "standard")) {
// type is not 'all' or 'standard'
continue ;
// if we've skipped enough valid values
if (skip-- <= 0) {
result.push_back(values[i]);
count--;

if (fCollate) {
if (!values[i]["amount"].isNull()) {
nTotalAmount += AmountFromValue(values[i]["amount"]);
}
} else if (!(values[i]["type"].get_str() == type || type == "all")) {
// value's type is defined
// value's type is not type or 'all'
continue ;
}
// if we've skipped enough valid values
if (skip-- <= 0) {
result.push_back(values[i]);
count--;

if (fCollate) {
if (!values[i]["amount"].isNull()) {
nTotalAmount += AmountFromValue(values[i]["amount"]);
}
if (!values[i]["reward"].isNull()) {
nTotalReward += AmountFromValue(values[i]["reward"]);
}
if (!values[i]["reward"].isNull()) {
nTotalReward += AmountFromValue(values[i]["reward"]);
}
}
}
Expand Down
15 changes: 13 additions & 2 deletions test/functional/feature_part_anon.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# Copyright (c) 2017-2018 The Particl Core developers
# Copyright (c) 2017-2019 The Particl Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

Expand Down Expand Up @@ -120,7 +120,18 @@ def run_test(self):
txnHash = nodes[1].sendtypeto('anon', 'part', outputs)
txnHashes = [txnHash,]


self.log.info('Test filtertransactions with type filter')
ro = nodes[1].filtertransactions({ 'type': 'anon', 'count': 20 })
assert(len(ro) > 2)
for t in ro:
foundA = False
for o in t['outputs']:
if 'type' in o and o['type'] == 'anon':
foundA = True
break
assert((foundA is True) or (t['type_in'] == 'anon'))

self.log.info('Test unspent with address filter')
unspent_filtered = nodes[1].listunspentanon(1, 9999, [sxAddrTo1_1])
assert(unspent_filtered[0]['label'] == 'lblsx11')

Expand Down
27 changes: 24 additions & 3 deletions test/functional/rpc_part_filtertransactions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# Copyright (c) 2017-2018 The Particl Core developers
# Copyright (c) 2017-2019 The Particl Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

Expand Down Expand Up @@ -269,18 +269,33 @@ def run_test(self):

# type 'standard'
ro = nodes[0].filtertransactions({ 'type': 'standard', 'count': 20 })
assert(len(ro) == 9)
for t in ro:
assert('type' not in t)
for o in t['outputs']:
assert('type' not in o)

# type 'anon'
ro = nodes[0].filtertransactions({ 'type': 'anon', 'count': 20 })
assert(len(ro) == 1)
for t in ro:
assert(t["type"] == 'anon')
foundA = False
for o in t['outputs']:
if 'type' in o and o['type'] == 'anon':
foundA = True
break
assert(foundA is True)

# type 'blind'
ro = nodes[0].filtertransactions({ 'type': 'blind', 'count': 20 })
assert(len(ro) == 1)
for t in ro:
assert(t["type"] == 'blind')
foundB = False
for o in t['outputs']:
if 'type' in o and o['type'] == 'blind':
foundB = True
break
assert(foundB is True)

# invalid transaction type
try:
Expand Down Expand Up @@ -328,5 +343,11 @@ def run_test(self):
except JSONRPCException as e:
assert('Invalid sort' in e.error['message'])

# Sent blind should show when filtered for blinded txns
nodes[0].sendblindtopart(targetStealth, 1.0)
ro = nodes[0].filtertransactions({ 'type': 'blind', 'count': 20 })
assert(len(ro) == 2)


if __name__ == '__main__':
FilterTransactionsTest().main()

0 comments on commit 1e8db96

Please sign in to comment.