Browse Source
gossipd.c is doing too many things: this is a start. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>travis-debug
Rusty Russell
5 years ago
7 changed files with 614 additions and 547 deletions
@ -0,0 +1,65 @@ |
|||
#ifndef LIGHTNING_GOSSIPD_GOSSIPD_H |
|||
#define LIGHTNING_GOSSIPD_GOSSIPD_H |
|||
#include "config.h" |
|||
#include <ccan/list/list.h> |
|||
#include <ccan/short_types/short_types.h> |
|||
#include <common/node_id.h> |
|||
|
|||
/* We talk to `hsmd` to sign our gossip messages with the node key */ |
|||
#define HSM_FD 3 |
|||
/* connectd asks us for help finding nodes, and gossip fds for new peers */ |
|||
#define CONNECTD_FD 4 |
|||
|
|||
/*~ The core daemon structure: */ |
|||
struct daemon { |
|||
/* Who am I? Helps us find ourself in the routing map. */ |
|||
struct node_id id; |
|||
|
|||
/* Peers we are gossiping to: id is unique */ |
|||
struct list_head peers; |
|||
|
|||
/* Connection to lightningd. */ |
|||
struct daemon_conn *master; |
|||
|
|||
/* Connection to connect daemon. */ |
|||
struct daemon_conn *connectd; |
|||
|
|||
/* Routing information */ |
|||
struct routing_state *rstate; |
|||
|
|||
/* chainhash for checking/making gossip msgs */ |
|||
struct bitcoin_blkid chain_hash; |
|||
|
|||
/* Timers: we batch gossip, and also refresh announcements */ |
|||
struct timers timers; |
|||
|
|||
/* Minimum interval for generating updated gossip */ |
|||
u32 gossip_min_interval; |
|||
|
|||
/* Global features to list in node_announcement. */ |
|||
u8 *globalfeatures; |
|||
|
|||
/* Alias (not NUL terminated) and favorite color for node_announcement */ |
|||
u8 alias[32]; |
|||
u8 rgb[3]; |
|||
|
|||
/* What addresses we can actually announce. */ |
|||
struct wireaddr *announcable; |
|||
|
|||
/* Do we think we're missing gossip? Contains timer to re-check */ |
|||
struct oneshot *gossip_missing; |
|||
|
|||
/* Channels we've heard about, but don't know. */ |
|||
struct short_channel_id *unknown_scids; |
|||
|
|||
/* Timer until we can send a new node_announcement */ |
|||
struct oneshot *node_announce_timer; |
|||
}; |
|||
|
|||
/* Search for a peer. */ |
|||
struct peer *find_peer(struct daemon *daemon, const struct node_id *id); |
|||
|
|||
/* Queue a gossip message for the peer: the subdaemon on the other end simply
|
|||
* forwards it to the peer. */ |
|||
void queue_peer_msg(struct peer *peer, const u8 *msg TAKES); |
|||
#endif /* LIGHTNING_GOSSIPD_GOSSIPD_H */ |
@ -0,0 +1,456 @@ |
|||
/* Routines to make our own gossip messages */ |
|||
#include <ccan/mem/mem.h> |
|||
#include <common/memleak.h> |
|||
#include <common/timeout.h> |
|||
#include <common/utils.h> |
|||
#include <common/wireaddr.h> |
|||
#include <errno.h> |
|||
#include <gossipd/gen_gossip_peerd_wire.h> |
|||
#include <gossipd/gossip_store.h> |
|||
#include <gossipd/gossipd.h> |
|||
#include <gossipd/make_gossip.h> |
|||
#include <hsmd/gen_hsm_wire.h> |
|||
#include <wire/gen_peer_wire.h> |
|||
#include <wire/wire_sync.h> |
|||
|
|||
/* Create a node_announcement with the given signature. It may be NULL in the
|
|||
* case we need to create a provisional announcement for the HSM to sign. |
|||
* This is called twice: once with the dummy signature to get it signed and a |
|||
* second time to build the full packet with the signature. The timestamp is |
|||
* handed in rather than using time_now() internally, since that could change |
|||
* between the dummy creation and the call with a signature. */ |
|||
static u8 *create_node_announcement(const tal_t *ctx, struct daemon *daemon, |
|||
const secp256k1_ecdsa_signature *sig, |
|||
u32 timestamp) |
|||
{ |
|||
u8 *addresses = tal_arr(tmpctx, u8, 0); |
|||
u8 *announcement; |
|||
size_t i; |
|||
|
|||
if (!sig) |
|||
sig = talz(tmpctx, secp256k1_ecdsa_signature); |
|||
|
|||
for (i = 0; i < tal_count(daemon->announcable); i++) |
|||
towire_wireaddr(&addresses, &daemon->announcable[i]); |
|||
|
|||
announcement = |
|||
towire_node_announcement(ctx, sig, daemon->globalfeatures, timestamp, |
|||
&daemon->id, daemon->rgb, daemon->alias, |
|||
addresses); |
|||
return announcement; |
|||
} |
|||
|
|||
/* Helper to get non-signature, non-timestamp parts of (valid!) channel_update */ |
|||
void get_cupdate_parts(const u8 *channel_update, |
|||
const u8 *parts[2], |
|||
size_t sizes[2]) |
|||
{ |
|||
/* BOLT #7:
|
|||
* |
|||
* 1. type: 258 (`channel_update`) |
|||
* 2. data: |
|||
* * [`signature`:`signature`] |
|||
* * [`chain_hash`:`chain_hash`] |
|||
* * [`short_channel_id`:`short_channel_id`] |
|||
* * [`u32`:`timestamp`] |
|||
*... |
|||
*/ |
|||
/* Note: 2 bytes for `type` field */ |
|||
/* We already checked it's valid before accepting */ |
|||
assert(tal_count(channel_update) > 2 + 64 + 32 + 8 + 4); |
|||
parts[0] = channel_update + 2 + 64; |
|||
sizes[0] = 32 + 8; |
|||
parts[1] = channel_update + 2 + 64 + 32 + 8 + 4; |
|||
sizes[1] = tal_count(channel_update) - (64 + 2 + 32 + 8 + 4); |
|||
} |
|||
|
|||
/* Is this channel_update different from prev (not sigs and timestamps)? */ |
|||
bool cupdate_different(struct gossip_store *gs, |
|||
const struct half_chan *hc, |
|||
const u8 *cupdate) |
|||
{ |
|||
const u8 *oparts[2], *nparts[2]; |
|||
size_t osizes[2], nsizes[2]; |
|||
const u8 *orig; |
|||
|
|||
/* Get last one we have. */ |
|||
orig = gossip_store_get(tmpctx, gs, hc->bcast.index); |
|||
get_cupdate_parts(orig, oparts, osizes); |
|||
get_cupdate_parts(cupdate, nparts, nsizes); |
|||
|
|||
return !memeq(oparts[0], osizes[0], nparts[0], nsizes[0]) |
|||
|| !memeq(oparts[1], osizes[1], nparts[1], nsizes[1]); |
|||
} |
|||
|
|||
/* Get non-signature, non-timestamp parts of (valid!) node_announcement */ |
|||
static void get_nannounce_parts(const u8 *node_announcement, |
|||
const u8 *parts[2], |
|||
size_t sizes[2]) |
|||
{ |
|||
size_t len; |
|||
const u8 *flen; |
|||
|
|||
/* BOLT #7:
|
|||
* |
|||
* 1. type: 257 (`node_announcement`) |
|||
* 2. data: |
|||
* * [`signature`:`signature`] |
|||
* * [`u16`:`flen`] |
|||
* * [`flen*byte`:`features`] |
|||
* * [`u32`:`timestamp`] |
|||
*... |
|||
*/ |
|||
/* Note: 2 bytes for `type` field */ |
|||
/* We already checked it's valid before accepting */ |
|||
assert(tal_count(node_announcement) > 2 + 64); |
|||
parts[0] = node_announcement + 2 + 64; |
|||
|
|||
/* Read flen to get size */ |
|||
flen = parts[0]; |
|||
len = tal_count(node_announcement) - (2 + 64); |
|||
sizes[0] = 2 + fromwire_u16(&flen, &len); |
|||
assert(flen != NULL && len >= 4); |
|||
|
|||
parts[1] = node_announcement + 2 + 64 + sizes[0] + 4; |
|||
sizes[1] = tal_count(node_announcement) - (2 + 64 + sizes[0] + 4); |
|||
} |
|||
|
|||
/* Is this node_announcement different from prev (not sigs and timestamps)? */ |
|||
bool nannounce_different(struct gossip_store *gs, |
|||
const struct node *node, |
|||
const u8 *nannounce) |
|||
{ |
|||
const u8 *oparts[2], *nparts[2]; |
|||
size_t osizes[2], nsizes[2]; |
|||
const u8 *orig; |
|||
|
|||
/* Get last one we have. */ |
|||
orig = gossip_store_get(tmpctx, gs, node->bcast.index); |
|||
get_nannounce_parts(orig, oparts, osizes); |
|||
get_nannounce_parts(nannounce, nparts, nsizes); |
|||
|
|||
return !memeq(oparts[0], osizes[0], nparts[0], nsizes[0]) |
|||
|| !memeq(oparts[1], osizes[1], nparts[1], nsizes[1]); |
|||
} |
|||
|
|||
/* This routine created a `node_announcement` for our node, and hands it to
|
|||
* the routing.c code like any other `node_announcement`. Such announcements |
|||
* are only accepted if there is an announced channel associated with that node |
|||
* (to prevent spam), so we only call this once we've announced a channel. */ |
|||
static void update_own_node_announcement(struct daemon *daemon) |
|||
{ |
|||
u32 timestamp = gossip_time_now(daemon->rstate).ts.tv_sec; |
|||
secp256k1_ecdsa_signature sig; |
|||
u8 *msg, *nannounce, *err; |
|||
struct node *self = get_node(daemon->rstate, &daemon->id); |
|||
|
|||
/* Discard existing timer. */ |
|||
daemon->node_announce_timer = tal_free(daemon->node_announce_timer); |
|||
|
|||
/* Make unsigned announcement. */ |
|||
nannounce = create_node_announcement(tmpctx, daemon, NULL, timestamp); |
|||
|
|||
/* If it's the same as the previous, nothing to do. */ |
|||
if (self && self->bcast.index) { |
|||
u32 next; |
|||
|
|||
if (!nannounce_different(daemon->rstate->gs, self, nannounce)) |
|||
return; |
|||
|
|||
/* BOLT #7:
|
|||
* |
|||
* The origin node: |
|||
* - MUST set `timestamp` to be greater than that of any |
|||
* previous `node_announcement` it has previously created. |
|||
*/ |
|||
/* We do better: never send them within more than 5 minutes. */ |
|||
next = self->bcast.timestamp + daemon->gossip_min_interval; |
|||
|
|||
if (timestamp < next) { |
|||
status_debug("node_announcement: delaying %u secs", |
|||
next - timestamp); |
|||
daemon->node_announce_timer |
|||
= new_reltimer(&daemon->timers, |
|||
daemon, |
|||
time_from_sec(next - timestamp), |
|||
update_own_node_announcement, |
|||
daemon); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
/* Ask hsmd to sign it (synchronous) */ |
|||
if (!wire_sync_write(HSM_FD, take(towire_hsm_node_announcement_sig_req(NULL, nannounce)))) |
|||
status_failed(STATUS_FAIL_MASTER_IO, "Could not write to HSM: %s", strerror(errno)); |
|||
|
|||
msg = wire_sync_read(tmpctx, HSM_FD); |
|||
if (!fromwire_hsm_node_announcement_sig_reply(msg, &sig)) |
|||
status_failed(STATUS_FAIL_MASTER_IO, "HSM returned an invalid node_announcement sig"); |
|||
|
|||
/* We got the signature for our provisional node_announcement back
|
|||
* from the HSM, create the real announcement and forward it to |
|||
* gossipd so it can take care of forwarding it. */ |
|||
nannounce = create_node_announcement(NULL, daemon, &sig, timestamp); |
|||
|
|||
/* This injects it into the routing code in routing.c; it should not
|
|||
* reject it! */ |
|||
err = handle_node_announcement(daemon->rstate, take(nannounce)); |
|||
if (err) |
|||
status_failed(STATUS_FAIL_INTERNAL_ERROR, |
|||
"rejected own node announcement: %s", |
|||
tal_hex(tmpctx, err)); |
|||
} |
|||
|
|||
/* Should we announce our own node? Called at strategic places. */ |
|||
void maybe_send_own_node_announce(struct daemon *daemon) |
|||
{ |
|||
/* We keep an internal flag in the routing code to say we've announced
|
|||
* a local channel. The alternative would be to have it make a |
|||
* callback, but when we start up we don't want to make multiple |
|||
* announcments, so we use this approach for now. */ |
|||
if (!daemon->rstate->local_channel_announced) |
|||
return; |
|||
|
|||
update_own_node_announcement(daemon); |
|||
daemon->rstate->local_channel_announced = false; |
|||
} |
|||
|
|||
/* Our timer callbacks take a single argument, so we marshall everything
|
|||
* we need into this structure: */ |
|||
struct local_cupdate { |
|||
struct daemon *daemon; |
|||
struct local_chan *local_chan; |
|||
|
|||
bool disable; |
|||
bool even_if_identical; |
|||
|
|||
u16 cltv_expiry_delta; |
|||
struct amount_msat htlc_minimum, htlc_maximum; |
|||
u32 fee_base_msat, fee_proportional_millionths; |
|||
}; |
|||
|
|||
/* This generates a `channel_update` message for one of our channels. We do
|
|||
* this here, rather than in `channeld` because we (may) need to do it |
|||
* ourselves anyway if channeld dies, or when we refresh it once a week, |
|||
* and so we can avoid creating redundant ones. */ |
|||
static void update_local_channel(struct local_cupdate *lc /* frees! */) |
|||
{ |
|||
struct daemon *daemon = lc->daemon; |
|||
secp256k1_ecdsa_signature dummy_sig; |
|||
u8 *update, *msg; |
|||
u32 timestamp = gossip_time_now(daemon->rstate).ts.tv_sec, next; |
|||
u8 message_flags, channel_flags; |
|||
struct chan *chan = lc->local_chan->chan; |
|||
struct half_chan *hc; |
|||
const int direction = lc->local_chan->direction; |
|||
|
|||
/* Discard existing timer. */ |
|||
lc->local_chan->channel_update_timer |
|||
= tal_free(lc->local_chan->channel_update_timer); |
|||
|
|||
/* So valgrind doesn't complain */ |
|||
memset(&dummy_sig, 0, sizeof(dummy_sig)); |
|||
|
|||
/* Create an unsigned channel_update: we backdate enables, so
|
|||
* we can always send a disable in an emergency. */ |
|||
if (!lc->disable) |
|||
timestamp -= daemon->gossip_min_interval; |
|||
|
|||
/* BOLT #7:
|
|||
* |
|||
* The `channel_flags` bitfield is used to indicate the direction of |
|||
* the channel: it identifies the node that this update originated |
|||
* from and signals various options concerning the channel. The |
|||
* following table specifies the meaning of its individual bits: |
|||
* |
|||
* | Bit Position | Name | Meaning | |
|||
* | ------------- | ----------- | -------------------------------- | |
|||
* | 0 | `direction` | Direction this update refers to. | |
|||
* | 1 | `disable` | Disable the channel. | |
|||
*/ |
|||
channel_flags = direction; |
|||
if (lc->disable) |
|||
channel_flags |= ROUTING_FLAGS_DISABLED; |
|||
|
|||
/* BOLT #7:
|
|||
* |
|||
* The `message_flags` bitfield is used to indicate the presence of |
|||
* optional fields in the `channel_update` message: |
|||
* |
|||
*| Bit Position | Name | Field | |
|||
*... |
|||
*| 0 | `option_channel_htlc_max` | `htlc_maximum_msat` | |
|||
*/ |
|||
message_flags = 0 | ROUTING_OPT_HTLC_MAX_MSAT; |
|||
|
|||
/* We create an update with a dummy signature, and hand to hsmd to get
|
|||
* it signed. */ |
|||
update = towire_channel_update_option_channel_htlc_max(tmpctx, &dummy_sig, |
|||
&daemon->chain_hash, |
|||
&chan->scid, |
|||
timestamp, |
|||
message_flags, channel_flags, |
|||
lc->cltv_expiry_delta, |
|||
lc->htlc_minimum, |
|||
lc->fee_base_msat, |
|||
lc->fee_proportional_millionths, |
|||
lc->htlc_maximum); |
|||
|
|||
hc = &chan->half[direction]; |
|||
if (is_halfchan_defined(hc)) { |
|||
/* Suppress duplicates. */ |
|||
if (!lc->even_if_identical |
|||
&& !cupdate_different(daemon->rstate->gs, hc, update)) { |
|||
tal_free(lc); |
|||
return; |
|||
} |
|||
|
|||
/* Is it too soon to send another update? */ |
|||
next = hc->bcast.timestamp + daemon->gossip_min_interval; |
|||
|
|||
if (timestamp < next) { |
|||
status_debug("channel_update %s/%u: delaying %u secs", |
|||
type_to_string(tmpctx, |
|||
struct short_channel_id, |
|||
&chan->scid), |
|||
direction, |
|||
next - timestamp); |
|||
lc->local_chan->channel_update_timer |
|||
= new_reltimer(&daemon->timers, lc, |
|||
time_from_sec(next - timestamp), |
|||
update_local_channel, |
|||
lc); |
|||
/* If local chan vanishes, so does update, and timer. */ |
|||
notleak(tal_steal(lc->local_chan, lc)); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
/* Note that we treat the hsmd as synchronous. This is simple (no
|
|||
* callback hell)!, but may need to change to async if we ever want |
|||
* remote HSMs */ |
|||
if (!wire_sync_write(HSM_FD, |
|||
towire_hsm_cupdate_sig_req(tmpctx, update))) { |
|||
status_failed(STATUS_FAIL_HSM_IO, "Writing cupdate_sig_req: %s", |
|||
strerror(errno)); |
|||
} |
|||
|
|||
msg = wire_sync_read(tmpctx, HSM_FD); |
|||
if (!msg || !fromwire_hsm_cupdate_sig_reply(NULL, msg, &update)) { |
|||
status_failed(STATUS_FAIL_HSM_IO, |
|||
"Reading cupdate_sig_req: %s", |
|||
strerror(errno)); |
|||
} |
|||
|
|||
/* BOLT #7:
|
|||
* |
|||
* The origin node: |
|||
* - MAY create a `channel_update` to communicate the channel parameters to the |
|||
* channel peer, even though the channel has not yet been announced (i.e. the |
|||
* `announce_channel` bit was not set). |
|||
*/ |
|||
if (!is_chan_public(chan)) { |
|||
/* handle_channel_update will not put private updates in the
|
|||
* broadcast list, but we send it direct to the peer (if we |
|||
* have one connected) now */ |
|||
struct peer *peer = find_peer(daemon, |
|||
&chan->nodes[!direction]->id); |
|||
if (peer) |
|||
queue_peer_msg(peer, update); |
|||
} |
|||
|
|||
/* We feed it into routing.c like any other channel_update; it may
|
|||
* discard it (eg. non-public channel), but it should not complain |
|||
* about it being invalid! __func__ is a magic C constant which |
|||
* expands to this function name. */ |
|||
msg = handle_channel_update(daemon->rstate, take(update), __func__, |
|||
NULL); |
|||
if (msg) |
|||
status_failed(STATUS_FAIL_INTERNAL_ERROR, |
|||
"%s: rejected local channel update %s: %s", |
|||
__func__, |
|||
/* Normally we must not touch something taken()
|
|||
* but we're in deep trouble anyway, and |
|||
* handle_channel_update only tal_steals onto |
|||
* tmpctx, so it's actually OK. */ |
|||
tal_hex(tmpctx, update), |
|||
tal_hex(tmpctx, msg)); |
|||
tal_free(lc); |
|||
} |
|||
|
|||
/* This is a refresh of a local channel: sends an update if one is needed. */ |
|||
void refresh_local_channel(struct daemon *daemon, |
|||
struct local_chan *local_chan, |
|||
bool even_if_identical) |
|||
{ |
|||
const struct half_chan *hc; |
|||
struct local_cupdate *lc; |
|||
|
|||
hc = &local_chan->chan->half[local_chan->direction]; |
|||
|
|||
/* Don't generate a channel_update for an uninitialized channel. */ |
|||
if (!is_halfchan_defined(hc)) |
|||
return; |
|||
|
|||
lc = tal(NULL, struct local_cupdate); |
|||
lc->daemon = daemon; |
|||
lc->local_chan = local_chan; |
|||
lc->even_if_identical = even_if_identical; |
|||
lc->disable = (hc->channel_flags & ROUTING_FLAGS_DISABLED) |
|||
|| local_chan->local_disabled; |
|||
lc->cltv_expiry_delta = hc->delay; |
|||
lc->htlc_minimum = hc->htlc_minimum; |
|||
lc->htlc_maximum = hc->htlc_maximum; |
|||
lc->fee_base_msat = hc->base_fee; |
|||
lc->fee_proportional_millionths = hc->proportional_fee; |
|||
|
|||
update_local_channel(lc); |
|||
} |
|||
|
|||
/* channeld asks us to update the local channel. */ |
|||
bool handle_local_channel_update(struct daemon *daemon, |
|||
const struct node_id *src, |
|||
const u8 *msg) |
|||
{ |
|||
struct short_channel_id scid; |
|||
struct local_cupdate *lc = tal(tmpctx, struct local_cupdate); |
|||
|
|||
lc->daemon = daemon; |
|||
lc->even_if_identical = false; |
|||
|
|||
/* FIXME: We should get scid from lightningd when setting up the
|
|||
* connection, so no per-peer daemon can mess with channels other than |
|||
* its own! */ |
|||
if (!fromwire_gossipd_local_channel_update(msg, |
|||
&scid, |
|||
&lc->disable, |
|||
&lc->cltv_expiry_delta, |
|||
&lc->htlc_minimum, |
|||
&lc->fee_base_msat, |
|||
&lc->fee_proportional_millionths, |
|||
&lc->htlc_maximum)) { |
|||
status_broken("peer %s bad local_channel_update %s", |
|||
type_to_string(tmpctx, struct node_id, src), |
|||
tal_hex(tmpctx, msg)); |
|||
return false; |
|||
} |
|||
|
|||
lc->local_chan = local_chan_map_get(&daemon->rstate->local_chan_map, |
|||
&scid); |
|||
/* Can theoretically happen if channel just closed. */ |
|||
if (!lc->local_chan) { |
|||
status_debug("peer %s local_channel_update for unknown %s", |
|||
type_to_string(tmpctx, struct node_id, src), |
|||
type_to_string(tmpctx, struct short_channel_id, |
|||
&scid)); |
|||
return true; |
|||
} |
|||
|
|||
/* Remove soft local_disabled flag, if they're marking it enabled. */ |
|||
if (!lc->disable) |
|||
local_enable_chan(daemon->rstate, lc->local_chan->chan); |
|||
|
|||
/* Apply the update they told us */ |
|||
update_local_channel(tal_steal(NULL, lc)); |
|||
return true; |
|||
} |
@ -0,0 +1,47 @@ |
|||
#ifndef LIGHTNING_GOSSIPD_MAKE_GOSSIP_H |
|||
#define LIGHTNING_GOSSIPD_MAKE_GOSSIP_H |
|||
#include "config.h" |
|||
#include <ccan/short_types/short_types.h> |
|||
#include <stdbool.h> |
|||
#include <stddef.h> |
|||
|
|||
struct chan; |
|||
struct daemon; |
|||
struct half_chan; |
|||
struct local_chan; |
|||
struct gossip_store; |
|||
struct peer; |
|||
struct node; |
|||
|
|||
/* Helper to get non-signature, non-timestamp parts of (valid!) channel_update */ |
|||
void get_cupdate_parts(const u8 *channel_update, |
|||
const u8 *parts[2], |
|||
size_t sizes[2]); |
|||
|
|||
|
|||
/* Is this channel_update different from prev (not sigs and timestamps)?
|
|||
* is_halfchan_defined(hc) must be true! */ |
|||
bool cupdate_different(struct gossip_store *gs, |
|||
const struct half_chan *hc, |
|||
const u8 *cupdate); |
|||
|
|||
/* Is this node_announcement different from prev (not sigs and timestamps)?
|
|||
* node->bcast.index must be non-zero! */ |
|||
bool nannounce_different(struct gossip_store *gs, |
|||
const struct node *node, |
|||
const u8 *nannounce); |
|||
|
|||
/* Should we announce our own node? Called at strategic places. */ |
|||
void maybe_send_own_node_announce(struct daemon *daemon); |
|||
|
|||
/* This is a refresh of a local channel: sends an update if one is needed. */ |
|||
void refresh_local_channel(struct daemon *daemon, |
|||
struct local_chan *local_chan, |
|||
bool even_if_identical); |
|||
|
|||
/* channeld asks us to update the local channel. */ |
|||
bool handle_local_channel_update(struct daemon *daemon, |
|||
const struct node_id *src, |
|||
const u8 *msg); |
|||
|
|||
#endif /* LIGHTNING_GOSSIPD_MAKE_GOSSIP_H */ |
Loading…
Reference in new issue