Skip to content

Commit a6d7026

Browse files
author
MarcoFalke
committed
Merge #15497: rpc: Consistent range arguments in scantxoutset/importmulti/deriveaddresses
ca253f6 Make deriveaddresses use stop/[start,stop] notation for ranges (Pieter Wuille) 1675b7c Use stop/[start,stop] notation in importmulti desc range (Pieter Wuille) 4566011 Add support for stop/[start,stop] ranges to scantxoutset (Pieter Wuille) 6b9f45e Support ranges arguments in RPC help (Pieter Wuille) 7aa6a8a Add ParseRange function to parse args of the form int/[int,int] (Pieter Wuille) Pull request description: This introduces a consistent notation for RPC arguments in `scantxoutset`, `importmulti`, and `deriveaddresses`, either: * `"range" : int` to just specify the end of the range * `"range" : [int,int]` to specify both the begin and the end of the range. For `scantxoutset`, this is a backward compatible new feature. For the two other RPCs, it's an incompatible change, but neither of them has been in a release so far. Because of that non-released reason, this only makes sense in 0.18, in my opinion. I suggest this as an alternative to #15496, which only makes `deriveaddresses` compatible with `importmulti`, but not with the existing `scantxoutset` RPC. I also think `[int,int]` is more convenient than `{"start":int,"stop":int}`. I realize this is technically a feature added to `scantxoutset` after the feature freeze. If desired, I'll drop the `scantxoutset` changes. Tree-SHA512: 1cbebb90cf34f106786dbcec7afbf3f43fb8b7e46cc7e6763faf1bc1babf12375a1b3c3cf86ee83c21ed2171d99b5a2f60331850bc613db25538c38b6a056676
2 parents e8612ad + ca253f6 commit a6d7026

File tree

8 files changed

+67
-42
lines changed

8 files changed

+67
-42
lines changed

