-
Notifications
You must be signed in to change notification settings - Fork 936
/
Copy pathestablish_onion_path.c
242 lines (209 loc) · 6.7 KB
/
establish_onion_path.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
#include "config.h"
#include <ccan/tal/str/str.h>
#include <common/dijkstra.h>
#include <common/gossmap.h>
#include <common/gossmods_listpeerchannels.h>
#include <common/json_stream.h>
#include <common/route.h>
#include <plugins/establish_onion_path.h>
struct connect_info {
struct pubkey local_id, dst;
bool connect_disable;
struct gossmap *gossmap;
struct command_result *(*cb)(struct command *,
const struct pubkey *,
void *arg);
struct command_result *(*fail)(struct command *, const char *,
void *arg);
void *arg;
};
static struct command_result *connect_ok(struct command *cmd,
const char *method,
const char *buf,
const jsmntok_t *result,
struct connect_info *ci)
{
struct pubkey *path = tal_arr(tmpctx, struct pubkey, 2);
/* Create direct mini-path */
path[0] = ci->local_id;
path[1] = ci->dst;
return ci->cb(cmd, path, ci->arg);
}
static struct command_result *command_failed(struct command *cmd,
const char *method,
const char *buf,
const jsmntok_t *result,
struct connect_info *ci)
{
return ci->fail(cmd, json_strdup(tmpctx, buf, result), ci->arg);
}
static struct command_result *connect_direct(struct command *cmd,
struct connect_info *ci)
{
struct out_req *req;
if (ci->connect_disable) {
return ci->fail(cmd, "fetchinvoice-noconnect set: not initiating a new connection",
ci->arg);
}
plugin_log(cmd->plugin, LOG_DBG, "connecting directly to %s",
fmt_pubkey(tmpctx, &ci->dst));
req = jsonrpc_request_start(cmd,
"connect", connect_ok, command_failed, ci);
json_add_pubkey(req->js, "id", &ci->dst);
return send_outreq(req);
}
static bool can_carry_onionmsg(const struct gossmap *map,
const struct gossmap_chan *c,
int dir,
struct amount_msat amount UNUSED,
void *arg UNUSED)
{
const struct gossmap_node *n;
/* Our local additions are always fine, since we checked features then */
if (gossmap_chan_is_localmod(map, c))
return true;
/* Check features of recipient */
n = gossmap_nth_node(map, c, !dir);
return gossmap_node_get_feature(map, n, OPT_ONION_MESSAGES) != -1;
}
/* We add fake channels to gossmap to represent current outgoing connections.
* This allows dijkstra to find transient connections as well. */
static struct gossmap_localmods *
gossmods_from_listpeers(const tal_t *ctx,
struct command *cmd,
const struct node_id *self,
const char *buf,
const jsmntok_t *toks)
{
struct gossmap_localmods *mods = gossmap_localmods_new(ctx);
const jsmntok_t *peers, *peer;
size_t i;
peers = json_get_member(buf, toks, "peers");
json_for_each_arr(i, peer, peers) {
bool connected;
struct node_id peer_id;
const char *err;
u8 *features = NULL;
struct short_channel_id_dir fake_scidd;
bool enabled = true;
err = json_scan(tmpctx, buf, peer,
"{connected:%,"
"id:%,"
"features?:%}",
JSON_SCAN(json_to_bool, &connected),
JSON_SCAN(json_to_node_id, &peer_id),
JSON_SCAN_TAL(tmpctx, json_tok_bin_from_hex, &features));
if (err) {
plugin_err(cmd->plugin, "Bad listpeers.peers %zu: %s", i, err);
}
if (!connected || !feature_offered(features, OPT_ONION_MESSAGES))
continue;
/* Add a fake channel */
fake_scidd.scid.u64 = i;
fake_scidd.dir = node_id_idx(self, &peer_id);
gossmap_local_addchan(mods, self, &peer_id, fake_scidd.scid,
AMOUNT_MSAT(1000), NULL);
gossmap_local_updatechan(mods, &fake_scidd, &enabled,
NULL, NULL, NULL, NULL, NULL);
}
return mods;
}
static const struct pubkey *path_to_node(const tal_t *ctx,
struct command *cmd,
struct gossmap *gossmap,
struct plugin *plugin,
const char *buf,
const jsmntok_t *listpeers,
const struct pubkey *local_id,
const struct pubkey *dst_key)
{
struct route_hop *r;
const struct dijkstra *dij;
const struct gossmap_node *src;
const struct gossmap_node *dst;
struct pubkey *nodes;
struct gossmap_localmods *mods;
struct node_id local_nodeid, dst_nodeid;
node_id_from_pubkey(&local_nodeid, local_id);
node_id_from_pubkey(&dst_nodeid, dst_key);
mods = gossmods_from_listpeers(tmpctx, cmd, &local_nodeid, buf, listpeers);
gossmap_apply_localmods(gossmap, mods);
dst = gossmap_find_node(gossmap, &dst_nodeid);
if (!dst)
goto fail;
/* If we don't exist in gossip, routing can't happen. */
src = gossmap_find_node(gossmap, &local_nodeid);
if (!src)
goto fail;
dij = dijkstra(tmpctx, gossmap, dst, AMOUNT_MSAT(0), 0,
can_carry_onionmsg, route_score_shorter, NULL);
r = route_from_dijkstra(tmpctx, gossmap, dij, src, AMOUNT_MSAT(0), 0);
if (!r)
goto fail;
nodes = tal_arr(ctx, struct pubkey, tal_count(r) + 1);
nodes[0] = *local_id;
plugin_log(plugin, LOG_DBG, "Found path to %s: %s(us)",
fmt_node_id(tmpctx, &dst_nodeid),
fmt_pubkey(tmpctx, local_id));
for (size_t i = 0; i < tal_count(r); i++) {
if (!pubkey_from_node_id(&nodes[i+1], &r[i].node_id)) {
plugin_err(plugin, "Could not convert nodeid %s",
fmt_node_id(tmpctx, &r[i].node_id));
}
plugin_log(plugin, LOG_DBG, "-> %s",
fmt_node_id(tmpctx, &r[i].node_id));
}
gossmap_remove_localmods(gossmap, mods);
return nodes;
fail:
gossmap_remove_localmods(gossmap, mods);
return NULL;
}
static struct command_result *listpeers_done(struct command *cmd,
const char *method,
const char *buf,
const jsmntok_t *result,
struct connect_info *ci)
{
const struct pubkey *path;
path = path_to_node(tmpctx, cmd,
ci->gossmap, cmd->plugin, buf, result,
&ci->local_id, &ci->dst);
if (!path)
return connect_direct(cmd, ci);
return ci->cb(cmd, path, ci->arg);
}
struct command_result *establish_onion_path_(struct command *cmd,
struct gossmap *gossmap,
const struct pubkey *local_id,
const struct pubkey *dst,
bool connect_disable,
struct command_result *(*success)(struct command *,
const struct pubkey *,
void *arg),
struct command_result *(*fail)(struct command *,
const char *why,
void *arg),
void *arg)
{
struct connect_info *ci = tal(cmd, struct connect_info);
struct out_req *req;
/* Talking to ourselves is trivial. */
if (pubkey_eq(local_id, dst)) {
struct pubkey *path = tal_dup_arr(tmpctx, struct pubkey, local_id, 1, 0);
return success(cmd, path, arg);
}
ci->local_id = *local_id;
ci->dst = *dst;
ci->cb = success;
ci->fail = fail;
ci->arg = arg;
ci->connect_disable = connect_disable;
ci->gossmap = gossmap;
/* We use listpeers here: we don't actually care about channels, just connections! */
req = jsonrpc_request_start(cmd, "listpeers",
listpeers_done,
command_failed,
ci);
return send_outreq(req);
}