Skip to content

Commit 442887f

Browse files
committed
Require timestamps for importmulti keys
Additionally, accept a "now" timestamp, to allow avoiding rescans for keys which are known never to have been used. Note that the behavior when "now" is specified is slightly different than the previous behavior when no timestamp was specified at all. Previously, when no timestamp was specified, it would avoid rescanning during the importmulti call, but set the key's nCreateTime value to 1, which would not prevent future block reads in later ScanForWalletTransactions calls. With this change, passing a "now" timestamp will set the key's nCreateTime to the current block time instead of 1. Fixes #9491
1 parent 02464da commit 442887f

File tree

3 files changed

+64
-9
lines changed

3 files changed

+64
-9
lines changed

qa/rpc-tests/import-rescan.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def call_import_rpc(call, data, address, scriptPubKey, pubkey, key, label, node,
3333
"scriptPubKey": {
3434
"address": address
3535
},
36+
"timestamp": "now",
3637
"pubkeys": [pubkey] if data == Data.pub else [],
3738
"keys": [key] if data == Data.priv else [],
3839
"label": label,

qa/rpc-tests/importmulti.py

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ def run_test (self):
5252
result = self.nodes[1].importmulti([{
5353
"scriptPubKey": {
5454
"address": address['address']
55-
}
55+
},
56+
"timestamp": "now",
5657
}])
5758
assert_equal(result[0]['success'], True)
5859
address_assert = self.nodes[1].validateaddress(address['address'])
@@ -65,6 +66,7 @@ def run_test (self):
6566
address = self.nodes[0].validateaddress(self.nodes[0].getnewaddress())
6667
result = self.nodes[1].importmulti([{
6768
"scriptPubKey": address['scriptPubKey'],
69+
"timestamp": "now",
6870
"internal": True
6971
}])
7072
assert_equal(result[0]['success'], True)
@@ -76,7 +78,8 @@ def run_test (self):
7678
print("Should not import a scriptPubKey without internal flag")
7779
address = self.nodes[0].validateaddress(self.nodes[0].getnewaddress())
7880
result = self.nodes[1].importmulti([{
79-
"scriptPubKey": address['scriptPubKey']
81+
"scriptPubKey": address['scriptPubKey'],
82+
"timestamp": "now",
8083
}])
8184
assert_equal(result[0]['success'], False)
8285
assert_equal(result[0]['error']['code'], -8)
@@ -93,6 +96,7 @@ def run_test (self):
9396
"scriptPubKey": {
9497
"address": address['address']
9598
},
99+
"timestamp": "now",
96100
"pubkeys": [ address['pubkey'] ]
97101
}])
98102
assert_equal(result[0]['success'], True)
@@ -106,6 +110,7 @@ def run_test (self):
106110
address = self.nodes[0].validateaddress(self.nodes[0].getnewaddress())
107111
request = [{
108112
"scriptPubKey": address['scriptPubKey'],
113+
"timestamp": "now",
109114
"pubkeys": [ address['pubkey'] ],
110115
"internal": True
111116
}]
@@ -120,6 +125,7 @@ def run_test (self):
120125
address = self.nodes[0].validateaddress(self.nodes[0].getnewaddress())
121126
request = [{
122127
"scriptPubKey": address['scriptPubKey'],
128+
"timestamp": "now",
123129
"pubkeys": [ address['pubkey'] ]
124130
}]
125131
result = self.nodes[1].importmulti(request)
@@ -137,6 +143,7 @@ def run_test (self):
137143
"scriptPubKey": {
138144
"address": address['address']
139145
},
146+
"timestamp": "now",
140147
"keys": [ self.nodes[0].dumpprivkey(address['address']) ]
141148
}])
142149
assert_equal(result[0]['success'], True)
@@ -151,6 +158,7 @@ def run_test (self):
151158
"scriptPubKey": {
152159
"address": address['address']
153160
},
161+
"timestamp": "now",
154162
"keys": [ self.nodes[0].dumpprivkey(address['address']) ],
155163
"watchonly": True
156164
}])
@@ -166,6 +174,7 @@ def run_test (self):
166174
address = self.nodes[0].validateaddress(self.nodes[0].getnewaddress())
167175
result = self.nodes[1].importmulti([{
168176
"scriptPubKey": address['scriptPubKey'],
177+
"timestamp": "now",
169178
"keys": [ self.nodes[0].dumpprivkey(address['address']) ],
170179
"internal": True
171180
}])
@@ -179,6 +188,7 @@ def run_test (self):
179188
address = self.nodes[0].validateaddress(self.nodes[0].getnewaddress())
180189
result = self.nodes[1].importmulti([{
181190
"scriptPubKey": address['scriptPubKey'],
191+
"timestamp": "now",
182192
"keys": [ self.nodes[0].dumpprivkey(address['address']) ]
183193
}])
184194
assert_equal(result[0]['success'], False)
@@ -203,7 +213,8 @@ def run_test (self):
203213
result = self.nodes[1].importmulti([{
204214
"scriptPubKey": {
205215
"address": multi_sig_script['address']
206-
}
216+
},
217+
"timestamp": "now",
207218
}])
208219
assert_equal(result[0]['success'], True)
209220
address_assert = self.nodes[1].validateaddress(multi_sig_script['address'])
@@ -229,6 +240,7 @@ def run_test (self):
229240
"scriptPubKey": {
230241
"address": multi_sig_script['address']
231242
},
243+
"timestamp": "now",
232244
"redeemscript": multi_sig_script['redeemScript']
233245
}])
234246
assert_equal(result[0]['success'], True)
@@ -253,6 +265,7 @@ def run_test (self):
253265
"scriptPubKey": {
254266
"address": multi_sig_script['address']
255267
},
268+
"timestamp": "now",
256269
"redeemscript": multi_sig_script['redeemScript'],
257270
"keys": [ self.nodes[0].dumpprivkey(sig_address_1['address']), self.nodes[0].dumpprivkey(sig_address_2['address'])]
258271
}])
@@ -277,6 +290,7 @@ def run_test (self):
277290
"scriptPubKey": {
278291
"address": multi_sig_script['address']
279292
},
293+
"timestamp": "now",
280294
"redeemscript": multi_sig_script['redeemScript'],
281295
"keys": [ self.nodes[0].dumpprivkey(sig_address_1['address']), self.nodes[0].dumpprivkey(sig_address_2['address'])],
282296
"watchonly": True
@@ -294,6 +308,7 @@ def run_test (self):
294308
"scriptPubKey": {
295309
"address": address['address']
296310
},
311+
"timestamp": "now",
297312
"pubkeys": [ address2['pubkey'] ]
298313
}])
299314
assert_equal(result[0]['success'], False)
@@ -310,6 +325,7 @@ def run_test (self):
310325
address2 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress())
311326
request = [{
312327
"scriptPubKey": address['scriptPubKey'],
328+
"timestamp": "now",
313329
"pubkeys": [ address2['pubkey'] ],
314330
"internal": True
315331
}]
@@ -330,6 +346,7 @@ def run_test (self):
330346
"scriptPubKey": {
331347
"address": address['address']
332348
},
349+
"timestamp": "now",
333350
"keys": [ self.nodes[0].dumpprivkey(address2['address']) ]
334351
}])
335352
assert_equal(result[0]['success'], False)
@@ -346,6 +363,7 @@ def run_test (self):
346363
address2 = self.nodes[0].validateaddress(self.nodes[0].getnewaddress())
347364
result = self.nodes[1].importmulti([{
348365
"scriptPubKey": address['scriptPubKey'],
366+
"timestamp": "now",
349367
"keys": [ self.nodes[0].dumpprivkey(address2['address']) ],
350368
"internal": True
351369
}])
@@ -356,5 +374,18 @@ def run_test (self):
356374
assert_equal(address_assert['iswatchonly'], False)
357375
assert_equal(address_assert['ismine'], False)
358376

