Skip to content
Snippets Groups Projects

WIP: RFC 3 : GraphQL API for Duniter Client

Open nanocryk requested to merge graphql_api_rfc into master
Compare and
1 file
+ 330
0
Compare changes
  • Side-by-side
  • Inline
+ 330
0
# GraphQL API for Duniter Client
```txt
RFC: 3
Title: GraphQL API for Duniter Client
Type: New API module dedicated for client needs
Parent:
Status: WIP
Author: vtexier <vit@free.fr>
Created: 2018-01-19
Last edited: 2018-01-19
License: AGPL-3
```
> This document is currently being written and thus it's not complete. Specifications may change in the future.
## Abstract
In the current Duniter server, the old BMA api is deprecated and will be removed.
The WS2P API is dedicated for the inter-node communication.
We propose here a new API Module dedicated to client software.
It should deliver efficiently all information needed without slowing down the Duniter node.
## Duniter Ecosystem
As you know, there are two types of clients in Duniter ecosystem :
- Naive clients : These clients trust a given node and only one node. It trusts the administrator of the node. In case of cheating or in case of a bug, the client can be mistaken. The advantage of this behaviour is that the client is really smooth and fast. It doesn’t consume too much bandwith, and is compatible with small ressources endpoints.
- Simplified Verification clients : These clients trust the data they get because they request many nodes. They requests nodes identified by an identity, and as such, they are not sybillizable. The advantage of this behaviour is safety : the client request any data against multiple nodes. If one is cheating or buggy, its data wont match the data of other nodes. In such a case, this data will be ignored
The problem with current API (Basic Merkled API) is that it’s not really merklized : when you require data, you can only get every things, to its smallest details. The consequence is that when you need to verify data, you can only request it again and again against multiple nodes, resulting in endless loading.
## API standard
It is important that we use an already known standard to avoid difficulty in contribution. And to get all the evolution and ideas coming from the community supporting the standard.
The proposed standard is [GraphQL](http://graphql.org/). Created by Facebook, it is used by many companies around the world.
The PRO: https://blog.risingstack.com/graphql-overview-getting-started-with-graphql-and-nodejs/
The CON: https://blog.hitchhq.com/graphql-3-reasons-not-to-use-it-7715f60cb934
### Specifications
http://facebook.github.io/graphql
### GraphQL License
The specification of the API is licensed under the Open Web Fundation:
The various implementation, for example the javascript one, are under the MIT license.
https://code.facebook.com/posts/121714468491809/relicensing-the-graphql-specification/
## Server Librairies
GraphQl use queries that need to be parsed by the server implementation. So the use of a library avoid to write ourselves the base code.
### Javascript library
Facebook Javascript is available [here](https://github.com/graphql/graphql-js)
### Rust Library
Rust library is available [here](https://github.com/graphql-rust/juniper)
### Other languages
http://graphql.org/code/#server-libraries
## Client Libraries
http://graphql.org/code/#graphql-clients
## GraphQL Verification API
This is the name proposed for this API implementation.
Why ?
- We use **GraphQL**
- We make data changes **Verification**
- This is an **API** ;-)
## Requests and answers proposal
### Requests process
1. The client sends a “Data Request” to a given node. It gets its answer, formed as a tree of data.
2. The client builds the Merkled Tree corresponding to this tree
3. The client send a “Verification request” to N given nodes. The verification request returns the root of the merkle tree.
4. The client compares the Root received by the verification nodes to the one he built, and validate or unvalidate the data
These requests can be done in parallel. They should be sent to nodes that are on the same blockchain HEAD.
Verification only concerns written state of data. Validity of the data can be insured thanks to hashes and signatures.
### General form of the requests
- The requests should be done using GraphQL (via HTTP or Websockets)
- The URI endpoint is unique.
- The Query should handle :
+ The type of data requested
+ The source of data : from the pools or from the blockchain. Data from the pools cannot be verified using a merkle tree : there are not guarantees that they are the same on every nodes.
+ Filters (range, uid, pubkey, blockstamp, txhash…)
+ The type of request (Data request or Verification)
### State requests
These requests have a merkle tree associated. They should be used to verify the state of a given data.
They can be used on data in the blockchain or in the pool, as below :
- If the data is found in the blockchain, its written field has a value and the hash is computed depending on this value
- If the data is found in the pool, its written field is empty. The hash is the hash of an empty string : e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
- If the data is not found, it returns a 404 (fixme: replace with GraphQL empty response)
## Types
We can request different types:
- Data: a GraphQL json representation of the type of state requested from a document or an agregation of documents.
- Raw: a GraphQL json containing a raw document.
- Merckle Tree: a GraphQL json containing the root of the merckle tree from a state.
- Lists: a list of state or others types enumerated above. Lists are limited to a maximum number by default and must be paginated.
### Identity example
#### Identities data
These datas are requested for a given triplet of `(uid/pubkey/blockstamp)`.
These data are not the raw data of an Identity. Only the convenient data for a client software.
The data we need when we request Identity informations are :
Is it a member ?
Its signature
Its blockchain state : written or not-written
Its distance state : outdistanced or not
Its sentry state : sentry or not
Its membership state : if a document was sent, which one (IN or OUT)
If IN document
at which block
at which timestamp
Its revokation state : revoked or not revoked
If revoked :
at which block
at which timestamp
**GraphQL Request:**
Parameters are mandatory, this is the key to retrieve the Identity.
Fields are optional like in any GrahQL query.
```javascript
query getIdentity {
Identity(uid: "john", pubkey: "z7rDt7...", blockstamp: "22765-1AD84...") {
member
signature
written
outdistanced
sentry
Membership
Revoke
}
}
```
**GraphQL Response:**
```javascript
{
"data": {
"member": True,
"signature": "c4zYpECnNa99xc8Np/6NXXSo6bK7eGVAdG8QcZE8ZhFZeZjCv0F1Zzeub1ZDnWm4/TvKK9PjpuQBv9tBn9hcAA==",
"written": True
"outdistanced": False,
"sentry": True,
"Membership": {
"type": "IN",
"blockstamp": "22965-7FD42...",
"timestamp": "229658678",
},
"Revoke": {
"revoked": True,
"blockstamp": "22965-7FD42...",
"timestamp": "229658678",
}
}
}
```
To get the full raw document type:
```javascript
query getIdentityRaw {
IdentityRaw(uid: "john", pubkey: "z7rDt7...", blockstamp: "22765-1AD84...")
}
```
Returns:
```javascript
{
"data": {
"IdentityRaw": "Version: 10\n
Type: Identity\n
Currency: CURRENCY_NAME\n
Issuer: PUBLIC_KEY\n
UniqueID: USER_ID\n
Timestamp: BLOCK_UID\n
SIGNATURE"
}
}
```
**Calculate the Merckle Root Hash:**
To be able to change version or add fields easily on data fields more often than raw document,
merckle check is done on raw document fields.
```javascript
{
result: Hash(
Hash(Hash(member),
Hash(Hash(revocation.blockstamp),
Hash(revocation.timestamp),
Hash(revocation.signature)
Hash(Hash(revocation.written.blockstamp),
Hash(revocation.written.timestamp))?
)?,
Hash(expired),
Hash(outdistanced),
Hash(sentry)),
Hash(Hash(issuance.blockstamp),Hash(issuance.timestamp)),
Hash(Hash(written.blockstamp),Hash(written.timestamp))
)
}
```
Send the verification request:
```javascript
query getIdentityMerckleTree {
IdentityMerckleTree(uid: "john", pubkey: "z7rDt7...", blockstamp: "22765-1AD84...")
}
```
Return the root of the merckle tree:
```javascript
{
"data": {
"IdentityMerckleTree": "478D46A98F75..."
}
}
```
**Query a list of identities UID:**
For all list queries, an offset and a limit variable are specified by default.
You can use these to paginate your list and avoid a "timebomb" request.
*A "timebomb" request is a request on an infinitly growing list of entities,
leading to slower and bigger responses that can, at the end, crash the server.*
```javascript
# Query with variables and default values
query getIdentities($offset: Int = 0, $limit: Int = 1000) {
Identities {
uid
}
}
# Variables values for the request
{
"offset": 1000,
"limit": 1000
}
```
List response:
```javascript
{
"data": {
"Identities": [
{
"uid": "Alice"
},
{
"uid": "Bob"
}
]
}
```
**Send an Identity Document:**
GraphQL use another type of query to modifiy server's data. It is called "mutation".
As arguments, you must use "input objects" defined in the schema on the server.
```javascript
# Send a write mutation
mutation sendIdentityDocument($identityRaw: InputIdentityRaw!) {
Identity
}
# Variables
{
"identityRaw": "Version: 10\n
Type: Identity\n
Currency: CURRENCY_NAME\n
Issuer: PUBLIC_KEY\n
UniqueID: USER_ID\n
Timestamp: BLOCK_UID\n
SIGNATURE"
}
```
## Duniter Server module
The Duniter module handling the GraphQL API on the server.
TODO
Loading