mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-01-18 05:12:45 +01:00
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.
This commit is contained in:
parent
44e8256338
commit
54cc735201
@ -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}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
generated
Normal file
36
doc/lightning-waitblockheight.7
generated
Normal file
@ -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
Normal file
37
doc/lightning-waitblockheight.7.md
Normal file
@ -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>
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
@ -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…
Reference in New Issue
Block a user