diff --git a/test/commits/13-fee.script b/test/commits/13-fee.script new file mode 100644 index 000000000..e040104fa --- /dev/null +++ b/test/commits/13-fee.script @@ -0,0 +1,13 @@ +# Simple test that we can change fee level +A:feechange +B:recvfeechange +A:commit +B:recvcommit +A:recvrevoke +B:commit +A:recvcommit +B:recvrevoke +echo ***A*** +A:dump +echo ***B*** +B:dump diff --git a/test/commits/13-fee.script.expected b/test/commits/13-fee.script.expected new file mode 100644 index 000000000..fae6d983b --- /dev/null +++ b/test/commits/13-fee.script.expected @@ -0,0 +1,24 @@ +***A*** +OUR COMMITS: + Commit 1: + Offered htlcs: + Received htlcs: + Fee level 1 + SIGNED +THEIR COMMITS: + Commit 1: + Offered htlcs: + Received htlcs: + SIGNED +***B*** +OUR COMMITS: + Commit 1: + Offered htlcs: + Received htlcs: + SIGNED +THEIR COMMITS: + Commit 1: + Offered htlcs: + Received htlcs: + Fee level 1 + SIGNED diff --git a/test/commits/14-fee-twice.script b/test/commits/14-fee-twice.script new file mode 100644 index 000000000..738edf44a --- /dev/null +++ b/test/commits/14-fee-twice.script @@ -0,0 +1,15 @@ +# We can change fee level twice. +A:feechange +A:feechange +A:commit +B:recvfeechange +B:recvfeechange +B:recvcommit +B:commit +A:recvrevoke +A:recvcommit +B:recvrevoke +echo ***A*** +A:dump +echo ***B*** +B:dump diff --git a/test/commits/14-fee-twice.script.expected b/test/commits/14-fee-twice.script.expected new file mode 100644 index 000000000..c94cc5e0a --- /dev/null +++ b/test/commits/14-fee-twice.script.expected @@ -0,0 +1,24 @@ +***A*** +OUR COMMITS: + Commit 1: + Offered htlcs: + Received htlcs: + Fee level 2 + SIGNED +THEIR COMMITS: + Commit 1: + Offered htlcs: + Received htlcs: + SIGNED +***B*** +OUR COMMITS: + Commit 1: + Offered htlcs: + Received htlcs: + SIGNED +THEIR COMMITS: + Commit 1: + Offered htlcs: + Received htlcs: + Fee level 2 + SIGNED diff --git a/test/commits/15-fee-twice-back-to-back.script b/test/commits/15-fee-twice-back-to-back.script new file mode 100644 index 000000000..db67a8e1e --- /dev/null +++ b/test/commits/15-fee-twice-back-to-back.script @@ -0,0 +1,25 @@ +# Test committing fees before receiving previous revocation. +A:feechange +A:commit +A:feechange +A:commit +echo ***A after two commits with fee changes*** +A:dump +B:recvfeechange +echo ***B after receiving fee change*** +B:dump +B:recvcommit +echo ***B after receiving first commit*** +B:dump +B:recvfeechange +B:recvcommit +echo ***B after receiving second commit*** +B:dump +A:recvrevoke +A:recvrevoke +B:commit +A:recvcommit +B:recvrevoke +echo ***B after committing*** +B:dump +checksync diff --git a/test/commits/15-fee-twice-back-to-back.script.expected b/test/commits/15-fee-twice-back-to-back.script.expected new file mode 100644 index 000000000..9288957be --- /dev/null +++ b/test/commits/15-fee-twice-back-to-back.script.expected @@ -0,0 +1,69 @@ +***A after two commits with fee changes*** +OUR COMMITS: + Commit 0: + Offered htlcs: + Received htlcs: + SIGNED +THEIR COMMITS: + Commit 0: + Offered htlcs: + Received htlcs: + Pending incoming: FEE + SIGNED + Commit 1: + Offered htlcs: + Received htlcs: + Pending incoming: FEE + SIGNED + Commit 2: + Offered htlcs: + Received htlcs: + SIGNED +***B after receiving fee change*** +OUR COMMITS: + Commit 0: + Offered htlcs: + Received htlcs: + Pending incoming: FEE + SIGNED +THEIR COMMITS: + Commit 0: + Offered htlcs: + Received htlcs: + SIGNED +***B after receiving first commit*** +OUR COMMITS: + Commit 1: + Offered htlcs: + Received htlcs: + SIGNED +THEIR COMMITS: + Commit 0: + Offered htlcs: + Received htlcs: + Pending outgoing: FEE + SIGNED +***B after receiving second commit*** +OUR COMMITS: + Commit 2: + Offered htlcs: + Received htlcs: + SIGNED +THEIR COMMITS: + Commit 0: + Offered htlcs: + Received htlcs: + Pending outgoing: FEE FEE + SIGNED +***B after committing*** +OUR COMMITS: + Commit 2: + Offered htlcs: + Received htlcs: + SIGNED +THEIR COMMITS: + Commit 1: + Offered htlcs: + Received htlcs: + Fee level 2 + SIGNED diff --git a/test/test_protocol.c b/test/test_protocol.c index 210a43928..959f9444c 100644 --- a/test/test_protocol.c +++ b/test/test_protocol.c @@ -16,6 +16,8 @@ struct funding { /* inhtlcs = htlcs they offered, outhtlcs = htlcs we offered */ u32 inhtlcs, outhtlcs; + /* This is a simple counter, reflecting fee updates. */ + u32 fee; }; /* We keep one for them, one for us. */ @@ -64,9 +66,10 @@ static bool add_change_internal(struct commit_info *ci, int **changes, int c) { size_t i, n = tal_count(*changes); - - if (!c) - errx(1, "INTERNAL: Adding zero change?"); + + /* You can have as many fee changes as you like */ + if (c == 0) + goto add; /* Can't add/remove it already there/absent. */ if (c > 0 && have_htlc(*add, c)) @@ -79,6 +82,7 @@ static bool add_change_internal(struct commit_info *ci, int **changes, if ((*changes)[i] == c) return false; +add: tal_resize(changes, n+1); (*changes)[n] = c; return true; @@ -127,15 +131,18 @@ static struct commit_info *new_commit_info(const tal_t *ctx, } /* Each side can add their own incoming HTLC, or close your outgoing HTLC. */ -static void do_change(u32 *add, u32 *remove, int htlc) +static void do_change(u32 *add, u32 *remove, u32 *fees, int htlc) { - if (htlc < 0) { + /* We ignore fee changes from them: they only count when reflected + * back to us via revocation. */ + if (htlc == 0) { + if (fees) + (*fees)++; + } else if (htlc < 0) { if (!have_htlc(*remove, -htlc)) errx(1, "Already removed htlc %u", -htlc); *remove &= ~htlc_mask(-htlc); } else { - if (htlc == 0) - errx(1, "zero change?"); if (have_htlc(*add, htlc)) errx(1, "Already added htlc %u", htlc); *add |= htlc_mask(htlc); @@ -154,6 +161,7 @@ static struct commit_info *apply_changes(const tal_t *ctx, for (i = 0; i < n; i++) do_change(&ci->funding.inhtlcs, &ci->funding.outhtlcs, + NULL, old->changes_incoming[i]); /* Changes we offered. */ @@ -161,6 +169,7 @@ static struct commit_info *apply_changes(const tal_t *ctx, for (i = 0; i < n; i++) do_change(&ci->funding.outhtlcs, &ci->funding.inhtlcs, + &ci->funding.fee, old->changes_outgoing[i]); return ci; @@ -198,18 +207,30 @@ static void dump_commit_info(const struct commit_info *ci) printf("\n Received htlcs:"); dump_htlcs(ci->funding.inhtlcs); + /* Don't clutter output if fee level untouched. */ + if (ci->funding.fee) + printf("\n Fee level %u", ci->funding.fee); + n = tal_count(ci->changes_incoming); if (n > 0) { printf("\n Pending incoming:"); - for (i = 0; i < n; i++) - printf(" %+i", ci->changes_incoming[i]); + for (i = 0; i < n; i++) { + if (ci->changes_incoming[i] == 0) + printf(" FEE"); + else + printf(" %+i", ci->changes_incoming[i]); + } } n = tal_count(ci->changes_outgoing); if (n > 0) { printf("\n Pending outgoing:"); - for (i = 0; i < n; i++) - printf(" %+i", ci->changes_outgoing[i]); + for (i = 0; i < n; i++) { + if (ci->changes_outgoing[i] == 0) + printf(" FEE"); + else + printf(" %+i", ci->changes_outgoing[i]); + } } if (ci->counterparty_signed) @@ -279,6 +300,16 @@ static void send_remove(struct peer *peer, unsigned int htlc) write_out(peer->outfd, &htlc, sizeof(htlc)); } +/* Fee change: + * - Record the change to them (but don't ever apply it!). + */ +static void send_feechange(struct peer *peer) +{ + if (!add_incoming_change(peer->them, 0)) + errx(1, "INTERNAL: failed to change fee"); + write_out(peer->outfd, "F", 1); +} + /* FIXME: * We don't enforce the rule that commits have to wait for revoke response * before the next one. It might be simpler if we did? @@ -363,6 +394,15 @@ static void receive_remove(struct peer *peer, unsigned int htlc) errx(1, "receive_remove: already removed %u", htlc); } +/* Receives fee change: + * - Record the change to us (but never apply it). + */ +static void receive_feechange(struct peer *peer) +{ + if (!add_incoming_change(peer->us, 0)) + errx(1, "INTERNAL: failed to change fee"); +} + /* Send revoke. * - Queue changes to them. */ @@ -401,9 +441,9 @@ static void receive_commit(struct peer *peer, const struct signature *sig) peer->us = apply_changes(peer, peer->us); oursig = commit_sig(peer->us); if (!structeq(sig, &oursig)) - errx(1, "Commit state %#x/%#x, they gave %#x/%#x", - sig->f.inhtlcs, sig->f.outhtlcs, - oursig.f.inhtlcs, oursig.f.outhtlcs); + errx(1, "Commit state %#x/%#x/%u, they gave %#x/%#x/%u", + sig->f.inhtlcs, sig->f.outhtlcs, sig->f.fee, + oursig.f.inhtlcs, oursig.f.outhtlcs, oursig.f.fee); peer->us->counterparty_signed = true; send_revoke(peer, peer->us->prev); } @@ -429,6 +469,8 @@ static void do_cmd(struct peer *peer) send_offer(peer, htlc); else if (sscanf(cmd, "remove %u", &htlc) == 1) send_remove(peer, htlc); + else if (streq(cmd, "feechange")) + send_feechange(peer); else if (streq(cmd, "commit")) send_commit(peer); else if (streq(cmd, "recvrevoke")) { @@ -444,6 +486,9 @@ static void do_cmd(struct peer *peer) read_peer(peer, "-", cmd); read_in(peer->infd, &htlc, sizeof(htlc)); receive_remove(peer, htlc); + } else if (streq(cmd, "recvfeechange")) { + read_peer(peer, "F", cmd); + receive_feechange(peer); } else if (streq(cmd, "recvcommit")) { struct signature sig; read_peer(peer, "C", cmd);