From 3f28a02067bdbfaad5668415c984c43ace64ae3f Mon Sep 17 00:00:00 2001
From: IvanBlacky <tcherivan@gmail.com>
Date: Thu, 7 Feb 2019 18:11:05 +0300
Subject: [PATCH] #3 Feat: implement pandwidth provider logic

---
 .gitignore                        |   1 +
 package.json                      |   7 +-
 src/data/env.js                   |  17 ++++-
 src/services/BandwidthProvider.js | 111 +++++++++++++++++-------------
 4 files changed, 87 insertions(+), 49 deletions(-)

diff --git a/.gitignore b/.gitignore
index 88f30ac..2262655 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@ db
 .env
 .vscode
 test.js
+.npmrc
diff --git a/package.json b/package.json
index cf38b96..6db86b7 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,12 @@
   "author": "golos.io",
   "license": "GPL-3.0",
   "dependencies": {
-    "gls-core-service": "^2.16.1"
+    "@types/node-fetch": "^2.1.4",
+    "@types/text-encoding": "0.0.35",
+    "cyberwayjs": "^20.0.0-beta3.4",
+    "gls-core-service": "^2.16.1",
+    "node-fetch": "^2.3.0",
+    "text-encoding": "^0.7.0"
   },
   "prettier": {
     "singleQuote": true,
diff --git a/src/data/env.js b/src/data/env.js
index 0b45f39..ac6a974 100644
--- a/src/data/env.js
+++ b/src/data/env.js
@@ -1,9 +1,22 @@
 const env = process.env;
 
+if (!env.CMN_PROVIDER_WIF) {
+    throw new Error('Env variable CMN_PROVIDER_WIF is required!');
+}
+
+if (!env.CMN_PROVIDER_USERNAME) {
+    throw new Error('Env variable CMN_PROVIDER_USERNAME is required!');
+}
+
+if (!env.CMN_CYBERWAY_HTTP_URL) {
+    throw new Error('Env variable CMN_CYBERWAY_HTTP_URL is required!');
+}
 module.exports = {
     GLS_CONNECTOR_HOST: env.GLS_CONNECTOR_HOST || '127.0.0.0',
     GLS_CONNECTOR_PORT: env.GLS_CONNECTOR_PORT || 3000,
-    GLS_PROVIDER_WIF: env.GLS_PROVIDER_WIF,
-    GLS_PROVIDER_USERNAME: env.GLS_PROVIDER_USERNAME,
+    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_CYBERWAY_HTTP_URL: env.CMN_CYBERWAY_HTTP_URL,
 };
diff --git a/src/services/BandwidthProvider.js b/src/services/BandwidthProvider.js
index 962dc19..7726595 100644
--- a/src/services/BandwidthProvider.js
+++ b/src/services/BandwidthProvider.js
@@ -1,55 +1,70 @@
+const { TextEncoder, TextDecoder } = require('text-encoding'); // node only; native TextEncoder/Decoder
 const core = require('gls-core-service');
+const fetch = require('node-fetch'); // node only; not needed in browsers
+const { JsonRpc, Api } = require('cyberwayjs');
+const JsSignatureProvider = require('cyberwayjs/dist/eosjs-jssig').default;
 const BasicService = core.services.Basic;
 const env = require('../data/env');
-const { GLS_PROVIDER_WIF, GLS_PROVIDER_USERNAME } = env;
+const {
+    CMN_PROVIDER_WIF,
+    CMN_PROVIDER_PUBLIC_KEY,
+    CMN_PROVIDER_USERNAME,
+    CMN_CYBERWAY_HTTP_URL,
+} = env;
+
+const rpc = new JsonRpc(CMN_CYBERWAY_HTTP_URL, { fetch });
+
+const requiredKeys = [CMN_PROVIDER_PUBLIC_KEY];
+const signatureProviderBP = new JsSignatureProvider([CMN_PROVIDER_WIF]);
+
+const api = new Api({
+    rpc,
+    signatureProviderBP,
+    textDecoder: new TextDecoder(),
+    textEncoder: new TextEncoder(),
+});
 
 class BandwidthProvider extends BasicService {
     constructor({ whitelist }) {
         super();
 
         this.whitelist = whitelist;
-
-        // we cannot use the service until it's authorized in blockchain
-        this._authorized = false;
-
-        // these are null by default
-        this._sign = null;
-        this._secret = null;
     }
 
-    async start() {
-        await this.authorize();
+    start() {
+        // do nothing, just override default
     }
 
-    serviceReady() {
-        // service is ready when and only then there it is authorized and has not-null secret and sign
-        return this._authorized && this._secret && this._sign;
-    }
+    async _signTransaction({ transaction, chainId }) {
+        const transactionBW = await signatureProviderBP.sign({
+            chainId,
+            requiredKeys,
+            serializedTransaction: transaction.serializedTransaction,
+        });
 
-    async authorize() {
-        try {
-            // first call `auth.generateSecret`
-            const secret = 'secret';
-            // store the given secret
-            this._secret = secret;
-            // secondly, sign the test vote transaction with the secret as a permlink and a user as a voter and the active key
-            // store the xsign
-            const xsign = 'sign';
-            this._sign = xsign;
-            // send `auth.authorize` request with a secret as a secret, xsign as a sign and user from env as a user
-
-            this._authorized = true;
-        } catch (error) {
-            console.error(error);
-            process.exit(1);
-        }
+        const transactionBoth = {
+            ...transaction,
+            signatures: [...transaction.signatures, ...transactionBW.signatures],
+            serializedTransaction: transaction.serializedTransaction,
+        };
+
+        const { signatures, serializedTransaction } = transactionBoth;
+
+        return { signatures, serializedTransaction };
     }
 
-    async provideBandwidth({ routing: { channelId }, auth: { user }, params: { transaction } }) {
-        if (!this.serviceReady()) {
-            await this.authorize();
-        }
+    async _sendTransaction({ signatures, serializedTransaction }) {
+        return await api.pushSignedTransaction({
+            signatures,
+            serializedTransaction,
+        });
+    }
 
+    async provideBandwidth({
+        routing: { channelId },
+        auth: { user },
+        params: { transaction, chainId },
+    }) {
         const isAllowed = await this.whitelist.isAllowed({ channelId, user });
 
         if (!isAllowed) {
@@ -59,20 +74,24 @@ class BandwidthProvider extends BasicService {
             };
         }
 
-        /*
-        Контракт: eosio
-Действие: providebw
-Аргументы: (name provider, name account):
-* provider - аккаунт, который предоставляет свой бендвич для выполнения транзакции
-* account - аккаунт пользователя, которому предоставляется бендвич
+        const serializedTransactionBuffer = Uint8Array.from(transaction.serializedTransaction);
+        transaction.serializedTransaction = serializedTransactionBuffer;
 
-Выполняемое действие:
-Предоставить бендвич для выполнения действий другому пользователю. При включении данного действия в транзакцию, CPU/NET бендвич за действия в данной транзакции будут списаны с аккаунты provider вместо их списания с аккаунта account.
+        const deserializedTransaction = await api.deserializeTransactionWithActions(
+            transaction.serializedTransaction
+        );
 
-Данная операция требует разрешения от пользователя provider.
+        const shouldProvideBandwidth = deserializedTransaction.actions.find(action => {
+            return action.name === 'providebw' && action.data.provider === CMN_PROVIDER_USERNAME;
+        });
+
+        let transactionToSend = transaction;
+
+        if (shouldProvideBandwidth) {
+            transactionToSend = await this._signTransaction({ transaction, chainId });
+        }
 
-Данная операция сейчас доступна на тестнете cyberway, но пока не запущен системный смарт-контракт ее действие невозможно увидеть, так как выключен подсчет бендвича.
-        */
+        return await this._sendTransaction(transactionToSend);
     }
 }