From 34ff80bedcd5c2dd423d4d11fb2cc734ba7a0ecf Mon Sep 17 00:00:00 2001 From: omahs <73983677+omahs@users.noreply.github.com> Date: Thu, 12 Jan 2023 11:41:33 +0100 Subject: [PATCH 1/2] Fix: typos Fix: typos --- raiden-ts/docs-source/SDK-Development.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/raiden-ts/docs-source/SDK-Development.md b/raiden-ts/docs-source/SDK-Development.md index 529d970a6e..8d567b6488 100644 --- a/raiden-ts/docs-source/SDK-Development.md +++ b/raiden-ts/docs-source/SDK-Development.md @@ -47,7 +47,7 @@ Below is a detailed explanation of the SDK architecture as well as things to kee ## Architecture -In this section we will dive into the the internal machinery of the SDK and outline how RxJS, Redux and Epics work together. +In this section we will dive into the internal machinery of the SDK and outline how RxJS, Redux and Epics work together. ### Vertical (Stack) @@ -186,7 +186,7 @@ We use `io-ts` to validate unsafe data and provide strong guarantees, define our TypeScript branded types (aka. poor man's nominal typing) helps developers provide hints/refinements about specific types which can be compared to full inheritance systems in OOP paradigms. It consists basically of making a branded type `TB` as the intersection of base type `A` with some brand `B`, which makes the branded type more specific. So, `type TB = number & { brand }` is equivalent in OO of making an inherited/child class `TB` extending `number`. You can still pass `TB` where `number` is expected (and it's a child of number), but you can't pass a simple `number` where `TB` is expected unless you type-cast or decode/validate it as such. -On TypeScript, all this normally happens only at compile-time (the brand usually is just an interface with a `unique symbol`), having no impact at runtime, when the variable would effectivelly be a simple `number`. `io-ts` allows us to have codecs which also validate if a parent type matches the expectations to be considered a branded/child type, allowing us to also have specific type safety beyond just validating if some data is a `string` or not. +On TypeScript, all this normally happens only at compile-time (the brand usually is just an interface with a `unique symbol`), having no impact at runtime, when the variable would effectively be a simple `number`. `io-ts` allows us to have codecs which also validate if a parent type matches the expectations to be considered a branded/child type, allowing us to also have specific type safety beyond just validating if some data is a `string` or not. For example, in our [types](https://github.com/raiden-network/light-client/tree/master/raiden-ts/src/utils/types.ts), we declare the type `Address`, which is (actually) a `string`, which happens to be validated to also be a `HexString` of size `20 bytes`, and which also happens to be in the checksummed format, without ceasing to be a `string`! @@ -249,13 +249,13 @@ Epics are just functions, which receive 3 parameters: `action$`, `state$` and `d ### Hints, tips & tricks and pitfalls when developing epics - Be careful to not complete the output observable if the system is still running, or its function will be lost, as subscriptions aren't restarted. -- Any unhandled exception (which shouldn't happen in normal operation) will cause a `raidenShutdown` action which in turn triggers completion of the inputs (`action$` and `state$`). The individual epics then have `httpTimeout` to detect this completion and gracefully complete on their own once they finish their latest async tasks/teardown. During this time some output action may still go through and change state, but this this will only be received by epics later in the subscription queue (as earlier epics should already have completed, i.e. a serial completion mechanism signaled by completion of the input observables). After this timeout, if some epic didn't complete, they're logged and then unsubscribed. Only after that the database is flushed and closed. +- Any unhandled exception (which shouldn't happen in normal operation) will cause a `raidenShutdown` action which in turn triggers completion of the inputs (`action$` and `state$`). The individual epics then have `httpTimeout` to detect this completion and gracefully complete on their own once they finish their latest async tasks/teardown. During this time some output action may still go through and change state, but this will only be received by epics later in the subscription queue (as earlier epics should already have completed, i.e. a serial completion mechanism signaled by completion of the input observables). After this timeout, if some epic didn't complete, they're logged and then unsubscribed. Only after that the database is flushed and closed. - Notice that catching the error in the first-level operator pipeline in an epic may prevent sdk's shutdown, but unless you're returning a long-lived/useful epic inside `catchError`, the main observable will already have completed/errored here, and whatever is above it will be noop on new/further actions; if you want to catch and handle an action, make sure to handle this action inside a `*Map` operator, and `catchError` by the end of it before returning values back to your top-level pipe. - If an epic acts directly (like a map) on `action$`, take care to filter early on the specific action you listen to and output a different action, or else your action output will feedback on your epic and cause an infinite loop. Same if you depend on a `state$` change and your output action causes the same state change that just went through. - A common epic pattern: `=> action$.pipe(filter(isActionOf(action)), withLatestFrom(state$), map((action, state) => ...))` - Never subscribe explicitly inside an epic if not strictly necessary; maps and pipes help into getting a proper _action pipeline_ where a single subscription is used for all epics, making the system more deterministic, declarative and performant. - Careful if you use `withLatestFrom` with `action$` or `state$` inside a `mergeMap`, `concatMap`, `exhaustMap`, etc, as the inner (returned) observable is created only when the outer value flows through and the callback of these operators is called. The use of `deps.latest$`, which is holding the current/latest emition of several reactive values (a `ReplaySubject(1)`), is safe in `withLatestFrom`. -- This example showcases the problem mentioed above: `withLatestFrom` only starts "seeing" values of the `input$` after it's created **and** subscribed, and will discard any source value while the `input$` didn't fire at least once, meaning it can be a silent source of bugs when used inside these mapping operators. e.g. of problematic logic: +- This example showcases the problem mentioned above: `withLatestFrom` only starts "seeing" values of the `input$` after it's created **and** subscribed, and will discard any source value while the `input$` didn't fire at least once, meaning it can be a silent source of bugs when used inside these mapping operators. e.g. of problematic logic: ```js action.pipe( @@ -271,7 +271,7 @@ action.pipe( - In the spirit of tips above, you should ALWAYS know when your (specially inner) observables are **created**, **subscribed** and **unsubscribed**. - On the outer/top level observable (the one returned by the epic), the creation and subscription is performed at the moment the SDK is instantiated, and unsubscription happens if the observable completes, errors or at SDK stop/shutdown. - - `mergeMap` creates the inner observable when the a value goes through, and subscribes to it immediatelly. completing the inner observable won't complete the outer, but unhandled errors do error the outer observable. + - `mergeMap` creates the inner observable when the a value goes through, and subscribes to it immediately. completing the inner observable won't complete the outer, but unhandled errors do error the outer observable. - `concatMap` **creates** the inner observable also at the exact moment a value goes through, but its subscription is only made when the previous observable completes. Keep an eye if the values you depended on at creation time are still valid/up-to-date at subscription time. Use `defer` if needed. - `exhaustMap` is like `concatMap`, but instead of queueing every output observable serially, it **ignores** the value if the previous subscription is still on, and outputs the next inner observable only on next value going through after previous observable completed. - Obvious but it's worth to mention: if you handle external/unsafe data, use proper data validation through `io-ts`. Distrust everything! @@ -290,7 +290,7 @@ Be careful when adding new properties to this object, as often this is something ## Testing -The SDK tests are located at the [raiden-ts/tests](https://github.com/raiden-network/light-client/tree/master/raiden-ts/tests) subfolder. The testing framework used is [jest](http://jestjs.io), and the complete suite can be run with `yarn test` command in the SDK root folder. This will run both [unit](https://github.com/raiden-network/light-client/tree/master/raiden-ts/tests/unit) and [integration](https://github.com/raiden-network/light-client/tree/master/raiden-ts/tests/integration) tests (files ending with `.spec.ts`), and collect coverage in the `raiden/.coverage` folder. Keeping an eye on `raiden/.coverage/lcov-report/index.html` during test writting can be a good guide to writing them, although just covering the lines isn't enough, and some thought must be put into imagining possible scenarios, invariants, critical and edge states, and testing them throughout the respective tests. +The SDK tests are located at the [raiden-ts/tests](https://github.com/raiden-network/light-client/tree/master/raiden-ts/tests) subfolder. The testing framework used is [jest](http://jestjs.io), and the complete suite can be run with `yarn test` command in the SDK root folder. This will run both [unit](https://github.com/raiden-network/light-client/tree/master/raiden-ts/tests/unit) and [integration](https://github.com/raiden-network/light-client/tree/master/raiden-ts/tests/integration) tests (files ending with `.spec.ts`), and collect coverage in the `raiden/.coverage` folder. Keeping an eye on `raiden/.coverage/lcov-report/index.html` during test writing can be a good guide to writing them, although just covering the lines isn't enough, and some thought must be put into imagining possible scenarios, invariants, critical and edge states, and testing them throughout the respective tests. ### Unit tests @@ -301,7 +301,7 @@ We try to split unit tests by kind of tested function. Most of the tested functi ### Integration tests The integration tests combine multiple units of the SDK and test if they work together as intended by their interface definitions. In the center of these tests are the epics which implement the Raiden protocol. They are the natural hub of the SDK where all units come together. Here the units dealing with the blockchain come together with units handling message on the transport layer. -These test must not use any external end but rather to continue using mocks and stubs where needed while continuously remove mocks for 1st party units. +These tests must not use any external end but rather to continue using mocks and stubs where needed while continuously remove mocks for 1st party units. The hardest to unit test are the epics. As they conceive most of the Raiden logic, are async and may depend on multiple parts working together (e.g. an output action changing input state). In the past we called epics directly, but the setup turned out to be to cumbersome to use. Therefore now the tests set up the whole state machine and subscribe to all epics at once. Inputs and requests are injected in the store, and outputs are collected in an output array per client. This new pattern has shown to be orders of magnitude easier to write and more correct to set up, while as fast. Individual utils and helpers are still unit-tested independently whenever possible. @@ -327,7 +327,7 @@ The SDK being a TypeScript/JavaScript library, debugging it can use a lot of the 4. Open Dev Tools (usually, shortcut `Ctrl+Shift+I`). You can already see the `redux-logger` output in `Console` tab, which can be very useful to see the live actions going through the Redux state machine, as you navigate through the dApp. 5. Install Vue DevTools for [Firefox](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/) or [Chrome](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd): now, you can go to the `Vue` tab, click on the `app` component (which will be bound to the console's `$vm0` var), and access the SDK instance from with `raiden = $vm0.$raiden.raiden`. Now, the variable `raiden` is the SDK instance, and one can use it to access and call properties and methods from the API. `await` or `.then()` can be useful when dealing with the `async` methods. 6. State can be inspected from the app's `indexedDB`, in the `Storage` or `Application` tabs. -7. While most epics variables and dependencies aren't persisted in the Raiden instance, but only used contextually in its epic, one can log them in any step and access it by right-clicking in the logged out object. If the need arises, the [RaidenEpicDeps](https://github.com/raiden-network/light-client/blob/84afc0939d267e99636147e8241d7bda4f55cbb1/raiden/src/types.ts#L32) object can be saved in a `Raiden` property in the constructor and acessed from the console as well, giving full access to the instance details and variables. +7. While most epics variables and dependencies aren't persisted in the Raiden instance, but only used contextually in its epic, one can log them in any step and access it by right-clicking in the logged out object. If the need arises, the [RaidenEpicDeps](https://github.com/raiden-network/light-client/blob/84afc0939d267e99636147e8241d7bda4f55cbb1/raiden/src/types.ts#L32) object can be saved in a `Raiden` property in the constructor and accessed from the console as well, giving full access to the instance details and variables. 8. Looking at the in-browser sourcecode can be tricky, as even with proper maps, it'll be the `webpack`ed version of the `tsc`ed sourcecode. Comparing the sources in `webpack-internal` to the actual `.ts` sourcecode can give good hints on what's failing. 9. The Redux store can be visualized and even be modified with the Redux DevTools Extension. Install it for [Firefox](https://addons.mozilla.org/en-US/firefox/addon/reduxdevtools/) or [Chrome](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd) and go to the `Redux` tab in your Dev Tools. From a7c1dd7b3b9a739d498a616a1feed34978aa4cfe Mon Sep 17 00:00:00 2001 From: omahs <73983677+omahs@users.noreply.github.com> Date: Thu, 12 Jan 2023 11:43:11 +0100 Subject: [PATCH 2/2] Fix: typo Fix: typo --- raiden-ts/docs-source/account.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raiden-ts/docs-source/account.md b/raiden-ts/docs-source/account.md index bb618527e0..46ebbf48f5 100644 --- a/raiden-ts/docs-source/account.md +++ b/raiden-ts/docs-source/account.md @@ -24,7 +24,7 @@ ETH is required for withdrawing either of the following tokens from a Raiden Acc - _Service tokens_ (like _SVT_ on Goerli or _RDN_ on Mainnet) or - tokens that have been used to open any channels, i.e. (_TTT_ or any other TestToken on testnets or _DAI_ and _WETH_ on Mainnet). -You can transfer ETH in either direction, from your Main Account to you Raiden Account or vice versa. +You can transfer ETH in either direction, from your Main Account to your Raiden Account or vice versa. ![transfer-eth-account](https://user-images.githubusercontent.com/15123108/102342273-0596bb80-3fbf-11eb-952c-c65b61a9d47a.png 'Transfer ETH Account')