diff --git a/common/jsonrpc_errors.h b/common/jsonrpc_errors.h index 069f89ef9d14..d7e5219f5bb6 100644 --- a/common/jsonrpc_errors.h +++ b/common/jsonrpc_errors.h @@ -92,6 +92,8 @@ static const errcode_t DATASTORE_DEL_WRONG_GENERATION = 1201; static const errcode_t DATASTORE_UPDATE_ALREADY_EXISTS = 1202; static const errcode_t DATASTORE_UPDATE_DOES_NOT_EXIST = 1203; static const errcode_t DATASTORE_UPDATE_WRONG_GENERATION = 1204; +static const errcode_t DATASTORE_UPDATE_HAS_CHILDREN = 1205; +static const errcode_t DATASTORE_UPDATE_NO_CHILDREN = 1206; /* Errors from wait* commands */ static const errcode_t WAIT_TIMEOUT = 2000; diff --git a/doc/lightning-datastore.7 b/doc/lightning-datastore.7 index 0203f0c555c5..45f7bca5864c 100644 --- a/doc/lightning-datastore.7 +++ b/doc/lightning-datastore.7 @@ -11,8 +11,11 @@ The \fBdatastore\fR RPC command allows plugins to store data in the c-lightning database, for later retrieval\. -There can only be one entry for each \fIkey\fR, so prefixing with the -plugin name (e\.g\. \fBsummary.\fR) is recommended\. +\fIkey\fR is an array of values (though a single value is treated as a +one-element array), to form a heirarchy\. Using the first element of +the key as the plugin name (e\.g\. \fB[ "summary" ]\fR) is recommended\. +A key can either have children or a value, never both: parents are +created and removed automatically\. \fImode\fR is one of "must-create" (default, fails it it already exists), @@ -33,11 +36,17 @@ On success, an object is returned, containing: .RS .IP \[bu] -\fBkey\fR (string): The key which has been added to the datastore +\fBkey\fR (array of strings): +.RS .IP \[bu] -\fBgeneration\fR (u64): The number of times this has been updated +Part of the key added to the datastore + +.RE + .IP \[bu] -\fBhex\fR (hex): The hex data which has been added to the datastore +\fBgeneration\fR (u64, optional): The number of times this has been updated +.IP \[bu] +\fBhex\fR (hex, optional): The hex data which has been added to the datastore .IP \[bu] \fBstring\fR (string, optional): The data as a string, if it's valid utf-8 @@ -53,6 +62,10 @@ The following error codes may occur: .IP \[bu] 1204: The generation was wrong (and generation was specified) .IP \[bu] +1205: The key has children already\. +.IP \[bu] +1206: One of the parents already exists with a value\. +.IP \[bu] -32602: invalid parameters .RE @@ -68,4 +81,4 @@ Rusty Russell \fI is mainly responsible\. Main web site: \fIhttps://github.com/ElementsProject/lightning\fR -\" SHA256STAMP:0ef09e6f98d7e34e7d8339351c29ffc70be71fbf9f05f581488e3c7f603d3721 +\" SHA256STAMP:cd66becc88b27328ebf6629d1ea607166b935fcd970c8a4a851e45fc1abe67d8 diff --git a/doc/lightning-datastore.7.md b/doc/lightning-datastore.7.md index df3d389c6364..63a082e00233 100644 --- a/doc/lightning-datastore.7.md +++ b/doc/lightning-datastore.7.md @@ -12,8 +12,11 @@ DESCRIPTION The **datastore** RPC command allows plugins to store data in the c-lightning database, for later retrieval. -There can only be one entry for each *key*, so prefixing with the -plugin name (e.g. `summary.`) is recommended. +*key* is an array of values (though a single value is treated as a +one-element array), to form a heirarchy. Using the first element of +the key as the plugin name (e.g. `[ "summary" ]`) is recommended. +A key can either have children or a value, never both: parents are +created and removed automatically. *mode* is one of "must-create" (default, fails it it already exists), "must-replace" (fails it it doesn't already exist), @@ -31,9 +34,10 @@ RETURN VALUE [comment]: # (GENERATE-FROM-SCHEMA-START) On success, an object is returned, containing: -- **key** (string): The key which has been added to the datastore -- **generation** (u64): The number of times this has been updated -- **hex** (hex): The hex data which has been added to the datastore +- **key** (array of strings): + - Part of the key added to the datastore +- **generation** (u64, optional): The number of times this has been updated +- **hex** (hex, optional): The hex data which has been added to the datastore - **string** (string, optional): The data as a string, if it's valid utf-8 [comment]: # (GENERATE-FROM-SCHEMA-END) @@ -41,6 +45,8 @@ The following error codes may occur: - 1202: The key already exists (and mode said it must not) - 1203: The key does not exist (and mode said it must) - 1204: The generation was wrong (and generation was specified) +- 1205: The key has children already. +- 1206: One of the parents already exists with a value. - -32602: invalid parameters AUTHOR @@ -58,4 +64,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:0867f9910b75ef66e640a92aad55dbab7ce0b3278fd1fb200f91c2a1a6164409) +[comment]: # ( SHA256STAMP:a66ad377a86e479704a3f5db06cffbd54bcd34fc9d36724649ace7b1f1e16bce) diff --git a/doc/lightning-deldatastore.7 b/doc/lightning-deldatastore.7 index 9c88b7167f1f..6a26ca8e32ca 100644 --- a/doc/lightning-deldatastore.7 +++ b/doc/lightning-deldatastore.7 @@ -20,11 +20,17 @@ On success, an object is returned, containing: .RS .IP \[bu] -\fBkey\fR (string): The key which has been removed from the datastore +\fBkey\fR (array of strings): +.RS +.IP \[bu] +Part of the key added to the datastore + +.RE + .IP \[bu] -\fBgeneration\fR (u64): The number of times this has been updated +\fBgeneration\fR (u64, optional): The number of times this has been updated .IP \[bu] -\fBhex\fR (hex): The hex data which has removed from the datastore +\fBhex\fR (hex, optional): The hex data which has removed from the datastore .IP \[bu] \fBstring\fR (string, optional): The data as a string, if it's valid utf-8 @@ -53,4 +59,4 @@ Rusty Russell \fI is mainly responsible\. Main web site: \fIhttps://github.com/ElementsProject/lightning\fR -\" SHA256STAMP:784f58fc76fc32b92d043b67b0b7efb88534dd29a7fabda2d705cdc0611e3c11 +\" SHA256STAMP:5450068ba0e62da8f46465b4f87cce4b4a59d064efbb13d03b24228790173dcc diff --git a/doc/lightning-deldatastore.7.md b/doc/lightning-deldatastore.7.md index 516cc70e2d1e..9ceeec1bc3f7 100644 --- a/doc/lightning-deldatastore.7.md +++ b/doc/lightning-deldatastore.7.md @@ -20,9 +20,10 @@ RETURN VALUE [comment]: # (GENERATE-FROM-SCHEMA-START) On success, an object is returned, containing: -- **key** (string): The key which has been removed from the datastore -- **generation** (u64): The number of times this has been updated -- **hex** (hex): The hex data which has removed from the datastore +- **key** (array of strings): + - Part of the key added to the datastore +- **generation** (u64, optional): The number of times this has been updated +- **hex** (hex, optional): The hex data which has removed from the datastore - **string** (string, optional): The data as a string, if it's valid utf-8 [comment]: # (GENERATE-FROM-SCHEMA-END) @@ -46,4 +47,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:ca2b7b8f45b3ecd6332978599c803e38c4f80945119a777cb8ae346cbf063b10) +[comment]: # ( SHA256STAMP:cd6f944965165b0e276da493592f9decb15046150367e06ff3e5b5547517d4b9) diff --git a/doc/lightning-listdatastore.7 b/doc/lightning-listdatastore.7 index 2eb3ac0eeced..9cc545875709 100644 --- a/doc/lightning-listdatastore.7 +++ b/doc/lightning-listdatastore.7 @@ -11,8 +11,8 @@ The \fBlistdatastore\fR RPC command allows plugins to fetch data which was stored in the c-lightning database\. -All entries are returned in \fIkey\fR isn't present; if \fIkey\fR is present, -zero or one entries are returned\. +All immediate children of the \fIkey\fR (or root children) are returned: +a \fIkey\fR with children won't have a \fIhex\fR or \fIgeneration\fR entry\. .SH RETURN VALUE @@ -20,11 +20,17 @@ On success, an object containing \fBdatastore\fR is returned\. It is an array o .RS .IP \[bu] -\fBkey\fR (string): The key which from the datastore +\fBkey\fR (array of strings): +.RS +.IP \[bu] +Part of the key added to the datastore + +.RE + .IP \[bu] -\fBgeneration\fR (u64): The number of times this has been updated +\fBgeneration\fR (u64, optional): The number of times this has been updated .IP \[bu] -\fBhex\fR (hex): The hex data from the datastore +\fBhex\fR (hex, optional): The hex data from the datastore .IP \[bu] \fBstring\fR (string, optional): The data as a string, if it's valid utf-8 @@ -49,4 +55,4 @@ Rusty Russell \fI is mainly responsible\. Main web site: \fIhttps://github.com/ElementsProject/lightning\fR -\" SHA256STAMP:b4128fc60690b3161eb76295e98f042c7be0142342bffa461c4f50f223c10684 +\" SHA256STAMP:bf75b8d32d0c95b4b5d5b9e8c220afc7c26cf8eef318facc9fb2923fdc3ab93b diff --git a/doc/lightning-listdatastore.7.md b/doc/lightning-listdatastore.7.md index 410234d0986b..a226c9fe5abc 100644 --- a/doc/lightning-listdatastore.7.md +++ b/doc/lightning-listdatastore.7.md @@ -12,17 +12,18 @@ DESCRIPTION The **listdatastore** RPC command allows plugins to fetch data which was stored in the c-lightning database. -All entries are returned in *key* isn't present; if *key* is present, -zero or one entries are returned. +All immediate children of the *key* (or root children) are returned: +a *key* with children won't have a *hex* or *generation* entry. RETURN VALUE ------------ [comment]: # (GENERATE-FROM-SCHEMA-START) On success, an object containing **datastore** is returned. It is an array of objects, where each object contains: -- **key** (string): The key which from the datastore -- **generation** (u64): The number of times this has been updated -- **hex** (hex): The hex data from the datastore +- **key** (array of strings): + - Part of the key added to the datastore +- **generation** (u64, optional): The number of times this has been updated +- **hex** (hex, optional): The hex data from the datastore - **string** (string, optional): The data as a string, if it's valid utf-8 [comment]: # (GENERATE-FROM-SCHEMA-END) @@ -44,4 +45,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:a6503e3d2da8f9a35a0d461b5b93248f3fea306371ad62f98df613efea51959d) +[comment]: # ( SHA256STAMP:ee29b53cad20c6dfe9e19a979816280cc9f778507e5638d1803418284125e4c1) diff --git a/doc/schemas/datastore.schema.json b/doc/schemas/datastore.schema.json index 7e63c2a21c29..783642a4f3be 100644 --- a/doc/schemas/datastore.schema.json +++ b/doc/schemas/datastore.schema.json @@ -2,12 +2,15 @@ "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "additionalProperties": false, - "required": [ "key", "hex", "generation" ], + "required": [ "key" ], "properties": { "key": { - "type": "string", - "description": "The key which has been added to the datastore" - }, + "type": "array", + "items": { + "type": "string", + "description": "Part of the key added to the datastore" + } + }, "generation": { "type": "u64", "description": "The number of times this has been updated" diff --git a/doc/schemas/deldatastore.schema.json b/doc/schemas/deldatastore.schema.json index 19dd7d591723..4f0c7c347043 100644 --- a/doc/schemas/deldatastore.schema.json +++ b/doc/schemas/deldatastore.schema.json @@ -2,12 +2,15 @@ "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "additionalProperties": false, - "required": [ "key", "hex", "generation" ], + "required": [ "key" ], "properties": { "key": { - "type": "string", - "description": "The key which has been removed from the datastore" - }, + "type": "array", + "items": { + "type": "string", + "description": "Part of the key added to the datastore" + } + }, "generation": { "type": "u64", "description": "The number of times this has been updated" diff --git a/doc/schemas/listdatastore.schema.json b/doc/schemas/listdatastore.schema.json index d4f5f33f2f24..0ce336f04c21 100644 --- a/doc/schemas/listdatastore.schema.json +++ b/doc/schemas/listdatastore.schema.json @@ -9,11 +9,14 @@ "items": { "type": "object", "additionalProperties": false, - "required": [ "key", "hex", "generation" ], + "required": [ "key" ], "properties": { "key": { - "type": "string", - "description": "The key which from the datastore" + "type": "array", + "items": { + "type": "string", + "description": "Part of the key added to the datastore" + } }, "generation": { "type": "u64", diff --git a/lightningd/datastore.c b/lightningd/datastore.c index 6047705d4949..861260f0867d 100644 --- a/lightningd/datastore.c +++ b/lightningd/datastore.c @@ -5,16 +5,23 @@ #include static void json_add_datastore(struct json_stream *response, - const char *key, const u8 *data, + const char **key, const u8 *data, u64 generation) { - const char *str; - json_add_string(response, "key", key); - json_add_u64(response, "generation", generation); - json_add_hex(response, "hex", data, tal_bytelen(data)); - str = utf8_str(response, data, tal_bytelen(data)); - if (str) - json_add_string(response, "string", str); + json_array_start(response, "key"); + for (size_t i = 0; i < tal_count(key); i++) + json_add_string(response, NULL, key[i]); + json_array_end(response); + + if (data) { + const char *str; + + json_add_u64(response, "generation", generation); + json_add_hex(response, "hex", data, tal_bytelen(data)); + str = utf8_str(response, data, tal_bytelen(data)); + if (str) + json_add_string(response, "string", str); + } } enum ds_mode { @@ -51,19 +58,78 @@ static struct command_result *param_mode(struct command *cmd, return NULL; } +static struct command_result *param_list_or_string(struct command *cmd, + const char *name, + const char *buffer, + const jsmntok_t *tok, + const char ***str) +{ + if (tok->type == JSMN_ARRAY) { + size_t i; + const jsmntok_t *t; + *str = tal_arr(cmd, const char *, tok->size); + json_for_each_arr(i, t, tok) { + if (t->type != JSMN_STRING && t->type != JSMN_PRIMITIVE) + return command_fail_badparam(cmd, name, + buffer, t, + "should be string"); + (*str)[i] = json_strdup(*str, buffer, t); + } + } else if (tok->type == JSMN_STRING || tok->type == JSMN_PRIMITIVE) { + *str = tal_arr(cmd, const char *, 1); + (*str)[0] = json_strdup(*str, buffer, tok); + } else + return command_fail_badparam(cmd, name, + buffer, tok, + "should be string or array"); + return NULL; +} + +/* Does k1 match k2 as far as k2 goes? */ +static bool datastore_key_startswith(const char **k1, const char **k2) +{ + size_t k1len = tal_count(k1), k2len = tal_count(k2); + + if (k2len > k1len) + return false; + + for (size_t i = 0; i < k2len; i++) { + if (!streq(k1[i], k2[i])) + return false; + } + return true; +} + +static bool datastore_key_eq(const char **k1, const char **k2) +{ + return tal_count(k1) == tal_count(k2) + && datastore_key_startswith(k1, k2); +} + +static char *datastore_key_fmt(const tal_t *ctx, const char **key) +{ + char *ret = tal_strdup(ctx, "["); + for (size_t i = 0; i < tal_count(key); i++) + tal_append_fmt(&ret, "%s%s", i ? "," : "", key[i]); + tal_append_fmt(&ret, "]"); + return ret; +} + static struct command_result *json_datastore(struct command *cmd, const char *buffer, const jsmntok_t *obj UNNEEDED, const jsmntok_t *params) { struct json_stream *response; - const char *key, *strdata; - u8 *data, *prevdata; + const char **key, *strdata, **k; + u8 *data; + const u8 *prevdata; enum ds_mode *mode; u64 *generation, actual_gen; + struct db_stmt *stmt; if (!param(cmd, buffer, params, - p_req("key", param_string, &key), + p_req("key", param_list_or_string, &key), p_opt("string", param_string, &strdata), p_opt("hex", param_bin_from_hex, &data), p_opt_def("mode", param_mode, &mode, DS_MUST_NOT_EXIST), @@ -87,8 +153,40 @@ static struct command_result *json_datastore(struct command *cmd, "generation only valid with must-replace" " or must-append"); - prevdata = wallet_datastore_fetch(cmd, cmd->ld->wallet, key, - &actual_gen); + /* Fetch, and make sure we don't have children! */ + stmt = wallet_datastore_first(cmd, cmd->ld->wallet, key, + &k, &prevdata, &actual_gen); + tal_free(stmt); + + /* We use prevdata as a "does it exist?" flag */ + if (!stmt) + prevdata = NULL; + else if (!datastore_key_eq(k, key)) { + prevdata = tal_free(prevdata); + /* Make sure we don't have a child! */ + if (datastore_key_startswith(k, key)) + return command_fail(cmd, DATASTORE_UPDATE_HAS_CHILDREN, + "Key has children already"); + } + + /* We have to make sure that parents don't exist. */ + if (!prevdata) { + for (size_t i = 1; i < tal_count(key); i++) { + const char **parent; + parent = tal_dup_arr(cmd, const char *, key, i, 0); + + stmt = wallet_datastore_first(cmd, cmd->ld->wallet, + parent, &k, NULL, NULL); + tal_free(stmt); + if (stmt && datastore_key_eq(k, parent)) + return command_fail(cmd, + DATASTORE_UPDATE_NO_CHILDREN, + "Parent key %s exists", + datastore_key_fmt(tmpctx, + parent)); + } + } + if ((*mode & DS_MUST_NOT_EXIST) && prevdata) return command_fail(cmd, DATASTORE_UPDATE_ALREADY_EXISTS, "Key already exists"); @@ -103,9 +201,10 @@ static struct command_result *json_datastore(struct command *cmd, if ((*mode & DS_APPEND) && prevdata) { size_t prevlen = tal_bytelen(prevdata); - tal_resize(&prevdata, prevlen + tal_bytelen(data)); - memcpy(prevdata + prevlen, data, tal_bytelen(data)); - data = prevdata; + u8 *newdata = tal_arr(cmd, u8, prevlen + tal_bytelen(data)); + memcpy(newdata, prevdata, prevlen); + memcpy(newdata + prevlen, data, tal_bytelen(data)); + data = newdata; } if (prevdata) { @@ -127,36 +226,50 @@ static struct command_result *json_listdatastore(struct command *cmd, const jsmntok_t *params) { struct json_stream *response; - const char *key; + const char **key, **k, **prev_k = NULL; const u8 *data; u64 generation; + struct db_stmt *stmt; if (!param(cmd, buffer, params, - p_opt("key", param_string, &key), + p_opt("key", param_list_or_string, &key), NULL)) return command_param_failed(); + if (key) + log_debug(cmd->ld->log, "Looking for %s", + datastore_key_fmt(tmpctx, key)); + response = json_stream_success(cmd); json_array_start(response, "datastore"); - if (key) { - data = wallet_datastore_fetch(cmd, cmd->ld->wallet, key, - &generation); - if (data) { - json_object_start(response, NULL); - json_add_datastore(response, key, data, generation); - json_object_end(response); - } - } else { - struct db_stmt *stmt; - - for (stmt = wallet_datastore_first(cmd, cmd->ld->wallet, - &key, &data, &generation); - stmt; - stmt = wallet_datastore_next(cmd, cmd->ld->wallet, - stmt, &key, &data, - &generation)) { + + for (stmt = wallet_datastore_first(cmd, cmd->ld->wallet, key, + &k, &data, &generation); + stmt; + stmt = wallet_datastore_next(cmd, cmd->ld->wallet, + stmt, &k, &data, + &generation)) { + log_debug(cmd->ld->log, "Got %s", + datastore_key_fmt(tmpctx, k)); + + /* Don't list children, except implicitly */ + if (tal_count(k) > tal_count(key) + 1) { + log_debug(cmd->ld->log, "Too long"); + if (!prev_k || !datastore_key_startswith(k, prev_k)) { + prev_k = tal_dup_arr(cmd, const char *, k, + tal_count(key) + 1, 0); + json_object_start(response, NULL); + json_add_datastore(response, prev_k, NULL, 0); + json_object_end(response); + } + } else if (key && !datastore_key_startswith(k, key)) { + log_debug(cmd->ld->log, "Not interested"); + tal_free(stmt); + break; + } else { + log_debug(cmd->ld->log, "Printing"); json_object_start(response, NULL); - json_add_datastore(response, key, data, generation); + json_add_datastore(response, k, data, generation); json_object_end(response); } } @@ -170,28 +283,31 @@ static struct command_result *json_deldatastore(struct command *cmd, const jsmntok_t *params) { struct json_stream *response; - const char *key; - u8 *data; + const char **key, **k; + const u8 *data; u64 *generation; u64 actual_gen; + struct db_stmt *stmt; if (!param(cmd, buffer, params, - p_req("key", param_string, &key), + p_req("key", param_list_or_string, &key), p_opt("generation", param_u64, &generation), NULL)) return command_param_failed(); - if (generation) { - data = wallet_datastore_fetch(cmd, cmd->ld->wallet, key, - &actual_gen); - if (data && actual_gen != *generation) - return command_fail(cmd, DATASTORE_DEL_WRONG_GENERATION, - "generation is different"); - } - data = wallet_datastore_remove(cmd, cmd->ld->wallet, key, &actual_gen); - if (!data) + stmt = wallet_datastore_first(cmd, cmd->ld->wallet, key, + &k, &data, &actual_gen); + tal_free(stmt); + + if (!stmt || !datastore_key_eq(k, key)) { return command_fail(cmd, DATASTORE_DEL_DOES_NOT_EXIST, "Key does not exist"); + } + if (generation && actual_gen != *generation) + return command_fail(cmd, DATASTORE_DEL_WRONG_GENERATION, + "generation is different"); + + wallet_datastore_remove(cmd->ld->wallet, key); response = json_stream_success(cmd); json_add_datastore(response, key, data, actual_gen); diff --git a/tests/test_misc.py b/tests/test_misc.py index 64bf767c63d7..65d810ad0070 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -2635,7 +2635,7 @@ def test_datastore(node_factory): # Add entries. somedata = b'somedata'.hex() - somedata_expect = {'key': 'somekey', + somedata_expect = {'key': ['somekey'], 'generation': 0, 'hex': somedata, 'string': 'somedata'} @@ -2673,7 +2673,7 @@ def test_datastore(node_factory): l1.rpc.datastore(key='otherkey', hex=somedata, mode="must-append") otherdata = b'otherdata'.hex() - otherdata_expect = {'key': 'otherkey', + otherdata_expect = {'key': ['otherkey'], 'generation': 0, 'hex': otherdata, 'string': 'otherdata'} @@ -2683,10 +2683,8 @@ def test_datastore(node_factory): assert l1.rpc.listdatastore('otherkey') == {'datastore': [otherdata_expect]} assert l1.rpc.listdatastore('badkey') == {'datastore': []} - ds = l1.rpc.listdatastore() - # Order is undefined! - assert (ds == {'datastore': [somedata_expect, otherdata_expect]} - or ds == {'datastore': [otherdata_expect, somedata_expect]}) + # Order is sorted! + assert l1.rpc.listdatastore() == {'datastore': [otherdata_expect, somedata_expect]} assert l1.rpc.deldatastore('somekey') == somedata_expect assert l1.rpc.listdatastore() == {'datastore': [otherdata_expect]} @@ -2696,7 +2694,7 @@ def test_datastore(node_factory): assert l1.rpc.listdatastore() == {'datastore': [otherdata_expect]} # if it's not a string, won't print - badstring_expect = {'key': 'badstring', + badstring_expect = {'key': ['badstring'], 'generation': 0, 'hex': '00'} assert l1.rpc.datastore(key='badstring', hex='00') == badstring_expect @@ -2731,3 +2729,65 @@ def test_datastore(node_factory): generation=otherdata_expect['generation']) == otherdata_expect) assert l1.rpc.listdatastore() == {'datastore': []} + + +def test_datastore_keylist(node_factory): + l1 = node_factory.get_node() + + # Starts empty + assert l1.rpc.listdatastore() == {'datastore': []} + assert l1.rpc.listdatastore(['a']) == {'datastore': []} + assert l1.rpc.listdatastore(['a', 'b']) == {'datastore': []} + + # Cannot add child to existing! + l1.rpc.datastore(key='a', string='aval') + with pytest.raises(RpcError, match=r'1206.*Parent key \[a\] exists'): + l1.rpc.datastore(key=['a', 'b'], string='abval', + mode='create-or-replace') + # Listing subkey gives DNE. + assert l1.rpc.listdatastore(['a', 'b']) == {'datastore': []} + l1.rpc.deldatastore(key=['a']) + + # Create child key. + l1.rpc.datastore(key=['a', 'b'], string='abval') + assert l1.rpc.listdatastore() == {'datastore': [{'key': ['a']}]} + assert l1.rpc.listdatastore(key=['a']) == {'datastore': [{'key': ['a', 'b'], + 'generation': 0, + 'string': 'abval', + 'hex': b'abval'.hex()}]} + + # Cannot create key over that + with pytest.raises(RpcError, match='has children'): + l1.rpc.datastore(key='a', string='aval', mode='create-or-replace') + + # Can create another key. + l1.rpc.datastore(key=['a', 'b2'], string='ab2val') + assert l1.rpc.listdatastore() == {'datastore': [{'key': ['a']}]} + assert l1.rpc.listdatastore(key=['a']) == {'datastore': [{'key': ['a', 'b'], + 'string': 'abval', + 'generation': 0, + 'hex': b'abval'.hex()}, + {'key': ['a', 'b2'], + 'string': 'ab2val', + 'generation': 0, + 'hex': b'ab2val'.hex()}]} + + # Can create subkey. + l1.rpc.datastore(key=['a', 'b3', 'c'], string='ab2val') + assert l1.rpc.listdatastore() == {'datastore': [{'key': ['a']}]} + assert l1.rpc.listdatastore(key=['a']) == {'datastore': [{'key': ['a', 'b'], + 'string': 'abval', + 'generation': 0, + 'hex': b'abval'.hex()}, + {'key': ['a', 'b2'], + 'string': 'ab2val', + 'generation': 0, + 'hex': b'ab2val'.hex()}, + {'key': ['a', 'b3']}]} + + # Can update subkey + l1.rpc.datastore(key=['a', 'b3', 'c'], string='2', mode='must-append') + assert l1.rpc.listdatastore(key=['a', 'b3', 'c']) == {'datastore': [{'key': ['a', 'b3', 'c'], + 'string': 'ab2val2', + 'generation': 1, + 'hex': b'ab2val2'.hex()}]} diff --git a/wallet/db.c b/wallet/db.c index 9cbbe424acf1..1d70f80f166e 100644 --- a/wallet/db.c +++ b/wallet/db.c @@ -750,7 +750,7 @@ static struct migration dbmigrations[] = { fillin_missing_channel_blockheights}, {SQL("ALTER TABLE outputs ADD csv_lock INTEGER DEFAULT 1;"), NULL}, {SQL("CREATE TABLE datastore (" - " key TEXT," + " key BLOB," " data BLOB," " generation BIGINT," " PRIMARY KEY (key)" diff --git a/wallet/db_postgres_sqlgen.c b/wallet/db_postgres_sqlgen.c index 4b58761780cd..19afa7edf94a 100644 --- a/wallet/db_postgres_sqlgen.c +++ b/wallet/db_postgres_sqlgen.c @@ -1023,8 +1023,8 @@ struct db_query db_postgres_queries[] = { .readonly = false, }, { - .name = "CREATE TABLE datastore ( key TEXT, data BLOB, generation BIGINT, PRIMARY KEY (key));", - .query = "CREATE TABLE datastore ( key TEXT, data BYTEA, generation BIGINT, PRIMARY KEY (key));", + .name = "CREATE TABLE datastore ( key BLOB, data BLOB, generation BIGINT, PRIMARY KEY (key));", + .query = "CREATE TABLE datastore ( key BYTEA, data BYTEA, generation BIGINT, PRIMARY KEY (key));", .placeholders = 0, .readonly = false, }, @@ -2025,14 +2025,14 @@ struct db_query db_postgres_queries[] = { .readonly = false, }, { - .name = "SELECT data, generation FROM datastore WHERE key = ?;", - .query = "SELECT data, generation FROM datastore WHERE key = $1;", + .name = "SELECT key, data, generation FROM datastore WHERE key >= ? ORDER BY key;", + .query = "SELECT key, data, generation FROM datastore WHERE key >= $1 ORDER BY key;", .placeholders = 1, .readonly = true, }, { - .name = "SELECT key, data, generation FROM datastore;", - .query = "SELECT key, data, generation FROM datastore;", + .name = "SELECT key, data, generation FROM datastore ORDER BY key;", + .query = "SELECT key, data, generation FROM datastore ORDER BY key;", .placeholders = 0, .readonly = true, }, @@ -2068,4 +2068,4 @@ struct db_query db_postgres_queries[] = { #endif /* LIGHTNINGD_WALLET_GEN_DB_POSTGRES */ -// SHA256STAMP:4c44b6b6e6409c3f08a1e3b5ecd637f0cceaf309b6772e3685c1e4280d4c8b08 +// SHA256STAMP:7e6598f7870752333509a79eec6fd0484db91230a4152517cb959d6ca61cca75 diff --git a/wallet/db_sqlite3_sqlgen.c b/wallet/db_sqlite3_sqlgen.c index 9ca9a8347111..e1c6fb06c2ae 100644 --- a/wallet/db_sqlite3_sqlgen.c +++ b/wallet/db_sqlite3_sqlgen.c @@ -1023,8 +1023,8 @@ struct db_query db_sqlite3_queries[] = { .readonly = false, }, { - .name = "CREATE TABLE datastore ( key TEXT, data BLOB, generation BIGINT, PRIMARY KEY (key));", - .query = "CREATE TABLE datastore ( key TEXT, data BLOB, generation INTEGER, PRIMARY KEY (key));", + .name = "CREATE TABLE datastore ( key BLOB, data BLOB, generation BIGINT, PRIMARY KEY (key));", + .query = "CREATE TABLE datastore ( key BLOB, data BLOB, generation INTEGER, PRIMARY KEY (key));", .placeholders = 0, .readonly = false, }, @@ -2025,14 +2025,14 @@ struct db_query db_sqlite3_queries[] = { .readonly = false, }, { - .name = "SELECT data, generation FROM datastore WHERE key = ?;", - .query = "SELECT data, generation FROM datastore WHERE key = ?;", + .name = "SELECT key, data, generation FROM datastore WHERE key >= ? ORDER BY key;", + .query = "SELECT key, data, generation FROM datastore WHERE key >= ? ORDER BY key;", .placeholders = 1, .readonly = true, }, { - .name = "SELECT key, data, generation FROM datastore;", - .query = "SELECT key, data, generation FROM datastore;", + .name = "SELECT key, data, generation FROM datastore ORDER BY key;", + .query = "SELECT key, data, generation FROM datastore ORDER BY key;", .placeholders = 0, .readonly = true, }, @@ -2068,4 +2068,4 @@ struct db_query db_sqlite3_queries[] = { #endif /* LIGHTNINGD_WALLET_GEN_DB_SQLITE3 */ -// SHA256STAMP:4c44b6b6e6409c3f08a1e3b5ecd637f0cceaf309b6772e3685c1e4280d4c8b08 +// SHA256STAMP:7e6598f7870752333509a79eec6fd0484db91230a4152517cb959d6ca61cca75 diff --git a/wallet/statements_gettextgen.po b/wallet/statements_gettextgen.po index 81d9377358c4..f38138c8e246 100644 --- a/wallet/statements_gettextgen.po +++ b/wallet/statements_gettextgen.po @@ -675,7 +675,7 @@ msgid "ALTER TABLE outputs ADD csv_lock INTEGER DEFAULT 1;" msgstr "" #: wallet/db.c:752 -msgid "CREATE TABLE datastore ( key TEXT, data BLOB, generation BIGINT, PRIMARY KEY (key));" +msgid "CREATE TABLE datastore ( key BLOB, data BLOB, generation BIGINT, PRIMARY KEY (key));" msgstr "" #: wallet/db.c:985 @@ -826,528 +826,528 @@ msgstr "" msgid "SELECT state, payment_key, payment_hash, label, msatoshi, expiry_time, pay_index, msatoshi_received, paid_timestamp, bolt11, description, features, local_offer_id FROM invoices WHERE id = ?;" msgstr "" -#: wallet/wallet.c:66 +#: wallet/wallet.c:67 msgid "SELECT txid, outnum FROM utxoset WHERE spendheight is NULL" msgstr "" -#: wallet/wallet.c:107 wallet/wallet.c:605 +#: wallet/wallet.c:108 wallet/wallet.c:606 msgid "SELECT * from outputs WHERE prev_out_tx=? AND prev_out_index=?" msgstr "" -#: wallet/wallet.c:121 +#: wallet/wallet.c:122 msgid "INSERT INTO outputs ( prev_out_tx, prev_out_index, value, type, status, keyindex, channel_id, peer_id, commitment_point, option_anchor_outputs, confirmation_height, spend_height, scriptpubkey) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);" msgstr "" -#: wallet/wallet.c:239 +#: wallet/wallet.c:240 msgid "UPDATE outputs SET status=? WHERE status=? AND prev_out_tx=? AND prev_out_index=?" msgstr "" -#: wallet/wallet.c:247 +#: wallet/wallet.c:248 msgid "UPDATE outputs SET status=? WHERE prev_out_tx=? AND prev_out_index=?" msgstr "" -#: wallet/wallet.c:266 +#: wallet/wallet.c:267 msgid "SELECT prev_out_tx, prev_out_index, value, type, status, keyindex, channel_id, peer_id, commitment_point, option_anchor_outputs, confirmation_height, spend_height, scriptpubkey , reserved_til , csv_lock FROM outputs" msgstr "" -#: wallet/wallet.c:284 +#: wallet/wallet.c:285 msgid "SELECT prev_out_tx, prev_out_index, value, type, status, keyindex, channel_id, peer_id, commitment_point, option_anchor_outputs, confirmation_height, spend_height, scriptpubkey , reserved_til , csv_lock FROM outputs WHERE status= ? " msgstr "" -#: wallet/wallet.c:323 +#: wallet/wallet.c:324 msgid "SELECT prev_out_tx, prev_out_index, value, type, status, keyindex, channel_id, peer_id, commitment_point, option_anchor_outputs, confirmation_height, spend_height, scriptpubkey, reserved_til, csv_lock FROM outputs WHERE channel_id IS NOT NULL AND confirmation_height IS NULL" msgstr "" -#: wallet/wallet.c:361 +#: wallet/wallet.c:362 msgid "SELECT prev_out_tx, prev_out_index, value, type, status, keyindex, channel_id, peer_id, commitment_point, option_anchor_outputs, confirmation_height, spend_height, scriptpubkey, reserved_til, csv_lock FROM outputs WHERE prev_out_tx = ? AND prev_out_index = ?" msgstr "" -#: wallet/wallet.c:454 +#: wallet/wallet.c:455 msgid "UPDATE outputs SET status=?, reserved_til=? WHERE prev_out_tx=? AND prev_out_index=?" msgstr "" -#: wallet/wallet.c:551 +#: wallet/wallet.c:552 msgid "SELECT prev_out_tx, prev_out_index, value, type, status, keyindex, channel_id, peer_id, commitment_point, option_anchor_outputs, confirmation_height, spend_height, scriptpubkey , reserved_til, csv_lock FROM outputs WHERE status = ? OR (status = ? AND reserved_til <= ?)ORDER BY RANDOM();" msgstr "" -#: wallet/wallet.c:619 +#: wallet/wallet.c:620 msgid "INSERT INTO outputs ( prev_out_tx, prev_out_index, value, type, status, keyindex, channel_id, peer_id, commitment_point, option_anchor_outputs, confirmation_height, spend_height, scriptpubkey, csv_lock) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);" msgstr "" -#: wallet/wallet.c:726 +#: wallet/wallet.c:727 msgid "INSERT INTO shachains (min_index, num_valid) VALUES (?, 0);" msgstr "" -#: wallet/wallet.c:770 +#: wallet/wallet.c:771 msgid "UPDATE shachains SET num_valid=?, min_index=? WHERE id=?" msgstr "" -#: wallet/wallet.c:777 +#: wallet/wallet.c:778 msgid "UPDATE shachain_known SET idx=?, hash=? WHERE shachain_id=? AND pos=?" msgstr "" -#: wallet/wallet.c:789 +#: wallet/wallet.c:790 msgid "INSERT INTO shachain_known (shachain_id, pos, idx, hash) VALUES (?, ?, ?, ?);" msgstr "" -#: wallet/wallet.c:811 +#: wallet/wallet.c:812 msgid "SELECT min_index, num_valid FROM shachains WHERE id=?" msgstr "" -#: wallet/wallet.c:826 +#: wallet/wallet.c:827 msgid "SELECT idx, hash, pos FROM shachain_known WHERE shachain_id=?" msgstr "" -#: wallet/wallet.c:849 +#: wallet/wallet.c:850 msgid "SELECT id, node_id, address FROM peers WHERE id=?;" msgstr "" -#: wallet/wallet.c:882 +#: wallet/wallet.c:883 msgid "SELECT signature FROM htlc_sigs WHERE channelid = ?" msgstr "" -#: wallet/wallet.c:916 +#: wallet/wallet.c:917 msgid "SELECT remote_ann_node_sig, remote_ann_bitcoin_sig FROM channels WHERE id = ?" msgstr "" -#: wallet/wallet.c:960 +#: wallet/wallet.c:961 msgid "SELECT hstate, feerate_per_kw FROM channel_feerates WHERE channel_id = ?" msgstr "" -#: wallet/wallet.c:996 +#: wallet/wallet.c:997 msgid "SELECT hstate, blockheight FROM channel_blockheights WHERE channel_id = ?" msgstr "" -#: wallet/wallet.c:1029 +#: wallet/wallet.c:1030 msgid "INSERT INTO channel_funding_inflights ( channel_id, funding_tx_id, funding_tx_outnum, funding_feerate, funding_satoshi, our_funding_satoshi, funding_psbt, last_tx, last_sig, lease_commit_sig, lease_chan_max_msat, lease_chan_max_ppt, lease_expiry, lease_blockheight_start) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);" msgstr "" -#: wallet/wallet.c:1085 +#: wallet/wallet.c:1086 msgid "UPDATE channel_funding_inflights SET funding_psbt=?, funding_tx_remote_sigs_received=? WHERE channel_id=? AND funding_tx_id=? AND funding_tx_outnum=?" msgstr "" -#: wallet/wallet.c:1108 +#: wallet/wallet.c:1109 msgid "DELETE FROM channel_funding_inflights WHERE channel_id = ?" msgstr "" -#: wallet/wallet.c:1178 +#: wallet/wallet.c:1179 msgid "SELECT funding_tx_id, funding_tx_outnum, funding_feerate, funding_satoshi, our_funding_satoshi, funding_psbt, last_tx, last_sig, funding_tx_remote_sigs_received, lease_expiry, lease_commit_sig, lease_chan_max_msat, lease_chan_max_ppt, lease_blockheight_start FROM channel_funding_inflights WHERE channel_id = ? ORDER BY funding_feerate" msgstr "" -#: wallet/wallet.c:1446 +#: wallet/wallet.c:1447 msgid "SELECT id FROM channels ORDER BY id DESC LIMIT 1;" msgstr "" -#: wallet/wallet.c:1463 +#: wallet/wallet.c:1464 msgid "SELECT id, peer_id, short_channel_id, full_channel_id, channel_config_local, channel_config_remote, state, funder, channel_flags, minimum_depth, next_index_local, next_index_remote, next_htlc_id, funding_tx_id, funding_tx_outnum, funding_satoshi, our_funding_satoshi, funding_locked_remote, push_msatoshi, msatoshi_local, fundingkey_remote, revocation_basepoint_remote, payment_basepoint_remote, htlc_basepoint_remote, delayed_payment_basepoint_remote, per_commit_remote, old_per_commit_remote, local_feerate_per_kw, remote_feerate_per_kw, shachain_remote_id, shutdown_scriptpubkey_remote, shutdown_keyidx_local, last_sent_commit_state, last_sent_commit_id, last_tx, last_sig, last_was_revoke, first_blocknum, min_possible_feerate, max_possible_feerate, msatoshi_to_us_min, msatoshi_to_us_max, future_per_commitment_point, last_sent_commit, feerate_base, feerate_ppm, remote_upfront_shutdown_script, local_static_remotekey_start, remote_static_remotekey_start, option_anchor_outputs, shutdown_scriptpubkey_local, closer, state_change_reason, revocation_basepoint_local, payment_basepoint_local, htlc_basepoint_local, delayed_payment_basepoint_local, funding_pubkey_local, shutdown_wrong_txid, shutdown_wrong_outnum, lease_expiry, lease_commit_sig, lease_chan_max_msat, lease_chan_max_ppt FROM channels WHERE state != ?;" msgstr "" -#: wallet/wallet.c:1575 +#: wallet/wallet.c:1576 msgid "UPDATE channels SET in_payments_offered = COALESCE(in_payments_offered, 0) + 1 , in_msatoshi_offered = COALESCE(in_msatoshi_offered, 0) + ? WHERE id = ?;" msgstr "" -#: wallet/wallet.c:1581 +#: wallet/wallet.c:1582 msgid "UPDATE channels SET in_payments_fulfilled = COALESCE(in_payments_fulfilled, 0) + 1 , in_msatoshi_fulfilled = COALESCE(in_msatoshi_fulfilled, 0) + ? WHERE id = ?;" msgstr "" -#: wallet/wallet.c:1587 +#: wallet/wallet.c:1588 msgid "UPDATE channels SET out_payments_offered = COALESCE(out_payments_offered, 0) + 1 , out_msatoshi_offered = COALESCE(out_msatoshi_offered, 0) + ? WHERE id = ?;" msgstr "" -#: wallet/wallet.c:1593 +#: wallet/wallet.c:1594 msgid "UPDATE channels SET out_payments_fulfilled = COALESCE(out_payments_fulfilled, 0) + 1 , out_msatoshi_fulfilled = COALESCE(out_msatoshi_fulfilled, 0) + ? WHERE id = ?;" msgstr "" -#: wallet/wallet.c:1638 +#: wallet/wallet.c:1639 msgid "SELECT in_payments_offered, in_payments_fulfilled, in_msatoshi_offered, in_msatoshi_fulfilled, out_payments_offered, out_payments_fulfilled, out_msatoshi_offered, out_msatoshi_fulfilled FROM channels WHERE id = ?" msgstr "" -#: wallet/wallet.c:1667 +#: wallet/wallet.c:1668 msgid "SELECT MIN(height), MAX(height) FROM blocks;" msgstr "" -#: wallet/wallet.c:1689 +#: wallet/wallet.c:1690 msgid "INSERT INTO channel_configs DEFAULT VALUES;" msgstr "" -#: wallet/wallet.c:1701 +#: wallet/wallet.c:1702 msgid "UPDATE channel_configs SET dust_limit_satoshis=?, max_htlc_value_in_flight_msat=?, channel_reserve_satoshis=?, htlc_minimum_msat=?, to_self_delay=?, max_accepted_htlcs=? WHERE id=?;" msgstr "" -#: wallet/wallet.c:1725 +#: wallet/wallet.c:1726 msgid "SELECT id, dust_limit_satoshis, max_htlc_value_in_flight_msat, channel_reserve_satoshis, htlc_minimum_msat, to_self_delay, max_accepted_htlcs FROM channel_configs WHERE id= ? ;" msgstr "" -#: wallet/wallet.c:1759 +#: wallet/wallet.c:1760 msgid "UPDATE channels SET remote_ann_node_sig=?, remote_ann_bitcoin_sig=? WHERE id=?" msgstr "" -#: wallet/wallet.c:1778 +#: wallet/wallet.c:1779 msgid "UPDATE channels SET shachain_remote_id=?, short_channel_id=?, full_channel_id=?, state=?, funder=?, channel_flags=?, minimum_depth=?, next_index_local=?, next_index_remote=?, next_htlc_id=?, funding_tx_id=?, funding_tx_outnum=?, funding_satoshi=?, our_funding_satoshi=?, funding_locked_remote=?, push_msatoshi=?, msatoshi_local=?, shutdown_scriptpubkey_remote=?, shutdown_keyidx_local=?, channel_config_local=?, last_tx=?, last_sig=?, last_was_revoke=?, min_possible_feerate=?, max_possible_feerate=?, msatoshi_to_us_min=?, msatoshi_to_us_max=?, feerate_base=?, feerate_ppm=?, remote_upfront_shutdown_script=?, local_static_remotekey_start=?, remote_static_remotekey_start=?, option_anchor_outputs=?, shutdown_scriptpubkey_local=?, closer=?, state_change_reason=?, shutdown_wrong_txid=?, shutdown_wrong_outnum=?, lease_expiry=?, lease_commit_sig=?, lease_chan_max_msat=?, lease_chan_max_ppt=? WHERE id=?" msgstr "" -#: wallet/wallet.c:1887 +#: wallet/wallet.c:1888 msgid "UPDATE channels SET fundingkey_remote=?, revocation_basepoint_remote=?, payment_basepoint_remote=?, htlc_basepoint_remote=?, delayed_payment_basepoint_remote=?, per_commit_remote=?, old_per_commit_remote=?, channel_config_remote=?, future_per_commitment_point=? WHERE id=?" msgstr "" -#: wallet/wallet.c:1914 +#: wallet/wallet.c:1915 msgid "DELETE FROM channel_feerates WHERE channel_id=?" msgstr "" -#: wallet/wallet.c:1924 +#: wallet/wallet.c:1925 msgid "INSERT INTO channel_feerates VALUES(?, ?, ?)" msgstr "" -#: wallet/wallet.c:1933 +#: wallet/wallet.c:1934 msgid "DELETE FROM channel_blockheights WHERE channel_id=?" msgstr "" -#: wallet/wallet.c:1943 +#: wallet/wallet.c:1944 msgid "INSERT INTO channel_blockheights VALUES(?, ?, ?)" msgstr "" -#: wallet/wallet.c:1960 +#: wallet/wallet.c:1961 msgid "UPDATE channels SET last_sent_commit=? WHERE id=?" msgstr "" -#: wallet/wallet.c:1983 +#: wallet/wallet.c:1984 msgid "INSERT INTO channel_state_changes ( channel_id, timestamp, old_state, new_state, cause, message) VALUES (?, ?, ?, ?, ?, ?);" msgstr "" -#: wallet/wallet.c:2011 +#: wallet/wallet.c:2012 msgid "SELECT timestamp, old_state, new_state, cause, message FROM channel_state_changes WHERE channel_id = ? ORDER BY timestamp ASC;" msgstr "" -#: wallet/wallet.c:2040 +#: wallet/wallet.c:2041 msgid "SELECT id FROM peers WHERE node_id = ?" msgstr "" -#: wallet/wallet.c:2052 +#: wallet/wallet.c:2053 msgid "UPDATE peers SET address = ? WHERE id = ?" msgstr "" -#: wallet/wallet.c:2061 +#: wallet/wallet.c:2062 msgid "INSERT INTO peers (node_id, address) VALUES (?, ?);" msgstr "" -#: wallet/wallet.c:2082 +#: wallet/wallet.c:2083 msgid "INSERT INTO channels ( peer_id, first_blocknum, id, revocation_basepoint_local, payment_basepoint_local, htlc_basepoint_local, delayed_payment_basepoint_local, funding_pubkey_local) VALUES (?, ?, ?, ?, ?, ?, ?, ?);" msgstr "" -#: wallet/wallet.c:2123 +#: wallet/wallet.c:2124 msgid "DELETE FROM channel_htlcs WHERE channel_id=?" msgstr "" -#: wallet/wallet.c:2129 +#: wallet/wallet.c:2130 msgid "DELETE FROM htlc_sigs WHERE channelid=?" msgstr "" -#: wallet/wallet.c:2135 +#: wallet/wallet.c:2136 msgid "DELETE FROM channeltxs WHERE channel_id=?" msgstr "" -#: wallet/wallet.c:2142 +#: wallet/wallet.c:2143 msgid "DELETE FROM channel_funding_inflights WHERE channel_id=?" msgstr "" -#: wallet/wallet.c:2148 +#: wallet/wallet.c:2149 msgid "DELETE FROM shachains WHERE id IN ( SELECT shachain_remote_id FROM channels WHERE channels.id=?)" msgstr "" -#: wallet/wallet.c:2158 +#: wallet/wallet.c:2159 msgid "UPDATE channels SET state=?, peer_id=? WHERE channels.id=?" msgstr "" -#: wallet/wallet.c:2172 +#: wallet/wallet.c:2173 msgid "SELECT * FROM channels WHERE peer_id = ?;" msgstr "" -#: wallet/wallet.c:2180 +#: wallet/wallet.c:2181 msgid "DELETE FROM peers WHERE id=?" msgstr "" -#: wallet/wallet.c:2191 +#: wallet/wallet.c:2192 msgid "UPDATE outputs SET confirmation_height = ? WHERE prev_out_tx = ?" msgstr "" -#: wallet/wallet.c:2294 +#: wallet/wallet.c:2295 msgid "INSERT INTO channel_htlcs ( channel_id, channel_htlc_id, direction, msatoshi, cltv_expiry, payment_hash, payment_key, hstate, shared_secret, routing_onion, received_time) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);" msgstr "" -#: wallet/wallet.c:2347 +#: wallet/wallet.c:2348 msgid "INSERT INTO channel_htlcs ( channel_id, channel_htlc_id, direction, origin_htlc, msatoshi, cltv_expiry, payment_hash, payment_key, hstate, routing_onion, malformed_onion, partid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?);" msgstr "" -#: wallet/wallet.c:2408 +#: wallet/wallet.c:2409 msgid "UPDATE channel_htlcs SET hstate=?, payment_key=?, malformed_onion=?, failuremsg=?, localfailmsg=?, we_filled=? WHERE id=?" msgstr "" -#: wallet/wallet.c:2625 +#: wallet/wallet.c:2626 msgid "SELECT id, channel_htlc_id, msatoshi, cltv_expiry, hstate, payment_hash, payment_key, routing_onion, failuremsg, malformed_onion, origin_htlc, shared_secret, received_time, we_filled FROM channel_htlcs WHERE direction= ? AND channel_id= ? AND hstate != ?" msgstr "" -#: wallet/wallet.c:2672 +#: wallet/wallet.c:2673 msgid "SELECT id, channel_htlc_id, msatoshi, cltv_expiry, hstate, payment_hash, payment_key, routing_onion, failuremsg, malformed_onion, origin_htlc, shared_secret, received_time, partid, localfailmsg FROM channel_htlcs WHERE direction = ? AND channel_id = ? AND hstate != ?" msgstr "" -#: wallet/wallet.c:2803 +#: wallet/wallet.c:2804 msgid "SELECT channel_id, direction, cltv_expiry, channel_htlc_id, payment_hash FROM channel_htlcs WHERE channel_id = ?;" msgstr "" -#: wallet/wallet.c:2837 +#: wallet/wallet.c:2838 msgid "DELETE FROM channel_htlcs WHERE direction = ? AND origin_htlc = ? AND payment_hash = ? AND partid = ?;" msgstr "" -#: wallet/wallet.c:2890 +#: wallet/wallet.c:2891 msgid "SELECT status FROM payments WHERE payment_hash=? AND partid = ?;" msgstr "" -#: wallet/wallet.c:2908 +#: wallet/wallet.c:2909 msgid "INSERT INTO payments ( status, payment_hash, destination, msatoshi, timestamp, path_secrets, route_nodes, route_channels, msatoshi_sent, description, bolt11, total_msat, partid, local_offer_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);" msgstr "" -#: wallet/wallet.c:2997 +#: wallet/wallet.c:2998 msgid "DELETE FROM payments WHERE payment_hash = ? AND partid = ?" msgstr "" -#: wallet/wallet.c:3011 +#: wallet/wallet.c:3012 msgid "DELETE FROM payments WHERE payment_hash = ?" msgstr "" -#: wallet/wallet.c:3112 +#: wallet/wallet.c:3113 msgid "SELECT id, status, destination, msatoshi, payment_hash, timestamp, payment_preimage, path_secrets, route_nodes, route_channels, msatoshi_sent, description, bolt11, failonionreply, total_msat, partid, local_offer_id FROM payments WHERE payment_hash = ? AND partid = ?" msgstr "" -#: wallet/wallet.c:3162 +#: wallet/wallet.c:3163 msgid "UPDATE payments SET status=? WHERE payment_hash=? AND partid=?" msgstr "" -#: wallet/wallet.c:3172 +#: wallet/wallet.c:3173 msgid "UPDATE payments SET payment_preimage=? WHERE payment_hash=? AND partid=?" msgstr "" -#: wallet/wallet.c:3182 +#: wallet/wallet.c:3183 msgid "UPDATE payments SET path_secrets = NULL , route_nodes = NULL , route_channels = NULL WHERE payment_hash = ? AND partid = ?;" msgstr "" -#: wallet/wallet.c:3214 +#: wallet/wallet.c:3215 msgid "SELECT failonionreply, faildestperm, failindex, failcode, failnode, failchannel, failupdate, faildetail, faildirection FROM payments WHERE payment_hash=? AND partid=?;" msgstr "" -#: wallet/wallet.c:3281 +#: wallet/wallet.c:3282 msgid "UPDATE payments SET failonionreply=? , faildestperm=? , failindex=? , failcode=? , failnode=? , failchannel=? , failupdate=? , faildetail=? , faildirection=? WHERE payment_hash=? AND partid=?;" msgstr "" -#: wallet/wallet.c:3340 +#: wallet/wallet.c:3341 msgid "SELECT id, status, destination, msatoshi, payment_hash, timestamp, payment_preimage, path_secrets, route_nodes, route_channels, msatoshi_sent, description, bolt11, failonionreply, total_msat, partid, local_offer_id FROM payments WHERE payment_hash = ? ORDER BY id;" msgstr "" -#: wallet/wallet.c:3363 +#: wallet/wallet.c:3364 msgid "SELECT id, status, destination, msatoshi, payment_hash, timestamp, payment_preimage, path_secrets, route_nodes, route_channels, msatoshi_sent, description, bolt11, failonionreply, total_msat, partid, local_offer_id FROM payments ORDER BY id;" msgstr "" -#: wallet/wallet.c:3414 +#: wallet/wallet.c:3415 msgid "SELECT id, status, destination, msatoshi, payment_hash, timestamp, payment_preimage, path_secrets, route_nodes, route_channels, msatoshi_sent, description, bolt11, failonionreply, total_msat, partid, local_offer_id FROM payments WHERE local_offer_id = ?;" msgstr "" -#: wallet/wallet.c:3459 +#: wallet/wallet.c:3460 msgid "DELETE FROM htlc_sigs WHERE channelid = ?" msgstr "" -#: wallet/wallet.c:3466 +#: wallet/wallet.c:3467 msgid "INSERT INTO htlc_sigs (channelid, signature) VALUES (?, ?)" msgstr "" -#: wallet/wallet.c:3478 +#: wallet/wallet.c:3479 msgid "SELECT blobval FROM vars WHERE name='genesis_hash'" msgstr "" -#: wallet/wallet.c:3502 +#: wallet/wallet.c:3503 msgid "INSERT INTO vars (name, blobval) VALUES ('genesis_hash', ?);" msgstr "" -#: wallet/wallet.c:3520 +#: wallet/wallet.c:3521 msgid "SELECT txid, outnum FROM utxoset WHERE spendheight < ?" msgstr "" -#: wallet/wallet.c:3532 +#: wallet/wallet.c:3533 msgid "DELETE FROM utxoset WHERE spendheight < ?" msgstr "" -#: wallet/wallet.c:3540 wallet/wallet.c:3654 +#: wallet/wallet.c:3541 wallet/wallet.c:3655 msgid "INSERT INTO blocks (height, hash, prev_hash) VALUES (?, ?, ?);" msgstr "" -#: wallet/wallet.c:3559 +#: wallet/wallet.c:3560 msgid "DELETE FROM blocks WHERE hash = ?" msgstr "" -#: wallet/wallet.c:3565 +#: wallet/wallet.c:3566 msgid "SELECT * FROM blocks WHERE height >= ?;" msgstr "" -#: wallet/wallet.c:3574 +#: wallet/wallet.c:3575 msgid "DELETE FROM blocks WHERE height > ?" msgstr "" -#: wallet/wallet.c:3586 +#: wallet/wallet.c:3587 msgid "UPDATE outputs SET spend_height = ?, status = ? WHERE prev_out_tx = ? AND prev_out_index = ?" msgstr "" -#: wallet/wallet.c:3604 +#: wallet/wallet.c:3605 msgid "UPDATE utxoset SET spendheight = ? WHERE txid = ? AND outnum = ?" msgstr "" -#: wallet/wallet.c:3627 wallet/wallet.c:3665 +#: wallet/wallet.c:3628 wallet/wallet.c:3666 msgid "INSERT INTO utxoset ( txid, outnum, blockheight, spendheight, txindex, scriptpubkey, satoshis) VALUES(?, ?, ?, ?, ?, ?, ?);" msgstr "" -#: wallet/wallet.c:3691 +#: wallet/wallet.c:3692 msgid "SELECT height FROM blocks WHERE height = ?" msgstr "" -#: wallet/wallet.c:3704 +#: wallet/wallet.c:3705 msgid "SELECT txid, spendheight, scriptpubkey, satoshis FROM utxoset WHERE blockheight = ? AND txindex = ? AND outnum = ? AND spendheight IS NULL" msgstr "" -#: wallet/wallet.c:3768 +#: wallet/wallet.c:3769 msgid "SELECT blockheight, txindex, outnum FROM utxoset WHERE spendheight = ?" msgstr "" -#: wallet/wallet.c:3785 +#: wallet/wallet.c:3786 msgid "SELECT blockheight, txindex, outnum FROM utxoset WHERE blockheight = ?" msgstr "" -#: wallet/wallet.c:3802 wallet/wallet.c:3962 +#: wallet/wallet.c:3803 wallet/wallet.c:3963 msgid "SELECT blockheight FROM transactions WHERE id=?" msgstr "" -#: wallet/wallet.c:3812 +#: wallet/wallet.c:3813 msgid "INSERT INTO transactions ( id, blockheight, txindex, rawtx) VALUES (?, ?, ?, ?);" msgstr "" -#: wallet/wallet.c:3833 +#: wallet/wallet.c:3834 msgid "UPDATE transactions SET blockheight = ?, txindex = ? WHERE id = ?" msgstr "" -#: wallet/wallet.c:3850 +#: wallet/wallet.c:3851 msgid "INSERT INTO transaction_annotations (txid, idx, location, type, channel) VALUES (?, ?, ?, ?, ?) ON CONFLICT(txid,idx) DO NOTHING;" msgstr "" -#: wallet/wallet.c:3882 +#: wallet/wallet.c:3883 msgid "SELECT type, channel_id FROM transactions WHERE id=?" msgstr "" -#: wallet/wallet.c:3898 +#: wallet/wallet.c:3899 msgid "UPDATE transactions SET type = ?, channel_id = ? WHERE id = ?" msgstr "" -#: wallet/wallet.c:3917 +#: wallet/wallet.c:3918 msgid "SELECT type FROM transactions WHERE id=?" msgstr "" -#: wallet/wallet.c:3940 +#: wallet/wallet.c:3941 msgid "SELECT rawtx FROM transactions WHERE id=?" msgstr "" -#: wallet/wallet.c:3986 +#: wallet/wallet.c:3987 msgid "SELECT blockheight, txindex FROM transactions WHERE id=?" msgstr "" -#: wallet/wallet.c:4014 +#: wallet/wallet.c:4015 msgid "SELECT id FROM transactions WHERE blockheight=?" msgstr "" -#: wallet/wallet.c:4033 +#: wallet/wallet.c:4034 msgid "INSERT INTO channeltxs ( channel_id, type, transaction_id, input_num, blockheight) VALUES (?, ?, ?, ?, ?);" msgstr "" -#: wallet/wallet.c:4057 +#: wallet/wallet.c:4058 msgid "SELECT DISTINCT(channel_id) FROM channeltxs WHERE type = ?;" msgstr "" -#: wallet/wallet.c:4078 +#: wallet/wallet.c:4079 msgid "SELECT c.type, c.blockheight, t.rawtx, c.input_num, c.blockheight - t.blockheight + 1 AS depth, t.id as txid FROM channeltxs c JOIN transactions t ON t.id = c.transaction_id WHERE c.channel_id = ? ORDER BY c.id ASC;" msgstr "" -#: wallet/wallet.c:4123 +#: wallet/wallet.c:4124 msgid "UPDATE forwarded_payments SET in_msatoshi=?, out_msatoshi=?, state=?, resolved_time=?, failcode=? WHERE in_htlc_id=?" msgstr "" -#: wallet/wallet.c:4181 +#: wallet/wallet.c:4182 msgid "INSERT INTO forwarded_payments ( in_htlc_id, out_htlc_id, in_channel_scid, out_channel_scid, in_msatoshi, out_msatoshi, state, received_time, resolved_time, failcode) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);" msgstr "" -#: wallet/wallet.c:4240 +#: wallet/wallet.c:4241 msgid "SELECT CAST(COALESCE(SUM(in_msatoshi - out_msatoshi), 0) AS BIGINT)FROM forwarded_payments WHERE state = ?;" msgstr "" -#: wallet/wallet.c:4289 +#: wallet/wallet.c:4290 msgid "SELECT f.state, in_msatoshi, out_msatoshi, hin.payment_hash as payment_hash, in_channel_scid, out_channel_scid, f.received_time, f.resolved_time, f.failcode FROM forwarded_payments f LEFT JOIN channel_htlcs hin ON (f.in_htlc_id = hin.id) WHERE (1 = ? OR f.state = ?) AND (1 = ? OR f.in_channel_scid = ?) AND (1 = ? OR f.out_channel_scid = ?)" msgstr "" -#: wallet/wallet.c:4411 +#: wallet/wallet.c:4412 msgid "SELECT t.id, t.rawtx, t.blockheight, t.txindex, t.type as txtype, c2.short_channel_id as txchan, a.location, a.idx as ann_idx, a.type as annotation_type, c.short_channel_id FROM transactions t LEFT JOIN transaction_annotations a ON (a.txid = t.id) LEFT JOIN channels c ON (a.channel = c.id) LEFT JOIN channels c2 ON (t.channel_id = c2.id) ORDER BY t.blockheight, t.txindex ASC" msgstr "" -#: wallet/wallet.c:4505 +#: wallet/wallet.c:4506 msgid "INSERT INTO penalty_bases ( channel_id, commitnum, txid, outnum, amount) VALUES (?, ?, ?, ?, ?);" msgstr "" -#: wallet/wallet.c:4530 +#: wallet/wallet.c:4531 msgid "SELECT commitnum, txid, outnum, amount FROM penalty_bases WHERE channel_id = ?" msgstr "" -#: wallet/wallet.c:4554 +#: wallet/wallet.c:4555 msgid "DELETE FROM penalty_bases WHERE channel_id = ? AND commitnum = ?" msgstr "" -#: wallet/wallet.c:4572 +#: wallet/wallet.c:4573 msgid "SELECT 1 FROM offers WHERE offer_id = ?;" msgstr "" -#: wallet/wallet.c:4585 +#: wallet/wallet.c:4586 msgid "INSERT INTO offers ( offer_id, bolt12, label, status) VALUES (?, ?, ?, ?);" msgstr "" -#: wallet/wallet.c:4612 +#: wallet/wallet.c:4613 msgid "SELECT bolt12, label, status FROM offers WHERE offer_id = ?;" msgstr "" -#: wallet/wallet.c:4640 +#: wallet/wallet.c:4641 msgid "SELECT offer_id FROM offers;" msgstr "" -#: wallet/wallet.c:4666 +#: wallet/wallet.c:4667 msgid "UPDATE offers SET status=? WHERE offer_id = ?;" msgstr "" -#: wallet/wallet.c:4677 +#: wallet/wallet.c:4678 msgid "UPDATE invoices SET state=? WHERE state=? AND local_offer_id = ?;" msgstr "" -#: wallet/wallet.c:4705 +#: wallet/wallet.c:4706 msgid "SELECT status FROM offers WHERE offer_id = ?;" msgstr "" -#: wallet/wallet.c:4740 +#: wallet/wallet.c:4791 msgid "UPDATE datastore SET data=?, generation=generation+1 WHERE key=?;" msgstr "" -#: wallet/wallet.c:4751 +#: wallet/wallet.c:4802 msgid "INSERT INTO datastore VALUES (?, ?, 0);" msgstr "" -#: wallet/wallet.c:4765 +#: wallet/wallet.c:4813 msgid "DELETE FROM datastore WHERE key = ?" msgstr "" -#: wallet/wallet.c:4781 -msgid "SELECT data, generation FROM datastore WHERE key = ?;" +#: wallet/wallet.c:4830 +msgid "SELECT key, data, generation FROM datastore WHERE key >= ? ORDER BY key;" msgstr "" -#: wallet/wallet.c:4806 -msgid "SELECT key, data, generation FROM datastore;" +#: wallet/wallet.c:4837 +msgid "SELECT key, data, generation FROM datastore ORDER BY key;" msgstr "" #: wallet/test/run-db.c:126 @@ -1365,4 +1365,4 @@ msgstr "" #: wallet/test/run-wallet.c:1696 msgid "INSERT INTO channels (id) VALUES (1);" msgstr "" -# SHA256STAMP:b53b30b037efb732878f8ace50cb7b452a5f76ac03a009fd4727c33c189239dc +# SHA256STAMP:1aa03018c8753da3af1ec554fb80dde12716d1fb9687c291f234b878ca19cdc0 diff --git a/wallet/wallet.c b/wallet/wallet.c index 8356e65a9bba..28aeccb71db9 100644 --- a/wallet/wallet.c +++ b/wallet/wallet.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -4732,78 +4733,111 @@ void wallet_offer_mark_used(struct db *db, const struct sha256 *offer_id) } } -void wallet_datastore_update(struct wallet *w, const char *key, const u8 *data) + +/* We join key parts with nuls for now. */ +static void db_bind_datastore_key(struct db_stmt *stmt, + int pos, + const char **key) +{ + u8 *joined; + size_t len; + + if (tal_count(key) == 1) { + db_bind_text(stmt, pos, key[0]); + return; + } + + len = strlen(key[0]); + joined = (u8 *)tal_strdup(tmpctx, key[0]); + for (size_t i = 1; i < tal_count(key); i++) { + tal_resize(&joined, len + 1 + strlen(key[i])); + joined[len] = '\0'; + memcpy(joined + len + 1, key[i], strlen(key[i])); + len += 1 + strlen(key[i]); + } + db_bind_blob(stmt, pos, joined, len); +} + +static const char **db_column_datastore_key(const tal_t *ctx, + struct db_stmt *stmt, + int col) +{ + char **key; + const u8 *joined = db_column_blob(stmt, col); + size_t len = db_column_bytes(stmt, col); + + key = tal_arr(ctx, char *, 0); + do { + size_t partlen; + for (partlen = 0; partlen < len; partlen++) { + if (joined[partlen] == '\0') { + partlen++; + break; + } + } + tal_arr_expand(&key, tal_strndup(key, (char *)joined, partlen)); + len -= partlen; + joined += partlen; + } while (len != 0); + + return cast_const2(const char **, key); +} + +void wallet_datastore_update(struct wallet *w, const char **key, const u8 *data) { struct db_stmt *stmt; stmt = db_prepare_v2(w->db, SQL("UPDATE datastore SET data=?, generation=generation+1 WHERE key=?;")); db_bind_talarr(stmt, 0, data); - db_bind_text(stmt, 1, key); + db_bind_datastore_key(stmt, 1, key); db_exec_prepared_v2(take(stmt)); } -void wallet_datastore_create(struct wallet *w, const char *key, const u8 *data) +void wallet_datastore_create(struct wallet *w, const char **key, const u8 *data) { struct db_stmt *stmt; stmt = db_prepare_v2(w->db, SQL("INSERT INTO datastore VALUES (?, ?, 0);")); - db_bind_text(stmt, 0, key); + db_bind_datastore_key(stmt, 0, key); db_bind_talarr(stmt, 1, data); db_exec_prepared_v2(take(stmt)); } -u8 *wallet_datastore_remove(const tal_t *ctx, struct wallet *w, const char *key, - u64 *generation) -{ - u8 *data = wallet_datastore_fetch(ctx, w, key, generation); - if (data) { - struct db_stmt *stmt; - - stmt = db_prepare_v2(w->db, SQL("DELETE FROM datastore" - " WHERE key = ?")); - db_bind_text(stmt, 0, key); - db_exec_prepared_v2(take(stmt)); - } - return data; -} - -u8 *wallet_datastore_fetch(const tal_t *ctx, - struct wallet *w, const char *key, - u64 *generation) +void wallet_datastore_remove(struct wallet *w, const char **key) { struct db_stmt *stmt; - u8 *data; - /* Test if already exists. */ - stmt = db_prepare_v2(w->db, SQL("SELECT data, generation" - " FROM datastore" - " WHERE key = ?;")); - db_bind_text(stmt, 0, key); - db_query_prepared(stmt); - - if (db_step(stmt)) { - data = db_column_talarr(ctx, stmt, 0); - if (generation) - *generation = db_column_u64(stmt, 1); - } else - data = NULL; - tal_free(stmt); - return data; + stmt = db_prepare_v2(w->db, SQL("DELETE FROM datastore" + " WHERE key = ?")); + db_bind_datastore_key(stmt, 0, key); + db_exec_prepared_v2(take(stmt)); } struct db_stmt *wallet_datastore_first(const tal_t *ctx, struct wallet *w, - const char **key, + const char **startkey, + const char ***key, const u8 **data, u64 *generation) { struct db_stmt *stmt; - stmt = db_prepare_v2(w->db, - SQL("SELECT key, data, generation FROM datastore;")); + if (startkey) { + stmt = db_prepare_v2(w->db, + SQL("SELECT key, data, generation" + " FROM datastore" + " WHERE key >= ?" + " ORDER BY key;")); + db_bind_datastore_key(stmt, 0, startkey); + } else { + stmt = db_prepare_v2(w->db, + SQL("SELECT key, data, generation" + " FROM datastore" + " ORDER BY key;")); + } db_query_prepared(stmt); return wallet_datastore_next(ctx, w, stmt, key, data, generation); @@ -4812,16 +4846,18 @@ struct db_stmt *wallet_datastore_first(const tal_t *ctx, struct db_stmt *wallet_datastore_next(const tal_t *ctx, struct wallet *w, struct db_stmt *stmt, - const char **key, + const char ***key, const u8 **data, u64 *generation) { if (!db_step(stmt)) return tal_free(stmt); - *key = tal_strdup(ctx, (const char *)db_column_text(stmt, 0)); - *data = db_column_talarr(ctx, stmt, 1); - *generation = db_column_u64(stmt, 2); + *key = db_column_datastore_key(ctx, stmt, 0); + if (data) + *data = db_column_talarr(ctx, stmt, 1); + if (generation) + *generation = db_column_u64(stmt, 2); return stmt; } diff --git a/wallet/wallet.h b/wallet/wallet.h index d5426380aad0..a28c3f6f920d 100644 --- a/wallet/wallet.h +++ b/wallet/wallet.h @@ -1533,10 +1533,10 @@ void wallet_offer_mark_used(struct db *db, const struct sha256 *offer_id) /** * Add an new key/value to the datastore (generation 0) * @w: the wallet - * @key: the first key (if returns non-NULL) - * @data: the first data (if returns non-NULL) + * @key: the new key (if returns non-NULL) + * @data: the new data (if returns non-NULL) */ -void wallet_datastore_create(struct wallet *w, const char *key, const u8 *data); +void wallet_datastore_create(struct wallet *w, const char **key, const u8 *data); /** * Update an existing key/value to the datastore. @@ -1544,37 +1544,22 @@ void wallet_datastore_create(struct wallet *w, const char *key, const u8 *data); * @key: the first key (if returns non-NULL) * @data: the first data (if returns non-NULL) */ -void wallet_datastore_update(struct wallet *w, const char *key, const u8 *data); +void wallet_datastore_update(struct wallet *w, + const char **key, + const u8 *data); /** - * Remove a key from the datastore (return the old data). - * @ctx: the tal ctx to allocate return off + * Remove a key from the datastore * @w: the wallet * @key: the key - * @generation: the generation of deleted record - * - * Returns NULL if the key was not in the store. - */ -u8 *wallet_datastore_remove(const tal_t *ctx, struct wallet *w, const char *key, - u64 *generation); - -/** - * Retrieve a value from the datastore. - * @ctx: the tal ctx to allocate return off - * @w: the wallet - * @key: the first key (if returns non-NULL) - * @generation: the generation (if returns non-NULL), or NULL. - * - * Returns NULL if the key is not in the store. */ -u8 *wallet_datastore_fetch(const tal_t *ctx, - struct wallet *w, const char *key, - u64 *generation); +void wallet_datastore_remove(struct wallet *w, const char **key); /** * Iterate through the datastore. * @ctx: the tal ctx to allocate off * @w: the wallet + * @startkey: NULL, or the first key to start with * @key: the first key (if returns non-NULL) * @data: the first data (if returns non-NULL) * @generation: the first generation (if returns non-NULL) @@ -1584,7 +1569,8 @@ u8 *wallet_datastore_fetch(const tal_t *ctx, */ struct db_stmt *wallet_datastore_first(const tal_t *ctx, struct wallet *w, - const char **key, + const char **startkey, + const char ***key, const u8 **data, u64 *generation); @@ -1603,7 +1589,7 @@ struct db_stmt *wallet_datastore_first(const tal_t *ctx, struct db_stmt *wallet_datastore_next(const tal_t *ctx, struct wallet *w, struct db_stmt *stmt, - const char **key, + const char ***key, const u8 **data, u64 *generation);