Browse Source

closingd: rewrite negotiation.

This follows the proposal to make the funder send the first offer.
The dual loops are because there's initially very little restriction
on the amounts, then once they're first established they tighten as
necessary.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
ppa-0.6.1
Rusty Russell 7 years ago
committed by Christian Decker
parent
commit
03e961897a
  1. 492
      closingd/closing.c

492
closingd/closing.c

@ -74,15 +74,6 @@ static struct bitcoin_tx *close_tx(const tal_t *ctx,
return tx; return tx;
} }
static u64 one_towards(u64 target, u64 value)
{
if (value > target)
return value-1;
else if (value < target)
return value+1;
return value;
}
/* Handle random messages we might get, returning the first non-handled one. */ /* Handle random messages we might get, returning the first non-handled one. */
static u8 *closing_read_peer_msg(const tal_t *ctx, static u8 *closing_read_peer_msg(const tal_t *ctx,
struct crypto_state *cs, struct crypto_state *cs,
@ -157,82 +148,24 @@ static void do_reconnect(struct crypto_state *cs,
tal_free(tmpctx); tal_free(tmpctx);
} }
int main(int argc, char *argv[]) static void send_offer(struct crypto_state *cs,
const struct channel_id *channel_id,
const struct pubkey funding_pubkey[NUM_SIDES],
const u8 *funding_wscript,
u8 *scriptpubkey[NUM_SIDES],
const struct bitcoin_txid *funding_txid,
unsigned int funding_txout,
u64 funding_satoshi,
const u64 satoshi_out[NUM_SIDES],
enum side funder,
uint64_t our_dust_limit,
const struct secrets *secrets,
uint64_t fee_to_offer)
{ {
struct crypto_state cs; const tal_t *tmpctx = tal_tmpctx(NULL);
const tal_t *ctx = tal_tmpctx(NULL);
u8 *msg;
struct privkey seed;
struct pubkey funding_pubkey[NUM_SIDES];
struct bitcoin_txid funding_txid;
u16 funding_txout;
u64 funding_satoshi, satoshi_out[NUM_SIDES];
u64 our_dust_limit;
u64 minfee, feelimit, maxfee, sent_fee;
s64 last_received_fee = -1;
enum side funder;
u8 *scriptpubkey[NUM_SIDES], *funding_wscript;
struct channel_id channel_id;
struct secrets secrets;
secp256k1_ecdsa_signature sig;
bool reconnected;
u64 next_index[NUM_SIDES], revocations_received;
u64 gossip_index;
subdaemon_setup(argc, argv);
status_setup_sync(REQ_FD);
msg = wire_sync_read(ctx, REQ_FD);
if (!fromwire_closing_init(ctx, msg, NULL,
&cs, &gossip_index, &seed,
&funding_txid, &funding_txout,
&funding_satoshi,
&funding_pubkey[REMOTE],
&funder,
&satoshi_out[LOCAL],
&satoshi_out[REMOTE],
&our_dust_limit,
&minfee, &maxfee, &sent_fee,
&scriptpubkey[LOCAL],
&scriptpubkey[REMOTE],
&reconnected,
&next_index[LOCAL],
&next_index[REMOTE],
&revocations_received))
master_badmsg(WIRE_CLOSING_INIT, msg);
status_trace("satoshi_out = %"PRIu64"/%"PRIu64,
satoshi_out[LOCAL], satoshi_out[REMOTE]);
status_trace("dustlimit = %"PRIu64, our_dust_limit);
status_trace("fee = %"PRIu64, sent_fee);
derive_channel_id(&channel_id, &funding_txid, funding_txout);
derive_basepoints(&seed, &funding_pubkey[LOCAL], NULL,
&secrets, NULL);
funding_wscript = bitcoin_redeem_2of2(ctx,
&funding_pubkey[LOCAL],
&funding_pubkey[REMOTE]);
if (reconnected)
do_reconnect(&cs, &channel_id, next_index, revocations_received);
/* BOLT #2:
*
* Nodes SHOULD send a `closing_signed` message after `shutdown` has
* been received and no HTLCs remain in either commitment transaction.
*/
/* BOLT #2:
*
* On reconnection, ... if the node has sent a previous
* `closing_signed` it MUST send another `closing_signed`, otherwise
* if the node has sent a previous `shutdown` it MUST retransmit it.
*/
for (;;) {
const tal_t *tmpctx = tal_tmpctx(ctx);
struct bitcoin_tx *tx; struct bitcoin_tx *tx;
u64 received_fee, limit_fee, new_fee; secp256k1_ecdsa_signature our_sig;
u8 *msg;
/* BOLT #2: /* BOLT #2:
* *
@ -240,12 +173,13 @@ int main(int argc, char *argv[])
* the close transaction as specified in [BOLT * the close transaction as specified in [BOLT
* #3](03-transactions.md#closing-transaction). * #3](03-transactions.md#closing-transaction).
*/ */
tx = close_tx(tmpctx, &cs, &channel_id, tx = close_tx(tmpctx, cs, channel_id,
scriptpubkey, scriptpubkey,
&funding_txid, funding_txid,
funding_txout, funding_txout,
funding_satoshi, funding_satoshi,
satoshi_out, funder, sent_fee, our_dust_limit); satoshi_out,
funder, fee_to_offer, our_dust_limit);
/* BOLT #3: /* BOLT #3:
* *
@ -256,25 +190,61 @@ int main(int argc, char *argv[])
*/ */
/* (We don't do this). */ /* (We don't do this). */
sign_tx_input(tx, 0, NULL, funding_wscript, sign_tx_input(tx, 0, NULL, funding_wscript,
&secrets.funding_privkey, &secrets->funding_privkey,
&funding_pubkey[LOCAL], &funding_pubkey[LOCAL],
&sig); &our_sig);
status_trace("sending fee offer %"PRIu64, sent_fee); status_trace("sending fee offer %"PRIu64, fee_to_offer);
/* Now send closing offer */ msg = towire_closing_signed(tmpctx, channel_id, fee_to_offer, &our_sig);
msg = towire_closing_signed(tmpctx, &channel_id, sent_fee, &sig); if (!sync_crypto_write(cs, PEER_FD, take(msg)))
if (!sync_crypto_write(&cs, PEER_FD, take(msg)))
status_failed(STATUS_FAIL_PEER_IO, status_failed(STATUS_FAIL_PEER_IO,
"Writing closing_signed"); "Writing closing_signed");
/* Did we just agree with them? If so, we're done. */ tal_free(tmpctx);
if (sent_fee == last_received_fee) }
break;
static void tell_master_their_offer(u64 their_offer,
const secp256k1_ecdsa_signature *their_sig,
const struct bitcoin_tx *tx)
{
u8 *msg = towire_closing_received_signature(NULL, their_sig, tx);
if (!wire_sync_write(REQ_FD, take(msg)))
status_failed(STATUS_FAIL_MASTER_IO,
"Writing received to master: %s",
strerror(errno));
/* Wait for master to ack, to make sure it's in db. */
msg = wire_sync_read(NULL, REQ_FD);
if (!fromwire_closing_received_signature_reply(msg,NULL))
master_badmsg(WIRE_CLOSING_RECEIVED_SIGNATURE_REPLY, msg);
tal_free(msg);
}
/* Returns fee they offered. */
static uint64_t receive_offer(struct crypto_state *cs,
const struct channel_id *channel_id,
const struct pubkey funding_pubkey[NUM_SIDES],
const u8 *funding_wscript,
u8 *scriptpubkey[NUM_SIDES],
const struct bitcoin_txid *funding_txid,
unsigned int funding_txout,
u64 funding_satoshi,
const u64 satoshi_out[NUM_SIDES],
enum side funder,
uint64_t our_dust_limit,
u64 min_fee_to_accept)
{
const tal_t *tmpctx = tal_tmpctx(NULL);
u8 *msg;
struct channel_id their_channel_id;
u64 received_fee;
secp256k1_ecdsa_signature their_sig;
struct bitcoin_tx *tx;
again:
/* Wait for them to say something interesting */ /* Wait for them to say something interesting */
msg = closing_read_peer_msg(tmpctx, &cs, &channel_id); do {
msg = closing_read_peer_msg(tmpctx, cs, channel_id);
/* BOLT #2: /* BOLT #2:
* *
@ -283,24 +253,20 @@ int main(int argc, char *argv[])
*/ */
/* This should only happen if we've made no commitments, but /* This should only happen if we've made no commitments, but
* we don't have to check that: it's their problem. */ * we don't have to check that: it's their problem. */
if (fromwire_peektype(msg) == WIRE_FUNDING_LOCKED) { if (fromwire_peektype(msg) == WIRE_FUNDING_LOCKED)
tal_free(msg); msg = tal_free(msg);
goto again;
}
/* BOLT #2: /* BOLT #2:
* *
* ...if the node has sent a previous `shutdown` it MUST * ...if the node has sent a previous `shutdown` it MUST
* retransmit it. * retransmit it.
*/ */
if (fromwire_peektype(msg) == WIRE_SHUTDOWN) { else if (fromwire_peektype(msg) == WIRE_SHUTDOWN)
tal_free(msg); msg = tal_free(msg);
goto again; } while (!msg);
}
if (!fromwire_closing_signed(msg, NULL, &channel_id, if (!fromwire_closing_signed(msg, NULL, &their_channel_id,
&received_fee, &sig)) &received_fee, &their_sig))
peer_failed(PEER_FD, &cs, &channel_id, peer_failed(PEER_FD, cs, channel_id,
"Expected closing_signed: %s", "Expected closing_signed: %s",
tal_hex(trc, msg)); tal_hex(trc, msg));
@ -311,15 +277,15 @@ int main(int argc, char *argv[])
* #3](03-transactions.md#closing-transaction), and MUST fail * #3](03-transactions.md#closing-transaction), and MUST fail
* the connection if it is not. * the connection if it is not.
*/ */
tx = close_tx(tmpctx, &cs, &channel_id, tx = close_tx(tmpctx, cs, channel_id,
scriptpubkey, scriptpubkey,
&funding_txid, funding_txid,
funding_txout, funding_txout,
funding_satoshi, funding_satoshi,
satoshi_out, funder, received_fee, our_dust_limit); satoshi_out, funder, received_fee, our_dust_limit);
if (!check_tx_sig(tx, 0, NULL, funding_wscript, if (!check_tx_sig(tx, 0, NULL, funding_wscript,
&funding_pubkey[REMOTE], &sig)) { &funding_pubkey[REMOTE], &their_sig)) {
/* Trim it by reducing their output to minimum */ /* Trim it by reducing their output to minimum */
struct bitcoin_tx *trimmed; struct bitcoin_tx *trimmed;
u64 trimming_satoshi_out[NUM_SIDES]; u64 trimming_satoshi_out[NUM_SIDES];
@ -332,23 +298,22 @@ int main(int argc, char *argv[])
/* BOLT #3: /* BOLT #3:
* *
* Each node offering a signature MUST subtract the * Each node offering a signature MUST subtract the fee given
* fee given by `fee_satoshis` from the output to the * by `fee_satoshis` from the output to the funder; it MUST
* funder; it MUST then remove any output below its * then remove any output below its own `dust_limit_satoshis`,
* own `dust_limit_satoshis`, and MAY also eliminate * and MAY also eliminate its own output.
* its own output.
*/ */
trimmed = close_tx(tmpctx, &cs, &channel_id, trimmed = close_tx(tmpctx, cs, channel_id,
scriptpubkey, scriptpubkey,
&funding_txid, funding_txid,
funding_txout, funding_txout,
funding_satoshi, funding_satoshi,
trimming_satoshi_out, trimming_satoshi_out,
funder, received_fee, our_dust_limit); funder, received_fee, our_dust_limit);
if (!trimmed if (!trimmed
|| !check_tx_sig(trimmed, 0, NULL, funding_wscript, || !check_tx_sig(trimmed, 0, NULL, funding_wscript,
&funding_pubkey[REMOTE], &sig)) { &funding_pubkey[REMOTE], &their_sig)) {
peer_failed(PEER_FD, &cs, &channel_id, peer_failed(PEER_FD, cs, channel_id,
"Bad closing_signed signature for" "Bad closing_signed signature for"
" %s (and trimmed version %s)", " %s (and trimmed version %s)",
type_to_string(tmpctx, type_to_string(tmpctx,
@ -365,103 +330,222 @@ int main(int argc, char *argv[])
status_trace("Received fee offer %"PRIu64, received_fee); status_trace("Received fee offer %"PRIu64, received_fee);
/* BOLT #2: /* Master sorts out what is best offer, we just tell it any above min */
* if (received_fee >= min_fee_to_accept) {
* Otherwise, the recipient MUST fail the connection if status_trace("...offer is reasonable");
* `fee_satoshis` is greater than the base fee of the final tell_master_their_offer(received_fee, &their_sig, tx);
* commitment transaction as calculated in [BOLT #3] */
if (received_fee > maxfee)
peer_failed(PEER_FD, &cs, &channel_id,
"Bad closing_signed fee %"PRIu64" > %"PRIu64,
received_fee, maxfee);
/* Is fee reasonable? Tell master. */
if (received_fee < minfee) {
status_trace("Fee too low, below %"PRIu64, minfee);
limit_fee = minfee;
} else {
status_trace("Fee accepted.");
msg = towire_closing_received_signature(tmpctx,
&sig, tx);
if (!wire_sync_write(REQ_FD, take(msg)))
status_failed(STATUS_FAIL_MASTER_IO,
"Writing received to master: %s",
strerror(errno));
msg = wire_sync_read(tmpctx, REQ_FD);
if (!fromwire_closing_received_signature_reply(msg,NULL))
master_badmsg(WIRE_CLOSING_RECEIVED_SIGNATURE_REPLY,
msg);
limit_fee = received_fee;
} }
tal_free(tmpctx);
return received_fee;
}
struct feerange {
enum side higher_side;
u64 min, max;
};
static void init_feerange(struct feerange *feerange,
u64 commitment_fee,
const u64 offer[NUM_SIDES])
{
feerange->min = 0;
/* BOLT #2: /* BOLT #2:
* *
* If `fee_satoshis` is equal to its previously sent * - MUST set `fee_satoshis` less than or equal to the base
* `fee_satoshis`, the receiver SHOULD sign and broadcast the * fee of the final commitment transaction, as calculated
* final closing transaction and MAY close the connection. * in [BOLT #3](03-transactions.md#fee-calculation).
*/ */
if (received_fee == sent_fee) feerange->max = commitment_fee;
break;
if (offer[LOCAL] > offer[REMOTE])
feerange->higher_side = LOCAL;
else
feerange->higher_side = REMOTE;
status_trace("Feerange init %"PRIu64"-%"PRIu64", %s higher",
feerange->min, feerange->max,
feerange->higher_side == LOCAL ? "local" : "remote");
}
static void adjust_feerange(struct crypto_state *cs,
const struct channel_id *channel_id,
struct feerange *feerange,
u64 offer, enum side side)
{
if (offer < feerange->min || offer > feerange->max)
peer_failed(PEER_FD, cs, channel_id,
"%s offer %"PRIu64
" not between %"PRIu64" and %"PRIu64,
side == LOCAL ? "local" : "remote",
offer, feerange->min, feerange->max);
/* BOLT #2: /* BOLT #2:
* *
* the recipient SHOULD fail the connection if `fee_satoshis` * ...otherwise it MUST propose a value strictly between the received
* is not strictly between its last-sent `fee_satoshis` and * `fee_satoshis` and its previously-sent `fee_satoshis`.
* its previously-received `fee_satoshis`, unless it has */
* reconnected since then. */ if (side == feerange->higher_side)
if (last_received_fee != -1) { feerange->max = offer - 1;
bool previous_dir = sent_fee < last_received_fee; else
bool dir = received_fee < last_received_fee; feerange->min = offer + 1;
bool next_dir = sent_fee < received_fee;
status_trace("Feerange %s update %"PRIu64": now %"PRIu64"-%"PRIu64,
/* They went away from our offer? */ side == LOCAL ? "local" : "remote",
if (dir != previous_dir) offer, feerange->min, feerange->max);
peer_failed(PEER_FD, &cs, &channel_id, }
"Their fee went %"
PRIu64" to %"PRIu64 /* Figure out what we should offer now. */
" when ours was %"PRIu64, static u64 adjust_offer(struct crypto_state *cs,
last_received_fee, const struct channel_id *channel_id,
received_fee, const struct feerange *feerange,
sent_fee); u64 remote_offer,
u64 min_fee_to_accept)
/* They jumped over our offer? */ {
if (next_dir != previous_dir) /* Within 1 satoshi? Agree. */
peer_failed(PEER_FD, &cs, &channel_id, if (feerange->min + 1 >= feerange->max)
"Their fee jumped %" return remote_offer;
PRIu64" to %"PRIu64
" when ours was %"PRIu64, /* Max is below our minimum acceptable? */
last_received_fee, if (feerange->max < min_fee_to_accept)
received_fee, peer_failed(PEER_FD, cs, channel_id,
sent_fee); "Feerange %"PRIu64"-%"PRIu64
" below minimum acceptable %"PRIu64,
feerange->min, feerange->max,
min_fee_to_accept);
/* Bisect between our minimum and max. */
if (feerange->min > min_fee_to_accept)
min_fee_to_accept = feerange->min;
return (feerange->max + min_fee_to_accept)/2;
} }
int main(int argc, char *argv[])
{
struct crypto_state cs;
const tal_t *ctx = tal_tmpctx(NULL);
u8 *msg;
struct privkey seed;
struct pubkey funding_pubkey[NUM_SIDES];
struct bitcoin_txid funding_txid;
u16 funding_txout;
u64 funding_satoshi, satoshi_out[NUM_SIDES];
u64 our_dust_limit;
u64 min_fee_to_accept, commitment_fee, offer[NUM_SIDES];
struct feerange feerange;
enum side funder;
u8 *scriptpubkey[NUM_SIDES], *funding_wscript;
struct channel_id channel_id;
struct secrets secrets;
bool reconnected;
u64 next_index[NUM_SIDES], revocations_received;
u64 gossip_index;
enum side whose_turn;
subdaemon_setup(argc, argv);
status_setup_sync(REQ_FD);
msg = wire_sync_read(ctx, REQ_FD);
if (!fromwire_closing_init(ctx, msg, NULL,
&cs, &gossip_index, &seed,
&funding_txid, &funding_txout,
&funding_satoshi,
&funding_pubkey[REMOTE],
&funder,
&satoshi_out[LOCAL],
&satoshi_out[REMOTE],
&our_dust_limit,
&min_fee_to_accept, &commitment_fee,
&offer[LOCAL],
&scriptpubkey[LOCAL],
&scriptpubkey[REMOTE],
&reconnected,
&next_index[LOCAL],
&next_index[REMOTE],
&revocations_received))
master_badmsg(WIRE_CLOSING_INIT, msg);
status_trace("satoshi_out = %"PRIu64"/%"PRIu64,
satoshi_out[LOCAL], satoshi_out[REMOTE]);
status_trace("dustlimit = %"PRIu64, our_dust_limit);
status_trace("fee = %"PRIu64, offer[LOCAL]);
derive_channel_id(&channel_id, &funding_txid, funding_txout);
derive_basepoints(&seed, &funding_pubkey[LOCAL], NULL,
&secrets, NULL);
funding_wscript = bitcoin_redeem_2of2(ctx,
&funding_pubkey[LOCAL],
&funding_pubkey[REMOTE]);
if (reconnected)
do_reconnect(&cs, &channel_id, next_index, revocations_received);
/* BOLT #2: /* BOLT #2:
* *
* ...otherwise it MUST propose a value strictly between the * The funding node:
* received `fee_satoshis` and its previously-sent * - after `shutdown` has been received, AND no HTLCs remain in either
* `fee_satoshis`. * commitment transaction:
* - SHOULD send a `closing_signed` message.
*/ */
whose_turn = funder;
for (size_t i = 0; i < 2; i++, whose_turn = !whose_turn) {
if (whose_turn == LOCAL) {
send_offer(&cs, &channel_id, funding_pubkey,
funding_wscript,
scriptpubkey, &funding_txid, funding_txout,
funding_satoshi, satoshi_out, funder,
our_dust_limit, &secrets, offer[LOCAL]);
} else {
offer[REMOTE]
= receive_offer(&cs, &channel_id, funding_pubkey,
funding_wscript,
scriptpubkey, &funding_txid,
funding_txout, funding_satoshi,
satoshi_out, funder,
our_dust_limit,
min_fee_to_accept);
}
}
/* We do it by bisection, with twists: /* Now we have first two points, we can init fee range. */
* 1. Don't go outside limits, or reach them immediately: init_feerange(&feerange, commitment_fee, offer);
* treat out-of-limit offers as on-limit offers.
* 2. Round towards the target, otherwise we can't close /* Now apply the one constraint from above (other is inside loop). */
* a final 1-satoshi gap. adjust_feerange(&cs, &channel_id, &feerange,
* offer[!whose_turn], !whose_turn);
* Note: Overflow impossible here, since fee <= funder amount */
new_fee = one_towards(limit_fee, limit_fee + sent_fee) / 2; /* Now any extra rounds required. */
while (offer[LOCAL] != offer[REMOTE]) {
/* If we didn't move, give up (we're ~ at min/max). */ /* If they differ, adjust feerate. */
if (new_fee == sent_fee) adjust_feerange(&cs, &channel_id, &feerange,
peer_failed(PEER_FD, &cs, &channel_id, offer[whose_turn], whose_turn);
"Final fee %"PRIu64" vs %"PRIu64
" at limits %"PRIu64"-%"PRIu64, /* Now its the other side's turn. */
sent_fee, received_fee, whose_turn = !whose_turn;
minfee, maxfee);
if (whose_turn == LOCAL) {
last_received_fee = received_fee; offer[LOCAL] = adjust_offer(&cs, &channel_id,
sent_fee = new_fee; &feerange, offer[REMOTE],
tal_free(tmpctx); min_fee_to_accept);
send_offer(&cs, &channel_id, funding_pubkey,
funding_wscript,
scriptpubkey, &funding_txid, funding_txout,
funding_satoshi, satoshi_out, funder,
our_dust_limit, &secrets, offer[LOCAL]);
} else {
offer[REMOTE]
= receive_offer(&cs, &channel_id, funding_pubkey,
funding_wscript,
scriptpubkey, &funding_txid,
funding_txout, funding_satoshi,
satoshi_out, funder,
our_dust_limit,
min_fee_to_accept);
}
} }
/* We're done! */ /* We're done! */

Loading…
Cancel
Save