0004_ws2p_v1.md 25.8 KB
Newer Older
Éloïs's avatar
Éloïs committed
1
# Duniter WS2P API v1
Éloïs's avatar
Éloïs committed
2

nanocryk's avatar
nanocryk committed
3
This document details the current specifications of WS2P v1 as they are already integrated in duniter-ts 1.6.
Éloïs's avatar
Éloïs committed
4

Éloïs's avatar
Éloïs committed
5
6
7
## Contents

* [Contents](#contents)
8
9
* [Conventions](#conventions)
  * [JSON format](#json-format)
Éloïs's avatar
Éloïs committed
10
* [What is WS2P ?](#what-is-ws2p-)
Éloïs's avatar
Éloïs committed
11
12
13
* [WS2P Endpoints](#ws2p-endpoints)
  * [Endpoint format](#endpoint-format)
  * [Getting endpoints from other Duniter nodes](#getting-endpoints-from-other-duniter-nodes)
14
* [Priority of WS2P outcoming connections](#priority-of-ws2p-outcoming-connections)
15
* [Overview on the format of ws2p messages](#overview-on-the-format-of-ws2p-messages)
Éloïs's avatar
Éloïs committed
16
* [Establishing a WS2P connection](#establishing-a-ws2p-connection)
17
* [Rules for accepting an incoming connection](#rules-for-accepting-an-incoming-connection)
Éloïs's avatar
Éloïs committed
18
* [HEAD messages](#head-messages)
Éloïs's avatar
Éloïs committed
19
* [Documents messages](#documents-messages)
Éloïs's avatar
Éloïs committed
20
  * [Peer format](#peer-format)
Éloïs's avatar
Éloïs committed
21
* [Rebound policy](#rebound-policy)
Éloïs's avatar
Éloïs committed
22
  * [HEAD Rebound policy](#head-rebound-policy)
Éloïs's avatar
Éloïs committed
23
  * [Documents rebound policy](#documents-rebound-policy)
Éloïs's avatar
Éloïs committed
24
25
26
27
28
29
30
* [WS2P requests](#ws2p-requests)
  * [getCurrent](#getcurrent)
  * [getBlock](#getblock)
  * [getBlocks](#getBlocks)
  * [getRequirementsPending](#getrequirementspending)
  * [List of error messages](#list-of-error-messages)

31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
## Conventions

### JSON format

All data in JSON format is presented in this RFC with explicit typing.

Types list : 

    Number(field description),
    String(field description),
    Bool(field description),
    Object(field description)

Example : 

    {
        "pubkey": String(identity pubkey in base58),
        "expires_on": Number(timestamp),
        "wasMember": Bool(true if the identity has already been a member at least once),
        "pendingMembership" : Object(pending membership document)
    ]

Éloïs's avatar
Éloïs committed
53
54
55

## What is WS2P ?

Éloïs's avatar
Éloïs committed
56
WS2P means "**W**eb **S**ocket **To** **P**eer".
Éloïs's avatar
Éloïs committed
57

nanocryk's avatar
nanocryk committed
58
59
60
61
62
WS2P is the inter-node network layer, it is the only guarantor of synchronization between the different nodes of the network, so its role is critical.

WS2P is the network part of the Duniter protocol, so any Duniter implementation must integrate the WS2P network layer in order to work.

WS2P is exclusively based on websocket technology.
Éloïs's avatar
Éloïs committed
63

Éloïs's avatar
Éloïs committed
64
65
66
67
## WS2P Endpoints

### Endpoint format

Éloïs's avatar
Éloïs committed
68
In the peer card : `API uuid host port path`  
Éloïs's avatar
Éloïs committed
69
70
Resolved in* : `ws://host:port/path`  
*If the port is `443`, the `wss://` protocol will be used.
Éloïs's avatar
Éloïs committed
71

Éloïs's avatar
Éloïs committed
72
api := Field indicating api type. The only two types are `WS2P` and `WS2PTOR`.
73
uuid := Random sequence of 8 hexadecimal characters. (This uuid, coupled with the public key, makes it possible to identify a Duniter node in a unique way.)
nanocryk's avatar
nanocryk committed
74
host := Domain name or ipv4 or ipv6 (it is not possible to declare both an ipv4 and an ipv6, for this it is necessary to use a domain name.)  
Éloïs's avatar
Éloïs committed
75
76
77
port := Mandatory. Must be an integer. (_By convention, port 20900 is used for currency g1-test and port 20901 for currency g1._)  
path := Optional access path

Éloïs's avatar
Éloïs committed
78
79
80
Example of a valid endpoint : `WS2P c1c39a0a g1-monit.librelois.fr 443 ws2p`  
This endpoint is resolved as follows: `wss://g1-monit.librelois.fr:443/ws2p`

Éloïs's avatar
Éloïs committed
81
Other valid endpoint : `WS2P a0a45ed2 88.174.120.187 20901`  
Éloïs's avatar
Éloïs committed
82
This endpoint is resolved as follows: `ws://88.174.120.187:20901`
Éloïs's avatar
Éloïs committed
83

nanocryk's avatar
nanocryk committed
84
Any WS2P endpoint must match this regexp : `/^WS2P (?:[1-9][0-9]* )?([a-f0-9]{8}) ([a-z_][a-z0-9-_.]*|[0-9.]+|[0-9a-f:]+) ([0-9]+)(?: (.+))?$/`
Éloïs's avatar
Éloïs committed
85
86

### Getting endpoints from other Duniter nodes
Éloïs's avatar
Éloïs committed
87

nanocryk's avatar
nanocryk committed
88
Initially, Duniter is totally endpoint agnostic and don't know any node.
Éloïs's avatar
Éloïs committed
89
90
The ws2p module requires that at least one or more peer cards are already stored in the database when launching the node, 
it's up to the Duniter sync command to handle this (ws2p does not handle this).
Éloïs's avatar
Éloïs committed
91
92

_*peer card : Signed document declaring all the access points of a peer._
Éloïs's avatar
Éloïs committed
93

nanocryk's avatar
nanocryk committed
94
When duniter-ts starts up, the WS2P module accesses the peer cards stored in duniter-ts's database following synchronization.
Éloïs's avatar
Éloïs committed
95

96
97
98
99
100
## Priority of WS2P outcoming connections

The WS2P module attempts to establish outcoming connections at the kick-start of the node and then every 10 minutes.  
At each attempt, all known endpoints are contacted until the outcoming quota is reached.  
If the outcoming quota is already reached at the beginning of an attempt, the least priority connection is removed in order to keep the network dynamic and scalable.
Éloïs's avatar
Éloïs committed
101

Éloïs's avatar
Éloïs committed
102
The WS2P module does not attempt to connect randomly to any node among those of which it knows a WS2P endpoint,
Éloïs's avatar
Éloïs committed
103
104
it classifies them according to the following criteria : 

Éloïs's avatar
Éloïs committed
105
### 1st criterion: the network layer
Éloïs's avatar
Éloïs committed
106
107
108
109
110
111

If the node is configured to use an X network layer, then all nodes that accept a connection through this X network layer will have priority.
Currently, only the TOR network layer is implemented.

_Note: the first criterion takes precedence over the second one._

Éloïs's avatar
Éloïs committed
112
### 2nd criterion: priority score
Éloïs's avatar
Éloïs committed
113

Éloïs's avatar
Éloïs committed
114
The priority score of a node depends on its public key and is calculated as follows :  
Éloïs's avatar
Éloïs committed
115
116
117
118
119
by default the priority score is zero  
If the key is a member: +1  
If the key is preferred/privileged: +2  
If the key is the same as yourself: +4 (for multi-node)

120
121
122
123
124
125
126
## Overview on the format of ws2p messages

Most ws2p messages exist in two formats : 

JSON: this is the format used to transmit the message through the websocket.  
RAW: this is the format used to sign the message. Messages received in JSON must therefore be converted to raw format in order to verify the validity of their signature.

Éloïs's avatar
Éloïs committed
127
128
## Establishing a WS2P connection

Éloïs's avatar
Éloïs committed
129
After sorting all WS2P endpoints according to the criteria described in the previous section,
Éloïs's avatar
Éloïs committed
130
131
132
133
Duniter-ts tries to connect to the other nodes by group of 5.  

for each connection attempt, the process is as follows:  

Éloïs's avatar
Éloïs committed
134
1. Opening a websocket pointing to the remote endpoint. If the websocket has been successfully opened before the timeout*, send a CONNECT message. Otherwise, log the error and close the websocket.
Éloïs's avatar
Éloïs committed
135
3. Wait to receive a CONNECT message from the remote node, checking the validity. If the CONNECT message is valid, then sending an ACK message. Otherwise, log the error and close the websocket.
136
137
4. Wait to receive an ACK message from the remote node, checking the validity. If the ACK message is valid, then sending an OK message. Otherwise, log the error and close the websocket.
5. Wait to receive an OK message from the remote node, checking the validity. If the OK message is valid, then consider the ws2p connection completely established. Otherwise, log the error and close the websocket.
Éloïs's avatar
Éloïs committed
138

Éloïs's avatar
Éloïs committed
139
*timeout : 15 seconds for connection to a conventional endpoint. 30 seconds for connection to a hidden tor service.
Éloïs's avatar
Éloïs committed
140

Éloïs's avatar
Éloïs committed
141
142
### CONNECT message

143
JSON format :
Éloïs's avatar
Éloïs committed
144
145

    {
146
147
148
149
        "auth": String("CONNECT"),
        "pub": String("D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx"),
        "challenge": String("28170b84-3468-4c21-806e-cf6457d43298055df085-8e5e-43cd-9907-e5c4f9de5bc7"),
        "sig": String("wIa/gohWYcJUt10xgsMAjlBiMYhxu2DOKDJdPiEFVB3OVynFvPPW4S/gGZQE7vlxzplSHUE3dCSWfrtjGtlGCw==")
Éloïs's avatar
Éloïs committed
150
151
    }
    
152
153
154
pub := Local node public key  
challenge := random string  
sig := ed25519 signature of the RAW format message in base64.
Éloïs's avatar
Éloïs committed
155
156
157

RAW format : `WS2P:CONNECT:currency_name:pub:challenge`

Éloïs's avatar
Éloïs committed
158
RAW format of the example message above : `WS2P:CONNECT:g1:D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx:28170b84-3468-4c21-806e-cf6457d43298055df085-8e5e-43cd-9907-e5c4f9de5bc7`
Éloïs's avatar
Éloïs committed
159
160
161

### ACK message

162
163
JSON format :

Éloïs's avatar
Éloïs committed
164
    {
165
166
167
        "auth": String("ACK"),
        "pub": String("D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx"),
        "sig": String("wIa/gohWYcJUt10xgsMAjlBiMYhxu2DOKDJdPiEFVB3OVynFvPPW4S/gGZQE7vlxzplSHUE3dCSWfrtjGtlGCw==")
Éloïs's avatar
Éloïs committed
168
169
    }
    
170
171
pub := Local node public key  
sig := ed25519 signature of the RAW format message in base64.
Éloïs's avatar
Éloïs committed
172
173
174
    
raw format : `WS2P:ACK:currency_name:pub:challenge`

175
176
177
_\*The ACK message is signed with the challenge of the other one. 
So your ACK message is signed with the challenge given by the remote node in its CONNECT message, 
and its ACK message is signed with the challenge you gave it in your CONNECT message.
178
The challenge is not retransmitted in json format of ACK messages because the remote node is supposed to already have this information._
Éloïs's avatar
Éloïs committed
179

Éloïs's avatar
Éloïs committed
180
181
### OK message

182
183
JSON format :

Éloïs's avatar
Éloïs committed
184
    {
185
186
        "auth: String("OK"),
        "sig": String(ed25519 signature of the RAW format message in base64)
Éloïs's avatar
Éloïs committed
187
188
189
190
    }
    
raw format : `WS2P:OK:currency_name:pub:challenge`

191
_**Be careful**, this time each one signs his message OK with his own challenge that he sent in his message CONNECT.
192
The challenge is not retransmitted in json format of OK messages because the remote node is supposed to already have this information._
Éloïs's avatar
Éloïs committed
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
## Rules for accepting an incoming connection

When a CONNECT message is received from another node, three decisions are possible : 
1. Accept connection
2. Refuse connection
3. not answering

The node will always try to answer when possible, to do so it launches a timeout on its side as soon as it receives the request.
When the timeout is reached, if the request has not been fully processed, it is purely abandoned and the remote node will not receive an answer.

In cases where the request is fully processed before the timeout, the response is determined according to the following algorithm : 

    If the public key of the remote node is the same as yourself
    If the uuid is not the same, accept the connection. Otherwise, refuse connection. (do not accept oneself connetion).
    If the pubkey is banned, refuse connection.
    If priorityKeysOnly is enable and the pubkey isn't privileged, refuse connection.
    If there is already an active ws2p connection with this key (whether incoming or outcoming), refuse connection.
    If the incoming quota is not reached, accept the connection.
    If there is a strictly lower priority* incoming connection, accept the connection.
    Refuse connection.

Following the acceptance of an incoming connection, it may happen that the quota is exceeded, 
in this case a low priority* connection is deleted until the quota is respected.

_*The priority score is calculated in the same way as for outcoming connections._

Éloïs's avatar
Éloïs committed
220
221
## HEAD messages

Éloïs's avatar
Éloïs committed
222
223
224
225
226
227
Each duniter node informs the network of its current state via a HEAD, which is renewed with each new valid block.
The heads received from other nodes are recorded and relayed, so by bouncing, each Duniter node to a global view of the network.
The set of heads known by a duniter node at the moment is public information available via the BMA client api (url /network/ws2p/heads).

Each new head received from the same node overwrites the previous head in memory, so that only the current state of the network is stored.

Éloïs's avatar
Éloïs committed
228
229
230
231
232
233
234
235
236
237
238
239
240
HEADs message format :

    {
        name: "HEAD",
        body: {
            heads: [
                HEAD_0,
                HEAD_1,
                ...
            ]
        }
    }

Éloïs's avatar
Éloïs committed
241
242
243
244
245
### HEAD v0

Accepted since duniter-ts **v1.6.0**

    {
246
247
        "message": String("API:MESSAGE_TYPE:PUBKEY:BLOCKSTAMP"),
        "sig": String("ZkatBTCYlp1KC/AS2TcDUYmxsWo0SaIDgkTZnhJzT2HU2OdJTqYr5s5JA+8iGCf0Qml8UgiwidscAEyeEl+WBg=="")
Éloïs's avatar
Éloïs committed
248
249
    }
    
Éloïs's avatar
Éloïs committed
250
251
#### message field

Éloïs's avatar
Éloïs committed
252
253
254
API = "WS2P"  
MESSAGE_TYPE = "HEAD"  
PUBKEY := public key of the issuer node of this head  
Éloïs's avatar
Éloïs committed
255
256
257
258
BLOCKSTAMP = `blockNumber-Hash`

#### sig field

Éloïs's avatar
Éloïs committed
259
260
261
262
263
264
265
sig: ed25519 signature of the message field in base64.

### HEAD v1

Accepted since duniter-ts **v1.6.9**

    {
266
267
        "message": String("API:MESSAGE_TYPE:1:PUBKEY:BLOCKSTAMP:WS2PID:SOFTWARE:SOFT_VERSION:POW_PREFIX"),
        "sig": String("MY90zXICfbYhLlz8VrL4HWPkphZEFR+bT2JWsoKdDMadgn0R0ZjsowDsnlfNqX4F4qeWeFoxhvdVgTO9VSghCA==")
Éloïs's avatar
Éloïs committed
268
269
    }
    
Éloïs's avatar
Éloïs committed
270
271
#### message field

Éloïs's avatar
Éloïs committed
272
273
274
275
276
277
API = `WS2P`  
MESSAGE_TYPE = `HEAD`  
PUBKEY := public key of the issuer node of this head  
BLOCKSTAMP = `blockNumber-Hash`  
WS2PID := unique identifier of the ws2p endpoint  
SOFTWARE = `duniter-ts`  
278
SOFT_VERSION = `X.Y.Z-suffix` (for example `1.6.14-beta`)  
Éloïs's avatar
Éloïs committed
279
280
281
282
POW_PREFIX := nonce prefix for proof of work (manually fixed by the user)

#### sig field

Éloïs's avatar
Éloïs committed
283
284
285
286
sig: ed25519 signature of the message field in base64.

### HEAD v2

Éloïs's avatar
Éloïs committed
287
288
Accepted since duniter-ts **v1.6.9**, relayed fully since duniter-ts **v1.6.15**  
_Nodes with versions lower than 1.6.15 bounce a degraded version of the head, only `message` and `sig` fields are relayed._
Éloïs's avatar
Éloïs committed
289
290

    {
291
292
293
294
295
        "message": String("API:HEAD:1:PUBKEY:BLOCKSTAMP:WS2PID:SOFTWARE:SOFT_VERSION:POW_PREFIX"),
        "sig": String("TPh2A3NS8cHj8yrJk1Yeldx2H6bPEp46cFAGZXKfxJcNgXL2sWrlirhIOlp8pkUFSrwDawWY1zO1jlgUqMvlAg=="),
        "messageV2": String("API:HEAD:2:PUBKEY:BLOCKSTAMP:WS2PID:SOFTWARE:SOFT_VERSION:POW_PREFIX:FREE_MEMBER_ROOM:FREE_MIRROR_ROOM"),
        "sigV2": String("ta1lRrWsjGcYHcLdS75JgEW5B8ByRetFVUVVpakKNJBirhRe8HcYUHEOM7xj/+gUQGGOit6Gm5Q/lsvfsngWAQ=="),
        "step": Number(0)
Éloïs's avatar
Éloïs committed
296
297
    }
    
Éloïs's avatar
Éloïs committed
298
299
#### message & messageV2 fields

Éloïs's avatar
Éloïs committed
300
API := field indicating the network layer type and giving some information about the Network configuration. (See section "API field" for details)
Éloïs's avatar
Éloïs committed
301
302
303
304
305
306
307
308
MESSAGE_TYPE = `HEAD`  
PUBKEY := public key of the issuer node of this head  
BLOCKSTAMP = `blockNumber-Hash`  
WS2PID := unique identifier of the ws2p endpoint  
SOFTWARE = `duniter-ts`  
SOFT_VERSION = `X.Y.Z` (for example `1.6.14`)  
POW_PREFIX := nonce prefix for proof of work (manually fixed by the user)  
FREE_MEMBER_ROOM := Number of incoming connection requests that can still be accepted from member nodes (FREE_MIROR_ROOM - Number of incoming connections established by non-priority mirror nodes).  
Éloïs's avatar
Éloïs committed
309
310
FREE_MIROR_ROOM := An integer indicating the number of incoming connections that the node can still receive.

Éloïs's avatar
Éloïs committed
311
312
313
314
315
316
317
318
319
320
##### API field

general scheme : `WS2P[PrivateConf][PublicConf]`

We have 4 types of WS2P Private:  
OCA : clear all  
OTM : tor mixed  
OTA : tor all  
OTS : tor strict

Éloïs's avatar
Éloïs committed
321
322
323
324
325
326
327
| WS2P Private Type | Reach Clear Endpoint | Reach Tor Endpoint 
|:----:|:----:|:----:
|OCA|in clear|never
|OTM|in clear|per Tor
|OTA|per Tor |per Tor
|OTS|never   |per Tor

Éloïs's avatar
Éloïs committed
328
329
330
331
332
333
334
335
And two types of WS2P Public :  
IC clear endpoint  
IT tor endpoint

The WS2P Private conf is prefixed with an O and the ws2p public conf with an I, so the classic nodes will be of type `WS2POCAIC`.

If WS2P Public is disabled, the [PublicConf] part is absent : `WS2POCA`

Éloïs's avatar
Éloïs committed
336
337
#### sig & sigv2 fields

Éloïs's avatar
Éloïs committed
338
sig: ed25519 signature of the message field in base64.  
Éloïs's avatar
Éloïs committed
339
sigV2: ed25519 signature of the messageV2 field in base64.  
Éloïs's avatar
Éloïs committed
340
341
342
343

#### step field

Distance of the corresponding node on the network = Number of intermediate nodes through which this head bounces -1
Éloïs's avatar
Éloïs committed
344
345
346

**WARNING:** The step field is not signed, so its value cannot be authenticated and should only be used as an indication.

Éloïs's avatar
Éloïs committed
347
348
## Documents messages

Éloïs's avatar
Éloïs committed
349
All documents are sent in json format, the body format of the request is always as follows : 
Éloïs's avatar
Éloïs committed
350

Éloïs's avatar
Éloïs committed
351
352
    {
        body: {
353
354
            name: Number(DOCUMENT_TYPE_ID),
            "DOCUMENT_TYPE_NAME": Object(DOCUMENT)
Éloïs's avatar
Éloïs committed
355
356
357
        }
    }
    
Éloïs's avatar
Éloïs committed
358
359
360
table of document types :

    |DOCUMENT_TYPE_ID|DOCUMENT_TYPE_NAME|
Éloïs's avatar
Éloïs committed
361
362
363
364
365
    |               0|              peer|
    |               1|       transaction|
    |               2|        membership|
    |               3|     certification|
    |               4|          identity|
Éloïs's avatar
Éloïs committed
366
    |               5|             block|
Éloïs's avatar
Éloïs committed
367

Éloïs's avatar
Éloïs committed
368
With the exception of the peer format detailed below, the json format of each document is already detailed in the DUP protocol v10 : https://git.duniter.org/nodes/typescript/duniter/blob/1.6/doc/Protocol.md
Éloïs's avatar
Éloïs committed
369

Éloïs's avatar
Éloïs committed
370
### Peer format
Éloïs's avatar
Éloïs committed
371

Éloïs's avatar
Éloïs committed
372
    {
373
374
      "version": Number(10),
      "currency": String(CURRENCY_NAME),
Éloïs's avatar
Éloïs committed
375
      "endpoints": [
376
377
378
        String(ENDPOINT_1),
        String(ENDPOINT_2),
       ...
Éloïs's avatar
Éloïs committed
379
      ],
380
381
382
383
384
      "status": String("UP"),
      "block": String(BLOCKSTAMP),
      "signature": String(SIGNATURE),
      "raw": String(RAW_FORMAT),
      "pubkey": String(PUBKEY)
Éloïs's avatar
Éloïs committed
385
    }
Éloïs's avatar
Éloïs committed
386

Éloïs's avatar
Éloïs committed
387
Real example :
Éloïs's avatar
Éloïs committed
388

Éloïs's avatar
Éloïs committed
389
390
391
392
393
394
395
396
397
398
399
400
401
402
    {
      "version": 10,
      "currency": "g1",
      "endpoints": [
        "BMAS g1.monnaielibreoccitanie.org 443",
        "BASIC_MERKLED_API g1.monnaielibreoccitanie.org 443",
        "WS2P b48824f0 g1.monnaielibreoccitanie.org 20901"
      ],
      "status": "UP",
      "block": "89908-000006F1C135E1D1CD41BF13DC3A406F2DF577144BEEAB49F437D661FF3E8018",
      "signature": "zTPLWdmHm5c3uIfTKxYGtSd59b13Lc+FCcbCBuHEEllYj+3xwquo/wo3VbF5J1gzMewamB9JYOV74uNOm8itAQ==",
      "raw": "Version: 10\nType: Peer\nCurrency: g1\nPublicKey: 7v2J4badvfWQ6qwRdCwhhJfAsmKwoxRUNpJHiJHj7zef\nBlock: 89908-000006F1C135E1D1CD41BF13DC3A406F2DF577144BEEAB49F437D661FF3E8018\nEndpoints:\nBMAS g1.monnaielibreoccitanie.org 443\nBASIC_MERKLED_API g1.monnaielibreoccitanie.org 443\nWS2P b48824f0 g1.monnaielibreoccitanie.org 20901\nzTPLWdmHm5c3uIfTKxYGtSd59b13Lc+FCcbCBuHEEllYj+3xwquo/wo3VbF5J1gzMewamB9JYOV74uNOm8itAQ==\n",
      "pubkey": "7v2J4badvfWQ6qwRdCwhhJfAsmKwoxRUNpJHiJHj7zef"
    }
Éloïs's avatar
Éloïs committed
403
404
405
406
407

## Rebound policy

### HEAD Rebound policy

Éloïs's avatar
Éloïs committed
408
409
410
411
412
413
414
### HEADCache

The local node has a HEADCache (only in RAM), which represents its network view.  
This cache is an associative array, the keys correspond to the pair (pubkey+guid) and the values correspond to the complete HEAD in JSON format.

Note : In duniter-ts 1.6, the content of this HEADCache is provided by a BMA url in the form of a JSON array of values (without keys) : /network/ws2p/heads

Éloïs's avatar
Éloïs committed
415
416
417
418
419
420
421
422
423
424
425
426
427
#### Local HEAD

The local node issues a new HEAD every time the last block of its local branch changes, this change can have two causes:  
1. Arrival of a new block valid and stackable on the local branch
2. Local branch change caused by the fork resolution algorithm

Each time the local node emits a new head, it sends it to all established ws2p connections.

### Remotes HEADs

When the local node receives a HEAD message, it checks whether all of the following conditions are verified : 

1. The head has a valid format and a valid signature  
Éloïs's avatar
Éloïs committed
428
2. The head has a block number equal to or greater than the head of the same key in the cache (hereafter referred "cached head").
Éloïs's avatar
Éloïs committed
429
430
3. If the block number is equal to the cached head, the head is of a higher or equal version.  
4. If the block number is equal to the cached head, and the version is identical, the step field is smaller.
Éloïs's avatar
Éloïs committed
431
5. If the head pubkey is not a member, make sure there is an active ws2p connection with this pubkey.
Éloïs's avatar
Éloïs committed
432

Éloïs's avatar
Éloïs committed
433
If all these conditions are respected, perform the following actions :  
Éloïs's avatar
Éloïs committed
434
1. Save this head in the headCache, overwrite the existing head with same key.  
Éloïs's avatar
Éloïs committed
435
436
2. Increment the step field of this head then send it to all established ws2p connections.

Éloïs's avatar
Éloïs committed
437
### Documents rebound policy
Éloïs's avatar
Éloïs committed
438

Éloïs's avatar
Éloïs committed
439
Each time Duniter receives a new document, either via the client api or via ws2p, it performs the following checks: 
Éloïs's avatar
Éloïs committed
440

Éloïs's avatar
Éloïs committed
441
442
443
444
445
1. Checking the document's newness (it is not already present in bdd)  
2. Verification of the document's conformity (correct format and valid signatures)  
3. Verification of compliance with all local index rules

If all conditions are satisfied, then the document is saved in the local bdd of the node and is transmitted to all active ws2p connections.
Éloïs's avatar
Éloïs committed
446
447
448
449
450

## WS2P requests

Specific queries can be sent via ws2p to obtain specific blocks or sandbox data.

451
Each type of query has a unique identifier called "name".
Éloïs's avatar
Éloïs committed
452
453
454
455
456
457

### getCurrent

JSON Message : 

    {
458
        reqId: String(REQUESTS_UNIQUE_ID),
Éloïs's avatar
Éloïs committed
459
        body: {
460
            name: String("CURRENT"),
Éloïs's avatar
Éloïs committed
461
462
463
464
465
466
467
468
469
470
            params: {}
        }
    }

REQUESTS_UNIQUE_ID := Random sequence of 8 hexadecimal characters. (This unique identifier will be returned to the header of the response,
it allows the requester node to identify to which query the answers it receives correspond, because the answers are asynchronous).

JSON response to success :

    {
471
472
        resId: String(REQUESTS_UNIQUE_ID),
        body: Object(BLOCK_IN_JSON_FORMAT)
Éloïs's avatar
Éloïs committed
473
474
475
476
477
    }

JSON error response :

    {
478
479
        resId: String(REQUESTS_UNIQUE_ID),
        err: String(error message)
Éloïs's avatar
Éloïs committed
480
481
482
483
484
485
486
    }

### getBlock

JSON Message : 

    {
487
        reqId: String(REQUESTS_UNIQUE_ID),
Éloïs's avatar
Éloïs committed
488
        body: {
489
            name: String("BLOCK_BY_NUMBER"),
Éloïs's avatar
Éloïs committed
490
            params: {
491
                number: Number(BLOCK_NUMBER)
Éloïs's avatar
Éloïs committed
492
493
494
495
496
497
498
499
500
501
            }
        }
    }

REQUESTS_UNIQUE_ID := Random sequence of 8 hexadecimal characters. (This unique identifier will be returned to the header of the response,
it allows the requester node to identify to which query the answers it receives correspond, because the answers are asynchronous).

JSON response to success :

    {
502
503
        resId: String(REQUESTS_UNIQUE_ID),
        body: Object(BLOCK)
Éloïs's avatar
Éloïs committed
504
505
506
507
508
    }

JSON error response :

    {
509
510
        resId: String(REQUESTS_UNIQUE_ID),
        err: String(error message)
Éloïs's avatar
Éloïs committed
511
512
513
514
515
516
517
518
519
    }

### getBlocks

JSON Message : 

    {
        reqId: REQUESTS_UNIQUE_ID,
        body: {
Éloïs's avatar
Éloïs committed
520
            name: "BLOCKS_CHUNK",
Éloïs's avatar
Éloïs committed
521
            params: {
522
523
                count: Number(number of blocks requested),
                fromNumber: Number(number of the 1st block of the requested interval)
Éloïs's avatar
Éloïs committed
524
525
526
527
528
529
530
531
532
533
            }
        }
    }

REQUESTS_UNIQUE_ID := Random sequence of 8 hexadecimal characters. (This unique identifier will be returned to the header of the response,
it allows the requester node to identify to which query the answers it receives correspond, because the answers are asynchronous).

JSON response to success :

    {
534
        resId: String(REQUESTS_UNIQUE_ID),
Éloïs's avatar
Éloïs committed
535
        body: [
536
537
            Object(BLOCK_1),
            Object(BLOCK_2),
Éloïs's avatar
Éloïs committed
538
539
540
541
542
543
544
            ...
        ]
    }

JSON error response :

    {
545
546
        resId: String(REQUESTS_UNIQUE_ID),
        err: String(error message)
Éloïs's avatar
Éloïs committed
547
548
549
550
551
552
553
554
555
    }

### getRequirementsPending

Requests the "requirements" of all identities that have received at least `minCert` certifications.

JSON Message : 

    {
556
        reqId: String(REQUEST_UNIQUE_ID),
Éloïs's avatar
Éloïs committed
557
        body: {
558
            name: String("WOT_REQUIREMENTS_OF_PENDING"),
Éloïs's avatar
Éloïs committed
559
            params: {
560
                minCert: Number()
Éloïs's avatar
Éloïs committed
561
562
563
564
565
566
567
568
569
570
            }
        }
    }

REQUESTS_UNIQUE_ID := Random sequence of 8 hexadecimal characters. (This unique identifier will be returned to the header of the response,
it allows the requester node to identify to which query the answers it receives correspond, because the answers are asynchronous).

JSON response to success :

    {
571
572
573
        "resId": REQUESTS_UNIQUE_ID,
        "body": {
            "identities": [
Éloïs's avatar
Éloïs committed
574
                {
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
                    "certifications": [
			            {
                            "expiresIn": Number(timestamp),
                            "from": String(issuer pubkey in base58),
                            "timestamp": Number(),
                            "to": String(receiver pubkey in base58)
                        }),
                        ...
                    ]
                    "expired": Bool(),
                    "isSentry": Bool(),
                    "membershipExpiresIn": Number(),
                    "membershipPendingExpiresInm": Number(),
                    "meta": {
                        "timestamp": String(identity document creation blockstamp)
                    },
                    "outdistanced": Bool(),
                    "pendingCerts": [
                        {
                            "block": Number(doubloon with "block_number" field),
                            "block_hash": String(certification document creation block hash),
                            "block_number": Number(certification document creation block number),
                            "blockstamp": String(certification document creation blockstamp),
                            "expired": Number(is always zero),
                            "expires_on": Number(timestamp),
                            "from": String(issuer pubkey in base58),
                            "linked": Bool(is always false),
                            "sig": String(certification document signature in base64),
                            "target": String(hash of the target identity),
                            "to": String(receiver pubkey in base58),
                            "written": Bool(is always false),
                            "written_block": is always Null,
                            "written_hash": is always Null
                        },
                        Object(PEDNIGN_CERT_2),
                        ...
                    ],
                    "pendingMemberships": [
                        {
                            "block": String(doubloon with "blockstamp" field),
                            "blockHash": String(membership document creation block hash),
                            "blockNumber": Number(99156),
                            "blockstamp": String(membership document creation blockstamp),
                            "certts": String(doubloon with "blockstamp" field),
                            "expired": Null,
                            "expires_on": Number(timestamp),
                            "fpr": String(doubloon with "blockHash" field),
                            "idtyHash": String(hash of the target identity),
                            "issuer": String(issuer pubkey in base58),
                            "membership": String(user request type : "IN" or "OUT"),
                            "number": Number(doubloon with "blockNumber" field),
                            "sig": String(membership document signature in base64),
                            "signature": String(doubloon with "sig" field),
                            "type": String(doubloon with "membership" field),
Éloïs's avatar
Éloïs committed
629
                            "userid": String(username),
630
631
632
633
634
635
636
637
                            "written": Bool(is always false),
                            "written_number": Null
                        },
                        ...
                    ],
                    "pubkey": String(identity pubkey in base58),
                    "revocation_sig": String(revocation document signature in base64, empty where the identity is not revoked),
                    "revoked": Bool(),
Éloïs's avatar
Éloïs committed
638
                    "revoked_on": Number(obsolete field to be deleted),
639
                    "sig":String(identity document signature in base64),
Éloïs's avatar
Éloïs committed
640
                    "uid": String(username),
641
                    "wasMember": Bool(true if the identity has already been a member at least once)
Éloïs's avatar
Éloïs committed
642
                },
643
                Object(IDTY_2),
Éloïs's avatar
Éloïs committed
644
645
646
647
648
649
650
651
                ...
            ]
        }
    }

JSON error response :

    {
652
653
        resId: String(REQUESTS_UNIQUE_ID),
        err: String(error message)
Éloïs's avatar
Éloïs committed
654
655
656
657
658
659
660
661
662
    }

### List of error messages

 * "Wrong param `number`"
 * "Wrong param `count`"
 * "Wrong param `fromNumber`"
 * "Wrong param `minCert`"
 * "Unknow request"