Skip to content

Commit 695204e

Browse files
committed
Add filtering options for name_scan.
This extends the "options" argument for name_scan to allow filtering of names by the number of confirmations, the prefix and a regexp as described in namecoin/namecoin-core#237. ("showExpired" is not yet implemented, as that will be part of a more general change also for other read-only RPCs in a later step.) With these options, name_scan now supports every feature of name_filter except for the "stats" mode. The latter will be removed in a follow-up change; the name_scanning.py regtest is already updated to only test name_scan (but all the new features) in this commit. This also implements namecoin/namecoin-core#16 through the new "prefix" option for name_scan.
1 parent e980bb9 commit 695204e

File tree

3 files changed

+160
-56
lines changed

3 files changed

+160
-56
lines changed

doc/release-notes-namecoin.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66
can now be used to specify per-RPC encodings for names and values by setting
77
the `nameEncoding` and `valueEncoding` fields, respectively.
88

9+
- `name_scan` now accepts an optional `options` argument, which can be used
10+
to specify filtering conditions (based on number of confirmations, prefix and
11+
regexp matches of a name).
12+
See [#237](https://github.com/namecoin/namecoin-core/issues/237)
13+
for more details.
14+
915
## Version 0.17
1016

1117
- Previously, `createrawtransaction` supported a separate argument for creating

src/rpc/names.cpp

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
#include <boost/xpressive/xpressive_dynamic.hpp>
2727

28+
#include <algorithm>
2829
#include <cassert>
2930
#include <memory>
3031
#include <stdexcept>
@@ -542,18 +543,90 @@ name_scan (const JSONRPCRequest& request)
542543
if (request.params.size () >= 2)
543544
count = request.params[1].get_int ();
544545

546+
/* Parse and interpret the name_scan-specific options. */
547+
RPCTypeCheckObj (options,
548+
{
549+
{"minConf", UniValueType (UniValue::VNUM)},
550+
{"maxConf", UniValueType (UniValue::VNUM)},
551+
{"prefix", UniValueType (UniValue::VSTR)},
552+
{"regexp", UniValueType (UniValue::VSTR)},
553+
},
554+
true, false);
555+
556+
int minConf = 1;
557+
if (options.exists ("minConf"))
558+
minConf = options["minConf"].get_int ();
559+
if (minConf < 1)
560+
throw JSONRPCError (RPC_INVALID_PARAMETER, "minConf must be >= 1");
561+
562+
int maxConf = -1;
563+
if (options.exists ("maxConf"))
564+
{
565+
maxConf = options["maxConf"].get_int ();
566+
if (maxConf < 0)
567+
throw JSONRPCError (RPC_INVALID_PARAMETER,
568+
"maxConf must not be negative");
569+
}
570+
571+
valtype prefix;
572+
if (options.exists ("prefix"))
573+
prefix = DecodeNameFromRPCOrThrow (options["prefix"], options);
574+
575+
bool haveRegexp = false;
576+
boost::xpressive::sregex regexp;
577+
if (options.exists ("regexp"))
578+
{
579+
haveRegexp = true;
580+
regexp = boost::xpressive::sregex::compile (options["regexp"].get_str ());
581+
}
582+
583+
/* Iterate over names and produce the result. */
545584
UniValue res(UniValue::VARR);
546585
if (count <= 0)
547586
return res;
548587

549588
MaybeWalletForRequest wallet(request);
550589
LOCK2 (cs_main, wallet.getLock ());
551590

591+
const int maxHeight = chainActive.Height () - minConf + 1;
592+
int minHeight = -1;
593+
if (maxConf >= 0)
594+
minHeight = chainActive.Height () - maxConf + 1;
595+
552596
valtype name;
553597
CNameData data;
554598
std::unique_ptr<CNameIterator> iter(pcoinsTip->IterateNames ());
555-
for (iter->seek (start); count > 0 && iter->next (name, data); --count)
556-
res.push_back (getNameInfo (options, name, data, wallet));
599+
for (iter->seek (start); count > 0 && iter->next (name, data); )
600+
{
601+
const int height = data.getHeight ();
602+
if (height > maxHeight)
603+
continue;
604+
if (minHeight >= 0 && height < minHeight)
605+
continue;
606+
607+
if (name.size () < prefix.size ())
608+
continue;
609+
if (!std::equal (prefix.begin (), prefix.end (), name.begin ()))
610+
continue;
611+
612+
if (haveRegexp)
613+
{
614+
try
615+
{
616+
const std::string nameStr = EncodeName (name, NameEncoding::UTF8);
617+
boost::xpressive::smatch matches;
618+
if (!boost::xpressive::regex_search (nameStr, matches, regexp))
619+
continue;
620+
}
621+
catch (const InvalidNameString& exc)
622+
{
623+
continue;
624+
}
625+
}
626+
627+
res.push_back (getNameInfo (options, name, data, wallet));
628+
--count;
629+
}
557630

558631
return res;
559632
}

