Skip to content

Instantly share code, notes, and snippets.

@justmoon
Last active December 9, 2016 02:22
Show Gist options
  • Save justmoon/8cc71824c19719c1c31fd4cf1941a229 to your computer and use it in GitHub Desktop.
Save justmoon/8cc71824c19719c1c31fd4cf1941a229 to your computer and use it in GitHub Desktop.
Ledger WebSocket API Proposal

Overview

The five-bells-ledger WebSocket API provides a way for real-time access to the ledger.

Endpoint

The URL for the websocket endpoint is available by GETting the ledger's main URI (metadata endpoint) and inspecting the urls.websocket property. Example:

Request
GET / HTTP/1.1
Host: ledger.example
Response (irrelevant fields omitted)
HTTP/1.1 200 OK
Content-Type: application/json

{
  "urls": {
    "websocket": "wss://ledger.example/websocket"
  }
}

Authentication

Unfortunately, browsers do not support setting a custom auth header (for Basic auth from a browser client) for websocket connections. So in order to support any type of client, we need to use token-based authentication.

The client first makes a regular HTTP request to obtain a token:

POST /authenticate HTTP/1.1
Accept: application/json
Authorization: Basic c3RlZmFuOnN0ZWZhbg==
HTTP 200 OK
Content-Type: application/json

{ "token": "...xyz..." }

Note that the authenticate request fails if the user does not successfully authenticate. Anonymous requests are not allowed.

The client then uses this token as a query parameter when initiating the websocket connection:

GET /websocket?token=...xyz... HTTP/1.1
Connection: Upgrade
Upgrade: websocket
HTTP 101 Switching Protocols
Connection: Upgrade
Upgrade: websocket

The websocket client is now treated as the user who initiated the /authenticate request. Anonymous connections (without token) are disallowed.

The token is simply an opaque string from the client's perspective. The server may use a technology like JWT to generate the token.

Bidirectional JSON-RPC 2.0

In JSON-RPC 2.0 requests always go from "client" to "server" and responses always go from "server" to "client". Each response must be solicited by a prior request. In order to implement a publish/subscribe flow, the ledger must be able to send unsolicited messages. Therefore, the ledger and the TCP client must be acting as both JSON-RPC client and JSON-RPC server.

Legend

The examples in this document use the following convention:

--> data sent from Client to Ledger
<-- data sent from Ledger to Client

Commands

subscribe_account

subscribe_account({ eventType: String, accounts: String[] }) ⇒ null

Subscribe to events related to zero or more accounts.

Parameters

  • eventType - Scope of the subscription. Valid scopes are:

    • "transfer.create" - Triggered for newly created transfer
    • "transfer.update" - Triggered for transfer changes (e.g. state changes)

    Note that a wildcard may be used to refer to multiple events, e.g. transfer.* would subscribe to all transfer events.

  • accounts - List of accounts to subscribe to.

    Admins are allowed to subscribe to any account whereas regular users are allowed only to subscribe to their own account.

Example
--> {
      "jsonrpc": "2.0",
      "method": "subscribe_account",
      "params": [{
        "eventType": "transfer.*",
        "accounts": ["https://ledger.example/accounts/alice"]
      }],
      "id": 1
    }
<-- {
      "jsonrpc": "2.0",
      "result": 19,
      "id": 1
    }
<-- {
      "jsonrpc": "2.0",
      "method": "notify",
      "params": [{
        "id": "https://ledger.example/notifications/c92f2a2c-b21d-4e6c-96e7-4f6d6df4bee9",
        "event": "transfer.update",

        "resource": {
          "id": "https://ledger.example/transfers/155dff3f-4915-44df-a707-acc4b527bcbd",
          "ledger": "http://localhost",
          "debits": [{
            "account": "https://ledger.example/accounts/alice",
            "amount": "10",
            "authorized": true
          }],
          "credits": [{
            "account": "https://ledger.example/accounts/bob",
            "amount": "10"
          }],
          "state": "executed"
        }
      }],
      "id": null
    }
<-- {
      "jsonrpc": "2.0",
      "method": "notify",
      "params": [{
        "id": "https://ledger.example/notifications/52a42d6f-8d9c-4c05-b31c-cccc8bbdb30d",
        "event": "transfer.update",
        "resource": { /* transfer resource */ },
        "related_resources": {
          "execution_condition_fulfillment": "[ fulfillment ]",
          "cancellation_condition_fulfillment": "[ fulfillment ]"
        }
      }],
      "id": null
    }

subscribe_all_accounts

subscribe_all_accounts({ eventType: String }) ⇒ null

Subscribe to account-related events from all accounts. Requires admin permission.

@emschwartz
Copy link

Does the ledger and the TCP client must be acting as both JSON-RPC client and JSON-RPC server mean that JSON-RPC isn't really meant for this use case?

For the eventType, would you consider the transfer being executed, rejected or cancelled an "update"?

What is the result: 19 in the subscribe response?

@justmoon
Copy link
Author

Does the ledger and the TCP client must be acting as both JSON-RPC client and JSON-RPC server mean that JSON-RPC isn't really meant for this use case?

No, this is how JSON-RPC is almost always used.

For the eventType, would you consider the transfer being executed, rejected or cancelled an "update"?

Yes.

What is the result: 19 in the subscribe response?

It snuck in from the JSON-RPC example I was copying, but that gave me an idea - perhaps it should be an ID to refer to the subscription, so you have a way to remove a certain subscription without reconnecting.

@mDuo13
Copy link

mDuo13 commented Oct 31, 2016

Proposal: Use / instead of . in the eventType field. It's a little confusing to have . literals in some API fields when I've mostly been using . in field names to indicate nested fields of objects.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment