mirror of
https://github.com/aljazceru/lightning.git
synced 2025-12-19 07:04:22 +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:
committed by
Christian Decker
parent
44e8256338
commit
54cc735201
@@ -997,6 +997,16 @@ class LightningRpc(UnixDomainSocketRpc):
|
|||||||
}
|
}
|
||||||
return self.call("waitanyinvoice", payload)
|
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):
|
def waitinvoice(self, label):
|
||||||
"""
|
"""
|
||||||
Wait for an incoming payment matching the invoice with {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-txsend.7 \
|
||||||
doc/lightning-waitinvoice.7 \
|
doc/lightning-waitinvoice.7 \
|
||||||
doc/lightning-waitanyinvoice.7 \
|
doc/lightning-waitanyinvoice.7 \
|
||||||
|
doc/lightning-waitblockheight.7 \
|
||||||
doc/lightning-waitsendpay.7 \
|
doc/lightning-waitsendpay.7 \
|
||||||
doc/lightning-withdraw.7
|
doc/lightning-withdraw.7
|
||||||
|
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ c-lightning Documentation
|
|||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
:caption: Manpages
|
:caption: Manpages
|
||||||
|
|
||||||
lightningd <lightningd.8.md>
|
|
||||||
lightningd-config <lightningd-config.5.md>
|
lightningd-config <lightningd-config.5.md>
|
||||||
|
lightningd <lightningd.8.md>
|
||||||
lightning-autocleaninvoice <lightning-autocleaninvoice.7.md>
|
lightning-autocleaninvoice <lightning-autocleaninvoice.7.md>
|
||||||
lightning-check <lightning-check.7.md>
|
lightning-check <lightning-check.7.md>
|
||||||
lightning-checkmessage <lightning-checkmessage.7.md>
|
lightning-checkmessage <lightning-checkmessage.7.md>
|
||||||
@@ -64,6 +64,7 @@ c-lightning Documentation
|
|||||||
lightning-txprepare <lightning-txprepare.7.md>
|
lightning-txprepare <lightning-txprepare.7.md>
|
||||||
lightning-txsend <lightning-txsend.7.md>
|
lightning-txsend <lightning-txsend.7.md>
|
||||||
lightning-waitanyinvoice <lightning-waitanyinvoice.7.md>
|
lightning-waitanyinvoice <lightning-waitanyinvoice.7.md>
|
||||||
|
lightning-waitblockheight <lightning-waitblockheight.7.md>
|
||||||
lightning-waitinvoice <lightning-waitinvoice.7.md>
|
lightning-waitinvoice <lightning-waitinvoice.7.md>
|
||||||
lightning-waitsendpay <lightning-waitsendpay.7.md>
|
lightning-waitsendpay <lightning-waitsendpay.7.md>
|
||||||
lightning-withdraw <lightning-withdraw.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->sendpay_commands);
|
||||||
list_head_init(&ld->close_commands);
|
list_head_init(&ld->close_commands);
|
||||||
list_head_init(&ld->ping_commands);
|
list_head_init(&ld->ping_commands);
|
||||||
|
list_head_init(&ld->waitblockheight_commands);
|
||||||
|
|
||||||
/*~ Tal also explicitly supports arrays: it stores the number of
|
/*~ Tal also explicitly supports arrays: it stores the number of
|
||||||
* elements, which can be accessed with tal_count() (or tal_bytelen()
|
* 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);
|
htlcs_notify_new_block(ld, block_height);
|
||||||
channel_notify_new_block(ld, block_height);
|
channel_notify_new_block(ld, block_height);
|
||||||
gossip_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)
|
static void on_sigint(int _ UNUSED)
|
||||||
|
|||||||
@@ -251,6 +251,9 @@ struct lightningd {
|
|||||||
bool encrypted_hsm;
|
bool encrypted_hsm;
|
||||||
|
|
||||||
mode_t initial_umask;
|
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.
|
/* 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/initial_commit_tx.h>
|
||||||
#include <common/json_command.h>
|
#include <common/json_command.h>
|
||||||
#include <common/json_helpers.h>
|
#include <common/json_helpers.h>
|
||||||
|
#include <common/json_tok.h>
|
||||||
#include <common/jsonrpc_errors.h>
|
#include <common/jsonrpc_errors.h>
|
||||||
#include <common/key_derive.h>
|
#include <common/key_derive.h>
|
||||||
#include <common/param.h>
|
#include <common/param.h>
|
||||||
@@ -1750,6 +1751,112 @@ static const struct json_command getinfo_command = {
|
|||||||
};
|
};
|
||||||
AUTODATA(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,
|
static struct command_result *param_channel_or_all(struct command *cmd,
|
||||||
const char *name,
|
const char *name,
|
||||||
const char *buffer,
|
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);
|
void peer_dev_memleak(struct command *cmd);
|
||||||
#endif /* DEVELOPER */
|
#endif /* DEVELOPER */
|
||||||
|
|
||||||
|
/* Triggered at each new block. */
|
||||||
|
void waitblockheight_notify_new_block(struct lightningd *ld,
|
||||||
|
u32 block_height);
|
||||||
|
|
||||||
#endif /* LIGHTNING_LIGHTNINGD_PEER_CONTROL_H */
|
#endif /* LIGHTNING_LIGHTNINGD_PEER_CONTROL_H */
|
||||||
|
|||||||
@@ -192,6 +192,9 @@ bool wallet_network_check(struct wallet *w UNNEEDED)
|
|||||||
/* Generated stub for wallet_new */
|
/* Generated stub for wallet_new */
|
||||||
struct wallet *wallet_new(struct lightningd *ld UNNEEDED, struct timers *timers UNNEEDED)
|
struct wallet *wallet_new(struct lightningd *ld UNNEEDED, struct timers *timers UNNEEDED)
|
||||||
{ fprintf(stderr, "wallet_new called!\n"); abort(); }
|
{ 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 */
|
/* AUTOGENERATED MOCKS END */
|
||||||
|
|
||||||
struct log *crashlog;
|
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"))
|
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()
|
node = node_factory.get_node()
|
||||||
desc = "Some candy 🍬 and a nice glass of milk 🥛."
|
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.
|
# Let's just call it again to make sure it really works.
|
||||||
l1.rpc.listconfigs()
|
l1.rpc.listconfigs()
|
||||||
l1.stop()
|
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user