You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2473 lines
65 KiB
2473 lines
65 KiB
/* Test for state machine. */
|
|
#include <stdbool.h>
|
|
#include <ccan/cast/cast.h>
|
|
#include <ccan/array_size/array_size.h>
|
|
#include <ccan/tal/tal.h>
|
|
#include <ccan/tal/str/str.h>
|
|
#include <ccan/err/err.h>
|
|
#include <ccan/structeq/structeq.h>
|
|
#include <ccan/htable/htable_type.h>
|
|
#include <ccan/hash/hash.h>
|
|
#include <ccan/opt/opt.h>
|
|
#include <ccan/asort/asort.h>
|
|
#include <ccan/list/list.h>
|
|
#include "version.h"
|
|
|
|
static bool record_input_mapping(int b);
|
|
#define MAPPING_INPUTS(b) \
|
|
do { if (record_input_mapping(b)) return false; } while(0)
|
|
|
|
#include "state.h"
|
|
#include "names.c"
|
|
|
|
static bool quick = false;
|
|
static bool dot_simplify = false;
|
|
static bool dot_enable = false;
|
|
static bool dot_include_abnormal = false;
|
|
static bool dot_include_errors = false;
|
|
static bool include_nops = false;
|
|
static enum state_input *mapping_inputs;
|
|
|
|
enum failure {
|
|
FAIL_NONE,
|
|
FAIL_DECLINE_HTLC,
|
|
FAIL_STEAL,
|
|
FAIL_ACCEPT_OPEN,
|
|
FAIL_ACCEPT_ANCHOR,
|
|
FAIL_ACCEPT_OPEN_COMMIT_SIG,
|
|
FAIL_ACCEPT_OPEN_COMPLETE,
|
|
FAIL_ACCEPT_HTLC_ADD,
|
|
FAIL_ACCEPT_HTLC_FAIL,
|
|
FAIL_ACCEPT_HTLC_FULFILL,
|
|
FAIL_ACCEPT_UPDATE_ACCEPT,
|
|
FAIL_ACCEPT_UPDATE_COMPLETE,
|
|
FAIL_ACCEPT_UPDATE_SIGNATURE,
|
|
FAIL_ACCEPT_CLOSE,
|
|
FAIL_ACCEPT_CLOSE_COMPLETE,
|
|
FAIL_ACCEPT_CLOSE_ACK,
|
|
FAIL_ACCEPT_SIMULTANEOUS_CLOSE
|
|
};
|
|
|
|
struct htlc {
|
|
bool to_them;
|
|
unsigned int id;
|
|
};
|
|
|
|
struct htlc_progress {
|
|
struct htlc htlc; /* id == -1 if none in progress. */
|
|
bool adding; /* otherwise, removing. */
|
|
};
|
|
|
|
struct htlc_spend_watch {
|
|
unsigned int id;
|
|
enum state_input done;
|
|
};
|
|
|
|
/* Beyond this we consider cases equal for traverse loop detection. */
|
|
#define CAP_HTLCS 1
|
|
|
|
/* How many HTLCs to negotiate. */
|
|
#define MAX_HTLCS 2
|
|
|
|
/* But we can have many different malleated commit txs. */
|
|
#define HTLC_ARRSIZE 20
|
|
|
|
/*
|
|
* Worst case:
|
|
* Low priority: PKT_UPDATE_ADD_HTLC
|
|
* Receive update, which we decline: PKT_UPDATE_DECLINE_HTLC
|
|
* Send new update: PKT_UPDATE_ADD_HTLC
|
|
* Start close: PKT_CLOSE
|
|
* Some error: PKT_ERROR
|
|
*/
|
|
#define MAX_OUTQ 5
|
|
|
|
/* No padding, for fast compare and hashing. */
|
|
struct core_state {
|
|
/* What bitcoin/timeout notifications are we subscribed to? */
|
|
uint64_t event_notifies;
|
|
|
|
enum state_input current_command;
|
|
|
|
enum state_input outputs[MAX_OUTQ];
|
|
|
|
uint8_t num_outputs;
|
|
/* Here down need to be generated from other fields */
|
|
uint8_t state;
|
|
uint8_t peercond;
|
|
bool has_current_htlc;
|
|
|
|
uint8_t capped_htlcs_to_them;
|
|
uint8_t capped_htlcs_to_us;
|
|
uint8_t capped_htlc_spends_to_them;
|
|
uint8_t capped_htlc_spends_to_us;
|
|
|
|
uint8_t capped_live_htlcs_to_them;
|
|
uint8_t capped_live_htlcs_to_us;
|
|
bool valid;
|
|
bool pad[5];
|
|
};
|
|
|
|
struct peer {
|
|
struct core_state core;
|
|
|
|
enum state state;
|
|
enum state_peercond cond;
|
|
|
|
/* To store HTLC numbers. */
|
|
unsigned int pkt_data[MAX_OUTQ];
|
|
|
|
/* id == -1 if none currently. */
|
|
struct htlc_progress current_htlc;
|
|
|
|
/* Transitory: True if we just declined an HTLC. */
|
|
bool htlc_declined;
|
|
|
|
/* Have we created an anchor tx? */
|
|
bool anchor;
|
|
/* Have we broadcast anchor? */
|
|
bool anchor_broadcast;
|
|
/* Have we spent anchor (or seen them do it?) */
|
|
bool anchor_spent;
|
|
|
|
unsigned int num_htlcs_to_them, num_htlcs_to_us;
|
|
struct htlc htlcs_to_them[MAX_HTLCS], htlcs_to_us[MAX_HTLCS];
|
|
|
|
unsigned int num_live_htlcs_to_them, num_live_htlcs_to_us;
|
|
struct htlc live_htlcs_to_them[HTLC_ARRSIZE], live_htlcs_to_us[HTLC_ARRSIZE];
|
|
|
|
unsigned int num_htlc_spends_to_them, num_htlc_spends_to_us;
|
|
struct htlc htlc_spends_to_us[HTLC_ARRSIZE],
|
|
htlc_spends_to_them[HTLC_ARRSIZE];
|
|
|
|
unsigned int num_rvals_known;
|
|
unsigned int rvals_known[HTLC_ARRSIZE];
|
|
|
|
/* Where we came from. */
|
|
const struct trail *trail;
|
|
|
|
/* Current input and idata (for fail()) */
|
|
enum state_input current_input;
|
|
const union input *current_idata;
|
|
|
|
const char *error;
|
|
|
|
/* ID. */
|
|
const char *name;
|
|
/* The other peer's data. */
|
|
struct peer *other;
|
|
};
|
|
|
|
/* To recontruct errors. */
|
|
struct trail {
|
|
const struct trail *prev;
|
|
const char *name;
|
|
enum state_input input;
|
|
const struct peer *before, *after;
|
|
int htlc_id;
|
|
unsigned int num_peer_outputs;
|
|
unsigned int depth;
|
|
const char *pkt_sent;
|
|
};
|
|
|
|
struct situation {
|
|
union {
|
|
struct core_state s;
|
|
uint32_t u32[sizeof(struct core_state)/sizeof(uint32_t)];
|
|
} a, b;
|
|
};
|
|
|
|
static const struct situation *situation_keyof(const struct situation *situation)
|
|
{
|
|
return situation;
|
|
}
|
|
|
|
/* After 2, we stop looping. */
|
|
static unsigned int cap(unsigned int val)
|
|
{
|
|
return val > CAP_HTLCS ? CAP_HTLCS : val;
|
|
}
|
|
|
|
static size_t situation_hash(const struct situation *situation)
|
|
{
|
|
BUILD_ASSERT(sizeof(situation->a.u32) == sizeof(situation->a.s));
|
|
return hash(situation->a.u32, ARRAY_SIZE(situation->a.u32), 0);
|
|
}
|
|
|
|
static bool situation_eq(const struct situation *a, const struct situation *b)
|
|
{
|
|
/* No padding */
|
|
BUILD_ASSERT(sizeof(a->a.s)
|
|
== (sizeof(a->a.s.event_notifies)
|
|
+ sizeof(a->a.s.state)
|
|
+ sizeof(a->a.s.current_command)
|
|
+ sizeof(a->a.s.outputs)
|
|
+ sizeof(a->a.s.num_outputs)
|
|
+ sizeof(a->a.s.peercond)
|
|
+ sizeof(a->a.s.has_current_htlc)
|
|
+ sizeof(a->a.s.capped_htlcs_to_us)
|
|
+ sizeof(a->a.s.capped_htlcs_to_them)
|
|
+ sizeof(a->a.s.capped_htlc_spends_to_us)
|
|
+ sizeof(a->a.s.capped_htlc_spends_to_them)
|
|
+ sizeof(a->a.s.capped_live_htlcs_to_us)
|
|
+ sizeof(a->a.s.capped_live_htlcs_to_them)
|
|
+ sizeof(a->a.s.valid)
|
|
+ sizeof(a->a.s.pad)));
|
|
return structeq(&a->a.s, &b->a.s) && structeq(&a->b.s, &b->b.s);
|
|
}
|
|
|
|
struct dot_edge {
|
|
const char *oldstate, *newstate;
|
|
enum state_input i;
|
|
const char *pkt;
|
|
};
|
|
|
|
static const struct dot_edge *dot_edge_keyof(const struct dot_edge *dot_edge)
|
|
{
|
|
return dot_edge;
|
|
}
|
|
|
|
static size_t dot_edge_hash(const struct dot_edge *d)
|
|
{
|
|
uint32_t pkthash;
|
|
|
|
if (d->pkt)
|
|
pkthash = hash(d->pkt, strlen(d->pkt), d->i);
|
|
else
|
|
pkthash = d->i;
|
|
return hash_pointer(d->oldstate, hash_pointer(d->newstate, pkthash));
|
|
}
|
|
|
|
static bool dot_edge_eq(const struct dot_edge *a, const struct dot_edge *b)
|
|
{
|
|
return a->oldstate == b->oldstate
|
|
&& a->newstate == b->newstate
|
|
&& a->i == b->i
|
|
&& ((a->pkt == NULL && b->pkt == NULL)
|
|
|| streq(a->pkt, b->pkt));
|
|
}
|
|
|
|
HTABLE_DEFINE_TYPE(struct dot_edge,
|
|
dot_edge_keyof, dot_edge_hash, dot_edge_eq,
|
|
edge_hash);
|
|
|
|
HTABLE_DEFINE_TYPE(struct situation,
|
|
situation_keyof, situation_hash, situation_eq,
|
|
sithash);
|
|
|
|
struct hist {
|
|
/* All the different state combinations. */
|
|
struct sithash sithash;
|
|
|
|
/* The different inputs. */
|
|
enum state_input **inputs_per_state;
|
|
|
|
/* The different outputs. */
|
|
enum state_input *outputs;
|
|
|
|
/* Edges for the dot graph, if any. */
|
|
struct edge_hash edges;
|
|
|
|
/* For dumping states. */
|
|
struct state_dump {
|
|
enum state_input input;
|
|
enum state next;
|
|
enum state_input pkt;
|
|
} **state_dump;
|
|
};
|
|
|
|
struct fail_details {
|
|
/* The universe state at the time. */
|
|
struct peer us, other;
|
|
enum state_input input;
|
|
union input idata;
|
|
/* Previous history. */
|
|
const struct trail *prev_trail;
|
|
};
|
|
|
|
struct failpoint {
|
|
/* Hash key (with which_fail) */
|
|
struct situation sit;
|
|
/* Which failure */
|
|
enum failure which_fail;
|
|
|
|
/* If we haven't tried failing yet, this is set */
|
|
struct fail_details *details;
|
|
};
|
|
|
|
static const struct failpoint *
|
|
failpoint_keyof(const struct failpoint *f)
|
|
{
|
|
return f;
|
|
}
|
|
|
|
static size_t failpoint_hash(const struct failpoint *f)
|
|
{
|
|
return situation_hash(&f->sit) + f->which_fail;
|
|
}
|
|
|
|
static bool failpoint_eq(const struct failpoint *a,
|
|
const struct failpoint *b)
|
|
{
|
|
return a->which_fail == b->which_fail
|
|
&& situation_eq(&a->sit, &b->sit);
|
|
}
|
|
|
|
HTABLE_DEFINE_TYPE(struct failpoint,
|
|
failpoint_keyof, failpoint_hash, failpoint_eq,
|
|
failhash);
|
|
|
|
static struct failhash failhash;
|
|
|
|
static void update_core(struct core_state *core, const struct peer *peer)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = core->num_outputs; i < ARRAY_SIZE(core->outputs); i++)
|
|
assert(core->outputs[i] == 0);
|
|
|
|
core->has_current_htlc = peer->current_htlc.htlc.id != -1;
|
|
core->state = peer->state;
|
|
BUILD_ASSERT(STATE_MAX < 256);
|
|
core->peercond = peer->cond;
|
|
core->capped_htlcs_to_us = cap(peer->num_htlcs_to_us);
|
|
core->capped_htlcs_to_them = cap(peer->num_htlcs_to_them);
|
|
core->capped_live_htlcs_to_us = cap(peer->num_live_htlcs_to_us);
|
|
core->capped_live_htlcs_to_them = cap(peer->num_live_htlcs_to_them);
|
|
core->capped_htlc_spends_to_us = cap(peer->num_htlc_spends_to_us);
|
|
core->capped_htlc_spends_to_them = cap(peer->num_htlc_spends_to_them);
|
|
core->valid = true;
|
|
}
|
|
|
|
static void situation_init(struct situation *sit,
|
|
const struct peer *peer)
|
|
{
|
|
if (streq(peer->name, "A")) {
|
|
sit->a.s = peer->core;
|
|
update_core(&sit->a.s, peer);
|
|
/* If we're still talking to peer, their state matters. */
|
|
if (peer->cond != PEER_CLOSED
|
|
|| peer->other->cond != PEER_CLOSED) {
|
|
sit->b.s = peer->other->core;
|
|
update_core(&sit->b.s, peer->other);
|
|
} else
|
|
memset(&sit->b.s, 0, sizeof(sit->b.s));
|
|
} else {
|
|
sit->b.s = peer->core;
|
|
update_core(&sit->b.s, peer);
|
|
/* If we're still talking to peer, their state matters. */
|
|
if (peer->cond != PEER_CLOSED
|
|
|| peer->other->cond != PEER_CLOSED) {
|
|
sit->a.s = peer->other->core;
|
|
update_core(&sit->a.s, peer->other);
|
|
} else
|
|
memset(&sit->a.s, 0, sizeof(sit->a.s));
|
|
}
|
|
}
|
|
|
|
|
|
/* Returns false if we've been here before. */
|
|
static bool sithash_update(struct sithash *sithash,
|
|
const struct peer *peer)
|
|
{
|
|
struct situation sit;
|
|
|
|
situation_init(&sit, peer);
|
|
|
|
if (sithash_get(sithash, &sit))
|
|
return false;
|
|
|
|
sithash_add(sithash, tal_dup(NULL, struct situation, &sit));
|
|
return true;
|
|
}
|
|
|
|
static void copy_peers(struct peer *dst, struct peer *other,
|
|
const struct peer *src)
|
|
{
|
|
*dst = *src;
|
|
*other = *src->other;
|
|
dst->other = other;
|
|
other->other = dst;
|
|
}
|
|
|
|
static const struct trail *clone_trail(const tal_t *ctx,
|
|
const struct trail *trail)
|
|
{
|
|
struct trail *t;
|
|
|
|
if (!trail)
|
|
return NULL;
|
|
|
|
t = tal_dup(ctx, struct trail, trail);
|
|
t->before = tal_dup(t, struct peer, trail->before);
|
|
t->after = trail->after ? tal_dup(t, struct peer, trail->after)
|
|
: NULL;
|
|
t->pkt_sent = trail->pkt_sent ? tal_strdup(t, trail->pkt_sent) : NULL;
|
|
t->prev = clone_trail(t, trail->prev);
|
|
return t;
|
|
}
|
|
|
|
static const union input dup_idata(const tal_t *ctx,
|
|
enum state_input input,
|
|
const union input *idata)
|
|
{
|
|
union input i;
|
|
|
|
if (input_is_pkt(input))
|
|
i.pkt = (Pkt *)tal_strdup(ctx, (const char *)idata->pkt);
|
|
else if (input == CMD_SEND_HTLC_ADD
|
|
|| input == CMD_SEND_HTLC_FULFILL
|
|
|| input == CMD_SEND_HTLC_FAIL) {
|
|
i.htlc_prog = tal_dup(ctx, struct htlc_progress,
|
|
idata->htlc_prog);
|
|
} else {
|
|
if (idata->htlc)
|
|
i.htlc = tal_dup(ctx, struct htlc, idata->htlc);
|
|
else
|
|
i.htlc = NULL;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
static bool fail(const struct peer *peer, enum failure which_fail)
|
|
{
|
|
struct failpoint *f = tal(NULL, struct failpoint), *old;
|
|
|
|
situation_init(&f->sit, peer);
|
|
f->which_fail = which_fail;
|
|
|
|
/* If we've been here before... */
|
|
old = failhash_get(&failhash, f);
|
|
if (old) {
|
|
tal_free(f);
|
|
/* If we haven't tried failing, try that now. */
|
|
if (old->details) {
|
|
old->details = tal_free(old->details);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* First time here, save details, don't fail yet. */
|
|
f->details = tal(f, struct fail_details);
|
|
/* Copy old peer, in case it has been changed since. */
|
|
copy_peers(&f->details->us, &f->details->other, peer->trail->before);
|
|
f->details->us.trail = clone_trail(f->details, peer->trail);
|
|
f->details->input = peer->current_input;
|
|
f->details->idata = dup_idata(f->details,
|
|
f->details->input, peer->current_idata);
|
|
|
|
failhash_add(&failhash, f);
|
|
return false;
|
|
}
|
|
|
|
static enum state_input input_by_name(const char *name)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; enum_state_input_names[i].name; i++) {
|
|
if (!strstarts(name, enum_state_input_names[i].name))
|
|
continue;
|
|
if (name[strlen(enum_state_input_names[i].name)] == '\0'
|
|
|| name[strlen(enum_state_input_names[i].name)] == ':')
|
|
return enum_state_input_names[i].v;
|
|
}
|
|
abort();
|
|
}
|
|
|
|
static Pkt *new_pkt(const tal_t *ctx, enum state_input i)
|
|
{
|
|
return (Pkt *)input_name(i);
|
|
}
|
|
|
|
static unsigned int htlc_id_from_pkt(const Pkt *pkt)
|
|
{
|
|
const char *s = strstr((const char *)pkt, ": HTLC #");
|
|
return s ? atoi(s + strlen(": HTLC #")) : -1U;
|
|
}
|
|
|
|
static Pkt *htlc_pkt(const tal_t *ctx, const char *prefix, unsigned int id)
|
|
{
|
|
assert(id != -1);
|
|
return (Pkt *)tal_fmt(ctx, "%s: HTLC #%u", prefix, id);
|
|
}
|
|
|
|
static unsigned int htlc_id_from_tx(const struct bitcoin_tx *tx)
|
|
{
|
|
const char *s = strstr((const char *)tx, "HTLC #");
|
|
return atoi(s + strlen("HTLC #"));
|
|
}
|
|
|
|
static struct bitcoin_tx *htlc_tx(const tal_t *ctx,
|
|
const char *prefix, unsigned int id)
|
|
{
|
|
return (struct bitcoin_tx *)tal_fmt(ctx, "%s HTLC #%u", prefix, id);
|
|
}
|
|
|
|
static struct htlc *find_any_htlc(const struct htlc *htlcs, size_t num,
|
|
unsigned id)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < num; i++)
|
|
if (htlcs[i].id == id)
|
|
return (struct htlc *)htlcs + i;
|
|
return NULL;
|
|
}
|
|
|
|
static struct htlc *find_htlc(const struct peer *peer, unsigned id)
|
|
{
|
|
const struct htlc *h;
|
|
|
|
h = find_any_htlc(peer->htlcs_to_us, peer->num_htlcs_to_us, id);
|
|
if (!h)
|
|
h = find_any_htlc(peer->htlcs_to_them,
|
|
peer->num_htlcs_to_them, id);
|
|
return (struct htlc *)h;
|
|
}
|
|
|
|
static struct htlc *find_live_htlc(const struct peer *peer,
|
|
unsigned id)
|
|
{
|
|
const struct htlc *h;
|
|
|
|
h = find_any_htlc(peer->live_htlcs_to_us, peer->num_live_htlcs_to_us,
|
|
id);
|
|
if (!h)
|
|
h = find_any_htlc(peer->live_htlcs_to_them,
|
|
peer->num_live_htlcs_to_them, id);
|
|
return (struct htlc *)h;
|
|
}
|
|
|
|
static struct htlc *find_htlc_spend(const struct peer *peer,
|
|
unsigned id)
|
|
{
|
|
const struct htlc *h;
|
|
|
|
h = find_any_htlc(peer->htlc_spends_to_us,
|
|
peer->num_htlc_spends_to_us,
|
|
id);
|
|
if (!h)
|
|
h = find_any_htlc(peer->htlc_spends_to_them,
|
|
peer->num_htlc_spends_to_them, id);
|
|
return (struct htlc *)h;
|
|
}
|
|
|
|
/* FIXME: order functions correctly. */
|
|
static void report_trail(const struct trail *t, const char *problem);
|
|
|
|
static void set_current_htlc(struct peer *peer,
|
|
unsigned int id,
|
|
bool to_them, bool adding)
|
|
{
|
|
if (peer->current_htlc.htlc.id != -1)
|
|
report_trail(peer->trail, "Already have current htlc");
|
|
|
|
assert(id != -1);
|
|
peer->current_htlc.htlc.id = id;
|
|
peer->current_htlc.htlc.to_them = to_them;
|
|
peer->current_htlc.adding = adding;
|
|
}
|
|
|
|
static void clear_current_htlc(struct peer *peer)
|
|
{
|
|
if (peer->current_htlc.htlc.id == -1)
|
|
report_trail(peer->trail, "No current htlc");
|
|
|
|
peer->current_htlc.htlc.id = -1;
|
|
}
|
|
|
|
static bool rval_known(const struct peer *peer, unsigned int id)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < peer->num_rvals_known; i++)
|
|
if (peer->rvals_known[i] == id)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static void add_rval(struct peer *peer, unsigned int id)
|
|
{
|
|
if (!rval_known(peer, id)) {
|
|
assert(peer->num_rvals_known < ARRAY_SIZE(peer->rvals_known));
|
|
peer->rvals_known[peer->num_rvals_known++] = id;
|
|
}
|
|
}
|
|
|
|
Pkt *pkt_open(const tal_t *ctx, const struct peer *peer,
|
|
OpenChannel__AnchorOffer anchor)
|
|
{
|
|
return new_pkt(ctx, PKT_OPEN);
|
|
}
|
|
|
|
Pkt *pkt_anchor(const tal_t *ctx, const struct peer *peer)
|
|
{
|
|
return new_pkt(ctx, PKT_OPEN_ANCHOR);
|
|
}
|
|
|
|
Pkt *pkt_open_commit_sig(const tal_t *ctx, const struct peer *peer)
|
|
{
|
|
return new_pkt(ctx, PKT_OPEN_COMMIT_SIG);
|
|
}
|
|
|
|
Pkt *pkt_open_complete(const tal_t *ctx, const struct peer *peer)
|
|
{
|
|
return new_pkt(ctx, PKT_OPEN_COMPLETE);
|
|
}
|
|
|
|
Pkt *pkt_htlc_add(const tal_t *ctx, const struct peer *peer,
|
|
const struct htlc_progress *htlc_prog)
|
|
{
|
|
return htlc_pkt(ctx, "PKT_UPDATE_ADD_HTLC", htlc_prog->htlc.id);
|
|
}
|
|
|
|
Pkt *pkt_htlc_fulfill(const tal_t *ctx, const struct peer *peer,
|
|
const struct htlc_progress *htlc_prog)
|
|
{
|
|
return htlc_pkt(ctx, "PKT_UPDATE_FULFILL_HTLC", htlc_prog->htlc.id);
|
|
}
|
|
|
|
Pkt *pkt_htlc_fail(const tal_t *ctx, const struct peer *peer,
|
|
const struct htlc_progress *htlc_prog)
|
|
{
|
|
return htlc_pkt(ctx, "PKT_UPDATE_FAIL_HTLC", htlc_prog->htlc.id);
|
|
}
|
|
|
|
Pkt *pkt_update_accept(const tal_t *ctx, const struct peer *peer)
|
|
{
|
|
return htlc_pkt(ctx, "PKT_UPDATE_ACCEPT", peer->current_htlc.htlc.id);
|
|
}
|
|
|
|
Pkt *pkt_update_signature(const tal_t *ctx, const struct peer *peer)
|
|
{
|
|
return htlc_pkt(ctx, "PKT_UPDATE_SIGNATURE",
|
|
peer->current_htlc.htlc.id);
|
|
}
|
|
|
|
Pkt *pkt_update_complete(const tal_t *ctx, const struct peer *peer)
|
|
{
|
|
return htlc_pkt(ctx, "PKT_UPDATE_COMPLETE",
|
|
peer->current_htlc.htlc.id);
|
|
}
|
|
|
|
Pkt *pkt_err(const tal_t *ctx, const char *fmt, ...)
|
|
{
|
|
char *str;
|
|
va_list ap;
|
|
|
|
str = tal_strdup(ctx, "PKT_ERROR: ");
|
|
va_start(ap, fmt);
|
|
tal_append_vfmt(&str, fmt, ap);
|
|
va_end(ap);
|
|
|
|
return (Pkt *)str;
|
|
}
|
|
|
|
Pkt *pkt_close(const tal_t *ctx, const struct peer *peer)
|
|
{
|
|
return new_pkt(ctx, PKT_CLOSE);
|
|
}
|
|
|
|
Pkt *pkt_close_complete(const tal_t *ctx, const struct peer *peer)
|
|
{
|
|
return new_pkt(ctx, PKT_CLOSE_COMPLETE);
|
|
}
|
|
|
|
Pkt *pkt_close_ack(const tal_t *ctx, const struct peer *peer)
|
|
{
|
|
return new_pkt(ctx, PKT_CLOSE_ACK);
|
|
}
|
|
|
|
Pkt *pkt_err_unexpected(const tal_t *ctx, const Pkt *pkt)
|
|
{
|
|
return pkt_err("Unexpected pkt %s", (const char *)pkt);
|
|
}
|
|
|
|
Pkt *accept_pkt_open(const tal_t *ctx,
|
|
struct peer *peer,
|
|
const Pkt *pkt)
|
|
{
|
|
if (fail(peer, FAIL_ACCEPT_OPEN))
|
|
return pkt_err(ctx, "Error inject");
|
|
return NULL;
|
|
}
|
|
|
|
Pkt *accept_pkt_anchor(const tal_t *ctx, struct peer *peer, const Pkt *pkt)
|
|
{
|
|
if (fail(peer, FAIL_ACCEPT_ANCHOR))
|
|
return pkt_err(ctx, "Error inject");
|
|
return NULL;
|
|
}
|
|
|
|
Pkt *accept_pkt_open_commit_sig(const tal_t *ctx,
|
|
struct peer *peer,
|
|
const Pkt *pkt)
|
|
{
|
|
if (fail(peer, FAIL_ACCEPT_OPEN_COMMIT_SIG))
|
|
return pkt_err(ctx, "Error inject");
|
|
return NULL;
|
|
}
|
|
|
|
Pkt *accept_pkt_open_complete(const tal_t *ctx,
|
|
struct peer *peer,
|
|
const Pkt *pkt)
|
|
{
|
|
if (fail(peer, FAIL_ACCEPT_OPEN_COMPLETE))
|
|
return pkt_err(ctx, "Error inject");
|
|
return NULL;
|
|
}
|
|
|
|
Pkt *accept_pkt_htlc_add(const tal_t *ctx,
|
|
struct peer *peer, const Pkt *pkt,
|
|
Pkt **decline)
|
|
{
|
|
if (fail(peer, FAIL_ACCEPT_HTLC_ADD))
|
|
return pkt_err(ctx, "Error inject");
|
|
|
|
/* This is the current htlc: If they propose it, it's to us. */
|
|
set_current_htlc(peer, htlc_id_from_pkt(pkt), false, true);
|
|
|
|
if (fail(peer, FAIL_DECLINE_HTLC))
|
|
*decline = new_pkt(ctx, PKT_UPDATE_DECLINE_HTLC);
|
|
else
|
|
*decline = NULL;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
Pkt *accept_pkt_htlc_fail(const tal_t *ctx,
|
|
struct peer *peer, const Pkt *pkt)
|
|
{
|
|
unsigned int id = htlc_id_from_pkt(pkt);
|
|
const struct htlc *h = find_htlc(peer, id);
|
|
|
|
if (fail(peer, FAIL_ACCEPT_HTLC_FAIL))
|
|
return pkt_err(ctx, "Error inject");
|
|
|
|
/* The shouldn't fail unless it's to them */
|
|
assert(h->to_them);
|
|
|
|
/* This is the current htlc */
|
|
set_current_htlc(peer, h->id, h->to_them, false);
|
|
return NULL;
|
|
}
|
|
|
|
Pkt *accept_pkt_htlc_fulfill(const tal_t *ctx,
|
|
struct peer *peer, const Pkt *pkt)
|
|
{
|
|
unsigned int id = htlc_id_from_pkt(pkt);
|
|
const struct htlc *h = find_htlc(peer, id);
|
|
|
|
if (fail(peer, FAIL_ACCEPT_HTLC_FULFILL))
|
|
return pkt_err(ctx, "Error inject");
|
|
|
|
/* The shouldn't complete unless it's to them */
|
|
assert(h->to_them);
|
|
|
|
/* This gives us the r value. */
|
|
add_rval(peer, htlc_id_from_pkt(pkt));
|
|
|
|
/* This is the current htlc */
|
|
set_current_htlc(peer, h->id, h->to_them, false);
|
|
return NULL;
|
|
}
|
|
|
|
Pkt *accept_pkt_update_accept(const tal_t *ctx,
|
|
struct peer *peer, const Pkt *pkt)
|
|
{
|
|
unsigned int id = htlc_id_from_pkt(pkt);
|
|
|
|
assert(id == peer->current_htlc.htlc.id);
|
|
|
|
if (fail(peer, FAIL_ACCEPT_UPDATE_ACCEPT))
|
|
return pkt_err(ctx, "Error inject");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
Pkt *accept_pkt_update_complete(const tal_t *ctx,
|
|
struct peer *peer, const Pkt *pkt)
|
|
{
|
|
unsigned int id = htlc_id_from_pkt(pkt);
|
|
|
|
assert(id == peer->current_htlc.htlc.id);
|
|
|
|
if (fail(peer, FAIL_ACCEPT_UPDATE_COMPLETE))
|
|
return pkt_err(ctx, "Error inject");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
Pkt *accept_pkt_update_signature(const tal_t *ctx,
|
|
struct peer *peer, const Pkt *pkt)
|
|
{
|
|
unsigned int id = htlc_id_from_pkt(pkt);
|
|
|
|
assert(id == peer->current_htlc.htlc.id);
|
|
|
|
if (fail(peer, FAIL_ACCEPT_UPDATE_SIGNATURE))
|
|
return pkt_err(ctx, "Error inject");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
Pkt *accept_pkt_close(const tal_t *ctx, struct peer *peer, const Pkt *pkt)
|
|
{
|
|
if (fail(peer, FAIL_ACCEPT_CLOSE))
|
|
return pkt_err(ctx, "Error inject");
|
|
return NULL;
|
|
}
|
|
|
|
Pkt *accept_pkt_close_complete(const tal_t *ctx,
|
|
struct peer *peer, const Pkt *pkt)
|
|
{
|
|
if (fail(peer, FAIL_ACCEPT_CLOSE_COMPLETE))
|
|
return pkt_err(ctx, "Error inject");
|
|
return NULL;
|
|
}
|
|
|
|
Pkt *accept_pkt_simultaneous_close(const tal_t *ctx,
|
|
struct peer *peer,
|
|
const Pkt *pkt)
|
|
{
|
|
if (fail(peer, FAIL_ACCEPT_SIMULTANEOUS_CLOSE))
|
|
return pkt_err(ctx, "Error inject");
|
|
return NULL;
|
|
}
|
|
|
|
Pkt *accept_pkt_close_ack(const tal_t *ctx,
|
|
struct peer *peer, const Pkt *pkt)
|
|
{
|
|
if (fail(peer, FAIL_ACCEPT_CLOSE_ACK))
|
|
return pkt_err(ctx, "Error inject");
|
|
return NULL;
|
|
}
|
|
|
|
static struct bitcoin_tx *bitcoin_tx(const char *str)
|
|
{
|
|
return (struct bitcoin_tx *)str;
|
|
}
|
|
|
|
static bool bitcoin_tx_is(const struct bitcoin_tx *btx, const char *str)
|
|
{
|
|
return streq((const char *)btx, str);
|
|
}
|
|
|
|
const struct bitcoin_tx *bitcoin_anchor(const tal_t *ctx, struct peer *peer)
|
|
{
|
|
if (!peer->anchor)
|
|
report_trail(peer->trail, "Can't create anchor tx: no anchor!");
|
|
peer->anchor = false;
|
|
peer->anchor_broadcast = true;
|
|
return bitcoin_tx("anchor");
|
|
}
|
|
|
|
const struct bitcoin_tx *bitcoin_close(const tal_t *ctx,
|
|
struct peer *peer)
|
|
{
|
|
peer->anchor_spent = true;
|
|
return bitcoin_tx("close");
|
|
}
|
|
|
|
const struct bitcoin_tx *bitcoin_spend_ours(const tal_t *ctx,
|
|
const struct peer *peer)
|
|
{
|
|
return bitcoin_tx("spend our commit");
|
|
}
|
|
|
|
const struct bitcoin_tx *bitcoin_spend_theirs(const tal_t *ctx,
|
|
const struct peer *peer,
|
|
const struct bitcoin_event *btc)
|
|
{
|
|
return bitcoin_tx("spend their commit");
|
|
}
|
|
|
|
const struct bitcoin_tx *bitcoin_steal(const tal_t *ctx,
|
|
const struct peer *peer,
|
|
struct bitcoin_event *btc)
|
|
{
|
|
if (fail(peer, FAIL_STEAL))
|
|
return NULL;
|
|
return bitcoin_tx("steal");
|
|
}
|
|
|
|
const struct bitcoin_tx *bitcoin_commit(const tal_t *ctx, struct peer *peer)
|
|
{
|
|
peer->anchor_spent = true;
|
|
return bitcoin_tx("our commit");
|
|
}
|
|
|
|
/* Create an HTLC refund collection */
|
|
const struct bitcoin_tx *bitcoin_htlc_timeout(const tal_t *ctx,
|
|
const struct peer *peer,
|
|
const struct htlc *htlc)
|
|
{
|
|
return htlc_tx(ctx, "htlc timeout", htlc->id);
|
|
}
|
|
|
|
/* Create an HTLC collection */
|
|
const struct bitcoin_tx *bitcoin_htlc_spend(const tal_t *ctx,
|
|
const struct peer *peer,
|
|
const struct htlc *htlc)
|
|
{
|
|
return htlc_tx(ctx, "htlc fulfill", htlc->id);
|
|
}
|
|
|
|
bool committed_to_htlcs(const struct peer *peer)
|
|
{
|
|
return peer->num_htlcs_to_them != 0 || peer->num_htlcs_to_us != 0;
|
|
}
|
|
|
|
#define TEST_STATE_COVERAGE 1
|
|
#include "state.c"
|
|
#include <ccan/tal/tal.h>
|
|
#include <stdio.h>
|
|
|
|
static void peer_init(struct peer *peer,
|
|
struct peer *other,
|
|
const char *name)
|
|
{
|
|
peer->cond = PEER_CMD_OK;
|
|
peer->state = STATE_INIT;
|
|
peer->core.num_outputs = 0;
|
|
peer->current_htlc.htlc.id = -1;
|
|
peer->num_htlcs_to_us = 0;
|
|
peer->num_htlcs_to_them = 0;
|
|
peer->num_live_htlcs_to_us = 0;
|
|
peer->num_live_htlcs_to_them = 0;
|
|
peer->num_htlc_spends_to_us = 0;
|
|
peer->num_htlc_spends_to_them = 0;
|
|
peer->num_rvals_known = 0;
|
|
peer->error = NULL;
|
|
peer->htlc_declined = false;
|
|
peer->anchor = false;
|
|
peer->anchor_broadcast = false;
|
|
peer->anchor_spent = false;
|
|
memset(peer->core.outputs, 0, sizeof(peer->core.outputs));
|
|
peer->pkt_data[0] = -1;
|
|
peer->core.current_command = INPUT_NONE;
|
|
peer->core.event_notifies = 0;
|
|
peer->name = name;
|
|
peer->other = other;
|
|
peer->trail = NULL;
|
|
memset(peer->core.pad, 0, sizeof(peer->core.pad));
|
|
}
|
|
|
|
/* Recursion! */
|
|
static void run_peer(const struct peer *peer,
|
|
bool normalpath, bool errorpath,
|
|
const struct trail *prev_trail,
|
|
struct hist *hist);
|
|
|
|
static void init_trail(struct trail *t,
|
|
enum state_input input,
|
|
const union input *idata,
|
|
const struct peer *before,
|
|
const struct trail *prev)
|
|
{
|
|
t->name = before->name;
|
|
t->prev = prev;
|
|
t->depth = prev ? prev->depth + 1 : 0;
|
|
t->input = input;
|
|
t->before = before;
|
|
t->after = NULL;
|
|
t->num_peer_outputs = -1;
|
|
t->pkt_sent = NULL;
|
|
if (input == CMD_SEND_HTLC_FULFILL
|
|
|| input == INPUT_RVALUE
|
|
|| input == BITCOIN_HTLC_TOTHEM_TIMEOUT
|
|
|| input == BITCOIN_HTLC_TOTHEM_SPENT
|
|
|| input == BITCOIN_HTLC_TOUS_TIMEOUT
|
|
|| input == BITCOIN_HTLC_FULFILL_SPEND_DONE
|
|
|| input == BITCOIN_HTLC_RETURN_SPEND_DONE)
|
|
t->htlc_id = idata->htlc->id;
|
|
else if (input == PKT_UPDATE_ADD_HTLC)
|
|
t->htlc_id = htlc_id_from_pkt(idata->pkt);
|
|
else
|
|
t->htlc_id = -1;
|
|
}
|
|
|
|
static void update_trail(struct trail *t,
|
|
const struct peer *after,
|
|
const Pkt *output)
|
|
{
|
|
t->after = after;
|
|
t->num_peer_outputs = after->other->core.num_outputs;
|
|
t->pkt_sent = (const char *)output;
|
|
}
|
|
|
|
static void report_trail_rev(const struct trail *t)
|
|
{
|
|
size_t i;
|
|
|
|
if (t->prev)
|
|
report_trail_rev(t->prev);
|
|
|
|
fprintf(stderr, "%s: %s(%i) %s -> %s",
|
|
t->name,
|
|
input_name(t->input), t->htlc_id,
|
|
state_name(t->before->state),
|
|
t->after ? state_name(t->after->state) : "<unknown>");
|
|
if (t->after) {
|
|
for (i = 0; i < t->after->core.num_outputs; i++)
|
|
fprintf(stderr, " >%s",
|
|
input_name(t->after->core.outputs[i]));
|
|
}
|
|
fprintf(stderr, " +%u in\n",
|
|
t->num_peer_outputs);
|
|
if (!t->after)
|
|
goto pkt_sent;
|
|
|
|
if (t->after->state >= STATE_CLOSED) {
|
|
if (t->after->num_live_htlcs_to_us
|
|
|| t->after->num_live_htlcs_to_them) {
|
|
fprintf(stderr, " Live HTLCs:");
|
|
for (i = 0; i < t->after->num_live_htlcs_to_us; i++)
|
|
fprintf(stderr, " <%u",
|
|
t->after->live_htlcs_to_us[i].id);
|
|
for (i = 0; i < t->after->num_live_htlcs_to_them; i++)
|
|
fprintf(stderr, " >%u",
|
|
t->after->live_htlcs_to_them[i].id);
|
|
fprintf(stderr, "\n");
|
|
}
|
|
if (t->after->num_htlc_spends_to_us
|
|
|| t->after->num_htlc_spends_to_them) {
|
|
fprintf(stderr, " HTLC spends:");
|
|
for (i = 0; i < t->after->num_htlc_spends_to_us; i++)
|
|
fprintf(stderr, " <%u",
|
|
t->after->htlc_spends_to_us[i].id);
|
|
for (i = 0; i < t->after->num_htlc_spends_to_them; i++)
|
|
fprintf(stderr, " <%u",
|
|
t->after->htlc_spends_to_them[i].id);
|
|
fprintf(stderr, "\n");
|
|
}
|
|
} else {
|
|
if (t->after->num_htlcs_to_us
|
|
|| t->after->num_htlcs_to_them) {
|
|
fprintf(stderr, " HTLCs:");
|
|
for (i = 0; i < t->after->num_htlcs_to_us; i++)
|
|
fprintf(stderr, " <%u",
|
|
t->after->htlcs_to_us[i].id);
|
|
for (i = 0; i < t->after->num_htlcs_to_them; i++)
|
|
fprintf(stderr, " >%u",
|
|
t->after->htlcs_to_them[i].id);
|
|
fprintf(stderr, "\n");
|
|
}
|
|
}
|
|
pkt_sent:
|
|
if (t->pkt_sent)
|
|
fprintf(stderr, " => %s\n", t->pkt_sent);
|
|
}
|
|
|
|
static void report_trail(const struct trail *t, const char *problem)
|
|
{
|
|
fprintf(stderr, "Error: %s\n", problem);
|
|
report_trail_rev(t);
|
|
exit(1);
|
|
}
|
|
|
|
static void add_htlc(struct htlc *to_us, unsigned int *num_to_us,
|
|
struct htlc *to_them, unsigned int *num_to_them,
|
|
size_t arrsize,
|
|
const struct htlc *h)
|
|
{
|
|
struct htlc *arr;
|
|
unsigned int *n;
|
|
|
|
if (h->to_them) {
|
|
arr = to_them;
|
|
n = num_to_them;
|
|
} else {
|
|
arr = to_us;
|
|
n = num_to_us;
|
|
}
|
|
assert(*n < arrsize);
|
|
arr[(*n)++] = *h;
|
|
}
|
|
|
|
static void remove_htlc(struct htlc *to_us, unsigned int *num_to_us,
|
|
struct htlc *to_them, unsigned int *num_to_them,
|
|
size_t arrsize,
|
|
const struct htlc *h)
|
|
{
|
|
size_t off;
|
|
struct htlc *arr;
|
|
unsigned int *n;
|
|
|
|
if (h->to_them) {
|
|
arr = to_them;
|
|
n = num_to_them;
|
|
} else {
|
|
arr = to_us;
|
|
n = num_to_us;
|
|
}
|
|
assert(*n <= arrsize);
|
|
assert(h >= arr && h < arr + *n);
|
|
|
|
off = h - arr;
|
|
memmove(arr + off, arr + off + 1, (char *)(arr + *n) - (char *)(h + 1));
|
|
(*n)--;
|
|
}
|
|
|
|
static void remove_htlc_id(struct peer *peer, unsigned int id)
|
|
{
|
|
const struct htlc *h = find_htlc(peer, id);
|
|
|
|
if (!h)
|
|
report_trail(peer->trail, "Removing nonexistent HTLC?");
|
|
if (h->to_them != peer->current_htlc.htlc.to_them)
|
|
report_trail(peer->trail,
|
|
"Removing disagreed about to_them");
|
|
remove_htlc(peer->htlcs_to_us, &peer->num_htlcs_to_us,
|
|
peer->htlcs_to_them, &peer->num_htlcs_to_them,
|
|
ARRAY_SIZE(peer->htlcs_to_us), h);
|
|
}
|
|
|
|
|
|
static bool outstanding_htlc_watches(const struct peer *peer)
|
|
{
|
|
return peer->num_live_htlcs_to_us
|
|
|| peer->num_live_htlcs_to_them
|
|
|| peer->num_htlc_spends_to_us
|
|
|| peer->num_htlc_spends_to_them;
|
|
}
|
|
|
|
static bool have_event(uint64_t events, enum state_input input)
|
|
{
|
|
return events & (1ULL << input);
|
|
}
|
|
|
|
static bool add_event_(struct peer *peer, enum state_input input)
|
|
{
|
|
/* This is how they say "no event please" */
|
|
if (input == INPUT_NONE)
|
|
return true;
|
|
|
|
assert(input < 64);
|
|
if (have_event(peer->core.event_notifies, input))
|
|
return false;
|
|
peer->core.event_notifies |= (1ULL << input);
|
|
return true;
|
|
}
|
|
|
|
static bool remove_event_(uint64_t *events, enum state_input input)
|
|
{
|
|
/* This is how they say "no event please" */
|
|
if (input == INPUT_NONE)
|
|
return true;
|
|
|
|
assert(input < 64);
|
|
if (!have_event(*events, input))
|
|
return false;
|
|
*events &= ~(1ULL << input);
|
|
return true;
|
|
}
|
|
|
|
static void remove_event(struct peer *peer, enum state_input input)
|
|
{
|
|
if (!remove_event_(&peer->core.event_notifies, input))
|
|
report_trail(peer->trail, "Removing event we don't have?");
|
|
}
|
|
|
|
static void add_event(struct peer *peer, enum state_input input)
|
|
{
|
|
if (!add_event_(peer, input))
|
|
report_trail(peer->trail, "Adding event we already have?");
|
|
}
|
|
|
|
void peer_watch_anchor(struct peer *peer,
|
|
enum state_input depthok,
|
|
enum state_input timeout,
|
|
enum state_input unspent,
|
|
enum state_input theyspent,
|
|
enum state_input otherspent)
|
|
{
|
|
/* We assume these values in activate_event. */
|
|
assert(timeout == BITCOIN_ANCHOR_TIMEOUT
|
|
|| timeout == INPUT_NONE);
|
|
assert(depthok == BITCOIN_ANCHOR_DEPTHOK);
|
|
|
|
add_event(peer, depthok);
|
|
add_event(peer, timeout);
|
|
add_event(peer, unspent);
|
|
add_event(peer, theyspent);
|
|
add_event(peer, otherspent);
|
|
}
|
|
|
|
void bitcoin_create_anchor(struct peer *peer, enum state_input done)
|
|
{
|
|
/* We assume this below */
|
|
assert(done == BITCOIN_ANCHOR_CREATED);
|
|
if (peer->anchor)
|
|
report_trail(peer->trail, "Anchor already created?");
|
|
|
|
peer->anchor = true;
|
|
add_event(peer, done);
|
|
}
|
|
|
|
void bitcoin_release_anchor(struct peer *peer, enum state_input done)
|
|
{
|
|
if (!peer->anchor)
|
|
report_trail(peer->trail, "Anchor not created?");
|
|
|
|
peer->anchor = false;
|
|
remove_event(peer, done);
|
|
}
|
|
|
|
void peer_unwatch_anchor_depth(struct peer *peer,
|
|
enum state_input depthok,
|
|
enum state_input timeout)
|
|
{
|
|
/* We assume these values in activate_event. */
|
|
assert(timeout == BITCOIN_ANCHOR_TIMEOUT
|
|
|| timeout == INPUT_NONE);
|
|
assert(depthok == BITCOIN_ANCHOR_DEPTHOK);
|
|
|
|
remove_event(peer, depthok);
|
|
remove_event(peer, timeout);
|
|
}
|
|
|
|
/* Wait for our commit to be spendable. */
|
|
void peer_watch_delayed(struct peer *peer,
|
|
const struct bitcoin_tx *tx, enum state_input canspend)
|
|
{
|
|
assert(bitcoin_tx_is(tx, "our commit"));
|
|
add_event(peer, canspend);
|
|
}
|
|
|
|
/* Wait for commit to be very deeply buried (so we no longer need to
|
|
* even watch) */
|
|
void peer_watch_tx(struct peer *peer,
|
|
const struct bitcoin_tx *tx,
|
|
enum state_input done)
|
|
{
|
|
/* We can have multiple steals or spendtheirs in flight, so
|
|
* allow repeats for
|
|
* BITCOIN_STEAL_DONE/BITCOIN_SPEND_THEIRS_DONE */
|
|
if (done == BITCOIN_STEAL_DONE) {
|
|
assert(bitcoin_tx_is(tx, "steal"));
|
|
add_event_(peer, done);
|
|
} else if (done == BITCOIN_SPEND_THEIRS_DONE) {
|
|
assert(bitcoin_tx_is(tx, "spend their commit"));
|
|
add_event_(peer, done);
|
|
} else if (done == BITCOIN_SPEND_OURS_DONE) {
|
|
assert(bitcoin_tx_is(tx, "spend our commit"));
|
|
add_event(peer, done);
|
|
} else
|
|
report_trail(peer->trail, "Unknown watch effect");
|
|
}
|
|
|
|
/* Other side should drop close tx; watch for it. */
|
|
void peer_watch_close(struct peer *peer,
|
|
enum state_input done, enum state_input timedout)
|
|
{
|
|
add_event(peer, done);
|
|
|
|
/* We assume this. */
|
|
assert(timedout == INPUT_CLOSE_COMPLETE_TIMEOUT || timedout == INPUT_NONE);
|
|
add_event(peer, timedout);
|
|
}
|
|
|
|
void peer_unwatch_close_timeout(struct peer *peer, enum state_input timedout)
|
|
{
|
|
assert(timedout == INPUT_CLOSE_COMPLETE_TIMEOUT);
|
|
if (!mapping_inputs)
|
|
remove_event(peer, timedout);
|
|
}
|
|
|
|
bool peer_watch_our_htlc_outputs(struct peer *peer,
|
|
const struct bitcoin_tx *tx,
|
|
enum state_input tous_timeout,
|
|
enum state_input tothem_spent,
|
|
enum state_input tothem_timeout)
|
|
{
|
|
/* FIXME: We assume these. */
|
|
assert(tous_timeout == BITCOIN_HTLC_TOUS_TIMEOUT);
|
|
assert(tothem_spent == BITCOIN_HTLC_TOTHEM_SPENT);
|
|
assert(tothem_timeout == BITCOIN_HTLC_TOTHEM_TIMEOUT);
|
|
|
|
if (!peer->num_htlcs_to_us && !peer->num_htlcs_to_them)
|
|
return false;
|
|
|
|
assert(peer->num_live_htlcs_to_us + peer->num_htlcs_to_us
|
|
<= ARRAY_SIZE(peer->live_htlcs_to_us));
|
|
assert(peer->num_live_htlcs_to_them + peer->num_htlcs_to_them
|
|
<= ARRAY_SIZE(peer->live_htlcs_to_them));
|
|
memcpy(peer->live_htlcs_to_us + peer->num_live_htlcs_to_us,
|
|
peer->htlcs_to_us,
|
|
peer->num_htlcs_to_us * sizeof(peer->htlcs_to_us[0]));
|
|
memcpy(peer->live_htlcs_to_them + peer->num_live_htlcs_to_them,
|
|
peer->htlcs_to_them,
|
|
peer->num_htlcs_to_them * sizeof(peer->htlcs_to_them[0]));
|
|
peer->num_live_htlcs_to_us += peer->num_htlcs_to_us;
|
|
peer->num_live_htlcs_to_them += peer->num_htlcs_to_them;
|
|
/* Can happen if we were finished, then new commit tx */
|
|
remove_event_(&peer->core.event_notifies, INPUT_NO_MORE_HTLCS);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool peer_watch_their_htlc_outputs(struct peer *peer,
|
|
const struct bitcoin_event *tx,
|
|
enum state_input tous_timeout,
|
|
enum state_input tothem_spent,
|
|
enum state_input tothem_timeout)
|
|
{
|
|
size_t i;
|
|
struct htlc *htlcs;
|
|
|
|
/* We assume these. */
|
|
assert(tous_timeout == BITCOIN_HTLC_TOUS_TIMEOUT);
|
|
assert(tothem_spent == BITCOIN_HTLC_TOTHEM_SPENT);
|
|
assert(tothem_timeout == BITCOIN_HTLC_TOTHEM_TIMEOUT);
|
|
|
|
/* It's what our peer thinks is current... */
|
|
if (!peer->other->num_htlcs_to_them && !peer->other->num_htlcs_to_us)
|
|
return false;
|
|
|
|
assert(peer->num_live_htlcs_to_us + peer->other->num_htlcs_to_them
|
|
<= ARRAY_SIZE(peer->live_htlcs_to_us));
|
|
assert(peer->num_live_htlcs_to_them + peer->other->num_htlcs_to_us
|
|
<= ARRAY_SIZE(peer->live_htlcs_to_them));
|
|
|
|
/* Copy from other peer, but reverse perspective */
|
|
htlcs = peer->live_htlcs_to_us + peer->num_live_htlcs_to_us;
|
|
memcpy(htlcs, peer->other->htlcs_to_them,
|
|
peer->other->num_htlcs_to_them * sizeof(peer->htlcs_to_us[0]));
|
|
for (i = 0; i < peer->other->num_htlcs_to_them; i++) {
|
|
assert(htlcs[i].to_them);
|
|
htlcs[i].to_them = false;
|
|
}
|
|
|
|
htlcs = peer->live_htlcs_to_them + peer->num_live_htlcs_to_them;
|
|
memcpy(htlcs,
|
|
peer->other->htlcs_to_us,
|
|
peer->other->num_htlcs_to_us * sizeof(peer->htlcs_to_them[0]));
|
|
for (i = 0; i < peer->other->num_htlcs_to_us; i++) {
|
|
assert(!htlcs[i].to_them);
|
|
htlcs[i].to_them = true;
|
|
}
|
|
|
|
peer->num_live_htlcs_to_us += peer->other->num_htlcs_to_them;
|
|
peer->num_live_htlcs_to_them += peer->other->num_htlcs_to_us;
|
|
/* Can happen if we were finished, then new commit tx */
|
|
remove_event_(&peer->core.event_notifies, INPUT_NO_MORE_HTLCS);
|
|
|
|
return true;
|
|
}
|
|
|
|
void peer_unwatch_htlc_output(struct peer *peer,
|
|
const struct htlc *htlc,
|
|
enum state_input all_done)
|
|
{
|
|
const struct htlc *h;
|
|
|
|
/* We assume this. */
|
|
assert(all_done == INPUT_NO_MORE_HTLCS);
|
|
|
|
h = find_live_htlc(peer, htlc->id);
|
|
|
|
/* That can fail, when we see them spend (and thus stop
|
|
* watching) after we've timed out, then our return tx wins
|
|
* and gets buried. */
|
|
if (h) {
|
|
remove_htlc(peer->live_htlcs_to_us,
|
|
&peer->num_live_htlcs_to_us,
|
|
peer->live_htlcs_to_them,
|
|
&peer->num_live_htlcs_to_them,
|
|
ARRAY_SIZE(peer->live_htlcs_to_us),
|
|
h);
|
|
|
|
/* If that was last, fire INPUT_NO_MORE_HTLCS */
|
|
if (!outstanding_htlc_watches(peer))
|
|
add_event(peer, all_done);
|
|
}
|
|
}
|
|
|
|
void peer_unwatch_all_htlc_outputs(struct peer *peer)
|
|
{
|
|
/* This can happen if we get in front of INPUT_NO_MORE_HTLCS */
|
|
if (!outstanding_htlc_watches(peer)
|
|
&& !have_event(peer->core.event_notifies, INPUT_NO_MORE_HTLCS))
|
|
report_trail(peer->trail, "unwatching all with no htlcs?");
|
|
|
|
peer->num_htlc_spends_to_us = 0;
|
|
peer->num_htlc_spends_to_them = 0;
|
|
peer->num_live_htlcs_to_us = 0;
|
|
peer->num_live_htlcs_to_them = 0;
|
|
}
|
|
|
|
void peer_watch_htlc_spend(struct peer *peer,
|
|
const struct bitcoin_tx *tx,
|
|
const struct htlc *htlc,
|
|
enum state_input done)
|
|
{
|
|
struct htlc *h = find_live_htlc(peer, htlc_id_from_tx(tx));
|
|
|
|
add_htlc(peer->htlc_spends_to_us, &peer->num_htlc_spends_to_us,
|
|
peer->htlc_spends_to_them,
|
|
&peer->num_htlc_spends_to_them,
|
|
ARRAY_SIZE(peer->htlc_spends_to_us),
|
|
h);
|
|
|
|
/* We assume this */
|
|
if (h->to_them)
|
|
assert(done == BITCOIN_HTLC_RETURN_SPEND_DONE);
|
|
else
|
|
assert(done == BITCOIN_HTLC_FULFILL_SPEND_DONE);
|
|
}
|
|
|
|
void peer_unwatch_htlc_spend(struct peer *peer,
|
|
const struct htlc *htlc,
|
|
enum state_input all_done)
|
|
{
|
|
struct htlc *h = find_htlc_spend(peer, htlc->id);
|
|
|
|
assert(all_done == INPUT_NO_MORE_HTLCS);
|
|
remove_htlc(peer->htlc_spends_to_us,
|
|
&peer->num_htlc_spends_to_us,
|
|
peer->htlc_spends_to_them,
|
|
&peer->num_htlc_spends_to_them,
|
|
ARRAY_SIZE(peer->htlc_spends_to_us),
|
|
h);
|
|
|
|
if (!outstanding_htlc_watches(peer))
|
|
add_event(peer, all_done);
|
|
}
|
|
|
|
void peer_unexpected_pkt(struct peer *peer, const Pkt *pkt)
|
|
{
|
|
const char *str = (const char *)pkt;
|
|
|
|
/* We can get errors. */
|
|
if (strstarts(str, "PKT_ERROR:"))
|
|
return;
|
|
|
|
/* Shouldn't get any other unexpected packets. */
|
|
report_trail(peer->trail, "Unexpected packet");
|
|
}
|
|
|
|
/* Called when their update overrides our update cmd. */
|
|
void peer_htlc_ours_deferred(struct peer *peer)
|
|
{
|
|
/* Only happens for HTLC commands of low priority. */
|
|
if (high_priority(peer->state))
|
|
report_trail(peer->trail, "Defer while high priority");
|
|
|
|
if (peer->current_htlc.htlc.id == -1)
|
|
report_trail(peer->trail, "Deferred with no current HTLC");
|
|
if (!input_is(peer->core.current_command, CMD_SEND_UPDATE_ANY))
|
|
report_trail(peer->trail, "Deferred their HTLC?");
|
|
|
|
/* FIXME: Expect CMD_REQUEUE */
|
|
clear_current_htlc(peer);
|
|
}
|
|
|
|
/* Successfully added/fulfilled/timedout/fail an HTLC. */
|
|
void peer_htlc_done(struct peer *peer)
|
|
{
|
|
if (peer->current_htlc.htlc.id == -1)
|
|
report_trail(peer->trail, "Adding with no current HTLC");
|
|
|
|
if (peer->current_htlc.adding) {
|
|
add_htlc(peer->htlcs_to_us, &peer->num_htlcs_to_us,
|
|
peer->htlcs_to_them, &peer->num_htlcs_to_them,
|
|
ARRAY_SIZE(peer->htlcs_to_us),
|
|
&peer->current_htlc.htlc);
|
|
} else {
|
|
remove_htlc_id(peer, peer->current_htlc.htlc.id);
|
|
}
|
|
clear_current_htlc(peer);
|
|
}
|
|
|
|
/* Someone aborted an existing HTLC update. */
|
|
void peer_htlc_aborted(struct peer *peer)
|
|
{
|
|
if (peer->current_htlc.htlc.id == -1)
|
|
report_trail(peer->trail, "Abort with no current HTLC");
|
|
clear_current_htlc(peer);
|
|
}
|
|
|
|
void peer_htlc_declined(struct peer *peer, const Pkt *pkt)
|
|
{
|
|
if (peer->current_htlc.htlc.id == -1)
|
|
report_trail(peer->trail, "Decline with no current HTLC");
|
|
if (!peer->current_htlc.adding)
|
|
report_trail(peer->trail, "Decline but HTLC not marked for add");
|
|
clear_current_htlc(peer);
|
|
peer->htlc_declined = true;
|
|
}
|
|
|
|
const struct htlc *peer_tx_revealed_r_value(struct peer *peer,
|
|
const struct bitcoin_event *btc)
|
|
{
|
|
const struct htlc *htlc = (struct htlc *)btc;
|
|
add_rval(peer, htlc->id);
|
|
return htlc;
|
|
}
|
|
|
|
static const char *check_changes(const struct peer *old, struct peer *new,
|
|
enum state_input input)
|
|
{
|
|
if (new->cond != old->cond) {
|
|
/* Only BUSY -> CMD_OK can go backwards. */
|
|
if (!(old->cond == PEER_BUSY && new->cond == PEER_CMD_OK))
|
|
if (new->cond < old->cond)
|
|
return tal_fmt(NULL, "cond from %u to %u",
|
|
old->cond, new->cond);
|
|
}
|
|
if (new->cond == PEER_CLOSING
|
|
|| new->cond == PEER_CLOSED) {
|
|
if (new->core.current_command != INPUT_NONE)
|
|
return tal_fmt(NULL,
|
|
"cond CLOSE with pending command %s",
|
|
input_name(new->core.current_command));
|
|
if (new->current_htlc.htlc.id != -1)
|
|
return tal_fmt(NULL,
|
|
"cond CLOSE with pending htlc");
|
|
if (new->anchor)
|
|
return tal_fmt(NULL,
|
|
"cond CLOSE with anchor");
|
|
}
|
|
if (new->cond == PEER_CLOSED) {
|
|
/* Should not have pending close timeout. */
|
|
if (have_event(new->core.event_notifies,
|
|
INPUT_CLOSE_COMPLETE_TIMEOUT))
|
|
return tal_fmt(NULL, "CLOSED with pending close timeout");
|
|
/* We must not leave anchor dangling, if we broadcast it. */
|
|
if (new->anchor_broadcast && !new->anchor_spent)
|
|
return tal_fmt(NULL, "CLOSED with unspent anchor?");
|
|
}
|
|
|
|
if (input == PKT_ERROR) {
|
|
/* We should stop talking to them after error recvd. */
|
|
if (new->cond != PEER_CLOSING
|
|
&& new->cond != PEER_CLOSED)
|
|
return "packets still open after error pkt";
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static const char *check_all_effects(const struct peer *old,
|
|
enum command_status cstatus,
|
|
enum state_input input,
|
|
struct peer *peer,
|
|
Pkt *output)
|
|
{
|
|
if (cstatus != CMD_NONE) {
|
|
assert(peer->core.current_command != INPUT_NONE);
|
|
/* We should only requeue HTLCs if we're lowprio */
|
|
if (cstatus == CMD_REQUEUE) {
|
|
if (high_priority(old->state))
|
|
return "CMD_REQUEUE despite high prio state";
|
|
else if (!input_is(peer->core.current_command,
|
|
CMD_SEND_UPDATE_ANY))
|
|
return tal_fmt(NULL, "CMD_REQUEUE on cmd %s",
|
|
input_name(peer->core.current_command));
|
|
/* We expect to be replaced by their HTLC
|
|
* (unless we inject an error!). */
|
|
if (peer->current_htlc.htlc.id == -1
|
|
&& peer->cond != PEER_CLOSED
|
|
&& !peer->htlc_declined)
|
|
return tal_fmt(NULL, "CMD_REQUEUE but no new htlc?");
|
|
} else {
|
|
/* Expect no HTLCs in other cases. */
|
|
if (peer->current_htlc.htlc.id != -1)
|
|
return tal_fmt(NULL, "%s but still have htlc?",
|
|
cstatus_name(cstatus));
|
|
}
|
|
peer->core.current_command = INPUT_NONE;
|
|
}
|
|
|
|
if (output) {
|
|
const char *pkt = (const char *)output;
|
|
/* Check for errors. */
|
|
if (strstarts(pkt, "PKT_ERROR: ")) {
|
|
/* Some are expected. */
|
|
if (!streq(pkt, "PKT_ERROR: Commit tx noticed")
|
|
&& !streq(pkt, "PKT_ERROR: Otherspend noticed")
|
|
&& !streq(pkt, "PKT_ERROR: Error inject")
|
|
&& !streq(pkt, "PKT_ERROR: Anchor timed out")
|
|
&& !streq(pkt, "PKT_ERROR: Close timed out")
|
|
&& !streq(pkt, "PKT_ERROR: Close forced due to HTLCs")) {
|
|
return pkt;
|
|
}
|
|
}
|
|
if (peer->core.num_outputs >= ARRAY_SIZE(peer->core.outputs))
|
|
return "Too many outputs";
|
|
peer->core.outputs[peer->core.num_outputs]
|
|
= input_by_name(pkt);
|
|
peer->pkt_data[peer->core.num_outputs++]
|
|
= htlc_id_from_pkt(output);
|
|
}
|
|
|
|
if (peer->state >= STATE_CLOSE_WAIT_STEAL
|
|
&& peer->state <= STATE_CLOSE_WAIT_STEAL_SPENDTHEM_CLOSE_SPENDOURS_WITH_HTLCS) {
|
|
if (STATE_TO_BITS(peer->state) & STATE_CLOSE_HTLCS_BIT) {
|
|
if (!outstanding_htlc_watches(peer)
|
|
&& !have_event(peer->core.event_notifies, INPUT_NO_MORE_HTLCS))
|
|
return "CLOSE_HTLCS with no outstanding watches?";
|
|
}
|
|
}
|
|
|
|
return check_changes(old, peer, input);
|
|
}
|
|
|
|
static void eliminate_input(enum state_input **inputs, enum state_input in)
|
|
{
|
|
size_t i, n = tal_count(*inputs);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
if ((*inputs)[i] != in)
|
|
continue;
|
|
|
|
if (i != n-1)
|
|
(*inputs)[i] = (*inputs)[n-1];
|
|
tal_resize(inputs, n - 1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static bool find_output(const enum state_input *outputs, enum state_input out)
|
|
{
|
|
size_t n, i;
|
|
|
|
n = tal_count(outputs);
|
|
for (i = 0; i < n; i++)
|
|
if (outputs[i] == out)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static void record_output(enum state_input **outputs, enum state_input out)
|
|
{
|
|
size_t n;
|
|
|
|
if (find_output(*outputs, out))
|
|
return;
|
|
|
|
n = tal_count(*outputs);
|
|
tal_resize(outputs, n+1);
|
|
(*outputs)[n] = out;
|
|
}
|
|
|
|
static void record_state(struct state_dump **sd,
|
|
enum state_input input,
|
|
enum state newstate,
|
|
const char *pktstr)
|
|
{
|
|
size_t i, n = tal_count(*sd);
|
|
enum state_input pkt;
|
|
|
|
if (!pktstr)
|
|
pkt = INPUT_NONE;
|
|
else
|
|
pkt = input_by_name(pktstr);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
if ((*sd)[i].input != input)
|
|
continue;
|
|
if ((*sd)[i].next != newstate)
|
|
continue;
|
|
if ((*sd)[i].pkt != pkt)
|
|
continue;
|
|
/* Duplicate. */
|
|
return;
|
|
}
|
|
tal_resize(sd, n+1);
|
|
(*sd)[n].input = input;
|
|
(*sd)[n].next = newstate;
|
|
(*sd)[n].pkt = pkt;
|
|
}
|
|
|
|
static bool error_path(enum state_input i, enum state src, enum state dst)
|
|
{
|
|
return state_is_error(dst) || i == PKT_ERROR;
|
|
}
|
|
|
|
static bool normal_path(enum state_input i, enum state src, enum state dst)
|
|
{
|
|
if (error_path(i, src, dst))
|
|
return false;
|
|
|
|
/* Weird inputs. */
|
|
if (i == BITCOIN_ANCHOR_TIMEOUT
|
|
|| i == BITCOIN_ANCHOR_UNSPENT
|
|
|| i == BITCOIN_ANCHOR_THEIRSPEND
|
|
|| i == BITCOIN_ANCHOR_OTHERSPEND
|
|
|| i == BITCOIN_STEAL_DONE
|
|
|| i == PKT_UPDATE_DECLINE_HTLC
|
|
|| i == PKT_UPDATE_FAIL_HTLC
|
|
|| i == INPUT_CLOSE_COMPLETE_TIMEOUT)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* These clutter the graph, so only handle from normal state. */
|
|
static bool too_cluttered(enum state_input i, enum state src)
|
|
{
|
|
if (i == CMD_CLOSE || i == PKT_CLOSE || i == PKT_UPDATE_ADD_HTLC || i == PKT_UPDATE_FULFILL_HTLC)
|
|
return src != STATE_NORMAL_LOWPRIO
|
|
&& src != STATE_NORMAL_HIGHPRIO;
|
|
return false;
|
|
}
|
|
|
|
static void add_dot(struct edge_hash *hash,
|
|
const char *oldstate,
|
|
const char *newstate,
|
|
enum state_input i,
|
|
const Pkt *pkt)
|
|
{
|
|
struct dot_edge *d = tal(NULL, struct dot_edge);
|
|
d->oldstate = oldstate;
|
|
d->newstate = newstate;
|
|
d->i = i;
|
|
if (pkt)
|
|
d->pkt = tal_strdup(d, (const char *)pkt);
|
|
else
|
|
d->pkt = NULL;
|
|
|
|
if (edge_hash_get(hash, d))
|
|
tal_free(d);
|
|
else
|
|
edge_hash_add(hash, d);
|
|
}
|
|
|
|
static const char *simplify_state(enum state s)
|
|
{
|
|
/* Turn all high prio into low prio, and merge some open states */
|
|
switch (s) {
|
|
case STATE_OPEN_WAITING_OURANCHOR:
|
|
case STATE_OPEN_WAITING_THEIRANCHOR:
|
|
return "STATE_OPEN_WAITING";
|
|
|
|
case STATE_OPEN_WAIT_FOR_COMPLETE_OURANCHOR:
|
|
case STATE_OPEN_WAIT_FOR_COMPLETE_THEIRANCHOR:
|
|
return "STATE_OPEN_WAIT_FOR_COMPLETE";
|
|
|
|
case STATE_NORMAL_LOWPRIO:
|
|
case STATE_NORMAL_HIGHPRIO:
|
|
return "STATE_NORMAL";
|
|
|
|
case STATE_WAIT_FOR_HTLC_ACCEPT_LOWPRIO:
|
|
case STATE_WAIT_FOR_HTLC_ACCEPT_HIGHPRIO:
|
|
return "STATE_WAIT_FOR_HTLC_ACCEPT";
|
|
|
|
case STATE_WAIT_FOR_UPDATE_COMPLETE_LOWPRIO:
|
|
case STATE_WAIT_FOR_UPDATE_COMPLETE_HIGHPRIO:
|
|
return "STATE_WAIT_FOR_UPDATE_COMPLETE";
|
|
|
|
case STATE_WAIT_FOR_UPDATE_SIG_LOWPRIO:
|
|
case STATE_WAIT_FOR_UPDATE_SIG_HIGHPRIO:
|
|
return "STATE_WAIT_FOR_UPDATE_SIG";
|
|
|
|
default:
|
|
return state_name(s);
|
|
}
|
|
}
|
|
|
|
static bool waiting_statepair(enum state a, enum state b)
|
|
{
|
|
/* We don't need inputs if we're waiting for anchors. */
|
|
if (a == STATE_OPEN_WAITING_OURANCHOR
|
|
|| a == STATE_OPEN_WAITING_OURANCHOR_THEYCOMPLETED
|
|
|| a == STATE_OPEN_WAITING_THEIRANCHOR
|
|
|| a == STATE_OPEN_WAITING_THEIRANCHOR_THEYCOMPLETED
|
|
|| a == STATE_OPEN_WAIT_FOR_ANCHOR_CREATE)
|
|
return true;
|
|
|
|
if (b == STATE_OPEN_WAITING_OURANCHOR
|
|
|| b == STATE_OPEN_WAITING_OURANCHOR_THEYCOMPLETED
|
|
|| b == STATE_OPEN_WAITING_THEIRANCHOR
|
|
|| b == STATE_OPEN_WAITING_THEIRANCHOR_THEYCOMPLETED
|
|
|| b == STATE_OPEN_WAIT_FOR_ANCHOR_CREATE)
|
|
return true;
|
|
|
|
/* We don't need inputs at start of main loop. */
|
|
if (a == STATE_NORMAL_LOWPRIO
|
|
&& b == STATE_NORMAL_HIGHPRIO)
|
|
return true;
|
|
|
|
if (a == STATE_NORMAL_HIGHPRIO
|
|
&& b == STATE_NORMAL_LOWPRIO)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool has_packets(const struct peer *peer)
|
|
{
|
|
return peer->core.num_outputs != 0;
|
|
}
|
|
|
|
static void try_input(const struct peer *peer,
|
|
enum state_input i,
|
|
const union input *idata,
|
|
bool normalpath, bool errorpath,
|
|
const struct trail *prev_trail,
|
|
struct hist *hist)
|
|
{
|
|
struct peer copy, other;
|
|
struct trail t;
|
|
const char *problem;
|
|
Pkt *output;
|
|
const struct bitcoin_tx *broadcast;
|
|
const tal_t *ctx = tal_tmpctx(NULL);
|
|
enum command_status cstatus;
|
|
|
|
copy_peers(©, &other, peer);
|
|
|
|
copy.current_input = i;
|
|
copy.current_idata = idata;
|
|
init_trail(&t, i, idata, peer, prev_trail);
|
|
copy.trail = &t;
|
|
|
|
/* The anchor is resolved in some way by these. */
|
|
if (i == BITCOIN_ANCHOR_UNSPENT
|
|
|| i == BITCOIN_ANCHOR_TIMEOUT
|
|
|| i == BITCOIN_ANCHOR_THEIRSPEND
|
|
|| i == BITCOIN_ANCHOR_OTHERSPEND
|
|
|| i == BITCOIN_CLOSE_DONE)
|
|
copy.anchor_spent = true;
|
|
|
|
eliminate_input(&hist->inputs_per_state[copy.state], i);
|
|
cstatus = state(ctx, ©, i, idata, &output, &broadcast);
|
|
|
|
normalpath &= normal_path(i, peer->state, copy.state);
|
|
errorpath |= error_path(i, peer->state, copy.state);
|
|
|
|
if (dot_enable
|
|
&& (dot_include_abnormal || normalpath)
|
|
&& (dot_include_errors || !errorpath)
|
|
&& (dot_include_abnormal || !too_cluttered(i, peer->state))) {
|
|
const char *oldstr, *newstr;
|
|
|
|
/* Simplify folds high and low prio, skip "STATE_" */
|
|
if (dot_simplify) {
|
|
oldstr = simplify_state(peer->state) + 6;
|
|
newstr = simplify_state(copy.state) + 6;
|
|
} else {
|
|
oldstr = state_name(peer->state) + 6;
|
|
newstr = state_name(copy.state) + 6;
|
|
}
|
|
if (newstr != oldstr || include_nops)
|
|
add_dot(&hist->edges, oldstr, newstr, i, output);
|
|
}
|
|
|
|
problem = check_all_effects(peer, cstatus, i, ©, output);
|
|
update_trail(&t, ©, output);
|
|
if (problem)
|
|
report_trail(&t, problem);
|
|
|
|
if (copy.state == STATE_ERR_INTERNAL)
|
|
report_trail(&t, "Internal error");
|
|
if (strstarts(state_name(copy.state), "STATE_UNUSED"))
|
|
report_trail(&t, "Unused state");
|
|
|
|
/* Re-set htlc_declined */
|
|
copy.htlc_declined = false;
|
|
|
|
/* Record any output. */
|
|
if (output) {
|
|
record_output(&hist->outputs,
|
|
input_by_name((const char *)output));
|
|
}
|
|
|
|
if (hist->state_dump) {
|
|
record_state(&hist->state_dump[peer->state], i, copy.state,
|
|
(const char *)output);
|
|
}
|
|
|
|
/* Have we been in this overall situation before? */
|
|
if (!sithash_update(&hist->sithash, ©)) {
|
|
/*
|
|
* We expect to loop if:
|
|
* 1) We deferred, OR
|
|
* 2) We get repeated BITCOIN_ANCHOR_OTHERSPEND/THEIRSPEND, OR
|
|
* 3) We pass through NORMAL state.
|
|
*
|
|
* And if we're being quick, always stop.
|
|
*/
|
|
if (quick
|
|
|| cstatus == CMD_REQUEUE
|
|
|| copy.state == STATE_NORMAL_LOWPRIO
|
|
|| copy.state == STATE_NORMAL_HIGHPRIO
|
|
|| i == BITCOIN_ANCHOR_OTHERSPEND
|
|
|| i == BITCOIN_ANCHOR_THEIRSPEND
|
|
|| quick) {
|
|
tal_free(ctx);
|
|
return;
|
|
}
|
|
if (t.depth > STATE_MAX * 10)
|
|
report_trail(&t, "Loop");
|
|
}
|
|
|
|
/* Don't continue if we reached a different error state. */
|
|
if (state_is_error(copy.state)) {
|
|
tal_free(ctx);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* If we're listening, someone should be talking (usually).
|
|
*/
|
|
if (copy.cond != PEER_CLOSED
|
|
&& !has_packets(©) && !has_packets(&other)
|
|
&& !waiting_statepair(copy.state, other.state)) {
|
|
report_trail(&t, "Deadlock");
|
|
}
|
|
|
|
/* Finished? */
|
|
if (copy.state == STATE_CLOSED) {
|
|
if (copy.cond != PEER_CLOSED)
|
|
report_trail(&t, "CLOSED but cond not CLOSED");
|
|
|
|
if (copy.core.current_command != INPUT_NONE)
|
|
report_trail(&t, input_name(copy.core.current_command));
|
|
|
|
if (copy.current_htlc.htlc.id != -1)
|
|
report_trail(&t, "CLOSED with htlc in progress?");
|
|
|
|
if (outstanding_htlc_watches(©))
|
|
report_trail(&t, "CLOSED but watching HTLCs?");
|
|
tal_free(ctx);
|
|
return;
|
|
}
|
|
|
|
/* Try inputs from here down. */
|
|
run_peer(©, normalpath, errorpath, &t, hist);
|
|
|
|
/* Don't bother running other peer we can't communicate. */
|
|
if (copy.cond != PEER_CLOSED
|
|
|| other.cond != PEER_CLOSED)
|
|
run_peer(&other, normalpath, errorpath, &t, hist);
|
|
tal_free(ctx);
|
|
}
|
|
|
|
static void sanity_check(const struct peer *peer)
|
|
{
|
|
if (peer->state == STATE_NORMAL_LOWPRIO
|
|
|| peer->state == STATE_NORMAL_HIGHPRIO) {
|
|
/* Home state: expect commands to be finished. */
|
|
if (peer->core.current_command != INPUT_NONE)
|
|
errx(1, "Unexpected command %u in state %u",
|
|
peer->core.current_command, peer->state);
|
|
}
|
|
}
|
|
|
|
static void activate_event(struct peer *peer, enum state_input i)
|
|
{
|
|
/* Events are not independent. */
|
|
switch (i) {
|
|
case BITCOIN_ANCHOR_DEPTHOK:
|
|
/* Can't sent TIMEOUT (may not be set) */
|
|
remove_event_(&peer->core.event_notifies, BITCOIN_ANCHOR_TIMEOUT);
|
|
break;
|
|
case BITCOIN_ANCHOR_TIMEOUT:
|
|
/* Can't sent DEPTHOK */
|
|
remove_event(peer, BITCOIN_ANCHOR_DEPTHOK);
|
|
break;
|
|
/* And of the "done" cases means we won't give the others. */
|
|
case BITCOIN_SPEND_THEIRS_DONE:
|
|
case BITCOIN_SPEND_OURS_DONE:
|
|
case BITCOIN_STEAL_DONE:
|
|
case BITCOIN_CLOSE_DONE:
|
|
remove_event_(&peer->core.event_notifies,
|
|
BITCOIN_SPEND_OURS_DONE);
|
|
remove_event_(&peer->core.event_notifies,
|
|
BITCOIN_SPEND_THEIRS_DONE);
|
|
remove_event_(&peer->core.event_notifies, BITCOIN_STEAL_DONE);
|
|
remove_event_(&peer->core.event_notifies, BITCOIN_CLOSE_DONE);
|
|
remove_event_(&peer->core.event_notifies,
|
|
BITCOIN_ANCHOR_OURCOMMIT_DELAYPASSED);
|
|
remove_event_(&peer->core.event_notifies,
|
|
BITCOIN_ANCHOR_THEIRSPEND);
|
|
remove_event_(&peer->core.event_notifies,
|
|
BITCOIN_ANCHOR_OTHERSPEND);
|
|
remove_event_(&peer->core.event_notifies,
|
|
BITCOIN_ANCHOR_UNSPENT);
|
|
break;
|
|
default:
|
|
;
|
|
}
|
|
}
|
|
|
|
static bool can_refire(enum state_input i)
|
|
{
|
|
/* They could have lots of old HTLCS */
|
|
if (i == BITCOIN_ANCHOR_OTHERSPEND)
|
|
return true;
|
|
/* Signature malleability means any number of these */
|
|
if (i == BITCOIN_ANCHOR_THEIRSPEND)
|
|
return true;
|
|
|
|
/* They could have lots of htlcs. */
|
|
if (i == BITCOIN_HTLC_TOTHEM_SPENT || i == BITCOIN_HTLC_TOTHEM_TIMEOUT
|
|
|| i == BITCOIN_HTLC_TOUS_TIMEOUT)
|
|
return true;
|
|
|
|
/* We manually remove these if they're not watching any more spends */
|
|
if (i == BITCOIN_HTLC_RETURN_SPEND_DONE
|
|
|| i == BITCOIN_HTLC_FULFILL_SPEND_DONE)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static unsigned int next_htlc_id(void)
|
|
{
|
|
static unsigned int num;
|
|
|
|
return ++num;
|
|
}
|
|
|
|
static void run_peer(const struct peer *peer,
|
|
bool normalpath, bool errorpath,
|
|
const struct trail *prev_trail,
|
|
struct hist *hist)
|
|
{
|
|
struct peer copy, other;
|
|
size_t i;
|
|
uint64_t old_notifies;
|
|
union input *idata = talz(NULL, union input);
|
|
|
|
sanity_check(peer);
|
|
|
|
/* We want to frob some things... */
|
|
copy_peers(©, &other, peer);
|
|
|
|
/* If in init state, we can only send start command. */
|
|
if (peer->state == STATE_INIT) {
|
|
if (streq(peer->name, "A"))
|
|
copy.core.current_command = CMD_OPEN_WITH_ANCHOR;
|
|
else
|
|
copy.core.current_command = CMD_OPEN_WITHOUT_ANCHOR;
|
|
try_input(©, copy.core.current_command, idata,
|
|
normalpath, errorpath,
|
|
prev_trail, hist);
|
|
return;
|
|
}
|
|
|
|
/* Try the event notifiers */
|
|
old_notifies = copy.core.event_notifies;
|
|
for (i = 0; i < 64; i++) {
|
|
if (!have_event(copy.core.event_notifies, i))
|
|
continue;
|
|
|
|
/* Don't re-fire most events */
|
|
if (!can_refire(i))
|
|
remove_event(©, i);
|
|
activate_event(©, i);
|
|
try_input(©, i, idata, normalpath, errorpath,
|
|
prev_trail, hist);
|
|
copy.core.event_notifies = old_notifies;
|
|
}
|
|
|
|
/* We can send a close command even if already sending a
|
|
* (different) command. */
|
|
if (peer->state != STATE_INIT
|
|
&& (peer->cond == PEER_CMD_OK
|
|
|| peer->cond == PEER_BUSY)) {
|
|
try_input(©, CMD_CLOSE, idata,
|
|
normalpath, errorpath, prev_trail, hist);
|
|
}
|
|
|
|
/* Try sending commands if allowed. */
|
|
if (peer->cond == PEER_CMD_OK) {
|
|
unsigned int i;
|
|
|
|
/* Shouldn't be currently doing an HTLC. */
|
|
assert(copy.current_htlc.htlc.id == -1);
|
|
|
|
/* Add a new HTLC if not at max. */
|
|
if (copy.num_htlcs_to_them < MAX_HTLCS) {
|
|
copy.core.current_command = CMD_SEND_HTLC_ADD;
|
|
idata->htlc_prog = tal(idata, struct htlc_progress);
|
|
idata->htlc_prog->adding = true;
|
|
idata->htlc_prog->htlc.to_them = true;
|
|
idata->htlc_prog->htlc.id = next_htlc_id();
|
|
|
|
set_current_htlc(©,
|
|
idata->htlc_prog->htlc.id,
|
|
idata->htlc_prog->htlc.to_them,
|
|
idata->htlc_prog->adding);
|
|
try_input(©, copy.core.current_command, idata,
|
|
normalpath, errorpath,
|
|
prev_trail, hist);
|
|
idata->htlc_prog = tal_free(idata->htlc_prog);
|
|
/* If it was requeued, may already be reset. */
|
|
copy.current_htlc.htlc.id = -1;
|
|
}
|
|
|
|
/* We can complete or fail an HTLC they offered */
|
|
for (i = 0; i < peer->num_htlcs_to_us; i++) {
|
|
idata->htlc_prog = tal(idata, struct htlc_progress);
|
|
idata->htlc_prog->htlc = peer->htlcs_to_us[i];
|
|
idata->htlc_prog->adding = false;
|
|
|
|
set_current_htlc(©,
|
|
idata->htlc_prog->htlc.id,
|
|
idata->htlc_prog->htlc.to_them,
|
|
idata->htlc_prog->adding);
|
|
|
|
/* Only send this once. */
|
|
if (!rval_known(peer, idata->htlc_prog->htlc.id)) {
|
|
copy.core.current_command
|
|
= CMD_SEND_HTLC_FULFILL;
|
|
try_input(©, copy.core.current_command,
|
|
idata, normalpath, errorpath,
|
|
prev_trail, hist);
|
|
}
|
|
copy.core.current_command = CMD_SEND_HTLC_FAIL;
|
|
try_input(©, copy.core.current_command,
|
|
idata, normalpath, errorpath,
|
|
prev_trail, hist);
|
|
/* If it was requeued, may already be reset. */
|
|
copy.current_htlc.htlc.id = -1;
|
|
}
|
|
|
|
/* Restore current_command */
|
|
copy.core.current_command = INPUT_NONE;
|
|
}
|
|
|
|
/* If they're watching HTLCs, we can send events. */
|
|
for (i = 0; i < peer->num_live_htlcs_to_us; i++) {
|
|
idata->htlc = (struct htlc *)©.live_htlcs_to_us[i];
|
|
/* Only send this once. */
|
|
if (!rval_known(peer, idata->htlc->id)) {
|
|
try_input(©, INPUT_RVALUE,
|
|
idata, normalpath, errorpath,
|
|
prev_trail, hist);
|
|
}
|
|
try_input(©, BITCOIN_HTLC_TOUS_TIMEOUT,
|
|
idata, normalpath, errorpath,
|
|
prev_trail, hist);
|
|
}
|
|
|
|
for (i = 0; i < peer->num_live_htlcs_to_them; i++) {
|
|
idata->btc = (struct bitcoin_event *)©.live_htlcs_to_them[i];
|
|
try_input(©, BITCOIN_HTLC_TOTHEM_SPENT,
|
|
idata, normalpath, errorpath,
|
|
prev_trail, hist);
|
|
idata->htlc = (struct htlc *)©.live_htlcs_to_them[i];
|
|
try_input(©, BITCOIN_HTLC_TOTHEM_TIMEOUT,
|
|
idata, normalpath, errorpath,
|
|
prev_trail, hist);
|
|
}
|
|
|
|
/* If they're watching HTLC spends, we can send events. */
|
|
for (i = 0; i < peer->num_htlc_spends_to_us; i++) {
|
|
idata->htlc = (struct htlc *)©.htlc_spends_to_us[i];
|
|
try_input(©, BITCOIN_HTLC_FULFILL_SPEND_DONE,
|
|
idata, normalpath, errorpath,
|
|
prev_trail, hist);
|
|
}
|
|
for (i = 0; i < peer->num_htlc_spends_to_them; i++) {
|
|
idata->htlc = (struct htlc *)©.htlc_spends_to_them[i];
|
|
try_input(©, BITCOIN_HTLC_RETURN_SPEND_DONE,
|
|
idata, normalpath, errorpath,
|
|
prev_trail, hist);
|
|
}
|
|
|
|
/* Allowed to send packets? */
|
|
if (copy.cond != PEER_CLOSED) {
|
|
enum state_input i;
|
|
|
|
if (other.core.num_outputs) {
|
|
i = other.core.outputs[0];
|
|
if (i == PKT_ERROR)
|
|
idata->pkt = pkt_err(idata, "");
|
|
else if (other.pkt_data[0] == -1U)
|
|
idata->pkt = (Pkt *)talz(idata, char);
|
|
else
|
|
idata->pkt = htlc_pkt(idata, input_name(i),
|
|
other.pkt_data[0]);
|
|
|
|
/* Do the first, recursion does the rest. */
|
|
memmove(other.core.outputs, other.core.outputs + 1,
|
|
sizeof(other.core.outputs)
|
|
- sizeof(other.core.outputs[0]));
|
|
memmove(other.pkt_data, other.pkt_data + 1,
|
|
sizeof(other.pkt_data)-sizeof(other.pkt_data[0]));
|
|
other.core.num_outputs--;
|
|
/* Reset so that hashing doesn't get confused. */
|
|
other.core.outputs[other.core.num_outputs] = 0;
|
|
try_input(©, i, idata, normalpath, errorpath,
|
|
prev_trail, hist);
|
|
}
|
|
}
|
|
tal_free(idata);
|
|
}
|
|
|
|
static bool record_input_mapping(int b)
|
|
{
|
|
size_t n;
|
|
|
|
if (!mapping_inputs)
|
|
return false;
|
|
|
|
/* Accumulating tested inputs? */
|
|
n = tal_count(mapping_inputs);
|
|
tal_resize(&mapping_inputs, n+1);
|
|
mapping_inputs[n] = b;
|
|
return true;
|
|
}
|
|
|
|
static enum state_input **map_inputs(void)
|
|
{
|
|
enum state_input **inps = tal_arr(NULL, enum state_input *, STATE_MAX);
|
|
unsigned int i;
|
|
const tal_t *ctx = tal_tmpctx(NULL);
|
|
|
|
for (i = 0; i < STATE_MAX; i++) {
|
|
/* This is a global */
|
|
mapping_inputs = tal_arr(inps, enum state_input, 0);
|
|
|
|
/* This adds to mapping_inputs every input_is() call */
|
|
if (!state_is_error(i)) {
|
|
struct peer dummy;
|
|
const struct bitcoin_tx *dummy_tx;
|
|
Pkt *dummy_pkt;
|
|
memset(&dummy, 0, sizeof(dummy));
|
|
dummy.state = i;
|
|
state(ctx, &dummy, INPUT_NONE, NULL, &dummy_pkt,
|
|
&dummy_tx);
|
|
}
|
|
inps[i] = mapping_inputs;
|
|
}
|
|
|
|
/* Reset global */
|
|
mapping_inputs = NULL;
|
|
tal_free(ctx);
|
|
return inps;
|
|
}
|
|
|
|
static bool visited_state(const struct sithash *sithash,
|
|
enum state state, bool b)
|
|
{
|
|
struct situation *h;
|
|
struct sithash_iter i;
|
|
unsigned int num = 0;
|
|
|
|
for (h = sithash_first(sithash, &i); h; h = sithash_next(sithash, &i)) {
|
|
num++;
|
|
if (b) {
|
|
if (h->a.s.valid && h->b.s.state == state)
|
|
return true;
|
|
} else {
|
|
if (h->a.s.valid && h->a.s.state == state)
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static int state_dump_cmp(const struct state_dump *a,
|
|
const struct state_dump *b,
|
|
void *unused)
|
|
{
|
|
if (a->input != b->input)
|
|
return a->input - b->input;
|
|
if (a->next != b->next)
|
|
return a->next - b->next;
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
struct peer a, b;
|
|
unsigned int i;
|
|
struct hist hist;
|
|
bool dump_states = false;
|
|
bool more_failpoints;
|
|
|
|
err_set_progname(argv[0]);
|
|
|
|
opt_register_noarg("--help|-h", opt_usage_and_exit,
|
|
""
|
|
"Test lightning state machine",
|
|
"Print this message.");
|
|
opt_register_noarg("--dot",
|
|
opt_set_bool, &dot_enable,
|
|
"Output dot format for normal paths");
|
|
opt_register_noarg("--dot-all",
|
|
opt_set_bool, &dot_include_abnormal,
|
|
"Output dot format for all non-error paths");
|
|
opt_register_noarg("--dot-include-errors",
|
|
opt_set_bool, &dot_include_errors,
|
|
"Output dot format for error paths");
|
|
opt_register_noarg("--include-nops",
|
|
opt_set_bool, &include_nops,
|
|
"Output even for inputs which don't change state");
|
|
opt_register_noarg("--dot-simplify",
|
|
opt_set_bool, &dot_simplify,
|
|
"Merge high and low priority states");
|
|
opt_register_noarg("--dump-states",
|
|
opt_set_bool, &dump_states,
|
|
"Summarize all state transitions");
|
|
opt_register_version();
|
|
|
|
opt_parse(&argc, argv, opt_log_stderr_exit);
|
|
if (dot_include_abnormal)
|
|
dot_enable = true;
|
|
if (dot_simplify && !dot_enable)
|
|
opt_usage_exit_fail("--dot-simplify needs --dot/--dot-all");
|
|
if (dot_include_errors && !dot_enable)
|
|
opt_usage_exit_fail("--dot-include-errors needs --dot/--dot-all");
|
|
if (include_nops && !dot_enable && !dump_states)
|
|
opt_usage_exit_fail("--include-nops needs --dot/--dot-all/--dump-states");
|
|
|
|
/* Map the inputs tested in each state. */
|
|
hist.inputs_per_state = map_inputs();
|
|
sithash_init(&hist.sithash);
|
|
hist.outputs = tal_arr(NULL, enum state_input, 0);
|
|
edge_hash_init(&hist.edges);
|
|
if (dump_states) {
|
|
hist.state_dump = tal_arr(NULL, struct state_dump *, STATE_MAX);
|
|
for (i = 0; i < STATE_MAX; i++)
|
|
hist.state_dump[i] = tal_arr(hist.state_dump,
|
|
struct state_dump, 0);
|
|
} else
|
|
hist.state_dump = NULL;
|
|
|
|
#if 0
|
|
quick = dot_enable || dump_states;
|
|
#else
|
|
quick = true;
|
|
#endif
|
|
|
|
/* Initialize universe. */
|
|
peer_init(&a, &b, "A");
|
|
peer_init(&b, &a, "B");
|
|
if (!sithash_update(&hist.sithash, &a))
|
|
abort();
|
|
failhash_init(&failhash);
|
|
|
|
/* Now, try each input in each state. */
|
|
run_peer(&a, true, false, NULL, &hist);
|
|
|
|
/* Now probe all the failure points */
|
|
do {
|
|
struct failpoint *f;
|
|
struct failhash_iter i;
|
|
more_failpoints = false;
|
|
|
|
for (f = failhash_first(&failhash, &i);
|
|
f;
|
|
f = failhash_next(&failhash, &i)) {
|
|
if (f->details) {
|
|
const struct trail *t;
|
|
|
|
/* Trail will vanish when f->details freed */
|
|
t = tal_steal(f, f->details->us.trail);
|
|
try_input(&f->details->us,
|
|
f->details->input,
|
|
&f->details->idata,
|
|
false, true,
|
|
t,
|
|
&hist);
|
|
/* Note: it can go down an earlier path and
|
|
* fail differently, so f->details may
|
|
* still be set. */
|
|
if (!f->details)
|
|
tal_free(t);
|
|
more_failpoints = true;
|
|
}
|
|
}
|
|
} while (!more_failpoints);
|
|
|
|
for (i = 0; i < STATE_MAX; i++) {
|
|
bool a_expect = true, b_expect = true;
|
|
|
|
/* Ignore unused states. */
|
|
if (strstarts(state_name(i), "STATE_UNUSED"))
|
|
continue;
|
|
|
|
/* A supplied anchor, so doesn't enter NOANCHOR states. */
|
|
if (i == STATE_OPEN_WAIT_FOR_OPEN_NOANCHOR
|
|
|| i == STATE_OPEN_WAIT_FOR_ANCHOR
|
|
|| i == STATE_OPEN_WAITING_THEIRANCHOR
|
|
|| i == STATE_OPEN_WAITING_THEIRANCHOR_THEYCOMPLETED
|
|
|| i == STATE_OPEN_WAIT_FOR_COMPLETE_THEIRANCHOR
|
|
|| i == STATE_ERR_ANCHOR_TIMEOUT)
|
|
a_expect = false;
|
|
if (i == STATE_OPEN_WAIT_FOR_OPEN_WITHANCHOR
|
|
|| i == STATE_OPEN_WAIT_FOR_ANCHOR_CREATE
|
|
|| i == STATE_OPEN_WAIT_FOR_COMMIT_SIG
|
|
|| i == STATE_OPEN_WAIT_FOR_COMPLETE_OURANCHOR
|
|
|| i == STATE_OPEN_WAITING_OURANCHOR
|
|
|| i == STATE_OPEN_WAITING_OURANCHOR_THEYCOMPLETED)
|
|
b_expect = false;
|
|
if (i == STATE_ERR_INTERNAL)
|
|
a_expect = b_expect = false;
|
|
if (visited_state(&hist.sithash, i, 0) != a_expect)
|
|
warnx("Peer A %s state %s",
|
|
a_expect ? "didn't visit" : "visited",
|
|
state_name(i));
|
|
if (visited_state(&hist.sithash, i, 1) != b_expect)
|
|
warnx("Peer B %s state %s",
|
|
b_expect ? "didn't visit" : "visited",
|
|
state_name(i));
|
|
if (!state_is_error(i) && tal_count(hist.inputs_per_state[i]))
|
|
warnx("Never sent %s input %s", state_name(i),
|
|
input_name(*hist.inputs_per_state[i]));
|
|
}
|
|
|
|
for (i = 0; i < INPUT_MAX; i++) {
|
|
/* Not all input values are valid. */
|
|
if (streq(input_name(i), "unknown"))
|
|
continue;
|
|
/* We only expect packets to be output. */
|
|
if (!input_is_pkt(i))
|
|
continue;
|
|
if (!find_output(hist.outputs, i))
|
|
warnx("Never sent output %s", input_name(i));
|
|
}
|
|
|
|
if (dot_enable) {
|
|
struct dot_edge *d;
|
|
struct edge_hash_iter i;
|
|
|
|
printf("digraph lightning {\n");
|
|
for (d = edge_hash_first(&hist.edges, &i);
|
|
d;
|
|
d = edge_hash_next(&hist.edges, &i)) {
|
|
printf("%s -> %s ", d->oldstate, d->newstate);
|
|
if (!d->pkt)
|
|
printf("[label=\"<%s\"];\n", input_name(d->i));
|
|
else {
|
|
printf("[label=\"<%s\\n>%s\"];\n",
|
|
input_name(d->i), d->pkt);
|
|
}
|
|
}
|
|
printf("}\n");
|
|
}
|
|
|
|
if (dump_states) {
|
|
for (i = 0; i < STATE_MAX; i++) {
|
|
size_t j;
|
|
size_t n = tal_count(hist.state_dump[i]);
|
|
if (!n)
|
|
continue;
|
|
printf("%s:\n", state_name(i) + 6);
|
|
asort(hist.state_dump[i], n, state_dump_cmp, NULL);
|
|
for (j = 0; j < n; j++) {
|
|
if (!include_nops
|
|
&& hist.state_dump[i][j].next == i)
|
|
continue;
|
|
printf("\t%s -> %s",
|
|
input_name(hist.state_dump[i][j].input),
|
|
state_name(hist.state_dump[i][j].next)+6);
|
|
if (hist.state_dump[i][j].pkt != INPUT_NONE)
|
|
printf(" (%s)",
|
|
input_name(hist.state_dump[i][j].pkt));
|
|
printf("\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|