From 188a6268837cc074c51458f8dbb0918a49729569 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vitor=20de=20Lima=20Matos?= Date: Fri, 5 Mar 2021 16:23:10 -0300 Subject: [PATCH 01/35] next: sdk: send service messages through toDevice --- raiden-ts/src/transport/epics/messages.ts | 107 ++++++++++------------ 1 file changed, 46 insertions(+), 61 deletions(-) diff --git a/raiden-ts/src/transport/epics/messages.ts b/raiden-ts/src/transport/epics/messages.ts index f7e51aae4f..b10539bd4b 100644 --- a/raiden-ts/src/transport/epics/messages.ts +++ b/raiden-ts/src/transport/epics/messages.ts @@ -1,4 +1,5 @@ -import type { MatrixClient, MatrixEvent } from 'matrix-js-sdk'; +import isEmpty from 'lodash/isEmpty'; +import type { MatrixEvent } from 'matrix-js-sdk'; import type { Observable } from 'rxjs'; import { asapScheduler, @@ -15,8 +16,10 @@ import { import { catchError, concatMap, + delayWhen, + endWith, filter, - groupBy, + ignoreElements, map, mergeMap, pluck, @@ -28,13 +31,12 @@ import { } from 'rxjs/operators'; import type { RaidenAction } from '../../actions'; -import type { RaidenConfig } from '../../config'; import { intervalFromConfig } from '../../config'; import { messageReceived, messageSend, messageServiceSend } from '../../messages/actions'; import type { Delivered, Message } from '../../messages/types'; import { MessageType, Processed, SecretRequest, SecretReveal } from '../../messages/types'; import { encodeJsonMessage, isMessageReceivedOfType, signMessage } from '../../messages/utils'; -import { Service } from '../../services/types'; +import { ServiceDeviceId } from '../../services/types'; import type { RaidenState } from '../../state'; import type { RaidenEpicDeps } from '../../types'; import { isActionOf } from '../../utils/actions'; @@ -43,7 +45,6 @@ import { LruCache } from '../../utils/lru'; import { getServerName } from '../../utils/matrix'; import { completeWith, - concatBuffer, dispatchRequestAndGetResponse, mergeWith, pluckDistinct, @@ -52,7 +53,7 @@ import { import { isntNil, Signed } from '../../utils/types'; import { matrixPresence } from '../actions'; import { getAddressFromUserId, getNoDeliveryPeers } from '../utils'; -import { getRoom$, globalRoomNames, parseMessage } from './helpers'; +import { parseMessage } from './helpers'; function getMessageBody(message: string | Signed): string { return typeof message === 'string' ? message : encodeJsonMessage(message); @@ -254,76 +255,60 @@ export function matrixMessageSendEpic( ); } -function sendGlobalMessages( - actions: readonly messageServiceSend.request[], - matrix: MatrixClient, - config: RaidenConfig, - { config$ }: Pick, -): Observable { - const servicesToRoomName = { - [Service.PFS]: config.pfsRoom, - [Service.MS]: config.monitoringRoom, - }; - const roomName = servicesToRoomName[actions[0].meta.service]; - const globalRooms = globalRoomNames(config); - assert(roomName && globalRooms.includes(roomName), [ - 'messageServiceSend for unknown global room', - { roomName, globalRooms: globalRooms.join(',') }, - ]); - const serverName = getServerName(matrix.getHomeserverUrl()); - const roomAlias = `#${roomName}:${serverName}`; - // batch action messages in a single text body - const body = actions.map((action) => getMessageBody(action.payload.message)).join('\n'); - const start = Date.now(); - let retries = -1; - return getRoom$(matrix, roomAlias).pipe( - // send message! - mergeMap(async (room) => { - retries++; - await matrix.sendEvent(room.roomId, 'm.room.message', { body, msgtype: textMsgType }, ''); - return { via: room.roomId, tookMs: Date.now() - start, retries }; +function sendServiceMessage( + request: messageServiceSend.request, + { matrix$, config$, latest$ }: Pick, +) { + return matrix$.pipe( + withLatestFrom(latest$), + mergeMap(([matrix, { state }]) => { + assert(!isEmpty(state.services), 'no services to messageServiceSend to'); + const serverName = getServerName(matrix.getHomeserverUrl()); + const userIds = Object.keys(state.services).map( + (service) => `@${service.toLowerCase()}:${serverName}`, + ); + // batch action messages in a single text body + const content = { + msgtype: 'm.text', + body: getMessageBody(request.payload.message), + }; + const payload = Object.fromEntries( + userIds.map((uid) => [uid, { [ServiceDeviceId[request.meta.service]]: content }]), + ); + const start = Date.now(); + let retries = -1; + return defer(async () => { + retries++; + await matrix.sendToDevice('m.room.message', payload); // send message! + return messageServiceSend.success( + { via: userIds, tookMs: Date.now() - start, retries }, + request.meta, + ); + }).pipe(retryWhile(intervalFromConfig(config$), { maxRetries: 3, onErrors: networkErrors })); }), - retryWhile(intervalFromConfig(config$), { maxRetries: 3, onErrors: networkErrors }), + catchError((err) => of(messageServiceSend.failure(err, request.meta))), ); } /** - * Handles a [[messageServiceSend.request]] action and send one-shot message to a global room + * Handles a [[messageServiceSend.request]] action and send one-shot message to a service * * @param action$ - Observable of messageServiceSend actions * @param state$ - Observable of RaidenStates - * @param deps - RaidenEpicDeps members - * @param deps.log - Logger instance - * @param deps.matrix$ - MatrixClient async subject - * @param deps.config$ - Config observable - * @returns Empty observable (whole side-effect on matrix instance) + * @param deps - Epics dependencies + * @returns Observable of messageServiceSend.success|failure actions */ -export function matrixMessageGlobalSendEpic( +export function matrixMessageServiceSendEpic( action$: Observable, {}: Observable, deps: RaidenEpicDeps, ): Observable { - const { matrix$, config$ } = deps; return action$.pipe( filter(isActionOf(messageServiceSend.request)), - groupBy((action) => action.meta.service), - mergeMap((grouped$) => - grouped$.pipe( - concatBuffer((actions) => { - return matrix$.pipe( - withLatestFrom(config$), - mergeMap(([matrix, config]) => sendGlobalMessages(actions, matrix, config, deps)), - mergeMap((payload) => - from(actions.map((action) => messageServiceSend.success(payload, action.meta))), - ), - catchError((err) => - from(actions.map((action) => messageServiceSend.failure(err, action.meta))), - ), - completeWith(action$, 10), - ); - }, 20), - ), - ), + // wait until init$ is completed before handling requests, so state.services is populated + delayWhen(() => deps.init$.pipe(ignoreElements(), endWith(true))), + mergeMap((request) => sendServiceMessage(request, deps), 5), + completeWith(action$, 10), ); } From df6df0f8973d0e44f835322b5390939bc4eaec6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vitor=20de=20Lima=20Matos?= Date: Fri, 5 Mar 2021 16:23:27 -0300 Subject: [PATCH 02/35] next: sdk: don't join PFS & MS global rooms --- raiden-ts/src/config.ts | 9 --------- raiden-ts/src/transport/epics/helpers.ts | 2 +- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/raiden-ts/src/config.ts b/raiden-ts/src/config.ts index 0464c8f736..3cbbd52b8a 100644 --- a/raiden-ts/src/config.ts +++ b/raiden-ts/src/config.ts @@ -35,11 +35,6 @@ const RTCIceServer = t.type({ urls: t.union([t.string, t.array(t.string)]) }); * - pfsMode - One of 'disabled' (disables PFS usage and notifications), 'auto' (notifies all of * registered and additionalServices, picks cheapest for transfers without explicit pfs), * or 'onlyAdditional' (notifies all, but pick first responding from additionalServices only). - * - pfsRoom - PFS Room to auto-join and send PFSCapacityUpdate to, use null to disable - * - monitoringRoom - MS global room to auto-join and send RequestMonitoring messages; - * use null to disable - * - pfs - Array of Path Finding Service Addresses (require PFS to be registered) or URLs. - * Set to false to disable, or true to enable automatic fetching from ServiceRegistry. * - pfsSafetyMargin - Safety margin to be added to fees received from PFS. Either a fee * multiplier, or a [fee, amount] pair ofmultipliers. Use `1.1` to add a 10% over estimated fee * margin, or `[0.03, 0.0005]` to add a 3% over fee plus 0.05% over amount. @@ -78,8 +73,6 @@ export const RaidenConfig = t.readonly( discoveryRoom: t.union([t.string, t.null]), additionalServices: t.readonlyArray(t.union([Address, t.string])), pfsMode: PfsModeC, - pfsRoom: t.union([t.string, t.null]), - monitoringRoom: t.union([t.string, t.null]), pfsSafetyMargin: t.union([t.number, t.tuple([t.number, t.number])]), pfsMaxPaths: t.number, pfsMaxFee: UInt(32), @@ -156,8 +149,6 @@ export function makeDefaultConfig( discoveryRoom: `raiden_${networkName}_discovery`, additionalServices: [], pfsMode: PfsMode.auto, - pfsRoom: `raiden_${networkName}_path_finding`, - monitoringRoom: `raiden_${networkName}_monitoring`, pfsSafetyMargin: 1.0, // multiplier pfsMaxPaths: 3, pfsMaxFee: parseEther('0.05') as UInt<32>, // in SVT/RDN, 18 decimals diff --git a/raiden-ts/src/transport/epics/helpers.ts b/raiden-ts/src/transport/epics/helpers.ts index e15409828d..aa279a2b06 100644 --- a/raiden-ts/src/transport/epics/helpers.ts +++ b/raiden-ts/src/transport/epics/helpers.ts @@ -19,7 +19,7 @@ import { isntNil } from '../../utils/types'; * @returns Array of room names */ export function globalRoomNames(config: RaidenConfig) { - return [config.discoveryRoom, config.pfsRoom, config.monitoringRoom].filter(isntNil); + return [config.discoveryRoom].filter(isntNil); } /** From cb9832f5af9445988ce48985bc19b3330c52b5c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vitor=20de=20Lima=20Matos?= Date: Fri, 5 Mar 2021 17:33:26 -0300 Subject: [PATCH 03/35] next: sdk: add tests for service messages toDevice --- raiden-ts/tests/integration/transport.spec.ts | 57 +++++++++++-------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/raiden-ts/tests/integration/transport.spec.ts b/raiden-ts/tests/integration/transport.spec.ts index b25edc4b52..a63bcc7c2e 100644 --- a/raiden-ts/tests/integration/transport.spec.ts +++ b/raiden-ts/tests/integration/transport.spec.ts @@ -12,15 +12,17 @@ import { messageReceived, messageSend, messageServiceSend } from '@/messages/act import type { Delivered, Processed } from '@/messages/types'; import { MessageType } from '@/messages/types'; import { encodeJsonMessage, signMessage } from '@/messages/utils'; -import { Service } from '@/services/types'; +import { servicesValid } from '@/services/actions'; +import { Service, ServiceDeviceId } from '@/services/types'; import { makeMessageId } from '@/transfers/utils'; import { matrixPresence, matrixSetup, rtcChannel } from '@/transport/actions'; import { getSortedAddresses } from '@/transport/utils'; import { ErrorCodes } from '@/utils/error'; +import { getServerName } from '@/utils/matrix'; import type { Address, Signed } from '@/utils/types'; import { isntNil } from '@/utils/types'; -import { sleep } from '../utils'; +import { makeAddress, sleep } from '../utils'; import type { MockedRaiden } from './mocks'; const accessToken = 'access_token'; @@ -898,51 +900,56 @@ describe('deliveredEpic', () => { }); }); -test('matrixMessageGlobalSendEpic', async () => { +test('matrixMessageServiceSendEpic', async () => { expect.assertions(6); const raiden = await makeRaiden(); const matrix = (await raiden.deps.matrix$.toPromise()) as jest.Mocked; + const service = makeAddress(); + const serviceUid = `@${service.toLowerCase()}:${getServerName(matrix.getHomeserverUrl())}`; const msgId = '123'; const message = await signMessage(raiden.deps.signer, processed), text = encodeJsonMessage(message); - raiden.store.dispatch(messageServiceSend.request({ message }, { service: Service.PFS, msgId })); + const meta = { service: Service.PFS, msgId }; + raiden.store.dispatch(messageServiceSend.request({ message }, meta)); await sleep(2 * raiden.config.pollingInterval); - expect(matrix.sendEvent).toHaveBeenCalledTimes(1); - expect(matrix.sendEvent).toHaveBeenCalledWith( - expect.any(String), - 'm.room.message', - expect.objectContaining({ body: text, msgtype: 'm.text' }), - expect.anything(), - ); + expect(matrix.sendToDevice).not.toHaveBeenCalled(); + expect(raiden.output).not.toContainEqual(messageServiceSend.success(expect.anything(), meta)); expect(raiden.output).toContainEqual( - messageServiceSend.success( - { via: expect.stringMatching(/!.*:/), tookMs: expect.any(Number), retries: 0 }, - { service: Service.PFS, msgId }, + messageServiceSend.failure( + expect.objectContaining({ message: expect.stringContaining('no services') }), + meta, ), ); - // test graceful failure raiden.output.splice(0, raiden.output.length); - matrix.sendEvent.mockClear(); - matrix.sendEvent.mockRejectedValueOnce(Object.assign(new Error('Failed'), { httpStatus: 429 })); + matrix.sendToDevice.mockClear(); + // network errors must be retried + matrix.sendToDevice.mockRejectedValueOnce( + Object.assign(new Error('Failed'), { httpStatus: 429 }), + ); + raiden.store.dispatch(servicesValid({ [service]: Date.now() + 1e8 })); - raiden.store.dispatch(messageServiceSend.request({ message }, { service: Service.PFS, msgId })); + raiden.store.dispatch(messageServiceSend.request({ message }, meta)); await sleep(raiden.config.httpTimeout); - expect(matrix.sendEvent).toHaveBeenCalledTimes(2); - expect(matrix.sendEvent).toHaveBeenCalledWith( - expect.any(String), + expect(matrix.sendToDevice).toHaveBeenCalledTimes(2); + expect(matrix.sendToDevice).toHaveBeenCalledWith( 'm.room.message', - expect.objectContaining({ body: text, msgtype: 'm.text' }), - expect.anything(), + expect.objectContaining({ + [serviceUid]: { [ServiceDeviceId[meta.service]]: { body: text, msgtype: 'm.text' } }, + }), ); expect(raiden.output).toContainEqual( messageServiceSend.success( - { via: expect.stringMatching(/!.*:/), tookMs: expect.any(Number), retries: 1 }, - { service: Service.PFS, msgId }, + { + via: expect.arrayContaining([serviceUid]), + tookMs: expect.any(Number), + retries: 1, + }, + meta, ), ); }); From 3f28608badf3818d515ffe8862b63145410f24da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vitor=20de=20Lima=20Matos?= Date: Mon, 15 Mar 2021 23:57:41 -0300 Subject: [PATCH 04/35] next: sdk: also send global messages to config.additionalServices --- raiden-ts/src/transport/epics/messages.ts | 40 +++++++++++++------ raiden-ts/tests/integration/transport.spec.ts | 26 ++++++++++++ 2 files changed, 54 insertions(+), 12 deletions(-) diff --git a/raiden-ts/src/transport/epics/messages.ts b/raiden-ts/src/transport/epics/messages.ts index b10539bd4b..6982e915f6 100644 --- a/raiden-ts/src/transport/epics/messages.ts +++ b/raiden-ts/src/transport/epics/messages.ts @@ -1,4 +1,5 @@ -import isEmpty from 'lodash/isEmpty'; +import constant from 'lodash/constant'; +import uniq from 'lodash/uniq'; import type { MatrixEvent } from 'matrix-js-sdk'; import type { Observable } from 'rxjs'; import { @@ -27,6 +28,7 @@ import { take, takeUntil, tap, + toArray, withLatestFrom, } from 'rxjs/operators'; @@ -37,6 +39,7 @@ import type { Delivered, Message } from '../../messages/types'; import { MessageType, Processed, SecretRequest, SecretReveal } from '../../messages/types'; import { encodeJsonMessage, isMessageReceivedOfType, signMessage } from '../../messages/utils'; import { ServiceDeviceId } from '../../services/types'; +import { pfsInfoAddress } from '../../services/utils'; import type { RaidenState } from '../../state'; import type { RaidenEpicDeps } from '../../types'; import { isActionOf } from '../../utils/actions'; @@ -50,7 +53,7 @@ import { pluckDistinct, retryWhile, } from '../../utils/rx'; -import { isntNil, Signed } from '../../utils/types'; +import { Address, isntNil, Signed } from '../../utils/types'; import { matrixPresence } from '../actions'; import { getAddressFromUserId, getNoDeliveryPeers } from '../utils'; import { parseMessage } from './helpers'; @@ -255,18 +258,31 @@ export function matrixMessageSendEpic( ); } -function sendServiceMessage( - request: messageServiceSend.request, - { matrix$, config$, latest$ }: Pick, -) { +function sendServiceMessage(request: messageServiceSend.request, deps: RaidenEpicDeps) { + const { matrix$, config$, latest$ } = deps; return matrix$.pipe( - withLatestFrom(latest$), - mergeMap(([matrix, { state }]) => { - assert(!isEmpty(state.services), 'no services to messageServiceSend to'); - const serverName = getServerName(matrix.getHomeserverUrl()); - const userIds = Object.keys(state.services).map( - (service) => `@${service.toLowerCase()}:${serverName}`, + withLatestFrom(latest$, config$), + mergeWith(([, , { additionalServices }]) => + from(additionalServices).pipe( + mergeMap( + (serviceAddrOrUrl) => + Address.is(serviceAddrOrUrl) + ? of(serviceAddrOrUrl) + : defer(async () => pfsInfoAddress(serviceAddrOrUrl, deps)).pipe( + catchError(constant(EMPTY)), + ), + 5, + ), + toArray(), + ), + ), + mergeMap(([[matrix, { state }], additionalServicesAddrs]) => { + const servicesAddrs = uniq( + additionalServicesAddrs.concat(Object.keys(state.services) as Address[]), ); + assert(servicesAddrs.length, 'no services to messageServiceSend to'); + const serverName = getServerName(matrix.getHomeserverUrl()); + const userIds = servicesAddrs.map((service) => `@${service.toLowerCase()}:${serverName}`); // batch action messages in a single text body const content = { msgtype: 'm.text', diff --git a/raiden-ts/tests/integration/transport.spec.ts b/raiden-ts/tests/integration/transport.spec.ts index a63bcc7c2e..d98aa37fbf 100644 --- a/raiden-ts/tests/integration/transport.spec.ts +++ b/raiden-ts/tests/integration/transport.spec.ts @@ -17,6 +17,7 @@ import { Service, ServiceDeviceId } from '@/services/types'; import { makeMessageId } from '@/transfers/utils'; import { matrixPresence, matrixSetup, rtcChannel } from '@/transport/actions'; import { getSortedAddresses } from '@/transport/utils'; +import { jsonStringify } from '@/utils/data'; import { ErrorCodes } from '@/utils/error'; import { getServerName } from '@/utils/matrix'; import type { Address, Signed } from '@/utils/types'; @@ -904,6 +905,29 @@ test('matrixMessageServiceSendEpic', async () => { expect.assertions(6); const raiden = await makeRaiden(); + const additionalServices = raiden.config.additionalServices; + // disable additionalServices for now + raiden.store.dispatch(raidenConfigUpdate({ additionalServices: [] })); + + const pfsAddress = makeAddress(); + const pfsInfoResponse = { + message: 'pfs message', + network_info: { + chain_id: raiden.deps.network.chainId, + token_network_registry_address: raiden.deps.contractsInfo.TokenNetworkRegistry.address, + }, + operator: 'pfs operator', + payment_address: pfsAddress, + price_info: 2, + version: '0.4.1', + }; + fetch.mockResolvedValueOnce({ + status: 200, + ok: true, + json: jest.fn(async () => pfsInfoResponse), + text: jest.fn(async () => jsonStringify(pfsInfoResponse)), + }); + const matrix = (await raiden.deps.matrix$.toPromise()) as jest.Mocked; const service = makeAddress(); const serviceUid = `@${service.toLowerCase()}:${getServerName(matrix.getHomeserverUrl())}`; @@ -926,6 +950,8 @@ test('matrixMessageServiceSendEpic', async () => { raiden.output.splice(0, raiden.output.length); matrix.sendToDevice.mockClear(); + // re-enable additionalServices + raiden.store.dispatch(raidenConfigUpdate({ additionalServices })); // network errors must be retried matrix.sendToDevice.mockRejectedValueOnce( Object.assign(new Error('Failed'), { httpStatus: 429 }), From 458505a8f202a57a64e0a4de85d1f717222f8af1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vitor=20de=20Lima=20Matos?= Date: Mon, 26 Apr 2021 21:49:20 -0300 Subject: [PATCH 05/35] next: sdk: fetch presence from PFS This removes completely the requirement to having access to peer's presence directly from the matrix server, and instead replace it with the requirement for the PFS to know (already required for mediated transfers) and provide that presence (implemented in `next` RSB). Instead of searchUserDirectory, we request the PFS `/address/.../metadata` endpoint for its view of latest peer's presence. --- raiden-ts/src/services/utils.ts | 89 +++++++++++++++++++-- raiden-ts/src/transport/epics/presence.ts | 97 +++++++++-------------- 2 files changed, 118 insertions(+), 68 deletions(-) diff --git a/raiden-ts/src/services/utils.ts b/raiden-ts/src/services/utils.ts index 6f9c17c4ff..d94492cc9d 100644 --- a/raiden-ts/src/services/utils.ts +++ b/raiden-ts/src/services/utils.ts @@ -5,13 +5,16 @@ import memoize from 'lodash/memoize'; import uniqBy from 'lodash/uniqBy'; import type { Observable } from 'rxjs'; import { defer, EMPTY, from } from 'rxjs'; +import { fromFetch } from 'rxjs/fetch'; import { catchError, first, map, mergeMap, toArray } from 'rxjs/operators'; import type { ServiceRegistry } from '../contracts'; import { MessageTypeId } from '../messages/utils'; +import { Caps } from '../transport/types'; +import { parseCaps } from '../transport/utils'; import type { RaidenEpicDeps } from '../types'; import { encode, jsonParse } from '../utils/data'; -import { assert, ErrorCodes, networkErrors } from '../utils/error'; +import { assert, ErrorCodes, networkErrors, RaidenError } from '../utils/error'; import { LruCache } from '../utils/lru'; import { retryAsync$ } from '../utils/rx'; import type { Signature, Signed } from '../utils/types'; @@ -25,6 +28,22 @@ const serviceRegistryToken = memoize( }).toPromise() as Promise
, ); +/** + * Fetch, validate and cache the service URL for a given URL or service address + * (if registered on ServiceRegistry) + * + * @param pfsAddressUrl - service Address or URL + * @returns Promise to validated URL + */ +const pfsAddressUrl = memoize(async function pfsAddressUrl_( + pfsAddrOrUrl: string, + { serviceRegistryContract }: Pick, +): Promise { + let url = pfsAddrOrUrl; + if (Address.is(pfsAddrOrUrl)) url = await serviceRegistryContract.callStatic.urls(pfsAddrOrUrl); + return validatePfsUrl(url); +}); + const urlRegex = process.env.NODE_ENV === 'production' ? /^(?:https:\/\/)?[^\s\/$.?#&"']+\.[^\s\/$?#&"']+$/ @@ -68,7 +87,16 @@ export type ServiceError = t.TypeOf; */ export async function pfsInfo( pfsAddrOrUrl: Address | string, - { serviceRegistryContract, network, contractsInfo, provider, config$ }: RaidenEpicDeps, + { + serviceRegistryContract, + network, + contractsInfo, + provider, + config$, + }: Pick< + RaidenEpicDeps, + 'serviceRegistryContract' | 'network' | 'contractsInfo' | 'provider' | 'config$' + >, ): Promise { const { pfsMaxFee } = await config$.pipe(first()).toPromise(); /** @@ -88,10 +116,8 @@ export async function pfsInfo( }); // if it's an address, fetch url from ServiceRegistry, else it's already the URL - let url = pfsAddrOrUrl; - if (Address.is(pfsAddrOrUrl)) url = await serviceRegistryContract.callStatic.urls(pfsAddrOrUrl); + const url = await pfsAddressUrl(pfsAddrOrUrl, { serviceRegistryContract }); - url = validatePfsUrl(url); const start = Date.now(); const res = await fetch(url + '/api/v1/info', { mode: 'cors' }); const text = await res.text(); @@ -122,7 +148,13 @@ export async function pfsInfo( * @returns Promise to Address of PFS on given URL */ export const pfsInfoAddress = Object.assign( - async function pfsInfoAddress(url: string, deps: RaidenEpicDeps): Promise
{ + async function pfsInfoAddress( + url: string, + deps: Pick< + RaidenEpicDeps, + 'serviceRegistryContract' | 'network' | 'contractsInfo' | 'provider' | 'config$' + >, + ): Promise
{ url = validatePfsUrl(url); let addrPromise = pfsAddressCache_.get(url); if (!addrPromise) { @@ -149,7 +181,10 @@ export const pfsInfoAddress = Object.assign( */ export function pfsListInfo( pfsList: readonly (string | Address)[], - deps: RaidenEpicDeps, + deps: Pick< + RaidenEpicDeps, + 'log' | 'serviceRegistryContract' | 'network' | 'contractsInfo' | 'provider' | 'config$' + >, ): Observable { const { log } = deps; return from(pfsList).pipe( @@ -176,6 +211,46 @@ export function pfsListInfo( ); } +const PresenceFromService = t.type({ + user_id: t.string, + capabilities: t.union([Caps, t.string]), +}); + +/** + * @param peer - Peer address to fetch presence for + * @param pfsAddrOrUrl - PFS/service address to fetch presence from + * @param deps - Epics dependencies subset + * @param deps.serviceRegistryContract - Contract instance + * @returns Observable to peer's presence or error + */ +export function getPresenceFromService$( + peer: Address, + pfsAddrOrUrl: string, + { serviceRegistryContract }: Pick, +): Observable<{ readonly user_id: string; readonly capabilities: Caps }> { + return defer(async () => pfsAddressUrl(pfsAddrOrUrl, { serviceRegistryContract })).pipe( + mergeMap((url) => fromFetch(`${url}/api/v1/address/${peer}/metadata`)), + mergeMap(async (res) => res.json()), + map((json) => { + try { + const presence = decode(PresenceFromService, json); + const capabilities = + typeof presence.capabilities === 'string' + ? parseCaps(presence.capabilities) + : presence.capabilities; + assert(capabilities, ['Invalid capabilities format', presence.capabilities]); + return { ...presence, capabilities }; + } catch (err) { + try { + const { errors: msg, ...details } = decode(ServiceError, json); + err = new RaidenError(msg, details); + } catch (e) {} + throw err; + } + }), + ); +} + /** * Pack an IOU for signing or verification * diff --git a/raiden-ts/src/transport/epics/presence.ts b/raiden-ts/src/transport/epics/presence.ts index 4407e23e84..ca069e3dc5 100644 --- a/raiden-ts/src/transport/epics/presence.ts +++ b/raiden-ts/src/transport/epics/presence.ts @@ -1,15 +1,15 @@ -import { verifyMessage } from '@ethersproject/wallet'; -import getOr from 'lodash/fp/getOr'; import isEmpty from 'lodash/isEmpty'; import isEqual from 'lodash/isEqual'; -import minBy from 'lodash/minBy'; +import uniq from 'lodash/uniq'; import type { Observable } from 'rxjs'; -import { defer, EMPTY, merge, of } from 'rxjs'; +import { combineLatest, defer, from, merge, of } from 'rxjs'; import { catchError, + concatMap, distinctUntilChanged, exhaustMap, filter, + first, groupBy, ignoreElements, map, @@ -18,89 +18,64 @@ import { skip, switchMap, tap, - toArray, + timeout, withLatestFrom, } from 'rxjs/operators'; import type { RaidenAction } from '../../actions'; import { channelMonitored } from '../../channels/actions'; import { intervalFromConfig } from '../../config'; +import { PfsMode } from '../../services/types'; +import { getPresenceFromService$ } from '../../services/utils'; import type { RaidenState } from '../../state'; import type { RaidenEpicDeps } from '../../types'; import { isActionOf } from '../../utils/actions'; -import { assert, ErrorCodes, networkErrors } from '../../utils/error'; -import { getUserPresence } from '../../utils/matrix'; -import { completeWith, mergeWith, retryWhile } from '../../utils/rx'; +import { networkErrors } from '../../utils/error'; +import { catchAndLog, completeWith, retryWhile } from '../../utils/rx'; import type { Address } from '../../utils/types'; import { matrixPresence } from '../actions'; -import { getAddressFromUserId, parseCaps, stringifyCaps } from '../utils'; - -// unavailable just means the user didn't do anything over a certain amount of time, but they're -// still there, so we consider the user as available/online then -const AVAILABLE = ['online', 'unavailable']; +import { stringifyCaps } from '../utils'; /** - * Search user directory for valid users matching a given address and return latest + * Fetch peer's presence info from services * * @param address - Address of interest - * @param deps - Epics dependencies subset - * @param deps.log - Logger instance - * @param deps.matrix$ - Matrix client instance observable - * @param deps.config$ - Config observable + * @param deps - Epics dependencies * @returns Observable of user with most recent presence */ function searchAddressPresence$( address: Address, - { log, matrix$, config$ }: Pick, + deps: Pick, ) { - // search for any user containing the address of interest in its userId - return matrix$.pipe( - mergeWith(async (matrix) => matrix.searchUserDirectory({ term: address.toLowerCase() })), - retryWhile(intervalFromConfig(config$), { onErrors: networkErrors }), - // for every result matches, verify displayName signature is address of interest - mergeWith(function* ([, { results }]) { - for (const user of results) { - if (!user.display_name) continue; - try { - if (getAddressFromUserId(user.user_id) !== address) continue; - const recovered = verifyMessage(user.user_id, user.display_name); - if (!recovered || recovered !== address) continue; - } catch (err) { - continue; - } - yield user; - } - }), - mergeMap( - ([[matrix], user]) => - defer(async () => getUserPresence(matrix, user.user_id)).pipe( - map((presence) => ({ ...presence, ...user })), - retryWhile(intervalFromConfig(config$), { onErrors: networkErrors }), - catchError((err) => { - log.info('Error fetching user presence, ignoring:', err); - return EMPTY; - }), + const { config$, latest$ } = deps; + return combineLatest([latest$, config$]).pipe( + first(), + mergeMap(([{ state }, { pfsMode, additionalServices, httpTimeout }]) => { + let services = additionalServices; + if (pfsMode !== PfsMode.onlyAdditional) + services = uniq([...services, ...Object.keys(state.services)]); + return from(services).pipe( + concatMap((service) => + getPresenceFromService$(address, service, deps).pipe( + timeout(httpTimeout), + catchAndLog( + { onErrors: networkErrors, maxRetries: 1 }, + 'Error fetching presence from service', + address, + ), + ), ), - 3, // max parallelism on these requests - ), - toArray(), - // for all matched/verified users, get its presence through dedicated API - // it's required because, as the user events could already have been handled - // and filtered out by matrixPresenceUpdateEpic because it wasn't yet a - // user-of-interest, we could have missed presence updates, then we need to - // fetch it here directly, and from now on, that other epic will monitor its - // updates, and sort by most recently seen user - map((presences) => { - assert(presences.length, [ErrorCodes.TRNS_NO_VALID_USER, { address }]); - return minBy(presences, getOr(Number.POSITIVE_INFINITY, 'last_active_ago'))!; + // TODO: validate signature coming from service, once displayName is sent + first(), + ); }), - map(({ presence, user_id: userId, avatar_url }) => + map(({ user_id: userId, capabilities }) => matrixPresence.success( { userId, - available: AVAILABLE.includes(presence), + available: true, ts: Date.now(), - caps: parseCaps(avatar_url), + caps: capabilities, }, { address }, ), From 1cf99e514d29521906029f233c8e827ed858e3ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vitor=20de=20Lima=20Matos?= Date: Mon, 26 Apr 2021 19:33:24 -0300 Subject: [PATCH 06/35] next: sdk: remove global rooms and helpers Now that presence is fetched from PFS, we don't need to join discovery global room anymore, and since PFS and MS rooms were removed previously, we can clear the utility and helpers which supported this. --- raiden-ts/src/config.ts | 5 -- raiden-ts/src/transport/epics/helpers.ts | 88 ------------------- raiden-ts/src/transport/epics/init.ts | 50 ++--------- raiden-ts/src/transport/epics/messages.ts | 3 +- raiden-ts/src/transport/epics/rooms.ts | 9 +- raiden-ts/src/transport/epics/webrtc.ts | 3 +- raiden-ts/src/transport/utils.ts | 42 ++++++++- raiden-ts/tests/integration/transport.spec.ts | 2 - 8 files changed, 50 insertions(+), 152 deletions(-) delete mode 100644 raiden-ts/src/transport/epics/helpers.ts diff --git a/raiden-ts/src/config.ts b/raiden-ts/src/config.ts index 3cbbd52b8a..d3df12f910 100644 --- a/raiden-ts/src/config.ts +++ b/raiden-ts/src/config.ts @@ -9,7 +9,6 @@ import { Capabilities, DEFAULT_CONFIRMATIONS } from './constants'; import { PfsMode, PfsModeC } from './services/types'; import { exponentialBackoff } from './transfers/epics/utils'; import { Caps } from './transport/types'; -import { getNetworkName } from './utils/ethers'; import { Address, UInt } from './utils/types'; const RTCIceServer = t.type({ urls: t.union([t.string, t.array(t.string)]) }); @@ -30,7 +29,6 @@ const RTCIceServer = t.type({ urls: t.union([t.string, t.array(t.string)]) }); * - expiryFactor - Multiply revealTimeout to get how far in the future * transfer expiration block should be * - httpTimeout - Used in http fetch requests - * - discoveryRoom - Discovery Room to auto-join, use null to disable * - additionalServices - Array of extra services URLs (or addresses, if URL set on SecretRegistry) * - pfsMode - One of 'disabled' (disables PFS usage and notifications), 'auto' (notifies all of * registered and additionalServices, picks cheapest for transfers without explicit pfs), @@ -70,7 +68,6 @@ export const RaidenConfig = t.readonly( settleTimeout: t.number, expiryFactor: t.number, // must be > 1.0 httpTimeout: t.number, - discoveryRoom: t.union([t.string, t.null]), additionalServices: t.readonlyArray(t.union([Address, t.string])), pfsMode: PfsModeC, pfsSafetyMargin: t.union([t.number, t.tuple([t.number, t.number])]), @@ -124,7 +121,6 @@ export function makeDefaultConfig( { network }: { network: Network }, overwrites?: PartialRaidenConfig, ): RaidenConfig { - const networkName = getNetworkName(network); const matrixServerInfos = network.chainId === 1 ? 'https://raw.githubusercontent.com/raiden-network/raiden-service-bundle/master/known_servers/known_servers-production-v1.2.0.json' @@ -146,7 +142,6 @@ export function makeDefaultConfig( revealTimeout: 50, expiryFactor: 1.1, // must be > 1.0 httpTimeout: 30e3, - discoveryRoom: `raiden_${networkName}_discovery`, additionalServices: [], pfsMode: PfsMode.auto, pfsSafetyMargin: 1.0, // multiplier diff --git a/raiden-ts/src/transport/epics/helpers.ts b/raiden-ts/src/transport/epics/helpers.ts deleted file mode 100644 index aa279a2b06..0000000000 --- a/raiden-ts/src/transport/epics/helpers.ts +++ /dev/null @@ -1,88 +0,0 @@ -import curry from 'lodash/curry'; -import type { MatrixClient, Room } from 'matrix-js-sdk'; -import type { Observable } from 'rxjs'; -import { fromEvent, of } from 'rxjs'; -import { filter, take } from 'rxjs/operators'; - -import type { RaidenConfig } from '../../config'; -import type { Message } from '../../messages/types'; -import { decodeJsonMessage, getMessageSigner } from '../../messages/utils'; -import type { RaidenEpicDeps } from '../../types'; -import { ErrorCodes, RaidenError } from '../../utils/error'; -import type { Address, Signed } from '../../utils/types'; -import { isntNil } from '../../utils/types'; - -/** - * Return the array of configured global rooms - * - * @param config - object to gather the list from - * @returns Array of room names - */ -export function globalRoomNames(config: RaidenConfig) { - return [config.discoveryRoom].filter(isntNil); -} - -/** - * Curried function (arity=2) which matches room passed as second argument based on roomId, name or - * alias passed as first argument - * - * @param roomIdOrAlias - Room Id, name, canonical or normal alias for room - * @param room - Room to test - * @returns True if room matches term, false otherwise - */ -export const roomMatch = curry( - (roomIdOrAlias: string, room: Room) => - roomIdOrAlias === room.roomId || - roomIdOrAlias === room.name || - roomIdOrAlias === room.getCanonicalAlias() || - room.getAliases().includes(roomIdOrAlias), -); - -/** - * Returns an observable to a (possibly pending) room matching roomId or some alias - * This method doesn't try to join the room, just wait for it to show up in MatrixClient. - * - * @param matrix - Client instance to fetch room info from - * @param roomIdOrAlias - room id or alias to look for - * @returns Observable to populated room instance - */ -export function getRoom$(matrix: MatrixClient, roomIdOrAlias: string): Observable { - let room: Room | null | undefined = matrix.getRoom(roomIdOrAlias); - if (!room) room = matrix.getRooms().find(roomMatch(roomIdOrAlias)); - if (room) return of(room); - return fromEvent(matrix, 'Room').pipe(filter(roomMatch(roomIdOrAlias)), take(1)); -} - -/** - * Parse a received message into either a Message or Signed - * If Signed, the signer must match the sender's address. - * Errors are logged and undefined returned - * - * @param line - String to be parsed as a single message - * @param address - Sender's address - * @param deps - Dependencies - * @param deps.log - Logger instance - * @returns Validated Signed or unsigned Message, or undefined - */ -export function parseMessage( - line: any, // eslint-disable-line @typescript-eslint/no-explicit-any - address: Address, - { log }: Pick, -): Message | Signed | undefined { - if (typeof line !== 'string') return; - try { - const message = decodeJsonMessage(line); - // if Signed, accept only if signature matches sender address - if ('signature' in message) { - const signer = getMessageSigner(message); - if (signer !== address) - throw new RaidenError(ErrorCodes.TRNS_MESSAGE_SIGNATURE_MISMATCH, { - sender: address, - signer, - }); - } - return message; - } catch (err) { - log.warn(`Could not decode message: ${line}: ${err}`); - } -} diff --git a/raiden-ts/src/transport/epics/init.ts b/raiden-ts/src/transport/epics/init.ts index 0d6ae83925..6b0553e8ca 100644 --- a/raiden-ts/src/transport/epics/init.ts +++ b/raiden-ts/src/transport/epics/init.ts @@ -3,7 +3,7 @@ import constant from 'lodash/constant'; import isEmpty from 'lodash/isEmpty'; import sortBy from 'lodash/sortBy'; import type { Filter, LoginPayload, MatrixClient } from 'matrix-js-sdk'; -import { createClient, MatrixEvent } from 'matrix-js-sdk'; +import { createClient } from 'matrix-js-sdk'; import { logger as matrixLogger } from 'matrix-js-sdk/lib/logger'; import type { AsyncSubject, Observable } from 'rxjs'; import { combineLatest, defer, EMPTY, from, merge, of, throwError, timer } from 'rxjs'; @@ -18,7 +18,6 @@ import { map, mapTo, mergeMap, - pluck, retryWhen, take, tap, @@ -43,56 +42,18 @@ import { matrixSetup } from '../actions'; import type { RaidenMatrixSetup } from '../state'; import type { Caps } from '../types'; import { stringifyCaps } from '../utils'; -import { globalRoomNames } from './helpers'; - -/** - * Joins the global broadcast rooms and returns the room ids. - * - * @param config - The {@link RaidenConfig} provides the global room aliases. - * @param matrix - The {@link MatrixClient} instance used to create the filter. - * @returns Observable of the list of room ids for the the broadcast rooms. - */ -function joinGlobalRooms(config: RaidenConfig, matrix: MatrixClient): Observable { - const serverName = getServerName(matrix.getHomeserverUrl())!; - return from(globalRoomNames(config)).pipe( - map((globalRoom) => `#${globalRoom}:${serverName}`), - mergeMap((alias) => - matrix.joinRoom(alias).then((room) => { - // set alias in room state directly - // this trick is needed because global rooms aren't synced - const event = { - type: 'm.room.aliases' as const, - state_key: serverName, - origin_server_ts: Date.now(), - content: { aliases: [alias] }, - event_id: `$local_${Date.now()}`, - room_id: room.roomId, - sender: matrix.getUserId()!, - }; - room.currentState.setStateEvents([ - new MatrixEvent(event), - ] as MatrixEvent[]); // eslint-disable-line @typescript-eslint/no-explicit-any - matrix.store.storeRoom(room); - matrix.emit('Room', room); - return room; - }), - ), - pluck('roomId'), - toArray(), - ); -} /** * Creates and returns a matrix filter. The filter reduces the size of the initial sync by * filtering out broadcast rooms, emphemeral messages like receipts etc. * * @param matrix - The {@link MatrixClient} instance used to create the filter. - * @param roomIds - The ids of the rooms to filter out during sync. + * @param notRooms - The ids of the rooms to filter out during sync. * @returns Observable of the {@link Filter} that was created. */ -async function createMatrixFilter(matrix: MatrixClient, roomIds: string[]): Promise { +async function createMatrixFilter(matrix: MatrixClient, notRooms: string[] = []): Promise { const roomFilter = { - not_rooms: roomIds, + not_rooms: notRooms, ephemeral: { not_types: ['m.receipt', 'm.typing'], }, @@ -124,8 +85,7 @@ function startMatrixSync( // wait 1s before starting matrix, so event listeners can be registered delayWhen(([, { pollingInterval }]) => timer(Math.ceil(pollingInterval / 5))), mergeMap(([, config]) => - joinGlobalRooms(config, matrix).pipe( - mergeMap(async (roomIds) => createMatrixFilter(matrix, roomIds)), + defer(async () => createMatrixFilter(matrix)).pipe( mergeMap(async (filter) => { await matrix.setPushRuleEnabled('global', 'override', '.m.rule.master', true); return filter; diff --git a/raiden-ts/src/transport/epics/messages.ts b/raiden-ts/src/transport/epics/messages.ts index 6982e915f6..6cd5f389bf 100644 --- a/raiden-ts/src/transport/epics/messages.ts +++ b/raiden-ts/src/transport/epics/messages.ts @@ -55,8 +55,7 @@ import { } from '../../utils/rx'; import { Address, isntNil, Signed } from '../../utils/types'; import { matrixPresence } from '../actions'; -import { getAddressFromUserId, getNoDeliveryPeers } from '../utils'; -import { parseMessage } from './helpers'; +import { getAddressFromUserId, getNoDeliveryPeers, parseMessage } from '../utils'; function getMessageBody(message: string | Signed): string { return typeof message === 'string' ? message : encodeJsonMessage(message); diff --git a/raiden-ts/src/transport/epics/rooms.ts b/raiden-ts/src/transport/epics/rooms.ts index 6f99af4266..53af729cd7 100644 --- a/raiden-ts/src/transport/epics/rooms.ts +++ b/raiden-ts/src/transport/epics/rooms.ts @@ -6,12 +6,10 @@ import { delayWhen, filter, ignoreElements, mergeMap, withLatestFrom } from 'rxj import type { RaidenAction } from '../../actions'; import type { RaidenState } from '../../state'; import type { RaidenEpicDeps } from '../../types'; -import { getServerName } from '../../utils/matrix'; import { completeWith, mergeWith } from '../../utils/rx'; -import { globalRoomNames, roomMatch } from './helpers'; /** - * Leave any (new or invited) room which is not global + * Leave any (new or invited) room * * @param action$ - Observable of RaidenActions * @param state$ - Observable of RaidenStates @@ -36,12 +34,9 @@ export function matrixLeaveUnknownRoomsEpic( ), completeWith(state$), // filter for leave events to us - filter(([[matrix, room], config]) => { + filter(([[, room]]) => { const myMembership = room.getMyMembership(); if (!myMembership || myMembership === 'leave') return false; // room already gone while waiting - const serverName = getServerName(matrix.getHomeserverUrl()); - if (globalRoomNames(config).some((g) => roomMatch(`#${g}:${serverName}`, room))) - return false; return true; }), mergeMap(async ([[matrix, room]]) => { diff --git a/raiden-ts/src/transport/epics/webrtc.ts b/raiden-ts/src/transport/epics/webrtc.ts index 5936ad3a68..75afc097d2 100644 --- a/raiden-ts/src/transport/epics/webrtc.ts +++ b/raiden-ts/src/transport/epics/webrtc.ts @@ -66,8 +66,7 @@ import { import type { Address } from '../../utils/types'; import { decode, isntNil, last } from '../../utils/types'; import { matrixPresence, rtcChannel } from '../actions'; -import { getCap } from '../utils'; -import { parseMessage } from './helpers'; +import { getCap, parseMessage } from '../utils'; interface CallInfo { callId: string; diff --git a/raiden-ts/src/transport/utils.ts b/raiden-ts/src/transport/utils.ts index 1770699f25..b41dc2255a 100644 --- a/raiden-ts/src/transport/utils.ts +++ b/raiden-ts/src/transport/utils.ts @@ -5,8 +5,12 @@ import { filter, scan, startWith } from 'rxjs/operators'; import type { RaidenAction } from '../actions'; import { Capabilities, CapsFallback } from '../constants'; +import type { Message } from '../messages/types'; +import { decodeJsonMessage, getMessageSigner } from '../messages/utils'; +import type { RaidenEpicDeps } from '../types'; import { jsonParse } from '../utils/data'; -import type { Address } from '../utils/types'; +import { assert, ErrorCodes } from '../utils/error'; +import type { Address, Signed } from '../utils/types'; import { matrixPresence } from './actions'; import type { Caps, CapsPrimitive } from './types'; @@ -116,3 +120,39 @@ export function getNoDeliveryPeers(): OperatorFunction + * If Signed, the signer must match the sender's address. + * Errors are logged and undefined returned + * + * @param line - String to be parsed as a single message + * @param address - Sender's address + * @param deps - Dependencies + * @param deps.log - Logger instance + * @returns Validated Signed or unsigned Message, or undefined + */ +export function parseMessage( + line: any, // eslint-disable-line @typescript-eslint/no-explicit-any + address: Address, + { log }: Pick, +): Message | Signed | undefined { + if (typeof line !== 'string') return; + try { + const message = decodeJsonMessage(line); + // if Signed, accept only if signature matches sender address + if ('signature' in message) { + const signer = getMessageSigner(message); + assert(signer === address, [ + ErrorCodes.TRNS_MESSAGE_SIGNATURE_MISMATCH, + { + sender: address, + signer, + }, + ]); + } + return message; + } catch (err) { + log.warn(`Could not decode message: ${line}: ${err}`); + } +} diff --git a/raiden-ts/tests/integration/transport.spec.ts b/raiden-ts/tests/integration/transport.spec.ts index d98aa37fbf..ed6319bb9c 100644 --- a/raiden-ts/tests/integration/transport.spec.ts +++ b/raiden-ts/tests/integration/transport.spec.ts @@ -682,8 +682,6 @@ describe('matrixMessageSendEpic', () => { describe('matrixMessageReceivedEpic', () => { test('receive: success', async () => { expect.assertions(1); - // Gets a log.warn(`Could not decode message: ${line}: ${err}`); - // at Object.parseMessage (src/transport/epics/helpers.ts:203:9) const message = 'test message'; const [raiden, partner] = getSortedClients(await makeRaidens(2)); const partnerMatrix = (await partner.deps.matrix$.toPromise()) as jest.Mocked; From f078cfbc5f34223ca5fb6e52cdcd894483f2439c Mon Sep 17 00:00:00 2001 From: Paul Lange Date: Tue, 11 May 2021 12:05:51 +0200 Subject: [PATCH 07/35] e2e: Update versions in e2e image --- e2e-environment/Dockerfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/e2e-environment/Dockerfile b/e2e-environment/Dockerfile index 28e479bb57..bdce346107 100644 --- a/e2e-environment/Dockerfile +++ b/e2e-environment/Dockerfile @@ -1,8 +1,8 @@ -ARG RAIDEN_VERSION="v1.2.0" -ARG CONTRACTS_PACKAGE_VERSION="0.37.1" +ARG RAIDEN_VERSION="v2.0.0rc0" +ARG CONTRACTS_PACKAGE_VERSION="0.37.6" ARG CONTRACTS_VERSION="0.37.0" -ARG SERVICES_VERSION="v0.13.1" -ARG SYNAPSE_VERSION="v1.19.1" +ARG SERVICES_VERSION="v0.15.4" +ARG SYNAPSE_VERSION="v1.33.2" ARG OS_NAME="LINUX" ARG GETH_VERSION="1.9.23" ARG GETH_URL_LINUX="https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.9.23-8c2f2715.tar.gz" From 5a423d0fe05f056a561268ac2404244ab3b60ecc Mon Sep 17 00:00:00 2001 From: Paul Lange Date: Tue, 11 May 2021 12:06:32 +0200 Subject: [PATCH 08/35] e2e: Update service contract deployment calls --- e2e-environment/geth/deploy_contracts.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/e2e-environment/geth/deploy_contracts.py b/e2e-environment/geth/deploy_contracts.py index be77a224a2..31d810289e 100755 --- a/e2e-environment/geth/deploy_contracts.py +++ b/e2e-environment/geth/deploy_contracts.py @@ -142,7 +142,8 @@ def main(keystore_file: str, contract_version: str, password: str, output: str, decay_constant=SERVICE_DEPOSIT_DECAY_CONSTANT, min_price=SERVICE_DEPOSIT_MIN_PRICE, registration_duration=SERVICE_REGISTRATION_DURATION, - token_network_registry_address=token_network_registry_address + token_network_registry_address=token_network_registry_address, + reuse_service_registry_from_deploy_file=None, ) except Exception as err: print(f'Service contract deployment failed: {err}') From 725ca96a7d62664979ce7bb495e1e83dc68cae01 Mon Sep 17 00:00:00 2001 From: Paul Lange Date: Tue, 11 May 2021 12:06:51 +0200 Subject: [PATCH 09/35] e2e: Remove empty lines --- e2e-environment/geth/deploy.sh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/e2e-environment/geth/deploy.sh b/e2e-environment/geth/deploy.sh index af3573891f..63bb5c195d 100755 --- a/e2e-environment/geth/deploy.sh +++ b/e2e-environment/geth/deploy.sh @@ -68,7 +68,3 @@ else kill -s TERM ${GETH_PID} exit 1 fi - - - - From 85bb8d05c5bd5435e02963fc313176b472a841bf Mon Sep 17 00:00:00 2001 From: Paul Lange Date: Tue, 11 May 2021 12:07:57 +0200 Subject: [PATCH 10/35] e2e: Remove room ensurer --- e2e-environment/setup/room_ensurer.py | 361 ------------------------ e2e-environment/setup/setup_channels.sh | 7 - 2 files changed, 368 deletions(-) delete mode 100644 e2e-environment/setup/room_ensurer.py diff --git a/e2e-environment/setup/room_ensurer.py b/e2e-environment/setup/room_ensurer.py deleted file mode 100644 index 0cb80741b7..0000000000 --- a/e2e-environment/setup/room_ensurer.py +++ /dev/null @@ -1,361 +0,0 @@ -""" -Utility that initializes public rooms and ensures correct federation - -In Raiden we use a public discovery room that all nodes join which provides the following features: -- Nodes are findable in the server-side user search -- All nodes receive presence updates about existing and newly joined nodes - -The global room is initially created on one server, after that it is federated to all other servers -and a server-local alias is added to it so it's discoverable on every server. - -This utility uses for following algorithm to ensure there are no races in room creation: -- Sort list of known servers lexicographically -- Connect to all known servers -- If not all servers are reachable, sleep and retry later -- Try to join room `#:` -- Compare room_id of all found rooms -- If either the room_ids differ or no room is found on a specific server: - - If `own_server` is the first server in the list: - - Create a room if it doesn't exist and assign the server-local alias - - Else: - - If a room with alias `#:` exists, remove that alias - - Wait for the room with `#:` to appear - - Add server-local alias to the first_server-room -""" -from gevent.monkey import patch_all # isort:skip - -patch_all() # isort:skip - -import json -import os -import sys -from dataclasses import dataclass -from enum import IntEnum, Enum -from itertools import chain -from json import JSONDecodeError -from typing import Any, Dict, Optional, Set, TextIO, Tuple, Union -from urllib.parse import urlparse - -import click -import gevent -from eth_utils import encode_hex, to_normalized_address -from matrix_client.errors import MatrixError -from raiden_contracts.utils.type_aliases import ChainID -from structlog import get_logger - -from raiden.constants import ( - DISCOVERY_DEFAULT_ROOM, - MONITORING_BROADCASTING_ROOM, - PATH_FINDING_BROADCASTING_ROOM, - Environment, -) -from raiden.log_config import configure_logging -from raiden.network.transport.matrix import make_room_alias -from raiden.network.transport.matrix.client import GMatrixHttpApi -from raiden.settings import DEFAULT_MATRIX_KNOWN_SERVERS -from raiden.tests.utils.factories import make_signer - -ENV_KEY_KNOWN_SERVERS = "URL_KNOWN_FEDERATION_SERVERS" - - -class Networks(Enum): - INTEGRATION = ChainID(4321) - - -class MatrixPowerLevels(IntEnum): - USER = 0 - MODERATOR = 50 - ADMINISTRATOR = 100 - - -log = get_logger(__name__) - - -class EnsurerError(Exception): - pass - - -class MultipleErrors(EnsurerError): - pass - - -@dataclass(frozen=True) -class RoomInfo: - room_id: str - aliases: Set[str] - server_name: str - - -class RoomEnsurer: - def __init__( - self, - username: str, - password: str, - own_server_name: str, - known_servers_url: Optional[str] = None, - ): - self._username = username - self._password = password - self._own_server_name = own_server_name - - if known_servers_url is None: - known_servers_url = DEFAULT_MATRIX_KNOWN_SERVERS[Environment.PRODUCTION] - - self._known_servers: Dict[str, str] = { - own_server_name: f"http://{own_server_name}:80" - } - - - if not self._known_servers: - raise RuntimeError(f"No known servers found from list at {known_servers_url}.") - self._first_server_name = list(self._known_servers.keys())[0] - self._is_first_server = own_server_name == self._first_server_name - self._apis: Dict[str, GMatrixHttpApi] = self._connect_all() - self._own_api = self._apis[own_server_name] - - log.debug( - "Room ensurer initialized", - own_server_name=own_server_name, - known_servers=self._known_servers.keys(), - first_server_name=self._first_server_name, - is_first_server=self._is_first_server, - ) - - def ensure_rooms(self) -> None: - exceptions = {} - for network in Networks: - for alias_fragment in [ - DISCOVERY_DEFAULT_ROOM, - MONITORING_BROADCASTING_ROOM, - PATH_FINDING_BROADCASTING_ROOM, - ]: - try: - self._ensure_room_for_network(network, alias_fragment) - except (MatrixError, EnsurerError) as ex: - log.exception(f"Error while ensuring room for {network.name}.") - exceptions[network] = ex - if exceptions: - log.error("Exceptions happened", exceptions=exceptions) - raise MultipleErrors(exceptions) - - def _ensure_room_for_network(self, network: Networks, alias_fragment: str) -> None: - log.info(f"Ensuring {alias_fragment} room for {network.name}") - room_alias_prefix = make_room_alias(ChainID(network.value), alias_fragment) - room_infos: Dict[str, Optional[RoomInfo]] = { - server_name: self._get_room(server_name, room_alias_prefix) - for server_name in self._known_servers.keys() - } - first_server_room_info = room_infos[self._first_server_name] - - if not first_server_room_info: - log.warning("First server room missing") - if self._is_first_server: - log.info("Creating room", server_name=self._own_server_name) - first_server_room_info = self._create_room( - self._own_server_name, room_alias_prefix - ) - room_infos[self._first_server_name] = first_server_room_info - else: - raise EnsurerError("First server room missing.") - - are_all_rooms_the_same = all( - room_info is not None and room_info.room_id == first_server_room_info.room_id - for room_info in room_infos.values() - ) - if not are_all_rooms_the_same: - log.warning( - "Room id mismatch", - alias_prefix=room_alias_prefix, - expected=first_server_room_info.room_id, - found={ - server_name: room_info.room_id if room_info else None - for server_name, room_info in room_infos.items() - }, - ) - own_server_room_info = room_infos.get(self._own_server_name) - own_server_room_alias = f"#{room_alias_prefix}:{self._own_server_name}" - first_server_room_alias = f"#{room_alias_prefix}:{self._first_server_name}" - if not own_server_room_info: - log.warning( - "Room missing on own server, adding alias", - server_name=self._own_server_name, - room_id=first_server_room_info.room_id, - new_room_alias=own_server_room_alias, - ) - self._join_and_alias_room(first_server_room_alias, own_server_room_alias) - log.info("Room alias set", alias=own_server_room_alias) - elif own_server_room_info.room_id != first_server_room_info.room_id: - log.warning( - "Conflicting local room, reassigning alias", - server_name=self._own_server_name, - expected_room_id=first_server_room_info.room_id, - current_room_id=own_server_room_info.room_id, - ) - self._own_api.remove_room_alias(own_server_room_alias) - self._join_and_alias_room(first_server_room_alias, own_server_room_alias) - log.info( - "Room alias updated", - alias=own_server_room_alias, - room_id=first_server_room_info.room_id, - ) - else: - log.warning("Mismatching rooms on other servers. Doing nothing.") - else: - log.info( - "Room state ok.", - network=network, - server_rooms={ - server_name: room_info.room_id if room_info else None - for server_name, room_info in room_infos.items() - }, - ) - - def _join_and_alias_room( - self, first_server_room_alias: str, own_server_room_alias: str - ) -> None: - response = self._own_api.join_room(first_server_room_alias) - own_room_id = response.get("room_id") - if not own_room_id: - raise EnsurerError("Couldn't join first server room via federation.") - log.debug("Joined room on first server", own_room_id=own_room_id) - self._own_api.set_room_alias(own_room_id, own_server_room_alias) - - def _get_room(self, server_name: str, room_alias_prefix: str) -> Optional[RoomInfo]: - api = self._apis[server_name] - room_alias_local = f"#{room_alias_prefix}:{server_name}" - try: - response = api.join_room(room_alias_local) - room_id = response.get("room_id") - if not room_id: - log.debug("Couldn't find room", room_alias=room_alias_local) - return None - room_state = api.get_room_state(response["room_id"]) - except MatrixError: - log.debug("Room doesn't exist", room_alias=room_alias_local) - return None - existing_room_aliases = set( - chain.from_iterable( - event["content"]["aliases"] - for event in room_state - if event["type"] == "m.room.aliases" - ) - ) - - log.debug( - "Room aliases", server_name=server_name, room_id=room_id, aliases=existing_room_aliases - ) - return RoomInfo(room_id=room_id, aliases=existing_room_aliases, server_name=server_name) - - def _create_server_user_power_levels(self) -> Dict[str, Any]: - - server_admin_power_levels: Dict[str, Union[int, Dict[str, int]]] = { - "users": {}, - "users_default": MatrixPowerLevels.USER, - "events": { - "m.room.power_levels": MatrixPowerLevels.ADMINISTRATOR, - "m.room.history_visibility": MatrixPowerLevels.ADMINISTRATOR, - }, - "events_default": MatrixPowerLevels.USER, - "state_default": MatrixPowerLevels.MODERATOR, - "ban": MatrixPowerLevels.MODERATOR, - "kick": MatrixPowerLevels.MODERATOR, - "redact": MatrixPowerLevels.MODERATOR, - "invite": MatrixPowerLevels.MODERATOR, - } - - for server_name in self._known_servers: - username = f"admin-{server_name}".replace(":", "-") - user_id = f"@{username}:{server_name}" - server_admin_power_levels["users"][user_id] = MatrixPowerLevels.MODERATOR - - own_user_id = f"@{self._username}:{self._own_server_name}" - server_admin_power_levels["users"][own_user_id] = MatrixPowerLevels.ADMINISTRATOR - - return server_admin_power_levels - - def _create_room(self, server_name: str, room_alias_prefix: str) -> RoomInfo: - api = self._apis[server_name] - server_admin_power_levels = self._create_server_user_power_levels() - response = api.create_room( - room_alias_prefix, - is_public=True, - # power_level_content_override=server_admin_power_levels, - ) - room_alias = f"#{room_alias_prefix}:{server_name}" - return RoomInfo(response["room_id"], {room_alias}, server_name) - - def _connect_all(self) -> Dict[str, GMatrixHttpApi]: - jobs = { - gevent.spawn(self._connect, server_name, server_url) - for server_name, server_url in self._known_servers.items() - } - gevent.joinall(jobs, raise_error=True) - log.info("All servers connected") - return {server_name: matrix_api for server_name, matrix_api in (job.get() for job in jobs)} - - def _connect(self, server_name: str, server_url: str) -> Tuple[str, GMatrixHttpApi]: - log.debug("Connecting", server=server_name) - api = GMatrixHttpApi(server_url) - username = self._username - password = self._password - - if server_name != self._own_server_name: - signer = make_signer() - username = str(to_normalized_address(signer.address)) - password = encode_hex(signer.sign(server_name.encode())) - - response = api.login("m.login.password", user=username, password=password) - api.token = response["access_token"] - log.debug("Connected", server=server_name) - return server_name, api - - -@click.command() -@click.option("--own-server", required=True) -@click.option( - "-i", - "--interval", - default=3600, - help="How often to perform the room check. Set to 0 to disable.", -) -@click.option("-l", "--log-level", default="INFO") -@click.option("-c", "--credentials-file", required=True, type=click.File("rt")) -def main(own_server: str, interval: int, credentials_file: TextIO, log_level: str) -> None: - configure_logging( - {"": log_level, "raiden": log_level, "__main__": log_level}, disable_debug_logfile=True - ) - known_servers_url = os.environ.get(ENV_KEY_KNOWN_SERVERS) - - try: - credentials = json.loads(credentials_file.read()) - username = credentials["username"] - password = credentials["password"] - - except (JSONDecodeError, UnicodeDecodeError, OSError, KeyError): - log.exception("Invalid credentials file") - sys.exit(1) - - while True: - try: - room_ensurer = RoomEnsurer(username, password, own_server, known_servers_url) - except MatrixError: - log.exception("Failure while communicating with matrix servers. Retrying in 60s") - gevent.sleep(60) - continue - - try: - room_ensurer.ensure_rooms() - except EnsurerError: - log.error("Retrying in 60s.") - gevent.sleep(60) - continue - - if interval == 0: - break - - log.info("Run finished, sleeping.", duration=interval) - gevent.sleep(interval) - - -if __name__ == "__main__": - main() # pylint: disable=no-value-for-parameter diff --git a/e2e-environment/setup/setup_channels.sh b/e2e-environment/setup/setup_channels.sh index 528f026e26..12d6871be7 100755 --- a/e2e-environment/setup/setup_channels.sh +++ b/e2e-environment/setup/setup_channels.sh @@ -9,13 +9,6 @@ echo Synapse server is running at "${SYNAPSE_PID}" source /opt/raiden/bin/activate -echo Preparing ROOMS - -python3 /usr/local/bin/room_ensurer.py --own-server "${SERVER_NAME}" \ - --log-level "DEBUG" \ - --credentials-file /opt/synapse/config/admin_user_cred.json \ - -i 0 - echo Starting Chain ACCOUNT=$(cat /opt/deployment/miner.sh) From eda792fdc3c9922898d7281a30c8f7880b09645d Mon Sep 17 00:00:00 2001 From: Paul Lange Date: Mon, 17 May 2021 15:32:52 +0200 Subject: [PATCH 11/35] e2e: Update geth --- e2e-environment/Dockerfile | 6 +++--- e2e-environment/geth/deploy.sh | 9 ++++----- e2e-environment/setup/setup_channels.sh | 9 ++++----- e2e-environment/supervisord.conf | 2 +- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/e2e-environment/Dockerfile b/e2e-environment/Dockerfile index bdce346107..e1e1b2db38 100644 --- a/e2e-environment/Dockerfile +++ b/e2e-environment/Dockerfile @@ -4,9 +4,9 @@ ARG CONTRACTS_VERSION="0.37.0" ARG SERVICES_VERSION="v0.15.4" ARG SYNAPSE_VERSION="v1.33.2" ARG OS_NAME="LINUX" -ARG GETH_VERSION="1.9.23" -ARG GETH_URL_LINUX="https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.9.23-8c2f2715.tar.gz" -ARG GETH_MD5_LINUX="4817ce02025ba3f876f7055e1e456555" +ARG GETH_VERSION="1.10.3" +ARG GETH_URL_LINUX="https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.10.3-991384a7.tar.gz" +ARG GETH_MD5_LINUX="c601f6a651e23878229dd4a39fa2e6e4" FROM python:3.9 as raiden-builder ARG RAIDEN_VERSION diff --git a/e2e-environment/geth/deploy.sh b/e2e-environment/geth/deploy.sh index 63bb5c195d..80e568109f 100755 --- a/e2e-environment/geth/deploy.sh +++ b/e2e-environment/geth/deploy.sh @@ -21,16 +21,15 @@ echo "${ACCOUNT}" > "${DEPLOYMENT_DIRECTORY}"/miner.sh genesis.py --validator "${ACCOUNT}" --output /tmp/genesis.json geth --datadir "${DATA_DIR}" init /tmp/genesis.json -geth --rpc --syncmode full \ +geth --syncmode full \ --gcmode archive \ --datadir "${DATA_DIR}" \ --networkid 4321 \ --nodiscover \ - --rpc \ - --rpcapi "eth,net,web3,txpool" \ - --minerthreads=1 \ + --http \ + --http.api "eth,net,web3,txpool" \ + --miner.threads 1 \ --mine \ - --nousb \ --unlock "${ACCOUNT}" \ --password "${PASSWORD_FILE}" \ --allow-insecure-unlock & diff --git a/e2e-environment/setup/setup_channels.sh b/e2e-environment/setup/setup_channels.sh index 12d6871be7..2ab418f1bc 100755 --- a/e2e-environment/setup/setup_channels.sh +++ b/e2e-environment/setup/setup_channels.sh @@ -12,14 +12,13 @@ source /opt/raiden/bin/activate echo Starting Chain ACCOUNT=$(cat /opt/deployment/miner.sh) -geth --rpc --syncmode full --gcmode archive --datadir "${DATA_DIR}" \ +geth --syncmode full --gcmode archive --datadir "${DATA_DIR}" \ --networkid 4321 \ --nodiscover \ - --rpc \ - --rpcapi "eth,net,web3,txpool" \ - --minerthreads=1 \ + --http \ + --http.api "eth,net,web3,txpool" \ + --miner.threads 1 \ --mine \ - --nousb \ --unlock "${ACCOUNT}" \ --password "${PASSWORD_FILE}" \ --allow-insecure-unlock & diff --git a/e2e-environment/supervisord.conf b/e2e-environment/supervisord.conf index f41af83ab8..c3afe21806 100644 --- a/e2e-environment/supervisord.conf +++ b/e2e-environment/supervisord.conf @@ -2,7 +2,7 @@ nodaemon=true [program:geth] -command=/usr/local/bin/geth --rpc --syncmode full --gcmode archive --datadir /opt/chain --networkid 4321 --nodiscover --rpc --rpcaddr 0.0.0.0 --rpcapi "eth,net,web3,txpool" --rpccorsdomain "*" --minerthreads=1 --mine --nousb --unlock %(ENV_MINER_ACCOUNT)s --password %(ENV_PASSWORD_FILE)s --allow-insecure-unlock +command=/usr/local/bin/geth --syncmode full --gcmode archive --datadir /opt/chain --networkid 4321 --nodiscover --http --http.addr 0.0.0.0 --http.api "eth,net,web3,txpool" --rpccorsdomain "*" --miner.threads=1 --mine --unlock %(ENV_MINER_ACCOUNT)s --password %(ENV_PASSWORD_FILE)s --allow-insecure-unlock [program:synapse] command=/usr/local/bin/synapse-entrypoint.sh From 42274ca7144076e974d278c24b283df4dc455f4a Mon Sep 17 00:00:00 2001 From: Paul Lange Date: Mon, 17 May 2021 15:36:40 +0200 Subject: [PATCH 12/35] e2e: Add RAIDEN_SYNAPSE_MODULES --- e2e-environment/Dockerfile | 55 ++++++++++++++++++++----- e2e-environment/geth/deploy.sh | 5 ++- e2e-environment/setup/setup_channels.sh | 6 +++ e2e-environment/supervisord.conf | 2 +- 4 files changed, 55 insertions(+), 13 deletions(-) diff --git a/e2e-environment/Dockerfile b/e2e-environment/Dockerfile index e1e1b2db38..ff09201f58 100644 --- a/e2e-environment/Dockerfile +++ b/e2e-environment/Dockerfile @@ -3,6 +3,7 @@ ARG CONTRACTS_PACKAGE_VERSION="0.37.6" ARG CONTRACTS_VERSION="0.37.0" ARG SERVICES_VERSION="v0.15.4" ARG SYNAPSE_VERSION="v1.33.2" +ARG RAIDEN_SYNAPSE_MODULES="0.1.3" ARG OS_NAME="LINUX" ARG GETH_VERSION="1.10.3" ARG GETH_URL_LINUX="https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.10.3-991384a7.tar.gz" @@ -21,13 +22,27 @@ RUN git checkout ${RAIDEN_VERSION} RUN make install FROM python:3.9 as synapse-builder + +RUN python -m venv /synapse-venv && /synapse-venv/bin/pip install wheel + ARG SYNAPSE_VERSION +ARG RAIDEN_SYNAPSE_MODULES + +RUN /synapse-venv/bin/pip install \ + "matrix-synapse[postgres,redis]==${SYNAPSE_VERSION}" \ + psycopg2 \ + coincurve \ + pycryptodome \ + "twisted>=20.3.0" \ + click==7.1.2 \ + docker-py \ + raiden-synapse-modules==${RAIDEN_SYNAPSE_MODULES} -RUN python -m venv /synapse-venv \ - && /synapse-venv/bin/pip install "matrix-synapse==${SYNAPSE_VERSION}" \ - && /synapse-venv/bin/pip install psycopg2 coincurve pycryptodome +# XXX Temporary hot-patch while https://github.com/matrix-org/synapse/pull/9820 is not released yet -- note: this should +# be run in in workerless setup +RUN sed -i 's/\(\s*\)if self.worker_type/\1if True or self.worker_type/' /synapse-venv/lib/python3.9/site-packages/raiden_synapse_modules/pfs_presence_router.py -COPY synapse/auth/ /synapse-venv/lib/python3.7/site-packages/ +COPY synapse/auth/ /synapse-venv/lib/python3.9/site-packages/ FROM python:3.9 LABEL maintainer="Raiden Network Team " @@ -67,7 +82,7 @@ ARG LOCAL_BASE=/usr/local ARG DATA_DIR=/opt/chain RUN download_geth.sh && deploy.sh \ - && cp -R /opt/deployment/* ${VENV}/lib/python3.7/site-packages/raiden_contracts/data_${CONTRACTS_VERSION}/ + && cp -R /opt/deployment/* ${VENV}/lib/python3.9/site-packages/raiden_contracts/data_${CONTRACTS_VERSION}/ RUN mkdir -p /opt/synapse/config \ && mkdir -p /opt/synapse/data_well_known \ @@ -88,11 +103,13 @@ RUN git checkout "${SERVICES_VERSION}" RUN apt-get update \ && apt-get install -y --no-install-recommends python3-dev \ + # FIXME: why use the system 3.7 here? && /usr/bin/python3 -m virtualenv -p /usr/bin/python3 /opt/services/venv \ + && /opt/services/venv/bin/pip install -U pip wheel \ && /opt/services/venv/bin/pip install -r requirements.txt \ && /opt/services/venv/bin/pip install -e . \ - && mkdir -p /opt/services/keystore \ - && cp -R /opt/raiden/lib/python3.7/site-packages/raiden_contracts/data_${CONTRACTS_VERSION}/* /opt/services/venv/lib/python3.7/site-packages/raiden_contracts/data \ + && mkdir -p /opt/services/keystore +RUN cp -R ${VENV}/lib/python3.9/site-packages/raiden_contracts/data_${CONTRACTS_VERSION}/* /opt/services/venv/lib/python3.7/site-packages/raiden_contracts/data \ && rm -rf ~/.cache/pip \ && apt-get -y remove python3-dev \ && apt-get -y autoremove \ @@ -104,10 +121,28 @@ ENV DEPLOYMENT_SERVICES_INFO=/opt/deployment/deployment_services_private_net.jso COPY services/keystore/UTC--2020-03-11T15-39-16.935381228Z--2b5e1928c25c5a326dbb61fc9713876dd2904e34 /opt/services/keystore +ENV ETH_RPC="http://localhost:8545" + RUN setup_channels.sh -# HTTP, HTTP metrics, TCP replication, HTTP replication -# GETH | RAIDEN |SUP |PFS | Matrix -EXPOSE 8545 8546 8547 30303 30303/udp 5001 5002 9001 5555 80 9101 9092 9093 + +## GETH +EXPOSE 8545 8546 8547 30303 30303/udp +## PFS +EXPOSE 5555 +## RAIDEN +# HTTP +EXPOSE 5001 5002 +## MATRIX +# HTTP +EXPOSE 8008 80 +# HTTP metrics +EXPOSE 9101 +# TCP replication +EXPOSE 9092 +# HTTP replication +EXPOSE 9093 +## ??? +EXPOSE 9001 COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf diff --git a/e2e-environment/geth/deploy.sh b/e2e-environment/geth/deploy.sh index 80e568109f..98edd00047 100755 --- a/e2e-environment/geth/deploy.sh +++ b/e2e-environment/geth/deploy.sh @@ -6,6 +6,7 @@ echo "Setting up private chain" VENV=/tmp/deploy_venv python3 -m venv $VENV source $VENV/bin/activate +pip install -U pip wheel pip install mypy_extensions pip install click>=7.0 pip install raiden-contracts==${CONTRACTS_PACKAGE_VERSION} @@ -42,8 +43,8 @@ deploy_contracts.py --contract-version "${CONTRACTS_VERSION}" \ --password "${PASSWORD}" if [[ -f ${SMARTCONTRACTS_ENV_FILE} ]]; then - cp ${VENV}/lib/python3.7/site-packages/raiden_contracts/data_${CONTRACTS_VERSION}/deployment_private_net.json /opt/deployment/ - cp ${VENV}/lib/python3.7/site-packages/raiden_contracts/data_${CONTRACTS_VERSION}/deployment_services_private_net.json /opt/deployment/ + cp ${VENV}/lib/python3.9/site-packages/raiden_contracts/data_${CONTRACTS_VERSION}/deployment_private_net.json /opt/deployment/ + cp ${VENV}/lib/python3.9/site-packages/raiden_contracts/data_${CONTRACTS_VERSION}/deployment_services_private_net.json /opt/deployment/ if [[ ! -f /opt/deployment/deployment_private_net.json ]]; then echo 'Could not find the deployment_private_net.json' diff --git a/e2e-environment/setup/setup_channels.sh b/e2e-environment/setup/setup_channels.sh index 2ab418f1bc..8e81891de3 100755 --- a/e2e-environment/setup/setup_channels.sh +++ b/e2e-environment/setup/setup_channels.sh @@ -2,6 +2,9 @@ source "${SMARTCONTRACTS_ENV_FILE}" +echo "${SERVICE_REGISTRY}" +echo "${ETH_RPC}" + synapse-entrypoint.sh & SYNAPSE_PID=$! @@ -41,6 +44,9 @@ until $(curl --output /dev/null --silent --get --fail http://localhost:5555/api/ fi echo "Waiting for Pathfinding service to start (${PFS_RETRIES})" PFS_RETRIES=$((PFS_RETRIES + 1)) + + cat /opt/services/raiden-services/homeserver.log + sleep 20 done diff --git a/e2e-environment/supervisord.conf b/e2e-environment/supervisord.conf index c3afe21806..1440136506 100644 --- a/e2e-environment/supervisord.conf +++ b/e2e-environment/supervisord.conf @@ -14,4 +14,4 @@ command=/usr/local/bin/pfs-entrypoint.sh command=/opt/raiden/bin/raiden --config-file /opt/raiden/config/node1.toml --no-sync-check --user-deposit-contract-address %(ENV_USER_DEPOSIT_ADDRESS)s [program:node2] -command=/opt/raiden/bin/raiden --config-file /opt/raiden/config/node2.toml --no-sync-check --user-deposit-contract-address %(ENV_USER_DEPOSIT_ADDRESS)s \ No newline at end of file +command=/opt/raiden/bin/raiden --config-file /opt/raiden/config/node2.toml --no-sync-check --user-deposit-contract-address %(ENV_USER_DEPOSIT_ADDRESS)s From d19b5432ad6df06a6168ae2f3a575d5081572248 Mon Sep 17 00:00:00 2001 From: Paul Lange Date: Wed, 12 May 2021 10:49:23 +0200 Subject: [PATCH 13/35] e2e: Update synapse cofig --- .../synapse/exec/render_config_template.py | 18 ++++++++++++--- e2e-environment/synapse/synapse.template.yaml | 22 ++++++++----------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/e2e-environment/synapse/exec/render_config_template.py b/e2e-environment/synapse/exec/render_config_template.py index f661dbdbfb..6f5659bc57 100644 --- a/e2e-environment/synapse/exec/render_config_template.py +++ b/e2e-environment/synapse/exec/render_config_template.py @@ -3,6 +3,8 @@ import random import string from pathlib import Path +from eth_typing import ChecksumAddress +from eth_utils import to_checksum_address PATH_CONFIG = Path("/opt/synapse/config/synapse.yaml") PATH_CONFIG_TEMPLATE = Path("/opt/synapse/config/synapse.template.yaml") @@ -22,11 +24,17 @@ def get_macaroon_key() -> str: return macaroon -def render_synapse_config(server_name: str) -> None: +def render_synapse_config( + server_name: str, + eth_rpc_url: str, + service_registry_address: ChecksumAddress, +) -> None: template_content = PATH_CONFIG_TEMPLATE.read_text() rendered_config = string.Template(template_content).substitute( MACAROON_KEY=get_macaroon_key(), - SERVER_NAME=server_name + SERVER_NAME=server_name, + ETH_RPC=eth_rpc_url, + SERVICE_REGISTRY=service_registry_address, ) PATH_CONFIG.write_text(rendered_config) @@ -53,9 +61,13 @@ def generate_admin_user_credentials(): def main() -> None: server_name = os.environ["SERVER_NAME"] + eth_rpc_url = os.environ["ETH_RPC"] + service_registry_address = to_checksum_address(os.environ["SERVICE_REGISTRY"]) render_synapse_config( - server_name=server_name + server_name=server_name, + eth_rpc_url=eth_rpc_url, + service_registry_address=service_registry_address, ) render_well_known_file(server_name=server_name) generate_admin_user_credentials() diff --git a/e2e-environment/synapse/synapse.template.yaml b/e2e-environment/synapse/synapse.template.yaml index 72d9aab4dc..9d8dd44045 100644 --- a/e2e-environment/synapse/synapse.template.yaml +++ b/e2e-environment/synapse/synapse.template.yaml @@ -102,30 +102,26 @@ bcrypt_rounds: 12 trusted_third_party_id_servers: - localhost +presence: + enabled: true + presence_router: + module: raiden_synapse_modules.pfs_presence_router.PFSPresenceRouter + config: + ethereum_rpc: ${ETH_RPC} + service_registry_address: ${SERVICE_REGISTRY} + blockchain_sync_seconds: 15 ## Metrics ### enable_metrics: False report_stats: False - -## API Configuration ## -room_invite_state_types: - - "m.room.join_rules" - - "m.room.canonical_alias" - - "m.room.name" - ## Room Creation Rules ## alias_creation_rules: - - user_id: "@admin*" - alias: "#raiden_*_*" - room_id: "*" - action: allow - user_id: "*" - alias: "#raiden_*_*" + alias: "*" room_id: "*" action: deny - # A list of application service config file to use app_service_config_files: [] From c2e3d58bc90da484c96afb5123bf23479e6d250f Mon Sep 17 00:00:00 2001 From: Paul Lange Date: Mon, 17 May 2021 11:19:18 +0200 Subject: [PATCH 14/35] e2e: Add PFS account to genesis --- e2e-environment/geth/genesis.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/e2e-environment/geth/genesis.py b/e2e-environment/geth/genesis.py index 983b027566..84dae111cf 100755 --- a/e2e-environment/geth/genesis.py +++ b/e2e-environment/geth/genesis.py @@ -43,7 +43,8 @@ def main(validator: str, output: str): "0x517aAD51D0e9BbeF3c64803F86b3B9136641D9ec", "0x14791697260E4c9A71f18484C9f997B308e59325", "0x4C42F75ceae7b0CfA9588B940553EB7008546C29", - "0x9fe3a28D581c2e9d7b3632EfDdfc76022E9FA7Ea" + "0x9fe3a28D581c2e9d7b3632EfDdfc76022E9FA7Ea", + "0x2b5e1928c25c5a326dbb61fc9713876dd2904e34" ] GENESIS_STUB['config']['clique']['validators'].append(validator) From a71e977ae48209545f5c04170ec521d64467e71a Mon Sep 17 00:00:00 2001 From: Paul Lange Date: Mon, 17 May 2021 11:19:37 +0200 Subject: [PATCH 15/35] e2e: Register PFS account in SR --- e2e-environment/setup/setup_channels.sh | 31 +++++++++++++++---------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/e2e-environment/setup/setup_channels.sh b/e2e-environment/setup/setup_channels.sh index 8e81891de3..f9591a3efa 100755 --- a/e2e-environment/setup/setup_channels.sh +++ b/e2e-environment/setup/setup_channels.sh @@ -2,16 +2,6 @@ source "${SMARTCONTRACTS_ENV_FILE}" -echo "${SERVICE_REGISTRY}" -echo "${ETH_RPC}" - -synapse-entrypoint.sh & -SYNAPSE_PID=$! - -echo Synapse server is running at "${SYNAPSE_PID}" - -source /opt/raiden/bin/activate - echo Starting Chain ACCOUNT=$(cat /opt/deployment/miner.sh) @@ -28,6 +18,25 @@ geth --syncmode full --gcmode archive --datadir "${DATA_DIR}" \ GETH_PID=$! +source /opt/services/venv/bin/activate +python3 -m raiden_libs.service_registry register \ + --log-level DEBUG \ + --keystore-file /opt/services/keystore/UTC--2020-03-11T15-39-16.935381228Z--2b5e1928c25c5a326dbb61fc9713876dd2904e34 \ + --password 1234 \ + --eth-rpc "http://localhost:8545" \ + --accept-all \ + --service-url "http://test.rsb" +deactivate + +synapse-entrypoint.sh & +SYNAPSE_PID=$! + +echo Synapse server is running at "${SYNAPSE_PID}" + +source /opt/raiden/bin/activate + + + echo Start PFS source /opt/services/venv/bin/activate pfs-entrypoint.sh & @@ -45,8 +54,6 @@ until $(curl --output /dev/null --silent --get --fail http://localhost:5555/api/ echo "Waiting for Pathfinding service to start (${PFS_RETRIES})" PFS_RETRIES=$((PFS_RETRIES + 1)) - cat /opt/services/raiden-services/homeserver.log - sleep 20 done From 7b85b86d2d25c706861f82f99ef3731b179b25a3 Mon Sep 17 00:00:00 2001 From: Paul Lange Date: Mon, 17 May 2021 11:19:56 +0200 Subject: [PATCH 16/35] e2e: Write SERVICE_REGISTRY into contracts file --- e2e-environment/geth/deploy_contracts.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/e2e-environment/geth/deploy_contracts.py b/e2e-environment/geth/deploy_contracts.py index 31d810289e..ea418e1a04 100755 --- a/e2e-environment/geth/deploy_contracts.py +++ b/e2e-environment/geth/deploy_contracts.py @@ -5,7 +5,7 @@ import click from eth_account import Account from raiden_contracts.constants import CONTRACT_TOKEN_NETWORK_REGISTRY, CONTRACT_USER_DEPOSIT, \ - CONTRACT_ONE_TO_N + CONTRACT_ONE_TO_N, CONTRACT_SERVICE_REGISTRY from raiden_contracts.deploy.__main__ import ( ContractDeployer, ) @@ -170,12 +170,14 @@ def main(keystore_file: str, contract_version: str, password: str, output: str, contracts_info = deployed_service_contracts_info['contracts'] user_deposit_address = contracts_info[CONTRACT_USER_DEPOSIT]['address'] one_to_n_address = contracts_info[CONTRACT_ONE_TO_N]['address'] + service_registry_address = contracts_info[CONTRACT_SERVICE_REGISTRY]['address'] + address_file.write(f'#!/bin/sh\n') address_file.write(f'export TTT_TOKEN_ADDRESS={ttt_token_address}\n') address_file.write(f'export SVT_TOKEN_ADDRESS={svt_token_address}\n') address_file.write(f'export USER_DEPOSIT_ADDRESS={user_deposit_address}\n') address_file.write(f'export TOKEN_NETWORK_REGISTRY_ADDRESS={token_network_registry_address}\n') - address_file.write(f'export TOKEN_NETWORK_REGISTRY_ADDRESS={token_network_registry_address}\n') + address_file.write(f'export SERVICE_REGISTRY={service_registry_address}\n') address_file.write(f'export ONE_TO_N_ADDRESS={one_to_n_address}\n') address_file.close() except Exception as err: From 8c3e85a6c3778d8a2b4485b65e266f3cd87d6773 Mon Sep 17 00:00:00 2001 From: Paul Lange Date: Fri, 21 May 2021 11:18:00 +0200 Subject: [PATCH 17/35] e2e: Lower python nodes query interval --- e2e-environment/raiden/node1.toml | 1 + e2e-environment/raiden/node2.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/e2e-environment/raiden/node1.toml b/e2e-environment/raiden/node1.toml index df1671816e..a379db4c08 100644 --- a/e2e-environment/raiden/node1.toml +++ b/e2e-environment/raiden/node1.toml @@ -19,6 +19,7 @@ routing-mode = "pfs" pathfinding-service-address = "http://localhost:5555" # Misc address = "0x517aAD51D0e9BbeF3c64803F86b3B9136641D9ec" +blockchain-query-interval = 1.0 log-json = true diff --git a/e2e-environment/raiden/node2.toml b/e2e-environment/raiden/node2.toml index 97b5b714c7..b6b650fb61 100644 --- a/e2e-environment/raiden/node2.toml +++ b/e2e-environment/raiden/node2.toml @@ -19,6 +19,7 @@ routing-mode = "pfs" pathfinding-service-address = "http://localhost:5555" # Misc address = "0xCBC49ec22c93DB69c78348C90cd03A323267db86" +blockchain-query-interval = 1.0 log-json = true From dfd40778258c7fa58b0fda01432624b218826e32 Mon Sep 17 00:00:00 2001 From: Paul Lange Date: Wed, 26 May 2021 15:26:57 +0200 Subject: [PATCH 18/35] e2e: Set receiving cap Also fix some typos --- raiden-ts/tests/e2e/e2e.spec.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/raiden-ts/tests/e2e/e2e.spec.ts b/raiden-ts/tests/e2e/e2e.spec.ts index 2fae0e1c86..05490c4307 100644 --- a/raiden-ts/tests/e2e/e2e.spec.ts +++ b/raiden-ts/tests/e2e/e2e.spec.ts @@ -60,6 +60,9 @@ async function createRaiden(account: number | string | Signer): Promise confirmationBlocks: 5, autoSettle: false, // required to use `settleChannel` later autoUDCWithdraw: false, // required to use `withdrawFromUDC` later + caps: { + [Capabilities.RECEIVE]: 1, + }, }, ); } @@ -122,7 +125,7 @@ describe('e2e', () => { * - Deposit and withdrawal from the UDC, including token minting * - Opening of channels * - Deposit and withdrawal of channels with PC and LC partners - * - Direct and mediatied transfers using PC and LC implementations + * - Direct and mediated transfers using PC and LC implementations * - Channel closing and settlement * * The topology of the nodes is the following: @@ -266,7 +269,7 @@ describe('e2e', () => { /* * Send mediated payment with LC as mediator * - * For this we need to enable medition in LC2 + * For this we need to enable mediation in LC2 */ raiden2.updateConfig({ caps: { From da8a54ea08d0fb853d41bbce495d1a2e16ff2b5c Mon Sep 17 00:00:00 2001 From: Paul Lange Date: Thu, 27 May 2021 14:46:41 +0200 Subject: [PATCH 19/35] e2e: Update README --- e2e-environment/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/e2e-environment/README.md b/e2e-environment/README.md index ff1ba653da..f3150f54f9 100644 --- a/e2e-environment/README.md +++ b/e2e-environment/README.md @@ -170,7 +170,6 @@ The configuration has been slightly modified over the original `RSB` configuration to fit the purposes of the integration image. When merging changes from upstream please evaluate if these changes are required or not. -- `setup/room_ensurer.py` is based on [room_ensurer.py](https://github.com/raiden-network/raiden-service-bundle/blob/master/build/room_ensurer/room_ensurer.py) - `synapse/auth/admin_user_auth_provider.py` is based on [admin_user_auth_provider.py](https://github.com/raiden-network/raiden-service-bundle/blob/master/build/synapse/admin_user_auth_provider.py) - `synapse/auth/eth_auth_provider.py` is based on [eth_auth_provider.py](https://github.com/raiden-network/raiden-service-bundle/blob/master/build/synapse/eth_auth_provider.py) - `synapse/exec/synapse-entrypoint.sh` is based on [synapse-entrypoint.sh](https://github.com/raiden-network/raiden-service-bundle/blob/master/build/synapse/synapse-entrypoint.sh) From f6f70c07e66145cb9d8ec631e80dc60e846b4853 Mon Sep 17 00:00:00 2001 From: Paul Lange Date: Thu, 27 May 2021 14:49:01 +0200 Subject: [PATCH 20/35] e2e: Update to updated image --- .../deployment_private_net.json | 10 ++--- .../deployment_services_private_net.json | 38 +++++++++---------- .../deployment_information/smartcontracts.sh | 12 +++--- .../deployment_information/version | 2 +- e2e-environment/shared-script.sh | 2 +- 5 files changed, 32 insertions(+), 32 deletions(-) diff --git a/e2e-environment/deployment_information/deployment_private_net.json b/e2e-environment/deployment_information/deployment_private_net.json index d971be02de..b4825e5959 100644 --- a/e2e-environment/deployment_information/deployment_private_net.json +++ b/e2e-environment/deployment_information/deployment_private_net.json @@ -3,19 +3,19 @@ "chain_id": 4321, "contracts": { "SecretRegistry": { - "address": "0x6D214Ac1068e70bfc0B7C8FD695D885DD29a2f1f", - "transaction_hash": "0xa9c0eab62235069586feb8479c169222361acf116ccefe288ecdc1d4da311ce1", + "address": "0xb078e7947492472f65f329219b7892d0Eaf2fa75", + "transaction_hash": "0xaa6f401fd36b3e0b48f297f628817bb880d5df6d088df0810a2a65da144c7ead", "block_number": 4, "gas_cost": 292942, "constructor_arguments": [] }, "TokenNetworkRegistry": { - "address": "0x7439D10dd5EfB111c2434C190C23934baf6101be", - "transaction_hash": "0xb28ca185ec39f2bc1be824d22f80c61b188981a07dbcf6e9930dd03c649aa80d", + "address": "0x40ddc0514ae849Dc3413E9853710556f08570855", + "transaction_hash": "0x3fdf97c8724d28fc6bdd8398517fa8d15e340ae4b6d89e18ba96e9c58649a7c6", "block_number": 14, "gas_cost": 4907586, "constructor_arguments": [ - "0x6D214Ac1068e70bfc0B7C8FD695D885DD29a2f1f", + "0xb078e7947492472f65f329219b7892d0Eaf2fa75", 4321, 20, 555428, diff --git a/e2e-environment/deployment_information/deployment_services_private_net.json b/e2e-environment/deployment_information/deployment_services_private_net.json index 353cbf6831..fcde1e8878 100644 --- a/e2e-environment/deployment_information/deployment_services_private_net.json +++ b/e2e-environment/deployment_information/deployment_services_private_net.json @@ -3,13 +3,13 @@ "chain_id": 4321, "contracts": { "ServiceRegistry": { - "address": "0x2bC1401E8995d7F6a6eA289226E6DD59451cc632", - "transaction_hash": "0xd2d50a6ba84f20eebfb8d962a6f146b22037c52baf35193007a7f23e8ac24461", + "address": "0x28F273e67e1216Ba59F15d77673910E875Ff2ed7", + "transaction_hash": "0x64a9aeada265d2c59a806076404e1fec689d8617631662b4817510cb634652cf", "block_number": 57, "gas_cost": 2496148, "constructor_arguments": [ - "0xb8f14F655A867D738bB1F9F3A829C87437E3c157", - "0x78b601c1464d6e8B1b7ae030ba9771dC18E13a5c", + "0x81e5b41E4906100aD677A8c1230262762aC5e9a4", + "0x6f9e0Eb79b11E82b77bf474b422275EA3a20f336", 2000000000000000000000, 6, 5, @@ -19,36 +19,36 @@ ] }, "UserDeposit": { - "address": "0xCeC4467491Eb80DA00bfb3cA1A0b5A72808CBE99", - "transaction_hash": "0x5b6b23dc49fb35a31f8efb633abc2c2428d2af23f3d65d7a458a37c4fc804c13", + "address": "0x1bE2305D71dC7fB870a87BA5a0b298Dbf9ec8164", + "transaction_hash": "0x743e316601ae49bc17302eb3d73e2979ee53e38a269096d33ac6d40a40d08359", "block_number": 67, "gas_cost": 1874250, "constructor_arguments": [ - "0xb8f14F655A867D738bB1F9F3A829C87437E3c157", + "0x81e5b41E4906100aD677A8c1230262762aC5e9a4", 115792089237316195423570985008687907853269984665640564039457584007913129639935 ] }, "MonitoringService": { - "address": "0x2045951D64A323B19C284D1C9782be29A29257A9", - "transaction_hash": "0xc7b7669ec409c244fb0bf1554922dc7901d3feb284af69ffa8757dcb81694a7b", + "address": "0x1237E1fD96fb37628218fA6f999386A21bD15464", + "transaction_hash": "0xb33267fa257239f61c7cc03b2ed6074d7dcf7d4676a497ddd9bcd8636ec7ab90", "block_number": 77, - "gas_cost": 2415017, + "gas_cost": 2415081, "constructor_arguments": [ - "0xb8f14F655A867D738bB1F9F3A829C87437E3c157", - "0x2bC1401E8995d7F6a6eA289226E6DD59451cc632", - "0xCeC4467491Eb80DA00bfb3cA1A0b5A72808CBE99", - "0x7439D10dd5EfB111c2434C190C23934baf6101be" + "0x81e5b41E4906100aD677A8c1230262762aC5e9a4", + "0x28F273e67e1216Ba59F15d77673910E875Ff2ed7", + "0x1bE2305D71dC7fB870a87BA5a0b298Dbf9ec8164", + "0x40ddc0514ae849Dc3413E9853710556f08570855" ] }, "OneToN": { - "address": "0x20E39a07624623f64a60825b2282Bf1069D04A45", - "transaction_hash": "0x22a8f8448ebb9343eb8e98cfb12555531dcdabcbcf115cdca9ac8a297c52d14e", + "address": "0x304E4e02A44E11A81EC50dEF61a85F7091a5c999", + "transaction_hash": "0xdb6d7143eeb5fc354c380a43d1d277bddccc8e73e3b3c48965ca75c96938c899", "block_number": 87, - "gas_cost": 1326177, + "gas_cost": 1326241, "constructor_arguments": [ - "0xCeC4467491Eb80DA00bfb3cA1A0b5A72808CBE99", + "0x1bE2305D71dC7fB870a87BA5a0b298Dbf9ec8164", 4321, - "0x2bC1401E8995d7F6a6eA289226E6DD59451cc632" + "0x28F273e67e1216Ba59F15d77673910E875Ff2ed7" ] } } diff --git a/e2e-environment/deployment_information/smartcontracts.sh b/e2e-environment/deployment_information/smartcontracts.sh index 0e42c840de..152622a09c 100755 --- a/e2e-environment/deployment_information/smartcontracts.sh +++ b/e2e-environment/deployment_information/smartcontracts.sh @@ -1,7 +1,7 @@ #!/bin/sh -export TTT_TOKEN_ADDRESS=0x0a5A3BEc2Fd467872740fd587A49f8EeBd6DF39e -export SVT_TOKEN_ADDRESS=0xb8f14F655A867D738bB1F9F3A829C87437E3c157 -export USER_DEPOSIT_ADDRESS=0xCeC4467491Eb80DA00bfb3cA1A0b5A72808CBE99 -export TOKEN_NETWORK_REGISTRY_ADDRESS=0x7439D10dd5EfB111c2434C190C23934baf6101be -export TOKEN_NETWORK_REGISTRY_ADDRESS=0x7439D10dd5EfB111c2434C190C23934baf6101be -export ONE_TO_N_ADDRESS=0x20E39a07624623f64a60825b2282Bf1069D04A45 +export TTT_TOKEN_ADDRESS=0x29D5DbEaF204110C9EfbE3d008913c22b59e90Bc +export SVT_TOKEN_ADDRESS=0x81e5b41E4906100aD677A8c1230262762aC5e9a4 +export USER_DEPOSIT_ADDRESS=0x1bE2305D71dC7fB870a87BA5a0b298Dbf9ec8164 +export TOKEN_NETWORK_REGISTRY_ADDRESS=0x40ddc0514ae849Dc3413E9853710556f08570855 +export SERVICE_REGISTRY=0x28F273e67e1216Ba59F15d77673910E875Ff2ed7 +export ONE_TO_N_ADDRESS=0x304E4e02A44E11A81EC50dEF61a85F7091a5c999 diff --git a/e2e-environment/deployment_information/version b/e2e-environment/deployment_information/version index 0f1acbd565..99a4aef0c4 100644 --- a/e2e-environment/deployment_information/version +++ b/e2e-environment/deployment_information/version @@ -1 +1 @@ -v1.1.2 +v1.1.3 diff --git a/e2e-environment/shared-script.sh b/e2e-environment/shared-script.sh index 796a4ab20a..e874855e8e 100644 --- a/e2e-environment/shared-script.sh +++ b/e2e-environment/shared-script.sh @@ -5,7 +5,7 @@ # very beginning of each script. DOCKER_IMAGE_REPOSITORY="raidennetwork/lightclient-e2e-environment" -DOCKER_IMAGE_TAG="v1.1.2" +DOCKER_IMAGE_TAG="v1.1.3" DOCKER_IMAGE_NAME="${DOCKER_IMAGE_REPOSITORY}:${DOCKER_IMAGE_TAG}" DOCKER_CONTAINER_NAME="lc-e2e" E2E_ENVIRONMENT_DIRECTORY="$(realpath "$(dirname "${BASH_SOURCE[0]}")")" From 8da7d6cad46f394402d893ea397c672da990c075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vitor=20de=20Lima=20Matos?= Date: Fri, 21 May 2021 11:54:26 -0300 Subject: [PATCH 21/35] next: sdk: introduce optional address_metadata in messages and validate peer's displayname signature coming from service --- raiden-ts/src/messages/types.ts | 17 +++++++++++++++-- raiden-ts/src/services/utils.ts | 20 +++++++++----------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/raiden-ts/src/messages/types.ts b/raiden-ts/src/messages/types.ts index 414a7cf1cc..e9880778ec 100644 --- a/raiden-ts/src/messages/types.ts +++ b/raiden-ts/src/messages/types.ts @@ -99,11 +99,24 @@ const EnvelopeMessage = t.readonly( ]), ); -const _RouteMetadata = t.readonly( +const _AddressMetadata = t.readonly( t.type({ - route: t.readonlyArray(Address), + user_id: t.string, + displayname: t.string, + capabilities: t.string, }), ); +export interface AddressMetadata extends t.TypeOf {} +export interface AddressMetadataC + extends t.Type> {} +export const AddressMetadata: AddressMetadataC = _AddressMetadata; + +const _RouteMetadata = t.readonly( + t.intersection([ + t.type({ route: t.readonlyArray(Address) }), + t.partial({ address_metadata: t.record(t.string, AddressMetadata) }), + ]), +); export interface RouteMetadata extends t.TypeOf {} export interface RouteMetadataC extends t.Type> {} export const RouteMetadata: RouteMetadataC = _RouteMetadata; diff --git a/raiden-ts/src/services/utils.ts b/raiden-ts/src/services/utils.ts index d94492cc9d..79d7bb5e41 100644 --- a/raiden-ts/src/services/utils.ts +++ b/raiden-ts/src/services/utils.ts @@ -1,5 +1,6 @@ import type { Signer } from '@ethersproject/abstract-signer'; import { concat as concatBytes } from '@ethersproject/bytes'; +import { verifyMessage } from '@ethersproject/wallet'; import * as t from 'io-ts'; import memoize from 'lodash/memoize'; import uniqBy from 'lodash/uniqBy'; @@ -9,8 +10,9 @@ import { fromFetch } from 'rxjs/fetch'; import { catchError, first, map, mergeMap, toArray } from 'rxjs/operators'; import type { ServiceRegistry } from '../contracts'; +import { AddressMetadata } from '../messages/types'; import { MessageTypeId } from '../messages/utils'; -import { Caps } from '../transport/types'; +import type { Caps } from '../transport/types'; import { parseCaps } from '../transport/utils'; import type { RaidenEpicDeps } from '../types'; import { encode, jsonParse } from '../utils/data'; @@ -211,11 +213,6 @@ export function pfsListInfo( ); } -const PresenceFromService = t.type({ - user_id: t.string, - capabilities: t.union([Caps, t.string]), -}); - /** * @param peer - Peer address to fetch presence for * @param pfsAddrOrUrl - PFS/service address to fetch presence from @@ -233,11 +230,12 @@ export function getPresenceFromService$( mergeMap(async (res) => res.json()), map((json) => { try { - const presence = decode(PresenceFromService, json); - const capabilities = - typeof presence.capabilities === 'string' - ? parseCaps(presence.capabilities) - : presence.capabilities; + const presence = decode(AddressMetadata, json); + assert(verifyMessage(presence.user_id, presence.displayname) === peer, [ + 'Invalid metadata signature', + { peer, presence }, + ]); + const capabilities = parseCaps(presence.capabilities); assert(capabilities, ['Invalid capabilities format', presence.capabilities]); return { ...presence, capabilities }; } catch (err) { From 7d5dd7c2842cb9cda9bd5d53efc496bc4c582ffd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vitor=20de=20Lima=20Matos?= Date: Fri, 21 May 2021 19:18:26 -0300 Subject: [PATCH 22/35] next: sdk: use canonical json for metadata encoding --- raiden-ts/package.json | 1 + raiden-ts/src/messages/utils.ts | 4 ++-- raiden-ts/tests/unit/messages/LockedTransfer.json | 2 +- yarn.lock | 5 +++++ 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/raiden-ts/package.json b/raiden-ts/package.json index dbec9547fe..26993165d0 100644 --- a/raiden-ts/package.json +++ b/raiden-ts/package.json @@ -101,6 +101,7 @@ "io-ts": "^2.2.16", "isomorphic-fetch": "^3.0.0", "json-bigint": "^1.0.0", + "json-canonicalize": "^1.0.4", "lodash": "^4.17.21", "loglevel": "^1.7.1", "matrix-js-sdk": "^11.2.0", diff --git a/raiden-ts/src/messages/utils.ts b/raiden-ts/src/messages/utils.ts index e23621bbeb..b900d5f07c 100644 --- a/raiden-ts/src/messages/utils.ts +++ b/raiden-ts/src/messages/utils.ts @@ -6,6 +6,7 @@ import { encode as rlpEncode } from '@ethersproject/rlp'; import { toUtf8Bytes } from '@ethersproject/strings'; import { verifyMessage } from '@ethersproject/wallet'; import type * as t from 'io-ts'; +import { canonicalize } from 'json-canonicalize'; import logging from 'loglevel'; import type { BalanceProof } from '../channels/types'; @@ -51,8 +52,7 @@ export enum MessageTypeId { * @returns Hash of the metadata. */ function createMetadataHash(metadata: Metadata): Hash { - const routeHashes = metadata.routes.map((value) => keccak256(rlpEncode(value.route)) as Hash); - return keccak256(rlpEncode(routeHashes)) as Hash; + return keccak256(toUtf8Bytes(canonicalize(metadata))) as Hash; } /** diff --git a/raiden-ts/tests/unit/messages/LockedTransfer.json b/raiden-ts/tests/unit/messages/LockedTransfer.json index 18c7c8ae59..c70765afb7 100644 --- a/raiden-ts/tests/unit/messages/LockedTransfer.json +++ b/raiden-ts/tests/unit/messages/LockedTransfer.json @@ -23,7 +23,7 @@ "nonce": "1", "payment_identifier": "1", "recipient": "0x7461726765747461726765747461726765747461", - "signature": "0x4136eebf7095249ac02c3e6cb3d55eaf0e6ab41a5f8b114b5fb173697be9d6056e18d291798760ed3d72a48c7fdd94fb5fd0400e9bc4e5bfa0cab6513c27f40f1c", + "signature": "0xa8c6aa0cbf6c6b8f8dab91d63c3860701693e1b4309550bace19a1ed6204622c5d84dddbe9aeeaaebd3acaf8ee9df765549f1ec109fdf78fa111dc1355ad79981b", "target": "0x7461726765747461726765747461726765747461", "token": "0x746F6b656E746F6b656E746f6b656e746f6B656e", "token_network_address": "0x6E6574776F726b6E6574776F726b6e6574776f72", diff --git a/yarn.lock b/yarn.lock index 4ced3461fb..1c93ef801b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12421,6 +12421,11 @@ json-buffer@3.0.0: resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= +json-canonicalize@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/json-canonicalize/-/json-canonicalize-1.0.4.tgz#efb2d0b07df12365e39028aa70f879237ec102ea" + integrity sha512-YNr/ePzgReHwlnAm3EVV1pcimwesI+1DZr5v7WBKOc1zE1t7pjxWAPRxJFT3ll6flLIdRe0DPia/8cl2FLAZNA== + json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" From 078024dab6295bf62f89d40e96690e6763b344a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vitor=20de=20Lima=20Matos?= Date: Sat, 22 May 2021 19:19:50 -0300 Subject: [PATCH 23/35] next: sdk: introduce Via partial & validateAddressMetadata util --- raiden-ts/src/messages/actions.ts | 4 +++- raiden-ts/src/messages/utils.ts | 21 ++++++++++++++++++++- raiden-ts/src/services/utils.ts | 5 ++--- raiden-ts/src/transport/types.ts | 8 +++----- 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/raiden-ts/src/messages/actions.ts b/raiden-ts/src/messages/actions.ts index 7f69ce0379..2584d43cb6 100644 --- a/raiden-ts/src/messages/actions.ts +++ b/raiden-ts/src/messages/actions.ts @@ -2,6 +2,7 @@ import * as t from 'io-ts'; import { ServiceC } from '../services/types'; +import { Via } from '../transport/types'; import type { ActionType } from '../utils/actions'; import { createAction, createAsyncAction } from '../utils/actions'; import { Address, Signed } from '../utils/types'; @@ -17,7 +18,8 @@ export const messageSend = createAsyncAction( 'message/send/failure', t.intersection([ t.type({ message: t.union([t.string, Signed(Message)]) }), - t.partial({ msgtype: t.string, userId: t.string }), + t.partial({ msgtype: t.string }), + Via, ]), t.union([t.undefined, t.type({ via: t.string, tookMs: t.number, retries: t.number })]), ); diff --git a/raiden-ts/src/messages/utils.ts b/raiden-ts/src/messages/utils.ts index b900d5f07c..d55947e515 100644 --- a/raiden-ts/src/messages/utils.ts +++ b/raiden-ts/src/messages/utils.ts @@ -16,7 +16,7 @@ import { encode, jsonParse, jsonStringify } from '../utils/data'; import type { Address, Hash, HexString } from '../utils/types'; import { decode, Signature, Signed } from '../utils/types'; import { messageReceived } from './actions'; -import type { EnvelopeMessage, Metadata } from './types'; +import type { AddressMetadata, EnvelopeMessage, Metadata } from './types'; import { Message, MessageType } from './types'; const CMDIDs: { readonly [T in MessageType]: number } = { @@ -408,3 +408,22 @@ export function isMessageReceivedOfType(messageCodecs: C | C[ ? messageCodecs.some((c) => c.is(action.payload.message)) : messageCodecs.is(action.payload.message)); } + +/** + * Validates metadata was signed by address + * + * @param metadata - Peer's metadata + * @param address - Peer's address + * @param opts - Options + * @param opts.log - Logger instance + * @returns Metadata iff it's valid and was signed by address + */ +export function validateAddressMetadata( + metadata: AddressMetadata | undefined, + address: Address, + { log }: { log: logging.Logger } = { log: logging }, +): AddressMetadata | undefined { + if (metadata && verifyMessage(metadata.user_id, metadata.displayname) === address) + return metadata; + else if (metadata) log?.warn('Invalid address metadata', { address, metadata }); +} diff --git a/raiden-ts/src/services/utils.ts b/raiden-ts/src/services/utils.ts index 79d7bb5e41..95569b8c02 100644 --- a/raiden-ts/src/services/utils.ts +++ b/raiden-ts/src/services/utils.ts @@ -1,6 +1,5 @@ import type { Signer } from '@ethersproject/abstract-signer'; import { concat as concatBytes } from '@ethersproject/bytes'; -import { verifyMessage } from '@ethersproject/wallet'; import * as t from 'io-ts'; import memoize from 'lodash/memoize'; import uniqBy from 'lodash/uniqBy'; @@ -11,7 +10,7 @@ import { catchError, first, map, mergeMap, toArray } from 'rxjs/operators'; import type { ServiceRegistry } from '../contracts'; import { AddressMetadata } from '../messages/types'; -import { MessageTypeId } from '../messages/utils'; +import { MessageTypeId, validateAddressMetadata } from '../messages/utils'; import type { Caps } from '../transport/types'; import { parseCaps } from '../transport/utils'; import type { RaidenEpicDeps } from '../types'; @@ -231,7 +230,7 @@ export function getPresenceFromService$( map((json) => { try { const presence = decode(AddressMetadata, json); - assert(verifyMessage(presence.user_id, presence.displayname) === peer, [ + assert(validateAddressMetadata(presence, peer), [ 'Invalid metadata signature', { peer, presence }, ]); diff --git a/raiden-ts/src/transport/types.ts b/raiden-ts/src/transport/types.ts index 03daa72268..b3fb817560 100644 --- a/raiden-ts/src/transport/types.ts +++ b/raiden-ts/src/transport/types.ts @@ -1,10 +1,8 @@ import * as t from 'io-ts'; -import type { matrixPresence } from './actions'; - -export interface Presences { - [address: string]: matrixPresence.success; -} +/** Partner through which to send transfer through */ +export const Via = t.partial({ userId: t.string }); +export type Via = t.TypeOf; export const CapsPrimitive = t.union([t.string, t.number, t.boolean, t.null], 'CapsPrimitive'); export type CapsPrimitive = t.TypeOf; From c6dc8513de2c7cea24f4ad9057349c895347ca9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vitor=20de=20Lima=20Matos?= Date: Sat, 22 May 2021 19:21:24 -0300 Subject: [PATCH 24/35] next: sdk: receive address_metadata and use it for transfer.request Services now return a new `address_metadata` field which maps addresses to their respective metadata/presence object. We need to handle this, and pass it in a transfer's metadata. Besides this, mediated transfers should try to use this mapping in the incoming transfer's metadata to optimize not having to request metadata from service to send the output message. This usage, despite validated by signature, should be constrained to this transfer only (for now). --- raiden-ts/src/raiden.ts | 3 +- raiden-ts/src/services/epics/helpers.ts | 65 ++++++++----- raiden-ts/src/services/types.ts | 34 ++----- raiden-ts/src/transfers/actions.ts | 13 ++- raiden-ts/src/transfers/epics/locked.ts | 18 +--- raiden-ts/src/transfers/epics/retry.ts | 4 +- raiden-ts/src/transfers/mediate/epics.ts | 115 ++++++++++------------- raiden-ts/src/transfers/utils.ts | 29 +++++- raiden-ts/tests/unit/raiden.spec.ts | 2 +- 9 files changed, 148 insertions(+), 135 deletions(-) diff --git a/raiden-ts/src/raiden.ts b/raiden-ts/src/raiden.ts index 2966d81ae3..0e19dc3760 100644 --- a/raiden-ts/src/raiden.ts +++ b/raiden-ts/src/raiden.ts @@ -83,6 +83,7 @@ import { getSecrethash, makePaymentId, makeSecret, + metadataFromPaths, raidenTransfer, transferKey, transferKeyToMeta, @@ -954,10 +955,10 @@ export class Raiden { tokenNetwork, target, value: decodedValue, - paths, paymentId, secret, expiration, + ...metadataFromPaths(paths), }, { secrethash, direction: Direction.SENT }, ), diff --git a/raiden-ts/src/services/epics/helpers.ts b/raiden-ts/src/services/epics/helpers.ts index 36689e961d..21049128b8 100644 --- a/raiden-ts/src/services/epics/helpers.ts +++ b/raiden-ts/src/services/epics/helpers.ts @@ -4,6 +4,7 @@ import { Zero } from '@ethersproject/constants'; import { toUtf8Bytes } from '@ethersproject/strings'; import { verifyMessage } from '@ethersproject/wallet'; import { Decimal } from 'decimal.js'; +import * as t from 'io-ts'; import type { Observable } from 'rxjs'; import { defer, from, of } from 'rxjs'; import { fromFetch } from 'rxjs/fetch'; @@ -15,6 +16,7 @@ import { channelAmounts, channelKey } from '../../channels/utils'; import type { RaidenConfig } from '../../config'; import { intervalFromConfig } from '../../config'; import { Capabilities } from '../../constants'; +import { AddressMetadata } from '../../messages/types'; import type { RaidenState } from '../../state'; import type { matrixPresence } from '../../transport/actions'; import { getCap } from '../../transport/utils'; @@ -22,18 +24,30 @@ import type { Latest, RaidenEpicDeps } from '../../types'; import { jsonParse, jsonStringify } from '../../utils/data'; import { assert, ErrorCodes, networkErrors, RaidenError } from '../../utils/error'; import { lastMap, mergeWith, retryWhile } from '../../utils/rx'; -import type { Address, Signature, Signed } from '../../utils/types'; -import { decode, Int, UInt } from '../../utils/types'; +import type { Signature, Signed } from '../../utils/types'; +import { Address, decode, Int, UInt } from '../../utils/types'; import { iouClear, iouPersist, pathFind } from '../actions'; import type { IOU, Paths, PFS } from '../types'; -import { LastIOUResults, PathResults, PfsMode } from '../types'; +import { LastIOUResults, PfsMode } from '../types'; import { packIOU, pfsInfo, pfsListInfo, ServiceError, signIOU } from '../utils'; -interface Route { - iou: Signed | undefined; - paths?: Paths; - error?: ServiceError; -} +type Route = { iou: Signed | undefined } & ({ paths: Paths } | { error: ServiceError }); + +/** + * Codec for PFS API returned data + */ +const PathResults = t.type({ + result: t.array( + t.intersection([ + t.type({ + path: t.array(Address), + estimated_fee: Int(32), + }), + t.partial({ address_metadata: t.record(t.string, AddressMetadata) }), + ]), + ), +}); +type PathResults = t.TypeOf; /** * Returns a ISO string with millisecond resolution (same as PC) @@ -216,13 +230,13 @@ function getRouteFromPfs$(action: pathFind.request, deps: RaidenEpicDeps): Obser function filterPaths( state: RaidenState, action: pathFind.request, - { address, log }: RaidenEpicDeps, + { address, log }: Pick, paths: Paths, ): Paths { - const filteredPaths: Paths = []; + const filteredPaths: Mutable = []; const invalidatedRecipients = new Set
(); - for (const { path, fee } of paths) { + for (const { path, fee, ...rest } of paths) { const cleanPath = getCleanPath(path, address); const recipient = cleanPath[0]; if (invalidatedRecipients.has(recipient)) continue; @@ -244,7 +258,7 @@ function filterPaths( } else shouldSelectPath = true; if (shouldSelectPath) { - filteredPaths.push({ path: cleanPath, fee }); + filteredPaths.push({ path: cleanPath, fee, ...rest }); } else { log.warn('Invalidated received route. Reason:', reasonToNotSelect, 'Route:', cleanPath); invalidatedRecipients.add(recipient); @@ -356,23 +370,20 @@ function addFeeSafetyMargin( ); } -function parsePfsResponse(amount: UInt<32>, data: unknown, config: RaidenConfig) { +function parsePfsResponse(amount: UInt<32>, data: unknown, config: RaidenConfig): Paths { // decode results and cap also client-side for pfsMaxPaths const results = decode(PathResults, data).result.slice(0, config.pfsMaxPaths); - return results.map(({ path, estimated_fee }) => { - let fee; - if (estimated_fee.isZero()) fee = estimated_fee; + return results.map(({ estimated_fee, ...rest }) => { // add fee margins iff estimated_fee is not zero - else { - fee = addFeeSafetyMargin(estimated_fee, amount, config); - } - return { path, fee } as const; + const fee = estimated_fee.isZero() + ? estimated_fee + : addFeeSafetyMargin(estimated_fee, amount, config); + return { ...rest, fee }; }); } function shouldPersistIou(route: Route): boolean { - const { paths, error } = route; - return paths !== undefined || isNoRouteFoundError(error); + return 'paths' in route || isNoRouteFoundError(route.error); } function getCleanPath(path: readonly Address[], address: Address): readonly Address[] { @@ -401,7 +412,7 @@ export function getRoute$( deps: RaidenEpicDeps, { state, config }: Pick, targetPresence: matrixPresence.success, -): Observable<{ paths?: Paths; iou: Signed | undefined; error?: ServiceError }> { +): Observable { validateRouteTarget(action, state, targetPresence); const { tokenNetwork, target, value } = action.meta; @@ -441,7 +452,7 @@ export function validateRoute$( route: Route, ): Observable { const { tokenNetwork } = action.meta; - const { iou, paths, error } = route; + const { iou } = route; return from( // looks like mergeMap with generator doesn't handle exceptions correctly @@ -458,7 +469,8 @@ export function validateRoute$( } } - if (error) { + if ('error' in route) { + const { error } = route; if (isNoRouteFoundError(error)) { throw new RaidenError(ErrorCodes.PFS_NO_ROUTES_BETWEEN_NODES); } else { @@ -469,7 +481,8 @@ export function validateRoute$( } } - const filteredPaths = paths ? filterPaths(state, action, deps, paths) : []; + const { paths } = route; + const filteredPaths = filterPaths(state, action, deps, paths); if (filteredPaths.length) { yield pathFind.success({ paths: filteredPaths }, action.meta); diff --git a/raiden-ts/src/services/types.ts b/raiden-ts/src/services/types.ts index 54fd3f35f7..fa47ef5807 100644 --- a/raiden-ts/src/services/types.ts +++ b/raiden-ts/src/services/types.ts @@ -1,6 +1,7 @@ import * as t from 'io-ts'; import invert from 'lodash/invert'; +import { AddressMetadata } from '../messages/types'; import type { Decodable } from '../utils/types'; import { Address, Int, Signed, UInt } from '../utils/types'; @@ -25,35 +26,18 @@ export const PfsMode = { export type PfsMode = typeof PfsMode[keyof typeof PfsMode]; export const PfsModeC = t.keyof(invert(PfsMode) as { [D in PfsMode]: string }); -/** - * Codec for PFS API returned data - */ -export const PathResults = t.readonly( - t.intersection([ - t.type({ - result: t.array( - t.readonly( - t.type({ - path: t.readonlyArray(Address), - estimated_fee: Int(32), - }), - ), - ), - }), - t.partial({ feedback_token: t.string }), - ]), -); -export interface PathResults extends t.TypeOf {} - /** * Codec for raiden-ts internal representation of a PFS result/routes */ -export const Paths = t.array( +export const Paths = t.readonlyArray( t.readonly( - t.type({ - path: t.readonlyArray(Address), - fee: Int(32), - }), + t.intersection([ + t.type({ + path: t.readonlyArray(Address), + fee: Int(32), + }), + t.partial({ address_metadata: t.readonly(t.record(t.string, AddressMetadata)) }), + ]), ), ); export type Paths = t.TypeOf; diff --git a/raiden-ts/src/transfers/actions.ts b/raiden-ts/src/transfers/actions.ts index 65d465ae8f..14e663c111 100644 --- a/raiden-ts/src/transfers/actions.ts +++ b/raiden-ts/src/transfers/actions.ts @@ -5,6 +5,7 @@ import { BalanceProof } from '../channels/types'; import { LockedTransfer, LockExpired, + Metadata, Processed, SecretRequest, SecretReveal, @@ -13,7 +14,7 @@ import { WithdrawExpired, WithdrawRequest, } from '../messages/types'; -import { Paths } from '../services/types'; +import { Via } from '../transport/types'; import type { ActionType } from '../utils/actions'; import { createAction, createAsyncAction } from '../utils/actions'; import { Address, Hash, Int, Secret, Signed, UInt } from '../utils/types'; @@ -51,9 +52,12 @@ export const transfer = createAsyncAction( tokenNetwork: Address, target: Address, value: UInt(32), - paths: Paths, paymentId: UInt(8), + metadata: Metadata, + fee: Int(32), + partner: Address, }), + Via, t.partial({ secret: Secret, expiration: t.number, @@ -72,7 +76,10 @@ export namespace transfer { /** A LockedTransfer was signed and should be sent to partner */ export const transferSigned = createAction( 'transfer/signed', - t.type({ message: Signed(LockedTransfer), fee: Int(32), partner: Address }), + t.intersection([ + t.type({ message: Signed(LockedTransfer), fee: Int(32), partner: Address }), + Via, + ]), TransferId, ); export interface transferSigned extends ActionType {} diff --git a/raiden-ts/src/transfers/epics/locked.ts b/raiden-ts/src/transfers/epics/locked.ts index dfc17df49e..66a4a4dfcd 100644 --- a/raiden-ts/src/transfers/epics/locked.ts +++ b/raiden-ts/src/transfers/epics/locked.ts @@ -21,7 +21,7 @@ import { channelAmounts, channelKey, channelUniqueKey } from '../../channels/uti import type { RaidenConfig } from '../../config'; import { Capabilities } from '../../constants'; import { messageReceived, messageSend } from '../../messages/actions'; -import type { Metadata, Processed, SecretRequest } from '../../messages/types'; +import type { Processed, SecretRequest } from '../../messages/types'; import { LockedTransfer, LockExpired, @@ -117,15 +117,7 @@ function makeAndSignTransfer$( { revealTimeout, confirmationBlocks, expiryFactor }: RaidenConfig, { log, address, network, signer }: RaidenEpicDeps, ): Observable { - // assume paths are valid and recipient is first hop of first route - // compose metadata from it, and use first path fee - const metadata: Metadata = { - routes: action.payload.paths.map(({ path }) => ({ route: path })), - }; - const fee = action.payload.paths[0].fee; - const partner = action.payload.paths[0].path[0]; - - const tokenNetwork = action.payload.tokenNetwork; + const { tokenNetwork, fee, partner, userId } = action.payload; const channel = getOpenChannel(state, { tokenNetwork, partner }); assert( @@ -170,7 +162,7 @@ function makeAndSignTransfer$( ', to', action.payload.target, ', through routes', - action.payload.paths, + action.payload.metadata.routes, ', paying', fee.toString(), 'in fees.', @@ -192,13 +184,13 @@ function makeAndSignTransfer$( lock, target: action.payload.target, initiator: action.payload.initiator ?? address, - metadata, + metadata: action.payload.metadata, // passthrough unchanged metadata }; return from(signMessage(signer, message, { log })).pipe( mergeMap(function* (signed) { // messageSend LockedTransfer handled by transferRetryMessageEpic - yield transferSigned({ message: signed, fee, partner }, action.meta); + yield transferSigned({ message: signed, fee, partner, userId }, action.meta); // besides transferSigned, also yield transferSecret (for registering) if we know it if (action.payload.secret) yield transferSecret({ secret: action.payload.secret }, action.meta); diff --git a/raiden-ts/src/transfers/epics/retry.ts b/raiden-ts/src/transfers/epics/retry.ts index 6ff844c375..d270dde7c5 100644 --- a/raiden-ts/src/transfers/epics/retry.ts +++ b/raiden-ts/src/transfers/epics/retry.ts @@ -52,11 +52,13 @@ export function transferRetryMessageEpic( const transfer$ = state$.pipe(pluckDistinct('transfers', transferKey(action.meta))); let to: Address | undefined; + let viaUserId: string | undefined; let stop$: Observable | undefined; switch (action.type) { case transferSigned.type: if (action.meta.direction === Direction.SENT) { to = action.payload.partner; + viaUserId = action.payload.userId; stop$ = transfer$.pipe( filter( (transfer) => @@ -115,7 +117,7 @@ export function transferRetryMessageEpic( return retrySendUntil$( messageSend.request( - { message: action.payload.message }, + { message: action.payload.message, userId: viaUserId }, { address: to, msgId: action.payload.message.message_identifier.toString() }, ), action$, diff --git a/raiden-ts/src/transfers/mediate/epics.ts b/raiden-ts/src/transfers/mediate/epics.ts index 64214e8560..8c900b8914 100644 --- a/raiden-ts/src/transfers/mediate/epics.ts +++ b/raiden-ts/src/transfers/mediate/epics.ts @@ -6,15 +6,15 @@ import { ChannelState } from '../../channels/state'; import { channelKey } from '../../channels/utils'; import type { RaidenConfig } from '../../config'; import { Capabilities } from '../../constants'; -import type { RouteMetadata } from '../../messages/types'; +import { validateAddressMetadata } from '../../messages/utils'; import type { RaidenState } from '../../state'; +import type { Via } from '../../transport/types'; import { getCap } from '../../transport/utils'; import type { RaidenEpicDeps } from '../../types'; import type { Address, Int } from '../../utils/types'; import { isntNil } from '../../utils/types'; import { transfer, transferSigned } from '../actions'; import { Direction } from '../state'; -import { transferKey } from '../utils'; function shouldMediate(action: transferSigned, address: Address, { caps }: RaidenConfig): boolean { const isMediationEnabled = getCap(caps, Capabilities.MEDIATE); @@ -30,52 +30,64 @@ function shouldMediate(action: transferSigned, address: Address, { caps }: Raide * we return a clean set of routes where all of them go through the same partner, since we need to * know the outbound channel in order to calculate the mediation fees. * - * @param routes - RouteMetadata array containing the received routes + * @param received - received transferSigned * @param state - Current state (to check for open channels) - * @param tokenNetwork - Transfer's TokenNetwork address + * @param config - Current config * @param deps - Epics dependencies subset * @param deps.address - Our address * @param deps.log - Logger instance + * @param deps.mediationFeeCalculator - Calculator for mediating transfer's fee * @returns Filtered and cleaned routes array, or undefined if no valid route was found */ -function filterAndCleanValidRoutes( - routes: readonly RouteMetadata[], +function findValidPartner( + received: transferSigned, state: RaidenState, - tokenNetwork: Address, - { address, log }: Pick, -) { - const allPartnersWithOpenChannels = new Set( + config: RaidenConfig, + { address, log, mediationFeeCalculator }: RaidenEpicDeps, +): Pick | undefined { + const message = received.payload.message; + const inPartner = received.payload.partner; + const tokenNetwork = message.token_network_address; + const partnersWithOpenChannels = new Set( Object.values(state.channels) .filter((channel) => channel.tokenNetwork === tokenNetwork) .filter((channel) => channel.state === ChannelState.open) .map(({ partner }) => partner.address), ); - - function clearRoute(metadata: RouteMetadata) { - let route = metadata.route; + for (const { route, address_metadata } of message.metadata.routes) { const ourIndex = route.findIndex((hop) => hop === address); - // if we're in the path (front or mid), forward only remaining path, starting with partner/next hop - if (ourIndex >= 0) route = route.slice(ourIndex + 1); - // next hop must be our partner, otherwise return null to drop this route - if (!allPartnersWithOpenChannels.has(route[0])) return null; - return { ...metadata, route }; - } - let result = routes.map(clearRoute).filter(isntNil); + const outPartner = route[ourIndex + 1]; + if (!outPartner || !partnersWithOpenChannels.has(outPartner)) continue; - const outPartner = result[0]?.route[0]; - if (!outPartner) { - log.warn('Mediation: could not find a suitable route, ignoring', { - inputRoutes: routes, - openPartners: allPartnersWithOpenChannels, - }); - return; - } - // filter only for routes matching first hop; in the old days of RefundTransfer, - // we could/should retry the following partners upon receiving a refund, but now we only - // support a single outgoing partner, and drop the others (if any) - result = result.filter(({ route }) => route[0] === outPartner); + const channelIn = state.channels[channelKey({ tokenNetwork, partner: inPartner })]; + const channelOut = state.channels[channelKey({ tokenNetwork, partner: outPartner })]; - return result; + let fee: Int<32>; + try { + fee = mediationFeeCalculator.fee( + config.mediationFees, + channelIn, + channelOut, + )(message.lock.amount); + } catch (error) { + log.warn('Mediation: could not calculate mediation fee, ignoring', { error }); + return; + } + // on a transfer.request, fee is *added* to the value to get final sent amount, + // therefore here it needs to contain a negative fee, which we will "earn" instead of pay + fee = fee.mul(-1) as Int<32>; + + const outPartnerMetadata = validateAddressMetadata( + address_metadata?.[outPartner], + outPartner, + { log }, + ); + return { partner: outPartner, fee, userId: outPartnerMetadata?.user_id }; + } + log.warn('Mediation: could not find a suitable route, ignoring', { + inputRoutes: message.metadata.routes, + openPartners: partnersWithOpenChannels, + }); } /** @@ -99,45 +111,19 @@ function filterAndCleanValidRoutes( export function transferMediateEpic( action$: Observable, state$: Observable, - { address, config$, log, mediationFeeCalculator }: RaidenEpicDeps, + deps: RaidenEpicDeps, ) { + const { address, config$ } = deps; return action$.pipe( filter(transferSigned.is), withLatestFrom(config$, state$), filter(([action, config]) => shouldMediate(action, address, config)), map(([action, config, state]) => { - const receivedState = state.transfers[transferKey(action.meta)]; const message = action.payload.message; - - const tokenNetwork = message.token_network_address; const secrethash = action.meta.secrethash; - const inPartner = receivedState.partner; - - const cleanRoutes = filterAndCleanValidRoutes(message.metadata.routes, state, tokenNetwork, { - address, - log, - }); - if (!cleanRoutes) return; - const outPartner = cleanRoutes[0].route[0]; - - const channelIn = state.channels[channelKey({ tokenNetwork, partner: inPartner })]; - const channelOut = state.channels[channelKey({ tokenNetwork, partner: outPartner })]; - let fee: Int<32>; - try { - fee = mediationFeeCalculator.fee( - config.mediationFees, - channelIn, - channelOut, - )(message.lock.amount); - } catch (error) { - log.warn('Mediation: could not calculate mediation fee, ignoring', { error }); - return; - } - // on a transfer.request, fee is *added* to the value to get final sent amount, - // therefore here it needs to contain a negative fee, which we will "earn" instead of pay - fee = fee.mul(-1) as Int<32>; - const paths = cleanRoutes.map(({ route }) => ({ path: route, fee })); + const outVia = findValidPartner(action, state, config, deps); + if (!outVia) return; // request an outbound transfer to target; will fail if already sent return transfer.request( @@ -146,9 +132,10 @@ export function transferMediateEpic( target: message.target, paymentId: message.payment_identifier, value: message.lock.amount, - paths, expiration: message.lock.expiration.toNumber(), initiator: message.initiator, + metadata: message.metadata, // passthrough unchanged metadata + ...outVia, }, { secrethash, direction: Direction.SENT }, ); diff --git a/raiden-ts/src/transfers/utils.ts b/raiden-ts/src/transfers/utils.ts index 09746639b9..131dc8ee17 100644 --- a/raiden-ts/src/transfers/utils.ts +++ b/raiden-ts/src/transfers/utils.ts @@ -13,12 +13,20 @@ import type { Lock } from '../channels/types'; import { BalanceProofZero } from '../channels/types'; import { channelUniqueKey } from '../channels/utils'; import type { RaidenDatabase, TransferStateish } from '../db/types'; -import { createBalanceHash, getBalanceProofFromEnvelopeMessage } from '../messages'; +import type { Metadata } from '../messages'; +import { + createBalanceHash, + getBalanceProofFromEnvelopeMessage, + validateAddressMetadata, +} from '../messages'; +import type { Paths } from '../services/types'; import type { RaidenState } from '../state'; +import type { Via } from '../transport/types'; import { assert } from '../utils'; import { encode } from '../utils/data'; import type { Hash, HexString, Secret, UInt } from '../utils/types'; import { decode, isntNil } from '../utils/types'; +import type { transfer } from './actions'; import type { RaidenTransfer } from './state'; import { Direction, RaidenTransferStatus, TransferState } from './state'; @@ -246,3 +254,22 @@ export async function getTransfer( if (key in state.transfers) return state.transfers[key]; return decode(TransferState, await db.get(key)); } + +/** + * Contructs transfer.request's payload paramaters from received PFS's Paths + * + * @param paths - Paths array coming from PFS + * @returns Respective members of transfer.request's payload + */ +export function metadataFromPaths( + paths: Paths, +): Pick { + const routes = paths.map(({ path: route, fee: _, ...rest }) => ({ route, ...rest })); + const metadata: Metadata = { routes }; + const viaPath = paths[0]; // assume incoming Paths is clean and best path is first + const partner = viaPath.path[0]; + const fee = viaPath.fee; + const partnerMetadata = validateAddressMetadata(viaPath.address_metadata?.[partner], partner); + const via: Via = { userId: partnerMetadata?.user_id }; + return { metadata, fee, partner, ...via }; +} diff --git a/raiden-ts/tests/unit/raiden.spec.ts b/raiden-ts/tests/unit/raiden.spec.ts index b7d10fd659..e5b439a6ee 100644 --- a/raiden-ts/tests/unit/raiden.spec.ts +++ b/raiden-ts/tests/unit/raiden.spec.ts @@ -1275,8 +1275,8 @@ describe('Raiden', () => { tokenNetwork: tokenNetwork, target: partner, value: One as UInt<32>, - paths: expect.anything(), paymentId: lockedTransferMessage.payment_identifier, + metadata: expect.anything(), }), { secrethash: lockedTransferMessage.lock.secrethash, From f07941f29e04ae949a540ca8b6b3e9ae05ddd8c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vitor=20de=20Lima=20Matos?= Date: Sat, 22 May 2021 20:42:57 -0300 Subject: [PATCH 25/35] next: sdk: optimization: send messages via userId Processed, SecretRequest, SecretReveal and Unlock messages Also, don't use messageSend's userId on retries: If this message had to be retried, maybe the userId isn't valid anymore, so we remove it on retries, to force transport to re-fetch this peer's presence/metadata, or mind its own caching mechanism as needed. --- raiden-ts/src/transfers/actions.ts | 14 +++---- raiden-ts/src/transfers/epics/locked.ts | 49 ++++++++++++++++++---- raiden-ts/src/transfers/epics/processed.ts | 11 ++--- raiden-ts/src/transfers/epics/retry.ts | 3 ++ raiden-ts/src/transfers/epics/secret.ts | 27 ++++++++---- raiden-ts/src/transfers/epics/utils.ts | 17 +++++--- raiden-ts/src/transfers/utils.ts | 19 ++++++++- 7 files changed, 103 insertions(+), 37 deletions(-) diff --git a/raiden-ts/src/transfers/actions.ts b/raiden-ts/src/transfers/actions.ts index 14e663c111..a765ae222b 100644 --- a/raiden-ts/src/transfers/actions.ts +++ b/raiden-ts/src/transfers/actions.ts @@ -87,7 +87,7 @@ export interface transferSigned extends ActionType {} /** Partner acknowledge they received and processed our LockedTransfer */ export const transferProcessed = createAction( 'transfer/processed', - t.type({ message: Signed(Processed) }), + t.intersection([t.type({ message: Signed(Processed) }), Via]), TransferId, ); export interface transferProcessed extends ActionType {} @@ -124,7 +124,7 @@ export namespace transferSecretRegister { /** A valid SecretRequest received from target */ export const transferSecretRequest = createAction( 'transfer/secret/request', - t.type({ message: Signed(SecretRequest) }), + t.intersection([t.type({ message: Signed(SecretRequest) }), Via]), TransferId, ); export interface transferSecretRequest extends ActionType {} @@ -132,7 +132,7 @@ export interface transferSecretRequest extends ActionType {} @@ -142,8 +142,8 @@ export const transferUnlock = createAsyncAction( 'transfer/unlock/request', 'transfer/unlock/success', 'transfer/unlock/failure', - undefined, - t.type({ message: Signed(Unlock), partner: Address }), + t.union([t.undefined, Via]), + t.intersection([t.type({ message: Signed(Unlock), partner: Address }), Via]), ); export namespace transferUnlock { @@ -155,7 +155,7 @@ export namespace transferUnlock { /** Partner acknowledge they received and processed our Unlock */ export const transferUnlockProcessed = createAction( 'transfer/unlock/processed', - t.type({ message: Signed(Processed) }), + t.intersection([t.type({ message: Signed(Processed) }), Via]), TransferId, ); export interface transferUnlockProcessed extends ActionType {} @@ -185,7 +185,7 @@ export namespace transferExpire { /** Partner acknowledge they received and processed our LockExpired */ export const transferExpireProcessed = createAction( 'transfer/expire/processed', - t.type({ message: Signed(Processed) }), + t.intersection([t.type({ message: Signed(Processed) }), Via]), TransferId, ); export interface transferExpireProcessed extends ActionType {} diff --git a/raiden-ts/src/transfers/epics/locked.ts b/raiden-ts/src/transfers/epics/locked.ts index 66a4a4dfcd..ffa130fff8 100644 --- a/raiden-ts/src/transfers/epics/locked.ts +++ b/raiden-ts/src/transfers/epics/locked.ts @@ -65,7 +65,14 @@ import { } from '../actions'; import type { TransferState } from '../state'; import { Direction } from '../state'; -import { getLocksroot, getSecrethash, getTransfer, makeMessageId, transferKey } from '../utils'; +import { + getLocksroot, + getSecrethash, + getTransfer, + makeMessageId, + searchValidViaAddress, + transferKey, +} from '../utils'; import { matchWithdraw } from './utils'; // calculate locks array for channel end without lock with given secrethash @@ -301,7 +308,10 @@ function makeAndSignUnlock$( channel.own.locks.find((lock) => lock.secrethash === secrethash && lock.registered), 'lock expired', ); - return transferUnlock.success({ message: signed, partner }, action.meta); + return transferUnlock.success( + { message: signed, partner, userId: action.payload?.userId }, + action.meta, + ); // messageSend Unlock handled by transferRetryMessageEpic // we don't check if transfer was refunded. If partner refunded the transfer but still // forwarded the payment, we still act honestly and unlock if they revealed @@ -455,7 +465,10 @@ function receiveTransferSigned( ) { // transferProcessed again will trigger messageSend.request return of( - transferProcessed({ message: untime(transferState.transferProcessed!) }, meta), + transferProcessed( + { message: untime(transferState.transferProcessed!), userId: action.payload.userId }, + meta, + ), ); } return EMPTY; @@ -554,12 +567,18 @@ function receiveTransferSigned( mergeMap(function* ([processed, request]) { yield transferSigned({ message: locked, fee: Zero as Int<32>, partner }, meta); // sets TransferState.transferProcessed - yield transferProcessed({ message: processed }, meta); + yield transferProcessed({ message: processed, userId: action.payload.userId }, meta); if (request) { // request initiator's presence, to be able to request secret yield matrixPresence.request(undefined, { address: locked.initiator }); // request secret iff we're the target and receiving is enabled - yield transferSecretRequest({ message: request }, meta); + yield transferSecretRequest( + { + message: request, + ...searchValidViaAddress(locked.metadata, locked.initiator), + }, + meta, + ); } }), ); @@ -596,7 +615,10 @@ function receiveTransferUnlocked( ) { // transferProcessed again will trigger messageSend.request return of( - transferUnlockProcessed({ message: untime(transferState.unlockProcessed!) }, meta), + transferUnlockProcessed( + { message: untime(transferState.unlockProcessed!), userId: action.payload.userId }, + meta, + ), ); } else return EMPTY; } @@ -631,7 +653,10 @@ function receiveTransferUnlocked( if (!transferState.secret) yield transferSecret({ secret: unlock.secret }, meta); yield transferUnlock.success({ message: unlock, partner }, meta); // sets TransferState.transferProcessed - yield transferUnlockProcessed({ message: processed }, meta); + yield transferUnlockProcessed( + { message: processed, userId: action.payload.userId }, + meta, + ); yield transfer.success( { balanceProof: getBalanceProofFromEnvelopeMessage(unlock) }, meta, @@ -672,7 +697,10 @@ function receiveTransferExpired( ) { // transferProcessed again will trigger messageSend.request return of( - transferExpireProcessed({ message: untime(transferState.expiredProcessed!) }, meta), + transferExpireProcessed( + { message: untime(transferState.expiredProcessed!), userId: action.payload.userId }, + meta, + ), ); } else return EMPTY; } @@ -710,7 +738,10 @@ function receiveTransferExpired( mergeMap(function* (processed) { yield transferExpire.success({ message: expired, partner }, meta); // sets TransferState.transferProcessed - yield transferExpireProcessed({ message: processed }, meta); + yield transferExpireProcessed( + { message: processed, userId: action.payload.userId }, + meta, + ); yield transfer.failure( new RaidenError(ErrorCodes.XFER_EXPIRED, { block: locked.lock.expiration.toString(), diff --git a/raiden-ts/src/transfers/epics/processed.ts b/raiden-ts/src/transfers/epics/processed.ts index 173d26237c..3df7392e6e 100644 --- a/raiden-ts/src/transfers/epics/processed.ts +++ b/raiden-ts/src/transfers/epics/processed.ts @@ -98,13 +98,10 @@ export function transferProcessedSendEpic( ), ), map(([action, transferState]) => - messageSend.request( - { message: action.payload.message }, - { - address: transferState.partner, - msgId: action.payload.message.message_identifier.toString(), - }, - ), + messageSend.request(action.payload, { + address: transferState.partner, + msgId: action.payload.message.message_identifier.toString(), + }), ), ); } diff --git a/raiden-ts/src/transfers/epics/retry.ts b/raiden-ts/src/transfers/epics/retry.ts index d270dde7c5..cfd9752041 100644 --- a/raiden-ts/src/transfers/epics/retry.ts +++ b/raiden-ts/src/transfers/epics/retry.ts @@ -75,6 +75,7 @@ export function transferRetryMessageEpic( case transferUnlock.success.type: if (action.meta.direction === Direction.SENT) { to = action.payload.partner; + viaUserId = action.payload.userId; stop$ = transfer$.pipe( filter((transfer) => !!(transfer.unlockProcessed || transfer.channelClosed)), ); @@ -91,6 +92,7 @@ export function transferRetryMessageEpic( case transferSecretRequest.type: if (action.meta.direction === Direction.RECEIVED && transfer) { to = transfer.transfer.initiator; + viaUserId = action.payload.userId; stop$ = combineLatest([state$, transfer$]).pipe( filter( ([{ blockNumber }, transfer]) => @@ -106,6 +108,7 @@ export function transferRetryMessageEpic( case transferSecretReveal.type: if (action.meta.direction === Direction.RECEIVED && transfer) { to = transfer.partner; + viaUserId = action.payload.userId; stop$ = transfer$.pipe( filter((transfer) => !!(transfer.unlock || transfer.channelClosed)), ); diff --git a/raiden-ts/src/transfers/epics/secret.ts b/raiden-ts/src/transfers/epics/secret.ts index 9a48c593a3..cf8f220102 100644 --- a/raiden-ts/src/transfers/epics/secret.ts +++ b/raiden-ts/src/transfers/epics/secret.ts @@ -42,7 +42,7 @@ import { transferUnlock, } from '../actions'; import { Direction } from '../state'; -import { getSecrethash, makeMessageId, transferKey } from '../utils'; +import { getSecrethash, makeMessageId, searchValidViaAddress, transferKey } from '../utils'; import { dispatchAndWait$ } from './utils'; /** @@ -82,7 +82,7 @@ export function transferSecretRequestedEpic( return; } yield transferSecretRequest( - { message }, + { message, userId: action.payload.userId }, { secrethash: message.secrethash, direction: Direction.SENT }, ); }), @@ -146,7 +146,7 @@ const secretReveal$ = ( mergeMap(function* (message) { yield transferSecretReveal({ message }, action.meta); yield messageSend.request( - { message }, + { message, userId: action.payload.userId }, { address: target, msgId: message.message_identifier.toString() }, ); }), @@ -223,8 +223,11 @@ export function transferSecretRevealedEpic( !sent.channelClosed // accepts secretReveal/unlock request even if registered on-chain ) { + let viaPayload: transferUnlock.request['payload']; + // unlock _through_ sender iff message is signed + if ('signature' in message) viaPayload = { userId: action.payload.userId }; // request unlock to be composed, signed & sent to partner - yield transferUnlock.request(undefined, meta); + yield transferUnlock.request(viaPayload, meta); } } // avoid unlocking received transfers if receiving is disabled @@ -268,19 +271,27 @@ export function transferRequestUnlockEpic( first(), filter(({ transfers }) => transferKey(action.meta) in transfers), mergeMap(({ transfers }) => { - const cached = transfers[transferKey(action.meta)]?.secretReveal; + const transferState = transfers[transferKey(action.meta)]!; + const cached = transferState.secretReveal; + let signed$; if (cached) { - return of(untime(cached)); + signed$ = of(untime(cached)); } else { const message: SecretReveal = { type: MessageType.SECRET_REVEAL, message_identifier: makeMessageId(), secret: action.payload.secret, }; - return signMessage(signer, message, { log }); + signed$ = from(signMessage(signer, message, { log })); } + const via = searchValidViaAddress( + transferState.transfer.metadata, + transferState.partner, + ); + return signed$.pipe( + map((message) => transferSecretReveal({ message, ...via }, action.meta)), + ); }), - map((message) => transferSecretReveal({ message }, action.meta)), catchError((err) => { log.warn('Error trying to sign SecretReveal - ignoring', err, action.meta); return EMPTY; diff --git a/raiden-ts/src/transfers/epics/utils.ts b/raiden-ts/src/transfers/epics/utils.ts index bffa040061..ac8597ca6a 100644 --- a/raiden-ts/src/transfers/epics/utils.ts +++ b/raiden-ts/src/transfers/epics/utils.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import type { Observable } from 'rxjs'; -import { merge, of } from 'rxjs'; +import { defer, merge, of } from 'rxjs'; import { filter, ignoreElements, take } from 'rxjs/operators'; import type { RaidenAction } from '../../actions'; @@ -73,10 +73,17 @@ export function retrySendUntil$( notifier: Observable, delayMs: number | Iterator = 30e3, ): Observable { - return dispatchAndWait$(action$, send, isResponseOf(messageSend, send.meta)).pipe( - repeatUntil(notifier, delayMs), - completeWith(action$), - ); + let first = true; + return defer(() => { + if (first) { + first = false; + } else if (send.payload.userId) { + // from 1st retry on, pop payload.userId, to force re-fetch presence/metadata + const { userId: _, ...payload } = send.payload; + send = { ...send, payload }; + } + return dispatchAndWait$(action$, send, isResponseOf(messageSend, send.meta)); + }).pipe(repeatUntil(notifier, delayMs), completeWith(action$)); } /** diff --git a/raiden-ts/src/transfers/utils.ts b/raiden-ts/src/transfers/utils.ts index 131dc8ee17..61d437f448 100644 --- a/raiden-ts/src/transfers/utils.ts +++ b/raiden-ts/src/transfers/utils.ts @@ -24,7 +24,7 @@ import type { RaidenState } from '../state'; import type { Via } from '../transport/types'; import { assert } from '../utils'; import { encode } from '../utils/data'; -import type { Hash, HexString, Secret, UInt } from '../utils/types'; +import type { Address, Hash, HexString, Secret, UInt } from '../utils/types'; import { decode, isntNil } from '../utils/types'; import type { transfer } from './actions'; import type { RaidenTransfer } from './state'; @@ -273,3 +273,20 @@ export function metadataFromPaths( const via: Via = { userId: partnerMetadata?.user_id }; return { metadata, fee, partner, ...via }; } + +/** + * @param metadata - Transfer metadata to search on + * @param address - Address metadata to search for + * @returns Via object or undefined + */ +export function searchValidViaAddress( + metadata: Metadata | undefined, + address: Address | undefined, +): Via | undefined { + let userId; + if (!metadata || !address) return; + for (const { address_metadata } of metadata.routes) { + if ((userId = validateAddressMetadata(address_metadata?.[address], address)?.user_id)) + return { userId }; + } +} From 22986179fdaf1e9797fae65f018e81c666b7fec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vitor=20de=20Lima=20Matos?= Date: Tue, 25 May 2021 08:40:32 -0300 Subject: [PATCH 26/35] next: sdk: clean paths coming from dApp --- raiden-ts/src/services/epics/helpers.ts | 8 ++------ raiden-ts/src/transfers/utils.ts | 7 ++++++- raiden-ts/src/transport/epics/presence.ts | 1 - 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/raiden-ts/src/services/epics/helpers.ts b/raiden-ts/src/services/epics/helpers.ts index 21049128b8..cff410e9b5 100644 --- a/raiden-ts/src/services/epics/helpers.ts +++ b/raiden-ts/src/services/epics/helpers.ts @@ -386,12 +386,8 @@ function shouldPersistIou(route: Route): boolean { return 'paths' in route || isNoRouteFoundError(route.error); } -function getCleanPath(path: readonly Address[], address: Address): readonly Address[] { - if (path[0] === address) { - return path.slice(1); - } else { - return path; - } +function getCleanPath(path: readonly Address[], address: Address) { + return path.slice(path.indexOf(address) + 1); } function isNoRouteFoundError(error: ServiceError | undefined): boolean { diff --git a/raiden-ts/src/transfers/utils.ts b/raiden-ts/src/transfers/utils.ts index 61d437f448..03e2e3c99d 100644 --- a/raiden-ts/src/transfers/utils.ts +++ b/raiden-ts/src/transfers/utils.ts @@ -4,6 +4,7 @@ import { HashZero } from '@ethersproject/constants'; import { keccak256 } from '@ethersproject/keccak256'; import { randomBytes } from '@ethersproject/random'; import { sha256 } from '@ethersproject/sha2'; +import isEmpty from 'lodash/isEmpty'; import type { Observable } from 'rxjs'; import { defer, from, of } from 'rxjs'; import { filter, first, map, mergeMap } from 'rxjs/operators'; @@ -264,7 +265,11 @@ export async function getTransfer( export function metadataFromPaths( paths: Paths, ): Pick { - const routes = paths.map(({ path: route, fee: _, ...rest }) => ({ route, ...rest })); + // paths may come with undesired parameters, so map&filter here before passing to metadata + const routes = paths.map(({ path: route, fee: _, address_metadata }) => ({ + route, + ...(address_metadata && !isEmpty(address_metadata) ? { address_metadata } : {}), + })); const metadata: Metadata = { routes }; const viaPath = paths[0]; // assume incoming Paths is clean and best path is first const partner = viaPath.path[0]; diff --git a/raiden-ts/src/transport/epics/presence.ts b/raiden-ts/src/transport/epics/presence.ts index ca069e3dc5..d27474cb35 100644 --- a/raiden-ts/src/transport/epics/presence.ts +++ b/raiden-ts/src/transport/epics/presence.ts @@ -65,7 +65,6 @@ function searchAddressPresence$( ), ), ), - // TODO: validate signature coming from service, once displayName is sent first(), ); }), From 856ddc38a6a328112e9d2fc81b1a510ac82e9238 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vitor=20de=20Lima=20Matos?= Date: Tue, 25 May 2021 08:33:00 -0300 Subject: [PATCH 27/35] next: sdk: send our address_metadata for direct transfers --- raiden-ts/src/services/epics/helpers.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/raiden-ts/src/services/epics/helpers.ts b/raiden-ts/src/services/epics/helpers.ts index cff410e9b5..5a4727e03c 100644 --- a/raiden-ts/src/services/epics/helpers.ts +++ b/raiden-ts/src/services/epics/helpers.ts @@ -5,6 +5,7 @@ import { toUtf8Bytes } from '@ethersproject/strings'; import { verifyMessage } from '@ethersproject/wallet'; import { Decimal } from 'decimal.js'; import * as t from 'io-ts'; +import isEmpty from 'lodash/isEmpty'; import type { Observable } from 'rxjs'; import { defer, from, of } from 'rxjs'; import { fromFetch } from 'rxjs/fetch'; @@ -19,7 +20,7 @@ import { Capabilities } from '../../constants'; import { AddressMetadata } from '../../messages/types'; import type { RaidenState } from '../../state'; import type { matrixPresence } from '../../transport/actions'; -import { getCap } from '../../transport/utils'; +import { getCap, stringifyCaps } from '../../transport/utils'; import type { Latest, RaidenEpicDeps } from '../../types'; import { jsonParse, jsonStringify } from '../../utils/data'; import { assert, ErrorCodes, networkErrors, RaidenError } from '../../utils/error'; @@ -423,8 +424,22 @@ export function getRoute$( value, ) === true ) { + const addressMetadata: { [k: string]: AddressMetadata } = {}; + if (state.transport.setup) { + addressMetadata[deps.address] = { + user_id: state.transport.setup.userId, + displayname: state.transport.setup.displayName, + capabilities: stringifyCaps(config.caps ?? {}), + }; + } return of({ - paths: [{ path: [deps.address, target], fee: Zero as Int<32> }], + paths: [ + { + path: [deps.address, target], + fee: Zero as Int<32>, + ...(!isEmpty(addressMetadata) ? { address_metadata: addressMetadata } : {}), + }, + ], iou: undefined, }); } else if (pfsIsDisabled(action, config)) { From 3406a81f15d0e782fa9dc62c468a907974cd4973 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vitor=20de=20Lima=20Matos?= Date: Wed, 26 May 2021 21:16:22 -0300 Subject: [PATCH 28/35] next: sdk: revert passing route unchanged, clean it So, PC mediators assume they're the 1st address in route, and their partner 2nd, and trim it when forwarding the transfer. We comply here until they can handle passing whole metadata unchanged. --- raiden-ts/src/services/epics/helpers.ts | 1 + raiden-ts/src/transfers/mediate/epics.ts | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/raiden-ts/src/services/epics/helpers.ts b/raiden-ts/src/services/epics/helpers.ts index 5a4727e03c..7a55254182 100644 --- a/raiden-ts/src/services/epics/helpers.ts +++ b/raiden-ts/src/services/epics/helpers.ts @@ -238,6 +238,7 @@ function filterPaths( const invalidatedRecipients = new Set
(); for (const { path, fee, ...rest } of paths) { + // FIXME: don't trim path; validate us as 1st and partner as 2nd addr, once PC can handle it const cleanPath = getCleanPath(path, address); const recipient = cleanPath[0]; if (invalidatedRecipients.has(recipient)) continue; diff --git a/raiden-ts/src/transfers/mediate/epics.ts b/raiden-ts/src/transfers/mediate/epics.ts index 8c900b8914..47a745a236 100644 --- a/raiden-ts/src/transfers/mediate/epics.ts +++ b/raiden-ts/src/transfers/mediate/epics.ts @@ -55,8 +55,7 @@ function findValidPartner( .map(({ partner }) => partner.address), ); for (const { route, address_metadata } of message.metadata.routes) { - const ourIndex = route.findIndex((hop) => hop === address); - const outPartner = route[ourIndex + 1]; + const outPartner = route[route.indexOf(address) + 1]; if (!outPartner || !partnersWithOpenChannels.has(outPartner)) continue; const channelIn = state.channels[channelKey({ tokenNetwork, partner: inPartner })]; @@ -125,6 +124,17 @@ export function transferMediateEpic( const outVia = findValidPartner(action, state, config, deps); if (!outVia) return; + // FIXME: passthrough metadata unchanged once PC supports searching themselves in the route + // clean up routes (strip hops before and up to us), while PC mediators require receiving + // routes where they're the 1st address and their partner, 2nd + const metadata: typeof message.metadata = { + ...message.metadata, + routes: message.metadata.routes.map(({ route, ...rest }) => ({ + ...rest, + route: route.slice(route.indexOf(deps.address) + 1), + })), + }; + // request an outbound transfer to target; will fail if already sent return transfer.request( { @@ -134,7 +144,7 @@ export function transferMediateEpic( value: message.lock.amount, expiration: message.lock.expiration.toNumber(), initiator: message.initiator, - metadata: message.metadata, // passthrough unchanged metadata + metadata, ...outVia, }, { secrethash, direction: Direction.SENT }, From d189e4ceb1980e38a213fab50fafd5bdb639dbcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vitor=20de=20Lima=20Matos?= Date: Fri, 28 May 2021 21:56:15 -0300 Subject: [PATCH 29/35] next: sdk: fix tests --- raiden-ts/tests/integration/fixtures.ts | 75 ++++++- raiden-ts/tests/integration/mediate.spec.ts | 20 +- raiden-ts/tests/integration/mocks.ts | 1 + raiden-ts/tests/integration/patches.ts | 9 +- raiden-ts/tests/integration/path.spec.ts | 59 +++++- raiden-ts/tests/integration/receive.spec.ts | 12 +- raiden-ts/tests/integration/send.spec.ts | 21 +- raiden-ts/tests/integration/transport.spec.ts | 192 +++++------------- 8 files changed, 201 insertions(+), 188 deletions(-) diff --git a/raiden-ts/tests/integration/fixtures.ts b/raiden-ts/tests/integration/fixtures.ts index 19640df80c..617764e599 100644 --- a/raiden-ts/tests/integration/fixtures.ts +++ b/raiden-ts/tests/integration/fixtures.ts @@ -17,11 +17,13 @@ import { getTransfer, makePaymentId, makeSecret, + metadataFromPaths, transferKey, } from '@/transfers/utils'; import { matrixPresence } from '@/transport/actions'; +import { stringifyCaps } from '@/transport/utils'; +import type { Latest } from '@/types'; import { assert } from '@/utils'; -import { isResponseOf } from '@/utils/actions'; import type { Address, Hash, Int, Secret, UInt } from '@/utils/types'; import { makeAddress, makeHash, sleep } from '../utils'; @@ -122,6 +124,7 @@ export async function ensureChannelIsOpen( if (getChannel(raiden, partner)) return; const openBlock = raiden.deps.provider.blockNumber + 1; const tokenNetworkContract = raiden.deps.getTokenNetworkContract(tokenNetwork); + await ensurePresence([raiden, partner]); await providersEmit( {}, makeLog({ @@ -286,8 +289,11 @@ export async function ensureTransferPending( tokenNetwork, target: partner.address, value, - paths: [{ path: [partner.address], fee: Zero as Int<32> }], paymentId, + metadata: { routes: [{ route: [partner.address] }] }, + fee: Zero as Int<32>, + partner: partner.address, + userId: (await partner.deps.matrix$.toPromise()).getUserId()!, }, { secrethash: secrethash_, direction: Direction.SENT }, ), @@ -365,15 +371,31 @@ export async function ensurePresence([raiden, partner]: [ MockedRaiden, MockedRaiden, ]): Promise { - const raidenPromise = raiden.action$ - .pipe(first(isResponseOf(matrixPresence, { address: partner.address }))) - .toPromise(); - const partnerPromise = partner.action$ - .pipe(first(isResponseOf(matrixPresence, { address: raiden.address }))) - .toPromise(); + partner.store.dispatch( + matrixPresence.success( + { + userId: (await raiden.deps.matrix$.toPromise()).getUserId()!, + available: true, + ts: Date.now() + 120e3, + caps: (await raiden.deps.latest$.pipe(first()).toPromise()).config.caps!, + }, + { address: raiden.address }, + ), + ); + raiden.store.dispatch( + matrixPresence.success( + { + userId: (await partner.deps.matrix$.toPromise()).getUserId()!, + available: true, + ts: Date.now() + 120e3, + caps: (await partner.deps.latest$.pipe(first()).toPromise()).config.caps!, + }, + { address: partner.address }, + ), + ); + await sleep(); partner.store.dispatch(matrixPresence.request(undefined, { address: raiden.address })); raiden.store.dispatch(matrixPresence.request(undefined, { address: partner.address })); - await Promise.all([raidenPromise, partnerPromise]); } /** @@ -385,3 +407,38 @@ export function expectChannelsAreInSync([raiden, partner]: [MockedRaiden, Mocked expect(getChannel(raiden, partner).own).toStrictEqual(getChannel(partner, raiden).partner); expect(getChannel(raiden, partner).partner).toStrictEqual(getChannel(partner, raiden).own); } + +/** + * @param clients - Clients list + * @param clients."0" - Main/our raiden instance + * @param clients."1" - Other clients in path + * @param fee_ - Estimated transfer fee + * @returns metadataFromPaths for a tansfer.request's payload + */ +export function metadataFromClients( + [raiden, ...hops]: readonly [T, ...T[]], + fee_ = fee, +) { + const isRaiden = (c: T): c is T & MockedRaiden => typeof c !== 'string'; + return metadataFromPaths([ + { + path: hops.map((c) => (isRaiden(c) ? c.address : (c as Address))), + fee: fee_, + address_metadata: Object.fromEntries( + [raiden, ...hops].filter(isRaiden).map(({ address, store, deps }) => { + const setup = store.getState().transport.setup!; + let latest!: Latest; + deps.latest$.pipe(first()).subscribe((l) => (latest = l)); + return [ + address, + { + user_id: setup.userId, + displayname: setup.displayName, + capabilities: stringifyCaps(latest.config.caps!), + }, + ] as const; + }), + ), + }, + ]); +} diff --git a/raiden-ts/tests/integration/mediate.spec.ts b/raiden-ts/tests/integration/mediate.spec.ts index 5015ef856d..f10baa5bce 100644 --- a/raiden-ts/tests/integration/mediate.spec.ts +++ b/raiden-ts/tests/integration/mediate.spec.ts @@ -4,6 +4,7 @@ import { ensureChannelIsOpen, ensurePresence, getOrWaitTransfer, + metadataFromClients, secret, secrethash, token, @@ -47,8 +48,8 @@ describe('mediate transfers', () => { target: target.address, value: amount, paymentId: makePaymentId(), - paths: [{ path: [partner.address, target.address], fee: flat }], secret, + ...metadataFromClients([raiden, partner, target], flat), }, { secrethash, direction: Direction.SENT }, ), @@ -74,16 +75,25 @@ describe('mediate transfers', () => { { secrethash, direction: Direction.RECEIVED }, ), ); - expect(partner.output).toContainEqual( + expect(partner.output.find(transfer.request.is)).toEqual( transfer.request( { tokenNetwork, target: target.address, value: amount.add(flat) as UInt<32>, paymentId: transf.payment_identifier, - paths: [{ path: [target.address], fee: flat.mul(-1) as Int<32> }], expiration: transf.lock.expiration.toNumber(), initiator: raiden.address, + fee: flat.mul(-1) as Int<32>, + metadata: expect.objectContaining({ + routes: expect.arrayContaining([ + expect.objectContaining({ + route: [target.address], + }), + ]), + }), + partner: target.address, + userId: (await target.deps.matrix$.toPromise()).getUserId()!, }, { secrethash, direction: Direction.SENT }, ), @@ -114,8 +124,8 @@ describe('mediate transfers', () => { target: target.address, value: amount, paymentId: makePaymentId(), - paths: [{ path: [partner.address, target.address], fee: Zero as Int<32> }], secret, + ...metadataFromClients([raiden, partner, target], Zero as Int<32>), }, { secrethash, direction: Direction.SENT }, ), @@ -163,8 +173,8 @@ describe('mediate transfers', () => { target: unknownTarget, value: amount, paymentId: makePaymentId(), - paths: [{ path: [partner.address, unknownTarget], fee: Zero as Int<32> }], secret, + ...metadataFromClients([raiden, partner, unknownTarget], Zero as Int<32>), }, { secrethash, direction: Direction.SENT }, ), diff --git a/raiden-ts/tests/integration/mocks.ts b/raiden-ts/tests/integration/mocks.ts index c12762306c..c4637599c1 100644 --- a/raiden-ts/tests/integration/mocks.ts +++ b/raiden-ts/tests/integration/mocks.ts @@ -659,6 +659,7 @@ export async function makeRaiden( const signer = (wallet ?? makeWallet()).connect(provider); const address = signer.address as Address; const log = logging.getLogger(`raiden:${address}`); + log.setLevel(logging.levels.INFO); Object.assign(provider, { _network: network }); jest.spyOn(provider, 'on'); diff --git a/raiden-ts/tests/integration/patches.ts b/raiden-ts/tests/integration/patches.ts index f9ca090f44..a1bb07e9ee 100644 --- a/raiden-ts/tests/integration/patches.ts +++ b/raiden-ts/tests/integration/patches.ts @@ -36,12 +36,9 @@ const patchVerifyMessage = () => { return { ...jest.requireActual('@ethersproject/wallet'), __esModule: true, - verifyMessage: jest.fn((msg: string, sig: string): string => { - // TODO: remove userId special case after mockedMatrixCreateClient is used - const match = /^@(0x[0-9a-f]{40})[.:]/i.exec(msg); - if (match?.[1]) return getAddress(match[1]); - return getAddress('0x' + sig.substr(-44, 40)); - }), + verifyMessage: jest.fn((_: string, sig: string): string => + getAddress('0x' + sig.substr(-44, 40)), + ), }; }); }; diff --git a/raiden-ts/tests/integration/path.spec.ts b/raiden-ts/tests/integration/path.spec.ts index 58aa153300..ef7d61af78 100644 --- a/raiden-ts/tests/integration/path.spec.ts +++ b/raiden-ts/tests/integration/path.spec.ts @@ -4,6 +4,7 @@ import { ensureChannelIsClosed, ensureChannelIsDeposited, ensureChannelIsOpen, + ensurePresence, fee, getChannel, openBlock, @@ -15,6 +16,7 @@ import { fetch, makeLog, makeRaiden, makeRaidens, providersEmit, waitBlock } fro import { defaultAbiCoder } from '@ethersproject/abi'; import { BigNumber } from '@ethersproject/bignumber'; import { AddressZero, One, Zero } from '@ethersproject/constants'; +import { first } from 'rxjs/operators'; import { raidenConfigUpdate, raidenShutdown } from '@/actions'; import { Capabilities } from '@/constants'; @@ -24,6 +26,7 @@ import { iouClear, iouPersist, pathFind, servicesValid } from '@/services/action import { IOU, PfsMode, Service } from '@/services/types'; import { signIOU } from '@/services/utils'; import { matrixPresence } from '@/transport/actions'; +import { stringifyCaps } from '@/transport/utils'; import { jsonStringify } from '@/utils/data'; import { ErrorCodes } from '@/utils/error'; import type { Address, Int, Signature, UInt } from '@/utils/types'; @@ -61,6 +64,7 @@ describe('PFS: pfsRequestEpic', () => { const mockedPfsInfoResponse: jest.MockedFunction = jest.fn(); const mockedIouResponse: jest.MockedFunction = jest.fn(); const mockedPfsResponse: jest.MockedFunction = jest.fn(); + const mockedPresenceResponse: jest.MockedFunction = jest.fn(); function makePfsInfoResponse() { return { @@ -109,12 +113,41 @@ describe('PFS: pfsRequestEpic', () => { }; }); + mockedPresenceResponse.mockImplementation(async (url) => { + const addr = /\/(0x[0-9a-f]{40})\/metadata/i.exec(url!)?.[1]; + let client: MockedRaiden | undefined; + if (!addr || !(client = [raiden, partner, target].find(({ address }) => address === addr))) { + return { + status: 404, + ok: false, + json: jest.fn(async () => ({})), + text: jest.fn(async () => ''), + }; + } + const userId = (await client!.deps.matrix$.toPromise()).getUserId()!; + const result = { + user_id: userId, + displayname: await client!.deps.signer.signMessage(userId), + capabilities: stringifyCaps( + (await client!.deps.latest$.pipe(first()).toPromise()).config.caps!, + ), + }; + return { + status: 200, + ok: true, + json: jest.fn(async () => result), + text: jest.fn(async () => jsonStringify(result)), + }; + }); + fetch.mockImplementation(async (...args) => { const url = args[0]; - if (url?.includes?.('/iou')) { + if (url?.includes('/iou')) { return mockedIouResponse(...args); - } else if (url?.includes?.('/info')) { + } else if (url?.includes('/info')) { return mockedPfsInfoResponse(...args); + } else if (url?.includes('/metadata')) { + return mockedPresenceResponse(...args); } else { return mockedPfsResponse(...args); } @@ -136,6 +169,7 @@ describe('PFS: pfsRequestEpic', () => { mockedPfsInfoResponse.mockRestore(); mockedIouResponse.mockRestore(); mockedPfsResponse.mockRestore(); + mockedPresenceResponse.mockRestore(); }); test('fail unknown tokenNetwork', async () => { @@ -203,11 +237,8 @@ describe('PFS: pfsRequestEpic', () => { test('fail on failing matrix presence request', async () => { expect.assertions(2); - const matrix = await raiden.deps.matrix$.toPromise(); const matrixError = new Error('Unspecific matrix error for testing purpose'); - ( - matrix.searchUserDirectory as jest.MockedFunction - ).mockRejectedValue(matrixError); + mockedPresenceResponse.mockRejectedValue(matrixError); const pathFindMeta = { tokenNetwork, @@ -337,7 +368,15 @@ describe('PFS: pfsRequestEpic', () => { // self should be taken out of route expect(raiden.output).toContainEqual( pathFind.success( - { paths: [{ path: [partner.address], fee: Zero as Int<32> }] }, + { + paths: [ + { + path: [partner.address], + fee: Zero as Int<32>, + address_metadata: expect.objectContaining({ [raiden.address]: expect.anything() }), + }, + ], + }, pathFindMeta, ), ); @@ -511,6 +550,7 @@ describe('PFS: pfsRequestEpic', () => { expect.assertions(1); raiden.store.dispatch(raidenConfigUpdate({ pfsMode: PfsMode.auto, additionalServices: [] })); + await ensurePresence([raiden, target]); // invalid url raiden.deps.serviceRegistryContract.urls.mockResolvedValueOnce('""'); @@ -519,7 +559,9 @@ describe('PFS: pfsRequestEpic', () => { // invalid schema (on development mode, both http & https are accepted) raiden.deps.serviceRegistryContract.urls.mockResolvedValueOnce('ftp://not.https.url'); - raiden.store.dispatch(servicesValid(makeValidServices([pfsAddress, pfsAddress, pfsAddress]))); + raiden.store.dispatch( + servicesValid(makeValidServices([makeAddress(), makeAddress(), makeAddress()])), + ); await waitBlock(); const pathFindMeta = { @@ -606,7 +648,6 @@ describe('PFS: pfsRequestEpic', () => { }); test('success with free pfs and valid route', async () => { - // Original test(old version) fails expect.assertions(1); const freePfsInfoResponse = { ...makePfsInfoResponse(), price_info: 0 }; diff --git a/raiden-ts/tests/integration/receive.spec.ts b/raiden-ts/tests/integration/receive.spec.ts index 17c11bee68..9ecaaecf7b 100644 --- a/raiden-ts/tests/integration/receive.spec.ts +++ b/raiden-ts/tests/integration/receive.spec.ts @@ -1,8 +1,11 @@ import { + amount, ensureChannelIsDeposited, ensureTransferPending, expectChannelsAreInSync, + fee, getOrWaitTransfer, + metadataFromClients, secret, secrethash, tokenNetwork, @@ -16,7 +19,6 @@ import { waitBlock, } from './mocks'; -import { BigNumber } from '@ethersproject/bignumber'; import { One, Zero } from '@ethersproject/constants'; import { first } from 'rxjs/operators'; @@ -54,8 +56,7 @@ import { makeHash, sleep } from '../utils'; const direction = Direction.RECEIVED; const paymentId = makePaymentId(); -const value = BigNumber.from(10) as UInt<32>; -const fee = BigNumber.from(3) as Int<32>; +const value = amount; const receivedMeta = { secrethash, direction }; const sentMeta = { secrethash, direction: Direction.SENT }; @@ -81,8 +82,8 @@ describe('receive transfers', () => { tokenNetwork, target: raiden.address, value, - paths: [{ path: [raiden.address], fee }], paymentId, + ...metadataFromClients([partner, raiden]), }, sentMeta, ), @@ -149,6 +150,7 @@ describe('receive transfers', () => { type: MessageType.SECRET_REQUEST, secrethash, }), + userId: (await partner.deps.matrix$.toPromise()).getUserId()!, }, receivedMeta, ), @@ -173,8 +175,8 @@ describe('receive transfers', () => { tokenNetwork, target: raiden.address, value, - paths: [{ path: [raiden.address], fee }], paymentId, + ...metadataFromClients([partner, raiden]), }, sentMeta, ), diff --git a/raiden-ts/tests/integration/send.spec.ts b/raiden-ts/tests/integration/send.spec.ts index 33bb6b554a..0c2cc6f94b 100644 --- a/raiden-ts/tests/integration/send.spec.ts +++ b/raiden-ts/tests/integration/send.spec.ts @@ -1,10 +1,13 @@ import { + amount, ensureChannelIsDeposited, ensureTransferPending, ensureTransferUnlocked, expectChannelsAreInSync, + fee, getChannel, getOrWaitTransfer, + metadataFromClients, secret, secrethash, tokenNetwork, @@ -12,7 +15,7 @@ import { import { makeLog, makeRaiden, makeRaidens, providersEmit, waitBlock } from './mocks'; import { BigNumber } from '@ethersproject/bignumber'; -import { Two, Zero } from '@ethersproject/constants'; +import { MaxUint256, Zero } from '@ethersproject/constants'; import { keccak256 } from '@ethersproject/keccak256'; import { first, pluck } from 'rxjs/operators'; @@ -42,9 +45,7 @@ import type { MockedRaiden } from './mocks'; const direction = Direction.SENT; const paymentId = makePaymentId(); -const value = BigNumber.from(10) as UInt<32>; -const fee = BigNumber.from(3) as Int<32>; -const maxUInt256 = Two.pow(256).sub(1) as UInt<32>; +const value = amount; const meta = { secrethash, direction }; describe('send transfer', () => { @@ -60,9 +61,9 @@ describe('send transfer', () => { tokenNetwork, target: partner.address, value, - paths: [{ path: [partner.address], fee }], paymentId, secret, + ...metadataFromClients([raiden, partner]), }, meta, ); @@ -90,6 +91,7 @@ describe('send transfer', () => { message: expectedLockedTransfer, fee, partner: partner.address, + userId: (await partner.deps.matrix$.toPromise()).getUserId()!, }, meta, ), @@ -119,10 +121,10 @@ describe('send transfer', () => { { tokenNetwork, target: partner.address, - value: maxUInt256, - paths: [{ path: [partner.address], fee }], + value: MaxUint256 as UInt<32>, paymentId, secret, + ...metadataFromClients([raiden, partner]), }, meta, ); @@ -151,9 +153,9 @@ describe('send transfer', () => { tokenNetwork, target: partner.address, value, - paths: [{ path: [partner.address], fee }], paymentId, secret, + ...metadataFromClients([raiden, partner]), }, meta, ), @@ -208,6 +210,7 @@ describe('send transfer', () => { { message: expectedUnlock, partner: partner.address, + userId: (await partner.deps.matrix$.toPromise()).getUserId()!, }, meta, ), @@ -466,8 +469,8 @@ describe('transferRetryMessageEpic', () => { tokenNetwork, target: partner.address, value, - paths: [{ path: [partner.address], fee: Zero as Int<32> }], paymentId, + ...metadataFromClients([raiden, partner], Zero as Int<32>), }, meta, ), diff --git a/raiden-ts/tests/integration/transport.spec.ts b/raiden-ts/tests/integration/transport.spec.ts index ed6319bb9c..1bff94fbe9 100644 --- a/raiden-ts/tests/integration/transport.spec.ts +++ b/raiden-ts/tests/integration/transport.spec.ts @@ -1,4 +1,4 @@ -import { ensureChannelIsOpen, ensurePresence, matrixServer, token } from './fixtures'; +import { ensureChannelIsOpen, ensurePresence, matrixServer } from './fixtures'; import { fetch, makeRaiden, makeRaidens, makeSignature } from './mocks'; import { verifyMessage } from '@ethersproject/wallet'; @@ -300,16 +300,19 @@ test('matrixShutdownEpic: stopClient called on action$ completion', async () => }); describe('matrixMonitorPresenceEpic', () => { + const json = jest.fn, []>(async () => ({})); + const capabilities = 'mxc://test?Delivery=0'; + + beforeAll(() => fetch.mockClear()); + beforeEach(() => fetch.mockImplementation(async () => ({ ok: true, status: 200, json }))); + afterEach(() => fetch.mockRestore()); + test('fails when users does not have displayName', async () => { expect.assertions(1); const [raiden, partner] = await makeRaidens(2); - const matrix = (await raiden.deps.matrix$.toPromise()) as jest.Mocked; - const partnerMatrix = (await partner.deps.matrix$.toPromise()) as jest.Mocked; - matrix.searchUserDirectory.mockImplementationOnce(async () => ({ - limited: false, - results: [{ user_id: partnerMatrix.getUserId()! }], - })); + const partnerUserId = (await partner.deps.matrix$.toPromise()).getUserId()!; + json.mockImplementationOnce(async () => ({ user_id: partnerUserId })); raiden.store.dispatch(matrixPresence.request(undefined, { address: partner.address })); @@ -322,31 +325,10 @@ describe('matrixMonitorPresenceEpic', () => { test('fails when users does not have valid addresses', async () => { expect.assertions(1); const [raiden, partner] = await makeRaidens(2); - const matrix = (await raiden.deps.matrix$.toPromise()) as jest.Mocked; - - matrix.searchUserDirectory.mockImplementation(async () => ({ - limited: false, - results: [{ user_id: `@invalidUser:${matrixServer}`, display_name: 'display_name' }], - })); - - raiden.store.dispatch(matrixPresence.request(undefined, { address: partner.address })); - - await sleep(2 * raiden.config.pollingInterval); - expect(raiden.output).toContainEqual( - matrixPresence.failure(expect.any(Error), { address: partner.address }), - ); - }); - - test('fails when users does not have presence or unknown address', async () => { - expect.assertions(1); - - const [raiden, partner] = await makeRaidens(2); - const matrix = (await raiden.deps.matrix$.toPromise()) as jest.Mocked; - const partnerMatrix = (await partner.deps.matrix$.toPromise()) as jest.Mocked; - (verifyMessage as jest.Mock).mockReturnValueOnce(token); - matrix.searchUserDirectory.mockImplementation(async () => ({ - limited: false, - results: [{ user_id: partnerMatrix.getUserId()!, display_name: 'display_name' }], + json.mockImplementationOnce(async () => ({ + user_id: `@invalidUser:${matrixServer}`, + displayname: '0x1234', + capabilities, })); raiden.store.dispatch(matrixPresence.request(undefined, { address: partner.address })); @@ -361,12 +343,11 @@ describe('matrixMonitorPresenceEpic', () => { expect.assertions(1); const [raiden, partner] = await makeRaidens(2); - const matrix = (await raiden.deps.matrix$.toPromise()) as jest.Mocked; - const partnerMatrix = (await partner.deps.matrix$.toPromise()) as jest.Mocked; - - matrix.searchUserDirectory.mockImplementation(async () => ({ - limited: false, - results: [{ user_id: partnerMatrix.getUserId()!, display_name: 'display_name' }], + const partnerUserId = (await partner.deps.matrix$.toPromise()).getUserId()!; + json.mockImplementationOnce(async () => ({ + user_id: partnerUserId, + displayname: '0x1234', + capabilities, })); (verifyMessage as jest.Mock).mockImplementationOnce(() => { throw new Error('invalid signature'); @@ -380,37 +361,15 @@ describe('matrixMonitorPresenceEpic', () => { ); }); - test('success with previously monitored user', async () => { - expect.assertions(1); - const [raiden, partner] = await makeRaidens(2); - const partnerMatrix = await partner.deps.matrix$.toPromise(); - const presence = matrixPresence.success( - { userId: partnerMatrix.getUserId()!, available: false, ts: Date.now() }, - { address: partner.address }, - ); - - raiden.store.dispatch(presence); - const sliceLength = raiden.output.length; - raiden.store.dispatch(matrixPresence.request(undefined, { address: partner.address })); - - await sleep(2 * raiden.config.pollingInterval); - expect(raiden.output.slice(sliceLength)).toContainEqual(presence); - }); - - test('success with searchUserDirectory and getUserPresence', async () => { + test('success', async () => { expect.assertions(1); const [raiden, partner] = await makeRaidens(2); - const matrix = (await raiden.deps.matrix$.toPromise()) as jest.Mocked; - const partnerMatrix = (await partner.deps.matrix$.toPromise()) as jest.Mocked; - matrix.searchUserDirectory.mockImplementation(async ({ term }) => ({ - results: [ - { - user_id: `@${term}:${matrixServer}`, - display_name: `${term}_display_name`, - avatar_url: 'mxc://raiden.network/cap?Delivery=0&randomCap=test', - }, - ], + const partnerUserId = (await partner.deps.matrix$.toPromise()).getUserId()!; + json.mockImplementationOnce(async () => ({ + user_id: partnerUserId, + displayname: partner.store.getState().transport.setup!.displayName, + capabilities: capabilities + '&randomCap=test', })); raiden.store.dispatch(matrixPresence.request(undefined, { address: partner.address })); @@ -419,7 +378,7 @@ describe('matrixMonitorPresenceEpic', () => { expect(raiden.output).toContainEqual( matrixPresence.success( { - userId: partnerMatrix.getUserId()!, + userId: partnerUserId, available: true, ts: expect.any(Number), caps: { [Capabilities.DELIVERY]: 0, randomCap: 'test' }, @@ -428,32 +387,6 @@ describe('matrixMonitorPresenceEpic', () => { ), ); }); - - test('success even if some getUserPresence fails', async () => { - expect.assertions(1); - - const [raiden, partner] = await makeRaidens(2); - const matrix = (await raiden.deps.matrix$.toPromise()) as jest.Mocked; - const partnerMatrix = (await partner.deps.matrix$.toPromise()) as jest.Mocked; - matrix.searchUserDirectory.mockImplementationOnce(async () => ({ - limited: false, - results: [ - { user_id: `@${partner.address.toLowerCase()}.2:${matrixServer}`, display_name: '2' }, - { user_id: partnerMatrix.getUserId()!, display_name: '1' }, - ], - })); - matrix._http.authedRequest.mockRejectedValueOnce(new Error('Could not fetch presence')); - - raiden.store.dispatch(matrixPresence.request(undefined, { address: partner.address })); - - await sleep(2 * raiden.config.pollingInterval); - expect(raiden.output).toContainEqual( - matrixPresence.success( - { userId: partnerMatrix.getUserId()!, available: true, ts: expect.any(Number) }, - { address: partner.address }, - ), - ); - }); }); test('matrixUpdateCapsEpic', async () => { @@ -491,57 +424,29 @@ test('matrixUpdateCapsEpic', async () => { ); }); -describe('matrixLeaveUnknownRoomsEpic', () => { - test('leave unknown rooms', async () => { - expect.assertions(3); - - const raiden = await makeRaiden(); - const matrix = (await raiden.deps.matrix$.toPromise()) as jest.Mocked; - const roomId = `!unknownRoomId:${matrixServer}`; - - matrix.emit('Room', { - roomId, - getCanonicalAlias: jest.fn(), - getAliases: jest.fn(() => []), - getMyMembership: jest.fn(() => 'join'), - }); - - await sleep(); +test('matrixLeaveUnknownRoomsEpic', async () => { + expect.assertions(3); - // we should wait a little before leaving rooms - expect(matrix.leave).not.toHaveBeenCalled(); - - await sleep(500); + const raiden = await makeRaiden(); + const matrix = (await raiden.deps.matrix$.toPromise()) as jest.Mocked; + const roomId = `!unknownRoomId:${matrixServer}`; - expect(matrix.leave).toHaveBeenCalledTimes(1); - expect(matrix.leave).toHaveBeenCalledWith(roomId); + matrix.emit('Room', { + roomId, + getCanonicalAlias: jest.fn(), + getAliases: jest.fn(() => []), + getMyMembership: jest.fn(() => 'join'), }); - test('do not leave global room', async () => { - expect.assertions(2); + await sleep(); - const roomId = `!discoveryRoomId:${matrixServer}`; - const raiden = await makeRaiden(undefined); - const matrix = (await raiden.deps.matrix$.toPromise()) as jest.Mocked; - const roomAlias = `#raiden_${raiden.deps.network.name}_discovery:${matrixServer}`; + // we should wait a little before leaving rooms + expect(matrix.leave).not.toHaveBeenCalled(); - matrix.emit('Room', { - roomId, - getCanonicalAlias: jest.fn(), - getAliases: jest.fn(() => [roomAlias]), - getMyMembership: jest.fn(() => 'join'), - }); - - await sleep(); - - // we should wait a little before leaving rooms - expect(matrix.leave).not.toHaveBeenCalled(); - - await sleep(500); + await sleep(500); - // even after some time, discovery room isn't left - expect(matrix.leave).not.toHaveBeenCalled(); - }); + expect(matrix.leave).toHaveBeenCalledTimes(1); + expect(matrix.leave).toHaveBeenCalledWith(roomId); }); describe('matrixMessageSendEpic', () => { @@ -552,7 +457,7 @@ describe('matrixMessageSendEpic', () => { const [raiden, partner] = getSortedClients(await makeRaidens(2)); raiden.store.dispatch(raidenConfigUpdate({ httpTimeout: 30 })); const matrix = (await raiden.deps.matrix$.toPromise()) as jest.Mocked; - const partnerMatrix = (await partner.deps.matrix$.toPromise()) as jest.Mocked; + const userId = '@peer:server'; await ensureChannelIsOpen([raiden, partner]); await sleep(); @@ -565,7 +470,7 @@ describe('matrixMessageSendEpic', () => { .mockRejectedValueOnce(Object.assign(new Error('Failed 4'), { httpStatus: 500 })); raiden.store.dispatch( - messageSend.request({ message }, { address: partner.address, msgId: message }), + messageSend.request({ message, userId }, { address: partner.address, msgId: message }), ); await sleep(200); @@ -579,7 +484,7 @@ describe('matrixMessageSendEpic', () => { expect(matrix.sendToDevice).toHaveBeenCalledWith( 'm.room.message', expect.objectContaining({ - [partnerMatrix.getUserId()!]: { ['*']: { body: message, msgtype: 'm.text' } }, + [userId]: { ['*']: { body: message, msgtype: 'm.text' } }, }), ); }); @@ -592,16 +497,13 @@ describe('matrixMessageSendEpic', () => { const partnerMatrix = (await partner.deps.matrix$.toPromise()) as jest.Mocked; const message = await signMessage(raiden.deps.signer, processed); - raiden.store.dispatch(matrixPresence.request(undefined, { address: partner.address })); - partner.store.dispatch(matrixPresence.request(undefined, { address: raiden.address })); - // fail once, succeed on retry matrix.sendToDevice.mockRejectedValueOnce( Object.assign(new Error('Failed'), { httpStatus: 500 }), ); raiden.store.dispatch( messageSend.request( - { message }, + { message, userId: partnerMatrix.getUserId()! }, { address: partner.address, msgId: message.message_identifier.toString() }, ), ); @@ -622,7 +524,7 @@ describe('matrixMessageSendEpic', () => { }); }); - test('success: batch multiple recipients', async () => { + test('success: batch multiple recipients, request presence', async () => { expect.assertions(4); const [raiden, p1, p2] = await makeRaidens(3); From 2d4087581dfcdca945d0c86b322551cfc42a95dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vitor=20de=20Lima=20Matos?= Date: Mon, 31 May 2021 18:09:04 -0300 Subject: [PATCH 30/35] next: sdk: workaround PC's signature/serialization asymmetry PC signs metadata with checksummed addresses, but send them all lowercase-serialized. We were also decoding addresses checksummed, but `address_metadata`, being a dict, was being exempted from this change. This is just a workaround: proper fix is to perfectly sign what is received/sent (passthrough), and being able to handle receiving both. --- raiden-ts/src/messages/types.ts | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/raiden-ts/src/messages/types.ts b/raiden-ts/src/messages/types.ts index e9880778ec..89bce8f642 100644 --- a/raiden-ts/src/messages/types.ts +++ b/raiden-ts/src/messages/types.ts @@ -3,7 +3,9 @@ * They include BigNumber strings validation, enum validation (if needed), Address checksum * validation, etc, and converting everything to its respective object, where needed. */ +import { isLeft } from 'fp-ts/lib/Either'; import * as t from 'io-ts'; +import isEmpty from 'lodash/isEmpty'; import { Lock } from '../channels/types'; import { Address, Hash, Int, Secret, Signature, UInt } from '../utils/types'; @@ -119,7 +121,36 @@ const _RouteMetadata = t.readonly( ); export interface RouteMetadata extends t.TypeOf {} export interface RouteMetadataC extends t.Type> {} -export const RouteMetadata: RouteMetadataC = _RouteMetadata; +// FIXME: remove all this decoding workaround when additional_hash is hash of exact canonicalized +// metadata, without changes; it's needed while PC hashes the checksummed addresses but sends the +// lowercased serialized version +const routeMetadataPredicate = (u: RouteMetadata) => + !u.address_metadata || t.array(Address).is(Object.keys(u.address_metadata)); +export const RouteMetadata: RouteMetadataC = new t.RefinementType( + 'RouteMetadata', + (u): u is RouteMetadata => _RouteMetadata.is(u) && routeMetadataPredicate(u), + (i, c) => { + const e = _RouteMetadata.validate(i, c); + if (isLeft(e)) return e; + const a = e.right; + if (!a.address_metadata) return t.success(a); + else if (isEmpty(a.address_metadata)) { + const { address_metadata: _, ...rest } = a; + return t.success(rest); + } + const address_metadata: NonNullable = {}; + // for each key of address_metadata's record, validate/decode it as Address + for (const [addr, meta] of Object.entries(a.address_metadata!)) { + const ev = Address.validate(addr, c); + if (isLeft(ev)) return ev; + address_metadata[ev.right] = meta; + } + return t.success({ ...a, address_metadata }); + }, + _RouteMetadata.encode, + _RouteMetadata, + routeMetadataPredicate, +); const _Metadata = t.readonly( t.type({ From 5edbe84506db83a1b951a70327fc5bb8a0d77faf Mon Sep 17 00:00:00 2001 From: Paul Lange Date: Thu, 10 Jun 2021 11:46:57 +0200 Subject: [PATCH 31/35] e2e: Update raiden & synapse --- e2e-environment/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e-environment/Dockerfile b/e2e-environment/Dockerfile index ff09201f58..eaa2a3543a 100644 --- a/e2e-environment/Dockerfile +++ b/e2e-environment/Dockerfile @@ -1,8 +1,8 @@ -ARG RAIDEN_VERSION="v2.0.0rc0" +ARG RAIDEN_VERSION="v2.0.0" ARG CONTRACTS_PACKAGE_VERSION="0.37.6" ARG CONTRACTS_VERSION="0.37.0" ARG SERVICES_VERSION="v0.15.4" -ARG SYNAPSE_VERSION="v1.33.2" +ARG SYNAPSE_VERSION="v1.35.1" ARG RAIDEN_SYNAPSE_MODULES="0.1.3" ARG OS_NAME="LINUX" ARG GETH_VERSION="1.10.3" From 836d03fbeb35ab2033731207eca4d218773fbe84 Mon Sep 17 00:00:00 2001 From: Paul Lange Date: Thu, 10 Jun 2021 11:59:52 +0200 Subject: [PATCH 32/35] e2e: Update to latest image --- .circleci/config.yml | 2 +- .../deployment_private_net.json | 10 ++--- .../deployment_services_private_net.json | 42 +++++++++---------- .../deployment_information/smartcontracts.sh | 12 +++--- .../deployment_information/version | 2 +- e2e-environment/shared-script.sh | 2 +- 6 files changed, 35 insertions(+), 35 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 62c0de7115..f584618308 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,7 +7,7 @@ parameters: e2e_environment_docker_image: type: string - default: raidennetwork/lightclient-e2e-environment:v1.1.2 + default: raidennetwork/lightclient-e2e-environment:v1.1.4 working_directory: type: string diff --git a/e2e-environment/deployment_information/deployment_private_net.json b/e2e-environment/deployment_information/deployment_private_net.json index b4825e5959..ba218c15e6 100644 --- a/e2e-environment/deployment_information/deployment_private_net.json +++ b/e2e-environment/deployment_information/deployment_private_net.json @@ -3,19 +3,19 @@ "chain_id": 4321, "contracts": { "SecretRegistry": { - "address": "0xb078e7947492472f65f329219b7892d0Eaf2fa75", - "transaction_hash": "0xaa6f401fd36b3e0b48f297f628817bb880d5df6d088df0810a2a65da144c7ead", + "address": "0x970514b8403ae51fd278Cd6bdDB65030B0D6E0c4", + "transaction_hash": "0x6f321e34860ab1bba642463e45bf99edb1014e1705ef45b8c6bd90b6156bc967", "block_number": 4, "gas_cost": 292942, "constructor_arguments": [] }, "TokenNetworkRegistry": { - "address": "0x40ddc0514ae849Dc3413E9853710556f08570855", - "transaction_hash": "0x3fdf97c8724d28fc6bdd8398517fa8d15e340ae4b6d89e18ba96e9c58649a7c6", + "address": "0x0b5b27dB8F19F5a2E4cebCe21BAD26DAd7021d3f", + "transaction_hash": "0x29e4ab150530586192a465edf5ee44551108375e492d8944fd4abf213b53ac5f", "block_number": 14, "gas_cost": 4907586, "constructor_arguments": [ - "0xb078e7947492472f65f329219b7892d0Eaf2fa75", + "0x970514b8403ae51fd278Cd6bdDB65030B0D6E0c4", 4321, 20, 555428, diff --git a/e2e-environment/deployment_information/deployment_services_private_net.json b/e2e-environment/deployment_information/deployment_services_private_net.json index fcde1e8878..f18b958f67 100644 --- a/e2e-environment/deployment_information/deployment_services_private_net.json +++ b/e2e-environment/deployment_information/deployment_services_private_net.json @@ -3,13 +3,13 @@ "chain_id": 4321, "contracts": { "ServiceRegistry": { - "address": "0x28F273e67e1216Ba59F15d77673910E875Ff2ed7", - "transaction_hash": "0x64a9aeada265d2c59a806076404e1fec689d8617631662b4817510cb634652cf", - "block_number": 57, + "address": "0x1BDA6f48607297E2E361Ec9ed714DABFa3D2566c", + "transaction_hash": "0x9016279be30fe4d8349515cea39cb24288919a0bb3af183f5343c7fae860581a", + "block_number": 56, "gas_cost": 2496148, "constructor_arguments": [ - "0x81e5b41E4906100aD677A8c1230262762aC5e9a4", - "0x6f9e0Eb79b11E82b77bf474b422275EA3a20f336", + "0xD68bA462913F1c4397bb9562326eA2D5DE042514", + "0xcAe3348675485be8885cdf8Db0072de3503f8cbA", 2000000000000000000000, 6, 5, @@ -19,36 +19,36 @@ ] }, "UserDeposit": { - "address": "0x1bE2305D71dC7fB870a87BA5a0b298Dbf9ec8164", - "transaction_hash": "0x743e316601ae49bc17302eb3d73e2979ee53e38a269096d33ac6d40a40d08359", - "block_number": 67, + "address": "0x8129F1294058086171F945196fE0A4E07593f522", + "transaction_hash": "0x302423bcf6ef7e228f3470e74d7d8929e942555091d41c9a4ead3a91bd825f45", + "block_number": 66, "gas_cost": 1874250, "constructor_arguments": [ - "0x81e5b41E4906100aD677A8c1230262762aC5e9a4", + "0xD68bA462913F1c4397bb9562326eA2D5DE042514", 115792089237316195423570985008687907853269984665640564039457584007913129639935 ] }, "MonitoringService": { - "address": "0x1237E1fD96fb37628218fA6f999386A21bD15464", - "transaction_hash": "0xb33267fa257239f61c7cc03b2ed6074d7dcf7d4676a497ddd9bcd8636ec7ab90", - "block_number": 77, + "address": "0x0F8ad70f3DEe8118a6E2cA71321c312E7C3dc3ee", + "transaction_hash": "0x53050f3fdac4b9ff4d7b47c0d4fe1ab8f9d718a9df4c3c343a93de5edc2303e0", + "block_number": 76, "gas_cost": 2415081, "constructor_arguments": [ - "0x81e5b41E4906100aD677A8c1230262762aC5e9a4", - "0x28F273e67e1216Ba59F15d77673910E875Ff2ed7", - "0x1bE2305D71dC7fB870a87BA5a0b298Dbf9ec8164", - "0x40ddc0514ae849Dc3413E9853710556f08570855" + "0xD68bA462913F1c4397bb9562326eA2D5DE042514", + "0x1BDA6f48607297E2E361Ec9ed714DABFa3D2566c", + "0x8129F1294058086171F945196fE0A4E07593f522", + "0x0b5b27dB8F19F5a2E4cebCe21BAD26DAd7021d3f" ] }, "OneToN": { - "address": "0x304E4e02A44E11A81EC50dEF61a85F7091a5c999", - "transaction_hash": "0xdb6d7143eeb5fc354c380a43d1d277bddccc8e73e3b3c48965ca75c96938c899", - "block_number": 87, + "address": "0xfd5500337063D12236bB6cc9330D32b210c1e5FD", + "transaction_hash": "0x9f368f62adf7ade9fd8b60901a6e82b47a83645a426c7a735c4f2a1f2008ef93", + "block_number": 86, "gas_cost": 1326241, "constructor_arguments": [ - "0x1bE2305D71dC7fB870a87BA5a0b298Dbf9ec8164", + "0x8129F1294058086171F945196fE0A4E07593f522", 4321, - "0x28F273e67e1216Ba59F15d77673910E875Ff2ed7" + "0x1BDA6f48607297E2E361Ec9ed714DABFa3D2566c" ] } } diff --git a/e2e-environment/deployment_information/smartcontracts.sh b/e2e-environment/deployment_information/smartcontracts.sh index 152622a09c..cccef34d78 100755 --- a/e2e-environment/deployment_information/smartcontracts.sh +++ b/e2e-environment/deployment_information/smartcontracts.sh @@ -1,7 +1,7 @@ #!/bin/sh -export TTT_TOKEN_ADDRESS=0x29D5DbEaF204110C9EfbE3d008913c22b59e90Bc -export SVT_TOKEN_ADDRESS=0x81e5b41E4906100aD677A8c1230262762aC5e9a4 -export USER_DEPOSIT_ADDRESS=0x1bE2305D71dC7fB870a87BA5a0b298Dbf9ec8164 -export TOKEN_NETWORK_REGISTRY_ADDRESS=0x40ddc0514ae849Dc3413E9853710556f08570855 -export SERVICE_REGISTRY=0x28F273e67e1216Ba59F15d77673910E875Ff2ed7 -export ONE_TO_N_ADDRESS=0x304E4e02A44E11A81EC50dEF61a85F7091a5c999 +export TTT_TOKEN_ADDRESS=0xe814154d412F0D708090873B198E0c9d6764D42f +export SVT_TOKEN_ADDRESS=0xD68bA462913F1c4397bb9562326eA2D5DE042514 +export USER_DEPOSIT_ADDRESS=0x8129F1294058086171F945196fE0A4E07593f522 +export TOKEN_NETWORK_REGISTRY_ADDRESS=0x0b5b27dB8F19F5a2E4cebCe21BAD26DAd7021d3f +export SERVICE_REGISTRY=0x1BDA6f48607297E2E361Ec9ed714DABFa3D2566c +export ONE_TO_N_ADDRESS=0xfd5500337063D12236bB6cc9330D32b210c1e5FD diff --git a/e2e-environment/deployment_information/version b/e2e-environment/deployment_information/version index 99a4aef0c4..c641220244 100644 --- a/e2e-environment/deployment_information/version +++ b/e2e-environment/deployment_information/version @@ -1 +1 @@ -v1.1.3 +v1.1.4 diff --git a/e2e-environment/shared-script.sh b/e2e-environment/shared-script.sh index e874855e8e..55c5b4797d 100644 --- a/e2e-environment/shared-script.sh +++ b/e2e-environment/shared-script.sh @@ -5,7 +5,7 @@ # very beginning of each script. DOCKER_IMAGE_REPOSITORY="raidennetwork/lightclient-e2e-environment" -DOCKER_IMAGE_TAG="v1.1.3" +DOCKER_IMAGE_TAG="v1.1.4" DOCKER_IMAGE_NAME="${DOCKER_IMAGE_REPOSITORY}:${DOCKER_IMAGE_TAG}" DOCKER_CONTAINER_NAME="lc-e2e" E2E_ENVIRONMENT_DIRECTORY="$(realpath "$(dirname "${BASH_SOURCE[0]}")")" From 0a322d24dc1b72e8b1a91feec1de809167ffcd2f Mon Sep 17 00:00:00 2001 From: Paul Lange Date: Thu, 10 Jun 2021 14:28:14 +0200 Subject: [PATCH 33/35] e2e: Describe image update procedure in README --- e2e-environment/README.md | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/e2e-environment/README.md b/e2e-environment/README.md index f3150f54f9..64ca222ae4 100644 --- a/e2e-environment/README.md +++ b/e2e-environment/README.md @@ -25,9 +25,10 @@ docker pull raidennetwork/lightclient-e2e-environment ``` Alternatively it can be built locally as well. This requires to use the -according script for the maintaince of the deployment information files. These +according script for the maintenance of the deployment information files. These will be automatically staged to the VCS after the script has run and need to be -commited afterwards. +committed afterwards. + ```sh bash ./build-e2e-environment.sh ``` @@ -184,3 +185,25 @@ file. Watch-out for the `SYNAPSE_VERSION` constant variable. Then update the ```dockerfile ARG SYNAPSE_VERSION=1.10.1 ``` + +## Upgrade image version for tests and CI + +To upgrade the end-to-end environment you need to build and upload a new image. +First update any component as described above. + +Then build and test those versions locally. Finally you need to increment +`DOCKER_IMAGE_TAG` in `shared-script.sh` and build the new image version: + +```sh +./build-e2e-environment.sh +``` + +This will also tag the image. You can then upload it to docker hub: + +``` +docker push raidennetwork/lightclient-e2e-environment:v1.1.4 +``` + +Once this is done, don't forget to update the image version used in CI. In +`.circleci/config.yml` update `e2e_environment_docker_image` to the version you +just created. From f387aab814a2f4cb76b6f0e31cc8bd2f174de9fc Mon Sep 17 00:00:00 2001 From: Paul Lange Date: Wed, 9 Jun 2021 15:01:29 +0200 Subject: [PATCH 34/35] Revert "dapp: Use demo env on staging temporarily" This reverts commit 4198e179fa1e570f3367a01d6ce21670d9b98f21. --- raiden-dapp/.env.staging | 3 --- 1 file changed, 3 deletions(-) diff --git a/raiden-dapp/.env.staging b/raiden-dapp/.env.staging index f568d8a352..bcd1f3ea95 100644 --- a/raiden-dapp/.env.staging +++ b/raiden-dapp/.env.staging @@ -1,8 +1,5 @@ VUE_APP_PUBLIC_PATH=/staging/ VUE_APP_HUB=hub.raiden.eth VUE_APP_ALLOW_MAINNET=false -VUE_APP_PFS=https://pfs.demo001.env.raiden.network -VUE_APP_MATRIX_SERVER=https://transport.demo001.env.raiden.network -VUE_APP_UDC_ADDRESS=0x0794F09913AA8C77C8c5bdd1Ec4Bb51759Ee0cC5 VUE_APP_IMPRINT=https://raiden.network/privacy.html VUE_APP_TERMS=https://github.com/raiden-network/light-client/blob/master/TERMS.md From 3837b941309c6f01043e7537188f5c54ca96b995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vitor=20de=20Lima=20Matos?= Date: Tue, 15 Jun 2021 14:17:05 -0300 Subject: [PATCH 35/35] next: sdk: add changelog entries --- raiden-ts/CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/raiden-ts/CHANGELOG.md b/raiden-ts/CHANGELOG.md index 92b13d8f10..01c1b3246c 100644 --- a/raiden-ts/CHANGELOG.md +++ b/raiden-ts/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## [Unreleased] +### Removed +- [#2571] **BREAKING** Remove ability to join and send messages to global service rooms +- [#2822] **BREAKING** Do not join global rooms anymore, so Matrix-based presence won't work + +### Changed +- [#2572] **BREAKING** Send services messages through `toDevice` instead of global rooms +- [#2822] **BREAKING** Presence now gets fetched from PFS and requires a Bespin-compatible (Raiden 2.0) service and transport network + +### Added +- [#2822] Added ability to use peer's presence from `LockedTransfer`'s `metadata.routes.address_metadata` + +[#2571]: https://github.com/raiden-network/light-client/issues/2571 +[#2572]: https://github.com/raiden-network/light-client/issues/2572 +[#2822]: https://github.com/raiden-network/light-client/pull/2822 + ## [0.17.0] - 2021-06-15 ### Added - [#1576] Add functionality to deploy token networks