mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-02-21 14:24:09 +01:00
Fixup
This commit is contained in:
parent
46ba180f94
commit
4ba8996ea4
9 changed files with 80 additions and 81 deletions
|
@ -17,37 +17,36 @@ tlvdata,scb_tlvs,opener,opener,enum side,
|
|||
tlvtype,scb_tlvs,remote_to_self_delay,7,
|
||||
tlvdata,scb_tlvs,remote_to_self_delay,remote_to_self_delay,u16,
|
||||
|
||||
# scb_chan stores min. info required to sweep the peer's force close.
|
||||
subtype,scb_chan
|
||||
subtypedata,scb_chan,id,u64,
|
||||
subtypedata,scb_chan,cid,channel_id,
|
||||
subtypedata,scb_chan,node_id,node_id,
|
||||
subtypedata,scb_chan,unused,u8,
|
||||
subtypedata,scb_chan,addr,wireaddr,
|
||||
subtypedata,scb_chan,funding,bitcoin_outpoint,
|
||||
subtypedata,scb_chan,funding_sats,amount_sat,
|
||||
subtypedata,scb_chan,type,channel_type,
|
||||
# legacy_scb_chan stores min. info required to sweep the peer's force close.
|
||||
subtype,legacy_scb_chan
|
||||
subtypedata,legacy_scb_chan,id,u64,
|
||||
subtypedata,legacy_scb_chan,cid,channel_id,
|
||||
subtypedata,legacy_scb_chan,node_id,node_id,
|
||||
subtypedata,legacy_scb_chan,unused,u8,
|
||||
subtypedata,legacy_scb_chan,addr,wireaddr,
|
||||
subtypedata,legacy_scb_chan,funding,bitcoin_outpoint,
|
||||
subtypedata,legacy_scb_chan,funding_sats,amount_sat,
|
||||
subtypedata,legacy_scb_chan,type,channel_type,
|
||||
|
||||
msgtype,static_chan_backup,6135,
|
||||
msgdata,static_chan_backup,version,u64,
|
||||
msgdata,static_chan_backup,timestamp,u32,
|
||||
msgdata,static_chan_backup,num,u16,
|
||||
msgdata,static_chan_backup,channels,scb_chan,num
|
||||
msgdata,static_chan_backup,channels,legacy_scb_chan,num
|
||||
|
||||
subtype,scb_chan_with_tlvs
|
||||
subtypedata,scb_chan_with_tlvs,id,u64,
|
||||
subtypedata,scb_chan_with_tlvs,cid,channel_id,
|
||||
subtypedata,scb_chan_with_tlvs,node_id,node_id,
|
||||
subtypedata,scb_chan_with_tlvs,unused,u8,
|
||||
subtypedata,scb_chan_with_tlvs,addr,wireaddr,
|
||||
subtypedata,scb_chan_with_tlvs,funding,bitcoin_outpoint,
|
||||
subtypedata,scb_chan_with_tlvs,funding_sats,amount_sat,
|
||||
subtypedata,scb_chan_with_tlvs,type,channel_type,
|
||||
subtypedata,scb_chan_with_tlvs,len_tlv,u32,
|
||||
subtypedata,scb_chan_with_tlvs,tlvs,scb_tlvs,len_tlv
|
||||
subtype,modern_scb_chan
|
||||
subtypedata,modern_scb_chan,id,u64,
|
||||
subtypedata,modern_scb_chan,cid,channel_id,
|
||||
subtypedata,modern_scb_chan,node_id,node_id,
|
||||
subtypedata,modern_scb_chan,addr,wireaddr,
|
||||
subtypedata,modern_scb_chan,funding,bitcoin_outpoint,
|
||||
subtypedata,modern_scb_chan,funding_sats,amount_sat,
|
||||
subtypedata,modern_scb_chan,type,channel_type,
|
||||
subtypedata,modern_scb_chan,len_tlv,u32,
|
||||
subtypedata,modern_scb_chan,tlvs,scb_tlvs,len_tlv
|
||||
|
||||
msgtype,static_chan_backup_with_tlvs,6137,
|
||||
msgdata,static_chan_backup_with_tlvs,version,u64,
|
||||
msgdata,static_chan_backup_with_tlvs,timestamp,u32,
|
||||
msgdata,static_chan_backup_with_tlvs,num,u16,
|
||||
msgdata,static_chan_backup_with_tlvs,channels,scb_chan_with_tlvs,num
|
||||
msgdata,static_chan_backup_with_tlvs,channels,modern_scb_chan,num
|
||||
|
|
Can't render this file because it has a wrong number of fields in line 11.
|
|
@ -486,9 +486,8 @@ struct channel *new_channel(struct peer *peer, u64 dbid,
|
|||
|
||||
/* If it's a unix domain socket connection, we don't save it */
|
||||
if (peer->addr.itype == ADDR_INTERNAL_WIREADDR) {
|
||||
channel->scb = tal(channel, struct scb_chan_with_tlvs);
|
||||
channel->scb = tal(channel, struct modern_scb_chan);
|
||||
channel->scb->id = dbid;
|
||||
channel->scb->unused = 0;
|
||||
/* More useful to have last_known_addr, if avail */
|
||||
if (peer->last_known_addr)
|
||||
channel->scb->addr = *peer->last_known_addr;
|
||||
|
|
|
@ -334,7 +334,7 @@ struct channel {
|
|||
|
||||
/* `Channel-shell` of this channel
|
||||
* (Minimum information required to backup this channel). */
|
||||
struct scb_chan_with_tlvs *scb;
|
||||
struct modern_scb_chan *scb;
|
||||
|
||||
/* Do we allow the peer to set any fee it wants? */
|
||||
bool ignore_fee_limits;
|
||||
|
|
|
@ -1462,9 +1462,8 @@ wallet_commit_channel(struct lightningd *ld,
|
|||
channel->min_possible_feerate = commitment_feerate;
|
||||
channel->max_possible_feerate = commitment_feerate;
|
||||
if (channel->peer->addr.itype == ADDR_INTERNAL_WIREADDR) {
|
||||
channel->scb = tal(channel, struct scb_chan_with_tlvs);
|
||||
channel->scb = tal(channel, struct modern_scb_chan);
|
||||
channel->scb->id = channel->dbid;
|
||||
channel->scb->unused = 0;
|
||||
channel->scb->addr = channel->peer->addr.u.wireaddr.wireaddr;
|
||||
channel->scb->node_id = channel->peer->id;
|
||||
channel->scb->funding = *funding;
|
||||
|
|
|
@ -1632,7 +1632,7 @@ static struct command_result *json_recoverchannel(struct command *cmd,
|
|||
const jsmntok_t *scb, *t;
|
||||
size_t i;
|
||||
struct json_stream *response;
|
||||
struct scb_chan_with_tlvs *scb_chan = tal(cmd, struct scb_chan_with_tlvs);
|
||||
struct modern_scb_chan *scb_chan = tal(cmd, struct modern_scb_chan);
|
||||
|
||||
if (!param(cmd, buffer, params,
|
||||
p_req("scb", param_array, &scb),
|
||||
|
@ -1653,7 +1653,7 @@ static struct command_result *json_recoverchannel(struct command *cmd,
|
|||
enum side opener = LOCAL;
|
||||
u16 remote_to_self_delay = 0;
|
||||
|
||||
scb_chan = fromwire_scb_chan_with_tlvs(cmd, &scb_arr, &scblen);
|
||||
scb_chan = fromwire_modern_scb_chan(cmd, &scb_arr, &scblen);
|
||||
|
||||
if (scb_chan == NULL) {
|
||||
log_broken(cmd->ld->log, "SCB is invalid!");
|
||||
|
|
|
@ -2371,7 +2371,7 @@ static void json_add_scb(struct command *cmd,
|
|||
/* Update shachain & basepoints in SCB. */
|
||||
c->scb->tlvs->shachain = &c->their_shachain.chain;
|
||||
c->scb->tlvs->basepoints = &c->channel_info.theirbase;
|
||||
towire_scb_chan_with_tlvs(&scb, c->scb);
|
||||
towire_modern_scb_chan(&scb, c->scb);
|
||||
|
||||
json_add_hex_talarr(response, fieldname,
|
||||
scb);
|
||||
|
|
|
@ -1012,6 +1012,9 @@ u8 *towire_hsmd_sign_commitment_tx(const tal_t *ctx UNNEEDED, const struct node_
|
|||
/* Generated stub for towire_hsmd_sign_invoice */
|
||||
u8 *towire_hsmd_sign_invoice(const tal_t *ctx UNNEEDED, const u8 *u5bytes UNNEEDED, const u8 *hrp UNNEEDED)
|
||||
{ fprintf(stderr, "towire_hsmd_sign_invoice called!\n"); abort(); }
|
||||
/* Generated stub for towire_modern_scb_chan */
|
||||
void towire_modern_scb_chan(u8 **p UNNEEDED, const struct modern_scb_chan *modern_scb_chan UNNEEDED)
|
||||
{ fprintf(stderr, "towire_modern_scb_chan called!\n"); abort(); }
|
||||
/* Generated stub for towire_node_id */
|
||||
void towire_node_id(u8 **pptr UNNEEDED, const struct node_id *id UNNEEDED)
|
||||
{ fprintf(stderr, "towire_node_id called!\n"); abort(); }
|
||||
|
@ -1021,9 +1024,6 @@ u8 *towire_onchaind_dev_memleak(const tal_t *ctx UNNEEDED)
|
|||
/* Generated stub for towire_openingd_dev_memleak */
|
||||
u8 *towire_openingd_dev_memleak(const tal_t *ctx UNNEEDED)
|
||||
{ fprintf(stderr, "towire_openingd_dev_memleak called!\n"); abort(); }
|
||||
/* Generated stub for towire_scb_chan_with_tlvs */
|
||||
void towire_scb_chan_with_tlvs(u8 **p UNNEEDED, const struct scb_chan_with_tlvs *scb_chan_with_tlvs UNNEEDED)
|
||||
{ fprintf(stderr, "towire_scb_chan_with_tlvs called!\n"); abort(); }
|
||||
/* Generated stub for towire_warningfmt */
|
||||
u8 *towire_warningfmt(const tal_t *ctx UNNEEDED,
|
||||
const struct channel_id *channel UNNEEDED,
|
||||
|
|
|
@ -34,12 +34,12 @@ static bool peer_backup;
|
|||
/* Helper to fetch out SCB from the RPC call */
|
||||
static bool json_to_scb_chan(const char *buffer,
|
||||
const jsmntok_t *tok,
|
||||
struct scb_chan_with_tlvs ***channels)
|
||||
struct modern_scb_chan ***channels)
|
||||
{
|
||||
size_t i;
|
||||
const jsmntok_t *t;
|
||||
*channels = tok->size ? tal_arr(tmpctx,
|
||||
struct scb_chan_with_tlvs *,
|
||||
struct modern_scb_chan *,
|
||||
tok->size) : NULL;
|
||||
|
||||
json_for_each_arr(i, t, tok) {
|
||||
|
@ -52,7 +52,7 @@ static bool json_to_scb_chan(const char *buffer,
|
|||
t)));
|
||||
size_t scblen_tmp = tal_count(scb_tmp);
|
||||
|
||||
(*channels)[i] = fromwire_scb_chan_with_tlvs(tmpctx,
|
||||
(*channels)[i] = fromwire_modern_scb_chan(tmpctx,
|
||||
&scb_tmp,
|
||||
&scblen_tmp);
|
||||
}
|
||||
|
@ -63,14 +63,14 @@ static bool json_to_scb_chan(const char *buffer,
|
|||
/* This writes encrypted static backup in the recovery file */
|
||||
static void write_scb(struct plugin *p,
|
||||
int fd,
|
||||
struct scb_chan_with_tlvs **scb_chan_arr)
|
||||
struct modern_scb_chan **scb_chan_arr)
|
||||
{
|
||||
u32 timestamp = time_now().ts.tv_sec;
|
||||
|
||||
u8 *decrypted_scb = towire_static_chan_backup_with_tlvs(tmpctx,
|
||||
VERSION,
|
||||
timestamp,
|
||||
cast_const2(const struct scb_chan_with_tlvs **,
|
||||
cast_const2(const struct modern_scb_chan **,
|
||||
scb_chan_arr));
|
||||
|
||||
u8 *encrypted_scb = tal_arr(tmpctx,
|
||||
|
@ -110,7 +110,7 @@ static void write_scb(struct plugin *p,
|
|||
|
||||
/* checks if the SCB file exists, creates a new one in case it doesn't. */
|
||||
static void maybe_create_new_scb(struct plugin *p,
|
||||
struct scb_chan_with_tlvs **channels)
|
||||
struct modern_scb_chan **channels)
|
||||
{
|
||||
|
||||
/* Note that this is opened for write-only, even though the permissions
|
||||
|
@ -234,6 +234,19 @@ static struct command_result *after_recover_rpc(struct command *cmd,
|
|||
return command_finished(cmd, response);
|
||||
}
|
||||
|
||||
static struct modern_scb_chan *convert_from_legacy(const tal_t *ctx, struct legacy_scb_chan *legacy_scb_chan) {
|
||||
struct modern_scb_chan *modern_scb_tlv = tal(ctx, struct modern_scb_chan);
|
||||
modern_scb_tlv->id = legacy_scb_chan->id;
|
||||
modern_scb_tlv->addr = legacy_scb_chan->addr;
|
||||
modern_scb_tlv->node_id = legacy_scb_chan->node_id;
|
||||
modern_scb_tlv->cid = legacy_scb_chan->cid;
|
||||
modern_scb_tlv->funding = legacy_scb_chan->funding;
|
||||
modern_scb_tlv->funding_sats = legacy_scb_chan->funding_sats;
|
||||
modern_scb_tlv->type = legacy_scb_chan->type;
|
||||
modern_scb_tlv->tlvs = tlv_scb_tlvs_new(ctx);
|
||||
return modern_scb_tlv;
|
||||
}
|
||||
|
||||
/* Recovers the channels by making RPC to `recoverchannel` */
|
||||
static struct command_result *json_emergencyrecover(struct command *cmd,
|
||||
const char *buf,
|
||||
|
@ -242,8 +255,8 @@ static struct command_result *json_emergencyrecover(struct command *cmd,
|
|||
struct out_req *req;
|
||||
u64 version;
|
||||
u32 timestamp;
|
||||
struct scb_chan **scb;
|
||||
struct scb_chan_with_tlvs **scb_tlvs;
|
||||
struct legacy_scb_chan **scb;
|
||||
struct modern_scb_chan **scb_tlvs;
|
||||
|
||||
if (!param(cmd, buf, params, NULL))
|
||||
return command_param_failed();
|
||||
|
@ -265,9 +278,9 @@ static struct command_result *json_emergencyrecover(struct command *cmd,
|
|||
is_tlvs = true;
|
||||
}
|
||||
|
||||
if (version != VERSION) {
|
||||
if ((version & 0x5555555555555555ULL) != (VERSION & 0x5555555555555555ULL)) {
|
||||
plugin_err(cmd->plugin,
|
||||
"Incompatible SCB file version on disk, contact the admin!");
|
||||
"Incompatible emergencyrecover version: loaded version %"PRIu64", expected version %"PRIu64". Contact the admin!", version, VERSION);
|
||||
}
|
||||
|
||||
req = jsonrpc_request_start(cmd, "recoverchannel",
|
||||
|
@ -278,28 +291,19 @@ static struct command_result *json_emergencyrecover(struct command *cmd,
|
|||
if (is_tlvs) {
|
||||
for (size_t i=0; i<tal_count(scb_tlvs); i++) {
|
||||
u8 *scb_hex = tal_arr(cmd, u8, 0);
|
||||
towire_scb_chan_with_tlvs(&scb_hex,scb_tlvs[i]);
|
||||
json_add_hex(req->js, NULL, scb_hex, tal_bytelen(scb_hex));
|
||||
towire_modern_scb_chan(&scb_hex,scb_tlvs[i]);
|
||||
json_add_hex_talarr(req->js, NULL, scb_hex);
|
||||
}
|
||||
} else {
|
||||
for (size_t i=0; i<tal_count(scb); i++) {
|
||||
plugin_log(cmd->plugin, LOG_DBG,
|
||||
"Processing legacy emergency.recover file format. "
|
||||
plugin_notify_message(cmd, LOG_DBG, "Processing legacy emergency.recover file format. "
|
||||
"Please migrate to the latest file format for improved "
|
||||
"compatibility and fund recovery.");
|
||||
|
||||
for (size_t i=0; i<tal_count(scb); i++) {
|
||||
u8 *scb_hex = tal_arr(cmd, u8, 0);
|
||||
struct scb_chan_with_tlvs *tmp_scb_tlv = tal(cmd, struct scb_chan_with_tlvs);
|
||||
tmp_scb_tlv->id = scb[i]->id;
|
||||
tmp_scb_tlv->addr = scb[i]->addr;
|
||||
tmp_scb_tlv->node_id = scb[i]->node_id;
|
||||
tmp_scb_tlv->cid = scb[i]->cid;
|
||||
tmp_scb_tlv->funding = scb[i]->funding;
|
||||
tmp_scb_tlv->funding_sats = scb[i]->funding_sats;
|
||||
tmp_scb_tlv->type = scb[i]->type;
|
||||
tmp_scb_tlv->tlvs = tlv_scb_tlvs_new(cmd);
|
||||
towire_scb_chan_with_tlvs(&scb_hex, tmp_scb_tlv);
|
||||
json_add_hex(req->js, NULL, scb_hex, tal_bytelen(scb_hex));
|
||||
struct modern_scb_chan *tmp_scb = convert_from_legacy(cmd, scb[i]);
|
||||
towire_modern_scb_chan(&scb_hex, tmp_scb);
|
||||
json_add_hex_talarr(req->js, NULL, scb_hex);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -308,7 +312,7 @@ static struct command_result *json_emergencyrecover(struct command *cmd,
|
|||
return send_outreq(req);
|
||||
}
|
||||
|
||||
static void update_scb(struct plugin *p, struct scb_chan_with_tlvs **channels)
|
||||
static void update_scb(struct plugin *p, struct modern_scb_chan **channels)
|
||||
{
|
||||
|
||||
/* If the temp file existed before, remove it */
|
||||
|
@ -394,8 +398,7 @@ static struct command_result *peer_after_listdatastore(struct command *cmd,
|
|||
NULL);
|
||||
|
||||
json_add_node_id(req->js, "node_id", nodeid);
|
||||
json_add_hex(req->js, "msg", payload,
|
||||
tal_bytelen(payload));
|
||||
json_add_hex_talarr(req->js, "msg", payload);
|
||||
|
||||
return send_outreq(req);
|
||||
}
|
||||
|
@ -519,8 +522,7 @@ static struct command_result *after_listpeers(struct command *cmd,
|
|||
info);
|
||||
|
||||
json_add_node_id(req->js, "node_id", &node_id);
|
||||
json_add_hex(req->js, "msg", serialise_scb,
|
||||
tal_bytelen(serialise_scb));
|
||||
json_add_hex_talarr(req->js, "msg", serialise_scb);
|
||||
info->idx++;
|
||||
send_outreq(req);
|
||||
}
|
||||
|
@ -537,7 +539,7 @@ static struct command_result *after_staticbackup(struct command *cmd,
|
|||
const jsmntok_t *params,
|
||||
void *cb_arg UNUSED)
|
||||
{
|
||||
struct scb_chan_with_tlvs **scb_chan;
|
||||
struct modern_scb_chan **scb_chan;
|
||||
const jsmntok_t *scbs = json_get_member(buf, params, "scb");
|
||||
struct out_req *req;
|
||||
json_to_scb_chan(buf, scbs, &scb_chan);
|
||||
|
@ -622,8 +624,7 @@ static struct command_result *peer_connected(struct command *cmd,
|
|||
node_id);
|
||||
|
||||
json_add_node_id(req->js, "node_id", node_id);
|
||||
json_add_hex(req->js, "msg", serialise_scb,
|
||||
tal_bytelen(serialise_scb));
|
||||
json_add_hex_talarr(req->js, "msg", serialise_scb);
|
||||
|
||||
return send_outreq(req);
|
||||
}
|
||||
|
@ -747,8 +748,8 @@ static struct command_result *after_latestscb(struct command *cmd,
|
|||
{
|
||||
u64 version;
|
||||
u32 timestamp;
|
||||
struct scb_chan_with_tlvs **scb_tlvs;
|
||||
struct scb_chan **scb;
|
||||
struct modern_scb_chan **scb_tlvs;
|
||||
struct legacy_scb_chan **scb;
|
||||
struct json_stream *response;
|
||||
struct out_req *req;
|
||||
|
||||
|
@ -776,9 +777,9 @@ static struct command_result *after_latestscb(struct command *cmd,
|
|||
is_tlvs = true;
|
||||
}
|
||||
|
||||
if (version != VERSION) {
|
||||
if ((version & 0x5555555555555555ULL) != (VERSION & 0x5555555555555555ULL)) {
|
||||
plugin_err(cmd->plugin,
|
||||
"Incompatible SCB file version on disk, contact the admin!");
|
||||
"Incompatible emergencyrecover version: loaded version %"PRIu64", expected version %"PRIu64". Contact the admin!", version, VERSION);
|
||||
}
|
||||
|
||||
req = jsonrpc_request_start(cmd, "recoverchannel",
|
||||
|
@ -789,13 +790,13 @@ static struct command_result *after_latestscb(struct command *cmd,
|
|||
if (is_tlvs) {
|
||||
for (size_t i=0; i<tal_count(scb_tlvs); i++) {
|
||||
u8 *scb_hex = tal_arr(cmd, u8, 0);
|
||||
towire_scb_chan_with_tlvs(&scb_hex,scb_tlvs[i]);
|
||||
json_add_hex(req->js, NULL, scb_hex, tal_bytelen(scb_hex));
|
||||
towire_modern_scb_chan(&scb_hex,scb_tlvs[i]);
|
||||
json_add_hex_talarr(req->js, NULL, scb_hex);
|
||||
}
|
||||
} else {
|
||||
for (size_t i=0; i<tal_count(scb); i++) {
|
||||
u8 *scb_hex = tal_arr(cmd, u8, 0);
|
||||
struct scb_chan_with_tlvs *tmp_scb_tlv = tal(cmd, struct scb_chan_with_tlvs);
|
||||
struct modern_scb_chan *tmp_scb_tlv = tal(cmd, struct modern_scb_chan);
|
||||
tmp_scb_tlv->id = scb[i]->id;
|
||||
tmp_scb_tlv->addr = scb[i]->addr;
|
||||
tmp_scb_tlv->cid = scb[i]->cid;
|
||||
|
@ -803,8 +804,8 @@ static struct command_result *after_latestscb(struct command *cmd,
|
|||
tmp_scb_tlv->funding_sats = scb[i]->funding_sats;
|
||||
tmp_scb_tlv->type = scb[i]->type;
|
||||
tmp_scb_tlv->tlvs = tlv_scb_tlvs_new(cmd);
|
||||
towire_scb_chan_with_tlvs(&scb_hex, tmp_scb_tlv);
|
||||
json_add_hex(req->js, NULL, scb_hex, tal_bytelen(scb_hex));
|
||||
towire_modern_scb_chan(&scb_hex, tmp_scb_tlv);
|
||||
json_add_hex_talarr(req->js, NULL, scb_hex);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -839,7 +840,8 @@ static struct command_result *json_getemergencyrecoverdata(struct command *cmd,
|
|||
|
||||
filedata = get_file_data(tmpctx, cmd->plugin);
|
||||
response = jsonrpc_stream_success(cmd);
|
||||
json_add_hex(response, "filedata", filedata, tal_bytelen(filedata));
|
||||
json_add_hex_talarr(response, "filedata", filedata);
|
||||
|
||||
|
||||
return command_finished(cmd, response);
|
||||
}
|
||||
|
@ -865,7 +867,7 @@ static const char *init(struct command *init_cmd,
|
|||
const char *buf UNUSED,
|
||||
const jsmntok_t *config UNUSED)
|
||||
{
|
||||
struct scb_chan_with_tlvs **scb_chan;
|
||||
struct modern_scb_chan **scb_chan;
|
||||
const char *info = "scb secret";
|
||||
u8 *info_hex = tal_dup_arr(tmpctx, u8, (u8*)info, strlen(info), 0);
|
||||
u8 *features;
|
||||
|
|
|
@ -249,8 +249,8 @@ class Type(FieldSet):
|
|||
'tx_parts',
|
||||
'wally_psbt',
|
||||
'wally_tx',
|
||||
'scb_chan',
|
||||
'scb_chan_with_tlvs',
|
||||
'legacy_scb_chan',
|
||||
'modern_scb_chan',
|
||||
'inflight',
|
||||
]
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue