From 809ee6993b936170337281877872302847e9f10a Mon Sep 17 00:00:00 2001 From: Donald Adu-Poku Date: Thu, 17 Mar 2022 22:41:32 +0000 Subject: [PATCH 1/3] docs: document rpc api versioning process. This documents the process expected to be followed by contributors in versioning json-rpc api endpoints. --- docs/README.md | 1 + docs/rpc_api_versioning.md | 272 +++++++++++++++++++++++++++++++++++++ 2 files changed, 273 insertions(+) create mode 100644 docs/rpc_api_versioning.md diff --git a/docs/README.md b/docs/README.md index c6c5000301..f71e230317 100644 --- a/docs/README.md +++ b/docs/README.md @@ -164,6 +164,7 @@ https://decred.org/community/ * [JSON-RPC Reference](https://github.com/decred/dcrd/tree/master/docs/json_rpc_api.mediawiki) * [RPC Examples](https://github.com/decred/dcrd/tree/master/docs/json_rpc_api.mediawiki#8-example-code) +* [RPC API Versioning](https://github.com/decred/dcrd/tree/master/docs/rpc_api_versioning.md) diff --git a/docs/rpc_api_versioning.md b/docs/rpc_api_versioning.md new file mode 100644 index 0000000000..e676ee1c79 --- /dev/null +++ b/docs/rpc_api_versioning.md @@ -0,0 +1,272 @@ +### Table of Contents +1. [Per-RPC Versioning](#PerRPCVersioning)
+2. [Versioning Scheme](#VersioningScheme)
+3. [Versioned Endpoint Accessibilty Scheme](#AccessibiltyScheme)
+4. [Versioned Enpoint Example](#Example)
+5. [Versioned Enpoint Deprecation And Removal Scheme](#DeprecationAndRemovalScheme)
+6. [Versioned Endpoint JSON-RPC Unit Testing](#UnitTesting)
+7. [Versioned JSON-RPC Documentation](#Documentation)
+ + +
+ +### 1. Per-RPC Versioning + +To streamline API access in the process of transitioning between release +versions, decred's RPC endpoints will be versioned individually for each +JSON-RPC endpoint. +Per-RPC versioning provides the flexibility needed to isolate the development +of JSON-RPC endpoints. This allows endpoints to evolve individually and +exclusively of each other. + + + +### 2. Versioning Scheme + +The versioning scheme constitutes appending the version to the type name or +handler name. + - JSON-RPC types will have the version spliced between the method name and + `Cmd` suffix. For example, the `V2` version of the type `ExistsAddressCmd` + becomes `ExistsAddressV2Cmd`. + + - JSON-RPC handler names will have the version appended to the handler name. + For example, the `V2` version of the handler `handleExistsAddress` becomes +`handleExistsAddressV2`. + + - JSON-RPC method names will have the lowercase version appended to the + method name. For example the `V2` version of method name `existsaddress` + becomes `existsaddressv2`. + + + +### 3. Versioned Endpoint Accessibilty Scheme + +A versioned JSON-RPC endpoint infers there is a current version and an older +version of the endpoint. To ensure a smooth transition between JSON-RPC +versions for consumers, both versions should be accessible. The current and +older versions should be explicitly accessible via their respective method +names while the most recent version becomes the version defaulted to. +Using `existsaddress` as an example: + - The `V2` version of the endpoint, which is the current version, should be + explictly accessible via `existsaddressv2`. + + - The `V1` version of the endpoint, which is now the older version, should be + explicitly accessible via `existsaddressv1`. + + - The default method name, `existsaddress` will resolve to the current version + of the endpoint which is `existsaddressv2` in this case. + + This scheme will be enforced via the JSON-RPC type and handler registrations. + + + +### 4. Versioned Enpoint Example + +Taking into consideration the accessibility scheme outlined above, implementing +the `V2` version of the `existaddress` JSON-RPC endpoint will comprise of the +following components: + +```go + // JSON-RPC versioned endpoint command definition. + + // ExistsAddressV2Cmd defines the existsaddressv2 JSON-RPC command. + type ExistsAddressV2Cmd struct { + Address string + PaymentScript string + } + // ... +``` +```go + // ExistsAddress JSON-RPC type registrations. + dcrjson.MustRegister(Method("existsaddress"), (*ExistsAddressV2Cmd)(nil), flags) + dcrjson.MustRegister(Method("existsaddressv1"), (*ExistsAddressCmd)(nil), flags) + dcrjson.MustRegister(Method("existsaddressv2"), (*ExistsAddressV2Cmd)(nil), flags) + // ... +``` + +```go + // ExistsAddressV2 JSON-RPC Handler definition. + + // handleExistsAddress implements the existsaddress command. + // + // Deprecated: This will be removed on the next versioned endpoint + // implementation. + func handleExistsAddress(_ context.Context, s *Server, cmd interface{}) (interface{}, error) { + if s.cfg.ExistsAddresser == nil { + return nil, rpcInternalError("Exists address index disabled", + "Configuration") + } + + c := cmd.(*types.ExistsAddressCmd) + // ... + } + + // handleExistsAddressV2 implements the existsaddressv2 command. + func handleExistsAddressV2(_ context.Context, s *Server, cmd interface{}) (interface{}, error) { + if s.cfg.ExistsAddresser == nil { + return nil, rpcInternalError("Exists address index disabled", + "Configuration") + } + + c := cmd.(*types.ExistsAddressV2Cmd) + // ... + } + +``` + +```go + // ExistsAddress JSON-RPC handler registrations. + var rpcHandlersBeforeInit = map[types.Method]commandHandler{ + // ... + "existsaddress": handleExistsAddressV2, + "existsaddressv1": handleExistsAddress, + "existsaddressv2": handleExistsAddressV2, + // ... + } +``` + +```go +// ExistsAddress JSON-RPC result type registrations. +var rpcResultTypes = map[types.Method][]interface{}{ + // ... + "existsaddress": {(*bool)(nil)}, + "existsaddressv1": {(*bool)(nil)}, + "existsaddressv2": {(*bool)(nil)}, + // ... +} +``` + + + +### 5. Versioned Enpoint Deprecation And Removal Scheme + +Only two versioned endpoints will be maintained per JSON-RPC endpoint. The +introduction of a versioned endpoint immediately marks the oldest maintained +endpoint for deprecation. This is expected to be done by the contributor in the +pull request introducing the new versioned endpoint. Using `existsaddress` as +an example, with the introduction of the versioned endpoint `existsaddressv2` +`existsaddressv1` will be marked as deprecated and eventually removed when +the next versioned endpoint `existsaddressv3` is introduced. + + + +### 6. Versioned Endpoint JSON-RPC Unit Testing + +All versioned JSON-RPC handlers are required to have their own set of unit tests. +Contributors versioning these endpoints will be required to include unit tests +in the pull request introducing the versioned JSON-RPC endpoint. Usually the +existing unit tests for the endpoint being versioned will serve as the base +for the new set of tests, these units will be copied and modified as needed. + +Using `existsaddress` as an example, a new unit test for the versioned endpoint +`existsaddressv2` will be added to the rpcserver test set. Note that the unit +test for the `existaddress` will remain in the rpcserver unit test set. It will +only be removed when the deprecated endpoint is being removed, when +`existsaddressv3` is introduced. + +```go +// handleExistsAddressV2 JSON-RPC unit test +func TestHandleExistsAddressV2(t *testing.T) { + t.Parallel() + + validAddr := "DcurAwesomeAddressmqDctW5wJCW1Cn2MF" + validPaymentScript := "a914f59833f104faa3c7fd0c7dc1e3967fe77a9c152387" + wrongPaymentScript := "a114f59833f104faa3c7fd0c7dc1e3967fe77a9c152354" + testRPCServerHandler(t, []rpcTest{{ + name: "handleExistsAddressV2: ok, index is synced", + handler: handleExistsAddressV2, + cmd: &types.ExistsAddressV2Cmd{ + Address: validAddr, + PaymentScript: validPaymentScript, + }, + result: false, + }, { + name: "handleExistsAddressV2: wrong payment script, index is synced", + handler: handleExistsAddressV2, + cmd: &types.ExistsAddressV2Cmd{ + Address: validAddr, + PaymentScript: wrongPaymentScript, + }, + result: false, + } + // ... + }) +} + +// ... +``` + + + +### 7. Versioned JSON-RPC Documentation + +Documentation for the newly versioned JSON-RPC endpoint will be expected in the +pull request introducing the versioned endpoint. Documentation for versioned +endpoints are expected to be grouped, contributors should add the new +documentation entry after the old version's documentation. The old version's +handler documentation should also indicate it has been deprecated in favour of +the new version and scheduled to the removed on the introduction of the next +endpoint version. + +Using `existsaddress` as an example, with the introduction of `existsaddressv2` +the JSON-RPC API documentation will be updated to include documentation for +`existsaddressv2`. + +``` + ... + + ====existsaddressv2==== + {| + !Method + |existsaddressv2 + |- + !Parameters + | + # address: (string, required) The address to check. + # paymentscript: (string, required) The hex encoded payment script to check. + |- + !Description + |Returns the existence of the provided address with the provided payment script. + |- + !Returns + |boolean + |- + !Example Return + |true + |} + + ---- + + ... + + |[[#existsaddress|existsaddress]] + |Y + |Returns the existence of the provided address. + |- + |[[#existsaddressv2|existsaddressv2]] + |Y + |Returns the existence of the provided address with the provided payment script. + |- + ... +``` + +``` go + // RPC Server Help Documentation + + // ... + // ExistsAddressCmd help. + "existsaddress--synopsis": "Test for the existence of the provided address", + "existsaddress-address": "The address to check", + "existsaddress--result0": "Bool showing if address exists or not", + + // ExistsAddressV2Cmd help. + "existsaddressv2--synopsis": "Test for the existence of the provided address and whether it has the provided payment script", + "existsaddressv2-address": "The address to check", + "existsaddressv2-paymentscript": "The address payment script to check", + "existsaddressv2--result0": "Bool showing if address exists or not", + // ... +``` + +With the outlined schemes adhered to, the versioned endpoint should be ready +for use once `dcrctl` is rebuilt once the `rpc/jsonrpc/types` dependency is +replaced with the updated local changes. From 2f22fe413dd5b1b457b850d557d99fbdf90ddc1d Mon Sep 17 00:00:00 2001 From: Donald Adu-Poku Date: Fri, 18 Mar 2022 20:56:35 +0000 Subject: [PATCH 2/3] resolve review issues (1 of x). --- docs/rpc_api_versioning.md | 72 ++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/docs/rpc_api_versioning.md b/docs/rpc_api_versioning.md index e676ee1c79..e538737fbe 100644 --- a/docs/rpc_api_versioning.md +++ b/docs/rpc_api_versioning.md @@ -1,23 +1,22 @@ ### Table of Contents -1. [Per-RPC Versioning](#PerRPCVersioning)
+1. [Per-Method Versioning](#PerMethodVersioning)
2. [Versioning Scheme](#VersioningScheme)
-3. [Versioned Endpoint Accessibilty Scheme](#AccessibiltyScheme)
-4. [Versioned Enpoint Example](#Example)
-5. [Versioned Enpoint Deprecation And Removal Scheme](#DeprecationAndRemovalScheme)
+3. [Versioned Endpoint Accessibility Scheme](#AccessibilityScheme)
+4. [Versioned Endpoint Example](#Example)
+5. [Versioned Endpoint Deprecation And Removal Scheme](#DeprecationAndRemovalScheme)
6. [Versioned Endpoint JSON-RPC Unit Testing](#UnitTesting)
-7. [Versioned JSON-RPC Documentation](#Documentation)
+7. [Versioned Endpoint Documentation](#Documentation)
-
+ -### 1. Per-RPC Versioning +### 1. Per-Method Versioning -To streamline API access in the process of transitioning between release -versions, decred's RPC endpoints will be versioned individually for each -JSON-RPC endpoint. -Per-RPC versioning provides the flexibility needed to isolate the development -of JSON-RPC endpoints. This allows endpoints to evolve individually and -exclusively of each other. +To prevent API compatibility issues when transitioning between release +versions, dcrd's JSON-RPC API methods will each have an individually versioned +endpoint. Per-method endpoint versioning provides the flexibility needed to +isolate the development of each method, and allows each method to evolve +individually and exclusively from each other. @@ -34,21 +33,23 @@ handler name. `handleExistsAddressV2`. - JSON-RPC method names will have the lowercase version appended to the - method name. For example the `V2` version of method name `existsaddress` - becomes `existsaddressv2`. + method name. For example the `V2` version of method `existsaddress` becomes + `existsaddressv2`. - + -### 3. Versioned Endpoint Accessibilty Scheme +### 3. Versioned Endpoint Accessibility Scheme + +A versioned method endpoint infers there is a current version and an older +version of the endpoint. To ensure a smooth transition between method versions +for consumers, both versions should be accessible. The current and older +versions should be explicitly accessible via their respective method names +while the most recent version becomes the default version. -A versioned JSON-RPC endpoint infers there is a current version and an older -version of the endpoint. To ensure a smooth transition between JSON-RPC -versions for consumers, both versions should be accessible. The current and -older versions should be explicitly accessible via their respective method -names while the most recent version becomes the version defaulted to. Using `existsaddress` as an example: + - The `V2` version of the endpoint, which is the current version, should be - explictly accessible via `existsaddressv2`. + explicitly accessible via `existsaddressv2`. - The `V1` version of the endpoint, which is now the older version, should be explicitly accessible via `existsaddressv1`. @@ -60,7 +61,7 @@ Using `existsaddress` as an example: -### 4. Versioned Enpoint Example +### 4. Versioned Endpoint Example Taking into consideration the accessibility scheme outlined above, implementing the `V2` version of the `existaddress` JSON-RPC endpoint will comprise of the @@ -138,21 +139,22 @@ var rpcResultTypes = map[types.Method][]interface{}{ -### 5. Versioned Enpoint Deprecation And Removal Scheme +### 5. Versioned Endpoint Deprecation And Removal Scheme -Only two versioned endpoints will be maintained per JSON-RPC endpoint. The -introduction of a versioned endpoint immediately marks the oldest maintained -endpoint for deprecation. This is expected to be done by the contributor in the -pull request introducing the new versioned endpoint. Using `existsaddress` as -an example, with the introduction of the versioned endpoint `existsaddressv2` -`existsaddressv1` will be marked as deprecated and eventually removed when -the next versioned endpoint `existsaddressv3` is introduced. +Each JSON-RPC method will only have two maintained endpoint versions. The +introduction of a new endpoint version should immediately indicate that the +oldest maintained endpoint will be deprecated. This is expected to be done +by the contributor in the pull request introducing the new versioned endpoint. +Using `existsaddress` as an example, with the introduction of the versioned +endpoint `existsaddressv2` `existsaddressv1` will be marked as deprecated and +eventually removed when the next versioned endpoint `existsaddressv3` is +introduced. ### 6. Versioned Endpoint JSON-RPC Unit Testing -All versioned JSON-RPC handlers are required to have their own set of unit tests. +All versioned method handlers are required to have their own set of unit tests. Contributors versioning these endpoints will be required to include unit tests in the pull request introducing the versioned JSON-RPC endpoint. Usually the existing unit tests for the endpoint being versioned will serve as the base @@ -198,9 +200,9 @@ func TestHandleExistsAddressV2(t *testing.T) { -### 7. Versioned JSON-RPC Documentation +### 7. Versioned Endpoint Documentation -Documentation for the newly versioned JSON-RPC endpoint will be expected in the +Documentation for the newly versioned endpoint will be expected in the pull request introducing the versioned endpoint. Documentation for versioned endpoints are expected to be grouped, contributors should add the new documentation entry after the old version's documentation. The old version's From 510259f4e02c6b254a54a9db77a20bbbb2fc03b7 Mon Sep 17 00:00:00 2001 From: Donald Adu-Poku Date: Thu, 24 Mar 2022 23:07:53 +0000 Subject: [PATCH 3/3] docs: add websocket versioning. This adds a section for versioning websockets according to the per-method versioning scheme. This also updates the documentation to keep the default method name resolving to the older version of a versioned endpoint to avoid breaking consumers on an upgrade. --- docs/rpc_api_versioning.md | 99 +++++++++++++++++++++++++++++++++++--- 1 file changed, 91 insertions(+), 8 deletions(-) diff --git a/docs/rpc_api_versioning.md b/docs/rpc_api_versioning.md index e538737fbe..ad943996ac 100644 --- a/docs/rpc_api_versioning.md +++ b/docs/rpc_api_versioning.md @@ -6,6 +6,7 @@ 5. [Versioned Endpoint Deprecation And Removal Scheme](#DeprecationAndRemovalScheme)
6. [Versioned Endpoint JSON-RPC Unit Testing](#UnitTesting)
7. [Versioned Endpoint Documentation](#Documentation)
+8. [Websocket Endpoints And Example](#Websockets)
@@ -54,8 +55,8 @@ Using `existsaddress` as an example: - The `V1` version of the endpoint, which is now the older version, should be explicitly accessible via `existsaddressv1`. - - The default method name, `existsaddress` will resolve to the current version - of the endpoint which is `existsaddressv2` in this case. + - The default method name, `existsaddress` will resolve to the older version + of the endpoint which is `existsaddress` in this case. This scheme will be enforced via the JSON-RPC type and handler registrations. @@ -68,7 +69,7 @@ the `V2` version of the `existaddress` JSON-RPC endpoint will comprise of the following components: ```go - // JSON-RPC versioned endpoint command definition. + // JSON-RPC versioned endpoint command definition. // ExistsAddressV2Cmd defines the existsaddressv2 JSON-RPC command. type ExistsAddressV2Cmd struct { @@ -79,7 +80,7 @@ following components: ``` ```go // ExistsAddress JSON-RPC type registrations. - dcrjson.MustRegister(Method("existsaddress"), (*ExistsAddressV2Cmd)(nil), flags) + dcrjson.MustRegister(Method("existsaddress"), (*ExistsAddressCmd)(nil), flags) dcrjson.MustRegister(Method("existsaddressv1"), (*ExistsAddressCmd)(nil), flags) dcrjson.MustRegister(Method("existsaddressv2"), (*ExistsAddressV2Cmd)(nil), flags) // ... @@ -119,7 +120,7 @@ following components: // ExistsAddress JSON-RPC handler registrations. var rpcHandlersBeforeInit = map[types.Method]commandHandler{ // ... - "existsaddress": handleExistsAddressV2, + "existsaddress": handleExistsAddress, "existsaddressv1": handleExistsAddress, "existsaddressv2": handleExistsAddressV2, // ... @@ -269,6 +270,88 @@ the JSON-RPC API documentation will be updated to include documentation for // ... ``` -With the outlined schemes adhered to, the versioned endpoint should be ready -for use once `dcrctl` is rebuilt once the `rpc/jsonrpc/types` dependency is -replaced with the updated local changes. + + +### 8. Websocket Endpoints & Example +Websocket endpoints will be versioned using the accessibility scheme outlined +above. Using `rescan` websocket endpoint and the introduction of `rescanv2` +as an example: + +```go +// Rescan websocket handler registrations. +var wsHandlersBeforeInit = map[types.Method]wsCommandHandler{ + // ... + "rescan": handleRescan, + "rescanv1": handleRescan, + "rescanv2": handleRescanV2, + // ... +} +``` + +```go +// Versioned JSON-RPC endpoint command definition. + +// RescanCmd defines the rescan JSON-RPC command. +type RescanCmd struct { + BlockHashes []string +} + +// RescanV2Cmd defines the rescanv2 JSON-RPC command. +type RescanV2Cmd struct { + From string + To string +} +``` + +```go +// Rescan JSON-RPC result type registrations. +var rpcResultTypes = map[types.Method][]interface{}{ + // Websocket commands. + + // ... + "rescan": nil, + "rescanv1": nil, + "rescanv2": nil, + // ... +} +``` + +```go +// Rescan Websocket type registrations. +func init() { + // The commands in this file are only usable by websockets. + flags := dcrjson.UFWebsocketOnly + + //... + dcrjson.MustRegister(Method("rescan"), (*RescanCmd)(nil), flags) + dcrjson.MustRegister(Method("rescanv1"), (*RescanCmd)(nil), flags) + dcrjson.MustRegister(Method("rescanv2"), (*RescanV2Cmd)(nil), flags) + //... +} +``` + +```go +// handleRescan implements the rescan command extension for websocket +// connections. +func handleRescan(wsc *wsClient, icmd interface{}) (interface{}, error) { + cmd, ok := icmd.(*types.RescanCmd) + if !ok { + return nil, dcrjson.ErrRPCInternal + } + // ... +} + +// handleRescanV2 implements the rescanv2 command extension for websocket +// connections. +func handleRescanV2(wsc *wsClient, icmd interface{}) (interface{}, error) { + cmd, ok := icmd.(*types.RescanV2Cmd) + if !ok { + return nil, dcrjson.ErrRPCInternal + } + // ... +} +``` + +Once all dcrd JSON-RPC API methods are updated using the schema outlined above, +the versioned endpoints will be ready for use. However, dcrctl and other +consumers will need to update their `rpc/jsonrpc/types` dependencies and logic.