From f7d86da1b5a5a60b32602a47c8686e0f2c96106c Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 12 Apr 2016 13:07:04 +0930 Subject: [PATCH] daemon: have user supply UTXO for enchor input. This lets us ensure that anchor tx has witness scripts for inputs, and thus is immalleable. Signed-off-by: Rusty Russell --- daemon/bitcoind.c | 143 -------------------------------------------- daemon/bitcoind.h | 8 --- daemon/peer.c | 105 +++++++++++++++++++++----------- daemon/peer.h | 14 +++++ daemon/test/test.sh | 14 +++-- 5 files changed, 95 insertions(+), 189 deletions(-) diff --git a/daemon/bitcoind.c b/daemon/bitcoind.c index e5c710069..6f4fc7d36 100644 --- a/daemon/bitcoind.c +++ b/daemon/bitcoind.c @@ -438,149 +438,6 @@ void bitcoind_send_tx(struct lightningd_state *dstate, tal_free(raw); } -struct funding_process { - struct peer *peer; - void (*cb)(struct lightningd_state *state, - const struct bitcoin_tx *tx, - int change_output, - struct peer *peer); - int change_output; -}; - -static void process_signrawtransaction(struct bitcoin_cli *bcli) -{ - const jsmntok_t *tokens, *hex, *complete; - bool valid; - struct bitcoin_tx *tx; - struct funding_process *f = bcli->cb_arg; - - /* Output: - "{\n" - " \"hex\" : \"value\", (string) The hex-encoded raw transaction with signature(s)\n" - " \"complete\" : true|false, (boolean) If the transaction has a complete set of signatures\n" - ...*/ - if (!bcli->output) - fatal("%s signrawtransaction %s: failed", - bcli->args[0], bcli->args[3]); - - tokens = json_parse_input(bcli->output, bcli->output_bytes, &valid); - if (!tokens) - fatal("%s signrawtransaction %s: %s response (%.*s)?", - bcli->args[0], bcli->args[2], - valid ? "partial" : "invalid", - (int)bcli->output_bytes, bcli->output); - if (tokens[0].type != JSMN_OBJECT) - fatal("%s signrawtransaction %s: gave non-object (%.*s)?", - bcli->args[0], bcli->args[2], - (int)bcli->output_bytes, bcli->output); - - complete = json_get_member(bcli->output, tokens, "complete"); - if (!complete) - fatal("%s signrawtransaction %s had no complete member (%.*s)?", - bcli->args[0], bcli->args[2], - (int)bcli->output_bytes, bcli->output); - if (complete->end - complete->start != strlen("true") - || strncmp(bcli->output + complete->start, "true", strlen("true"))) - fatal("%s signrawtransaction %s not complete (%.*s)?", - bcli->args[0], bcli->args[2], - complete->end - complete->start, - bcli->output + complete->start); - - hex = json_get_member(bcli->output, tokens, "hex"); - if (!hex) - fatal("%s signrawtransaction %s had no hex member (%.*s)?", - bcli->args[0], bcli->args[2], - (int)bcli->output_bytes, bcli->output); - - tx = bitcoin_tx_from_hex(bcli, bcli->output + hex->start, - hex->end - hex->start); - if (!tx) - fatal("%s signrawtransaction %s had bad hex member (%.*s)?", - bcli->args[0], bcli->args[2], - hex->end - hex->start, bcli->output + hex->start); - - f->cb(bcli->dstate, tx, f->change_output, f->peer); -} - -/* FIXME: handle lack of funds! */ -static void process_fundrawtransaction(struct bitcoin_cli *bcli) -{ - const jsmntok_t *tokens, *hex, *changepos; - char *hexstr, *end; - bool valid; - struct funding_process *f = bcli->cb_arg; - - /* Output: - "{\n" - " \"hex\": \"value\", (string) The resulting raw transaction (hex-encoded string)\n" - " \"fee\": n, (numeric) Fee the resulting transaction pays\n" - " \"changepos\": n (numeric) The position of the added change output, or -1\n" - "}\n" - */ - if (!bcli->output) - fatal("%s fundrawtransaction %s: failed", - bcli->args[0], bcli->args[3]); - - tokens = json_parse_input(bcli->output, bcli->output_bytes, &valid); - if (!tokens) - fatal("%s fundrawtransaction %s: %s response (%.*s)?", - bcli->args[0], bcli->args[2], - valid ? "partial" : "invalid", - (int)bcli->output_bytes, bcli->output); - if (tokens[0].type != JSMN_OBJECT) - fatal("%s fundrawtransaction %s: gave non-object (%.*s)?", - bcli->args[0], bcli->args[2], - (int)bcli->output_bytes, bcli->output); - hex = json_get_member(bcli->output, tokens, "hex"); - if (!hex) - fatal("%s fundrawtransaction %s had no hex member (%.*s)?", - bcli->args[0], bcli->args[2], - (int)bcli->output_bytes, bcli->output); - - changepos = json_get_member(bcli->output, tokens, "changepos"); - if (!changepos) - fatal("%s fundrawtransaction %s had no changepos member (%.*s)?", - bcli->args[0], bcli->args[2], - (int)bcli->output_bytes, bcli->output); - f->change_output = strtol(bcli->output + changepos->start, &end, 0); - if (end != bcli->output + changepos->end) - fatal("%s fundrawtransaction %s had bad changepos (%.*s)?", - bcli->args[0], bcli->args[2], - changepos->end - changepos->start, - bcli->output + changepos->start); - - /* We need a nul-terminated string. */ - hexstr = tal_strndup(bcli, bcli->output + hex->start, - hex->end - hex->start); - /* Now we need to sign those inputs! */ - start_bitcoin_cli(bcli->dstate, process_signrawtransaction, NULL, - f, "signrawtransaction", hexstr, NULL); -} - -/* Adds and signs inputs to this tx from wallet. */ -void bitcoind_fund_transaction(struct lightningd_state *dstate, - struct bitcoin_tx *tx_no_inputs, - void (*cb)(struct lightningd_state *dstate, - const struct bitcoin_tx *tx, - int change_output, - struct peer *peer), - struct peer *peer) -{ - struct funding_process *f = tal(peer, struct funding_process); - u8 *raw = linearize_tx_force_extended(dstate, tx_no_inputs); - char *hex = tal_arr(raw, char, hex_str_size(tal_count(raw))); - - assert(tx_no_inputs->input_count == 0); - - hex_encode(raw, tal_count(raw), hex, tal_count(hex)); - - f->peer = peer; - f->cb = cb; - start_bitcoin_cli(dstate, process_fundrawtransaction, NULL, f, - "fundrawtransaction", hex, NULL); - tal_free(raw); -} - static void process_getblock(struct bitcoin_cli *bcli) { const jsmntok_t *tokens, *mediantime; diff --git a/daemon/bitcoind.h b/daemon/bitcoind.h index 1dff3ea7c..db1ed99cb 100644 --- a/daemon/bitcoind.h +++ b/daemon/bitcoind.h @@ -53,14 +53,6 @@ void bitcoind_estimate_fee_(struct lightningd_state *dstate, void bitcoind_send_tx(struct lightningd_state *dstate, const struct bitcoin_tx *tx); -void bitcoind_fund_transaction(struct lightningd_state *dstate, - struct bitcoin_tx *tx_no_inputs, - void (*cb)(struct lightningd_state *dstate, - const struct bitcoin_tx *tx, - int change_output, - struct peer *peer), - struct peer *peer); - void bitcoind_get_mediantime(struct lightningd_state *dstate, const struct sha256_double *blockid, u32 *mediantime); diff --git a/daemon/peer.c b/daemon/peer.c index 4c5209f65..abe0f59a1 100644 --- a/daemon/peer.c +++ b/daemon/peer.c @@ -14,6 +14,7 @@ #include "secrets.h" #include "state.h" #include "timeout.h" +#include "wallet.h" #include #include #include @@ -39,7 +40,7 @@ struct json_connecting { /* This owns us, so we're freed after command_fail or command_success */ struct command *cmd; const char *name, *port; - u64 satoshis; + struct anchor_input *input; }; struct pending_cmd { @@ -450,7 +451,8 @@ static struct io_plan *peer_connected_out(struct io_conn *conn, } log_info(peer->log, "Connected out to %s:%s", connect->name, connect->port); - peer->anchor.satoshis = connect->satoshis; + + peer->anchor.input = tal_steal(peer, connect->input); command_success(connect->cmd, null_response(connect)); return peer_crypto_setup(conn, peer, peer_crypto_on); @@ -565,14 +567,17 @@ static void json_connect(struct command *cmd, const char *buffer, const jsmntok_t *params) { struct json_connecting *connect; - jsmntok_t *host, *port, *satoshis; + jsmntok_t *host, *port, *txtok; + struct bitcoin_tx *tx; + int output; + size_t txhexlen; if (!json_get_params(buffer, params, "host", &host, "port", &port, - "satoshis", &satoshis, + "tx", &txtok, NULL)) { - command_fail(cmd, "Need host, port and satoshis"); + command_fail(cmd, "Need host, port and tx to a wallet address"); return; } @@ -582,10 +587,35 @@ static void json_connect(struct command *cmd, host->end - host->start); connect->port = tal_strndup(connect, buffer + port->start, port->end - port->start); - if (!json_tok_u64(buffer, satoshis, &connect->satoshis)) - command_fail(cmd, "'%.*s' is not a valid number", - (int)(satoshis->end - satoshis->start), - buffer + satoshis->start); + connect->input = tal(connect, struct anchor_input); + + txhexlen = txtok->end - txtok->start; + tx = bitcoin_tx_from_hex(connect->input, buffer + txtok->start, + txhexlen); + if (!tx) { + command_fail(cmd, "'%.*s' is not a valid transaction", + txtok->end - txtok->start, + buffer + txtok->start); + return; + } + + bitcoin_txid(tx, &connect->input->txid); + + /* Find an output we know how to spend. */ + connect->input->w = NULL; + for (output = 0; output < tx->output_count; output++) { + connect->input->w + = wallet_can_spend(cmd->dstate, &tx->output[output]); + if (connect->input->w) + break; + } + if (!connect->input->w) { + command_fail(cmd, "Tx doesn't send to wallet address"); + return; + } + + connect->input->index = output; + connect->input->amount = tx->output[output].amount; if (!dns_resolve_and_connect(cmd->dstate, connect->name, connect->port, peer_connected_out, peer_failed, connect)) { command_fail(cmd, "DNS failed"); @@ -1219,45 +1249,52 @@ const struct bitcoin_tx *bitcoin_htlc_spend(const struct peer *peer, FIXME_STUB(peer); } -static void created_anchor(struct lightningd_state *dstate, - const struct bitcoin_tx *tx, - int change_output, - struct peer *peer) +/* Now we can create anchor tx. */ +static void got_feerate(struct lightningd_state *dstate, + u64 rate, struct peer *peer) { - size_t real_out; + u64 fee; + struct bitcoin_tx *tx = bitcoin_tx(peer, 1, 1); - bitcoin_txid(tx, &peer->anchor.txid); - if (change_output == -1) - real_out = 0; - else - real_out = !change_output; + tx->output[0].script = scriptpubkey_p2sh(tx, peer->anchor.redeemscript); + tx->output[0].script_length = tal_count(tx->output[0].script); + + /* Add input script length. FIXME: This is normal case, not exact. */ + fee = fee_by_feerate(measure_tx_len(tx) + 1+73 + 1+33 + 1, rate); + if (fee >= peer->anchor.input->amount) + /* FIXME: Report an error here! + * We really should set this when they do command, but + * we need to modify state to allow immediate anchor + * creation: using estimate_fee is a convenient workaround. */ + fatal("Amount %"PRIu64" below fee %"PRIu64, + peer->anchor.input->amount, fee); + + tx->output[0].amount = peer->anchor.input->amount - fee; + + tx->input[0].txid = peer->anchor.input->txid; + tx->input[0].index = peer->anchor.input->index; + tx->input[0].amount = tal_dup(tx->input, u64, + &peer->anchor.input->amount); + + wallet_add_signed_input(peer->dstate, peer->anchor.input->w, tx, 0); - assert(find_p2sh_out(tx, peer->anchor.redeemscript) == real_out); - peer->anchor.index = real_out; - assert(peer->anchor.satoshis == tx->output[peer->anchor.index].amount); + bitcoin_txid(tx, &peer->anchor.txid); + peer->anchor.tx = tx; + peer->anchor.index = 0; /* We'll need this later, when we're told to broadcast it. */ - peer->anchor.tx = tal_steal(peer, tx); + peer->anchor.satoshis = tx->output[0].amount; state_event(peer, BITCOIN_ANCHOR_CREATED, NULL); } -/* Start creation of the bitcoin anchor tx. */ +/* Creation the bitcoin anchor tx, spending output user provided. */ void bitcoin_create_anchor(struct peer *peer, enum state_input done) { - struct bitcoin_tx *template = bitcoin_tx(peer, 0, 1); - /* We must be offering anchor for us to try creating it */ assert(peer->us.offer_anchor); - template->output[0].amount = peer->anchor.satoshis; - template->output[0].script - = scriptpubkey_p2sh(template, peer->anchor.redeemscript); - template->output[0].script_length - = tal_count(template->output[0].script); - assert(done == BITCOIN_ANCHOR_CREATED); - - bitcoind_fund_transaction(peer->dstate, template, created_anchor, peer); + bitcoind_estimate_fee(peer->dstate, got_feerate, peer); } /* We didn't end up broadcasting the anchor: release the utxos. diff --git a/daemon/peer.h b/daemon/peer.h index ba1d64407..315562f11 100644 --- a/daemon/peer.h +++ b/daemon/peer.h @@ -2,6 +2,7 @@ #define LIGHTNING_DAEMON_PEER_H #include "config.h" #include "bitcoin/locktime.h" +#include "bitcoin/privkey.h" #include "bitcoin/pubkey.h" #include "bitcoin/script.h" #include "bitcoin/shadouble.h" @@ -42,6 +43,15 @@ union htlc_staging { struct htlc_fail fail; }; +struct anchor_input { + struct sha256_double txid; + unsigned int index; + /* Amount of input (satoshis) */ + u64 amount; + /* Wallet entry to use to spend. */ + struct wallet *w; +}; + struct commit_info { /* Previous one, if any. */ struct commit_info *prev; @@ -131,6 +141,10 @@ struct peer { unsigned int index; u64 satoshis; u8 *redeemscript; + + /* If we're creating anchor, this tells us where to source it */ + struct anchor_input *input; + /* If we created it, we keep entire tx. */ const struct bitcoin_tx *tx; struct anchor_watch *watches; diff --git a/daemon/test/test.sh b/daemon/test/test.sh index 24094d58d..50cfb62e0 100755 --- a/daemon/test/test.sh +++ b/daemon/test/test.sh @@ -16,8 +16,9 @@ REDIRERR1="$DIR1/errors" REDIRERR2="$DIR2/errors" FGREP="fgrep -q" -# setup.sh gives us 0.00999999 bitcoin, = 999999 satoshi = 999999000 millisatoshi -AMOUNT=999999000 +# We inject 0.01 bitcoin, but then fees (estimatefee fails and we use a +# fee rate as per the close tx). +AMOUNT=996160000 # Default fee rate per kb. FEE_RATE=200000 @@ -129,7 +130,7 @@ check_staged() check_tx_spend() { $CLI generate 1 - if [ $($CLI getblock $($CLI getbestblockhash) | grep -c '^ "') = 2 ]; then + if [ $($CLI getblock $($CLI getbestblockhash) | grep -c '^ "') -gt 1 ]; then : else echo "Block didn't include tx:" >&2 @@ -189,7 +190,12 @@ ID2=`$LCLI2 getlog | sed -n 's/.*"ID: \([0-9a-f]*\)".*/\1/p'` PORT2=`$LCLI2 getlog | sed -n 's/.*on port \([0-9]*\).*/\1/p'` -lcli1 connect localhost $PORT2 999999 +# Make a payment into a P2SH for anchor. +P2SHADDR=`$LCLI1 newaddr | sed -n 's/{ "address" : "\(.*\)" }/\1/p'` +TXID=`$CLI sendtoaddress $P2SHADDR 0.01` +TX=`$CLI getrawtransaction $TXID` + +lcli1 connect localhost $PORT2 $TX sleep 2 # Expect them to be waiting for anchor.