Browse Source

lightningd/peer_control.c: Implement waitblockheight.

This is needed to fully implement handling of blockheight disagreements
between us and payee.
If payee believes the blockheight is higher than ours, then `pay`
should wait for our node to achieve that blockheight.

Changelog-Add: Implement `waitblockheight` to wait for a specific blockheight.
travis-debug
ZmnSCPxj 5 years ago
committed by Christian Decker
parent
commit
54cc735201
  1. 10
      contrib/pyln-client/pyln/client/lightning.py
  2. 1
      doc/Makefile
  3. 3
      doc/index.rst
  4. 36
      doc/lightning-waitblockheight.7
  5. 37
      doc/lightning-waitblockheight.7.md
  6. 2
      lightningd/lightningd.c
  7. 3
      lightningd/lightningd.h
  8. 107
      lightningd/peer_control.c
  9. 4
      lightningd/peer_control.h
  10. 3
      lightningd/test/run-find_my_abspath.c
  11. 40
      tests/test_misc.py

10
contrib/pyln-client/pyln/client/lightning.py

@ -997,6 +997,16 @@ class LightningRpc(UnixDomainSocketRpc):
}
return self.call("waitanyinvoice", payload)
def waitblockheight(self, blockheight, timeout=None):
"""
Wait for the blockchain to reach the specified block height.
"""
payload = {
"blockheight": blockheight,
"timeout": timeout
}
return self.call("waitblockheight", payload)
def waitinvoice(self, label):
"""
Wait for an incoming payment matching the invoice with {label}

1
doc/Makefile

@ -42,6 +42,7 @@ MANPAGES := doc/lightning-cli.1 \
doc/lightning-txsend.7 \
doc/lightning-waitinvoice.7 \
doc/lightning-waitanyinvoice.7 \
doc/lightning-waitblockheight.7 \
doc/lightning-waitsendpay.7 \
doc/lightning-withdraw.7

3
doc/index.rst

@ -27,8 +27,8 @@ c-lightning Documentation
:maxdepth: 1
:caption: Manpages
lightningd <lightningd.8.md>
lightningd-config <lightningd-config.5.md>
lightningd <lightningd.8.md>
lightning-autocleaninvoice <lightning-autocleaninvoice.7.md>
lightning-check <lightning-check.7.md>
lightning-checkmessage <lightning-checkmessage.7.md>
@ -64,6 +64,7 @@ c-lightning Documentation
lightning-txprepare <lightning-txprepare.7.md>
lightning-txsend <lightning-txsend.7.md>
lightning-waitanyinvoice <lightning-waitanyinvoice.7.md>
lightning-waitblockheight <lightning-waitblockheight.7.md>
lightning-waitinvoice <lightning-waitinvoice.7.md>
lightning-waitsendpay <lightning-waitsendpay.7.md>
lightning-withdraw <lightning-withdraw.7.md>

36
doc/lightning-waitblockheight.7

@ -0,0 +1,36 @@
.TH "LIGHTNING-WAITBLOCKHEIGHT" "7" "" "" "lightning-waitblockheight"
.SH NAME
lightning-waitblockheight -- Command for waiting for blocks on the blockchain
lightning-waitblockheight -- Command for waiting for blocks on the blockchain
.SH SYNOPSIS
\fBwaitblockheight\fR \fIblockheight\fR [\fItimeout\fR]
.SH DESCRIPTION
The \fBwaitblockheight\fR RPC command waits until the blockchain
has reached the specified \fIblockheight\fR.
It will only wait up to \fItimeout\fR seconds (default 60).
If the \fIblockheight\fR is a present or past block height, then this
command returns immediately.
.SH RETURN VALUE
Once the specified block height has been achieved by the blockchain,
an object with the single field \fIblockheight\fR is returned, which is
the block height at the time the command returns.
If \fItimeout\fR seconds is reached without the specified blockheight
being reached, this command will fail.
.SH AUTHOR
ZmnSCPxj <\fIZmnSCPxj@protonmail.com\fR> is mainly responsible.
.SH RESOURCES
Main web site: \fIhttps://github.com/ElementsProject/lightning\fR

37
doc/lightning-waitblockheight.7.md

@ -0,0 +1,37 @@
lightning-waitblockheight -- Command for waiting for blocks on the blockchain
=============================================================================
SYNOPSIS
--------
**waitblockheight** *blockheight* \[*timeout*\]
DESCRIPTION
-----------
The **waitblockheight** RPC command waits until the blockchain
has reached the specified *blockheight*.
It will only wait up to *timeout* seconds (default 60).
If the *blockheight* is a present or past block height, then this
command returns immediately.
RETURN VALUE
------------
Once the specified block height has been achieved by the blockchain,
an object with the single field *blockheight* is returned, which is
the block height at the time the command returns.
If *timeout* seconds is reached without the specified blockheight
being reached, this command will fail.
AUTHOR
------
ZmnSCPxj <<ZmnSCPxj@protonmail.com>> is mainly responsible.
RESOURCES
---------
Main web site: <https://github.com/ElementsProject/lightning>

2
lightningd/lightningd.c

@ -185,6 +185,7 @@ static struct lightningd *new_lightningd(const tal_t *ctx)
list_head_init(&ld->sendpay_commands);
list_head_init(&ld->close_commands);
list_head_init(&ld->ping_commands);
list_head_init(&ld->waitblockheight_commands);
/*~ Tal also explicitly supports arrays: it stores the number of
* elements, which can be accessed with tal_count() (or tal_bytelen()
@ -597,6 +598,7 @@ void notify_new_block(struct lightningd *ld, u32 block_height)
htlcs_notify_new_block(ld, block_height);
channel_notify_new_block(ld, block_height);
gossip_notify_new_block(ld, block_height);
waitblockheight_notify_new_block(ld, block_height);
}
static void on_sigint(int _ UNUSED)

3
lightningd/lightningd.h

@ -251,6 +251,9 @@ struct lightningd {
bool encrypted_hsm;
mode_t initial_umask;
/* Outstanding waitblockheight commands. */
struct list_head waitblockheight_commands;
};
/* Turning this on allows a tal allocation to return NULL, rather than aborting.

107
lightningd/peer_control.c

@ -21,6 +21,7 @@
#include <common/initial_commit_tx.h>
#include <common/json_command.h>
#include <common/json_helpers.h>
#include <common/json_tok.h>
#include <common/jsonrpc_errors.h>
#include <common/key_derive.h>
#include <common/param.h>
@ -1750,6 +1751,112 @@ static const struct json_command getinfo_command = {
};
AUTODATA(json_command, &getinfo_command);
/* Wait for at least a specific blockheight, then return, or time out. */
struct waitblockheight_waiter {
/* struct lightningd::waitblockheight_commands. */
struct list_node list;
/* Command structure. This is the parent of the close command. */
struct command *cmd;
/* The block height being waited for. */
u32 block_height;
/* Whether we have been removed from the list. */
bool removed;
};
/* Completes a pending waitblockheight. */
static struct command_result *
waitblockheight_complete(struct command *cmd,
u32 block_height)
{
struct json_stream *response;
response = json_stream_success(cmd);
json_add_num(response, "blockheight", block_height);
return command_success(cmd, response);
}
/* Called when command is destroyed without being resolved. */
static void
destroy_waitblockheight_waiter(struct waitblockheight_waiter *w)
{
if (!w->removed)
list_del(&w->list);
}
/* Called on timeout. */
static void
timeout_waitblockheight_waiter(struct waitblockheight_waiter *w)
{
list_del(&w->list);
w->removed = true;
tal_steal(tmpctx, w);
was_pending(command_fail(w->cmd, LIGHTNINGD,
"Timed out."));
}
/* Called by lightningd at each new block. */
void waitblockheight_notify_new_block(struct lightningd *ld,
u32 block_height)
{
struct waitblockheight_waiter *w, *n;
char *to_delete = tal(NULL, char);
/* Use safe since we could resolve commands and thus
* trigger removal of list elements.
*/
list_for_each_safe(&ld->waitblockheight_commands, w, n, list) {
/* Skip commands that have not been reached yet. */
if (w->block_height > block_height)
continue;
list_del(&w->list);
w->removed = true;
tal_steal(to_delete, w);
was_pending(waitblockheight_complete(w->cmd,
block_height));
}
tal_free(to_delete);
}
static struct command_result *json_waitblockheight(struct command *cmd,
const char *buffer,
const jsmntok_t *obj,
const jsmntok_t *params)
{
unsigned int *target_block_height;
u32 block_height;
unsigned int *timeout;
struct waitblockheight_waiter *w;
if (!param(cmd, buffer, params,
p_req("blockheight", param_number, &target_block_height),
p_opt_def("timeout", param_number, &timeout, 60),
NULL))
return command_param_failed();
/* Check if already reached anyway. */
block_height = get_block_height(cmd->ld->topology);
if (*target_block_height <= block_height)
return waitblockheight_complete(cmd, block_height);
/* Create a new waitblockheight command. */
w = tal(cmd, struct waitblockheight_waiter);
tal_add_destructor(w, &destroy_waitblockheight_waiter);
list_add(&cmd->ld->waitblockheight_commands, &w->list);
w->cmd = cmd;
w->block_height = *target_block_height;
w->removed = false;
/* Install the timeout. */
(void) new_reltimer(cmd->ld->timers, w, time_from_sec(*timeout),
&timeout_waitblockheight_waiter, w);
return command_still_pending(cmd);
}
static const struct json_command waitblockheight_command = {
"waitblockheight",
"utility",
&json_waitblockheight,
"Wait for the blockchain to reach {blockheight}, up to "
"{timeout} seconds."
};
AUTODATA(json_command, &waitblockheight_command);
static struct command_result *param_channel_or_all(struct command *cmd,
const char *name,
const char *buffer,

4
lightningd/peer_control.h

@ -95,4 +95,8 @@ struct htlc_in_map *load_channels_from_wallet(struct lightningd *ld);
void peer_dev_memleak(struct command *cmd);
#endif /* DEVELOPER */
/* Triggered at each new block. */
void waitblockheight_notify_new_block(struct lightningd *ld,
u32 block_height);
#endif /* LIGHTNING_LIGHTNINGD_PEER_CONTROL_H */

3
lightningd/test/run-find_my_abspath.c

@ -192,6 +192,9 @@ bool wallet_network_check(struct wallet *w UNNEEDED)
/* Generated stub for wallet_new */
struct wallet *wallet_new(struct lightningd *ld UNNEEDED, struct timers *timers UNNEEDED)
{ fprintf(stderr, "wallet_new called!\n"); abort(); }
/* Generated stub for waitblockheight_notify_new_block */
void waitblockheight_notify_new_block(struct lightningd *ld UNNEEDED, u32 blockheight UNNEEDED)
{ fprintf(stderr, "waitblockheight_notify_new_block called!\n"); abort(); }
/* AUTOGENERATED MOCKS END */
struct log *crashlog;

40
tests/test_misc.py

@ -1993,7 +1993,7 @@ def test_new_node_is_mainnet(node_factory):
assert os.path.isfile(os.path.join(basedir, "lightningd-bitcoin.pid"))
def test_unicode_rpc(node_factory):
def test_unicode_rpc(node_factory, executor, bitcoind):
node = node_factory.get_node()
desc = "Some candy 🍬 and a nice glass of milk 🥛."
@ -2019,3 +2019,41 @@ def test_unix_socket_path_length(node_factory, bitcoind, directory, executor, db
# Let's just call it again to make sure it really works.
l1.rpc.listconfigs()
l1.stop()
def test_waitblockheight(node_factory, executor, bitcoind):
node = node_factory.get_node()
sync_blockheight(bitcoind, [node])
blockheight = node.rpc.getinfo()['blockheight']
# Should succeed without waiting.
node.rpc.waitblockheight(blockheight - 2)
node.rpc.waitblockheight(blockheight - 1)
node.rpc.waitblockheight(blockheight)
# Should not succeed yet.
fut2 = executor.submit(node.rpc.waitblockheight, blockheight + 2)
fut1 = executor.submit(node.rpc.waitblockheight, blockheight + 1)
assert not fut1.done()
assert not fut2.done()
# Should take about ~1second and time out.
with pytest.raises(RpcError):
node.rpc.waitblockheight(blockheight + 2, 1)
# Others should still not be done.
assert not fut1.done()
assert not fut2.done()
# Trigger just one more block.
bitcoind.generate_block(1)
sync_blockheight(bitcoind, [node])
fut1.result(5)
assert not fut2.done()
# Trigger two blocks.
bitcoind.generate_block(1)
sync_blockheight(bitcoind, [node])
fut2.result(5)

Loading…
Cancel
Save