5 changed files with 481 additions and 0 deletions
@ -0,0 +1,59 @@ |
|||
#! /usr/bin/make
|
|||
|
|||
# Designed to be run one level up
|
|||
lightningd/closing-wrongdir: |
|||
$(MAKE) -C ../.. lightningd/closing-all |
|||
|
|||
default: lightningd/closing-all |
|||
|
|||
lightningd/closing-all: lightningd/lightningd_closing |
|||
|
|||
# lightningd/closing needs these:
|
|||
LIGHTNINGD_CLOSING_HEADERS_GEN := \
|
|||
lightningd/closing/gen_closing_wire.h |
|||
|
|||
LIGHTNINGD_CLOSING_HEADERS_NOGEN := |
|||
|
|||
LIGHTNINGD_CLOSING_HEADERS := $(LIGHTNINGD_CLOSING_HEADERS_GEN) $(LIGHTNINGD_CLOSING_HEADERS_NOGEN) |
|||
|
|||
LIGHTNINGD_CLOSING_SRC := lightningd/closing/closing.c \
|
|||
$(LIGHTNINGD_CLOSING_HEADERS:.h=.c) |
|||
LIGHTNINGD_CLOSING_OBJS := $(LIGHTNINGD_CLOSING_SRC:.c=.o) |
|||
|
|||
# Control daemon uses this:
|
|||
LIGHTNINGD_CLOSING_CONTROL_HEADERS := $(LIGHTNINGD_CLOSING_HEADERS) |
|||
LIGHTNINGD_CLOSING_CONTROL_SRC := $(LIGHTNINGD_CLOSING_HEADERS:.h=.c) |
|||
LIGHTNINGD_CLOSING_CONTROL_OBJS := $(LIGHTNINGD_CLOSING_CONTROL_SRC:.c=.o) |
|||
|
|||
LIGHTNINGD_CLOSING_GEN_SRC := $(filter lightningd/closing/gen_%, $(LIGHTNINGD_CLOSING_SRC) $(LIGHTNINGD_CLOSING_CONTROL_SRC)) |
|||
|
|||
LIGHTNINGD_CLOSING_SRC_NOGEN := $(filter-out lightningd/closing/gen_%, $(LIGHTNINGD_CLOSING_SRC)) |
|||
|
|||
# Add to headers which any object might need.
|
|||
LIGHTNINGD_HEADERS_GEN += $(LIGHTNINGD_CLOSING_HEADERS_GEN) |
|||
LIGHTNINGD_HEADERS_NOGEN += $(LIGHTNINGD_CLOSING_HEADERS_NOGEN) |
|||
|
|||
$(LIGHTNINGD_CLOSING_OBJS): $(LIGHTNINGD_HEADERS) |
|||
|
|||
lightningd/closing/gen_closing_wire.h: $(WIRE_GEN) lightningd/closing/closing_wire.csv |
|||
$(WIRE_GEN) --header $@ closing_wire_type < lightningd/closing/closing_wire.csv > $@ |
|||
|
|||
lightningd/closing/gen_closing_wire.c: $(WIRE_GEN) lightningd/closing/closing_wire.csv |
|||
$(WIRE_GEN) ${@:.c=.h} closing_wire_type < lightningd/closing/closing_wire.csv > $@ |
|||
|
|||
LIGHTNINGD_CLOSING_OBJS := $(LIGHTNINGD_CLOSING_SRC:.c=.o) $(LIGHTNINGD_CLOSING_GEN_SRC:.c=.o) |
|||
|
|||
lightningd/lightningd_closing: $(LIGHTNINGD_OLD_LIB_OBJS) $(LIGHTNINGD_LIB_OBJS) $(LIGHTNINGD_CLOSING_OBJS) $(WIRE_ONION_OBJS) $(CORE_OBJS) $(CORE_TX_OBJS) $(WIRE_OBJS) $(BITCOIN_OBJS) $(CCAN_OBJS) $(CCAN_SHACHAIN48_OBJ) $(LIGHTNINGD_HSM_CLIENT_OBJS) $(LIBBASE58_OBJS) libsecp256k1.a libsodium.a libwallycore.a |
|||
$(CC) $(CFLAGS) -o $@ $^ $(LDLIBS) |
|||
|
|||
check-source: $(LIGHTNINGD_CLOSING_SRC_NOGEN:%=check-src-include-order/%) |
|||
check-source-bolt: $(LIGHTNINGD_CLOSING_SRC:%=bolt-check/%) $(LIGHTNINGD_CLOSING_HEADERS:%=bolt-check/%) |
|||
|
|||
check-whitespace: $(LIGHTNINGD_CLOSING_SRC_NOGEN:%=check-whitespace/%) $(LIGHTNINGD_CLOSING_HEADERS_NOGEN:%=check-whitespace/%) |
|||
|
|||
clean: lightningd/closing-clean |
|||
|
|||
lightningd/closing-clean: |
|||
$(RM) $(LIGHTNINGD_CLOSING_OBJS) gen_* |
|||
|
|||
-include lightningd/closing/test/Makefile |
@ -0,0 +1,366 @@ |
|||
#include <bitcoin/script.h> |
|||
#include <close_tx.h> |
|||
#include <daemon/htlc.h> |
|||
#include <errno.h> |
|||
#include <inttypes.h> |
|||
#include <lightningd/closing/gen_closing_wire.h> |
|||
#include <lightningd/crypto_sync.h> |
|||
#include <lightningd/debug.h> |
|||
#include <lightningd/derive_basepoints.h> |
|||
#include <lightningd/status.h> |
|||
#include <lightningd/subd.h> |
|||
#include <signal.h> |
|||
#include <stdio.h> |
|||
#include <type_to_string.h> |
|||
#include <utils.h> |
|||
#include <version.h> |
|||
#include <wire/peer_wire.h> |
|||
#include <wire/wire_sync.h> |
|||
|
|||
/* stdin == requests, 3 == peer, 4 = gossip */ |
|||
#define REQ_FD STDIN_FILENO |
|||
#define PEER_FD 3 |
|||
#define GOSSIP_FD 4 |
|||
|
|||
static struct bitcoin_tx *close_tx(const tal_t *ctx, |
|||
u8 *scriptpubkey[NUM_SIDES], |
|||
const struct sha256_double *funding_txid, |
|||
unsigned int funding_txout, |
|||
u64 funding_satoshi, |
|||
const u64 satoshi_out[NUM_SIDES], |
|||
enum side funder, |
|||
uint64_t fee, |
|||
uint64_t dust_limit) |
|||
{ |
|||
struct bitcoin_tx *tx; |
|||
|
|||
if (satoshi_out[funder] < fee) |
|||
status_failed(WIRE_CLOSING_NEGOTIATION_ERROR, |
|||
"Funder cannot afford fee %"PRIu64 |
|||
" (%"PRIu64" and %"PRIu64")", |
|||
fee, satoshi_out[LOCAL], |
|||
satoshi_out[REMOTE]); |
|||
|
|||
tx = create_close_tx(ctx, scriptpubkey[LOCAL], scriptpubkey[REMOTE], |
|||
funding_txid, |
|||
funding_txout, |
|||
funding_satoshi, |
|||
satoshi_out[LOCAL] - (funder == LOCAL ? fee : 0), |
|||
satoshi_out[REMOTE] - (funder == REMOTE ? fee : 0), |
|||
dust_limit); |
|||
if (!tx) |
|||
status_failed(WIRE_CLOSING_NEGOTIATION_ERROR, |
|||
"Both outputs below dust limit"); |
|||
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; |
|||
} |
|||
|
|||
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 sha256_double funding_txid; |
|||
u16 funding_txout; |
|||
u64 funding_satoshi, satoshi_out[NUM_SIDES]; |
|||
u64 our_dust_limit; |
|||
u64 minfee, 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; |
|||
|
|||
if (argc == 2 && streq(argv[1], "--version")) { |
|||
printf("%s\n", version()); |
|||
exit(0); |
|||
} |
|||
|
|||
subdaemon_debug(argc, argv); |
|||
|
|||
/* We handle write returning errors! */ |
|||
signal(SIGCHLD, SIG_IGN); |
|||
secp256k1_ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY |
|||
| SECP256K1_CONTEXT_SIGN); |
|||
status_setup_sync(REQ_FD); |
|||
|
|||
msg = wire_sync_read(ctx, REQ_FD); |
|||
if (!fromwire_closing_init(ctx, msg, NULL, |
|||
&cs, &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])) { |
|||
status_failed(WIRE_CLOSING_PEER_BAD_MESSAGE, |
|||
"Bad init message %s", tal_hex(ctx, msg)); |
|||
} |
|||
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]); |
|||
|
|||
/* 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 then retransmit the last `closing_signed`. |
|||
*/ |
|||
for (;;) { |
|||
const tal_t *tmpctx = tal_tmpctx(ctx); |
|||
struct bitcoin_tx *tx; |
|||
u64 received_fee, limit_fee, new_fee; |
|||
|
|||
/* BOLT #2:
|
|||
* |
|||
* The sender MUST set `signature` to the Bitcoin signature of |
|||
* the close transaction with the node responsible for paying |
|||
* the bitcoin fee paying `fee_satoshis`, then removing any |
|||
* output which is below its own `dust_limit_satoshis`. The |
|||
* sender MAY then also eliminate its own output from the |
|||
* mutual close transaction. |
|||
*/ |
|||
tx = close_tx(tmpctx, scriptpubkey, |
|||
&funding_txid, |
|||
funding_txout, |
|||
funding_satoshi, |
|||
satoshi_out, funder, sent_fee, our_dust_limit); |
|||
if (!tx) |
|||
status_failed(WIRE_CLOSING_NEGOTIATION_ERROR, |
|||
"Both outputs below dust limit"); |
|||
|
|||
/* BOLT #2:
|
|||
* |
|||
* The sender MAY then also eliminate its own output from the |
|||
* mutual close transaction. |
|||
*/ |
|||
/* (We don't do this). */ |
|||
sign_tx_input(tx, 0, NULL, funding_wscript, |
|||
&secrets.funding_privkey, |
|||
&funding_pubkey[LOCAL], |
|||
&sig); |
|||
|
|||
/* Tell master we're making an offer, wait for db commit. */ |
|||
msg = towire_closing_offered_signature(tmpctx, sent_fee, &sig); |
|||
if (!wire_sync_write(REQ_FD, msg)) |
|||
status_failed(WIRE_CLOSING_INTERNAL_ERROR, |
|||
"Writing offer to master failed: %s", |
|||
strerror(errno)); |
|||
msg = wire_sync_read(tmpctx, REQ_FD); |
|||
if (!fromwire_closing_offered_signature_reply(msg, NULL)) |
|||
status_failed(WIRE_CLOSING_INTERNAL_ERROR, |
|||
"Reading offer reply from master failed"); |
|||
|
|||
status_trace("sending fee offer %"PRIu64, sent_fee); |
|||
|
|||
/* Now send closing offer */ |
|||
msg = towire_closing_signed(tmpctx, &channel_id, sent_fee, &sig); |
|||
if (!sync_crypto_write(&cs, PEER_FD, take(msg))) |
|||
status_failed(WIRE_CLOSING_PEER_WRITE_FAILED, |
|||
"Writing closing_signed"); |
|||
|
|||
/* Did we just agree with them? If so, we're done. */ |
|||
if (sent_fee == last_received_fee) |
|||
break; |
|||
|
|||
again: |
|||
msg = sync_crypto_read(tmpctx, &cs, PEER_FD); |
|||
if (!msg) |
|||
status_failed(WIRE_CLOSING_PEER_READ_FAILED, |
|||
"Reading input"); |
|||
|
|||
/* We don't send gossip at this stage, but we can recv it */ |
|||
if (is_gossip_msg(msg)) { |
|||
if (!wire_sync_write(GOSSIP_FD, take(msg))) |
|||
status_failed(WIRE_CLOSING_GOSSIP_FAILED, |
|||
"Writing gossip"); |
|||
goto again; |
|||
} |
|||
|
|||
/* BOLT #2:
|
|||
* |
|||
* On reconnection, a node MUST ignore a redundant |
|||
* `funding_locked` if it receives one. |
|||
*/ |
|||
/* This should only happen if we've made no commitments, but
|
|||
* we don't have to check that: it's their problem. */ |
|||
if (fromwire_peektype(msg) == WIRE_FUNDING_LOCKED) { |
|||
tal_free(msg); |
|||
goto again; |
|||
} |
|||
|
|||
if (!fromwire_closing_signed(msg, NULL, &channel_id, |
|||
&received_fee, &sig)) |
|||
status_failed(WIRE_CLOSING_PEER_BAD_MESSAGE, |
|||
"Expected closing_signed: %s", |
|||
tal_hex(trc, msg)); |
|||
|
|||
/* BOLT #2:
|
|||
* |
|||
* The receiver MUST check `signature` is valid for either the |
|||
* close transaction with the given `fee_satoshis` as detailed |
|||
* above and its own `dust_limit_satoshis` OR that same |
|||
* transaction with the sender's output eliminated, and MUST |
|||
* fail the connection if it is not. |
|||
*/ |
|||
tx = close_tx(tmpctx, scriptpubkey, |
|||
&funding_txid, |
|||
funding_txout, |
|||
funding_satoshi, |
|||
satoshi_out, funder, received_fee, our_dust_limit); |
|||
|
|||
if (!check_tx_sig(tx, 0, NULL, funding_wscript, |
|||
&funding_pubkey[REMOTE], &sig)) { |
|||
/* Trim it by reducing their output to minimum */ |
|||
struct bitcoin_tx *trimmed; |
|||
u64 trimming_satoshi_out[NUM_SIDES]; |
|||
|
|||
if (funder == REMOTE) |
|||
trimming_satoshi_out[REMOTE] = received_fee; |
|||
else |
|||
trimming_satoshi_out[REMOTE] = 0; |
|||
trimming_satoshi_out[LOCAL] = satoshi_out[LOCAL]; |
|||
|
|||
trimmed = close_tx(tmpctx, scriptpubkey, |
|||
&funding_txid, |
|||
funding_txout, |
|||
funding_satoshi, |
|||
trimming_satoshi_out, |
|||
funder, received_fee, our_dust_limit); |
|||
if (!trimmed |
|||
|| !check_tx_sig(trimmed, 0, NULL, funding_wscript, |
|||
&funding_pubkey[REMOTE], &sig)) { |
|||
status_failed(WIRE_CLOSING_PEER_BAD_MESSAGE, |
|||
"Bad closing_signed signature for" |
|||
" %s (and trimmed version %s)", |
|||
type_to_string(tmpctx, |
|||
struct bitcoin_tx, |
|||
tx), |
|||
trimmed ? |
|||
type_to_string(tmpctx, |
|||
struct bitcoin_tx, |
|||
trimmed) |
|||
: "NONE"); |
|||
} |
|||
} |
|||
|
|||
status_trace("Received fee offer %"PRIu64, received_fee); |
|||
|
|||
/* Is fee reasonable? Tell master. */ |
|||
if (received_fee < minfee) { |
|||
status_trace("Fee too low, below %"PRIu64, minfee); |
|||
limit_fee = minfee; |
|||
} else if (received_fee > maxfee) { |
|||
status_trace("Fee too high, above %"PRIu64, maxfee); |
|||
limit_fee = maxfee; |
|||
} else { |
|||
status_trace("Fee accepted."); |
|||
msg = towire_closing_received_signature(tmpctx, |
|||
received_fee, |
|||
&sig); |
|||
if (!wire_sync_write(REQ_FD, take(msg))) |
|||
status_failed(WIRE_CLOSING_INTERNAL_ERROR, |
|||
"Writing received to master: %s", |
|||
strerror(errno)); |
|||
msg = wire_sync_read(tmpctx, REQ_FD); |
|||
if (!fromwire_closing_received_signature_reply(msg,NULL)) |
|||
status_failed(WIRE_CLOSING_INTERNAL_ERROR, |
|||
"Bad received reply from master"); |
|||
limit_fee = received_fee; |
|||
} |
|||
|
|||
/* BOLT #2:
|
|||
* |
|||
* Once a node has sent or received a `closing_signed` with |
|||
* matching `fee_satoshis` it SHOULD close the connection and |
|||
* SHOULD sign and broadcast the final closing transaction. |
|||
*/ |
|||
if (received_fee == sent_fee) |
|||
break; |
|||
|
|||
/* Check that they moved in right direction. Not really
|
|||
* a requirement that we check, but good to catch their bugs. */ |
|||
if (last_received_fee != -1) { |
|||
bool previous_dir = sent_fee < last_received_fee; |
|||
bool dir = received_fee < last_received_fee; |
|||
bool next_dir = sent_fee < received_fee; |
|||
|
|||
/* They went away from our offer? */ |
|||
if (dir != previous_dir) |
|||
status_failed(WIRE_CLOSING_NEGOTIATION_ERROR, |
|||
"Their fee went %" |
|||
PRIu64" to %"PRIu64 |
|||
" when ours was %"PRIu64, |
|||
last_received_fee, |
|||
received_fee, |
|||
sent_fee); |
|||
|
|||
/* They jumped over our offer? */ |
|||
if (next_dir != previous_dir) |
|||
status_failed(WIRE_CLOSING_NEGOTIATION_ERROR, |
|||
"Their fee jumped %" |
|||
PRIu64" to %"PRIu64 |
|||
" when ours was %"PRIu64, |
|||
last_received_fee, |
|||
received_fee, |
|||
sent_fee); |
|||
} |
|||
|
|||
/* BOLT #2:
|
|||
* |
|||
* ...otherwise it SHOULD propose a value strictly between the |
|||
* received `fee_satoshis` and its previously-sent |
|||
* `fee_satoshis`. |
|||
*/ |
|||
|
|||
/* We do it by bisection, with twists:
|
|||
* 1. Don't go outside limits, or reach them immediately: |
|||
* treat out-of-limit offers as on-limit offers. |
|||
* 2. Round towards the target, otherwise we can't close |
|||
* a final 1-satoshi gap. |
|||
* |
|||
* Note: Overflow impossible here, since fee <= funder amount */ |
|||
new_fee = one_towards(limit_fee, limit_fee + sent_fee) / 2; |
|||
|
|||
/* If we didn't move, give up (we're ~ at min/max). */ |
|||
if (new_fee == sent_fee) |
|||
status_failed(WIRE_CLOSING_NEGOTIATION_ERROR, |
|||
"Final fee %"PRIu64" vs %"PRIu64 |
|||
" at limits %"PRIu64"-%"PRIu64, |
|||
sent_fee, received_fee, |
|||
minfee, maxfee); |
|||
|
|||
last_received_fee = received_fee; |
|||
sent_fee = new_fee; |
|||
tal_free(tmpctx); |
|||
} |
|||
|
|||
/* We're done! */ |
|||
wire_sync_write(REQ_FD, take(towire_closing_complete(ctx))); |
|||
tal_free(ctx); |
|||
|
|||
return 0; |
|||
} |
Can't render this file because it has a wrong number of fields in line 2.
|
Loading…
Reference in new issue