Browse Source

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 <rusty@rustcorp.com.au>
ppa-0.6.1
Rusty Russell 9 years ago
parent
commit
f7d86da1b5
  1. 143
      daemon/bitcoind.c
  2. 8
      daemon/bitcoind.h
  3. 105
      daemon/peer.c
  4. 14
      daemon/peer.h
  5. 14
      daemon/test/test.sh

143
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;

8
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);

105
daemon/peer.c

@ -14,6 +14,7 @@
#include "secrets.h"
#include "state.h"
#include "timeout.h"
#include "wallet.h"
#include <bitcoin/base58.h>
#include <bitcoin/script.h>
#include <bitcoin/tx.h>
@ -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.

14
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;

14
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.

Loading…
Cancel
Save