test/functional/name_scanning.py

Lines changed: 79 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# Distributed under the MIT/X11 software license, see the accompanying
44
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
55

6-
# RPC test for the name scanning functions (name_scan and name_filter).
6+
# RPC test for the name_scan RPC method.
77

88
from test_framework.names import NameTestFramework
99
from test_framework.util import *
@@ -22,83 +22,107 @@ def run_test (self):
2222
# Initially, all should be empty.
2323
assert_equal (self.node.name_scan (), [])
2424
assert_equal (self.node.name_scan ("foo", 10), [])
25-
assert_equal (self.node.name_filter (), [])
26-
assert_equal (self.node.name_filter ("", 0, 0, 0, "stat"),
27-
{"blocks": 201,"count": 0})
2825

2926
# Register some names with various data, heights and expiration status.
3027
# Using both "aa" and "b" ensures that we can also check for the expected
3128
# comparison order between string length and lexicographic ordering.
32-
33-
newA = self.node.name_new ("a")
34-
newAA = self.node.name_new ("aa")
35-
newB = self.node.name_new ("b")
36-
newC = self.node.name_new ("c")
29+
newA = self.node.name_new ("d/a")
30+
newAA = self.node.name_new ("d/aa")
31+
newB = self.node.name_new ("d/b")
32+
newC = self.node.name_new ("d/c")
3733
self.node.generate (15)
3834

39-
self.firstupdateName (0, "a", newA, "wrong value")
40-
self.firstupdateName (0, "aa", newAA, "value aa")
41-
self.firstupdateName (0, "b", newB, "value b")
35+
self.firstupdateName (0, "d/a", newA, "wrong value")
36+
self.firstupdateName (0, "d/aa", newAA, "value aa")
37+
self.firstupdateName (0, "d/b", newB, "value b")
4238
self.node.generate (15)
43-
self.firstupdateName (0, "c", newC, "value c")
44-
self.node.name_update ("a", "value a")
39+
self.firstupdateName (0, "d/c", newC, "value c")
40+
self.node.name_update ("d/a", "value a")
4541
self.node.generate (20)
4642

4743
# Check the expected name_scan data values.
4844
scan = self.node.name_scan ()
4945
assert_equal (len (scan), 4)
50-
self.checkNameData (scan[0], "a", "value a", 11, False)
51-
self.checkNameData (scan[1], "b", "value b", -4, True)
52-
self.checkNameData (scan[2], "c", "value c", 11, False)
53-
self.checkNameData (scan[3], "aa", "value aa", -4, True)
46+
self.checkNameData (scan[0], "d/a", "value a", 11, False)
47+
self.checkNameData (scan[1], "d/b", "value b", -4, True)
48+
self.checkNameData (scan[2], "d/c", "value c", 11, False)
49+
self.checkNameData (scan[3], "d/aa", "value aa", -4, True)
5450