src/rpc/blockchain.cpp

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2159,7 +2159,7 @@ UniValue scantxoutset(const JSONRPCRequest& request)
21592159
{"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "An object with output descriptor and metadata",
21602160
{
21612161
{"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "An output descriptor"},
2162-
{"range", RPCArg::Type::NUM, /* default */ "1000", "Up to what child index HD chains should be explored"},
2162+
{"range", RPCArg::Type::RANGE, /* default */ "1000", "The range of HD chain indexes to explore (either end or [begin,end])"},
21632163
},
21642164
},
21652165
},
@@ -2216,7 +2216,7 @@ UniValue scantxoutset(const JSONRPCRequest& request)
22162216
// loop through the scan objects
22172217
for (const UniValue& scanobject : request.params[1].get_array().getValues()) {
22182218
std::string desc_str;
2219-
int range = 1000;
2219+
std::pair<int64_t, int64_t> range = {0, 1000};
22202220
if (scanobject.isStr()) {
22212221
desc_str = scanobject.get_str();
22222222
} else if (scanobject.isObject()) {
@@ -2225,8 +2225,8 @@ UniValue scantxoutset(const JSONRPCRequest& request)
22252225
desc_str = desc_uni.get_str();
22262226
UniValue range_uni = find_value(scanobject, "range");
22272227
if (!range_uni.isNull()) {
2228-
range = range_uni.get_int();
2229-
if (range < 0 || range > 1000000) throw JSONRPCError(RPC_INVALID_PARAMETER, "range out of range");
2228+
range = ParseRange(range_uni);
2229+
if (range.first < 0 || (range.second >> 31) != 0 || range.second >= range.first + 1000000) throw JSONRPCError(RPC_INVALID_PARAMETER, "range out of range");
22302230
}
22312231
} else {
22322232
throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan object needs to be either a string or an object");
@@ -2237,8 +2237,11 @@ UniValue scantxoutset(const JSONRPCRequest& request)
22372237
if (!desc) {
22382238
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid descriptor '%s'", desc_str));
22392239
}
2240-
if (!desc->IsRange()) range = 0;
2241-
for (int i = 0; i <= range; ++i) {
2240+
if (!desc->IsRange()) {
2241+
range.first = 0;
2242+
range.second = 0;
2243+
}
2244+
for (int i = range.first; i <= range.second; ++i) {
22422245
std::vector<CScript> scripts;
22432246
if (!desc->Expand(i, provider, scripts, provider)) {
22442247
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Cannot derive script without private keys: '%s'", desc_str));

src/rpc/misc.cpp

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ UniValue getdescriptorinfo(const JSONRPCRequest& request)
185185

186186
UniValue deriveaddresses(const JSONRPCRequest& request)
187187
{
188-
if (request.fHelp || request.params.empty() || request.params.size() > 3) {
188+
if (request.fHelp || request.params.empty() || request.params.size() > 2) {
189189
throw std::runtime_error(
190190
RPCHelpMan{"deriveaddresses",
191191
{"\nDerives one or more addresses corresponding to an output descriptor.\n"
@@ -199,37 +199,37 @@ UniValue deriveaddresses(const JSONRPCRequest& request)
199199
"For more information on output descriptors, see the documentation in the doc/descriptors.md file.\n"},
200200
{
201201
{"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The descriptor."},
202-
{"begin", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "If a ranged descriptor is used, this specifies the beginning of the range to import."},
203-
{"end", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "If a ranged descriptor is used, this specifies the end of the range to import."}
202+
{"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED_NAMED_ARG, "If a ranged descriptor is used, this specifies the end or the range (in [begin,end] notation) to derive."},
204203
},
205204
RPCResult{
206205
"[ address ] (array) the derived addresses\n"
207206
},
208207
RPCExamples{
209208
"First three native segwit receive addresses\n" +
210-
HelpExampleCli("deriveaddresses", "\"wpkh([d34db33f/84h/0h/0h]xpub6DJ2dNUysrn5Vt36jH2KLBT2i1auw1tTSSomg8PhqNiUtx8QX2SvC9nrHu81fT41fvDUnhMjEzQgXnQjKEu3oaqMSzhSrHMxyyoEAmUHQbY/0/*)#trd0mf0l\" 0 2")
209+
HelpExampleCli("deriveaddresses", "\"wpkh([d34db33f/84h/0h/0h]xpub6DJ2dNUysrn5Vt36jH2KLBT2i1auw1tTSSomg8PhqNiUtx8QX2SvC9nrHu81fT41fvDUnhMjEzQgXnQjKEu3oaqMSzhSrHMxyyoEAmUHQbY/0/*)#trd0mf0l\" \"[0,2]\"")
211210
}}.ToString()
212211
);
213212
}
214213

215-
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VNUM, UniValue::VNUM});
214+
RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType()}); // Range argument is checked later
216215
const std::string desc_str = request.params[0].get_str();
217216

218-
int range_begin = 0;
219-
int range_end = 0;
217+
int64_t range_begin = 0;
218+
int64_t range_end = 0;
220219

221-
if (request.params.size() >= 2) {
222-
if (request.params.size() == 2) {
223-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Missing range end parameter");
224-
}
225-
range_begin = request.params[1].get_int();
226-
range_end = request.params[2].get_int();
227-
if (range_begin < 0) {
220+
if (request.params.size() >= 2 && !request.params[1].isNull()) {
221+
auto range = ParseRange(request.params[1]);
222+
if (range.first < 0) {
228223
throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should be greater or equal than 0");
229224
}
230-
if (range_begin > range_end) {
231-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Range end should be equal to or greater than begin");
225+
if ((range.second >> 31) != 0) {
226+
throw JSONRPCError(RPC_INVALID_PARAMETER, "End of range is too high");
227+
}
228+
if (range.second >= range.first + 1000000) {
229+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Range is too large");
232230
}
231+
range_begin = range.first;
232+
range_end = range.second;
233233
}
234234

235235
FlatSigningProvider provider;

src/rpc/util.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ struct Sections {
200200
case RPCArg::Type::STR:
201201
case RPCArg::Type::NUM:
202202
case RPCArg::Type::AMOUNT:
203+
case RPCArg::Type::RANGE:
203204
case RPCArg::Type::BOOL: {
204205
if (outer_type == OuterType::NAMED_ARG) return; // Nothing more to do for non-recursive types on first recursion
205206
auto left = indent;
@@ -405,6 +406,10 @@ std::string RPCArg::ToDescriptionString() const
405406
ret += "numeric or string";
406407
break;
407408
}
409+
case Type::RANGE: {
410+
ret += "numeric or array";
411+
break;
412+
}
408413
case Type::BOOL: {
409414
ret += "boolean";
410415
break;
@@ -464,6 +469,8 @@ std::string RPCArg::ToStringObj(const bool oneline) const
464469
return res + "\"hex\"";
465470
case Type::NUM:
466471
return res + "n";
472+
case Type::RANGE:
473+
return res + "n or [n,n]";
467474
case Type::AMOUNT:
468475
return res + "amount";
469476
case Type::BOOL:
@@ -494,6 +501,7 @@ std::string RPCArg::ToString(const bool oneline) const
494501
return "\"" + m_name + "\"";
495502
}
496503
case Type::NUM:
504+
case Type::RANGE:
497505
case Type::AMOUNT:
498506
case Type::BOOL: {
499507
return m_name;
@@ -523,3 +531,17 @@ std::string RPCArg::ToString(const bool oneline) const
523531
}
524532
assert(false);
525533
}
534+
535+
std::pair<int64_t, int64_t> ParseRange(const UniValue& value)
536+
{
537+
if (value.isNum()) {
538+
return {0, value.get_int64()};
539+
}
540+
if (value.isArray() && value.size() == 2 && value[0].isNum() && value[1].isNum()) {
541+
int64_t low = value[0].get_int64();
542+
int64_t high = value[1].get_int64();
543+
if (low > high) throw JSONRPCError(RPC_INVALID_PARAMETER, "Range specified as [begin,end] must not have begin after end");
544+
return {low, high};
545+
}
546+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Range must be specified as end or as [begin,end]");
547+
}

src/rpc/util.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ unsigned int ParseConfirmTarget(const UniValue& value);
3838
RPCErrorCode RPCErrorFromTransactionError(TransactionError terr);
3939
UniValue JSONRPCTransactionError(TransactionError terr, const std::string& err_string = "");
4040

41+
//! Parse a JSON range specified as int64, or [int64, int64]
42+
std::pair<int64_t, int64_t> ParseRange(const UniValue& value);
43+
4144
struct RPCArg {
4245
enum class Type {
4346
OBJ,
@@ -48,6 +51,7 @@ struct RPCArg {
4851
OBJ_USER_KEYS, //!< Special type where the user must set the keys e.g. to define multiple addresses; as opposed to e.g. an options object where the keys are predefined
4952
AMOUNT, //!< Special type representing a floating point amount (can be either NUM or STR)
5053
STR_HEX, //!< Special type that is a STR with only hex chars
54+
RANGE, //!< Special type that is a NUM or [NUM,NUM]
5155
};
5256

5357
enum class Optional {

src/wallet/rpcdump.cpp

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,13 +1132,10 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID
11321132
if (!data.exists("range")) {
11331133
throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor is ranged, please specify the range");
11341134
}
1135-
const UniValue& range = data["range"];
1136-
range_start = range.exists("start") ? range["start"].get_int64() : 0;
1137-
if (!range.exists("end")) {
1138-
throw JSONRPCError(RPC_INVALID_PARAMETER, "End of range for descriptor must be specified");
1139-
}
1140-
range_end = range["end"].get_int64();
1141-
if (range_end < range_start || range_start < 0) {
1135+
auto range = ParseRange(data["range"]);
1136+
range_start = range.first;
1137+
range_end = range.second;
1138+
if (range_start < 0 || (range_end >> 31) != 0 || range_end - range_start >= 1000000) {
11421139
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid descriptor range specified");
11431140
}
11441141
}
@@ -1373,12 +1370,7 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
13731370
{"key", RPCArg::Type::STR, RPCArg::Optional::OMITTED, ""},
13741371
}
13751372
},
1376-
{"range", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "If a ranged descriptor is used, this specifies the start and end of the range to import",
1377-
{
1378-
{"start", RPCArg::Type::NUM, /* default */ "0", "Start of the range to import"},
1379-
{"end", RPCArg::Type::NUM, RPCArg::Optional::NO, "End of the range to import (inclusive)"},
1380-
}
1381-
},
1373+
{"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED, "If a ranged descriptor is used, this specifies the end or the range (in the form [begin,end]) to import"},
13821374
{"internal", RPCArg::Type::BOOL, /* default */ "false", "Stating whether matching outputs should be treated as not incoming payments (also known as change)"},
13831375
{"watchonly", RPCArg::Type::BOOL, /* default */ "false", "Stating whether matching outputs should be considered watchonly."},
13841376
{"label", RPCArg::Type::STR, /* default */ "''", "Label to assign to the address, only allowed with internal=false"},

test/functional/rpc_deriveaddresses.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,20 @@ def run_test(self):
2727
assert_equal(self.nodes[0].deriveaddresses(descriptor_pubkey), [address])
2828

2929
ranged_descriptor = "wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)#kft60nuy"
30-
assert_equal(self.nodes[0].deriveaddresses(ranged_descriptor, 0, 2), [address, "bcrt1qhku5rq7jz8ulufe2y6fkcpnlvpsta7rq4442dy", "bcrt1qpgptk2gvshyl0s9lqshsmx932l9ccsv265tvaq"])
30+
assert_equal(self.nodes[0].deriveaddresses(ranged_descriptor, [1, 2]), ["bcrt1qhku5rq7jz8ulufe2y6fkcpnlvpsta7rq4442dy", "bcrt1qpgptk2gvshyl0s9lqshsmx932l9ccsv265tvaq"])
31+
assert_equal(self.nodes[0].deriveaddresses(ranged_descriptor, 2), [address, "bcrt1qhku5rq7jz8ulufe2y6fkcpnlvpsta7rq4442dy", "bcrt1qpgptk2gvshyl0s9lqshsmx932l9ccsv265tvaq"])
3132

32-
assert_raises_rpc_error(-8, "Range should not be specified for an un-ranged descriptor", self.nodes[0].deriveaddresses, descsum_create("wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)"), 0, 2)
33+
assert_raises_rpc_error(-8, "Range should not be specified for an un-ranged descriptor", self.nodes[0].deriveaddresses, descsum_create("wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)"), [0, 2])
3334

3435
assert_raises_rpc_error(-8, "Range must be specified for a ranged descriptor", self.nodes[0].deriveaddresses, descsum_create("wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)"))
3536

36-
assert_raises_rpc_error(-8, "Missing range end parameter", self.nodes[0].deriveaddresses, descsum_create("wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)"), 0)
37+
assert_raises_rpc_error(-8, "End of range is too high", self.nodes[0].deriveaddresses, descsum_create("wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)"), 10000000000)
3738

38-
assert_raises_rpc_error(-8, "Range end should be equal to or greater than begin", self.nodes[0].deriveaddresses, descsum_create("wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)"), 2, 0)
39+
assert_raises_rpc_error(-8, "Range is too large", self.nodes[0].deriveaddresses, descsum_create("wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)"), [1000000000, 2000000000])
3940

40-
assert_raises_rpc_error(-8, "Range should be greater or equal than 0", self.nodes[0].deriveaddresses, descsum_create("wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)"), -1, 0)
41+
assert_raises_rpc_error(-8, "Range specified as [begin,end] must not have begin after end", self.nodes[0].deriveaddresses, descsum_create("wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)"), [2, 0])
42+
43+
assert_raises_rpc_error(-8, "Range should be greater or equal than 0", self.nodes[0].deriveaddresses, descsum_create("wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)"), [-1, 0])
4144

4245
combo_descriptor = descsum_create("combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)")
4346
assert_equal(self.nodes[0].deriveaddresses(combo_descriptor), ["mtfUoUax9L4tzXARpw1oTGxWyoogp52KhJ", "mtfUoUax9L4tzXARpw1oTGxWyoogp52KhJ", address, "2NDvEwGfpEqJWfybzpKPHF2XH3jwoQV3D7x"])

test/functional/rpc_scantxoutset.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ def run_test(self):
9595
assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)", "range": 1500}])['total_amount'], Decimal("28.672"))
9696
assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1499}])['total_amount'], Decimal("12.288"))
9797
assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1500}])['total_amount'], Decimal("28.672"))
98+
assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": [1500,1500]}])['total_amount'], Decimal("16.384"))
9899