377+
# Bad or missing timestamps
378+
print("Should throw on invalid or missing timestamp values")
379+
assert_raises_message(JSONRPCException, 'Missing required timestamp field for key',
380+
self.nodes[1].importmulti, [{
381+
"scriptPubKey": address['scriptPubKey'],
382+
}])
383+
assert_raises_message(JSONRPCException, 'Expected number or "now" timestamp value for key. got type string',
384+
self.nodes[1].importmulti, [{
385+
"scriptPubKey": address['scriptPubKey'],
386+
"timestamp": "",
387+
}])
388+
389+
359390
if __name__ == '__main__':
360391
ImportMultiTest ().main ()

src/wallet/rpcdump.cpp

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -640,7 +640,8 @@ UniValue dumpwallet(const JSONRPCRequest& request)
640640
}
641641

642642

643-
UniValue processImport(const UniValue& data) {
643+
UniValue ProcessImport(const UniValue& data, const int64_t timestamp)
644+
{
644645
try {
645646
bool success = false;
646647

@@ -659,7 +660,6 @@ UniValue processImport(const UniValue& data) {
659660
const bool& internal = data.exists("internal") ? data["internal"].get_bool() : false;
660661
const bool& watchOnly = data.exists("watchonly") ? data["watchonly"].get_bool() : false;
661662
const string& label = data.exists("label") && !internal ? data["label"].get_str() : "";
662-
const int64_t& timestamp = data.exists("timestamp") && data["timestamp"].get_int64() > 1 ? data["timestamp"].get_int64() : 1;
663663

664664
bool isScript = scriptPubKey.getType() == UniValue::VSTR;
665665
bool isP2SH = strRedeemScript.length() > 0;
@@ -958,6 +958,20 @@ UniValue processImport(const UniValue& data) {
958958
}
959959
}
960960

961+
int64_t GetImportTimestamp(const UniValue& data, int64_t now)
962+
{
963+
if (data.exists("timestamp")) {
964+
const UniValue& timestamp = data["timestamp"];
965+
if (timestamp.isNum()) {
966+
return timestamp.get_int64();
967+
} else if (timestamp.isStr() && timestamp.get_str() == "now") {
968+
return now;
969+
}
970+
throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Expected number or \"now\" timestamp value for key. got type %s", uvTypeName(timestamp.type())));
971+
}
972+
throw JSONRPCError(RPC_TYPE_ERROR, "Missing required timestamp field for key");
973+
}
974+
961975
UniValue importmulti(const JSONRPCRequest& mainRequest)
962976
{
963977
// clang-format off
@@ -970,13 +984,17 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
970984
" [ (array of json objects)\n"
971985
" {\n"
972986
" \"scriptPubKey\": \"<script>\" | { \"address\":\"<address>\" }, (string / json, required) Type of scriptPubKey (string for script, json for address)\n"
987+
" \"timestamp\": timestamp | \"now\" , (integer / string, required) Creation time of the key in seconds since epoch (Jan 1 1970 GMT),\n"
988+
" or the string \"now\" to substitute the current synced blockchain time. The timestamp of the oldest\n"
989+
" key will determine how far back blockchain rescans need to begin for missing wallet transactions.\n"
990+
" \"now\" can be specified to bypass scanning, for keys which are known to never have been used, and\n"
991+
" 0 can be specified to scan the entire blockchain.\n"
973992
" \"redeemscript\": \"<script>\" , (string, optional) Allowed only if the scriptPubKey is a P2SH address or a P2SH scriptPubKey\n"
974993
" \"pubkeys\": [\"<pubKey>\", ... ] , (array, optional) Array of strings giving pubkeys that must occur in the output or redeemscript\n"
975994
" \"keys\": [\"<key>\", ... ] , (array, optional) Array of strings giving private keys whose corresponding public keys must occur in the output or redeemscript\n"
976995
" \"internal\": <true> , (boolean, optional, default: false) Stating whether matching outputs should be be treated as not incoming payments\n"
977996
" \"watchonly\": <true> , (boolean, optional, default: false) Stating whether matching outputs should be considered watched even when they're not spendable, only allowed if keys are empty\n"
978997
" \"label\": <label> , (string, optional, default: '') Label to assign to the address (aka account name, for now), only allowed with internal=false\n"
979-
" \"timestamp\": 1454686740, (integer, optional, default now) Timestamp\n"
980998
" }\n"
981999
" ,...\n"
9821000
" ]\n"
@@ -1015,6 +1033,12 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
10151033
LOCK2(cs_main, pwalletMain->cs_wallet);
10161034
EnsureWalletIsUnlocked();
10171035

1036+
// Verify all timestamps are present before importing any keys.
1037+
const int64_t now = chainActive.Tip() ? chainActive.Tip()->GetBlockTime() : 0;
1038+
for (const UniValue& data : requests.getValues()) {
1039+
GetImportTimestamp(data, now);
1040+
}
1041+
10181042
bool fRunScan = false;
10191043
const int64_t minimumTimestamp = 1;
10201044
int64_t nLowestTimestamp = 0;
@@ -1028,7 +1052,8 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
10281052
UniValue response(UniValue::VARR);
10291053

10301054
BOOST_FOREACH (const UniValue& data, requests.getValues()) {
1031-
const UniValue result = processImport(data);
1055+
const int64_t timestamp = std::max(GetImportTimestamp(data, now), minimumTimestamp);
1056+
const UniValue result = ProcessImport(data, timestamp);
10321057
response.push_back(result);
10331058

10341059
if (!fRescan) {
@@ -1041,8 +1066,6 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
10411066
}
10421067

10431068
// Get the lowest timestamp.
1044-
const int64_t& timestamp = data.exists("timestamp") && data["timestamp"].get_int64() > minimumTimestamp ? data["timestamp"].get_int64() : minimumTimestamp;
1045-
10461069
if (timestamp < nLowestTimestamp) {
10471070
nLowestTimestamp = timestamp;
10481071
}

0 commit comments

Comments
 (0)