diff --git a/wallet/Makefile b/wallet/Makefile index a8bfb33b2..6e936149e 100644 --- a/wallet/Makefile +++ b/wallet/Makefile @@ -6,9 +6,10 @@ wallet-wrongdir: check: wallet/tests -WALLET_LIB_SRC := \ - wallet/db.c \ - wallet/wallet.c +WALLET_LIB_SRC := \ + wallet/db.c \ + wallet/wallet.c \ + wallet/walletrpc.c WALLET_LIB_OBJS := $(WALLET_LIB_SRC:.c=.o) WALLET_LIB_HEADERS := $(WALLET_LIB_SRC:.c=.h) diff --git a/wallet/walletrpc.c b/wallet/walletrpc.c new file mode 100644 index 000000000..f79c8ffab --- /dev/null +++ b/wallet/walletrpc.c @@ -0,0 +1,189 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct withdrawal { + u64 amount, changesatoshi; + struct bitcoin_address destination; + const struct utxo **utxos; + u64 change_key_index; + struct command *cmd; + const char *hextx; +}; + +/** + * wallet_withdrawal_broadcast - The tx has been broadcast (or it failed) + * + * This is the final step in the withdrawal. We either successfully + * broadcast the withdrawal transaction or it failed somehow. So we + * report success or a broadcast failure. Upon success we also mark + * the used outputs as spent, and add the change output to our pool of + * available outputs. + */ +static void wallet_withdrawal_broadcast(struct bitcoind *bitcoind, + int exitstatus, const char *msg, + struct withdrawal *withdraw) +{ + struct command *cmd = withdraw->cmd; + struct lightningd *ld = ld_from_dstate(withdraw->cmd->dstate); + + /* Massage output into shape so it doesn't kill the JSON serialization */ + char *output = tal_strjoin(cmd, tal_strsplit(cmd, msg, "\n", STR_NO_EMPTY), " ", STR_NO_TRAIL); + if (exitstatus == 0) { + wallet_confirm_utxos(ld->wallet, withdraw->utxos); + /* TODO(cdecker) Add the change output to the database */ + struct json_result *response = new_json_result(cmd); + json_object_start(response, NULL); + json_add_string(response, "tx", withdraw->hextx); + json_add_string(response, "txid", output); + json_object_end(response); + command_success(cmd, response); + } else { + command_fail(cmd, "Error broadcasting transaction: %s", output); + } +} + +/** + * wallet_withdrawal_signed - The HSM has signed our withdrawal request + * + * This is the second step (2/3) of the withdrawal flow. The HSM has + * returned the necessary signatures for the withdrawal transaction, + * so now we can assemble the transaction and kick off the broadcast. + */ +static bool wallet_withdrawal_signed(struct subd *hsm, const u8 *reply, + const int *fds, + struct withdrawal *withdraw) +{ + struct command *cmd = withdraw->cmd; + struct lightningd *ld = ld_from_dstate(cmd->dstate); + struct ext_key ext; + struct pubkey changekey; + secp256k1_ecdsa_signature *sigs; + struct bitcoin_tx *tx; + + if (!fromwire_hsmctl_sign_withdrawal_reply(withdraw, reply, NULL, &sigs)) + fatal("HSM gave bad sign_withdrawal_reply %s", + tal_hex(withdraw, reply)); + + if (bip32_key_from_parent(ld->bip32_base, withdraw->change_key_index, + BIP32_FLAG_KEY_PUBLIC, &ext) != WALLY_OK) { + command_fail(cmd, "Changekey generation failure"); + return true; + } + + pubkey_from_der(ext.pub_key, sizeof(ext.pub_key), &changekey); + tx = withdraw_tx(withdraw, withdraw->utxos, &withdraw->destination, + withdraw->amount, &changekey, withdraw->changesatoshi, + ld->bip32_base); + + if (tal_count(sigs) != tal_count(tx->input)) + fatal("HSM gave %zu sigs, needed %zu", + tal_count(sigs), tal_count(tx->input)); + + /* Create input parts from signatures. */ + for (size_t i = 0; i < tal_count(tx->input); i++) { + struct pubkey key; + + if (!bip32_pubkey(hsm->ld->bip32_base, + &key, withdraw->utxos[i]->keyindex)) + fatal("Cannot generate BIP32 key for UTXO %u", + withdraw->utxos[i]->keyindex); + + /* P2SH inputs have same witness. */ + tx->input[i].witness + = bitcoin_witness_p2wpkh(tx, &sigs[i], &key); + } + + /* Now broadcast the transaction */ + withdraw->hextx = tal_hex(withdraw, linearize_tx(cmd, tx)); + bitcoind_sendrawtx(ld->topology->bitcoind, withdraw->hextx, + wallet_withdrawal_broadcast, withdraw); + return true; +} + +/** + * json_withdraw - Entrypoint for the withdrawal flow + * + * A user has requested a withdrawal over the JSON-RPC, parse the + * request, select coins and a change key. Then send the request to + * the HSM to generate the signatures. + */ +static void json_withdraw(struct command *cmd, + const char *buffer, const jsmntok_t *params) +{ + struct lightningd *ld = ld_from_dstate(cmd->dstate); + jsmntok_t *desttok, *sattok; + struct withdrawal *withdraw; + bool testnet; + /* FIXME: Read feerate and dustlimit */ + u32 feerate_per_kw = 15000; + //u64 dust_limit = 600; + u64 fee_estimate; + struct utxo *utxos; + if (!json_get_params(buffer, params, + "destination", &desttok, + "satoshi", &sattok, + NULL)) { + command_fail(cmd, "Need destination and satoshi."); + return; + } + + withdraw = tal(cmd, struct withdrawal); + withdraw->cmd = cmd; + + if (!json_tok_u64(buffer, sattok, &withdraw->amount)) { + command_fail(cmd, "Invalid satoshis"); + return; + } + if (!bitcoin_from_base58(&testnet, &withdraw->destination, + buffer + desttok->start, + desttok->end - desttok->start)) { + command_fail(cmd, "Could not parse destination address"); + return; + } + + /* Select the coins */ + withdraw->utxos = wallet_select_coins(cmd, ld->wallet, withdraw->amount, + feerate_per_kw, &fee_estimate, + &withdraw->changesatoshi); + if (!withdraw->utxos) { + command_fail(cmd, "Not enough funds available"); + return; + } + + withdraw->change_key_index = + db_get_intvar(ld->wallet->db, "bip32_max_index", 0) + 1; + db_set_intvar(ld->wallet->db, "bip32_max_index", + withdraw->change_key_index); + + utxos = from_utxoptr_arr(withdraw, withdraw->utxos); + u8 *msg = towire_hsmctl_sign_withdrawal(cmd, + withdraw->amount, + withdraw->changesatoshi, + withdraw->change_key_index, + withdraw->destination.addr.u.u8, + utxos); + subd_req(cmd, ld->hsm, take(msg), -1, 0, wallet_withdrawal_signed, + withdraw); + tal_free(utxos); +} + +static const struct json_command withdraw_command = { + "withdraw", + json_withdraw, + "Send {satoshi} to the {destination} address via Bitcoin transaction", + "Returns the withdrawal transaction ID" +}; +AUTODATA(json_command, &withdraw_command); diff --git a/wallet/walletrpc.h b/wallet/walletrpc.h new file mode 100644 index 000000000..fbcd6605e --- /dev/null +++ b/wallet/walletrpc.h @@ -0,0 +1,11 @@ +#ifndef WALLET_WALLETRPC_H +#define WALLET_WALLETRPC_H + +#include "config.h" + +/** + * Placeholder file, we aren't really exposing anything directly. RPC + * handlers are registered using AUTODATA. + */ + +#endif /* WALLET_WALLETRPC_H */