99100
# Test the reported descriptors for a few matches
100101
assert_equal(descriptors(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/*)", "range": 1499}])), ["pkh([0c5f9a1e/0'/0'/0]026dbd8b2315f296d36e6b6920b1579ca75569464875c7ebe869b536a7d9503c8c)#dzxw429x", "pkh([0c5f9a1e/0'/0'/1]033e6f25d76c00bedb3a8993c7d5739ee806397f0529b1b31dda31ef890f19a60c)#43rvceed"])

test/functional/wallet_importmulti.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,7 @@ def run_test(self):
584584
self.log.info("Should import the ranged descriptor with specified range as solvable")
585585
self.test_importmulti({"desc": descsum_create(desc),
586586
"timestamp": "now",
587-
"range": {"end": 1}},
587+
"range": 1},
588588
success=True,
589589
warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."])
590590
for address in addresses:
@@ -807,7 +807,7 @@ def run_test(self):
807807
'desc': descsum_create('wpkh([80002067/0h/0h]' + xpub + '/*)'),
808808
'keypool': True,
809809
'timestamp': 'now',
810-
'range' : {'start': 0, 'end': 4}
810+
'range' : [0, 4],
811811
}]
812812
)
813813
for i in range(0, 5):

0 commit comments

Comments
 (0)