Charon allows users to access game-state data from a GSP running on a remote server through XMPP. In other words, one or more GSP processes can connect to an XMPP server and listen there for requests, and players can connect as well using a custom XMPP client, which is then able to send requests to the GSP connections.
In this document, we will give a brief overview of the underlying
protocol, i.e. what XMPP stanzas are exchanged in order to relay some
request from the player to the GSP (and the reply back). For the examples,
we will assume that the player is player@server/resource
, while the GSP
is connected as gsp@server/resource
.
When a new player connects, they first look for a particular GSP instance (i.e. resource) that they will then talk to. This is necessary because IQ stanzas will not be relayed to bare JIDs, and also so that the player gets a consistent view of the game state in case the different GSPs are slightly out-of-sync when they process a new block.
For this, the client initially sends an ordinary message stanza (not IQ)
to the GSPs bare JID gsp@server
, asking for a reply from a suitable instance:
<message to="gsp@server">
<ping xmlns="https://xaya.io/charon/" />
</message>
This will then be relayed by the XMPP server to one or more available GSP connections (taking their priorities also into account). A GSP client that receives such a message and feels ready to accept another client (i.e. is not overloaded) will reply with a directed presence as acknowledgement:
<presence to="player@server/resource">
<pong xmlns="https://xaya.io/charon/" version="backend version" />
</presence>
The client can then select one of the replies it gets (in case there are multiple) and record the GSP client's full JID (including its resource) for further requests. It can also take the backend version provided by the server into account, in case it wants to ensure a particular set of consensus rules (while a fork is going on) or interface. Once a server is selected, the client will send a directed presence as well:
<presence to="gsp@server/resource" />
By exchanging a pair of directed presence stanzas, the client and server will temporarily subscribe to each other's presence, so that they will also be notified about one becoming unavailable. (And then e.g. the client can perform another handshake to find a different server resource.)
For an ordinary RPC call that should retrieve some data from the
current game state in a non-blocking way (i.e. not one of the waitfor*
methods), the player sends an IQ get
request to the chosen GSP,
e.g. gsp@server/resource
. It has the following form:
<iq type="get">
<request xmlns="https://xaya.io/charon/">
<method>METHOD</method>
<params>...</params>
</request>
</iq>
Here, METHOD
is just a string representing the method name of the RPC call.
The <params>
tag carries a blob payload, which should
be the serialised JSON of the call parameters (i.e. a JSON array or object).
The GSP responds with an IQ result
. For a successful call, it returns
the result of the JSON-RPC method:
<iq type="result">
<response xmlns="https://xaya.io/charon/">
<result>...</result>
</response>
</iq>
<result>
in this case again holds a data blob
of serialised JSON. In case of an error result from JSON-RPC,
a stanza like this is returned instead:
<iq type="result">
<response xmlns="https://xaya.io/charon/">
<error code="CODE">
<message>MESSAGE</message>
<data>...</data>
</error>
</response>
</iq>
This holds the JSON-RPC error CODE
, MESSAGE
and data (the latter
again as serialised JSON in a data blob).
Note: Even for a JSON-RPC error, the IQ type is result
. IQ error
s
would indicate an issue with the transport over XMPP, not a successful
transport but an error from the JSON-RPC call.
In addition to ordinary calls to get some state, GSPs also support
notifying a client for updates. In particular, this is what is
behind the waitforchange
and waitforpendingchange
long-poll RPC methods.
Methods like that are supported specifically by Charon, by means of its
own subscription model based on
XEP-0060 (PubSub).
When a server supports broadcasting updates of a certain type (e.g.
for the game state or pending moves), it creates one node per notification
type with its XMPP pubsub service. Let's say the nodes are node-state
and
node-pending
, and the service is pubsub.server
.
In this case, the server includes the notifications it supports in any "pong"
presence sent to a newly connected client (in addition to the pong
tag):
<presence to="player@server/resource">
<pong xmlns="https://xaya.io/charon/" />
<notifications xmlns="https://xaya.io/charon/" service="pubsub.service">
<notification type="state">node-state</notification>
<notification type="pending">node-pending</notification>
</notifications>
</presence>
Also, whenever the server detects an update to one of the states it provides
subscription notifications for, it publishes an <update>
stanza
to the corresponding pubsub node
with a payload that contains the updated value as JSON data
(in the same way it is returned from the waitfor*
RPC method):
<item>
<update xmlns="https://xaya.io/charon/" type="state">
<raw>"NEW BEST BLOCK HASH"</raw>
</update>
</item>
<item>
<update xmlns="https://xaya.io/charon/" type="pending">
<raw>
{
"version": 42,
"pending": {"foo": "bar"}
}
</raw>
</update>
</item>
The Charon client, when it needs support for a particular RPC method like
waitforchange
, will select a server that announces a pubsub node for the
required type. It will then subscribe to updates on that node, and use this
to keep its own "copy" of the current state. From that, it can handle
RPC methods just like an ordinary GSP would.