mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-02-21 14:24:09 +01:00
decode: support decoding runes.
This is a bit weird since it lives in the offers plugin, but it works well. This should make runes much more approachable for people! Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
parent
468dff1723
commit
8c48eda8c7
5 changed files with 354 additions and 9 deletions
|
@ -120,6 +120,56 @@ time in seconds:
|
||||||
"unique_id": "3"
|
"unique_id": "3"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
You can also use lightning-decode(7) to examine runes you have been given:
|
||||||
|
|
||||||
|
$ .lightning-cli decode tU-RLjMiDpY2U0o3W1oFowar36RFGpWloPbW9-RuZdo9MyZpZD0wMjRiOWExZmE4ZTAwNmYxZTM5MzdmNjVmNjZjNDA4ZTZkYThlMWNhNzI4ZWE0MzIyMmE3MzgxZGYxY2M0NDk2MDUmbWV0aG9kPWxpc3RwZWVycyZwbnVtPTEmcG5hbWVpZF4wMjRiOWExZmE4ZTAwNmYxZTM5M3xwYXJyMF4wMjRiOWExZmE4ZTAwNmYxZTM5MyZ0aW1lPDE2NTY5MjA1MzgmcmF0ZT0y
|
||||||
|
{
|
||||||
|
"type": "rune",
|
||||||
|
"unique_id": "3",
|
||||||
|
"string": "b54f912e33220e9636534a375b5a05a306abdfa4451a95a5a0f6d6f7e46e65da:=3&id=024b9a1fa8e006f1e3937f65f66c408e6da8e1ca728ea43222a7381df1cc449605&method=listpeers&pnum=1&pnameid^024b9a1fa8e006f1e393|parr0^024b9a1fa8e006f1e393&time<1656920538&rate=2",
|
||||||
|
"restrictions": [
|
||||||
|
{
|
||||||
|
"alternatives": [
|
||||||
|
"id=024b9a1fa8e006f1e3937f65f66c408e6da8e1ca728ea43222a7381df1cc449605"
|
||||||
|
],
|
||||||
|
"summary": "id (of commanding peer) equal to '024b9a1fa8e006f1e3937f65f66c408e6da8e1ca728ea43222a7381df1cc449605'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alternatives": [
|
||||||
|
"method=listpeers"
|
||||||
|
],
|
||||||
|
"summary": "method (of command) equal to 'listpeers'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alternatives": [
|
||||||
|
"pnum=1"
|
||||||
|
],
|
||||||
|
"summary": "pnum (number of command parameters) equal to 1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alternatives": [
|
||||||
|
"pnameid^024b9a1fa8e006f1e393",
|
||||||
|
"parr0^024b9a1fa8e006f1e393"
|
||||||
|
],
|
||||||
|
"summary": "pnameid (object parameter 'id') starts with '024b9a1fa8e006f1e393' OR parr0 (array parameter #0) starts with '024b9a1fa8e006f1e393'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alternatives": [
|
||||||
|
"time<1656920538"
|
||||||
|
],
|
||||||
|
"summary": "time (in seconds since 1970) less than 1656920538 (approximately 19 hours 18 minutes from now)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alternatives": [
|
||||||
|
"rate=2"
|
||||||
|
],
|
||||||
|
"summary": "rate (max per minute) equal to 2"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"valid": true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
SHARING RUNES
|
SHARING RUNES
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
@ -157,7 +207,7 @@ excuses his previous adoption of the name "Eltoo".
|
||||||
SEE ALSO
|
SEE ALSO
|
||||||
--------
|
--------
|
||||||
|
|
||||||
lightning-commando(7)
|
lightning-commando(7), lightning-decode(7)
|
||||||
|
|
||||||
RESOURCES
|
RESOURCES
|
||||||
---------
|
---------
|
||||||
|
|
|
@ -9,17 +9,21 @@ SYNOPSIS
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
The **decode** RPC command checks and parses a *bolt11* or *bolt12*
|
The **decode** RPC command checks and parses:
|
||||||
string (optionally prefixed by `lightning:` or `LIGHTNING:`) as
|
|
||||||
specified by the BOLT 11 and BOLT 12 specifications. It may decode
|
- a *bolt11* or *bolt12* string (optionally prefixed by `lightning:`
|
||||||
other formats in future.
|
or `LIGHTNING:`) as specified by the BOLT 11 and BOLT 12
|
||||||
|
specifications.
|
||||||
|
- a *rune* as created by lightning-commando-rune(7).
|
||||||
|
|
||||||
|
It may decode other formats in future.
|
||||||
|
|
||||||
RETURN VALUE
|
RETURN VALUE
|
||||||
------------
|
------------
|
||||||
|
|
||||||
[comment]: # (GENERATE-FROM-SCHEMA-START)
|
[comment]: # (GENERATE-FROM-SCHEMA-START)
|
||||||
On success, an object is returned, containing:
|
On success, an object is returned, containing:
|
||||||
- **type** (string): what kind of object it decoded to (one of "bolt12 offer", "bolt12 invoice", "bolt12 invoice_request", "bolt11 invoice")
|
- **type** (string): what kind of object it decoded to (one of "bolt12 offer", "bolt12 invoice", "bolt12 invoice_request", "bolt11 invoice", "rune")
|
||||||
- **valid** (boolean): if this is false, you *MUST* not use the result except for diagnostics!
|
- **valid** (boolean): if this is false, you *MUST* not use the result except for diagnostics!
|
||||||
|
|
||||||
If **type** is "bolt12 offer", and **valid** is *true*:
|
If **type** is "bolt12 offer", and **valid** is *true*:
|
||||||
|
@ -159,6 +163,16 @@ If **type** is "bolt11 invoice", and **valid** is *true*:
|
||||||
- **tag** (string): The bech32 letter which identifies this field (always 1 characters)
|
- **tag** (string): The bech32 letter which identifies this field (always 1 characters)
|
||||||
- **data** (string): The bech32 data for this field
|
- **data** (string): The bech32 data for this field
|
||||||
|
|
||||||
|
If **type** is "rune":
|
||||||
|
- **string** (string): the string encoding of the rune
|
||||||
|
- **restrictions** (array of objects): restrictions built into the rune: all must pass:
|
||||||
|
- **alternatives** (array of strings): each way restriction can be met: any can pass:
|
||||||
|
- the alternative of form fieldname condition fieldname
|
||||||
|
- **summary** (string): human-readable summary of this restriction
|
||||||
|
- **unique_id** (string, optional): unique id (always a numeric id on runes we create)
|
||||||
|
- **version** (string, optional): rune version, not currently set on runes we create
|
||||||
|
- **valid** (boolean, optional) (always *true*)
|
||||||
|
|
||||||
[comment]: # (GENERATE-FROM-SCHEMA-END)
|
[comment]: # (GENERATE-FROM-SCHEMA-END)
|
||||||
|
|
||||||
AUTHOR
|
AUTHOR
|
||||||
|
@ -169,7 +183,7 @@ Rusty Russell <<rusty@rustcorp.com.au>> is mainly responsible.
|
||||||
SEE ALSO
|
SEE ALSO
|
||||||
--------
|
--------
|
||||||
|
|
||||||
lightning-pay(7), lightning-offer(7), lightning-offerout(7), lightning-fetchinvoice(7), lightning-sendinvoice(7)
|
lightning-pay(7), lightning-offer(7), lightning-offerout(7), lightning-fetchinvoice(7), lightning-sendinvoice(7), lightning-commando-rune(7)
|
||||||
|
|
||||||
[BOLT \#11](https://github.com/lightningnetwork/lightning-rfc/blob/master/11-payment-encoding.md).
|
[BOLT \#11](https://github.com/lightningnetwork/lightning-rfc/blob/master/11-payment-encoding.md).
|
||||||
|
|
||||||
|
@ -181,4 +195,4 @@ RESOURCES
|
||||||
|
|
||||||
Main web site: <https://github.com/ElementsProject/lightning>
|
Main web site: <https://github.com/ElementsProject/lightning>
|
||||||
|
|
||||||
[comment]: # ( SHA256STAMP:bc3778965137591623ce08ff51adf411bc42e6d1a4200692961b69962da39be7)
|
[comment]: # ( SHA256STAMP:d1e1f044c2e67ec169728dbc551903c97f9a9daa1f42e9d2f1686fc692d25be8)
|
||||||
|
|
|
@ -12,7 +12,8 @@
|
||||||
"bolt12 offer",
|
"bolt12 offer",
|
||||||
"bolt12 invoice",
|
"bolt12 invoice",
|
||||||
"bolt12 invoice_request",
|
"bolt12 invoice_request",
|
||||||
"bolt11 invoice"
|
"bolt11 invoice",
|
||||||
|
"rune"
|
||||||
],
|
],
|
||||||
"description": "what kind of object it decoded to"
|
"description": "what kind of object it decoded to"
|
||||||
},
|
},
|
||||||
|
@ -909,6 +910,72 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"if": {
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"rune"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"required": [
|
||||||
|
"string",
|
||||||
|
"restrictions"
|
||||||
|
],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"unique_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "unique id (always a numeric id on runes we create)"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "rune version, not currently set on runes we create"
|
||||||
|
},
|
||||||
|
"valid": {
|
||||||
|
"type": "boolean",
|
||||||
|
"enum": [
|
||||||
|
true
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"type": {},
|
||||||
|
"string": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "the string encoding of the rune"
|
||||||
|
},
|
||||||
|
"restrictions": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "restrictions built into the rune: all must pass",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"alternatives",
|
||||||
|
"summary"
|
||||||
|
],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"alternatives": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "each way restriction can be met: any can pass",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "the alternative of form fieldname condition fieldname"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "human-readable summary of this restriction"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
161
plugins/offers.c
161
plugins/offers.c
|
@ -3,6 +3,7 @@
|
||||||
#include <bitcoin/chainparams.h>
|
#include <bitcoin/chainparams.h>
|
||||||
#include <ccan/array_size/array_size.h>
|
#include <ccan/array_size/array_size.h>
|
||||||
#include <ccan/cast/cast.h>
|
#include <ccan/cast/cast.h>
|
||||||
|
#include <ccan/rune/rune.h>
|
||||||
#include <ccan/tal/str/str.h>
|
#include <ccan/tal/str/str.h>
|
||||||
#include <common/bech32.h>
|
#include <common/bech32.h>
|
||||||
#include <common/bolt11.h>
|
#include <common/bolt11.h>
|
||||||
|
@ -204,6 +205,7 @@ struct decodable {
|
||||||
struct tlv_offer *offer;
|
struct tlv_offer *offer;
|
||||||
struct tlv_invoice *invoice;
|
struct tlv_invoice *invoice;
|
||||||
struct tlv_invoice_request *invreq;
|
struct tlv_invoice_request *invreq;
|
||||||
|
struct rune *rune;
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct command_result *param_decodable(struct command *cmd,
|
static struct command_result *param_decodable(struct command *cmd,
|
||||||
|
@ -271,6 +273,13 @@ static struct command_result *param_decodable(struct command *cmd,
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
decodable->rune = rune_from_base64n(decodable, buffer + tok.start,
|
||||||
|
tok.end - tok.start);
|
||||||
|
if (decodable->rune) {
|
||||||
|
decodable->type = "rune";
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/* Return failure message from most likely parsing candidate */
|
/* Return failure message from most likely parsing candidate */
|
||||||
return command_fail_badparam(cmd, name, buffer, &tok, likely_fail);
|
return command_fail_badparam(cmd, name, buffer, &tok, likely_fail);
|
||||||
}
|
}
|
||||||
|
@ -808,6 +817,156 @@ static void json_add_invoice_request(struct json_stream *js,
|
||||||
json_add_bool(js, "valid", valid);
|
json_add_bool(js, "valid", valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void json_add_rune(struct command *cmd, struct json_stream *js, const struct rune *rune)
|
||||||
|
{
|
||||||
|
if (rune->unique_id)
|
||||||
|
json_add_string(js, "unique_id", rune->unique_id);
|
||||||
|
if (rune->version)
|
||||||
|
json_add_string(js, "version", rune->version);
|
||||||
|
json_add_string(js, "string", take(rune_to_string(NULL, rune)));
|
||||||
|
|
||||||
|
json_array_start(js, "restrictions");
|
||||||
|
for (size_t i = rune->unique_id ? 1 : 0; i < tal_count(rune->restrs); i++) {
|
||||||
|
const struct rune_restr *restr = rune->restrs[i];
|
||||||
|
char *summary = tal_strdup(tmpctx, "");
|
||||||
|
const char *sep = "";
|
||||||
|
|
||||||
|
json_object_start(js, NULL);
|
||||||
|
json_array_start(js, "alternatives");
|
||||||
|
for (size_t j = 0; j < tal_count(restr->alterns); j++) {
|
||||||
|
const struct rune_altern *alt = restr->alterns[j];
|
||||||
|
const char *annotation, *value;
|
||||||
|
bool int_val = false, time_val = false;
|
||||||
|
|
||||||
|
if (streq(alt->fieldname, "time")) {
|
||||||
|
annotation = "in seconds since 1970";
|
||||||
|
time_val = true;
|
||||||
|
} else if (streq(alt->fieldname, "id"))
|
||||||
|
annotation = "of commanding peer";
|
||||||
|
else if (streq(alt->fieldname, "method"))
|
||||||
|
annotation = "of command";
|
||||||
|
else if (streq(alt->fieldname, "pnum")) {
|
||||||
|
annotation = "number of command parameters";
|
||||||
|
int_val = true;
|
||||||
|
} else if (streq(alt->fieldname, "rate")) {
|
||||||
|
annotation = "max per minute";
|
||||||
|
int_val = true;
|
||||||
|
} else if (strstarts(alt->fieldname, "parr")) {
|
||||||
|
annotation = tal_fmt(tmpctx, "array parameter #%s", alt->fieldname+4);
|
||||||
|
} else if (strstarts(alt->fieldname, "pname"))
|
||||||
|
annotation = tal_fmt(tmpctx, "object parameter '%s'", alt->fieldname+5);
|
||||||
|
else
|
||||||
|
annotation = "unknown condition?";
|
||||||
|
|
||||||
|
tal_append_fmt(&summary, "%s", sep);
|
||||||
|
|
||||||
|
/* Where it's ambiguous, quote if it's not treated as an int */
|
||||||
|
if (int_val)
|
||||||
|
value = alt->value;
|
||||||
|
else if (time_val) {
|
||||||
|
u64 t = atol(alt->value);
|
||||||
|
|
||||||
|
if (t) {
|
||||||
|
u64 diff, now = time_now().ts.tv_sec;
|
||||||
|
/* Need a non-const during construction */
|
||||||
|
char *v;
|
||||||
|
|
||||||
|
if (now > t)
|
||||||
|
diff = now - t;
|
||||||
|
else
|
||||||
|
diff = t - now;
|
||||||
|
if (diff < 60)
|
||||||
|
v = tal_fmt(tmpctx, "%"PRIu64" seconds", diff);
|
||||||
|
else if (diff < 60 * 60)
|
||||||
|
v = tal_fmt(tmpctx, "%"PRIu64" minutes %"PRIu64" seconds",
|
||||||
|
diff / 60, diff % 60);
|
||||||
|
else {
|
||||||
|
v = tal_strdup(tmpctx, "approximately ");
|
||||||
|
/* diff is in minutes */
|
||||||
|
diff /= 60;
|
||||||
|
if (diff < 48 * 60)
|
||||||
|
tal_append_fmt(&v, "%"PRIu64" hours %"PRIu64" minutes",
|
||||||
|
diff / 60, diff % 60);
|
||||||
|
else {
|
||||||
|
/* hours */
|
||||||
|
diff /= 60;
|
||||||
|
if (diff < 60 * 24)
|
||||||
|
tal_append_fmt(&v, "%"PRIu64" days %"PRIu64" hours",
|
||||||
|
diff / 24, diff % 24);
|
||||||
|
else {
|
||||||
|
/* days */
|
||||||
|
diff /= 24;
|
||||||
|
if (diff < 365 * 2)
|
||||||
|
tal_append_fmt(&v, "%"PRIu64" months %"PRIu64" days",
|
||||||
|
diff / 30, diff % 30);
|
||||||
|
else {
|
||||||
|
/* months */
|
||||||
|
diff /= 30;
|
||||||
|
tal_append_fmt(&v, "%"PRIu64" years %"PRIu64" months",
|
||||||
|
diff / 12, diff % 12);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (now > t)
|
||||||
|
tal_append_fmt(&v, " ago");
|
||||||
|
else
|
||||||
|
tal_append_fmt(&v, " from now");
|
||||||
|
value = tal_fmt(tmpctx, "%s (%s)", alt->value, v);
|
||||||
|
} else
|
||||||
|
value = alt->value;
|
||||||
|
} else
|
||||||
|
value = tal_fmt(tmpctx, "'%s'", alt->value);
|
||||||
|
|
||||||
|
switch (alt->condition) {
|
||||||
|
case RUNE_COND_IF_MISSING:
|
||||||
|
tal_append_fmt(&summary, "%s (%s) is missing", alt->fieldname, annotation);
|
||||||
|
break;
|
||||||
|
case RUNE_COND_EQUAL:
|
||||||
|
tal_append_fmt(&summary, "%s (%s) equal to %s", alt->fieldname, annotation, value);
|
||||||
|
break;
|
||||||
|
case RUNE_COND_NOT_EQUAL:
|
||||||
|
tal_append_fmt(&summary, "%s (%s) unequal to %s", alt->fieldname, annotation, value);
|
||||||
|
break;
|
||||||
|
case RUNE_COND_BEGINS:
|
||||||
|
tal_append_fmt(&summary, "%s (%s) starts with '%s'", alt->fieldname, annotation, alt->value);
|
||||||
|
break;
|
||||||
|
case RUNE_COND_ENDS:
|
||||||
|
tal_append_fmt(&summary, "%s (%s) ends with '%s'", alt->fieldname, annotation, alt->value);
|
||||||
|
break;
|
||||||
|
case RUNE_COND_CONTAINS:
|
||||||
|
tal_append_fmt(&summary, "%s (%s) contains '%s'", alt->fieldname, annotation, alt->value);
|
||||||
|
break;
|
||||||
|
case RUNE_COND_INT_LESS:
|
||||||
|
tal_append_fmt(&summary, "%s (%s) less than %s", alt->fieldname, annotation,
|
||||||
|
time_val ? value : alt->value);
|
||||||
|
break;
|
||||||
|
case RUNE_COND_INT_GREATER:
|
||||||
|
tal_append_fmt(&summary, "%s (%s) greater than %s", alt->fieldname, annotation,
|
||||||
|
time_val ? value : alt->value);
|
||||||
|
break;
|
||||||
|
case RUNE_COND_LEXO_BEFORE:
|
||||||
|
tal_append_fmt(&summary, "%s (%s) sorts before '%s'", alt->fieldname, annotation, alt->value);
|
||||||
|
break;
|
||||||
|
case RUNE_COND_LEXO_AFTER:
|
||||||
|
tal_append_fmt(&summary, "%s (%s) sorts after '%s'", alt->fieldname, annotation, alt->value);
|
||||||
|
break;
|
||||||
|
case RUNE_COND_COMMENT:
|
||||||
|
tal_append_fmt(&summary, "[comment: %s%s]", alt->fieldname, alt->value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sep = " OR ";
|
||||||
|
json_add_str_fmt(js, NULL, "%s%c%s", alt->fieldname, alt->condition, alt->value);
|
||||||
|
}
|
||||||
|
json_array_end(js);
|
||||||
|
json_add_string(js, "summary", summary);
|
||||||
|
json_object_end(js);
|
||||||
|
}
|
||||||
|
json_array_end(js);
|
||||||
|
/* FIXME: do some sanity checks? */
|
||||||
|
json_add_bool(js, "valid", true);
|
||||||
|
}
|
||||||
|
|
||||||
static struct command_result *json_decode(struct command *cmd,
|
static struct command_result *json_decode(struct command *cmd,
|
||||||
const char *buffer,
|
const char *buffer,
|
||||||
const jsmntok_t *params)
|
const jsmntok_t *params)
|
||||||
|
@ -833,6 +992,8 @@ static struct command_result *json_decode(struct command *cmd,
|
||||||
json_add_bolt11(response, decodable->b11);
|
json_add_bolt11(response, decodable->b11);
|
||||||
json_add_bool(response, "valid", true);
|
json_add_bool(response, "valid", true);
|
||||||
}
|
}
|
||||||
|
if (decodable->rune)
|
||||||
|
json_add_rune(cmd, response, decodable->rune);
|
||||||
return command_finished(cmd, response);
|
return command_finished(cmd, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2673,6 +2673,59 @@ def test_commando_rune(node_factory):
|
||||||
assert rune9['rune'] == 'O8Zr-ULTBKO3_pKYz0QKE9xYl1vQ4Xx9PtlHuist9Rk9NCZwbnVtPTAmcmF0ZT0zJnJhdGU9MQ=='
|
assert rune9['rune'] == 'O8Zr-ULTBKO3_pKYz0QKE9xYl1vQ4Xx9PtlHuist9Rk9NCZwbnVtPTAmcmF0ZT0zJnJhdGU9MQ=='
|
||||||
assert rune9['unique_id'] == '4'
|
assert rune9['unique_id'] == '4'
|
||||||
|
|
||||||
|
runedecodes = ((rune1, []),
|
||||||
|
(rune2, [{'alternatives': ['method^list', 'method^get', 'method=summary'],
|
||||||
|
'summary': "method (of command) starts with 'list' OR method (of command) starts with 'get' OR method (of command) equal to 'summary'"},
|
||||||
|
{'alternatives': ['method/listdatastore'],
|
||||||
|
'summary': "method (of command) unequal to 'listdatastore'"}]),
|
||||||
|
(rune4, [{'alternatives': ['id^022d223620a359a47ff7'],
|
||||||
|
'summary': "id (of commanding peer) starts with '022d223620a359a47ff7'"},
|
||||||
|
{'alternatives': ['method=listpeers'],
|
||||||
|
'summary': "method (of command) equal to 'listpeers'"}]),
|
||||||
|
(rune5, [{'alternatives': ['id^022d223620a359a47ff7'],
|
||||||
|
'summary': "id (of commanding peer) starts with '022d223620a359a47ff7'"},
|
||||||
|
{'alternatives': ['method=listpeers'],
|
||||||
|
'summary': "method (of command) equal to 'listpeers'"},
|
||||||
|
{'alternatives': ['pnamelevel!', 'pnamelevel/io'],
|
||||||
|
'summary': "pnamelevel (object parameter 'level') is missing OR pnamelevel (object parameter 'level') unequal to 'io'"}]),
|
||||||
|
(rune6, [{'alternatives': ['id^022d223620a359a47ff7'],
|
||||||
|
'summary': "id (of commanding peer) starts with '022d223620a359a47ff7'"},
|
||||||
|
{'alternatives': ['method=listpeers'],
|
||||||
|
'summary': "method (of command) equal to 'listpeers'"},
|
||||||
|
{'alternatives': ['pnamelevel!', 'pnamelevel/io'],
|
||||||
|
'summary': "pnamelevel (object parameter 'level') is missing OR pnamelevel (object parameter 'level') unequal to 'io'"},
|
||||||
|
{'alternatives': ['parr1!', 'parr1/io'],
|
||||||
|
'summary': "parr1 (array parameter #1) is missing OR parr1 (array parameter #1) unequal to 'io'"}]),
|
||||||
|
(rune7, [{'alternatives': ['pnum=0'],
|
||||||
|
'summary': "pnum (number of command parameters) equal to 0"}]),
|
||||||
|
(rune8, [{'alternatives': ['pnum=0'],
|
||||||
|
'summary': "pnum (number of command parameters) equal to 0"},
|
||||||
|
{'alternatives': ['rate=3'],
|
||||||
|
'summary': "rate (max per minute) equal to 3"}]),
|
||||||
|
(rune9, [{'alternatives': ['pnum=0'],
|
||||||
|
'summary': "pnum (number of command parameters) equal to 0"},
|
||||||
|
{'alternatives': ['rate=3'],
|
||||||
|
'summary': "rate (max per minute) equal to 3"},
|
||||||
|
{'alternatives': ['rate=1'],
|
||||||
|
'summary': "rate (max per minute) equal to 1"}]))
|
||||||
|
for decode in runedecodes:
|
||||||
|
rune = decode[0]
|
||||||
|
restrictions = decode[1]
|
||||||
|
decoded = l1.rpc.decode(rune['rune'])
|
||||||
|
assert decoded['type'] == 'rune'
|
||||||
|
assert decoded['unique_id'] == rune['unique_id']
|
||||||
|
assert decoded['valid'] is True
|
||||||
|
assert decoded['restrictions'] == restrictions
|
||||||
|
|
||||||
|
# Time handling is a bit special, since we annotate the timestamp with how far away it is.
|
||||||
|
decoded = l1.rpc.decode(rune3['rune'])
|
||||||
|
assert decoded['type'] == 'rune'
|
||||||
|
assert decoded['unique_id'] == rune3['unique_id']
|
||||||
|
assert decoded['valid'] is True
|
||||||
|
assert len(decoded['restrictions']) == 1
|
||||||
|
assert decoded['restrictions'][0]['alternatives'] == ['time>1656675211']
|
||||||
|
assert decoded['restrictions'][0]['summary'].startswith("time (in seconds since 1970) greater than 1656675211 (")
|
||||||
|
|
||||||
# Replace rune3 with a more useful timestamp!
|
# Replace rune3 with a more useful timestamp!
|
||||||
expiry = int(time.time()) + 15
|
expiry = int(time.time()) + 15
|
||||||
rune3 = l1.rpc.commando_rune(restrictions="time<{}".format(expiry))
|
rune3 = l1.rpc.commando_rune(restrictions="time<{}".format(expiry))
|
||||||
|
|
Loading…
Add table
Reference in a new issue