Files
lightning/lightningd/pay.c
Rusty Russell 3f65f0f000 lightningd: fix up malformed onion handling.
In the case where we can't decrypt the onion, we can't fail it in the
normal way (which is encrypted using the onion shared secret), we need
to respond with a update_fail_malformed_htlc message.

Moreover, we need to remember this for persistence.  This means that
we really have three conclusions for an HTLC: fulfilled, failed,
malformed.  Fix up the logic everywhere which assumed failed or
fulfilled.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2017-06-23 09:29:42 +09:30

381 lines
10 KiB
C

#include "pay.h"
#include <bitcoin/preimage.h>
#include <ccan/str/hex/hex.h>
#include <ccan/structeq/structeq.h>
#include <daemon/chaintopology.h>
#include <daemon/jsonrpc.h>
#include <daemon/log.h>
#include <inttypes.h>
#include <lightningd/channel/gen_channel_wire.h>
#include <lightningd/lightningd.h>
#include <lightningd/peer_control.h>
#include <lightningd/sphinx.h>
#include <lightningd/subd.h>
#include <sodium/randombytes.h>
/* Outstanding "pay" commands. */
struct pay_command {
struct list_node list;
struct sha256 rhash;
u64 msatoshi;
const struct pubkey *ids;
/* Set if this is in progress. */
struct htlc_out *out;
/* Preimage if this succeeded. */
const struct preimage *rval;
struct command *cmd;
/* Remember all shared secrets, so we can unwrap an eventual failure */
struct secret *path_secrets;
};
static void json_pay_success(struct command *cmd, const struct preimage *rval)
{
struct json_result *response;
/* Can be NULL if JSON RPC goes away. */
if (!cmd)
return;
response = new_json_result(cmd);
json_object_start(response, NULL);
json_add_hex(response, "preimage", rval, sizeof(*rval));
json_object_end(response);
command_success(cmd, response);
}
static void json_pay_failed(struct pay_command *pc,
const struct pubkey *sender,
enum onion_type failure_code,
const char *details)
{
/* Can be NULL if JSON RPC goes away. */
if (!pc->cmd)
return;
/* FIXME: Report sender! */
command_fail(pc->cmd, "failed: %s (%s)",
onion_type_name(failure_code), details);
pc->out = NULL;
}
void payment_succeeded(struct lightningd *ld, struct htlc_out *hout,
const struct preimage *rval)
{
assert(!hout->pay_command->rval);
hout->pay_command->rval = tal_dup(hout->pay_command,
struct preimage, rval);
json_pay_success(hout->pay_command->cmd, rval);
hout->pay_command->out = NULL;
}
void payment_failed(struct lightningd *ld, const struct htlc_out *hout)
{
struct pay_command *pc = hout->pay_command;
enum onion_type failcode;
struct onionreply *reply;
if (hout->malformed)
failcode = hout->malformed;
else {
reply = unwrap_onionreply(pc, pc->path_secrets,
tal_count(pc->path_secrets),
hout->failuremsg);
if (!reply) {
log_info(hout->key.peer->log,
"htlc %"PRIu64" failed with bad reply (%s)",
hout->key.id,
tal_hex(pc, hout->failuremsg));
failcode = WIRE_PERMANENT_NODE_FAILURE;
} else {
failcode = fromwire_peektype(reply->msg);
log_info(hout->key.peer->log,
"htlc %"PRIu64" failed from %ith node with code 0x%04x (%s)",
hout->key.id,
reply->origin_index,
failcode, onion_type_name(failcode));
}
}
/* FIXME: save ids we can turn reply->origin_index into sender. */
/* FIXME: check for routing failure / perm fail. */
/* check_for_routing_failure(i, sender, failure_code); */
json_pay_failed(pc, NULL, failcode, "reply from remote");
}
static bool rcvd_htlc_reply(struct subd *subd, const u8 *msg, const int *fds,
struct pay_command *pc)
{
u16 failcode;
u8 *failstr;
if (!fromwire_channel_offer_htlc_reply(msg, msg, NULL,
&pc->out->key.id,
&failcode, &failstr)) {
json_pay_failed(pc, &subd->ld->dstate.id, -1,
"daemon bad response");
return false;
}
if (failcode != 0) {
/* Make sure we have a nul-terminated string. */
char *str = (char *)tal_dup_arr(msg, u8,
failstr, tal_len(failstr), 1);
str[tal_len(failstr)] = 0;
json_pay_failed(pc, &subd->ld->dstate.id, failcode, str);
return true;
}
/* HTLC endpoint now owned by lightningd. */
tal_steal(subd->ld, pc->out);
connect_htlc_out(&subd->ld->htlcs_out, pc->out);
return true;
}
/* When JSON RPC goes away, cmd is freed: detach from any running paycommand */
static void remove_cmd_from_pc(struct command *cmd, struct pay_command *pc)
{
/* This can be false, in the case where another pay command
* re-uses the pc->cmd before we get around to cleaning up. */
if (pc->cmd == cmd)
pc->cmd = NULL;
}
static struct pay_command *find_pay_command(struct lightningd *ld,
const struct sha256 *rhash)
{
struct pay_command *pc;
list_for_each(&ld->dstate.pay_commands, pc, list) {
if (structeq(rhash, &pc->rhash))
return pc;
}
return NULL;
}
static void pay_command_destroyed(struct pay_command *pc)
{
list_del(&pc->list);
}
static void json_sendpay(struct command *cmd,
const char *buffer, const jsmntok_t *params)
{
struct lightningd *ld = ld_from_dstate(cmd->dstate);
struct pubkey *ids;
jsmntok_t *routetok, *rhashtok;
const jsmntok_t *t, *end;
unsigned int delay, base_expiry;
size_t n_hops;
struct sha256 rhash;
struct peer *peer;
struct pay_command *pc;
const u8 *onion;
u8 sessionkey[32];
struct hop_data *hop_data;
struct hop_data first_hop_data;
u64 amount, lastamount;
struct onionpacket *packet;
u8 *msg;
struct secret *path_secrets;
if (!json_get_params(buffer, params,
"route", &routetok,
"rhash", &rhashtok,
NULL)) {
command_fail(cmd, "Need route and rhash");
return;
}
if (!hex_decode(buffer + rhashtok->start,
rhashtok->end - rhashtok->start,
&rhash, sizeof(rhash))) {
command_fail(cmd, "'%.*s' is not a valid sha256 hash",
(int)(rhashtok->end - rhashtok->start),
buffer + rhashtok->start);
return;
}
if (routetok->type != JSMN_ARRAY) {
command_fail(cmd, "'%.*s' is not an array",
(int)(routetok->end - routetok->start),
buffer + routetok->start);
return;
}
/* Expiry for HTLCs is absolute. And add one to give some margin. */
base_expiry = get_block_height(cmd->dstate->topology) + 1;
end = json_next(routetok);
n_hops = 0;
ids = tal_arr(cmd, struct pubkey, n_hops);
hop_data = tal_arr(cmd, struct hop_data, n_hops);
for (t = routetok + 1; t < end; t = json_next(t)) {
const jsmntok_t *amttok, *idtok, *delaytok, *chantok;
if (t->type != JSMN_OBJECT) {
command_fail(cmd, "route %zu '%.*s' is not an object",
n_hops,
(int)(t->end - t->start),
buffer + t->start);
return;
}
amttok = json_get_member(buffer, t, "msatoshi");
idtok = json_get_member(buffer, t, "id");
delaytok = json_get_member(buffer, t, "delay");
chantok = json_get_member(buffer, t, "channel");
if (!amttok || !idtok || !delaytok || !chantok) {
command_fail(cmd, "route %zu needs msatoshi/id/channel/delay",
n_hops);
return;
}
tal_resize(&hop_data, n_hops + 1);
tal_resize(&ids, n_hops+1);
hop_data[n_hops].realm = 0;
/* What that hop will forward */
if (!json_tok_u64(buffer, amttok, &amount)) {
command_fail(cmd, "route %zu invalid msatoshi",
n_hops);
return;
}
hop_data[n_hops].amt_forward = amount;
if (!short_channel_id_from_str(buffer + chantok->start,
chantok->end - chantok->start,
&hop_data[n_hops].channel_id)) {
command_fail(cmd, "route %zu invalid id", n_hops);
return;
}
if (!pubkey_from_hexstr(buffer + idtok->start,
idtok->end - idtok->start,
&ids[n_hops])) {
command_fail(cmd, "route %zu invalid id", n_hops);
return;
}
if (!json_tok_number(buffer, delaytok, &delay)) {
command_fail(cmd, "route %zu invalid delay", n_hops);
return;
}
hop_data[n_hops].outgoing_cltv = base_expiry + delay;
n_hops++;
}
if (n_hops == 0) {
command_fail(cmd, "Empty route");
return;
}
/* Store some info we'll need for our own HTLC */
amount = hop_data[0].amt_forward;
lastamount = hop_data[n_hops-1].amt_forward;
first_hop_data = hop_data[0];
/* Shift the hop_data down by one, so each hop gets its
* instructions, not how we got there */
for (size_t i=0; i < n_hops - 1; i++) {
hop_data[i] = hop_data[i+1];
}
/* And finally set the final hop to the special values in
* BOLT04 */
hop_data[n_hops-1].outgoing_cltv = base_expiry + delay;
memset(&hop_data[n_hops-1].channel_id, 0, sizeof(struct short_channel_id));
pc = find_pay_command(ld, &rhash);
if (pc) {
log_debug(ld->log, "json_sendpay: found previous");
if (pc->out) {
log_add(ld->log, "... still in progress");
command_fail(cmd, "still in progress");
return;
}
if (pc->rval) {
size_t old_nhops = tal_count(pc->ids);
log_add(ld->log, "... succeeded");
/* Must match successful payment parameters. */
if (pc->msatoshi != lastamount) {
command_fail(cmd,
"already succeeded with amount %"
PRIu64, pc->msatoshi);
return;
}
if (!structeq(&pc->ids[old_nhops-1], &ids[n_hops-1])) {
char *previd;
previd = pubkey_to_hexstr(cmd,
&pc->ids[old_nhops-1]);
command_fail(cmd,
"already succeeded to %s",
previd);
return;
}
json_pay_success(cmd, pc->rval);
return;
}
/* FIXME: We can free failed ones... */
log_add(ld->log, "... retrying");
}
peer = peer_by_id(ld, &ids[0]);
if (!peer) {
command_fail(cmd, "no connection to first peer found");
return;
}
if (!peer->scid) {
command_fail(cmd, "first peer channel not locked");
return;
}
if (!peer->owner || !streq(peer->owner->name, "lightningd_channel")) {
command_fail(cmd, "first peer in %s",
peer->owner ? peer->owner->name : "limbo");
return;
}
randombytes_buf(&sessionkey, sizeof(sessionkey));
/* Onion will carry us from first peer onwards. */
packet = create_onionpacket(cmd, ids, hop_data, sessionkey, rhash.u.u8,
sizeof(struct sha256), &path_secrets);
onion = serialize_onionpacket(cmd, packet);
if (pc)
pc->ids = tal_free(pc->ids);
else {
pc = tal(ld, struct pay_command);
list_add_tail(&cmd->dstate->pay_commands, &pc->list);
tal_add_destructor(pc, pay_command_destroyed);
}
pc->cmd = cmd;
pc->rhash = rhash;
pc->rval = NULL;
pc->ids = tal_steal(pc, ids);
pc->msatoshi = lastamount;
pc->path_secrets = tal_steal(pc, path_secrets);
pc->out = new_htlc_out(pc, peer, amount, first_hop_data.outgoing_cltv,
&rhash, onion, NULL, pc);
log_info(ld->log, "Sending %"PRIu64" over %zu hops to deliver %"PRIu64,
amount, n_hops, lastamount);
msg = towire_channel_offer_htlc(pc, amount,
first_hop_data.outgoing_cltv,
&pc->rhash, onion);
subd_req(pc, peer->owner, take(msg), -1, 0, rcvd_htlc_reply, pc);
/* Wait until we get response. */
tal_add_destructor2(cmd, remove_cmd_from_pc, pc);
}
static const struct json_command sendpay_command = {
"sendpay",
json_sendpay,
"Send along {route} in return for preimage of {rhash}",
"Returns the {preimage} on success"
};
AUTODATA(json_command, &sendpay_command);