Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle blockheight disagreement between us and payee #3376

Merged
merged 6 commits into from
Jan 21, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
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.
  • Loading branch information
ZmnSCPxj committed Jan 15, 2020
commit f9decf171418ad7d05a8f911cb716e39d088fa3a
10 changes: 10 additions & 0 deletions contrib/pyln-client/pyln/client/lightning.py
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,16 @@ def waitanyinvoice(self, lastpay_index=None):
}
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}
Expand Down
1 change: 1 addition & 0 deletions doc/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 2 additions & 1 deletion doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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 changes: 36 additions & 0 deletions doc/lightning-waitblockheight.7

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 37 additions & 0 deletions doc/lightning-waitblockheight.7.md
Original file line number Diff line number Diff line change
@@ -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 changes: 2 additions & 0 deletions lightningd/lightningd.c
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions lightningd/lightningd.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
107 changes: 107 additions & 0 deletions lightningd/peer_control.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions lightningd/peer_control.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 3 additions & 0 deletions lightningd/test/run-find_my_abspath.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
40 changes: 39 additions & 1 deletion tests/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 🥛."

Expand All @@ -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)