From e9cf1f8ed5e0165671832e1fa6068249339a3a2e Mon Sep 17 00:00:00 2001 From: IvanBlacky Date: Fri, 8 Feb 2019 13:40:13 +0300 Subject: [PATCH] #3 Refactor: resolve review comments --- .env.example | 7 +-- Dockerfile | 7 +++ README.md | 51 +++++++++++++++---- docker-compose.yaml | 31 ----------- docker-compose.yml | 33 ++++++++++++ .../BandwidthProvider.js | 32 ++++++------ src/data/env.js | 3 +- src/services/Connector.js | 13 +++-- src/services/StorageService.js | 4 +- 9 files changed, 113 insertions(+), 68 deletions(-) create mode 100644 Dockerfile delete mode 100644 docker-compose.yaml create mode 100644 docker-compose.yml rename src/{services => controllers}/BandwidthProvider.js (77%) diff --git a/.env.example b/.env.example index 0d85920..5852069 100644 --- a/.env.example +++ b/.env.example @@ -1,10 +1,11 @@ -GLS_CONNECTOR_HOST=127.0.0.1 +GLS_CONNECTOR_HOST=0.0.0.0 GLS_CONNECTOR_PORT=3000 GLS_METRICS_HOST= GLS_METRICS_PORT= -GLS_MONGO_CONNECT=mongodb://localhost:27017/admin +GLS_MONGO_CONNECT=mongodb://bandwidth_mongo:27017/admin CMN_CYBERWAY_HTTP_URL= CMN_PROVIDER_PUBLIC_KEY= CMN_PROVIDER_WIF= CMN_PROVIDER_USERNAME= -GLS_CHANNEL_TTL= +CMN_REGISTRATION_CONNECT= +CMN_CHANNEL_TTL= diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0c6c8c5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +FROM node:10-alpine +WORKDIR /usr/src/app +COPY ./package*.json ./ +COPY ./.npmrc ./ +RUN npm install --only=production +COPY ./src/ ./src +CMD [ "node", "./src/index.js" ] diff --git a/README.md b/README.md index 5e91b92..7e0e5e6 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,67 @@ # Bandwidth service -**Bandwidth service** является сервисом для предоставления необходимых мощностей пользователям [golos.io](https://golos.io) (например, для публикации постов). +**Bandwidth service** является сервисом для предоставления необходимых мощностей (бендвича) пользователям [golos.io](https://golos.io) (например, для публикации постов). Сервис подписывает транзакции вместо пользователя, сохраняя его авторство. ### Основное -Микросервис реализует один метод: +Микросервис содержит 2 контроллера и 1 сервис, помимо служебных: -- 'bandwidth.provide': будет добавлено позже +#### Сервисы: + +- `Storage`: сервис, который является, по сути, базой данных в оперативной памяти. Его задачей является хранение имен пользователей и идентификаторов каналов, а также самоочищаться раз в один час. Предоставляет публичные методы для добавления/удаления пользователей в базу, для проверки, есть ли пользователь в базе и для удаления канала соединения при его разрыве. + +#### Контроллеры: + +- `Whitelist`: контроллер, задачей которого является управление списком пользователей, которым разрешен доступ к запросу бендвича. Данный контроллер предоставляет два публичных метода класса: на бан пользователя и на проверку доступа по его имени и его идентификатору канала (`channelId`, `cid`). Когда запрашивается проверка доступа, сначала контроллер ищет пользователя в базе данных в памяти (storage service), и если он там не найден, то в базе данных монго, а если и там пользователь не найден, то контроллер делает запрос в сервис регистрации. Если пользователь найден в сервисе регистрации, то он добавляется и в монго и в базу данных в памяти, если нет, то доступ запрещается. Если пользователь забанен (что выясняется на этапе запроса к монго), то доступ запрещается. Когда запрашивается бан пользователя, то в базе данных пользователю присваивается флаг "забанен" и он удаляется из базы данных в памяти. + +- `Bandwidth Provider`: контроллер, который предоставляет один публичный метод: предоставления бендвича. Этот метод принимает транзакцию и проверяет, есть ли в ней запрос на предоставление бендвича и совпадает ли имя пользователя, который должен предоставить бендвич с пользователем, от имени которого запущен микросервис, а также, разрешено ли пользователю запрашивать бендвич. Если транзакцию не требуется подписывать, то она передается в блокчейн. Если требуется, и у пользователя есть право это запросить, то транзакция подписывается и передается в блокчейн. + +Микросервис реализует 3 метода: + +- 'bandwidth.provide': метод, который принимает транзакцию, для которой требуется предоставить бендвич, подписывает и отправляет ее в блокчейн. Метод возвращает ответ от блокчейна. +- 'bandwidth.banUser': метод, который принимает имя пользователя, которого требуется исключить пользователя из списка пользователей, которым разрешено получение бендвича (вайтлист). Возвращает статус исполнения запроса. +- 'bandwidth.notifyOffline': метод, который принимает имя пользователя, статус которого поменялся на "оффлайн", а также его `channelId` и исключает его из вайтлиста, который хранится в оперативной памяти (но не из базы данных). ##### API JSON-RPC -Будет добавлено позже +- `bandwidth.provide`: принимает параметры вида `{transaction, chainId}`, возвращает ответ от блокчейна либо ошибку +- `bandwidth.banUser`: принимает параметры вида `{user, channelId}`, возвращает статус либо ошибку +- `bandwidth.notifyOffline`: принимает параметры вида `{user, channelId}`, возвращает статус либо ошибку ### Переменные окружения Возможные переменные окружения `ENV`: -- `GLS_CONNECTOR_HOST` _(обязательно)_ - адрес, который будет использован для входящих подключений связи микросервисов. +- `GLS_CONNECTOR_HOST` _(обязательно)_ - адрес, который будет использован для входящих подключений связи микросервисов. Дефолтное значение при запуске без докера - `127.0.0.1` -- `GLS_CONNECTOR_PORT` _(обязательно)_ - адрес порта, который будет использован для входящих подключений связи микросервисов. +- `GLS_CONNECTOR_PORT` _(обязательно)_ - адрес порта, который будет использован для входящих подключений связи микросервисов. Дефолтное значение при запуске без докера - `3000`, пересекается с `GLS_FRONTEND_GATE_PORT` -- `GLS_METRICS_HOST` _(обязательно)_ - адрес хоста для метрик StatsD. +- `GLS_METRICS_HOST` _(обязательно)_ - адрес хоста для метрик StatsD. Дефолтное значение при запуске без докера - `127.0.0.1` -- `GLS_METRICS_PORT` _(обязательно)_ - адрес порта для метрик StatsD. - Дефолтное значение при запуске без докера - `8125` +- `GLS_METRICS_PORT` _(обязательно)_ - адрес порта для метрик StatsD. Дефолтное значение при запуске без докера - `8125` + +- `GLS_MONGO_CONNECT` _(обязательно)_ - ссылка на подключение к MongoDB + +- `CMN_CYBERWAY_HTTP_URL` _(обязательно)_ - ссылка на подключение к блокчейну Cyberway + +- `CMN_PROVIDER_PUBLIC_KEY` _(обязательно)_ - публичный ключ провайдера бендвича + +- `CMN_PROVIDER_WIF` _(обязательно)_ - приватный ключ провайдера бендвича + +- `CMN_PROVIDER_USERNAME` _(обязательно)_ - имя пользователя провайдера бендвича + +- `CMN_REGISTRATION_CONNECT` _(обязательно)_ - ссылка на подключение к сервису регистрации + +- `CMN_CHANNEL_TTL` _(обязательно)_ - время жизни канала (время, которое будет хранится channelId в базе данных в памяти). **Важно:** _storage проверяет, не превышено ли это значение лишь раз в час, поэтому между тем, как данный лимит будет превышен, и тем, как канал будет фактически удален из памяти, может пройти существенное количество времени_ ### Запуск Для запуска достаточно вызвать команду `docker-compose up` в корне проекта, предварительно указав необходимые `ENV` переменные. + +``` + +``` diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index d87c51d..0000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,31 +0,0 @@ -version: '3.6' -services: - node: - container_name: bandwidth-provider-node - restart: always - build: - context: . - dockerfile: Dockerfile - networks: - - services-tier - ports: - - $GLS_CONNECTOR_HOST:$GLS_CONNECTOR_PORT:$GLS_CONNECTOR_PORT - env_file: - - .env - - mongo: - container_name: bandwidth_mongo - image: mongo - restart: always - volumes: - - badwidth_mongodb_vol:/data/db - ports: - - 127.0.0.1:27017:27017 - networks: - - services-tier - -networks: - services-tier: - -volumes: - badwidth_mongodb_vol: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f5b9a43 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,33 @@ +version: '3.6' +services: + node: + container_name: bandwidth-provider-node + restart: always + build: + context: . + dockerfile: Dockerfile + networks: + - services-tier + ports: + - $GLS_CONNECTOR_HOST:$GLS_CONNECTOR_PORT:$GLS_CONNECTOR_PORT + env_file: + - .env + depends_on: + - mongo + + mongo: + container_name: bandwidth_mongo + image: mongo + restart: always + volumes: + - badwidth_mongodb_vol:/data/db + ports: + - 127.0.0.1:27017:27017 + networks: + - services-tier + +networks: + services-tier: + +volumes: + badwidth_mongodb_vol: diff --git a/src/services/BandwidthProvider.js b/src/controllers/BandwidthProvider.js similarity index 77% rename from src/services/BandwidthProvider.js rename to src/controllers/BandwidthProvider.js index 7726595..d1b7613 100644 --- a/src/services/BandwidthProvider.js +++ b/src/controllers/BandwidthProvider.js @@ -1,9 +1,9 @@ -const { TextEncoder, TextDecoder } = require('text-encoding'); // node only; native TextEncoder/Decoder +const { TextEncoder, TextDecoder } = require('text-encoding'); const core = require('gls-core-service'); -const fetch = require('node-fetch'); // node only; not needed in browsers +const fetch = require('node-fetch'); const { JsonRpc, Api } = require('cyberwayjs'); const JsSignatureProvider = require('cyberwayjs/dist/eosjs-jssig').default; -const BasicService = core.services.Basic; +const BasicController = core.controllers.Basic; const env = require('../data/env'); const { CMN_PROVIDER_WIF, @@ -24,11 +24,11 @@ const api = new Api({ textEncoder: new TextEncoder(), }); -class BandwidthProvider extends BasicService { +class BandwidthProvider extends BasicController { constructor({ whitelist }) { super(); - this.whitelist = whitelist; + this._whitelist = whitelist; } start() { @@ -65,22 +65,11 @@ class BandwidthProvider extends BasicService { auth: { user }, params: { transaction, chainId }, }) { - const isAllowed = await this.whitelist.isAllowed({ channelId, user }); - - if (!isAllowed) { - throw { - code: 1103, - message: 'This user is not allowed to require bandwidth', - }; - } - - const serializedTransactionBuffer = Uint8Array.from(transaction.serializedTransaction); - transaction.serializedTransaction = serializedTransactionBuffer; + transaction.serializedTransaction = Uint8Array.from(transaction.serializedTransaction); const deserializedTransaction = await api.deserializeTransactionWithActions( transaction.serializedTransaction ); - const shouldProvideBandwidth = deserializedTransaction.actions.find(action => { return action.name === 'providebw' && action.data.provider === CMN_PROVIDER_USERNAME; }); @@ -88,6 +77,15 @@ class BandwidthProvider extends BasicService { let transactionToSend = transaction; if (shouldProvideBandwidth) { + const isAllowed = await this._whitelist.isAllowed({ channelId, user }); + + if (!isAllowed) { + throw { + code: 1103, + message: 'This user is not allowed to require bandwidth', + }; + } + transactionToSend = await this._signTransaction({ transaction, chainId }); } diff --git a/src/data/env.js b/src/data/env.js index ac6a974..45241a6 100644 --- a/src/data/env.js +++ b/src/data/env.js @@ -17,6 +17,7 @@ module.exports = { CMN_PROVIDER_WIF: env.CMN_PROVIDER_WIF, CMN_PROVIDER_USERNAME: env.CMN_PROVIDER_USERNAME, CMN_PROVIDER_PUBLIC_KEY: env.CMN_PROVIDER_PUBLIC_KEY, - GLS_CHANNEL_TTL: env.GLS_CHANNEL_TTL || 1000, + CMN_CHANNEL_TTL: env.CMN_CHANNEL_TTL || 1000, CMN_CYBERWAY_HTTP_URL: env.CMN_CYBERWAY_HTTP_URL, + CMN_REGISTRATION_CONNECT: env.CMN_REGISTRATION_CONNECT, }; diff --git a/src/services/Connector.js b/src/services/Connector.js index d85ed00..4dd5f9c 100644 --- a/src/services/Connector.js +++ b/src/services/Connector.js @@ -1,13 +1,15 @@ const core = require('gls-core-service'); const BasicConnector = core.services.Connector; -const BandwidthProvider = require('./BandwidthProvider'); +const BandwidthProvider = require('../controllers/BandwidthProvider'); const StorageService = require('./StorageService'); const Whitelist = require('../controllers/Whitelist'); +const env = require('../data/env'); class Connector extends BasicConnector { constructor() { super(); this._storageService = new StorageService(); + this.addNested(this._storageService); this._whitelistController = new Whitelist({ connector: this, storage: this._storageService, @@ -18,20 +20,21 @@ class Connector extends BasicConnector { } async start() { - const storage = this._storageService; const provider = this._bandwidthProvider; const whitelist = this._whitelistController; - await provider.start(); - await storage.start(); - await super.start({ serverRoutes: { 'bandwidth.provide': provider.provideBandwidth.bind(provider), 'bandwidth.banUser': whitelist.banUser.bind(whitelist), 'bandwidth.notifyOffline': whitelist.handleOffline.bind(whitelist), }, + requiredClients: { + registration: env.CMN_REGISTRATION_CONNECT, + }, }); + + await this.startNested(); } } diff --git a/src/services/StorageService.js b/src/services/StorageService.js index 938c43f..beb0432 100644 --- a/src/services/StorageService.js +++ b/src/services/StorageService.js @@ -1,7 +1,7 @@ const core = require('gls-core-service'); const BasicService = core.services.Basic; const env = require('../data/env'); -const { GLS_CHANNEL_TTL } = env; +const { CMN_CHANNEL_TTL } = env; class Storage extends BasicService { constructor() { @@ -21,7 +21,7 @@ class Storage extends BasicService { _cleanup() { const now = Date.now(); for ([channelId, lastRequestDate] of this._timeoutMap) { - const shouldBeDeleted = now - lastRequestDate >= GLS_CHANNEL_TTL; + const shouldBeDeleted = now - lastRequestDate >= CMN_CHANNEL_TTL; if (shouldBeDeleted) { this._timeoutMap.delete(channelId);