|
10 | 10 | from twisted.protocols.basic import LineReceiver |
11 | 11 | log = get_log() |
12 | 12 |
|
| 13 | +""" |
| 14 | +Messaging protocol (which wraps the underlying Joinmarket |
| 15 | +messaging protocol) used here is documented in: |
| 16 | +Joinmarket-Docs/lightning-messaging.md |
| 17 | +""" |
| 18 | + |
13 | 19 | LOCAL_CONTROL_MESSAGE_TYPES = {"connect": 785, "disconnect": 787, "connect-in": 797} |
14 | 20 | CONTROL_MESSAGE_TYPES = {"peerlist": 789, "getpeerlist": 791, |
15 | 21 | "handshake": 793, "dn-handshake": 795} |
|
35 | 41 | "proto-ver-max": JM_VERSION, |
36 | 42 | "features": {}, |
37 | 43 | "accepted": False, |
38 | | - "nick": "" |
| 44 | + "nick": "", |
| 45 | + "motd": "Default MOTD, replace with information for the directory." |
39 | 46 | } |
40 | 47 |
|
41 | 48 | # states that keep track of relationship to a peer |
42 | 49 | PEER_STATUS_UNCONNECTED, PEER_STATUS_CONNECTED, PEER_STATUS_HANDSHAKED, \ |
43 | 50 | PEER_STATUS_DISCONNECTED = range(4) |
44 | 51 |
|
45 | | -""" |
46 | | -### MESSAGE FORMAT USED on the LN-ONION CHANNELS |
47 | | -
|
48 | | -( || means concatenation for strings, here) |
49 | | -
|
50 | | -Messages conveyed between directly connected nodes with `sendcustommsg` |
51 | | -have the format described here: |
52 | | -
|
53 | | -https://lightning.readthedocs.io/PLUGINS.html#custommsg |
54 | | -
|
55 | | -Note in particular, that `type` is a two byte hex-encoded string |
56 | | -that prepends the actual message (also hex-encoded), without any intervening |
57 | | -length field. This `type` is the integer specified in this file as *_MESSAGE_TYPES. |
58 | | -
|
59 | | -Note also that the type *must* be odd for custom messages (the "it's OK to be odd" principle). |
60 | | -
|
61 | | -In text, the messages we send via this mechanism are of this format: |
62 | | -
|
63 | | -from-nick || COMMAND_PREFIX || to-nick || COMMAND_PREFIX || cmd || " " || innermessage |
64 | | -
|
65 | | -(COMMAND_PREFIX defined in this package's protocol.py) |
66 | | -
|
67 | | -Here `innermessage` may be a list of messages (e.g. the multiple offer case) separated by COMMAND PREFIX. |
68 | | -
|
69 | | -Note that this syntax will still be as was described here: |
70 | | -
|
71 | | -https://github.com/JoinMarket-Org/JoinMarket-Docs/blob/master/Joinmarket-messaging-protocol.md#joinmarket-messaging-protocol |
72 | | -
|
73 | | -Note also that there is no chunking requirement applied here, based on the assumption that we have a sufficient 1300 byte limit. |
74 | | -That'll probably be changed later. |
75 | | -
|
76 | | -### CONTROL MESSAGES |
77 | | -
|
78 | | -#### HANDSHAKE CONTROL MESSAGES |
79 | | -
|
80 | | -The message `handshake` is sent by any peer/node not configured to act as |
81 | | -directory node, to any other node it connects to, as the first message. |
82 | | -The message `dn-handshake` is sent by any peer/node which is configured to |
83 | | -act as a directory node, to any other node, as a response to the initial |
84 | | -`handshake` message. |
85 | | -(Notice that this configuration implies that directory nodes do not currently |
86 | | -talk to each other). |
87 | | -
|
88 | | -The syntax of `handshake` is: |
89 | | -json serialized: |
90 | | - {"app-name": "joinmarket", |
91 | | - "directory": false, |
92 | | - "location-string": "hex-key@host:port", |
93 | | - "proto-ver": 5, |
94 | | - "features": {}, |
95 | | - "nick": "J5***" |
96 | | - } |
97 | | -Note that `proto-ver` is the version specified as `JM_VERSION` in jmdaemon.protocol. |
98 | | -(It has not changed for many years, it only specifies the syntax of the messages). |
99 | | -The `features` field is currently empty. |
100 | | -
|
101 | | -The syntax of `dn-handshake` is: |
102 | | -json serialized: |
103 | | - {"app-name": "joinmarket", |
104 | | - "directory": true, |
105 | | - "proto-ver-min": 5, |
106 | | - "proto-ver-max": 5, |
107 | | - "features": {} |
108 | | - "accepted": true, |
109 | | - "nick": "J5**" |
110 | | - } |
111 | | -
|
112 | | - Non-directory nodes should send `handshake` to directory nodes, |
113 | | - and directory nodes should return the `dn-handshake` method with `true` |
114 | | - for accepted, if and only if: |
115 | | - * the protocol version is in the accepted range |
116 | | - * the `directory` field of the peer is false |
117 | | - * the `app-name` is joinmarket |
118 | | - * the set of features requested is both recognized and accepted (currently: none) |
119 | | - * the nick used by this entity/bot across all message channels, used for cross-channel message spoofing protection |
120 | | -
|
121 | | -Notice that more than one nick is NOT allowed per LN node; this is deferred to |
122 | | -future updates. |
123 | | -
|
124 | | - In case those conditions are met, return `"accepted": true`, else return |
125 | | - `"accepted": false` and immediately disconnect the new peer. |
126 | | - (in this rejection case, the remaining fields of the `dn-handshake` message do |
127 | | - not matter, but can be kept as before for convenience). |
128 | | -
|
129 | | -In case of a direct connection between peers (neither are directory nodes), |
130 | | -the party which connects then sends the first `handshake` message, and the |
131 | | -connected-to party responds with their own `handshake`. |
132 | | -
|
133 | | -In this case, the connection should be accepted and maintained by the receiver |
134 | | -if and only if: |
135 | | -* the protocol version is identical |
136 | | -* the `directory` field of the peer is false |
137 | | -* the `app-name` is joinmarket |
138 | | -* the set of features is both recognized and accepted (currently: none) |
139 | | -
|
140 | | -otherwise the peer should be immediately disconnected. |
141 | | -
|
142 | | -ALL OTHER MESSAGES (control or otherwise, as detailed below), cannot be sent/ |
143 | | -will be ignored until the above two-way handshake is complete. |
144 | | -
|
145 | | -#### OTHER CONTROL MESSAGES |
146 | | -
|
147 | | -The syntax of `peerlist` is: |
148 | | -
|
149 | | -nick || NICK_PEERLOCATOR_SEPARATOR || peer-location || "," ... (repeated) |
150 | | -
|
151 | | -i.e. a serialized list of two-element tuples, each of which is a Joinmarket nick |
152 | | -followed by a peer location. |
153 | | -
|
154 | | -`peerlist` may be sent by directory nodes to non-directory nodes at any time, |
155 | | -but currently it is sent according to a specific rule described below. |
156 | | -
|
157 | | -#### LOCAL CONTROL MESSAGES |
158 | | -
|
159 | | -There are two messages passed inside the plugin, to Joinmarketd, in response to events in lightningd, |
160 | | -namely the `connect` and `disconnect` events triggered at the LN level. These are used to update |
161 | | -the *state* of existing peers that we have recorded as connected at some point, to ourselves. |
162 | | -
|
163 | | -The mechanisms here are loosely synchronizing a database of JM peers, with obviously the directory |
164 | | -node(s) acting as the data provider. It's notable that there are no guarantees of accuracy or |
165 | | -synchrony here. |
166 | | -
|
167 | | -### PARSING OF RECEIVED JM_MESSAGES |
168 | | -
|
169 | | -
|
170 | | -The text will be utf-encoded before being hexlified, and therefore the following actions are needed of the receiver: |
171 | | -
|
172 | | -Extract the peerid of the sender, and the actual message, received as encoded json: |
173 | | -
|
174 | | -* peerid: json.loads(msg.decode("utf-8"))["peer_id"] |
175 | | -* message: json.loads(msg.decode("utf-8"))["payload"] |
176 | | -(which is prepended by a two byte message_type; see below). |
177 | | -
|
178 | | -##### peerid: |
179 | | -
|
180 | | -This field comes in three potential forms (as hex): |
181 | | -"00" : special null peerid indicating a local control message (see above). |
182 | | -"hex-key": peerid without connection information, allowing us to record the existence of a peer, |
183 | | - but not to send messages to it directly (only via the directory node). |
184 | | -"hex-key@host:port": peerid with connection information, allowing us to attempt to connect to it, |
185 | | - and send private messages to it directly. |
186 | | -
|
187 | | -##### payload: |
188 | | -
|
189 | | -This is parsed by: |
190 | | -
|
191 | | -1. Take the first two hex-encoded bytes and convert to an integer: this is the |
192 | | - message type. Take the remaining part of the hex string and unhexlify, |
193 | | - converting to binary, then .decode("utf-8") again, converting to a string. |
194 | | - This means encoding is sometimes very inefficient, if the underlying string actually |
195 | | - contains hex or other encoding, but can't really be changed until the underlying |
196 | | - Joinmarket messaging protocol is changed. |
197 | | -
|
198 | | -2. Split the decoded string by COMMAND PREFIX and parse as `from nick, to nick, command, message(s)` |
199 | | - (see above for syntax). |
200 | | -
|
201 | | -The resulting messages can be passed into Joinmarket's normal message channel processing, and should be |
202 | | -identical to that coming from IRC. |
203 | | -
|
204 | | -However, before doing so, we need to identify "public messages" versus private, which does not |
205 | | -have as natural a meaning here as it does on IRC; we impose it by using a to-nick value of PUBLIC |
206 | | -and by sending the message_type `687` to the Lightning RPC instead of the default |
207 | | -message_type `685` for privmsgs to a single counterparty. This will instruct the directory node |
208 | | -to send the message to every peer it knows about. |
209 | | -
|
210 | | -### GETTING INFORMATION ABOUT PEERS FOR DIRECT CONNECTIONS |
211 | | -
|
212 | | -To avoid passing huge lists of peers around, the directory node takes a "lazy" approach to |
213 | | -sharing connection info between peers: |
214 | | -
|
215 | | -When Peer J51 asks to privmsg J52 (which it discovered when receiving a privmsg from J52, usually |
216 | | -here that would be in response to a `!orderbook` pubmsg by J51), the directory node does as instructed, |
217 | | -but then sends also a `peerlist` message to J51, containing the full network location of J52. |
218 | | -
|
219 | | -Given this new information, J51 opportunistically tries to connect to J52 directly and if the network |
220 | | -connection succeeds, sends a handshake to J52. If J52 responds with acceptance, the direct messaging |
221 | | -connection is established, and from then on, until J51 sees a disconnect event for that network peer, |
222 | | -he will divert any `privmsg` to that party to use the direct connection instead of the directory node. |
223 | | -
|
224 | | -""" |
225 | 52 |
|
226 | 53 | """ this passthrough protocol allows |
227 | 54 | the joinmarket daemon to receive messages |
@@ -826,6 +653,8 @@ def forward_privmsg_to_peer(self, nick, message, from_nick): |
826 | 653 | assert self.self_as_peer.directory |
827 | 654 | peerid = self.get_peerid_by_nick(nick) |
828 | 655 | if not peerid: |
| 656 | + log.debug("We were asked to send a message from {} to {}, " |
| 657 | + "but {} is not connected.".format(from_nick, nick, nick)) |
829 | 658 | return |
830 | 659 | # The `message` passed in has format COMMAND_PREFIX||command||" "||msg |
831 | 660 | # we need to parse out cmd, message for sending. |
|
0 commit comments