-
Notifications
You must be signed in to change notification settings - Fork 3
Description
Currently server.version is very naive: the client sends a range of protocol versions it supports, and the server selects a single version from that range and replies with that selected version.
This implies that the client supports a continuous range of protocol versions (given there being protocol 1.2, 1.3, 1.4, the client can't proclaim they want either 1.2 or 1.4).
Interestingly, the server is allowed to only implement support for a non-continuous range of versions:
imagine the client sends [1.2, 1.4], that means the client must support {1.2, 1.3, 1.4}, but it is fine for a server to implement {1.2, 1.4} (excluding 1.3), as it is ultimately the server that chooses.
I think it would be useful to extend server.version, in a generic way, to be used for negotiating certain features.
For example, electrumx 1.18 added some basic countermeasures against traffic analysis in spesmilo/electrumx#301, which can result in messages being delayed by up to 1 second.
Some clients might want to opt-out of (or opt-in to) such delays.
Idea:
RPC signature:
server.version(client_name="", protocol_version="1.4", options={})
where options is a dictionary, keyed by feature_name strings.
The values could be lists: only the first item of the list is defined: "force_on", "prefer_on", "prefer_off", "force_off".
(making this a 4-state might seem like over-engineering but - depending on the features - each state makes sense)
The rest of the items in the list are specific to the given feature, to allow general customisation, and should be ignored by servers in case of unknown features.
If a server sees an unknown feature in the options dict with "force_on" or "force_off", they should send back an RPCError and disconnect.
RPC result:
[server_software_version, protocol_version, options]
where options is a dictionary, keyed by feature_name strings, filtered to only contain the keys the server understands. (i.e. features unknown to the server got removed)
The values could be lists: only the first item of the list is defined: "on" or "off".
The rest of the items in the list are specific to the given feature, to allow general customisation.
Concrete example:
We could add a feature named "delays". This feature could potentially be customisable, so that the client can say they want delays to never be longer than e.g. 250 ms.
The client could then send:
<-- server.version("electrum/4.6.2", "1.7", {"delays": ["prefer_on", 250]})
To which the server might reply, if they know the feature:
--> ["ElectrumX 1.18.0", "1.7", {"delays": ["on"]}]
Or if the server does not know the feature:
--> ["ElectrumX 1.18.0", "1.7", {}]
Or the client could send:
<-- server.version("electrum/4.6.2", "1.7", {"delays": ["force_off"]})
To which the server might reply, if they know the feature:
--> ["ElectrumX 1.18.0", "1.7", {"delays": ["off"]}]
Or if the server does not know the feature, the server would send back and error and disconnect:
--> RPCError(-32603, "unknown required feature: 'delays'")
Unfortunately, with current server implementations, adding a new arg to server.version seems to be a breaking change. Ideally servers should ignore the extraneous arg, but that's often not the case.
I think we should add a requirement to the protocol spec to relax the server behaviour re parsing server.version args: extraneous unknown args should be tolerated and ignored. Also, in case of json-rpc 2.0, named args should be supported.
server.versionarg list, len 2 <<< current widespread client behaviour- electrs OK
1.27 | D | interface.[blockstream.info:700] | <-- ('server.version', ['electrum/4.6.2', '1.4']) {} (id: 1) 1.47 | D | interface.[blockstream.info:700] | --> ['electrs-esplora 0.4.1', '1.4'] (id: 1) - fulcrum OK
119.14 | D | interface.[blackie.c3-soft.com:57002] | <-- ('server.version', ['electrum/4.6.2', '1.4']) {} (id: 1) 119.35 | D | interface.[blackie.c3-soft.com:57002] | --> ['Fulcrum 2.0', '1.4'] (id: 1) - electrumx OK
155.17 | D | interface.[fortress.qtornado.com:443] | <-- ('server.version', ['electrum/4.6.2', '1.4']) {} (id: 1) 156.31 | D | interface.[fortress.qtornado.com:443] | --> ['ElectrumX 1.18.0', '1.4'] (id: 1)
- electrs OK
server.versionarg list, len 3- electrs OK
50.49 | D | interface.[blockstream.info:700] | <-- ('server.version', ['electrum/4.6.2', '1.4', {'no_delays': True}]) {} (id: 1) 50.69 | D | interface.[blockstream.info:700] | --> ['electrs-esplora 0.4.1', '1.4'] (id: 1) - fulcrum RPCError
39.87 | D | interface.[blackie.c3-soft.com:57002] | <-- ('server.version', ['electrum/4.6.2', '1.4', {'no_delays': True}]) {} (id: 1) 40.09 | D | interface.[blackie.c3-soft.com:57002] | --> RPCError(-32602, 'Expected at most 2 parameters for server.version, got 3 instead') (id: 1) - electrumx RPCError
3.74 | D | interface.[fortress.qtornado.com:443] | <-- ('server.version', ['electrum/4.6.2', '1.4', {'no_delays': True}]) {} (id: 1) 4.73 | D | interface.[fortress.qtornado.com:443] | --> RPCError(-32602, '3 arguments passed to method server.version taking at most 2') (id: 1)
- electrs OK
server.versionarg dict, len 2- electrs REMOTE_DC
44.55 | D | interface.[blockstream.info:700] | <-- ('server.version', {'client_name': 'electrum/4.6.2', 'protocol_version': '1.4'}) {} (id: 1) 44.75 | D | interface.[blockstream.info:700] | --> CancelledError() (id: 1) - fulcrum RPCError
10.24 | D | interface.[blackie.c3-soft.com:57002] | <-- ('server.version', {'client_name': 'electrum/4.6.2', 'protocol_version': '1.4'}) {} (id: 1) 10.45 | D | interface.[blackie.c3-soft.com:57002] | --> RPCError(-32602, 'Named params are not supported for this method') (id: 1) - electrumx OK
1.57 | D | interface.[fortress.qtornado.com:443] | <-- ('server.version', {'client_name': 'electrum/4.6.2', 'protocol_version': '1.4'}) {} (id: 1) 2.78 | D | interface.[fortress.qtornado.com:443] | --> ['ElectrumX 1.18.0', '1.4'] (id: 1)
- electrs REMOTE_DC
server.versionarg dict, len 3- electrs REMOTE_DC
18.36 | D | interface.[blockstream.info:700] | <-- ('server.version', {'client_name': 'electrum/4.6.2', 'protocol_version': '1.4', 'options': {'no_delays': True}}) {} (id: 1) 18.56 | D | interface.[blockstream.info:700] | --> CancelledError() (id: 1) - fulcrum RPCError
63.11 | D | interface.[blackie.c3-soft.com:57002] | <-- ('server.version', {'client_name': 'electrum/4.6.2', 'protocol_version': '1.4', 'options': {'no_delays': True}}) {} (id: 1) 63.31 | D | interface.[blackie.c3-soft.com:57002] | --> RPCError(-32602, 'Named params are not supported for this method') (id: 1) - electrumx RPCError
90.77 | D | interface.[fortress.qtornado.com:443] | <-- ('server.version', {'client_name': 'electrum/4.6.2', 'protocol_version': '1.4', 'options': {'no_delays': True}}) {} (id: 1) 91.78 | D | interface.[fortress.qtornado.com:443] | --> RPCError(-32602, 'method "server.version" does not take parameter "options"') (id: 1)
- electrs REMOTE_DC