Browse Source

common/json: make json_scan return an error string.

This makes for more useful errors.  It prints where it was up to in
the guide, but doesn't print the entire JSON it's scanning.

Suggested-by: Christian Decker
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
ppa
Rusty Russell 4 years ago
committed by Christian Decker
parent
commit
3b7d0e7a62
  1. 271
      common/json.c
  2. 11
      common/json.h
  3. 76
      common/test/run-json_scan.c
  4. 52
      lightningd/bitcoind.c
  5. 32
      plugins/bcli.c
  6. 7
      plugins/keysend.c
  7. 20
      plugins/libplugin.c
  8. 63
      plugins/pay.c
  9. 10
      plugins/spender/openchannel.c

271
common/json.c

@ -720,30 +720,42 @@ void json_tok_remove(jsmntok_t **tokens,
} }
/* talfmt take a ctx pointer and return NULL or a valid pointer. /* talfmt take a ctx pointer and return NULL or a valid pointer.
* fmt takes the argument, and returns a bool. */ * fmt takes the argument, and returns a bool.
static bool handle_percent(const char *buffer, *
* This function returns NULL on success, or errmsg on failure.
*/
static const char *handle_percent(const char *buffer,
const jsmntok_t *tok, const jsmntok_t *tok,
va_list *ap) va_list *ap)
{ {
void *ctx; void *ctx;
const char *fmtname;
/* This is set to (dummy) json_scan if it's a non-tal fmt */ /* This is set to (dummy) json_scan if it's a non-tal fmt */
ctx = va_arg(*ap, void *); ctx = va_arg(*ap, void *);
fmtname = va_arg(*ap, const char *);
if (ctx != json_scan) { if (ctx != json_scan) {
void *(*talfmt)(void *, const char *, const jsmntok_t *); void *(*talfmt)(void *, const char *, const jsmntok_t *);
void **p; void **p;
p = va_arg(*ap, void **); p = va_arg(*ap, void **);
talfmt = va_arg(*ap, void *(*)(void *, const char *, const jsmntok_t *)); talfmt = va_arg(*ap, void *(*)(void *, const char *, const jsmntok_t *));
*p = talfmt(ctx, buffer, tok); *p = talfmt(ctx, buffer, tok);
return *p != NULL; if (*p != NULL)
return NULL;
} else { } else {
bool (*fmt)(const char *, const jsmntok_t *, void *); bool (*fmt)(const char *, const jsmntok_t *, void *);
void *p; void *p;
p = va_arg(*ap, void *); p = va_arg(*ap, void *);
fmt = va_arg(*ap, bool (*)(const char *, const jsmntok_t *, void *)); fmt = va_arg(*ap, bool (*)(const char *, const jsmntok_t *, void *));
return fmt(buffer, tok, p); if (fmt(buffer, tok, p))
return NULL;
} }
return tal_fmt(tmpctx, "%s could not parse %.*s",
fmtname,
json_tok_full_len(tok),
json_tok_full(buffer, tok));
} }
/* GUIDE := OBJ | ARRAY | '%' /* GUIDE := OBJ | ARRAY | '%'
@ -756,235 +768,280 @@ static bool handle_percent(const char *buffer,
* ARRELEM := NUMBER ':' FIELDVAL * ARRELEM := NUMBER ':' FIELDVAL
*/ */
/* Returns NULL on failure, or offset into guide */ static void parse_literal(const char **guide,
static const char *parse_literal(const char *guide,
const char **literal, const char **literal,
size_t *len) size_t *len)
{ {
*literal = guide; *literal = *guide;
*len = strspn(guide, *len = strspn(*guide,
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyz"
"0123456789" "0123456789"
"_-"); "_-");
return guide + *len; *guide += *len;
} }
static const char *parse_number(const char *guide, u32 *number) static void parse_number(const char **guide, u32 *number)
{ {
char *endp; char *endp;
long int l; long int l;
l = strtol(guide, &endp, 10); l = strtol(*guide, &endp, 10);
if (endp == guide || errno == ERANGE) assert(endp != *guide);
return NULL; assert(errno != ERANGE);
/* Test for overflow */ /* Test for overflow */
*number = l; *number = l;
if (*number != l) assert(*number == l);
return NULL;
return endp; *guide = endp;
} }
/* Recursion */ static char guide_consume_one(const char **guide)
{
char c = **guide;
(*guide)++;
return c;
}
static void guide_must_be(const char **guide, char c)
{
char actual = guide_consume_one(guide);
assert(actual == c);
}
/* Recursion: return NULL on success, errmsg on fail */
static const char *parse_obj(const char *buffer, static const char *parse_obj(const char *buffer,
const jsmntok_t *tok, const jsmntok_t *tok,
const char *guide, const char **guide,
va_list *ap); va_list *ap);
static const char *parse_arr(const char *buffer, static const char *parse_arr(const char *buffer,
const jsmntok_t *tok, const jsmntok_t *tok,
const char *guide, const char **guide,
va_list *ap); va_list *ap);
static const char *parse_guide(const char *buffer, static const char *parse_guide(const char *buffer,
const jsmntok_t *tok, const jsmntok_t *tok,
const char *guide, const char **guide,
va_list *ap) va_list *ap)
{ {
if (*guide == '{') { const char *errmsg;
guide = parse_obj(buffer, tok, guide, ap);
if (!guide) if (**guide == '{') {
return NULL; errmsg = parse_obj(buffer, tok, guide, ap);
assert(*guide == '}'); if (errmsg)
return guide + 1; return errmsg;
} else if (*guide == '[') { } else if (**guide == '[') {
guide = parse_arr(buffer, tok, guide, ap); errmsg = parse_arr(buffer, tok, guide, ap);
if (!guide) if (errmsg)
return NULL; return errmsg;
assert(*guide == ']');
return guide + 1;
} else { } else {
assert(*guide == '%'); guide_must_be(guide, '%');
if (!handle_percent(buffer, tok, ap)) errmsg = handle_percent(buffer, tok, ap);
return NULL; if (errmsg)
return guide + 1; return errmsg;
} }
return NULL;
} }
static const char *parse_fieldval(const char *buffer, static const char *parse_fieldval(const char *buffer,
const jsmntok_t *tok, const jsmntok_t *tok,
const char *guide, const char **guide,
va_list *ap) va_list *ap)
{ {
if (*guide == '{') { const char *errmsg;
guide = parse_obj(buffer, tok, guide, ap);
if (!guide) if (**guide == '{') {
return NULL; errmsg = parse_obj(buffer, tok, guide, ap);
assert(*guide == '}'); if (errmsg)
return guide + 1; return errmsg;
} else if (*guide == '[') { } else if (**guide == '[') {
guide = parse_arr(buffer, tok, guide, ap); errmsg = parse_arr(buffer, tok, guide, ap);
if (!guide) if (errmsg)
return NULL; return errmsg;
assert(*guide == ']'); } else if (**guide == '%') {
return guide + 1; guide_consume_one(guide);
} else if (*guide == '%') { errmsg = handle_percent(buffer, tok, ap);
if (!handle_percent(buffer, tok, ap)) if (errmsg)
return NULL; return errmsg;
return guide + 1;
} else { } else {
const char *literal; const char *literal;
size_t len; size_t len;
/* Literal must match exactly */ /* Literal must match exactly (modulo quotes for strings) */
guide = parse_literal(guide, &literal, &len); parse_literal(guide, &literal, &len);
if (!memeq(buffer + tok->start, tok->end - tok->start, if (!memeq(buffer + tok->start, tok->end - tok->start,
literal, len)) literal, len)) {
return NULL; return tal_fmt(tmpctx,
return guide; "%.*s does not match expected %.*s",
json_tok_full_len(tok),
json_tok_full(buffer, tok),
(int)len, literal);
} }
} }
return NULL;
}
static const char *parse_field(const char *buffer, static const char *parse_field(const char *buffer,
const jsmntok_t *tok, const jsmntok_t *tok,
const char *guide, const char **guide,
va_list *ap) va_list *ap)
{ {
const jsmntok_t *member; const jsmntok_t *member;
size_t len; size_t len;
const char *memname; const char *memname;
guide = parse_literal(guide, &memname, &len); parse_literal(guide, &memname, &len);
assert(*guide == ':'); guide_must_be(guide, ':');
member = json_get_membern(buffer, tok, memname, guide - memname); member = json_get_membern(buffer, tok, memname, len);
if (!member) if (!member) {
return NULL; return tal_fmt(tmpctx, "object does not have member %.*s",
(int)len, memname);
}
return parse_fieldval(buffer, member, guide + 1, ap); return parse_fieldval(buffer, member, guide, ap);
} }
static const char *parse_fieldlist(const char *buffer, static const char *parse_fieldlist(const char *buffer,
const jsmntok_t *tok, const jsmntok_t *tok,
const char *guide, const char **guide,
va_list *ap) va_list *ap)
{ {
for (;;) { for (;;) {
guide = parse_field(buffer, tok, guide, ap); const char *errmsg;
if (!guide)
return NULL; errmsg = parse_field(buffer, tok, guide, ap);
if (*guide != ',') if (errmsg)
return errmsg;
if (**guide != ',')
break; break;
guide++; guide_consume_one(guide);
} }
return guide; return NULL;
} }
static const char *parse_obj(const char *buffer, static const char *parse_obj(const char *buffer,
const jsmntok_t *tok, const jsmntok_t *tok,
const char *guide, const char **guide,
va_list *ap) va_list *ap)
{ {
assert(*guide == '{'); const char *errmsg;
if (tok->type != JSMN_OBJECT) guide_must_be(guide, '{');
return NULL;
guide = parse_fieldlist(buffer, tok, guide + 1, ap); if (tok->type != JSMN_OBJECT) {
if (!guide) return tal_fmt(tmpctx, "token is not an object: %.*s",
json_tok_full_len(tok),
json_tok_full(buffer, tok));
}
errmsg = parse_fieldlist(buffer, tok, guide, ap);
if (errmsg)
return errmsg;
guide_must_be(guide, '}');
return NULL; return NULL;
return guide;
} }
static const char *parse_arrelem(const char *buffer, static const char *parse_arrelem(const char *buffer,
const jsmntok_t *tok, const jsmntok_t *tok,
const char *guide, const char **guide,
va_list *ap) va_list *ap)
{ {
const jsmntok_t *member; const jsmntok_t *member;
u32 idx; u32 idx;
guide = parse_number(guide, &idx); parse_number(guide, &idx);
assert(*guide == ':'); guide_must_be(guide, ':');
member = json_get_arr(tok, idx); member = json_get_arr(tok, idx);
if (!member) if (!member) {
return NULL; return tal_fmt(tmpctx, "token has no index %u: %.*s",
idx,
json_tok_full_len(tok),
json_tok_full(buffer, tok));
}
return parse_fieldval(buffer, member, guide + 1, ap); return parse_fieldval(buffer, member, guide, ap);
} }
static const char *parse_arrlist(const char *buffer, static const char *parse_arrlist(const char *buffer,
const jsmntok_t *tok, const jsmntok_t *tok,
const char *guide, const char **guide,
va_list *ap) va_list *ap)
{ {
const char *errmsg;
for (;;) { for (;;) {
guide = parse_arrelem(buffer, tok, guide, ap); errmsg = parse_arrelem(buffer, tok, guide, ap);
if (!guide) if (errmsg)
return NULL; return errmsg;
if (*guide != ',') if (**guide != ',')
break; break;
guide++; guide_consume_one(guide);
} }
return guide; return NULL;
} }
static const char *parse_arr(const char *buffer, static const char *parse_arr(const char *buffer,
const jsmntok_t *tok, const jsmntok_t *tok,
const char *guide, const char **guide,
va_list *ap) va_list *ap)
{ {
assert(*guide == '['); const char *errmsg;
if (tok->type != JSMN_ARRAY) guide_must_be(guide, '[');
return NULL;
guide = parse_arrlist(buffer, tok, guide + 1, ap); if (tok->type != JSMN_ARRAY) {
if (!guide) return tal_fmt(tmpctx, "token is not an array: %.*s",
json_tok_full_len(tok),
json_tok_full(buffer, tok));
}
errmsg = parse_arrlist(buffer, tok, guide, ap);
if (errmsg)
return errmsg;
guide_must_be(guide, ']');
return NULL; return NULL;
return guide;
} }
bool json_scanv(const char *buffer, const char *json_scanv(const tal_t *ctx,
const char *buffer,
const jsmntok_t *tok, const jsmntok_t *tok,
const char *guide, const char *guide,
va_list ap) va_list ap)
{ {
va_list cpy; va_list cpy;
const char *orig_guide = guide, *errmsg;
/* We need this, since &ap doesn't work on some platforms... */ /* We need this, since &ap doesn't work on some platforms... */
va_copy(cpy, ap); va_copy(cpy, ap);
guide = parse_guide(buffer, tok, guide, &cpy); errmsg = parse_guide(buffer, tok, &guide, &cpy);
va_end(cpy); va_end(cpy);
if (!guide) if (errmsg) {
return false; return tal_fmt(ctx, "Parsing '%.*s': %s",
(int)(guide - orig_guide), orig_guide,
errmsg);
}
assert(guide[0] == '\0'); assert(guide[0] == '\0');
return true; return NULL;
} }
bool json_scan(const char *buffer, const char *json_scan(const tal_t *ctx,
const char *buffer,
const jsmntok_t *tok, const jsmntok_t *tok,
const char *guide, const char *guide,
...) ...)
{ {
va_list ap; va_list ap;
bool ret; const char *ret;
va_start(ap, guide); va_start(ap, guide);
ret = json_scanv(buffer, tok, guide, ap); ret = json_scanv(ctx, buffer, tok, guide, ap);
va_end(ap); va_end(ap);
return ret; return ret;
} }

