From 847ef21c0700c85c4d297e5ac1d13a43771403c1 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 25 Sep 2015 11:51:18 +0930 Subject: [PATCH] state: Core state machine for lightning. It's written in a repetitive and stylized form, for easier testing. Signed-off-by: Rusty Russell --- state.c | 850 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ state.h | 258 +++++++++++++++++ 2 files changed, 1108 insertions(+) create mode 100644 state.c create mode 100644 state.h diff --git a/state.c b/state.c new file mode 100644 index 000000000..077404731 --- /dev/null +++ b/state.c @@ -0,0 +1,850 @@ +#include + +char cmd_requeue; + +static inline bool high_priority(enum state state) +{ + return (state & 1) == (STATE_NORMAL_HIGHPRIO & 1); +} + +#define prio(state, name) \ + (high_priority(state) ? name##_HIGHPRIO : name##_LOWPRIO) + +#define toggle_prio(state, name) \ + (!high_priority(state) ? name##_HIGHPRIO : name##_LOWPRIO) + +#define INIT_EFFECT_broadcast NULL +#define INIT_EFFECT_send NULL +#define INIT_EFFECT_watch NULL +#define INIT_EFFECT_unwatch NULL +#define INIT_EFFECT_defer INPUT_NONE +#define INIT_EFFECT_complete INPUT_NONE +#define INIT_EFFECT_complete_data NULL +#define INIT_EFFECT_stop_packets false +#define INIT_EFFECT_stop_commands false +#define INIT_EFFECT_close_timeout INPUT_NONE +#define INIT_EFFECT_in_error NULL + +void state_effect_init(struct state_effect *effect) +{ + effect->broadcast = INIT_EFFECT_broadcast; + effect->send = INIT_EFFECT_send; + effect->watch = INIT_EFFECT_watch; + effect->unwatch = INIT_EFFECT_unwatch; + effect->defer = INIT_EFFECT_defer; + effect->complete = INIT_EFFECT_complete; + effect->complete_data = INIT_EFFECT_complete_data; + effect->stop_packets = INIT_EFFECT_stop_packets; + effect->stop_commands = INIT_EFFECT_stop_commands; + effect->close_timeout = INIT_EFFECT_close_timeout; + effect->in_error = INIT_EFFECT_in_error; +} + +#define set_effect(effect, field, val) \ + do { \ + struct state_effect *_e = (effect); \ + assert(_e->field == INIT_EFFECT_##field); \ + _e->field = (val); \ + assert(_e->field != INIT_EFFECT_##field); \ + } while(0) + +static void fail_cmd(struct state_effect *effect, + const enum state_input input, + void *faildata) +{ + set_effect(effect, complete, input); + /* Use dummy value if they don't want one. */ + set_effect(effect, complete_data, faildata ? faildata : effect); +} + +static void requeue_cmd(struct state_effect *effect, + const enum state_input input) +{ + set_effect(effect, complete, input); + set_effect(effect, complete_data, &cmd_requeue); +} + +enum state state(const enum state state, const struct state_data *sdata, + const enum state_input input, const union input *idata, + struct state_effect *effect) +{ + Pkt *decline; + struct bitcoin_tx *steal; + Pkt *err; + + switch (state) { + /* + * Initial channel opening states. + */ + case STATE_INIT_NOANCHOR: + assert(input == INPUT_NONE); + set_effect(effect, send, pkt_open(effect, sdata)); + return STATE_OPEN_WAIT_FOR_OPEN_NOANCHOR; + case STATE_INIT_WITHANCHOR: + assert(input == INPUT_NONE); + set_effect(effect, send, pkt_open(effect, sdata)); + return STATE_OPEN_WAIT_FOR_OPEN_WITHANCHOR; + case STATE_OPEN_WAIT_FOR_OPEN_NOANCHOR: + if (input_is(input, PKT_OPEN)) { + err = accept_pkt_open(effect, sdata, idata->pkt); + if (err) + goto err_close_nocleanup; + return STATE_OPEN_WAIT_FOR_ANCHOR; + } else if (input_is(input, CMD_SEND_UPDATE_ANY)) { + /* Can't do these until we're open. */ + set_effect(effect, defer, input); + return state; + } else if (input_is(input, CMD_CLOSE)) { + goto instant_close; + } else if (input_is_pkt(input)) { + goto unexpected_pkt_nocleanup; + } + break; + case STATE_OPEN_WAIT_FOR_OPEN_WITHANCHOR: + if (input_is(input, PKT_OPEN)) { + err = accept_pkt_open(effect, sdata, idata->pkt); + if (err) + goto err_close_nocleanup; + set_effect(effect, send, pkt_anchor(effect, sdata)); + return STATE_OPEN_WAIT_FOR_COMMIT_SIG; + } else if (input_is(input, CMD_SEND_UPDATE_ANY)) { + /* Can't do these until we're open. */ + set_effect(effect, defer, input); + return state; + } else if (input_is(input, CMD_CLOSE)) { + goto instant_close; + } else if (input_is_pkt(input)) { + goto unexpected_pkt_nocleanup; + } + break; + case STATE_OPEN_WAIT_FOR_ANCHOR: + if (input_is(input, PKT_OPEN_ANCHOR)) { + err = accept_pkt_anchor(effect, sdata, idata->pkt); + if (err) + goto err_close_nocleanup; + set_effect(effect, send, + pkt_open_commit_sig(effect, sdata)); + set_effect(effect, watch, + bitcoin_watch_anchor(effect, sdata, + BITCOIN_ANCHOR_DEPTHOK, + BITCOIN_ANCHOR_TIMEOUT, + BITCOIN_ANCHOR_UNSPENT, + BITCOIN_ANCHOR_THEIRSPEND, + BITCOIN_ANCHOR_OTHERSPEND)); + + return STATE_OPEN_WAITING_THEIRANCHOR; + } else if (input_is(input, CMD_SEND_UPDATE_ANY)) { + /* Can't do these until we're open. */ + set_effect(effect, defer, input); + return state; + } else if (input_is(input, CMD_CLOSE)) { + goto instant_close; + } else if (input_is_pkt(input)) { + goto unexpected_pkt_nocleanup; + } + break; + case STATE_OPEN_WAIT_FOR_COMMIT_SIG: + if (input_is(input, PKT_OPEN_COMMIT_SIG)) { + err = accept_pkt_open_commit_sig(effect, sdata, + idata->pkt); + if (err) + goto err_start_unilateral_close; + set_effect(effect, broadcast, + bitcoin_anchor(effect, sdata)); + set_effect(effect, watch, + bitcoin_watch_anchor(effect, sdata, + BITCOIN_ANCHOR_DEPTHOK, + INPUT_NONE, + BITCOIN_ANCHOR_UNSPENT, + BITCOIN_ANCHOR_THEIRSPEND, + BITCOIN_ANCHOR_OTHERSPEND)); + return STATE_OPEN_WAITING_OURANCHOR; + } else if (input_is(input, CMD_SEND_UPDATE_ANY)) { + /* Can't do these until we're open. */ + set_effect(effect, defer, input); + return state; + } else if (input_is(input, CMD_CLOSE)) { + goto instant_close; + } else if (input_is_pkt(input)) { + goto unexpected_pkt_nocleanup; + } + break; + case STATE_OPEN_WAITING_OURANCHOR: + if (input_is(input, BITCOIN_ANCHOR_DEPTHOK)) { + set_effect(effect, send, + pkt_open_complete(effect, sdata)); + return STATE_OPEN_WAIT_FOR_COMPLETE_OURANCHOR; + } else if (input_is(input, BITCOIN_ANCHOR_UNSPENT)) { + goto anchor_unspent; + } else if (input_is(input, PKT_OPEN_COMPLETE)) { + /* Ignore until we've hit depth ourselves. */ + set_effect(effect, defer, input); + return state; + } else if (input_is(input, CMD_SEND_UPDATE_ANY)) { + /* Can't do these until we're open. */ + set_effect(effect, defer, input); + return state; + } else if (input_is(input, BITCOIN_ANCHOR_THEIRSPEND)) { + /* We no longer care about anchor depth. */ + set_effect(effect, unwatch, + bitcoin_unwatch_anchor_depth(effect, sdata, + BITCOIN_ANCHOR_DEPTHOK, + INPUT_NONE)); + goto them_unilateral; + } else if (input_is(input, BITCOIN_ANCHOR_OTHERSPEND)) { + /* This should be impossible. */ + return STATE_ERR_INFORMATION_LEAK; + } else if (input_is(input, CMD_CLOSE)) { + /* We no longer care about anchor depth. */ + set_effect(effect, unwatch, + bitcoin_unwatch_anchor_depth(effect, sdata, + BITCOIN_ANCHOR_DEPTHOK, + INPUT_NONE)); + goto start_closing; + } else if (input_is(input, PKT_CLOSE)) { + /* We no longer care about anchor depth. */ + set_effect(effect, unwatch, + bitcoin_unwatch_anchor_depth(effect, sdata, + BITCOIN_ANCHOR_DEPTHOK, + INPUT_NONE)); + goto accept_closing; + } else if (input_is_pkt(input)) { + /* We no longer care about anchor depth. */ + set_effect(effect, unwatch, + bitcoin_unwatch_anchor_depth(effect, sdata, + BITCOIN_ANCHOR_DEPTHOK, + INPUT_NONE)); + goto unexpected_pkt; + } + break; + case STATE_OPEN_WAITING_THEIRANCHOR: + if (input_is(input, BITCOIN_ANCHOR_TIMEOUT)) { + /* Anchor didn't reach blockchain in reasonable time. */ + set_effect(effect, send, + pkt_err(effect, "Anchor timed out")); + return STATE_ERR_ANCHOR_TIMEOUT; + } else if (input_is(input, BITCOIN_ANCHOR_DEPTHOK)) { + set_effect(effect, send, + pkt_open_complete(effect, sdata)); + return STATE_OPEN_WAIT_FOR_COMPLETE_THEIRANCHOR; + } else if (input_is(input, BITCOIN_ANCHOR_UNSPENT)) { + goto anchor_unspent; + } else if (input_is(input, BITCOIN_ANCHOR_OTHERSPEND)) { + /* This should be impossible. */ + return STATE_ERR_INFORMATION_LEAK; + } else if (input_is(input, BITCOIN_ANCHOR_THEIRSPEND)) { + /* We no longer care about anchor depth. */ + set_effect(effect, unwatch, + bitcoin_unwatch_anchor_depth(effect, sdata, + BITCOIN_ANCHOR_DEPTHOK, + BITCOIN_ANCHOR_TIMEOUT)); + goto them_unilateral; + } else if (input_is(input, PKT_OPEN_COMPLETE)) { + /* Ignore until we've hit depth ourselves. */ + set_effect(effect, defer, input); + return state; + } else if (input_is(input, CMD_SEND_UPDATE_ANY)) { + /* Can't do these until we're open. */ + set_effect(effect, defer, input); + return state; + } else if (input_is(input, CMD_CLOSE)) { + /* We no longer care about anchor depth. */ + set_effect(effect, unwatch, + bitcoin_unwatch_anchor_depth(effect, sdata, + BITCOIN_ANCHOR_DEPTHOK, + BITCOIN_ANCHOR_TIMEOUT)); + goto start_closing; + } else if (input_is(input, PKT_CLOSE)) { + /* We no longer care about anchor depth. */ + set_effect(effect, unwatch, + bitcoin_unwatch_anchor_depth(effect, sdata, + BITCOIN_ANCHOR_DEPTHOK, + BITCOIN_ANCHOR_TIMEOUT)); + goto accept_closing; + } else if (input_is_pkt(input)) { + /* We no longer care about anchor depth. */ + set_effect(effect, unwatch, + bitcoin_unwatch_anchor_depth(effect, sdata, + BITCOIN_ANCHOR_DEPTHOK, + BITCOIN_ANCHOR_TIMEOUT)); + goto unexpected_pkt; + } + break; + case STATE_OPEN_WAIT_FOR_COMPLETE_OURANCHOR: + case STATE_OPEN_WAIT_FOR_COMPLETE_THEIRANCHOR: + if (input_is(input, PKT_OPEN_COMPLETE)) { + /* Ready for business! Anchorer goes first. */ + if (state == STATE_OPEN_WAIT_FOR_COMPLETE_OURANCHOR) + return STATE_NORMAL_HIGHPRIO; + else + return STATE_NORMAL_LOWPRIO; + } else if (input_is(input, BITCOIN_ANCHOR_UNSPENT)) { + goto anchor_unspent; + /* Nobody should be able to spend anchor, except via the + * commit txs. */ + } else if (input_is(input, BITCOIN_ANCHOR_OTHERSPEND)) { + return STATE_ERR_INFORMATION_LEAK; + } else if (input_is(input, BITCOIN_ANCHOR_THEIRSPEND)) { + goto them_unilateral; + } else if (input_is(input, PKT_OPEN_COMPLETE)) { + /* Ready for business! */ + return STATE_NORMAL_HIGHPRIO; + } else if (input_is(input, CMD_SEND_UPDATE_ANY)) { + /* Can't do these until we're open. */ + set_effect(effect, defer, input); + return state; + } else if (input_is(input, CMD_CLOSE)) { + goto start_closing; + } else if (input_is(input, PKT_CLOSE)) { + goto accept_closing; + } else if (input_is_pkt(input)) { + goto unexpected_pkt; + } + break; + + /* + * Channel normal operating states. + */ + case STATE_NORMAL_LOWPRIO: + case STATE_NORMAL_HIGHPRIO: + if (input_is(input, CMD_SEND_UPDATE)) { + /* We are to send an update. */ + set_effect(effect, send, + pkt_update(effect, sdata, idata->cmd)); + return prio(state, STATE_WAIT_FOR_UPDATE_ACCEPT); + } else if (input_is(input, CMD_SEND_HTLC_UPDATE)) { + /* We are to send an HTLC update. */ + set_effect(effect, send, + pkt_htlc_update(effect, sdata, idata->cmd)); + return prio(state, STATE_WAIT_FOR_HTLC_ACCEPT); + } else if (input_is(input, CMD_SEND_HTLC_COMPLETE)) { + /* We are to send an HTLC complete. */ + set_effect(effect, send, + pkt_htlc_complete(effect, sdata, idata->cmd)); + return prio(state, STATE_WAIT_FOR_HTLC_ACCEPT); + } else if (input_is(input, CMD_SEND_HTLC_TIMEDOUT)) { + /* We are to send an HTLC timedout. */ + set_effect(effect, send, + pkt_htlc_timedout(effect, sdata, idata->cmd)); + return prio(state, STATE_WAIT_FOR_HTLC_ACCEPT); + } else if (input_is(input, CMD_SEND_HTLC_ROUTEFAIL)) { + /* We are to send an HTLC routefail. */ + set_effect(effect, send, + pkt_htlc_routefail(effect, sdata, + idata->cmd)); + return prio(state, STATE_WAIT_FOR_HTLC_ACCEPT); + } else if (input_is(input, CMD_CLOSE)) { + goto start_closing; + } else if (input_is(input, PKT_UPDATE)) { + goto accept_update; + } else if (input_is(input, PKT_UPDATE_ADD_HTLC)) { + goto accept_htlc_update; + } else if (input_is(input, PKT_UPDATE_COMPLETE_HTLC)) { + goto accept_htlc_complete; + } else if (input_is(input, PKT_UPDATE_TIMEDOUT_HTLC)) { + goto accept_htlc_timedout; + } else if (input_is(input, PKT_UPDATE_ROUTEFAIL_HTLC)) { + goto accept_htlc_routefail; + } else if (input_is(input, BITCOIN_ANCHOR_THEIRSPEND)) { + goto them_unilateral; + } else if (input_is(input, BITCOIN_ANCHOR_OTHERSPEND)) { + goto old_commit_spotted; + } else if (input_is(input, BITCOIN_ANCHOR_UNSPENT)) { + goto anchor_unspent; + } else if (input_is(input, PKT_CLOSE)) { + goto accept_closing; + } else if (input_is_pkt(input)) { + goto unexpected_pkt; + } + break; + case STATE_WAIT_FOR_HTLC_ACCEPT_LOWPRIO: + case STATE_WAIT_FOR_HTLC_ACCEPT_HIGHPRIO: + /* HTLCs can also evoke a refusal. */ + if (input_is(input, PKT_UPDATE_DECLINE_HTLC)) { + fail_cmd(effect, CMD_SEND_HTLC_UPDATE, idata->pkt); + /* Toggle between high and low priority states. */ + return toggle_prio(state, STATE_NORMAL); + } + /* Fall thru... */ + case STATE_WAIT_FOR_UPDATE_ACCEPT_LOWPRIO: + case STATE_WAIT_FOR_UPDATE_ACCEPT_HIGHPRIO: + if (input_is(input, PKT_UPDATE)) { + /* If we're high priority, ignore their packet */ + if (high_priority(state)) + return state; + + /* Otherwise, process their request first: defer ours */ + requeue_cmd(effect, CMD_SEND_UPDATE_ANY); + goto accept_update; + } else if (input_is(input, PKT_UPDATE_ADD_HTLC)) { + /* If we're high priority, ignore their packet */ + if (high_priority(state)) + return state; + + /* Otherwise, process their request first: defer ours */ + requeue_cmd(effect, CMD_SEND_UPDATE_ANY); + goto accept_htlc_update; + } else if (input_is(input, PKT_UPDATE_COMPLETE_HTLC)) { + /* If we're high priority, ignore their packet */ + if (high_priority(state)) + return state; + + /* Otherwise, process their request first: defer ours */ + requeue_cmd(effect, CMD_SEND_UPDATE_ANY); + goto accept_htlc_complete; + } else if (input_is(input, PKT_UPDATE_TIMEDOUT_HTLC)) { + /* If we're high priority, ignore their packet */ + if (high_priority(state)) + return state; + + /* Otherwise, process their request first: defer ours */ + requeue_cmd(effect, CMD_SEND_UPDATE_ANY); + goto accept_htlc_timedout; + } else if (input_is(input, PKT_UPDATE_ROUTEFAIL_HTLC)) { + /* If we're high priority, ignore their packet */ + if (high_priority(state)) + return state; + + /* Otherwise, process their request first: defer ours */ + requeue_cmd(effect, CMD_SEND_UPDATE_ANY); + goto accept_htlc_routefail; + } else if (input_is(input, PKT_UPDATE_ACCEPT)) { + err = accept_pkt_update_accept(effect, sdata, + idata->pkt); + if (err) { + fail_cmd(effect, CMD_SEND_UPDATE_ANY, NULL); + goto err_start_unilateral_close; + } + set_effect(effect, send, + pkt_update_signature(effect, sdata)); + return prio(state, STATE_WAIT_FOR_UPDATE_COMPLETE); + } else if (input_is(input, BITCOIN_ANCHOR_UNSPENT)) { + fail_cmd(effect, CMD_SEND_UPDATE_ANY, NULL); + goto anchor_unspent; + } else if (input_is(input, BITCOIN_ANCHOR_THEIRSPEND)) { + fail_cmd(effect, CMD_SEND_UPDATE_ANY, NULL); + goto them_unilateral; + } else if (input_is(input, BITCOIN_ANCHOR_OTHERSPEND)) { + fail_cmd(effect, CMD_SEND_UPDATE_ANY, NULL); + goto old_commit_spotted; + } else if (input_is(input, PKT_CLOSE)) { + fail_cmd(effect, CMD_SEND_UPDATE_ANY, NULL); + goto accept_closing; + } else if (input_is_pkt(input)) { + fail_cmd(effect, CMD_SEND_UPDATE_ANY, NULL); + goto unexpected_pkt; + } + break; + case STATE_WAIT_FOR_UPDATE_COMPLETE_LOWPRIO: + case STATE_WAIT_FOR_UPDATE_COMPLETE_HIGHPRIO: + if (input_is(input, PKT_UPDATE_COMPLETE)) { + err = accept_pkt_update_complete(effect, sdata, + idata->pkt); + if (err) + goto err_start_unilateral_close; + set_effect(effect, complete, CMD_SEND_UPDATE_ANY); + return toggle_prio(state, STATE_NORMAL); + } else if (input_is(input, BITCOIN_ANCHOR_UNSPENT)) { + fail_cmd(effect, CMD_SEND_UPDATE_ANY, NULL); + goto anchor_unspent; + } else if (input_is(input, BITCOIN_ANCHOR_THEIRSPEND)) { + fail_cmd(effect, CMD_SEND_UPDATE_ANY, NULL); + goto them_unilateral; + } else if (input_is(input, BITCOIN_ANCHOR_OTHERSPEND)) { + fail_cmd(effect, CMD_SEND_UPDATE_ANY, NULL); + goto old_commit_spotted; + } else if (input_is(input, PKT_CLOSE)) { + fail_cmd(effect, CMD_SEND_UPDATE_ANY, NULL); + goto accept_closing; + } else if (input_is_pkt(input)) { + fail_cmd(effect, CMD_SEND_UPDATE_ANY, NULL); + goto unexpected_pkt; + } + break; + case STATE_WAIT_FOR_UPDATE_SIG_LOWPRIO: + case STATE_WAIT_FOR_UPDATE_SIG_HIGHPRIO: + if (input_is(input, PKT_UPDATE_SIGNATURE)) { + err = accept_pkt_update_signature(effect, sdata, + idata->pkt); + if (err) + goto err_start_unilateral_close; + set_effect(effect, send, + pkt_update_complete(effect, sdata)); + /* Toggle between high and low priority states. */ + return toggle_prio(state, STATE_NORMAL); + } else if (input_is(input, CMD_SEND_UPDATE_ANY)) { + set_effect(effect, defer, input); + return state; + } else if (input_is(input, BITCOIN_ANCHOR_UNSPENT)) { + goto anchor_unspent; + } else if (input_is(input, BITCOIN_ANCHOR_THEIRSPEND)) { + goto them_unilateral; + } else if (input_is(input, BITCOIN_ANCHOR_OTHERSPEND)) { + goto old_commit_spotted; + } else if (input_is(input, CMD_CLOSE)) { + goto start_closing; + } else if (input_is_pkt(input)) { + goto unexpected_pkt; + } + break; + + case STATE_WAIT_FOR_CLOSE_COMPLETE: + if (input_is(input, PKT_CLOSE_COMPLETE)) { + err = accept_pkt_close_complete(effect, sdata, + idata->pkt); + if (err) + goto err_start_unilateral_close; + set_effect(effect, complete, CMD_CLOSE); + set_effect(effect, send, pkt_close_ack(effect, sdata)); + set_effect(effect, broadcast, + bitcoin_close(effect, sdata)); + set_effect(effect, stop_commands, true); + set_effect(effect, stop_packets, true); + return STATE_CLOSE_WAIT_CLOSE; + } else if (input_is_pkt(input)) { + /* FIXME: Mutual close if they send PKT_CLOSE? */ + /* We ignore all other packets while closing. */ + return STATE_WAIT_FOR_CLOSE_COMPLETE; + } else if (input_is(input, INPUT_CLOSE_COMPLETE_TIMEOUT)) { + /* They didn't respond in time. Unilateral close. */ + set_effect(effect, send, + pkt_err(effect, "Close timed out")); + fail_cmd(effect, CMD_CLOSE, effect->send); + set_effect(effect, stop_commands, true); + set_effect(effect, stop_packets, true); + set_effect(effect, broadcast, + bitcoin_commit(effect, sdata)); + set_effect(effect, watch, + bitcoin_watch_delayed(effect, + BITCOIN_ANCHOR_OURCOMMIT_DELAYPASSED)); + /* They could still close. */ + return STATE_CLOSE_WAIT_CLOSE_OURCOMMIT; + } + fail_cmd(effect, CMD_CLOSE, NULL); + set_effect(effect, stop_commands, true); + goto fail_during_close; + + case STATE_WAIT_FOR_CLOSE_ACK: + if (input_is(input, PKT_CLOSE_ACK)) { + err = accept_pkt_close_ack(effect, sdata, idata->pkt); + if (err) + set_effect(effect, send, err); + set_effect(effect, stop_packets, true); + /* Just wait for close to happen now. */ + return STATE_CLOSE_WAIT_CLOSE; + } else if (input_is_pkt(input)) { + if (input_is(input, PKT_ERROR)) { + set_effect(effect, in_error, + tal_steal(effect, idata->pkt)); + } else { + set_effect(effect, send, + unexpected_pkt(effect, input)); + } + set_effect(effect, stop_packets, true); + /* Just wait for close to happen now. */ + return STATE_CLOSE_WAIT_CLOSE; + } else if (input_is(input, BITCOIN_CLOSE_DONE)) { + /* They didn't ack, but we're closed, so stop. */ + set_effect(effect, stop_packets, true); + return STATE_CLOSED; + } + goto fail_during_close; + + /* Close states are regular: handle as a group. */ + case STATE_CLOSE_WAIT_STEAL: + case STATE_CLOSE_WAIT_SPENDTHEM: + case STATE_CLOSE_WAIT_STEAL_SPENDTHEM: + case STATE_CLOSE_WAIT_CLOSE: + case STATE_CLOSE_WAIT_STEAL_CLOSE: + case STATE_CLOSE_WAIT_SPENDTHEM_CLOSE: + case STATE_CLOSE_WAIT_STEAL_SPENDTHEM_CLOSE: + case STATE_CLOSE_WAIT_STEAL_OURCOMMIT: + case STATE_CLOSE_WAIT_SPENDTHEM_OURCOMMIT: + case STATE_CLOSE_WAIT_STEAL_SPENDTHEM_OURCOMMIT: + case STATE_CLOSE_WAIT_CLOSE_OURCOMMIT: + case STATE_CLOSE_WAIT_STEAL_CLOSE_OURCOMMIT: + case STATE_CLOSE_WAIT_SPENDTHEM_CLOSE_OURCOMMIT: + case STATE_CLOSE_WAIT_STEAL_SPENDTHEM_CLOSE_OURCOMMIT: + case STATE_CLOSE_WAIT_STEAL_SPENDOURS: + case STATE_CLOSE_WAIT_SPENDTHEM_SPENDOURS: + case STATE_CLOSE_WAIT_STEAL_SPENDTHEM_SPENDOURS: + case STATE_CLOSE_WAIT_CLOSE_SPENDOURS: + case STATE_CLOSE_WAIT_STEAL_CLOSE_SPENDOURS: + case STATE_CLOSE_WAIT_SPENDTHEM_CLOSE_SPENDOURS: + case STATE_CLOSE_WAIT_STEAL_SPENDTHEM_CLOSE_SPENDOURS: + case STATE_CLOSE_WAIT_OURCOMMIT: + case STATE_CLOSE_WAIT_SPENDOURS: { + unsigned int bits, base; + + base = (unsigned)STATE_CLOSE_WAIT_STEAL - 1; + bits = (unsigned)state - base; + + if ((bits & STATE_CLOSE_STEAL_BIT) + && input_is(input, BITCOIN_STEAL_DONE)) { + return STATE_CLOSED; + } + + if ((bits & STATE_CLOSE_SPENDTHEM_BIT) + && input_is(input, BITCOIN_SPEND_THEIRS_DONE)) { + return STATE_CLOSED; + } + + if ((bits & STATE_CLOSE_CLOSE_BIT) + && input_is(input, BITCOIN_CLOSE_DONE)) { + return STATE_CLOSED; + } + + if ((bits & STATE_CLOSE_OURCOMMIT_BIT) + && input_is(input, BITCOIN_ANCHOR_OURCOMMIT_DELAYPASSED)) { + /* Now we need to wait for our commit to be done. */ + set_effect(effect, broadcast, + bitcoin_spend_ours(effect, sdata)); + set_effect(effect, watch, + bitcoin_watch(effect, + BITCOIN_SPEND_OURS_DONE)); + bits &= ~STATE_CLOSE_OURCOMMIT_BIT; + bits |= STATE_CLOSE_SPENDOURS_BIT; + return base + bits; + } + + if ((bits & STATE_CLOSE_SPENDOURS_BIT) + && input_is(input, BITCOIN_SPEND_OURS_DONE)) { + return STATE_CLOSED; + } + + /* Now, other side can always spring a commit transaction on us + * (if they haven't already) */ + if (!(bits & STATE_CLOSE_SPENDTHEM_BIT) + && input_is(input, BITCOIN_ANCHOR_THEIRSPEND)) { + set_effect(effect, broadcast, + bitcoin_spend_theirs(effect, sdata)); + set_effect(effect, watch, + bitcoin_watch(effect, + BITCOIN_SPEND_THEIRS_DONE)); + bits |= STATE_CLOSE_SPENDTHEM_BIT; + return base + bits; + /* This can happen multiple times: need to steal ALL */ + } else if (input_is(input, BITCOIN_ANCHOR_OTHERSPEND)) { + struct bitcoin_tx *steal; + steal = bitcoin_steal(effect, sdata, idata->btc); + if (!steal) + return STATE_ERR_INFORMATION_LEAK; + set_effect(effect, broadcast, steal); + set_effect(effect, watch, + bitcoin_watch(effect, BITCOIN_STEAL_DONE)); + bits |= STATE_CLOSE_STEAL_BIT; + return base + bits; + } else if (input_is(input, BITCOIN_ANCHOR_UNSPENT)) + goto anchor_unspent; + break; + } + + /* Should never happen. */ + case STATE_ERR_INTERNAL: + case STATE_ERR_INFORMATION_LEAK: + case STATE_ERR_ANCHOR_TIMEOUT: + case STATE_ERR_ANCHOR_LOST: + case STATE_CLOSED: + case STATE_MAX: + return STATE_ERR_INTERNAL; + } + + /* State machine should handle all possible states. */ + return STATE_ERR_INTERNAL; + +unexpected_pkt: + /* + * We got a weird packet, so we need to close unilaterally. + */ + /* Don't reply to an error with an error. */ + if (input_is(input, PKT_ERROR)) { + set_effect(effect, in_error, tal_steal(effect, idata->pkt)); + goto start_unilateral_close; + } + err = unexpected_pkt(effect, input); + goto err_start_unilateral_close; + +unexpected_pkt_nocleanup: + /* + * Unexpected packet, but nothing sent to chain yet, so no cleanup. + */ + err = unexpected_pkt(effect, input); + goto err_close_nocleanup; + +anchor_unspent: + /* + * Bitcoind tells us anchor got double-spent. If we double-spent it + * then we're malfunctioning. If they double-spent it, then they + * managed to cheat us: post_to_reddit(); + */ + return STATE_ERR_ANCHOR_LOST; + +err_close_nocleanup: + /* + * Something went wrong, but we haven't sent anything to the blockchain + * so there's nothing to clean up. + */ + set_effect(effect, send, err); + set_effect(effect, stop_packets, true); + set_effect(effect, stop_commands, true); + return STATE_CLOSED; + +err_start_unilateral_close: + /* + * They timed out, or were broken; we are going to close unilaterally. + */ + set_effect(effect, send, err); + +start_unilateral_close: + /* + * Close unilaterally. + */ + /* No more inputs, no more commands. */ + set_effect(effect, stop_packets, true); + set_effect(effect, stop_commands, true); + set_effect(effect, broadcast, bitcoin_commit(effect, sdata)); + set_effect(effect, watch, + bitcoin_watch_delayed(effect, + BITCOIN_ANCHOR_OURCOMMIT_DELAYPASSED)); + return STATE_CLOSE_WAIT_OURCOMMIT; + +them_unilateral: + /* + * Bitcoind tells us they did unilateral close. + */ + set_effect(effect, send, pkt_err(effect, "Commit tx noticed")); + + /* No more inputs, no more commands. */ + set_effect(effect, stop_packets, true); + set_effect(effect, stop_commands, true); + set_effect(effect, broadcast, bitcoin_spend_theirs(effect, sdata)); + set_effect(effect, watch, + bitcoin_watch(effect, BITCOIN_SPEND_THEIRS_DONE)); + return STATE_CLOSE_WAIT_SPENDTHEM; + +accept_update: + err = accept_pkt_update(effect, sdata, idata->pkt); + if (err) + goto err_start_unilateral_close; + set_effect(effect, send, pkt_update_accept(effect, sdata)); + return prio(state, STATE_WAIT_FOR_UPDATE_SIG); + +accept_htlc_update: + err = accept_pkt_htlc_update(effect, sdata, idata->pkt, &decline); + if (err) + goto err_start_unilateral_close; + if (decline) { + set_effect(effect, send, decline); + /* Toggle between high/low priority states. */ + return toggle_prio(state, STATE_NORMAL); + } + set_effect(effect, send, pkt_update_accept(effect, sdata)); + return prio(state, STATE_WAIT_FOR_UPDATE_SIG); + +accept_htlc_routefail: + err = accept_pkt_htlc_routefail(effect, sdata, idata->pkt); + if (err) + goto err_start_unilateral_close; + set_effect(effect, send, pkt_update_accept(effect, sdata)); + return prio(state, STATE_WAIT_FOR_UPDATE_SIG); + +accept_htlc_timedout: + err = accept_pkt_htlc_timedout(effect, sdata, idata->pkt); + if (err) + goto err_start_unilateral_close; + set_effect(effect, send, pkt_update_accept(effect, sdata)); + return prio(state, STATE_WAIT_FOR_UPDATE_SIG); + +accept_htlc_complete: + err = accept_pkt_htlc_complete(effect, sdata, idata->pkt); + if (err) + goto err_start_unilateral_close; + set_effect(effect, send, pkt_update_accept(effect, sdata)); + return prio(state, STATE_WAIT_FOR_UPDATE_SIG); + +start_closing: + /* + * Start a mutual close. + */ + set_effect(effect, close_timeout, INPUT_CLOSE_COMPLETE_TIMEOUT); + + set_effect(effect, watch, + bitcoin_watch_close(effect, BITCOIN_CLOSE_DONE)); + + /* As soon as we send packet, they could close. */ + set_effect(effect, send, pkt_close(effect, sdata)); + return STATE_WAIT_FOR_CLOSE_COMPLETE; + +accept_closing: + err = accept_pkt_close(effect, sdata, idata->pkt); + if (err) + goto err_start_unilateral_close; + /* As soon as we send packet, they could close. */ + set_effect(effect, watch, + bitcoin_watch_close(effect, sdata, BITCOIN_CLOSE_DONE)); + set_effect(effect, send, pkt_close_complete(effect, sdata)); + /* No more commands, we're already closing. */ + set_effect(effect, stop_commands, true); + return STATE_WAIT_FOR_CLOSE_ACK; + +instant_close: + /* + * Closing, but we haven't sent anything to the blockchain so + * there's nothing to clean up. + */ + set_effect(effect, complete, CMD_CLOSE); + /* FIXME: Should we tell other side we're going? */ + set_effect(effect, stop_packets, true); + set_effect(effect, stop_commands, true); + return STATE_CLOSED; + +fail_during_close: + /* + * We've broadcast close tx; if anything goes wrong, we just close + * connection and wait. + */ + set_effect(effect, stop_packets, true); + + /* Once close tx is deep enough, we consider it done. */ + if (input_is(input, BITCOIN_CLOSE_DONE)) { + return STATE_CLOSED; + } else if (input_is(input, BITCOIN_ANCHOR_THEIRSPEND)) { + /* A reorganization could make this happen. */ + set_effect(effect, broadcast, + bitcoin_spend_theirs(effect, sdata)); + set_effect(effect, watch, + bitcoin_watch(effect, BITCOIN_SPEND_THEIRS_DONE)); + /* Expect either close or spendthem to complete */ + return STATE_CLOSE_WAIT_SPENDTHEM_CLOSE; + } else if (input_is(input, BITCOIN_ANCHOR_OTHERSPEND)) { + steal = bitcoin_steal(effect, sdata, idata->btc); + if (!steal) + return STATE_ERR_INFORMATION_LEAK; + set_effect(effect, broadcast, steal); + set_effect(effect, watch, + bitcoin_watch(effect, BITCOIN_STEAL_DONE)); + /* Expect either close or steal to complete */ + return STATE_CLOSE_WAIT_STEAL_CLOSE; + } else if (input_is(input, BITCOIN_ANCHOR_UNSPENT)) { + return STATE_ERR_ANCHOR_LOST; + } + return STATE_ERR_INTERNAL; + +old_commit_spotted: + /* + * bitcoind reported a broadcast of the not-latest commit tx. + */ + set_effect(effect, send, pkt_err(effect, "Otherspend noticed")); + + /* No more packets, no more commands. */ + set_effect(effect, stop_packets, true); + set_effect(effect, stop_commands, true); + + /* If we can't find it, we're lost. */ + steal = bitcoin_steal(effect, sdata, idata->btc); + if (!steal) + return STATE_ERR_INFORMATION_LEAK; + set_effect(effect, broadcast, steal); + set_effect(effect, watch, bitcoin_watch(effect, BITCOIN_STEAL_DONE)); + return STATE_CLOSE_WAIT_STEAL; +} diff --git a/state.h b/state.h new file mode 100644 index 000000000..fbec0147a --- /dev/null +++ b/state.h @@ -0,0 +1,258 @@ +#ifndef LIGHTNING_STATE_H +#define LIGHTNING_STATE_H +#include +#include +#include + +/* + * This is the core state machine. + * + * Calling the state machine with an input simply returns the new state, + * and populates the "effect" struct with what it wants done. + */ +extern char cmd_requeue; + +struct state_effect { + /* Transaction to broadcast. */ + struct bitcoin_tx *broadcast; + + /* Packet to send. */ + Pkt *send; + + /* Event to watch for. */ + struct watch *watch; + + /* Events to no longer watch for. */ + struct watch *unwatch; + + /* Defer an input. */ + enum state_input defer; + + /* Complete a command. */ + enum state_input complete; + /* NULL on success, &cmd_requeue on requeue, otherwise + * command-specific fail information. */ + void *complete_data; + + /* Stop taking packets? commands? */ + bool stop_packets, stop_commands; + + /* Set a timeout for close tx. */ + enum state_input close_timeout; + + /* Error received from other side. */ + Pkt *in_error; + + /* FIXME: More to come (for accept_*) */ +}; + +/* Initialize the above struct. */ +void state_effect_init(struct state_effect *effect); + +static inline bool state_is_error(enum state s) +{ + return s >= STATE_ERR_ANCHOR_TIMEOUT && s <= STATE_ERR_INTERNAL; +} + +struct state_data; + +static bool input_is_pkt(enum state_input input) +{ + return input <= PKT_ERROR; +} + +union input { + Pkt *pkt; + struct command *cmd; + struct bitcoin_event *btc; +}; + +enum state state(const enum state state, const struct state_data *sdata, + const enum state_input input, const union input *idata, + struct state_effect *effect); + +/* Either CMD_SEND_UPDATE or CMD_SEND_HTLC_UPDATE */ +#define CMD_SEND_UPDATE_ANY INPUT_MAX + +/* a == b? (or one of several for CMD_SEND_UPDATE_ANY) */ +static inline bool input_is(enum state_input a, enum state_input b) +{ + if (b == CMD_SEND_UPDATE_ANY) { + /* Single | here, we want to record all. */ + return input_is(a, CMD_SEND_UPDATE) + | input_is(a, CMD_SEND_HTLC_UPDATE) + | input_is(a, CMD_SEND_HTLC_COMPLETE) + | input_is(a, CMD_SEND_HTLC_TIMEDOUT) + | input_is(a, CMD_SEND_HTLC_ROUTEFAIL); + } + +/* For test_state_coverate to make the states. */ +#ifdef MAPPING_INPUTS + MAPPING_INPUTS(b); +#endif + return a == b; +} + +/* Create various kinds of packets, allocated off @ctx */ +Pkt *pkt_open(const tal_t *ctx, const struct state_data *sdata); +Pkt *pkt_anchor(const tal_t *ctx, const struct state_data *sdata); +Pkt *pkt_open_commit_sig(const tal_t *ctx, const struct state_data *sdata); +Pkt *pkt_open_complete(const tal_t *ctx, const struct state_data *sdata); +Pkt *pkt_update(const tal_t *ctx, const struct state_data *sdata, void *data); +Pkt *pkt_htlc_update(const tal_t *ctx, const struct state_data *sdata, void *data); +Pkt *pkt_htlc_complete(const tal_t *ctx, const struct state_data *sdata, void *data); +Pkt *pkt_htlc_timedout(const tal_t *ctx, const struct state_data *sdata, void *data); +Pkt *pkt_htlc_routefail(const tal_t *ctx, const struct state_data *sdata, void *data); +Pkt *pkt_update_accept(const tal_t *ctx, const struct state_data *sdata); +Pkt *pkt_update_signature(const tal_t *ctx, const struct state_data *sdata); +Pkt *pkt_update_complete(const tal_t *ctx, const struct state_data *sdata); +Pkt *pkt_err(const tal_t *ctx, const char *msg); +Pkt *pkt_close(const tal_t *ctx, const struct state_data *sdata); +Pkt *pkt_close_complete(const tal_t *ctx, const struct state_data *sdata); +Pkt *pkt_close_ack(const tal_t *ctx, const struct state_data *sdata); +Pkt *unexpected_pkt(const tal_t *ctx, enum state_input input); + +/* Process various packets: return an error packet on failure. */ +Pkt *accept_pkt_open(struct state_effect *effect, + const struct state_data *sdata, + const Pkt *pkt); + +Pkt *accept_pkt_anchor(struct state_effect *effect, + const struct state_data *sdata, + const Pkt *pkt); + +Pkt *accept_pkt_open_commit_sig(struct state_effect *effect, + const struct state_data *sdata, const Pkt *pkt); + +Pkt *accept_pkt_update(struct state_effect *effect, + const struct state_data *sdata, const Pkt *pkt); + +Pkt *accept_pkt_htlc_update(struct state_effect *effect, + const struct state_data *sdata, const Pkt *pkt, + Pkt **decline); + +Pkt *accept_pkt_htlc_routefail(struct state_effect *effect, + const struct state_data *sdata, const Pkt *pkt); + +Pkt *accept_pkt_htlc_timedout(struct state_effect *effect, + const struct state_data *sdata, const Pkt *pkt); + +Pkt *accept_pkt_htlc_complete(struct state_effect *effect, + const struct state_data *sdata, const Pkt *pkt); + +Pkt *accept_pkt_update_accept(struct state_effect *effect, + const struct state_data *sdata, const Pkt *pkt); + +Pkt *accept_pkt_update_complete(struct state_effect *effect, + const struct state_data *sdata, const Pkt *pkt); + +Pkt *accept_pkt_update_signature(struct state_effect *effect, + const struct state_data *sdata, + const Pkt *pkt); + +Pkt *accept_pkt_close(struct state_effect *effect, + const struct state_data *sdata, const Pkt *pkt); + +Pkt *accept_pkt_close_complete(struct state_effect *effect, + const struct state_data *sdata, const Pkt *pkt); + +Pkt *accept_pkt_simultaneous_close(struct state_effect *effect, + const struct state_data *sdata, + const Pkt *pkt); + +Pkt *accept_pkt_close_ack(struct state_effect *effect, + const struct state_data *sdata, const Pkt *pkt); + +/** + * bitcoin_watch_anchor: create a watch for the anchor. + * @ctx: context to tal the watch struct off. + * @sdata: the state data for this peer. + * @depthok: the input to give when anchor reaches expected depth. + * @timeout: the input to give if anchor doesn't reach depth in time. + * @unspent: the input to give if anchor is unspent after @depthok. + * @theyspent: the input to give if they spend anchor with their commit tx. + * @otherspent: the input to give if they spend anchor otherwise. + * + * @depthok can be INPUT_NONE if it's our anchor (we don't time + * ourselves out). + */ +struct watch *bitcoin_watch_anchor(const tal_t *ctx, + const struct state_data *sdata, + enum state_input depthok, + enum state_input timeout, + enum state_input unspent, + enum state_input theyspent, + enum state_input otherspent); + +/** + * bitcoin_unwatch_anchor_depth: remove depth watch for the anchor. + * @ctx: context to tal the watch struct off. + * @sdata: the state data for this peer. + * @depthok: the input to give when anchor reaches expected depth. + * @timeout: the input to give if anchor doesn't reach depth in time. + * + * @depthok and @timeout must match bitcoin_watch_anchor() call. + */ +struct watch *bitcoin_unwatch_anchor_depth(const tal_t *ctx, + const struct state_data *sdata, + enum state_input depthok, + enum state_input timeout); + +/** + * bitcoin_watch_delayed: watch this (commit) tx, tell me when I can spend it + * @effect: both the context to tal the watch off, and tx we're watching. + * @canspend: the input to give when commit reaches spendable depth. + */ +struct watch *bitcoin_watch_delayed(const struct state_effect *effect, + enum state_input canspend); + +/** + * bitcoin_watch: watch this tx until it's "irreversible" + * @effect: both the context to tal the watch off, and tx we're watching. + * @done: the input to give when tx is completely buried. + * + * The tx should be immalleable by BIP62; once this fires we consider + * the channel completely closed and stop watching (eg 100 txs down). + */ +struct watch *bitcoin_watch(const struct state_effect *effect, + enum state_input done); + +/** + * bitcoin_watch_close: watch close tx until it's "irreversible" + * @ctx: context to tal the watch struct off. + * @sdata: the state data for this peer. + * @done: the input to give when tx is completely buried. + * + * This tx *is* malleable, since the other side can transmit theirs. + */ +struct watch *bitcoin_watch_close(const tal_t *ctx, + const struct state_data *sdata, + enum state_input done); + + +/* Create a bitcoin anchor tx. */ +struct bitcoin_tx *bitcoin_anchor(const tal_t *ctx, + const struct state_data *sdata); + +/* Create a bitcoin close tx. */ +struct bitcoin_tx *bitcoin_close(const tal_t *ctx, + const struct state_data *sdata); + +/* Create a bitcoin spend tx (to spend our commit's outputs) */ +struct bitcoin_tx *bitcoin_spend_ours(const tal_t *ctx, + const struct state_data *sdata); + +/* Create a bitcoin spend tx (to spend their commit's outputs) */ +struct bitcoin_tx *bitcoin_spend_theirs(const tal_t *ctx, + const struct state_data *sdata); + +/* Create a bitcoin steal tx (to steal all their commit's outputs) */ +struct bitcoin_tx *bitcoin_steal(const tal_t *ctx, + const struct state_data *sdata, + struct bitcoin_event *btc); + +/* Create our commit tx */ +struct bitcoin_tx *bitcoin_commit(const tal_t *ctx, + const struct state_data *sdata); + +#endif /* LIGHTNING_STATE_H */