55-
# Check for expected names in various name_scan calls.
56-
self.checkList (self.node.name_scan (), ["a", "b", "c", "aa"])
51+
# Check for expected names in various basic name_scan calls.
52+
self.checkList (self.node.name_scan (), ["d/a", "d/b", "d/c", "d/aa"])
5753
self.checkList (self.node.name_scan ("", 0), [])
5854
self.checkList (self.node.name_scan ("", -1), [])
59-
self.checkList (self.node.name_scan ("b"), ["b", "c", "aa"])
60-
self.checkList (self.node.name_scan ("zz"), [])
61-
self.checkList (self.node.name_scan ("", 2), ["a", "b"])
62-
self.checkList (self.node.name_scan ("b", 1), ["b"])
63-
64-
# Check the expected name_filter data values.
65-
scan = self.node.name_scan ()
66-
assert_equal (len (scan), 4)
67-
self.checkNameData (scan[0], "a", "value a", 11, False)
68-
self.checkNameData (scan[1], "b", "value b", -4, True)
69-
self.checkNameData (scan[2], "c", "value c", 11, False)
70-
self.checkNameData (scan[3], "aa", "value aa", -4, True)
71-
72-
# Check for expected names in various name_filter calls.
73-
height = self.node.getblockcount ()
74-
self.checkList (self.node.name_filter (), ["a", "b", "c", "aa"])
75-
self.checkList (self.node.name_filter ("[ac]"), ["a", "c", "aa"])
76-
self.checkList (self.node.name_filter ("", 10), [])
77-
self.checkList (self.node.name_filter ("", 30), ["a", "c"])
78-
self.checkList (self.node.name_filter ("", 0, 0, 0),
79-
["a", "b", "c", "aa"])
80-
self.checkList (self.node.name_filter ("", 0, 0, 1), ["a"])
81-
self.checkList (self.node.name_filter ("", 0, 1, 4), ["b", "c", "aa"])
82-
self.checkList (self.node.name_filter ("", 0, 4, 4), [])
83-
assert_equal (self.node.name_filter ("", 30, 0, 0, "stat"),
84-
{"blocks": height, "count": 2})
85-
86-
# Check test for "stat" argument.
87-
assert_raises_rpc_error (-8, "must be the literal string 'stat'",
88-
self.node.name_filter, "", 0, 0, 0, "string")
55+
self.checkList (self.node.name_scan ("d/b"), ["d/b", "d/c", "d/aa"])
56+
self.checkList (self.node.name_scan ("d/zz"), [])
57+
self.checkList (self.node.name_scan ("", 2), ["d/a", "d/b"])
58+
self.checkList (self.node.name_scan ("d/b", 1), ["d/b"])
59+
60+
# Verify encoding for start argument.
61+
self.checkList (self.node.name_scan ("642f63", 10, {"nameEncoding": "hex"}),
62+
["642f63", "642f6161"])
63+
64+
# Verify filtering based on number of confirmations.
65+
self.checkList (self.node.name_scan ("", 100, {"minConf": 35}),
66+
["d/b", "d/aa"])
67+
self.checkList (self.node.name_scan ("", 100, {"minConf": 36}), [])
68+
self.checkList (self.node.name_scan ("", 100, {"maxConf": 19}), [])
69+
self.checkList (self.node.name_scan ("", 100, {"maxConf": 20}),
70+
["d/a", "d/c"])
71+
72+
# Verify interaction with filtering and count.
73+
self.checkList (self.node.name_scan ("", 1, {"maxConf": 20}), ["d/a"])
74+
self.checkList (self.node.name_scan ("", 2, {"maxConf": 20}),
75+
["d/a", "d/c"])
76+
77+
# Error checks for confirmation options.
78+
assert_raises_rpc_error (-8, "minConf must be >= 1",
79+
self.node.name_scan, "", 100, {"minConf": 0})
80+
assert_raises_rpc_error (-8, "minConf must be >= 1",
81+
self.node.name_scan, "", 100, {"minConf": -42})
82+
self.node.name_scan ("", 100, {"minConf": 1})
83+
assert_raises_rpc_error (-8, "maxConf must not be negative",
84+
self.node.name_scan, "", 100, {"maxConf": -1})
85+
self.node.name_scan ("", 100, {"maxConf": 0})
86+
87+
# Verify filtering based on prefix.
88+
self.checkList (self.node.name_scan ("", 100, {"prefix": ""}),
89+
["d/a", "d/b", "d/c", "d/aa"])
90+
self.checkList (self.node.name_scan ("", 100, {"prefix": "d/a"}),
91+
["d/a", "d/aa"])
92+
93+
# Check prefix and nameEncoding.
94+
options = {
95+
"prefix": "642f61",
96+
"nameEncoding": "hex",
97+
}
98+
self.checkList (self.node.name_scan ("", 100, options),
99+
["642f61", "642f6161"])
100+
assert_raises_rpc_error (-1000, "Name/value is invalid for encoding ascii",
101+
self.node.name_scan, "", 100, {"prefix": "äöü"})
102+
103+
# Verify filtering based on regexp.
104+
self.checkList (self.node.name_scan ("", 100, {"regexp": "[ac]"}),
105+
["d/a", "d/c", "d/aa"])
106+
107+
# Multiple filters are combined using "and".
108+
options = {
109+
"prefix": "d/a",
110+
"maxConf": 20,
111+
}
112+
self.checkList (self.node.name_scan ("", 100, options), ["d/a"])
89113

90114
# Include a name with invalid UTF-8 to make sure it doesn't break
91-
# name_filter's regexp check.
115+
# the regexp check.
92116
self.restart_node (0, extra_args=["-nameencoding=hex"])
93117
hexName = "642f00ff"
94118
new = self.node.name_new (hexName)
95119
self.node.generate (10)
96120
self.firstupdateName (0, hexName, new, "{}")
97121
self.node.generate (5)
98-
fullHexList = ['61', '62', '63', '6161', hexName]
122+
fullHexList = ['642f61', '642f62', '642f63', hexName, '642f6161']
99123
self.checkList (self.node.name_scan (), fullHexList)
100-
self.checkList (self.node.name_filter (), fullHexList)
101-
self.checkList (self.node.name_filter ("a"), ['61', '6161'])
124+
self.checkList (self.node.name_scan ("", 100, {"regexp": "a"}),
125+
['642f61', '642f6161'])
102126

103127
def checkList (self, data, names):
104128
"""
@@ -112,5 +136,6 @@ def walker (e):
112136

113137
assert_equal (dataNames, names)
114138

139+
115140
if __name__ == '__main__':
116141
NameScanningTest ().main ()

0 commit comments

Comments
 (0)