From 3260fb2ed19281197a67c1c690de586c285e9871 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 29 Jul 2015 16:16:24 +0930 Subject: [PATCH] protocol: add commitment fee logic. Both sides elect a commitment fee, and the lowest is chosen. That means you can't game the other side (but if you offer too low, then can error out of course). Fees are split 50-50 if possible: originally the whole fee has to be paid by the (single) funder. Neither side can withdraw funds which would make them unable to pay fees. Signed-off-by: Rusty Russell --- funding.c | 51 +++++++++++++++++++++++++---- funding.h | 10 ++++++ lightning.pb-c.c | 19 +++++++++-- lightning.pb-c.h | 6 +++- lightning.proto | 3 ++ pkt.c | 4 ++- pkt.h | 4 ++- test-cli/check-commit-sig.c | 3 +- test-cli/close-channel.c | 2 +- test-cli/create-close-tx.c | 2 +- test-cli/create-commit-spend-tx.c | 4 ++- test-cli/create-commit-tx.c | 4 ++- test-cli/gather_updates.c | 6 ++-- test-cli/gather_updates.h | 2 +- test-cli/open-channel.c | 9 ++++- test-cli/open-commit-sig.c | 2 +- test-cli/scripts/test.sh | 4 +-- test-cli/update-channel-accept.c | 3 +- test-cli/update-channel-complete.c | 2 +- test-cli/update-channel-signature.c | 3 +- 20 files changed, 115 insertions(+), 28 deletions(-) diff --git a/funding.c b/funding.c index 2f536b13a..496814f55 100644 --- a/funding.c +++ b/funding.c @@ -1,17 +1,18 @@ #include "funding.h" #include -/* FIXME: Fees! */ bool funding_delta(const OpenChannel *a, const OpenChannel *b, const OpenAnchor *anchor, + uint64_t fee, uint64_t *channel_delta, int64_t delta_a_to_b, uint64_t *a_amount, uint64_t *b_amount) { - uint64_t *funder_amount, *non_funder_amount; + uint64_t *funder_amount, *non_funder_amount, new_delta; int64_t delta_to_funder; + uint64_t funder_fee, non_funder_fee; assert(*channel_delta <= anchor->amount); @@ -39,20 +40,58 @@ bool funding_delta(const OpenChannel *a, } else if (-delta_to_funder > anchor->amount - *channel_delta) return false; - *channel_delta -= delta_to_funder; - *funder_amount = anchor->amount - *channel_delta; - *non_funder_amount = *channel_delta; + new_delta = *channel_delta - delta_to_funder; + *funder_amount = anchor->amount - new_delta; + *non_funder_amount = new_delta; + + /* We try to split fee. */ + funder_fee = fee / 2; + /* Funder gets any 1 satoshi rounding benefit! */ + non_funder_fee = fee - funder_fee; + + if (*non_funder_amount < non_funder_fee) { + /* + * This happens initially, as funder has all the money. + * That's OK, but don't let non-funder withdraw if they can't + * cover fee. + */ + if (delta_to_funder > 0) + return false; + + /* Pay everything they can, funder pays rest. */ + non_funder_fee = *non_funder_amount; + funder_fee = fee - non_funder_fee; + } + + /* Funder must always ensure they can pay their share. */ + if (*funder_amount < funder_fee) + return false; + + *funder_amount -= funder_fee; + *non_funder_amount -= non_funder_fee; + + /* Now we know we're succeeding, update caller's channel_delta */ + *channel_delta = new_delta; return true; } bool initial_funding(const OpenChannel *a, const OpenChannel *b, const OpenAnchor *anchor, + uint64_t fee, uint64_t *a_amount, uint64_t *b_amount) { uint64_t channel_delta = 0; - return funding_delta(a, b, anchor, &channel_delta, 0, + return funding_delta(a, b, anchor, fee, &channel_delta, 0, a_amount, b_amount); } + +/* We take the minimum. If one side offers too little, it should be rejected */ +uint64_t commit_fee(const OpenChannel *a, const OpenChannel *b) +{ + if (a->commitment_fee < b->commitment_fee) + return a->commitment_fee; + return b->commitment_fee; +} diff --git a/funding.h b/funding.h index 5c1c0accb..76b1e3782 100644 --- a/funding.h +++ b/funding.h @@ -9,12 +9,14 @@ * @a: A's openchannel offer * @b: B's openchannel offer * @anchor: The anchor offer (A or B) + * @fee: amount to pay in fees. * @a_amount: amount commit tx will output to A. * @b_amount: amount commit tx will output to B. */ bool initial_funding(const OpenChannel *a, const OpenChannel *b, const OpenAnchor *anchor, + uint64_t fee, uint64_t *a_amount, uint64_t *b_amount); @@ -23,6 +25,7 @@ bool initial_funding(const OpenChannel *a, * @a: A's openchannel offer * @b: B's openchannel offer * @anchor: The anchor offer (A or B) + * @fee: amount to pay in fees. * @channel_delta: In/out amount funder pays to non-funder (channel state) * @delta_a_to_b: How much A pays to B (satoshi). * @a_amount: amount commit tx will output to A. @@ -31,9 +34,16 @@ bool initial_funding(const OpenChannel *a, bool funding_delta(const OpenChannel *a, const OpenChannel *b, const OpenAnchor *anchor, + uint64_t fee, uint64_t *channel_delta, int64_t delta_a_to_b, uint64_t *a_amount, uint64_t *b_amount); +/** + * commit_fee: Fee amount for commit tx. + * @a: A's openchannel offer + * @b: B's openchannel offer + */ +uint64_t commit_fee(const OpenChannel *a, const OpenChannel *b); #endif /* LIGHTNING_FUNDING_H */ diff --git a/lightning.pb-c.c b/lightning.pb-c.c index 49c5060e3..8590f7d4b 100644 --- a/lightning.pb-c.c +++ b/lightning.pb-c.c @@ -925,7 +925,7 @@ const ProtobufCEnumDescriptor open_channel__anchor_offer__descriptor = NULL,NULL,NULL,NULL /* reserved[1234] */ }; static const uint32_t open_channel__min_depth__default_value = 0u; -static const ProtobufCFieldDescriptor open_channel__field_descriptors[7] = +static const ProtobufCFieldDescriptor open_channel__field_descriptors[8] = { { "final_key", @@ -1011,10 +1011,23 @@ static const ProtobufCFieldDescriptor open_channel__field_descriptors[7] = 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, + { + "commitment_fee", + 8, + PROTOBUF_C_LABEL_REQUIRED, + PROTOBUF_C_TYPE_UINT64, + 0, /* quantifier_offset */ + offsetof(OpenChannel, commitment_fee), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, }; static const unsigned open_channel__field_indices_by_name[] = { 5, /* field[5] = anch */ 4, /* field[4] = commit_key */ + 7, /* field[7] = commitment_fee */ 0, /* field[0] = final_key */ 2, /* field[2] = locktime_blocks */ 1, /* field[1] = locktime_seconds */ @@ -1024,7 +1037,7 @@ static const unsigned open_channel__field_indices_by_name[] = { static const ProtobufCIntRange open_channel__number_ranges[1 + 1] = { { 1, 0 }, - { 0, 7 } + { 0, 8 } }; const ProtobufCMessageDescriptor open_channel__descriptor = { @@ -1034,7 +1047,7 @@ const ProtobufCMessageDescriptor open_channel__descriptor = "OpenChannel", "", sizeof(OpenChannel), - 7, + 8, open_channel__field_descriptors, open_channel__field_indices_by_name, 1, open_channel__number_ranges, diff --git a/lightning.pb-c.h b/lightning.pb-c.h index 37924b60e..2e1818cbc 100644 --- a/lightning.pb-c.h +++ b/lightning.pb-c.h @@ -127,6 +127,10 @@ struct _OpenChannel */ protobuf_c_boolean has_min_depth; uint32_t min_depth; + /* + * How much fee would I like on commitment tx? + */ + uint64_t commitment_fee; OpenChannel__LocktimeCase locktime_case; union { uint32_t locktime_seconds; @@ -135,7 +139,7 @@ struct _OpenChannel }; #define OPEN_CHANNEL__INIT \ { PROTOBUF_C_MESSAGE_INIT (&open_channel__descriptor) \ - , NULL, NULL, NULL, 0, 0,0u, OPEN_CHANNEL__LOCKTIME__NOT_SET, {} } + , NULL, NULL, NULL, 0, 0,0u, 0, OPEN_CHANNEL__LOCKTIME__NOT_SET, {} } /* diff --git a/lightning.proto b/lightning.proto index c4688af55..1cbef50d0 100644 --- a/lightning.proto +++ b/lightning.proto @@ -58,6 +58,9 @@ message open_channel { // How far must anchor be buried before we consider channel live? optional uint32 min_depth = 7 [ default = 0 ]; + + // How much fee would I like on commitment tx? + required uint64 commitment_fee = 8; } // Whoever is supplying anchor sends this. diff --git a/pkt.c b/pkt.c index eeffef8fd..de6460213 100644 --- a/pkt.c +++ b/pkt.c @@ -37,7 +37,8 @@ struct pkt *open_channel_pkt(const tal_t *ctx, const struct pubkey *final, u32 rel_locktime_seconds, bool offer_anchor, - u32 min_depth) + u32 min_depth, + u64 commitment_fee) { OpenChannel o = OPEN_CHANNEL__INIT; @@ -46,6 +47,7 @@ struct pkt *open_channel_pkt(const tal_t *ctx, o.final_key = pubkey_to_proto(ctx, final); o.locktime_case = OPEN_CHANNEL__LOCKTIME_LOCKTIME_SECONDS; o.locktime_seconds = rel_locktime_seconds; + o.commitment_fee = commitment_fee; if (offer_anchor) o.anch = OPEN_CHANNEL__ANCHOR_OFFER__WILL_CREATE_ANCHOR; else diff --git a/pkt.h b/pkt.h index e081f21df..3e18f2241 100644 --- a/pkt.h +++ b/pkt.h @@ -37,6 +37,7 @@ struct pubkey; * @rel_locktime_seconds: relative seconds for commitment locktime. * @offer_anchor: whether we will offer anchor. * @min_depth: minimum depth to insist on (if non-zero) + * @commitment_fee: fee we would like for commitment txs. */ struct pkt *open_channel_pkt(const tal_t *ctx, const struct sha256 *revocation_hash, @@ -44,7 +45,8 @@ struct pkt *open_channel_pkt(const tal_t *ctx, const struct pubkey *final, u32 rel_locktime_seconds, bool offer_anchor, - u32 min_depth); + u32 min_depth, + u64 commitment_fee); /** * open_anchor_pkt - create an open_anchor message packet diff --git a/test-cli/check-commit-sig.c b/test-cli/check-commit-sig.c index 321214134..19b3ef0d2 100644 --- a/test-cli/check-commit-sig.c +++ b/test-cli/check-commit-sig.c @@ -68,7 +68,8 @@ int main(int argc, char *argv[]) if (!proto_to_pubkey(o2->commit_key, &pubkey2)) errx(1, "Invalid o2 commit_key"); - if (!initial_funding(o1, o2, a, &our_amount, &their_amount)) + if (!initial_funding(o1, o2, a, commit_fee(o1, o2), + &our_amount, &their_amount)) errx(1, "Invalid open combination (need 1 anchor offer)"); /* Now create our commitment tx. */ diff --git a/test-cli/close-channel.c b/test-cli/close-channel.c index 03b1af69c..51fddfa5e 100644 --- a/test-cli/close-channel.c +++ b/test-cli/close-channel.c @@ -57,7 +57,7 @@ int main(int argc, char *argv[]) if (!testnet) errx(1, "Private key '%s' not on testnet!", argv[4]); - gather_updates(o1, o2, a, argv + 5, &our_amount, &their_amount, + gather_updates(o1, o2, a, 0, argv + 5, &our_amount, &their_amount, NULL, NULL, NULL); /* Get pubkeys */ diff --git a/test-cli/create-close-tx.c b/test-cli/create-close-tx.c index 1c2de558c..894c2b8aa 100644 --- a/test-cli/create-close-tx.c +++ b/test-cli/create-close-tx.c @@ -56,7 +56,7 @@ int main(int argc, char *argv[]) errx(1, "Invalid o2 commit_key"); /* Get delta by accumulting all the updates. */ - gather_updates(o1, o2, a, argv + 6, &our_amount, &their_amount, + gather_updates(o1, o2, a, 0, argv + 6, &our_amount, &their_amount, NULL, NULL, NULL); /* This is what the anchor pays to; figure out which output. */ diff --git a/test-cli/create-commit-spend-tx.c b/test-cli/create-commit-spend-tx.c index ca3ea2163..d782b147b 100644 --- a/test-cli/create-commit-spend-tx.c +++ b/test-cli/create-commit-spend-tx.c @@ -19,6 +19,7 @@ #include "find_p2sh_out.h" #include "protobuf_convert.h" #include "test-cli/gather_updates.h" +#include "funding.h" #include int main(int argc, char *argv[]) @@ -81,7 +82,8 @@ int main(int argc, char *argv[]) errx(1, "Invalid o2 final pubkey"); /* We use this simply to get final revocation hash. */ - gather_updates(o1, o2, a, argv + 7, &our_amount, &their_amount, + gather_updates(o1, o2, a, commit_fee(o1, o2), argv + 7, + &our_amount, &their_amount, &rhash, NULL, NULL); /* Create redeem script */ diff --git a/test-cli/create-commit-tx.c b/test-cli/create-commit-tx.c index 7c21c32e4..d52ccfe5d 100644 --- a/test-cli/create-commit-tx.c +++ b/test-cli/create-commit-tx.c @@ -16,6 +16,7 @@ #include "find_p2sh_out.h" #include "protobuf_convert.h" #include "gather_updates.h" +#include "funding.h" #include /* FIXME: this code doesn't work if we're not the ones proposing the delta */ @@ -65,7 +66,8 @@ int main(int argc, char *argv[]) sig2.stype = SIGHASH_ALL; - gather_updates(o1, o2, a, argv + 5, &our_amount, &their_amount, + gather_updates(o1, o2, a, commit_fee(o1, o2), argv + 5, + &our_amount, &their_amount, &rhash, NULL, &sig2.sig); redeemscript = bitcoin_redeem_2of2(ctx, &pubkey1, &pubkey2); diff --git a/test-cli/gather_updates.c b/test-cli/gather_updates.c index 2579e6541..00c19a211 100644 --- a/test-cli/gather_updates.c +++ b/test-cli/gather_updates.c @@ -33,7 +33,7 @@ static void get_rhash(const Sha256Hash *rhash, struct sha256 *old, /* Takes complete update history, gets summary of last state. */ uint64_t gather_updates(const OpenChannel *o1, const OpenChannel *o2, - const OpenAnchor *oa, + const OpenAnchor *oa, uint64_t fee, char **argv, uint64_t *our_amount, uint64_t *their_amount, struct sha256 *our_rhash, @@ -46,7 +46,7 @@ uint64_t gather_updates(const OpenChannel *o1, const OpenChannel *o2, struct sha256 old_our_rhash, old_their_rhash; /* Start sanity check. */ - if (!initial_funding(o1, o2, oa, our_amount, their_amount)) + if (!initial_funding(o1, o2, oa, fee, our_amount, their_amount)) errx(1, "Invalid open combination (need 1 anchor offer)"); if (our_rhash) @@ -88,7 +88,7 @@ uint64_t gather_updates(const OpenChannel *o1, const OpenChannel *o2, get_rhash(pkt->update->revocation_hash, &old_our_rhash, our_rhash); } - if (!funding_delta(o1, o2, oa, &cdelta, delta, + if (!funding_delta(o1, o2, oa, fee, &cdelta, delta, our_amount, their_amount)) errx(1, "Impossible funding update %lli %s", (long long)delta, *argv); diff --git a/test-cli/gather_updates.h b/test-cli/gather_updates.h index bfd183dad..57309ffef 100644 --- a/test-cli/gather_updates.h +++ b/test-cli/gather_updates.h @@ -6,7 +6,7 @@ struct signature; struct sha256; uint64_t gather_updates(const OpenChannel *o1, const OpenChannel *o2, - const OpenAnchor *oa, + const OpenAnchor *oa, uint64_t fee, char **argv, uint64_t *our_amount, uint64_t *their_amount, struct sha256 *our_rhash, diff --git a/test-cli/open-channel.c b/test-cli/open-channel.c index fe3959994..4f8edfb2d 100644 --- a/test-cli/open-channel.c +++ b/test-cli/open-channel.c @@ -29,6 +29,7 @@ int main(int argc, char *argv[]) struct pkt *pkt; const tal_t *ctx = tal_arr(NULL, char, 0); unsigned int locktime_seconds, min_confirms; + u64 commit_tx_fee; bool offer_anchor = false; struct pubkey commitkey, finalkey; @@ -38,6 +39,8 @@ int main(int argc, char *argv[]) locktime_seconds = LOCKTIME_MIN + 24 * 60 * 60; /* Zero, unless they set --offer-anchor or --min-anchor-confirms */ min_confirms = 0; + /* We only need this for involuntary close, so make it larger. */ + commit_tx_fee = 100000; opt_register_noarg("--help|-h", opt_usage_and_exit, " \n" @@ -52,6 +55,9 @@ int main(int argc, char *argv[]) opt_register_noarg("--offer-anchor", opt_set_bool, &offer_anchor, "Offer to create anchor transaction"); + opt_register_arg("--commitment-fee=", + opt_set_bits, opt_show_bits, &commit_tx_fee, + "100's of satoshi to pay for commitment"); opt_parse(&argc, argv, opt_log_stderr_exit); @@ -76,7 +82,8 @@ int main(int argc, char *argv[]) revocation_hash.u.u8, sizeof(revocation_hash.u.u8)); pkt = open_channel_pkt(ctx, &revocation_hash, &commitkey, &finalkey, - locktime_seconds, offer_anchor, min_confirms); + locktime_seconds, offer_anchor, min_confirms, + commit_tx_fee); if (!write_all(STDOUT_FILENO, pkt, pkt_totlen(pkt))) err(1, "Writing out packet"); diff --git a/test-cli/open-commit-sig.c b/test-cli/open-commit-sig.c index f3ee3c638..af5f08a74 100644 --- a/test-cli/open-commit-sig.c +++ b/test-cli/open-commit-sig.c @@ -55,7 +55,7 @@ int main(int argc, char *argv[]) errx(1, "Private key '%s' not on testnet!", argv[4]); /* Now create THEIR commitment tx to spend 2/2 output of anchor. */ - if (!initial_funding(o1, o2, a, &to_us, &to_them)) + if (!initial_funding(o1, o2, a, commit_fee(o1, o2), &to_us, &to_them)) errx(1, "Invalid open combination (need 1 anchor offer)"); proto_to_sha256(o2->revocation_hash, &rhash); diff --git a/test-cli/scripts/test.sh b/test-cli/scripts/test.sh index 62a1d450d..ed8337099 100755 --- a/test-cli/scripts/test.sh +++ b/test-cli/scripts/test.sh @@ -114,8 +114,8 @@ B_UPDATE_PKTS="-- +B-commit-sig.pb" $PREFIX ./create-commit-tx A-open.pb B-open.pb A-anchor.pb $A_TMPKEY $A_UPDATE_PKTS > A-commit-0.tx $PREFIX ./create-commit-tx B-open.pb A-open.pb A-anchor.pb $B_TMPKEY $B_UPDATE_PKTS > B-commit-0.tx -# Now, update the channel, so I pay you 500 satoshi. -$PREFIX ./update-channel --to-them=500 $A_SEED 1 > A-update-1.pb +# Now, update the channel, so I pay you 60000 satoshi (covers 50000 fee) +$PREFIX ./update-channel --to-them=60000 $A_SEED 1 > A-update-1.pb A_UPDATE_PKTS="$A_UPDATE_PKTS +A-update-1.pb" B_UPDATE_PKTS="$B_UPDATE_PKTS -A-update-1.pb" diff --git a/test-cli/update-channel-accept.c b/test-cli/update-channel-accept.c index e589b05e4..0120d86e8 100644 --- a/test-cli/update-channel-accept.c +++ b/test-cli/update-channel-accept.c @@ -17,6 +17,7 @@ #include "find_p2sh_out.h" #include "protobuf_convert.h" #include "gather_updates.h" +#include "funding.h" #include int main(int argc, char *argv[]) @@ -60,7 +61,7 @@ int main(int argc, char *argv[]) errx(1, "Private key '%s' not on testnet!", argv[5]); /* Figure out cumulative delta since anchor. */ - num_updates = gather_updates(o1, o2, a, argv + 6, + num_updates = gather_updates(o1, o2, a, commit_fee(o1, o2), argv + 6, &our_amount, &their_amount, NULL, &their_rhash, NULL); diff --git a/test-cli/update-channel-complete.c b/test-cli/update-channel-complete.c index b709ddaa0..3ccc2f5ae 100644 --- a/test-cli/update-channel-complete.c +++ b/test-cli/update-channel-complete.c @@ -56,7 +56,7 @@ int main(int argc, char *argv[]) sig.stype = SIGHASH_ALL; /* This also checks that preimage is correct! */ - num_updates = gather_updates(o1, o2, a, argv + 5, + num_updates = gather_updates(o1, o2, a, commit_fee(o1, o2), argv + 5, &our_amount, &their_amount, &our_rhash, &their_rhash, &sig.sig); if (num_updates < 1) diff --git a/test-cli/update-channel-signature.c b/test-cli/update-channel-signature.c index 08f1a0799..734f14866 100644 --- a/test-cli/update-channel-signature.c +++ b/test-cli/update-channel-signature.c @@ -17,6 +17,7 @@ #include "find_p2sh_out.h" #include "protobuf_convert.h" #include "gather_updates.h" +#include "funding.h" #include int main(int argc, char *argv[]) @@ -62,7 +63,7 @@ int main(int argc, char *argv[]) sig.stype = SIGHASH_ALL; /* Figure out cumulative delta since anchor. */ - num_updates = gather_updates(o1, o2, a, argv + 6, + num_updates = gather_updates(o1, o2, a, commit_fee(o1, o2), argv + 6, &our_amount, &their_amount, &our_rhash, &their_rhash, &sig.sig); if (num_updates < 1)