11
common/json.h

@ -152,8 +152,10 @@ jsmntok_t *json_tok_copy(const tal_t *ctx, const jsmntok_t *tok);
void json_tok_remove(jsmntok_t **tokens, void json_tok_remove(jsmntok_t **tokens,
jsmntok_t *obj_or_array, const jsmntok_t *tok, size_t num); jsmntok_t *obj_or_array, const jsmntok_t *tok, size_t num);
/* Guide is % for a token: each must be followed by JSON_SCAN(). */ /* Guide is % for a token: each must be followed by JSON_SCAN().
bool json_scan(const char *buffer, * Returns NULL on error (asserts() on bad guide). */
const char *json_scan(const tal_t *ctx,
const char *buffer,
const jsmntok_t *tok, const jsmntok_t *tok,
const char *guide, const char *guide,
...); ...);
@ -161,6 +163,7 @@ bool json_scan(const char *buffer,
/* eg. JSON_SCAN(json_to_bool, &boolvar) */ /* eg. JSON_SCAN(json_to_bool, &boolvar) */
#define JSON_SCAN(fmt, var) \ #define JSON_SCAN(fmt, var) \
json_scan, \ json_scan, \
stringify(fmt), \
((var) + 0*sizeof(fmt((const char *)NULL, \ ((var) + 0*sizeof(fmt((const char *)NULL, \
(const jsmntok_t *)NULL, var) == true)), \ (const jsmntok_t *)NULL, var) == true)), \
(fmt) (fmt)
@ -168,13 +171,15 @@ bool json_scan(const char *buffer,
/* eg. JSON_SCAN_TAL(tmpctx, json_strdup, &charvar) */ /* eg. JSON_SCAN_TAL(tmpctx, json_strdup, &charvar) */
#define JSON_SCAN_TAL(ctx, fmt, var) \ #define JSON_SCAN_TAL(ctx, fmt, var) \
(ctx), \ (ctx), \
stringify(fmt), \
((var) + 0*sizeof((*var) = fmt((ctx), \ ((var) + 0*sizeof((*var) = fmt((ctx), \
(const char *)NULL, \ (const char *)NULL, \
(const jsmntok_t *)NULL))), \ (const jsmntok_t *)NULL))), \
(fmt) (fmt)
/* Already-have-varargs version */ /* Already-have-varargs version */
bool json_scanv(const char *buffer, const char *json_scanv(const tal_t *ctx,
const char *buffer,
const jsmntok_t *tok, const jsmntok_t *tok,
const char *guide, const char *guide,
va_list ap); va_list ap);

76
common/test/run-json_scan.c

@ -137,6 +137,7 @@ int main(int argc, char *argv[])
char *s; char *s;
u32 u32val; u32 u32val;
u8 *hex; u8 *hex;
const char *err;
common_setup(argv[0]); common_setup(argv[0]);
@ -146,54 +147,69 @@ int main(int argc, char *argv[])
assert(toks->size == 4); assert(toks->size == 4);
/* These are direct matches, and they work. */ /* These are direct matches, and they work. */
assert(json_scan(buf, toks, "{1:one}")); assert(!json_scan(tmpctx, buf, toks, "{1:one}"));
assert(json_scan(buf, toks, "{1:one,2:two}")); assert(!json_scan(tmpctx, buf, toks, "{1:one,2:two}"));
assert(json_scan(buf, toks, "{2:two,1:one}")); assert(!json_scan(tmpctx, buf, toks, "{2:two,1:one}"));
assert(json_scan(buf, toks, "{2:two,1:one,3:{three:{deeper:17}}}")); assert(!json_scan(tmpctx, buf, toks, "{2:two,1:one,3:{three:{deeper:17}}}"));
assert(json_scan(buf, toks, "{2:two,1:one,3:{three:{deeper:17}},arr:[0:{1:arrone}]}")); assert(!json_scan(tmpctx, buf, toks, "{2:two,1:one,3:{three:{deeper:17}},arr:[0:{1:arrone}]}"));
assert(json_scan(buf, toks, "{2:two,1:one,3:{three:{deeper:17}},arr:[1:2]}")); assert(!json_scan(tmpctx, buf, toks, "{2:two,1:one,3:{three:{deeper:17}},arr:[1:2]}"));
assert(json_scan(buf, toks, "{2:two,1:one,3:{three:{deeper:17}},arr:[1:2,2:[0:3,1:4]]}")); assert(!json_scan(tmpctx, buf, toks, "{2:two,1:one,3:{three:{deeper:17}},arr:[1:2,2:[0:3,1:4]]}"));
/* These do not match */ /* These do not match */
assert(!json_scan(buf, toks, "{2:one}")); err = json_scan(tmpctx, buf, toks, "{2:one}");
assert(!json_scan(buf, toks, "{1:one,2:tw}")); assert(streq(err, "Parsing '{2:one': \"two\" does not match expected one"));
assert(!json_scan(buf, toks, "{1:one,2:twoo}")); err = json_scan(tmpctx, buf, toks, "{1:one,2:tw}");
assert(!json_scan(buf, toks, "{4:one}")); assert(streq(err, "Parsing '{1:one,2:tw': \"two\" does not match expected tw"));
assert(!json_scan(buf, toks, "{2:two,1:one,3:three}")); err = json_scan(tmpctx, buf, toks, "{1:one,2:twoo}");
assert(!json_scan(buf, toks, "{3:{three:deeper}}")); assert(streq(err, "Parsing '{1:one,2:twoo': \"two\" does not match expected twoo"));
assert(!json_scan(buf, toks, "{3:{three:{deeper:{}}}}")); err = json_scan(tmpctx, buf, toks, "{4:one}");
assert(!json_scan(buf, toks, "{arr:{}}")); assert(streq(err, "Parsing '{4:': object does not have member 4"));
assert(!json_scan(buf, toks, "{arr:[0:1]}")); err = json_scan(tmpctx, buf, toks, "{2:two,1:one,3:three}");
assert(!json_scan(buf, toks, "{arr:[4:0]}")); assert(streq(err, "Parsing '{2:two,1:one,3:three': {\"three\": {\"deeper\": 17}} does not match expected three"));
assert(!json_scan(buf, toks, "{arr:[0:{1:arrtwo}]}")); err = json_scan(tmpctx, buf, toks, "{3:{three:deeper}}");
assert(!json_scan(buf, toks, "{arr:[1:3]}")); assert(streq(err, "Parsing '{3:{three:deeper': {\"deeper\": 17} does not match expected deeper"));
assert(!json_scan(buf, toks, "{arr:[2:[0:1]]}")); err = json_scan(tmpctx, buf, toks, "{3:{three:{deeper:{}}}}");
assert(streq(err, "Parsing '{3:{three:{deeper:{': token is not an object: 17"));
err = json_scan(tmpctx, buf, toks, "{arr:{}}");
assert(streq(err, "Parsing '{arr:{': token is not an object: [{\"1\": \"arrone\"}, 2, [3, 4]]"));
err = json_scan(tmpctx, buf, toks, "{arr:[0:1]}");
assert(streq(err, "Parsing '{arr:[0:1': {\"1\": \"arrone\"} does not match expected 1"));
err = json_scan(tmpctx, buf, toks, "{arr:[4:0]}");
assert(streq(err, "Parsing '{arr:[4:': token has no index 4: [{\"1\": \"arrone\"}, 2, [3, 4]]"));
err = json_scan(tmpctx, buf, toks, "{arr:[0:{1:arrtwo}]}");
assert(streq(err, "Parsing '{arr:[0:{1:arrtwo': \"arrone\" does not match expected arrtwo"));
err = json_scan(tmpctx, buf, toks, "{arr:[1:3]}");
assert(streq(err, "Parsing '{arr:[1:3': 2 does not match expected 3"));
err = json_scan(tmpctx, buf, toks, "{arr:[2:[0:1]]}");
assert(streq(err, "Parsing '{arr:[2:[0:1': 3 does not match expected 1"));
/* These capture simple values. */ /* These capture simple values. */
assert(json_scan(buf, toks, "{3:{three:{deeper:%}}}", assert(!json_scan(tmpctx, buf, toks, "{3:{three:{deeper:%}}}",
JSON_SCAN(json_to_number, &u32val))); JSON_SCAN(json_to_number, &u32val)));
assert(u32val == 17); assert(u32val == 17);
assert(!json_scan(buf, toks, "{1:%}", err = json_scan(tmpctx, buf, toks, "{1:%}",
JSON_SCAN(json_to_number, &u32val))); JSON_SCAN(json_to_number, &u32val));
assert(json_scan(buf, toks, "{1:%}", assert(streq(err, "Parsing '{1:%': json_to_number could not parse \"one\""));
assert(!json_scan(tmpctx, buf, toks, "{1:%}",
JSON_SCAN_TAL(tmpctx, json_strdup, &s))); JSON_SCAN_TAL(tmpctx, json_strdup, &s)));
assert(tal_parent(s) == tmpctx); assert(tal_parent(s) == tmpctx);
assert(streq(s, "one")); assert(streq(s, "one"));
assert(!json_scan(buf, toks, "{1:%}", err = json_scan(tmpctx, buf, toks, "{1:%}",
JSON_SCAN_TAL(tmpctx, json_tok_bin_from_hex, &hex))); JSON_SCAN_TAL(tmpctx, json_tok_bin_from_hex, &hex));
assert(streq(err, "Parsing '{1:%': json_tok_bin_from_hex could not parse \"one\""));
assert(json_scan(buf, toks, "{3:%}", JSON_SCAN(json_to_tok, &t))); assert(!json_scan(tmpctx, buf, toks, "{3:%}", JSON_SCAN(json_to_tok, &t)));
assert(t == &toks[6]); assert(t == &toks[6]);
assert(json_scan(buf, toks, "{arr:%}", JSON_SCAN(json_to_tok, &t))); assert(!json_scan(tmpctx, buf, toks, "{arr:%}", JSON_SCAN(json_to_tok, &t)));
assert(t == &toks[12]); assert(t == &toks[12]);
assert(json_scan(buf, toks, "{arr:[1:%]}", assert(!json_scan(tmpctx, buf, toks, "{arr:[1:%]}",
JSON_SCAN(json_to_number, &u32val))); JSON_SCAN(json_to_number, &u32val)));
assert(u32val == 2); assert(u32val == 2);
assert(json_scan(buf, toks, "{arr:[2:[1:%]]}", assert(!json_scan(tmpctx, buf, toks, "{arr:[2:[1:%]]}",
JSON_SCAN(json_to_number, &u32val))); JSON_SCAN(json_to_number, &u32val)));
assert(u32val == 4); assert(u32val == 4);

52
lightningd/bitcoind.c

@ -265,20 +265,24 @@ static void sendrawtx_callback(const char *buf, const jsmntok_t *toks,
const jsmntok_t *idtok, const jsmntok_t *idtok,
struct sendrawtx_call *call) struct sendrawtx_call *call)
{ {
const char *err;
const char *errmsg = NULL; const char *errmsg = NULL;
bool success = false; bool success = false;
if (!json_scan(buf, toks, "{result:{success:%}}", err = json_scan(tmpctx, buf, toks, "{result:{success:%}}",
JSON_SCAN(json_to_bool, &success))) { JSON_SCAN(json_to_bool, &success));
if (err) {
bitcoin_plugin_error(call->bitcoind, buf, toks, bitcoin_plugin_error(call->bitcoind, buf, toks,
"sendrawtransaction", "sendrawtransaction",
"bad 'result' field"); "bad 'result' field: %s", err);
} else if (!success) { } else if (!success) {
if (!json_scan(buf, toks, "{result:{errmsg:%}}", err = json_scan(tmpctx, buf, toks, "{result:{errmsg:%}}",
JSON_SCAN_TAL(tmpctx, json_strdup, &errmsg))) JSON_SCAN_TAL(tmpctx, json_strdup, &errmsg));
if (err)
bitcoin_plugin_error(call->bitcoind, buf, toks, bitcoin_plugin_error(call->bitcoind, buf, toks,
"sendrawtransaction", "sendrawtransaction",
"bad 'errmsg' field"); "bad 'errmsg' field: %s",
err);
} }
db_begin_transaction(call->bitcoind->ld->wallet->db); db_begin_transaction(call->bitcoind->ld->wallet->db);
@ -311,8 +315,8 @@ static void sendrawtx_compatv090_callback(const char *buf,
* because it is an old plugin which does not support the * because it is an old plugin which does not support the
* new `allowhighfees` parameter. * new `allowhighfees` parameter.
*/ */
if (!json_scan(buf, toks, "{error:{code:" if (json_scan(tmpctx, buf, toks, "{error:{code:"
stringify(JSONRPC2_INVALID_PARAMS)"}}")) { stringify(JSONRPC2_INVALID_PARAMS)"}}") != NULL) {
goto fallback; goto fallback;
} }
@ -407,25 +411,27 @@ getrawblockbyheight_callback(const char *buf, const jsmntok_t *toks,
const jsmntok_t *idtok, const jsmntok_t *idtok,
struct getrawblockbyheight_call *call) struct getrawblockbyheight_call *call)
{ {
const char *block_str; const char *block_str, *err;
struct bitcoin_blkid blkid; struct bitcoin_blkid blkid;
struct bitcoin_block *blk; struct bitcoin_block *blk;
/* If block hash is `null`, this means not found! Call the callback /* If block hash is `null`, this means not found! Call the callback
* with NULL values. */ * with NULL values. */
if (json_scan(buf, toks, "{result:{blockhash:null}}")) { err = json_scan(tmpctx, buf, toks, "{result:{blockhash:null}}");
if (!err) {
db_begin_transaction(call->bitcoind->ld->wallet->db); db_begin_transaction(call->bitcoind->ld->wallet->db);
call->cb(call->bitcoind, NULL, NULL, call->cb_arg); call->cb(call->bitcoind, NULL, NULL, call->cb_arg);
db_commit_transaction(call->bitcoind->ld->wallet->db); db_commit_transaction(call->bitcoind->ld->wallet->db);
goto clean; goto clean;
} }
if (!json_scan(buf, toks, "{result:{blockhash:%,block:%}}", err = json_scan(tmpctx, buf, toks, "{result:{blockhash:%,block:%}}",
JSON_SCAN(json_to_sha256, &blkid.shad.sha), JSON_SCAN(json_to_sha256, &blkid.shad.sha),
JSON_SCAN_TAL(tmpctx, json_strdup, &block_str))) JSON_SCAN_TAL(tmpctx, json_strdup, &block_str));
if (err)
bitcoin_plugin_error(call->bitcoind, buf, toks, bitcoin_plugin_error(call->bitcoind, buf, toks,
"getrawblockbyheight", "getrawblockbyheight",
"bad 'result' field"); "bad 'result' field: %s", err);
blk = bitcoin_block_from_hex(tmpctx, chainparams, block_str, blk = bitcoin_block_from_hex(tmpctx, chainparams, block_str,
strlen(block_str)); strlen(block_str));
@ -499,18 +505,19 @@ static void getchaininfo_callback(const char *buf, const jsmntok_t *toks,
const jsmntok_t *idtok, const jsmntok_t *idtok,
struct getchaininfo_call *call) struct getchaininfo_call *call)
{ {
const char *chain; const char *err, *chain;
u32 headers, blocks; u32 headers, blocks;
bool ibd; bool ibd;
if (!json_scan(buf, toks, err = json_scan(tmpctx, buf, toks,
"{result:{chain:%,headercount:%,blockcount:%,ibd:%}}", "{result:{chain:%,headercount:%,blockcount:%,ibd:%}}",
JSON_SCAN_TAL(tmpctx, json_strdup, &chain), JSON_SCAN_TAL(tmpctx, json_strdup, &chain),
JSON_SCAN(json_to_number, &headers), JSON_SCAN(json_to_number, &headers),
JSON_SCAN(json_to_number, &blocks), JSON_SCAN(json_to_number, &blocks),
JSON_SCAN(json_to_bool, &ibd))) JSON_SCAN(json_to_bool, &ibd));
if (err)
bitcoin_plugin_error(call->bitcoind, buf, toks, "getchaininfo", bitcoin_plugin_error(call->bitcoind, buf, toks, "getchaininfo",
"bad 'result' field"); "bad 'result' field: %s", err);
db_begin_transaction(call->bitcoind->ld->wallet->db); db_begin_transaction(call->bitcoind->ld->wallet->db);
call->cb(call->bitcoind, chain, headers, blocks, ibd, call->cb(call->bitcoind, chain, headers, blocks, ibd,
@ -570,21 +577,24 @@ static void getutxout_callback(const char *buf, const jsmntok_t *toks,
const jsmntok_t *idtok, const jsmntok_t *idtok,
struct getutxout_call *call) struct getutxout_call *call)
{ {
const char *err;
struct bitcoin_tx_output txout; struct bitcoin_tx_output txout;
if (json_scan(buf, toks, "{result:{script:null}}")) { err = json_scan(tmpctx, buf, toks, "{result:{script:null}}");
if (!err) {
db_begin_transaction(call->bitcoind->ld->wallet->db); db_begin_transaction(call->bitcoind->ld->wallet->db);
call->cb(call->bitcoind, NULL, call->cb_arg); call->cb(call->bitcoind, NULL, call->cb_arg);
db_commit_transaction(call->bitcoind->ld->wallet->db); db_commit_transaction(call->bitcoind->ld->wallet->db);
goto clean; goto clean;
} }
if (!json_scan(buf, toks, "{result:{script:%,amount:%}}", err = json_scan(tmpctx, buf, toks, "{result:{script:%,amount:%}}",
JSON_SCAN_TAL(tmpctx, json_tok_bin_from_hex, JSON_SCAN_TAL(tmpctx, json_tok_bin_from_hex,
&txout.script), &txout.script),
JSON_SCAN(json_to_sat, &txout.amount))) JSON_SCAN(json_to_sat, &txout.amount));
if (err)
bitcoin_plugin_error(call->bitcoind, buf, toks, "getutxout", bitcoin_plugin_error(call->bitcoind, buf, toks, "getutxout",
"bad 'result' field"); "bad 'result' field: %s", err);
db_begin_transaction(call->bitcoind->ld->wallet->db); db_begin_transaction(call->bitcoind->ld->wallet->db);
call->cb(call->bitcoind, &txout, call->cb_arg); call->cb(call->bitcoind, &txout, call->cb_arg);

32
plugins/bcli.c

@ -326,6 +326,7 @@ static struct command_result *process_getutxout(struct bitcoin_cli *bcli)
const jsmntok_t *tokens; const jsmntok_t *tokens;
struct json_stream *response; struct json_stream *response;
struct bitcoin_tx_output output; struct bitcoin_tx_output output;
const char *err;
/* As of at least v0.15.1.0, bitcoind returns "success" but an empty /* As of at least v0.15.1.0, bitcoind returns "success" but an empty
string on a spent txout. */ string on a spent txout. */
@ -343,14 +344,14 @@ static struct command_result *process_getutxout(struct bitcoin_cli *bcli)
return command_err_bcli_badjson(bcli, "cannot parse"); return command_err_bcli_badjson(bcli, "cannot parse");
} }
if (!json_scan(bcli->output, tokens, err = json_scan(tmpctx, bcli->output, tokens,
"{value:%,scriptPubKey:{hex:%}}", "{value:%,scriptPubKey:{hex:%}}",
JSON_SCAN(json_to_bitcoin_amount, JSON_SCAN(json_to_bitcoin_amount,
&output.amount.satoshis), /* Raw: bitcoind */ &output.amount.satoshis), /* Raw: bitcoind */
JSON_SCAN_TAL(bcli, json_tok_bin_from_hex, JSON_SCAN_TAL(bcli, json_tok_bin_from_hex,
&output.script))) { &output.script));
return command_err_bcli_badjson(bcli, "cannot scan"); if (err)
} return command_err_bcli_badjson(bcli, err);
response = jsonrpc_stream_success(bcli->cmd); response = jsonrpc_stream_success(bcli->cmd);
json_add_amount_sat_only(response, "amount", output.amount); json_add_amount_sat_only(response, "amount", output.amount);
@ -365,7 +366,7 @@ static struct command_result *process_getblockchaininfo(struct bitcoin_cli *bcli
struct json_stream *response; struct json_stream *response;
bool ibd; bool ibd;
u32 headers, blocks; u32 headers, blocks;
const char *chain; const char *chain, *err;
tokens = json_parse_simple(bcli->output, tokens = json_parse_simple(bcli->output,
bcli->output, bcli->output_bytes); bcli->output, bcli->output_bytes);
@ -373,14 +374,14 @@ static struct command_result *process_getblockchaininfo(struct bitcoin_cli *bcli
return command_err_bcli_badjson(bcli, "cannot parse"); return command_err_bcli_badjson(bcli, "cannot parse");
} }
if (!json_scan(bcli->output, tokens, err = json_scan(tmpctx, bcli->output, tokens,
"{chain:%,headers:%,blocks:%,initialblockdownload:%}", "{chain:%,headers:%,blocks:%,initialblockdownload:%}",
JSON_SCAN_TAL(tmpctx, json_strdup, &chain), JSON_SCAN_TAL(tmpctx, json_strdup, &chain),
JSON_SCAN(json_to_number, &headers), JSON_SCAN(json_to_number, &headers),
JSON_SCAN(json_to_number, &blocks), JSON_SCAN(json_to_number, &blocks),
JSON_SCAN(json_to_bool, &ibd))) { JSON_SCAN(json_to_bool, &ibd));
return command_err_bcli_badjson(bcli, "cannot scan"); if (err)
} return command_err_bcli_badjson(bcli, err);
response = jsonrpc_stream_success(bcli->cmd); response = jsonrpc_stream_success(bcli->cmd);
json_add_string(response, "chain", chain); json_add_string(response, "chain", chain);
@ -425,8 +426,8 @@ estimatefees_parse_feerate(struct bitcoin_cli *bcli, u64 *feerate)
return command_err_bcli_badjson(bcli, "cannot parse"); return command_err_bcli_badjson(bcli, "cannot parse");
} }
if (!json_scan(bcli->output, tokens, "{feerate:%}", if (json_scan(tmpctx, bcli->output, tokens, "{feerate:%}",
JSON_SCAN(json_to_bitcoin_amount, feerate))) { JSON_SCAN(json_to_bitcoin_amount, feerate)) != NULL) {
/* Paranoia: if it had a feerate, but was malformed: */ /* Paranoia: if it had a feerate, but was malformed: */
if (json_get_member(bcli->output, tokens, "feerate")) if (json_get_member(bcli->output, tokens, "feerate"))
return command_err_bcli_badjson(bcli, "cannot scan"); return command_err_bcli_badjson(bcli, "cannot scan");
@ -782,6 +783,7 @@ static void parse_getnetworkinfo_result(struct plugin *p, const char *buf)
const jsmntok_t *result; const jsmntok_t *result;
bool tx_relay; bool tx_relay;
u32 min_version = 160000; u32 min_version = 160000;
const char *err;
result = json_parse_simple(NULL, buf, strlen(buf)); result = json_parse_simple(NULL, buf, strlen(buf));
if (!result) if (!result)
@ -790,11 +792,13 @@ static void parse_getnetworkinfo_result(struct plugin *p, const char *buf)
gather_args(bitcoind, "getnetworkinfo", NULL), buf); gather_args(bitcoind, "getnetworkinfo", NULL), buf);
/* Check that we have a fully-featured `estimatesmartfee`. */ /* Check that we have a fully-featured `estimatesmartfee`. */
if (!json_scan(buf, result, "{version:%,localrelay:%}", err = json_scan(tmpctx, buf, result, "{version:%,localrelay:%}",
JSON_SCAN(json_to_u32, &bitcoind->version), JSON_SCAN(json_to_u32, &bitcoind->version),
JSON_SCAN(json_to_bool, &tx_relay))) JSON_SCAN(json_to_bool, &tx_relay));
plugin_err(p, "No 'version' or localrelay in '%s' ? Got '%s'. Can not" if (err)
plugin_err(p, "%s. Got '%s'. Can not"
" continue without proceeding to sanity checks.", " continue without proceeding to sanity checks.",
err,
gather_args(bitcoind, "getnetworkinfo", NULL), buf); gather_args(bitcoind, "getnetworkinfo", NULL), buf);
if (bitcoind->version < min_version) if (bitcoind->version < min_version)

7
plugins/keysend.c

@ -251,10 +251,13 @@ static struct command_result *htlc_accepted_call(struct command *cmd,
struct keysend_in *ki; struct keysend_in *ki;
struct out_req *req; struct out_req *req;
struct timeabs now = time_now(); struct timeabs now = time_now();
const char *err;
if (!json_scan(buf, params, "{onion:{payload:%},htlc:{payment_hash:%}}", err = json_scan(tmpctx, buf, params,
"{onion:{payload:%},htlc:{payment_hash:%}}",
JSON_SCAN_TAL(cmd, json_tok_bin_from_hex, &rawpayload), JSON_SCAN_TAL(cmd, json_tok_bin_from_hex, &rawpayload),
JSON_SCAN(json_to_sha256, &payment_hash))) JSON_SCAN(json_to_sha256, &payment_hash));
if (err)
return htlc_accepted_continue(cmd, NULL); return htlc_accepted_continue(cmd, NULL);
max = tal_bytelen(rawpayload); max = tal_bytelen(rawpayload);

20
plugins/libplugin.c

@ -498,6 +498,7 @@ void rpc_scan(struct plugin *plugin,
...) ...)
{ {
bool error; bool error;
const char *err;
const jsmntok_t *contents; const jsmntok_t *contents;
int reqlen; int reqlen;
const char *p; const char *p;
@ -515,12 +516,13 @@ void rpc_scan(struct plugin *plugin,
p = membuf_consume(&plugin->rpc_conn->mb, reqlen); p = membuf_consume(&plugin->rpc_conn->mb, reqlen);
va_start(ap, guide); va_start(ap, guide);
error = !json_scanv(p, contents, guide, ap); err = json_scanv(tmpctx, p, contents, guide, ap);
va_end(ap); va_end(ap);
if (error) if (err)
plugin_err(plugin, "Could not parse %s in reply to %s: '%.*s'", plugin_err(plugin, "Could not parse %s in reply to %s: %s: '%.*s'",
guide, method, reqlen, membuf_elems(&plugin->rpc_conn->mb)); guide, method, err,
reqlen, membuf_elems(&plugin->rpc_conn->mb));
} }
static void handle_rpc_reply(struct plugin *plugin, const jsmntok_t *toks) static void handle_rpc_reply(struct plugin *plugin, const jsmntok_t *toks)
@ -810,9 +812,10 @@ static struct command_result *handle_init(struct command *cmd,
char *dir, *network; char *dir, *network;
struct plugin *p = cmd->plugin; struct plugin *p = cmd->plugin;
bool with_rpc = p->rpc_conn != NULL; bool with_rpc = p->rpc_conn != NULL;
const char *err;
configtok = json_get_member(buf, params, "configuration"); configtok = json_get_member(buf, params, "configuration");
if (!json_scan(buf, configtok, err = json_scan(tmpctx, buf, configtok,
"{lightning-dir:%" "{lightning-dir:%"
",network:%" ",network:%"
",feature_set:%" ",feature_set:%"
@ -820,9 +823,10 @@ static struct command_result *handle_init(struct command *cmd,
JSON_SCAN_TAL(tmpctx, json_strdup, &dir), JSON_SCAN_TAL(tmpctx, json_strdup, &dir),
JSON_SCAN_TAL(tmpctx, json_strdup, &network), JSON_SCAN_TAL(tmpctx, json_strdup, &network),
JSON_SCAN_TAL(p, json_to_feature_set, &p->our_features), JSON_SCAN_TAL(p, json_to_feature_set, &p->our_features),
JSON_SCAN_TAL(p, json_strdup, &p->rpc_location))) JSON_SCAN_TAL(p, json_strdup, &p->rpc_location));
plugin_err(p, "cannot scan init params: %.*s", if (err)
json_tok_full_len(params), plugin_err(p, "cannot scan init params: %s: %.*s",
err, json_tok_full_len(params),
json_tok_full(buf, params)); json_tok_full(buf, params));
/* Move into lightning directory: other files are relative */ /* Move into lightning directory: other files are relative */

63
plugins/pay.c

@ -399,9 +399,9 @@ get_remote_block_height(const char *buf, const jsmntok_t *error)
u16 type; u16 type;
/* Is there even a raw_message? */ /* Is there even a raw_message? */
if (!json_scan(buf, error, "{data:{raw_message:%}}", if (json_scan(tmpctx, buf, error, "{data:{raw_message:%}}",
JSON_SCAN_TAL(tmpctx, json_tok_bin_from_hex, JSON_SCAN_TAL(tmpctx, json_tok_bin_from_hex,
&raw_message))) &raw_message)) != NULL)
return 0; return 0;
/* BOLT #4: /* BOLT #4:
@ -431,19 +431,24 @@ static struct command_result *waitsendpay_error(struct command *cmd,
struct pay_attempt *attempt = current_attempt(pc); struct pay_attempt *attempt = current_attempt(pc);
errcode_t code; errcode_t code;
int failcode; int failcode;
const char *err;
attempt_failed_tok(pc, "waitsendpay", buf, error); attempt_failed_tok(pc, "waitsendpay", buf, error);
if (!json_scan(buf, error, "{code:%}", err = json_scan(tmpctx, buf, error, "{code:%}",
JSON_SCAN(json_to_errcode, &code))) JSON_SCAN(json_to_errcode, &code));
plugin_err(cmd->plugin, "waitsendpay error gave no 'code'? '%.*s'", if (err)
error->end - error->start, buf + error->start); plugin_err(cmd->plugin, "waitsendpay error %s? '%.*s'",
err,
json_tok_full_len(error), json_tok_full(buf, error));
if (code != PAY_UNPARSEABLE_ONION) { if (code != PAY_UNPARSEABLE_ONION) {
if (!json_scan(buf, error, "{data:{failcode:%}}", err = json_scan(tmpctx, buf, error, "{data:{failcode:%}}",
JSON_SCAN(json_to_int, &failcode))) JSON_SCAN(json_to_int, &failcode));
plugin_err(cmd->plugin, "waitsendpay error gave no 'failcode'? '%.*s'", if (err)
error->end - error->start, buf + error->start); plugin_err(cmd->plugin, "waitsendpay failcode error %s '%.*s'",
err,
json_tok_full_len(error), json_tok_full(buf, error));
} }
/* Special case for WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS. /* Special case for WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS.
@ -510,12 +515,15 @@ static struct command_result *waitsendpay_error(struct command *cmd,
if (failcode & NODE) { if (failcode & NODE) {
struct node_id id; struct node_id id;
const char *idstr; const char *idstr, *err;
if (!json_scan(buf, error, "{data:{erring_node:%}}", err = json_scan(tmpctx, buf, error, "{data:{erring_node:%}}",
JSON_SCAN(json_to_node_id, &id))) JSON_SCAN(json_to_node_id, &id));
plugin_err(cmd->plugin, "waitsendpay error no erring_node '%.*s'", if (err)
error->end - error->start, buf + error->start); plugin_err(cmd->plugin, "waitsendpay error %s '%.*s'",
err,
json_tok_full_len(error),
json_tok_full(buf, error));
/* FIXME: Keep as node_id, don't use strings. */ /* FIXME: Keep as node_id, don't use strings. */
idstr = node_id_to_hexstr(tmpctx, &id); idstr = node_id_to_hexstr(tmpctx, &id);
@ -530,13 +538,17 @@ static struct command_result *waitsendpay_error(struct command *cmd,
} else { } else {
struct short_channel_id scid; struct short_channel_id scid;
u32 dir; u32 dir;
const char *scidstr; const char *scidstr, *err;
if (!json_scan(buf, error, "{data:{erring_channel:%,erring_direction:%}}", err = json_scan(tmpctx, buf, error,
"{data:{erring_channel:%,erring_direction:%}}",
JSON_SCAN(json_to_short_channel_id, &scid), JSON_SCAN(json_to_short_channel_id, &scid),
JSON_SCAN(json_to_number, &dir))) JSON_SCAN(json_to_number, &dir));
plugin_err(cmd->plugin, "waitsendpay error no erring_channel/direction '%.*s'", if (err)
error->end - error->start, buf + error->start); plugin_err(cmd->plugin, "waitsendpay error %s '%.*s'",
err,
json_tok_full_len(error),
json_tok_full(buf, error));
scidstr = short_channel_id_to_str(tmpctx, &scid); scidstr = short_channel_id_to_str(tmpctx, &scid);
/* If failure is in routehint part, try next one */ /* If failure is in routehint part, try next one */
@ -736,6 +748,7 @@ static struct command_result *getroute_done(struct command *cmd,
struct amount_msat max_fee; struct amount_msat max_fee;
u32 delay; u32 delay;
struct out_req *req; struct out_req *req;
const char *err;
if (!t) if (!t)
plugin_err(cmd->plugin, "getroute gave no 'route'? '%.*s'", plugin_err(cmd->plugin, "getroute gave no 'route'? '%.*s'",
@ -754,11 +767,13 @@ static struct command_result *getroute_done(struct command *cmd,
} else } else
attempt->route = json_strdup(pc->ps->attempts, buf, t); attempt->route = json_strdup(pc->ps->attempts, buf, t);
if (!json_scan(buf, t, "[0:{msatoshi:%,delay:%}]", err = json_scan(tmpctx, buf, t, "[0:{msatoshi:%,delay:%}]",
JSON_SCAN(json_to_msat, &fee), JSON_SCAN(json_to_msat, &fee),
JSON_SCAN(json_to_number, &delay))) JSON_SCAN(json_to_number, &delay));
plugin_err(cmd->plugin, "getroute with invalid msatoshi/delay? %.*s", if (err)
result->end - result->start, buf); plugin_err(cmd->plugin, "getroute %s? %.*s",
err,
json_tok_full_len(result), json_tok_full(buf, result));
if (pc->maxfee_pct_millionths / 100 > UINT32_MAX) if (pc->maxfee_pct_millionths / 100 > UINT32_MAX)
plugin_err(cmd->plugin, "max fee percent too large: %lf", plugin_err(cmd->plugin, "max fee percent too large: %lf",

10
plugins/spender/openchannel.c

@ -547,15 +547,17 @@ static void json_peer_sigs(struct command *cmd,
struct channel_id cid; struct channel_id cid;
const struct wally_psbt *psbt; const struct wally_psbt *psbt;
struct multifundchannel_destination *dest; struct multifundchannel_destination *dest;
const char *err;
if (!json_scan(buf, params, err = json_scan(tmpctx, buf, params,
"{openchannel_peer_sigs:" "{openchannel_peer_sigs:"
"{channel_id:%,signed_psbt:%}}", "{channel_id:%,signed_psbt:%}}",
JSON_SCAN(json_to_channel_id, &cid), JSON_SCAN(json_to_channel_id, &cid),
JSON_SCAN_TAL(cmd, json_to_psbt, &psbt))) JSON_SCAN_TAL(cmd, json_to_psbt, &psbt));
if (err)
plugin_err(cmd->plugin, plugin_err(cmd->plugin,
"`openchannel_peer_sigs` did not scan", "`openchannel_peer_sigs` did not scan: %s",
json_tok_full_len(params), err, json_tok_full_len(params),
json_tok_full(buf, params)); json_tok_full(buf, params));
/* Find the destination that's got this channel_id on it! */ /* Find the destination that's got this channel_id on it! */

Loading…
Cancel
Save