core-lightning/lightningd/htlc_end.c
Rusty Russell 33736b860a lightningd: attach HTLC timeout to htlc itself, fix gratuitous disconnect bug.
We set the timeout on first HTLC, but didn't clear it if that HTLC failed.

It's saner to have a per-HTLC timeout (since that's what it is!) and
also our timer infra is specially coded to scale approximately infinitely so
trying to optimize this is vastly premature.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Changelog-Fixed: Protocol: We would sometimes gratuitously disconnect 30 seconds after an HTLC failed.
2021-05-21 14:45:05 +09:30

315 lines
9.2 KiB
C

#include <ccan/cast/cast.h>
#include <ccan/crypto/siphash24/siphash24.h>
#include <ccan/tal/str/str.h>
#include <ccan/tal/tal.h>
#include <common/htlc.h>
#include <common/memleak.h>
#include <common/pseudorand.h>
#include <lightningd/htlc_end.h>
#include <lightningd/log.h>
#include <stdio.h>
size_t hash_htlc_key(const struct htlc_key *k)
{
struct siphash24_ctx ctx;
siphash24_init(&ctx, siphash_seed());
/* channel doesn't move while in this hash, so we just hash pointer. */
siphash24_update(&ctx, &k->channel, sizeof(k->channel));
siphash24_u64(&ctx, k->id);
return siphash24_done(&ctx);
}
struct htlc_in *find_htlc_in(const struct htlc_in_map *map,
const struct channel *channel,
u64 htlc_id)
{
const struct htlc_key key = { (struct channel *)channel, htlc_id };
return htlc_in_map_get(map, &key);
}
struct htlc_in *remove_htlc_in_by_dbid(struct htlc_in_map *remaining_htlcs_in,
u64 dbid)
{
struct htlc_in *hin;
struct htlc_in_map_iter ini;
for (hin = htlc_in_map_first(remaining_htlcs_in, &ini); hin;
hin = htlc_in_map_next(remaining_htlcs_in, &ini)) {
if (hin->dbid == dbid) {
htlc_in_map_del(remaining_htlcs_in, hin);
return hin;
}
}
return NULL;
}
static void destroy_htlc_in(struct htlc_in *hend, struct htlc_in_map *map)
{
htlc_in_map_del(map, hend);
}
void connect_htlc_in(struct htlc_in_map *map, struct htlc_in *hend)
{
tal_add_destructor2(hend, destroy_htlc_in, map);
htlc_in_map_add(map, hend);
}
struct htlc_out *find_htlc_out(const struct htlc_out_map *map,
const struct channel *channel,
u64 htlc_id)
{
const struct htlc_key key = { (struct channel *)channel, htlc_id };
return htlc_out_map_get(map, &key);
}
static void destroy_htlc_out(struct htlc_out *hend, struct htlc_out_map *map)
{
htlc_out_map_del(map, hend);
}
void connect_htlc_out(struct htlc_out_map *map, struct htlc_out *hend)
{
tal_add_destructor2(hend, destroy_htlc_out, map);
htlc_out_map_add(map, hend);
}
static void *corrupt(const char *abortstr, const char *fmt, ...)
PRINTF_FMT(2,3);
static void *corrupt(const char *abortstr, const char *fmt, ...)
{
if (abortstr) {
char *p;
va_list ap;
va_start(ap, fmt);
p = tal_vfmt(NULL, fmt, ap);
fatal("%s:%s\n", abortstr, p);
va_end(ap);
}
return NULL;
}
struct htlc_in *htlc_in_check(const struct htlc_in *hin, const char *abortstr)
{
if (amount_msat_eq(hin->msat, AMOUNT_MSAT(0)))
return corrupt(abortstr, "zero msatoshi");
else if (htlc_state_owner(hin->hstate) != REMOTE)
return corrupt(abortstr, "invalid state %s",
htlc_state_name(hin->hstate));
else if (hin->failonion && hin->preimage)
return corrupt(abortstr, "Both failonion and succeeded");
else if (hin->badonion != 0 && hin->preimage)
return corrupt(abortstr, "Both badonion and succeeded");
else if (hin->failonion && hin->badonion)
return corrupt(abortstr, "Both failed and malformed");
/* Can't have a resolution while still being added. */
if (hin->hstate >= RCVD_ADD_HTLC
&& hin->hstate <= RCVD_ADD_ACK_REVOCATION) {
if (hin->preimage)
return corrupt(abortstr, "Still adding, has preimage");
if (hin->failonion)
return corrupt(abortstr, "Still adding, has failmsg");
if (hin->badonion)
return corrupt(abortstr, "Still adding, has badonion");
} else if (hin->hstate >= SENT_REMOVE_HTLC
&& hin->hstate <= SENT_REMOVE_ACK_REVOCATION) {
if (!hin->preimage && !hin->failonion && !hin->badonion)
return corrupt(abortstr, "Removing, no resolution");
} else
return corrupt(abortstr, "Bad state %s",
htlc_state_name(hin->hstate));
return cast_const(struct htlc_in *, hin);
}
struct htlc_in *new_htlc_in(const tal_t *ctx,
struct channel *channel, u64 id,
struct amount_msat msat, u32 cltv_expiry,
const struct sha256 *payment_hash,
const struct secret *shared_secret TAKES,
const struct pubkey *blinding TAKES,
const struct secret *blinding_ss,
const u8 *onion_routing_packet)
{
struct htlc_in *hin = tal(ctx, struct htlc_in);
hin->dbid = 0;
hin->key.channel = channel;
hin->key.id = id;
hin->msat = msat;
hin->cltv_expiry = cltv_expiry;
hin->payment_hash = *payment_hash;
if (shared_secret)
hin->shared_secret = tal_dup(hin, struct secret, shared_secret);
else
hin->shared_secret = NULL;
if (blinding) {
hin->blinding = tal_dup(hin, struct pubkey, blinding);
hin->blinding_ss = *blinding_ss;
} else
hin->blinding = NULL;
memcpy(hin->onion_routing_packet, onion_routing_packet,
sizeof(hin->onion_routing_packet));
hin->hstate = RCVD_ADD_COMMIT;
hin->badonion = 0;
hin->failonion = NULL;
hin->preimage = NULL;
hin->we_filled = NULL;
hin->received_time = time_now();
return htlc_in_check(hin, "new_htlc_in");
}
struct htlc_out *htlc_out_check(const struct htlc_out *hout,
const char *abortstr)
{
if (htlc_state_owner(hout->hstate) != LOCAL)
return corrupt(abortstr, "invalid state %s",
htlc_state_name(hout->hstate));
else if (hout->failonion && hout->preimage)
return corrupt(abortstr, "Both failed and succeeded");
if (hout->am_origin && hout->in)
return corrupt(abortstr, "Both origin and incoming");
if (hout->in) {
if (amount_msat_less(hout->in->msat, hout->msat))
return corrupt(abortstr, "Input amount %s"
" less than %s",
type_to_string(tmpctx, struct amount_msat,
&hout->in->msat),
type_to_string(tmpctx, struct amount_msat,
&hout->msat));
if (hout->in->cltv_expiry <= hout->cltv_expiry)
return corrupt(abortstr, "Input cltv_expiry %u"
" less than %u",
hout->in->cltv_expiry, hout->cltv_expiry);
if (!sha256_eq(&hout->in->payment_hash, &hout->payment_hash))
return corrupt(abortstr, "Input hash != output hash");
/* If output is resolved, input must be resolved same
* way (or not resolved yet). */
if (hout->failonion) {
if (hout->in->badonion)
return corrupt(abortstr,
"Output failmsg, input badonion");
if (hout->in->preimage)
return corrupt(abortstr,
"Output failmsg, input preimage");
} else if (hout->failmsg) {
if (hout->in->failonion)
return corrupt(abortstr,
"Output failmsg, input failonion");
if (hout->in->preimage)
return corrupt(abortstr,
"Output failmsg, input preimage");
} else if (hout->preimage) {
if (hout->in->failonion)
return corrupt(abortstr,
"Output preimage, input failonion");
if (hout->in->badonion)
return corrupt(abortstr,
"Output preimage, input badonion");
} else {
if (hout->in->preimage)
return corrupt(abortstr,
"Output unresolved, input preimage");
if (hout->in->failonion)
return corrupt(abortstr,
"Output unresovled, input failmsg");
if (hout->in->badonion)
return corrupt(abortstr,
"Output unresolved, input badonion");
}
}
/* Can't have a resolution while still being added. */
if (hout->hstate >= SENT_ADD_HTLC
&& hout->hstate <= SENT_ADD_ACK_REVOCATION) {
if (hout->preimage)
return corrupt(abortstr, "Still adding, has preimage");
if (hout->failonion)
return corrupt(abortstr, "Still adding, has failmsg");
if (hout->failmsg)
return corrupt(abortstr, "Still adding, has failmsg");
} else if (hout->hstate >= RCVD_REMOVE_HTLC
&& hout->hstate <= RCVD_REMOVE_ACK_REVOCATION) {
if (!hout->preimage && !hout->failonion && !hout->failmsg)
return corrupt(abortstr, "Removing, no resolution");
} else
return corrupt(abortstr, "Bad state %s",
htlc_state_name(hout->hstate));
return cast_const(struct htlc_out *, hout);
}
static void htlc_out_clear_hin(struct htlc_in *hin, struct htlc_out *hout)
{
assert(hout->in == hin);
hout->in = NULL;
}
static void destroy_htlc_out_with_hin(struct htlc_out *hout)
{
/* Don't try to clear our ptr if we're freed before hin! */
if (hout->in)
tal_del_destructor2(hout->in, htlc_out_clear_hin, hout);
}
void htlc_out_connect_htlc_in(struct htlc_out *hout, struct htlc_in *hin)
{
assert(!hout->in);
hout->in = hin;
tal_add_destructor2(hin, htlc_out_clear_hin, hout);
tal_add_destructor(hout, destroy_htlc_out_with_hin);
}
/* You need to set the ID, then connect_htlc_out this! */
struct htlc_out *new_htlc_out(const tal_t *ctx,
struct channel *channel,
struct amount_msat msat,
u32 cltv_expiry,
const struct sha256 *payment_hash,
const u8 *onion_routing_packet,
const struct pubkey *blinding,
bool am_origin,
u64 partid,
struct htlc_in *in)
{
struct htlc_out *hout = tal(ctx, struct htlc_out);
/* Mark this as an as of now unsaved HTLC */
hout->dbid = 0;
hout->key.channel = channel;
hout->key.id = HTLC_INVALID_ID;
hout->msat = msat;
hout->cltv_expiry = cltv_expiry;
hout->payment_hash = *payment_hash;
memcpy(hout->onion_routing_packet, onion_routing_packet,
sizeof(hout->onion_routing_packet));
hout->hstate = SENT_ADD_HTLC;
hout->failmsg = NULL;
hout->failonion = NULL;
hout->preimage = NULL;
hout->timeout = NULL;
if (blinding)
hout->blinding = tal_dup(hout, struct pubkey, blinding);
else
hout->blinding = NULL;
hout->am_origin = am_origin;
if (am_origin)
hout->partid = partid;
hout->in = NULL;
if (in)
htlc_out_connect_htlc_in(hout, in);
return htlc_out_check(hout, "new_htlc_out");
}